• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#|
2Copyright 2019 Dmitry Arkhipov
3Distributed under the Boost Software License, Version 1.0. (See
4accompanying file LICENSE_1_0.txt or copy at
5http://www.boost.org/LICENSE_1_0.txt)
6|#
7
8
9import "class" : new ;
10import common ;
11import errors ;
12import feature ;
13import os ;
14import param ;
15import project ;
16import regex ;
17import sequence ;
18import string ;
19import targets ;
20
21
22#| tag::doc[]
23
24= pkg-config
25The *pkg-config* program is used to retrieve information about installed
26libraries in the system. It retrieves information about packages from special
27metadata files. These files are named after the package, and have a `.pc`
28extension. The package name specified to *pkg-config* is defined to be the name
29of the metadata file, minus the `.pc` extension.
30
31|# # end::doc[]
32
33
34#| tag::doc[]
35
36== Feature: `pkg-config`
37
38Selects one of the initialized `pkg-config` configurations. This feature is
39`propagated` to dependencies. Its use is dicussed in
40section <<pkg-config-init>>.
41
42|# # end::doc[]
43
44feature.feature pkg-config : : propagated ;
45
46
47#| tag::doc[]
48
49== Feature: `pkg-config-define`
50
51This `free` feature adds a variable assignment to pkg-config invocation. For
52example,
53
54[source, jam]
55----
56pkg-config.import mypackage : requirements <pkg-config-define>key=value ;
57----
58
59is equivalent to invoking on the comand line
60
61[source, shell]
62----
63pkg-config --define-variable=key=value mypackage ;
64----
65
66|# # end::doc[]
67
68feature.feature pkg-config-define : : free ;
69
70
71#| tag::doc[]
72
73== Rule: `import`
74
75Main target rule that imports a *pkg-config* package. When its consumer targets
76are built, *pkg-config* command will be invoked with arguments that depend on
77current property set. The features that have an effect are:
78
79* `<pkg-config-define>`: adds a `--define-variable` argument;
80* `<link>`: adds `--static` argument when `<link>static`;
81* `<link>`: adds `--static` argument when `<link>static`;
82* `<name>`: specifies package name (target name is used instead if the property
83    is not present);
84* `<version>`: specifies package version range, can be used multiple times and
85    should be a dot-separated sequence of numbers optionally prefixed with `=`,
86    `<`, `>`, `<=` or `>=`.
87
88Example:
89
90[source, jam]
91----
92pkg-config.import my-package
93    : requirements <name>my_package <version><4 <version>>=3.1 ;
94----
95
96|# # end::doc[]
97
98
99rule import
100    ( target-name
101    : sources *
102    : requirements *
103    : default-build *
104    : usage-requirements *
105    )
106{
107    param.handle-named-params
108        sources requirements default-build usage-requirements ;
109    targets.create-metatarget pkg-config-target
110        : [ project.current ]
111        : $(target-name)
112        : $(sources)
113        : $(requirements)
114        : $(default-build)
115        : $(usage-requirements)
116        ;
117}
118
119
120#| tag::doc[]
121
122== Initialization [[ pkg-config-init ]]
123
124To use the `pkg-config` tool you need to declare it in a configuration file
125with the `using` rule:
126
127[source, jam]
128----
129using pkg-config : [config] : [command] ... : [ options ] ... ;
130----
131
132
133* `config`: the name of initialized configuration. The name can be omitted, in
134    which case the configuration will become the default one.
135* `command`: the command, with any extra arguments, to execute. If no command
136    is given, first `PKG_CONFIG` environment variable is checked, and if its
137    empty the string `pkg-config` is used.
138* `options`: options that modify `pkg-config` behavior. Allowed options are:
139    * `<path>`: sets `PKG_CONFIG_PATH` environment variable;
140        multiple occurences are allowed.
141    * `<libdir>`: sets `PKG_CONFIG_LIBDIR` environment variable;
142        multiple occurences are allowed.
143    * `<allow-system-cflags>`: sets `PKG_CONFIG_ALLOW_SYSTEM_CFLAGS`
144        environment variable; multiple occurences are allowed.
145    * `<allow-system-libs>`: sets `PKG_CONFIG_ALLOW_SYSTEM_LIBS`
146        environment variable; multiple occurences are allowed.
147    * `<sysroot>`: sets `PKG_CONFIG_SYSROOT_DIR` environment variable;
148        multiple occurences are allowed.
149    * `<variable>`: adds a variable definition argument to command invocation;
150        multiple occurences are allowed.
151
152|# # end::doc[]
153
154rule init ( config ? : command * : options * )
155{
156    config ?= [ default-config ] ;
157
158    local tool = [ os.environ PKG_CONFIG ] ;
159    tool ?= pkg-config ;
160    command =
161        [ common.get-invocation-command pkg-config : $(tool) : $(command) ] ;
162
163    configure $(config) : $(command) : $(options) ;
164    $(.configs).use $(config) ;
165}
166
167
168rule run ( config ? : args * )
169{
170    config ?= [ default-config ] ;
171
172    local command = [ $(.configs).get $(config) : command ] ;
173    command = "$(command) $(args:J= )" ;
174
175    local output = [ SHELL "$(command)" : exit-status ] ;
176    if 0 != $(output[2])
177    {
178      errors.error "pkg-config: command '$(command)' resulted in error:"
179          [ common.newline-char ] $(output[1]) ;
180    }
181
182    local ws = [ string.whitespace ] ;
183    output = [ regex.split $(output[1]) "[$(ws)]" ] ;
184    return [ sequence.filter non-empty : $(output) ] ;
185}
186
187
188#| tag::doc[]
189
190== Class `pkg-config-target`
191
192[source, jam]
193----
194class pkg-config-target : alias-target-class {
195    rule construct ( name : sources * : property-set )
196    rule version ( property-set )
197    rule variable ( name : property-set )
198}
199----
200
201The class of objects returned by `import` rule. The objects themselves could be
202useful in situations that require more complicated logic for consuming a
203package. See <<pkg-config-tips>> for examples.
204
205. `rule construct ( name : sources * : property-set )`
206  Overrides `alias-target.construct`.
207
208. `rule version ( property-set )`
209  Returns the package's version, in the context of `property-set`.
210
211. `rule variable ( name : property-set )`
212  Returns the value of variable `name` in the package, in the context of
213  `property-set`.
214
215
216|# # end::doc[]
217
218class pkg-config-target : alias-target-class
219{
220    import pkg-config ;
221    import regex ;
222
223    rule construct ( name : sources * : property-set )
224    {
225        local config = [ $(property-set).get <pkg-config> ] ;
226        local args = [ common-arguments $(name) : $(property-set) ] ;
227        return
228            [ property-set.create
229              [ compile-flags $(config) $(property-set) : $(args) ]
230              [ link-flags $(config) $(property-set) : $(args) ]
231            ] ;
232    }
233
234    rule version ( property-set )
235    {
236        local config = [ $(property-set).get <pkg-config> ] ;
237        local args = [ common-arguments [ name ] : $(property-set) ] ;
238        local version = [ pkg-config.run $(config) : --modversion $(args) ] ;
239        return [ regex.split $(version) "\\." ] ;
240    }
241
242    rule variable ( name : property-set )
243    {
244        local config = [ $(property-set).get <pkg-config> ] ;
245        local args = [ common-arguments [ name ] : $(property-set) ] ;
246        return [ pkg-config.run $(config) : --variable=$(name) $(args) ] ;
247    }
248
249    local rule common-arguments ( name : property-set )
250    {
251        local defines = [ $(property-set).get <pkg-config-define> ] ;
252        local args = --define-variable=$(defines) ;
253        if [ $(property-set).get <link> ] = static
254        {
255            args += --static ;
256        }
257        return $(args) [ get-package-request $(property-set) $(name) ] ;
258    }
259
260    local rule get-package-request ( property-set name )
261    {
262        local pkg-name = [ $(property-set).get <name> ] ;
263        pkg-name ?= $(name) ;
264        if $(pkg-name[2])
265        {
266            errors.error "multiple package names were specified for target "
267                "'$(name)': $(pkg-name)" ;
268        }
269
270        local versions ;
271        for local version in [ $(property-set).get <version> ]
272        {
273            local match = [ MATCH "^(<=)(.*)" : $(version) ] ;
274            match ?= [ MATCH "^(>=)(.*)" : $(version) ] ;
275            match ?= [ MATCH "^([><=])(.*)" : $(version) ] ;
276            if $(match)
277            {
278                version = " $(match:J= )" ;
279            }
280            else
281            {
282                version = " = $(version)" ;
283            }
284            versions += $(version) ;
285        }
286        versions ?= "" ;
287
288        return "'$(pkg-name)"$(versions)"'" ;
289    }
290
291    local rule link-flags ( config property-set : args * )
292    {
293        local flags = [ pkg-config.run $(config) : --libs $(args) ] ;
294        return <linkflags>$(flags) ;
295    }
296
297    local rule compile-flags ( config property-set : args * )
298    {
299        local flags = [ pkg-config.run $(config) : --cflags $(args) ] ;
300        return <cflags>$(flags) ;
301    }
302}
303
304
305local rule default-config ( )
306{
307    return default ;
308}
309
310
311local rule configure ( config : command + : options * )
312{
313    $(.configs).register $(config) ;
314
315    local path ;
316    local libdir ;
317    local allow-system-cflags ;
318    local allow-system-libs ;
319    local sysroot ;
320    local defines ;
321    for local opt in $(options)
322    {
323        switch $(opt:G)
324        {
325            case <path> : path += $(opt:G=) ;
326            case <libdir> : libdir += $(opt:G=) ;
327            case <allow-system-cflags> : allow-system-cflags += $(opt:G=) ;
328            case <allow-system-libs> : allow-system-libs += $(opt:G=) ;
329            case <sysroot> : sysroot += $(opt:G=) ;
330            case <variable> : defines += $(opt:G=) ;
331            case * :
332                errors.error "pkg-config: invalid property '$(opt)' was "
333                    "specified for configuration '$(config)'." ;
334        }
335    }
336
337    for local opt in allow-system-cflags allow-system-libs
338    {
339        if ! $($(opt)) in "on" off
340        {
341            errors.error "pkg-config: invalid value '$($(opt))' was specified "
342                "for option <$(opt)> of configuration '$(config)'."
343                [ common.newline-char ] "Available values are 'on' and 'off'" ;
344        }
345    }
346
347    if $(sysroot[2])
348    {
349        errors.error "pkg-config: several values were specified for option "
350            "<sysroot> of configuration '$(config)'."
351            [ common.newline-char ] "Only one value is allowed." ;
352    }
353
354    local sep = [ os.path-separator ] ;
355    path = [ envar-set-command PKG_CONFIG_PATH : $(path:J=$(sep)) ] ;
356    libdir = [ envar-set-command PKG_CONFIG_LIBDIR : $(libdir:J=$(sep)) ] ;
357    sysroot = [ envar-set-command PKG_CONFIG_SYSROOT_DIR : $(sysroot) ] ;
358    allow-cflags =
359        [ envar-set-command PKG_CONFIG_ALLOW_SYSTEM_CFLAGS
360        : $(allow-cflags)
361        : 1
362        ] ;
363    allow-libs =
364        [ envar-set-command PKG_CONFIG_ALLOW_SYSTEM_LIBS
365        : $(allow-libs)
366        : 1
367        ] ;
368
369    command += --print-errors --errors-to-stdout --define-variable=$(defines) ;
370    $(.configs).set $(config)
371        : command
372        : "$(path)$(libdir)$(sysroot)$(allow-cflags)$(allow-libs)$(command:J= )"
373        ;
374
375    feature.extend pkg-config : $(config) ;
376}
377
378
379local rule envar-set-command ( envar : value * : implied-value * )
380{
381    if $(value)
382    {
383        if $(implied-value)
384        {
385            value = $(implied-value) ;
386        }
387        return [ common.path-variable-setting-command $(envar) : $(value) ] ;
388    }
389    else
390    {
391        return "" ;
392    }
393}
394
395
396local rule non-empty ( string )
397{
398    if $(string) != "" { return true ; }
399}
400
401
402.configs = [ new configurations ] ;
403
404
405#| tag::doc[]
406
407== Tips [[pkg-config-tips]]
408
409
410=== Using several configurations
411
412Suppose, you have 2 collections of `.pc` files: one for platform A, and another
413for platform B. You can initialize 2 configurations of `pkg-config` tool each
414corresponding to specific collection:
415
416[source, jam]
417----
418using pkg-config : A : : <libdir>path/to/collection/A ;
419using pkg-config : B : : <libdir>path/to/collection/B ;
420----
421
422Then, you can specify that builds for platform A should use configuration A,
423while builds for B should use configuration B:
424
425[source, jam]
426----
427project
428    : requirements
429      <target-os>A-os,<architecture>A-arch:<pkg-config>A
430      <target-os>B-os,<architecture>B-arch:<pkg-config>B
431    ;
432----
433
434Thanks to the fact, that `project-config`, `user-config` and `site-config`
435modules are parents of jamroot module, you can put it in any of those files.o
436
437
438=== Choosing the package name based on the property set
439
440Since a file for a package should be named after the package suffixed with
441`.pc`, some projects came up with naming schemes in order to allow simultaneous
442installation of several major versions or build variants. In order to pick the
443specific name corresponding to the build request you can use `<conditional>`
444property in requirements:
445
446[source, jam]
447----
448pkg-config.import mypackage : requirements <conditional>@infer-name ;
449
450rule infer-name ( properties * )
451{
452    local name = mypackage ;
453    local variant = [ property.select <variant> : $(properties) ] ;
454    if $(variant) = debug
455    {
456      name += -d ;
457    }
458    return <name>$(name) ;
459}
460----
461
462The `common.format-name` rule can be very useful in this situation.
463
464
465=== Modify usage requirements based on package version or variable
466
467Sometimes you need to apply some logic based on package's version or a
468variable that it defines. For that you can use `<conditional>` property in
469usage requirements:
470
471----
472mypackage =
473  [ pkg-config.import mypackage : usage-requirements <conditional>@define_ns
474  ] ;
475
476rule extra-props ( properties * )
477{
478    local ps = [ property-set.create $(properties) ] ;
479    local prefix = [ $(mypackage).variable name_prefix : $(ps) ] ;
480    prefix += [ $(mypackage).version $(ps) ] ;
481    return <define>$(prefix:J=_) ;
482}
483----
484
485|# # end::doc[]
486