v2 / vlib / v / pkgconfig / pkgconfig.v
314 lines · 294 sloc · 7.51 KB · 343f94ca915b34f80bd05e77a95354a3898ae9a2
Raw
1module pkgconfig
2
3import semver
4import os
5
6const version = '0.3.4'
7
8const default_paths = [
9 '/usr/local/lib/x86_64-linux-gnu/pkgconfig',
10 '/usr/local/lib64/pkgconfig',
11 '/usr/local/lib/pkgconfig',
12 '/usr/local/share/pkgconfig',
13 '/usr/lib/x86_64-linux-gnu/pkgconfig',
14 '/usr/lib/aarch64-linux-gnu/pkgconfig',
15 '/usr/lib64/pkgconfig',
16 '/usr/lib/pkgconfig',
17 '/usr/share/pkgconfig',
18 '/opt/homebrew/lib/pkgconfig', // Brew on macOS
19 '/opt/homebrew/share/pkgconfig', // Brew on macOS. Needed for fish.pc, eigen3.pc, applewmproto.pc, fontsproto.pc, xextproto.pc, SPIRV-Headers.pc etc; seems like a legacy folder.
20 '/opt/homebrew/Library/Homebrew/os/mac/pkgconfig/11', // Brew on macOS. Needed for zlib.pc, libcurl.pc, expat.pc and a few others; all the rest are symlinked in /opt/homebrew/lib/pkgconfig .
21 '/opt/local/lib/pkgconfig', // MacPorts on macOS
22 '/usr/local/libdata/pkgconfig', // FreeBSD
23 '/usr/libdata/pkgconfig', // FreeBSD
24 '/usr/lib/i386-linux-gnu/pkgconfig', // Debian 32bit
25 '/data/data/com.termux/files/usr/lib/pkgconfig', // Termux
26 '/usr/pkg/lib/pkgconfig', // NetBSD
27]
28
29pub struct Options {
30pub:
31 path string
32 debug bool
33 norecurse bool
34 only_description bool
35 use_default_paths bool = true
36}
37
38pub struct PkgConfig {
39pub mut:
40 file_path string
41 options Options
42 name string
43 modname string
44 url string
45 version string
46 description string
47 libs []string
48 libs_private []string
49 cflags []string
50 paths []string // TODO: move to options?
51 vars map[string]string
52 requires []string
53 requires_private []string
54 conflicts []string
55 loaded []string
56}
57
58fn (mut pc PkgConfig) parse_list_no_comma(s string) []string {
59 return pc.parse_list(s.replace(',', ' '))
60}
61
62fn (mut pc PkgConfig) parse_list(s string) []string {
63 operators := ['=', '<', '>', '>=', '<=']
64 r := pc.parse_line(s.replace(' ', ' ').replace(', ', ' ')).split(' ')
65 mut res := []string{}
66 mut skip := false
67 for a in r {
68 b := a.trim_space()
69 if skip {
70 skip = false
71 } else if b in operators {
72 skip = true
73 } else if b != '' {
74 res << b
75 }
76 }
77 return res
78}
79
80fn (mut pc PkgConfig) parse_line(s string) string {
81 mut r := s.split('#')[0]
82 for r.contains('\${') {
83 tok0 := r.index('\${') or { break }
84 mut tok1 := r[tok0..].index('}') or { break }
85 tok1 += tok0
86 v := r[tok0 + 2..tok1]
87 r = r.replace('\${${v}}', pc.vars[v])
88 }
89 return r.trim_space()
90}
91
92fn (mut pc PkgConfig) setvar(line string) {
93 kv := line.trim_space().split('=')
94 if kv.len == 2 {
95 k := kv[0]
96 v := pc.parse_line(kv[1])
97 pc.vars[k] = pc.parse_line(v)
98 }
99}
100
101fn (mut pc PkgConfig) parse(file string) bool {
102 pc.file_path = file
103 pc.vars['pcfiledir'] = os.real_path(os.dir(file))
104 data := os.read_file(file) or { return false }
105 if pc.options.debug {
106 eprintln(data)
107 }
108 lines := data.split('\n')
109 if pc.options.only_description {
110 // 2x faster than original pkg-config for --list-all --description
111 for line in lines {
112 if line.starts_with('Description: ') {
113 pc.description = pc.parse_line(line[13..])
114 }
115 }
116 } else {
117 for oline in lines {
118 line := oline.trim_space()
119 if line.starts_with('#') {
120 continue
121 }
122 if line.contains('=') && !line.contains(' ') {
123 pc.setvar(line)
124 continue
125 }
126 if line.starts_with('Name:') {
127 pc.name = pc.parse_line(line[5..])
128 } else if line.starts_with('Description:') {
129 pc.description = pc.parse_line(line[12..])
130 } else if line.starts_with('Version:') {
131 pc.version = pc.parse_line(line[8..])
132 } else if line.starts_with('Requires:') {
133 pc.requires = pc.parse_list_no_comma(line[9..])
134 } else if line.starts_with('Requires.private:') {
135 pc.requires_private = pc.parse_list_no_comma(line[17..])
136 } else if line.starts_with('Conflicts:') {
137 pc.conflicts = pc.parse_list_no_comma(line[10..])
138 } else if line.starts_with('Cflags:') {
139 pc.cflags = pc.parse_list(line[7..])
140 } else if line.starts_with('Libs:') {
141 pc.libs = pc.parse_list(line[5..])
142 } else if line.starts_with('Libs.private:') {
143 pc.libs_private = pc.parse_list(line[13..])
144 } else if line.starts_with('URL:') {
145 pc.url = pc.parse_line(line[4..])
146 }
147 }
148 }
149 return true
150}
151
152fn (mut pc PkgConfig) resolve(pkgname string) !string {
153 if pkgname.ends_with('.pc') {
154 if os.exists(pkgname) {
155 return pkgname
156 }
157 } else {
158 if pc.paths.len == 0 {
159 pc.paths << '.'
160 }
161 for path in pc.paths {
162 file := '${path}/${pkgname}.pc'
163 if os.exists(file) {
164 return file
165 }
166 }
167 }
168 return error('Cannot find "${pkgname}" pkgconfig file')
169}
170
171pub fn atleast(v string) bool {
172 v0 := semver.from(version) or { return false }
173 v1 := semver.from(v) or { return false }
174 return v0 > v1
175}
176
177pub fn (mut pc PkgConfig) atleast(v string) bool {
178 v0 := semver.from(pc.version) or { return false }
179 v1 := semver.from(v) or { return false }
180 return v0 > v1
181}
182
183pub fn (mut pc PkgConfig) extend(pcdep &PkgConfig) string {
184 for flag in pcdep.cflags {
185 if pc.cflags.index(flag) == -1 {
186 pc.cflags << flag
187 }
188 }
189 for lib in pcdep.libs {
190 if pc.libs.index(lib) == -1 {
191 pc.libs << lib
192 }
193 }
194 for lib in pcdep.libs_private {
195 if pc.libs_private.index(lib) == -1 {
196 pc.libs_private << lib
197 }
198 }
199 return ''
200}
201
202fn (mut pc PkgConfig) load_requires() ! {
203 for dep in pc.requires {
204 pc.load_require(dep)!
205 }
206 for dep in pc.requires_private {
207 pc.load_require(dep)!
208 }
209}
210
211fn (mut pc PkgConfig) load_require(dep string) ! {
212 if dep in pc.loaded {
213 return
214 }
215 pc.loaded << dep
216 mut pcdep := PkgConfig{
217 paths: pc.paths
218 loaded: pc.loaded
219 }
220 depfile := pcdep.resolve(dep) or {
221 if pc.options.debug {
222 eprintln('cannot resolve ${dep}')
223 }
224 return error('could not resolve dependency ${dep}')
225 }
226 if !pcdep.parse(depfile) {
227 return error('required file "${depfile}" could not be parsed')
228 }
229 if !pc.options.norecurse {
230 pcdep.load_requires()!
231 }
232 pc.extend(pcdep)
233}
234
235fn (mut pc PkgConfig) add_path(path string) {
236 if path == '' {
237 return
238 }
239 p := path.trim_right('/')
240 if !os.exists(p) {
241 return
242 }
243 $if trace_pkgconfig_add_path ? {
244 eprintln('> PkgConfig.add_path path: ${p}')
245 }
246 if pc.paths.index(p) == -1 {
247 pc.paths << p
248 }
249}
250
251fn (mut pc PkgConfig) load_paths() {
252 // Allow for full custom user control over the default paths too, through
253 // setting `PKG_CONFIG_PATH_DEFAULTS` to a list of search paths, separated
254 // by `:`.
255 split_c := $if windows { ';' } $else { ':' }
256 config_path_override := os.getenv('PKG_CONFIG_PATH_DEFAULTS')
257 if config_path_override != '' {
258 for path in config_path_override.split(split_c) {
259 pc.add_path(path)
260 }
261 } else {
262 if pc.options.use_default_paths {
263 for path in default_paths {
264 pc.add_path(path)
265 }
266 }
267 }
268 for path in pc.options.path.split(split_c) {
269 pc.add_path(path)
270 }
271 env_var := os.getenv('PKG_CONFIG_PATH')
272 if env_var != '' {
273 env_paths := env_var.trim_space().split(split_c)
274 for path in env_paths {
275 pc.add_path(path)
276 }
277 }
278}
279
280pub fn load(pkgname string, options Options) !&PkgConfig {
281 mut pc := &PkgConfig{
282 modname: pkgname
283 options: options
284 }
285 pc.load_paths()
286 file := pc.resolve(pkgname) or { return err }
287 if !pc.parse(file) {
288 return error('file "${file}" could not be parsed')
289 }
290 if !options.norecurse {
291 pc.load_requires()!
292 }
293 return pc
294}
295
296pub fn list() []string {
297 mut pc := &PkgConfig{
298 options: Options{}
299 }
300 pc.load_paths()
301 mut modules := []string{}
302 for path in pc.paths {
303 files := os.ls(path) or { continue }
304 for file in files {
305 if file.ends_with('.pc') {
306 name := file.replace('.pc', '')
307 if modules.index(name) == -1 {
308 modules << name
309 }
310 }
311 }
312 }
313 return modules
314}
315