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