From 501f72a136be830bbd442c4d089717bb1a2a605a Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 24 Apr 2023 14:35:46 +0300 Subject: [PATCH 1/4] module for creation of toolset projects for libs --- doc/src/tools.adoc | 4 + src/build/project.jam | 3 + src/tools/external.jam | 526 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 533 insertions(+) create mode 100644 src/tools/external.jam diff --git a/doc/src/tools.adoc b/doc/src/tools.adoc index 006d4e57d2..e8ef811762 100644 --- a/doc/src/tools.adoc +++ b/doc/src/tools.adoc @@ -9,6 +9,10 @@ include::../../src/tools/asciidoctor.jam[tag=doc] == Miscellaneous Tools +:leveloffset: +2 +include::../../src/tools/external.jam[tag=doc] +:leveloffset: -2 + :leveloffset: +2 include::../../src/tools/pkg-config.jam[tag=doc] :leveloffset: -2 diff --git a/src/build/project.jam b/src/build/project.jam index 095a879a6d..c17e377c11 100644 --- a/src/build/project.jam +++ b/src/build/project.jam @@ -510,6 +510,8 @@ rule initialize ( ECHO "Initializing project '$(module-name)'" ; } + .init-stack = $(module-name) $(.init-stack) ; + local jamroot ; local parent-module ; @@ -642,6 +644,7 @@ rule initialize ( } .current-project = [ target $(module-name) ] ; + .init-stack = $(.init-stack[2-]) ; } diff --git a/src/tools/external.jam b/src/tools/external.jam new file mode 100644 index 0000000000..4e4dff5da5 --- /dev/null +++ b/src/tools/external.jam @@ -0,0 +1,526 @@ +#| +Copyright 2023 Dmitry Arkhipov (grisumbras@yandex.ru) +Distributed under the Boost Software License, Version 1.0. (See +accompanying file LICENSE.txt or copy at +https://www.bfgroup.xyz/b2/LICENSE.txt) +|# + + +import ac ; +import errors ; +import feature ; +import param ; +import path ; +import project ; +import property-set ; +import targets ; + +import "class" : new ; + +if --debug-configuration in [ modules.peek : ARGV ] +{ + .debug = true ; +} + +#| tag::doc[] + +[[bbv2.tools.external]] += external +The module allows simple declaration of targets for external dependencies. This +can be used to create <>, but +rules from this module can also be used directly. In the latter case, rules +`lib` and `alias` are intended for project authors (i.e. should be used in +jamfiles), while rule `configure`, is intended for project users (i.e. should +be used in config files). + +The module only creates projects if they do not exist yet, and only creates +targets inside projects it created. This allows seamless integration with +package managers, if certain care is taken. In particular, it has become a +convention to call the sole target in a header-only library project `libs`. + +[[bbv2.tools.external.configure]] +== Rule `configure` + +[source, jam] +---- +rule configure ( project : options * : condition * ) +---- + +The rule registers a configuration of `project` for the given `condition`. +Using free or incidental features in `condition` is not supported. + +Allowed options: + +* ``: the directory containing project's headers. +* ``: the directory containing project's library files. +* ``: should be in the form `LIBRARY=NAME`; overrides the searched name + for library `LIBRARY` with the name `NAME`. Multiple instances (even for + the same library) are allowed. + +The rule supports keyword arguments. + +Example: + +[source, jam] +---- +external.configure sqlite3 + : /opt/sqlite3/include /opt/sqlite3/lib + : static + ; +---- + +|# # end::doc[] +rule configure ( proj-name : options * : condition * ) +{ + param.handle-named-params options condition ; + + local ps = [ property-set.create $(condition) ] ; + local base = [ property-set.create [ $(ps).base ] ] ; + if $(ps) != $(base) + { + errors.user-error "Condition for external project '$(proj-name)'" + "contains free or incidental features:" [ $(ps).free ] + [ $(ps).incidental ] ; + } + + local configs = [ get-configs $(proj-name) ] ; + register-config $(proj-name) : $(configs) : $(options) : $(ps) ; +} + +#| tag::doc[] + +[[bbv2.tools.external.lib]] +== Rule `lib` + +[source, jam] +---- +rule lib ( target : sources * : options * ) +---- + +The rule declares a target for external searched library. `target` argument +should take the form `/PROJECT//LIBRARY`, which results in creation of target +`LIBRARY` in project `PROJECT`. The `sources` argument can be used to +represent transitive dependencies. + +Allowed options: + +* `
`: the header file (relative to include directory) that will be + used for include directory search. *This argument is required*. +* ``: {CPP} code that tests if the header can be included. This + option may become relevant, if the build system can't find the header's + file, but it can still be included (e.g. it is located in a compiler's + hard-coded include directory). If omitted, `#include
` is used, + where `HEADER` is the valiue of option `
`. +* ``: provides alternative names for the searched library; multiple + instances are allowed. If omitted, the target name will be used. + +The rule supports keyword arguments. + +Example: + +[source, jam] +---- +external.lib /sqlite3//sqlite3 : options
sqlite3.h ; +exe myapp : /sqlite3//sqlite3 main.cpp ; +---- + +|# # end::doc[] +rule lib ( tgt-id : sources * : options * ) +{ + param.handle-named-params sources options ; + declare-target $(tgt-id) : $(sources) : $(options) ; +} + +#| tag::doc[] + +[[bbv2.tools.external.alias]] +== Rule `alias` + +[source, jam] +---- +rule alias ( target : sources * : options * ) +---- + +The rule declares a target for external header-only library. `target` argument +should take the form `/PROJECT//LIBRARY`, which results in creation of target +`LIBRARY` in project `PROJECT`. The `sources` argument can be used to +represent transitive dependencies. + +Allowed options: + +* `
`: the header file (relative to include directory) that will be + used for include directory search. *This argument is required*. +* ``: {CPP} code that tests if the header can be included. This + option may become relevant, if the build system can't find the header's + file, but it can still be included (e.g. it is located in a compiler's + hard-coded include directory). If omitted, `#include
` is used, + where `HEADER` is the valiue of option `
`. + +The rule supports keyword arguments. + +Example: + +[source, jam] +---- +external.alias /catch2//libs : options
catch2/catch_version.hpp ; +exe myapp : /catch2//libs main.cpp ; +---- + +|# # end::doc[] +rule alias ( tgt-id : sources * : options * ) +{ + param.handle-named-params sources options ; + declare-target $(tgt-id) : $(sources) : $(options) : header-only ; +} + +#| tag::doc[] + +[[bbv2.tools.external.toolset_modules]] +== Implementing toolset modules + +Implementing <> using this +module is done simply by using `configure`, followed by invocations of +`lib` and `alias` for each library in the project. For example: + +[source,jam] +---- +# sqlite3.jam + +import external ; + +rule init ( options * : condition * ) +{ + external.configure sqlite3 : $(options) : $(condition) ; + external.alias /sqlite3//sqlite3 : options
sqlite3.h ; +} +---- + +|# # end::doc[] + +local rule declare-target ( tgt-id : sources * : options * : header-only ? ) +{ + local tgt-name = [ MATCH ^/(.*)//(.*) : $(tgt-id) ] ; + local proj-name = $(tgt-name[1]) ; + tgt-name = $(tgt-name[2-]) ; + + local proj-tgt = [ get-project $(proj-name) ] ; + if ! $(proj-tgt) + { + return ; + } + + local configs = [ get-configs $(proj-name) ] ; + local registered-configs = [ $(configs).all ] ; + if ! $(registered-configs) + { + local ps = [ property-set.empty ] ; + register-config $(proj-name) : $(configs) : : $(ps) ; + registered-configs = [ $(configs).all ] ; + } + + if $(.debug) + { + local kind = searched ; + if $(header-only) + { + kind = header-only ; + } + + echo "notice: [$(proj-name)] declaring $(kind) library $(tgt-name)" ; + } + + for local config in $(registered-configs) + { + local registered-targets = [ $(configs).get $(config) : targets ] ; + if $(tgt-name) in $(registered-targets) + { + continue ; + } + + local user-options = [ $(configs).get $(config) : options ] ; + local condition = [ $(configs).get $(config) : condition ] ; + + local lib-tgt = [ new external-library $(tgt-name) + : $(proj-tgt) + : $(sources) + : $(condition) + : [ feature.get-values : $(user-options) ] + : [ feature.get-values : $(user-options) ] + ] ; + + local header = [ feature.get-values
: $(options) ] ; + if $(header) + { + $(lib-tgt).set-header $(header) ; + } + + local header-test = [ feature.get-values : $(options) ] ; + if $(header-test) + { + $(lib-tgt).set-header-test $(header-test) ; + } + + if ! $(header-only) + { + $(lib-tgt).set-default-names [ lib-name $(tgt-name) : $(options) + : $(user-options) ] ; + } + + targets.main-target-alternative $(lib-tgt) ; + } +} + +local rule lib-name ( lib-name : options * : user-options * ) +{ + local result ; + for local opt in $(user-options) + { + if $(opt:G) != { continue ; } + result += [ MATCH ^$(lib-name)=(.*) : $(opt:G=) ] ; + } + if ! $(result) + { + result = [ feature.get-values : $(options) ] ; + } + result ?= $(lib-name) ; + return $(result) ; +} + +local rule get-configs ( proj-name ) +{ + local configs = .configs-$(proj-name) ; + if ! $($(configs)) + { + $(configs) = [ new configurations ] ; + } + return $($(configs)) ; +} + +local rule register-config ( proj-name : configs : options * : condition ) +{ + # multiple empty config registrations (i.e. default configs) + # are ignored + if ! $(options) && ( $(condition) = [ property-set.empty ] ) + { + if [ $(configs).get $(condition) : condition ] + { + return ; + } + } + + $(configs).register $(condition) ; + $(configs).set $(condition) : options : $(options) ; + $(configs).set $(condition) : condition : $(condition) ; + $(configs).set $(condition) : targets ; + + $(configs).use $(condition) ; + if $(.debug) + { + echo "notice: [$(proj-name)] registering configuration" + [ $(condition).raw ] ; + } +} + +local rule get-project ( proj-name ) +{ + local proj-id = /$(proj-name) ; + local proj-mod ; + # If the project is already registered, we don't create it again. + # The project may have been created by this module, or some package + # manager, or even some other extension module. + if ! [ project.is-registered-id $(proj-id) ] + { + local cur-tgt = [ project.current ] ; + local cur-mod = [ $(cur-tgt).project-module ] ; + local cur-loc = [ project.attribute $(cur-mod) location ] ; + + local proj-loc = [ path.join $(cur-loc) $(proj-name) ] ; + proj-mod = [ project.module-name $(proj-loc) ] ; + if $(proj-mod) in [ modules.peek project : .init-stack ] + { + # the same or similar extension project is already being initialized + return ; + } + + proj-mod = [ project.load $(proj-loc) : synthesize ] ; + + .projects += $(proj-name) ; + + project.push-current $(cur-tgt) ; + project.initialize $(proj-mod) : $(proj-loc) ; + project.pop-current ; + + project.inherit-attributes $(proj-mod) : $(cur-mod) ; + local attributes = [ project.attributes $(proj-mod) ] ; + $(attributes).set id : $(proj-name) ; + $(attributes).set parent-module : $(cur-mod) : exact ; + # use-project $(proj-id) : $(proj-name) ; + project.register-id $(proj-id) : $(proj-mod) ; + } + else + { + if ! $(proj-name) in $(.projects) + { + return ; + } + proj-mod = [ project.find $(proj-id) : / ] ; + } + + return [ project.target $(proj-mod) ] ; +} + +class external-library : ac-library +{ + import configure ; + import os ; + import property ; + + rule __init__ ( name : project : sources * : requirements + : include-path ? : library-path ? : library-name ? ) + { + ac-library.__init__ $(name) : $(project) : $(requirements) + : $(include-path) : $(library-path) : $(library-name) ; + self.sources = $(sources) ; + } + + rule compute-usage-requirements ( subvariant ) + { + local base = [ basic-target.compute-usage-requirements $(subvariant) ] ; + return [ $(base).add [ $(subvariant).sources-usage-requirements ] ] ; + } + + rule construct ( name : sources * : property-set ) + { + if $(self.target) + { + return [ $(self.target).generate $(property-set) ] $(sources) ; + } + + local proj = [ project ] ; + proj = [ $(proj).get id ] ; + proj = $(proj:B) ; + + local use-environment ; + if ! $(self.library-name) && ! $(self.include-path) + { + use-environment = true ; + } + + local libnames = $(self.library-name) ; + if ! $(libnames) && $(use-environment) && $(self.default-names) + { + libnames = [ os.environ $(proj:U)_$(name:U)_NAME ] ; + libnames ?= [ os.environ $(name:U)_NAME ] ; + } + libnames ?= $(self.default-names) ; + + local include-path = $(self.include-path) ; + if ! $(include-path) && $(use-environment) + { + include-path = [ os.environ $(proj:U)_$(name:U)_INCLUDE ] ; + include-path ?= [ os.environ $(name:U)_INCLUDE ] ; + } + + local library-path = $(self.library-path) ; + if ! $(library-path) && $(use-environment) + { + library-path = [ os.environ $(proj:U)_$(name:U)_LIBRARY_PATH ] ; + library-path ?= [ os.environ $(name:U)_LIBRARY_PATH ] ; + } + + local relevant = [ + configure.get-relevant-features [ $(self.requirements).raw ] ] ; + relevant = $(relevant:G) ; + relevant = [ sequence.unique [ SORT $(relevant) ] ] ; + relevant = [ property.select $(relevant) : [ $(property-set).raw ] ] ; + relevant ?= "" ; + + local min = [ property.as-path + [ SORT [ feature.minimize $(relevant) ] ] ] ; + + local key = ext-lib-$(name)-$(relevant:J=-) ; + local lookup = [ config-cache.get $(key) ] ; + + local result ; + local includes ; + if $(lookup) + { + if $(lookup) = missing + { + configure.log-library-search-result "$(name) ($(proj))" + : "no (cached)" $(min) ; + return [ property-set.empty ] ; + } + + includes = $(lookup[1]) ; + if $(includes) = %default + { + includes = ; + } + + if $(libnames) + { + result = [ ac.construct-library $(lookup[2]) : + [ $(property-set).refine + [ property-set.create $(lookup[3]) ] ] + : $(library-path) ] ; + } + else + { + result = [ property-set.empty ] ; + } + + configure.log-library-search-result "$(name) ($(proj))" + : "yes (cached)" $(min) ; + } + else + { + includes = [ ac.find-include-path $(property-set) + : $(self.header) : $(include-path) : $(self.header-test) ] ; + + local library ; + # we don't for library binary if there's no name (we assume it is + # header only) + if $(libnames) + { + library = [ ac.find-library $(property-set) : $(libnames) + : $(library-path) ] ; + } + + if $(includes) && ( ! $(libnames) || $(library) ) + { + config-cache.set $(key) : $(includes) $(library) ; + if $(includes) = %default + { + includes = ; + } + + if $(library) + { + result = [ ac.construct-library $(library[1]) : + [ $(property-set).refine + [ property-set.create $(library[2]) ] ] + : $(library-path) ] ; + } + else + { + result = [ property-set.empty ] ; + } + + configure.log-library-search-result "$(name) ($(proj))" + : "yes " $(min) ; + } + else + { + config-cache.set $(key) : missing ; + configure.log-library-search-result "$(name) ($(proj))" + : "no " $(min) ; + return [ property-set.empty ] ; + } + } + + result = [ $(result[1]).add-raw $(includes) ] + $(result[2-]) ; + return $(result) $(sources) ; + } +} From eb40d1a9c06a55f5aaaccad0a71460a14680e102 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Sat, 8 Apr 2023 15:07:16 +0300 Subject: [PATCH 2/4] vcpkg module --- doc/src/tasks.adoc | 4 + src/build/ac.jam | 16 ++ src/tools/external.jam | 37 +++- src/tools/vcpkg.jam | 395 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 src/tools/vcpkg.jam diff --git a/doc/src/tasks.adoc b/doc/src/tasks.adoc index 53152af569..995fcf53da 100644 --- a/doc/src/tasks.adoc +++ b/doc/src/tasks.adoc @@ -726,3 +726,7 @@ managers. Currently the supported ones are: * Conan (`conan`): currently supports the link:https://docs.conan.io/en/latest/reference/generators/b2.html[`b2 generator`]. + +:leveloffset: +1 +include::../../src/tools/vcpkg.jam[tag=doc] +:leveloffset: -1 diff --git a/src/build/ac.jam b/src/build/ac.jam index 53412ab89c..352af46693 100644 --- a/src/build/ac.jam +++ b/src/build/ac.jam @@ -199,9 +199,17 @@ class ac-library : basic-target else { local use-environment ; + local vcpkg-ps ; if ! $(self.library-name) && ! $(self.include-path) && ! $(self.library-path) { use-environment = true ; + local vcpkg-proj = [ project.target vcpkg : allow-missing ] ; + if $(vcpkg-proj) + { + vcpkg-ps = [ targets.generate-from-reference + /vcpkg//prefix : $(vcpkg-proj) : $(property-set) ] ; + } + } local libnames = $(self.library-name) ; if ! $(libnames) && $(use-environment) @@ -213,12 +221,20 @@ class ac-library : basic-target libnames ?= $(self.default-names) ; local include-path = $(self.include-path) ; + if ! $(include-path) && $(vcpkg-ps) + { + include-path = [ $(vcpkg-ps).get ] ; + } if ! $(include-path) && $(use-environment) { include-path = [ os.environ $(name:U)_INCLUDE ] ; } local library-path = $(self.library-path) ; + if ! $(library-path) && $(vcpkg-ps) + { + library-path = [ $(vcpkg-ps).get ] ; + } if ! $(library-path) && $(use-environment) { library-path = [ os.environ $(name:U)_LIBRARY_PATH ] ; diff --git a/src/tools/external.jam b/src/tools/external.jam index 4e4dff5da5..c014a19bfa 100644 --- a/src/tools/external.jam +++ b/src/tools/external.jam @@ -38,6 +38,11 @@ targets inside projects it created. This allows seamless integration with package managers, if certain care is taken. In particular, it has become a convention to call the sole target in a header-only library project `libs`. +The module interoperates with <> +module. If configuration for some build request does not set include or library +directories, but there is a fitting vcpkg configuration, the module will +attempt to use it. + [[bbv2.tools.external.configure]] == Rule `configure` @@ -401,9 +406,17 @@ class external-library : ac-library proj = $(proj:B) ; local use-environment ; + local vcpkg-ps ; + local used-vcpkg ; if ! $(self.library-name) && ! $(self.include-path) { use-environment = true ; + local vcpkg-proj = [ project.target vcpkg : allow-missing ] ; + if $(vcpkg-proj) + { + vcpkg-ps = [ targets.generate-from-reference + /vcpkg//prefix : $(vcpkg-proj) : $(property-set) ] ; + } } local libnames = $(self.library-name) ; @@ -415,6 +428,11 @@ class external-library : ac-library libnames ?= $(self.default-names) ; local include-path = $(self.include-path) ; + if ! $(include-path) && $(vcpkg-ps) + { + include-path = [ $(vcpkg-ps).get ] ; + used-vcpkg = true ; + } if ! $(include-path) && $(use-environment) { include-path = [ os.environ $(proj:U)_$(name:U)_INCLUDE ] ; @@ -422,14 +440,29 @@ class external-library : ac-library } local library-path = $(self.library-path) ; + if ! $(library-path) && $(vcpkg-ps) + { + library-path = [ $(vcpkg-ps).get ] ; + used-vcpkg = true ; + } if ! $(library-path) && $(use-environment) { library-path = [ os.environ $(proj:U)_$(name:U)_LIBRARY_PATH ] ; library-path ?= [ os.environ $(name:U)_LIBRARY_PATH ] ; } - local relevant = [ - configure.get-relevant-features [ $(self.requirements).raw ] ] ; + local relevant = [ $(self.requirements).raw ] ; + if $(used-vcpkg) + { + local vcpkg-proj = [ project.target vcpkg ] ; + local vcpkg-main = [ $(vcpkg-proj).main-target prefix ] ; + local vcpkg-alt = [ + $(vcpkg-main).select-alternatives $(property-set) ] ; + local vcpkg-condition = [ $(vcpkg-alt).requirements ] ; + relevant += [ $(vcpkg-condition).raw ] ; + } + + relevant = [ configure.get-relevant-features $(relevant) ] ; relevant = $(relevant:G) ; relevant = [ sequence.unique [ SORT $(relevant) ] ] ; relevant = [ property.select $(relevant) : [ $(property-set).raw ] ] ; diff --git a/src/tools/vcpkg.jam b/src/tools/vcpkg.jam new file mode 100644 index 0000000000..c75ac34a53 --- /dev/null +++ b/src/tools/vcpkg.jam @@ -0,0 +1,395 @@ +#| +Copyright 2023 Dmitry Arkhipov (grisumbras@yandex.ru) +Distributed under the Boost Software License, Version 1.0. (See +accompanying file LICENSE.txt or copy at +https://www.bfgroup.xyz/b2/LICENSE.txt) +|# + +import errors ; +import feature ; +import msvc ; +import os ; +import path ; +import project ; +import property ; +import property-set ; +import targets ; + +import "class" : new ; + +#| tag::doc[] + +[[bbv2.tasks.packagemanagers.vcpkg]] += vcpkg support +https://learn.microsoft.com/vcpkg[vcpkg] is a cross-platform package manager +for C and {CPP} developed by Microsoft. vcpkg provides first-class support +for CMake and MSBuild build systems, but you can still use packages installed +by vcpkg in b2 either as link:#bbv2.tutorial.prebuilt[prebuilt libraries], +with the help of link:#_pkg_config[pkg-config] tool, or if the package provides +a link:#bbv2.extending.toolset_modules[toolset module]. + +The module declares the project `/vcpkg` with a main target `/vcpkg//prefix`. +The target can be used as a dependency to add its `` and `` +usage requirements. + +[source, jam] +---- +lib sqlite3 : /vcpkg//prefix ; +---- + +|# # end::doc[] + +if --debug-configuration in [ modules.peek : ARGV ] +{ + .debug = true ; +} + +#| tag::doc[] + + +== Initialization +To enable vcpkg integration you need to declare it in a configuration file +with the help of `using` rule: + +[source, jam] +---- +using vcpkg : [root] : [options] ... : [ condition ] ... ; +---- + +* `root`: vcpkg installation root. + +* `options`: options that specify the location of vcpkg package installation + tree. Allowed options are: + + ** ``: directory with header files; + ** ``: directory with library binaries; + ** ``: installation prefix for the triplet; + ** ``: the triplet to use. + +* `condition`: properties that distinguish this configuration. + +If options contain neither `` nor ``, they are set to +`include` and `lib` subdirectories of ``. + +If options do not contain ``, it is set to `root/`. + +If `root` is empty, it is set to `installed` subdirectory of the directory +pointed to by `VCPKG_ROOT` environment variable if it is not empty. Otherwise, +https://learn.microsoft.com/vcpkg/users/manifests[Manisfest mode] is assumed, +and `vcpkg.json` file is searched for in the current directory and its parents. +If the file is found, then `root` is set to `vcpkg_installed` subdirectory +of its parent directory. + +If options do not contain ``, it is set to the value of +`VCPKG_DEFAULT_TRIPLET` environment variable if it is not empty. Otherwise, +if there is exactly one triplet subdirectory of `root` directory, then its +name is used. + +If `condition` does not contain a property of ``, and `options` do not +contain ``, then two versions are configured: one for +`release` that sets `` to `/lib`, and another for +`debug` that sets `` to `/debug/lib`. + +Finally, if `options` contain none of ``, ``, ``, and +`` and `requirements` are empty, appropriate configurations for +several default triplets provided by vcpkg are made. + +|# # end::doc[] + +rule init ( root ? : options * : condition * ) +{ + local incdir = [ feature.get-values : $(options) ] ; + local libdir = [ feature.get-values : $(options) ] ; + local prefix = [ feature.get-values : $(options) ] ; + local triplet = [ feature.get-values : $(options) ] ; + + if ( ( $(root) || $(triplet) ) && ( $(prefix) || $(incdir) || $(libdir) ) ) + || ( $(prefix) && ( $(incdir) || $(libdir) ) ) + || ( $(incdir) && ! $(libdir) ) + || ( $(libdir) && ! $(incdir) ) + { + errors.user-error "incompatible options for vcpkg:" $(options) ; + } + + local kind = predefined-triplets ; + if $(prefix) || $(incdir) || $(libdir) + { + kind = specific-triplet ; + } + else + { + if $(triplet) || $(condition) + { + kind = specific-triplet ; + } + } + + if ! $(.initialized) + { + .initialized = true ; + + project.initialize $(__name__) ; + project $(__name__) ; + } + else + { + if ! $(options) && ! $(condition) + { + kind = ; + } + } + + local project = [ project.target $(__name__) ] ; + + switch $(kind) + { + case predefined-triplets : + configure-predefined $(project) : $(root) ; + case specific-triplet : + configure-specific $(project) : $(root) : $(triplet) : $(prefix) + : $(incdir) : $(libdir) : $(condition) ; + } +} + +local rule configure-predefined ( project : root ? ) +{ + if ! $(root) + { + root = [ deduce-root ] ; + } + + # arm-neon-android, arm6-android, arm64ec-windows, wasm32-emscripten, + # all xbox, mingw, and -release triplets are skipped + + # Linux + configure-specific $(project) : $(root) : x64-linux : : : + : linux 64 x86 static shared ; + configure-specific $(project) : $(root) : x64-linux-dynamic : : : + : linux 64 x86 shared shared ; + + configure-specific $(project) : $(root) : x86-linux : : : + : linux 32 x86 static shared ; + + configure-specific $(project) : $(root) : arm-linux : : : + : linux 32 arm static shared ; + + configure-specific $(project) : $(root) : arm64-linux : : : + : linux 64 arm static shared ; + + configure-specific $(project) : $(root) : ppc64le-linux : : : + : linux 64 power static shared ; + + configure-specific $(project) : $(root) : riscv32-linux : : : + : linux 32 riscv static shared ; + + configure-specific $(project) : $(root) : riscv64-linux : : : + : linux 64 riscv static shared ; + + configure-specific $(project) : $(root) : s390x-linux : : : + : linux 64 s390x static shared ; + + # Windows + configure-specific $(project) : $(root) : x64-windows : : : + : windows 64 x86 shared shared desktop ; + configure-specific $(project) : $(root) : x64-windows-static : : : + : windows 64 x86 static static desktop ; + configure-specific $(project) : $(root) : x64-windows-static-md : : : + : windows 64 x86 static shared desktop ; + + configure-specific $(project) : $(root) : x86-windows : : : + : windows 32 x86 shared shared desktop ; + configure-specific $(project) : $(root) : x86-windows-static-md : : : + : windows 32 x86 static shared desktop ; + configure-specific $(project) : $(root) : x86-windows-static : : : + : windows 32 x86 static static desktop ; + + configure-specific $(project) : $(root) : arm64-windows : : : + : windows 64 arm shared shared desktop ; + configure-specific $(project) : $(root) : arm64-windows-static-md : : : + : windows 64 arm static shared desktop ; + configure-specific $(project) : $(root) : arm64-windows-static : : : + : windows 64 arm static static desktop ; + + configure-specific $(project) : $(root) : arm-windows : : : + : windows 32 arm shared shared desktop ; + configure-specific $(project) : $(root) : arm-windows-static : : : + : windows 32 arm static static desktop ; + + # WindowsStore + configure-specific $(project) : $(root) : x64-uwp : : : + : windows 64 x86 shared shared store ; + configure-specific $(project) : $(root) : x64-uwp-static-md : : : + : windows 64 x86 static shared store ; + + configure-specific $(project) : $(root) : x86-uwp : : : + : windows 32 x86 shared shared store ; + configure-specific $(project) : $(root) : x86-uwp-static-md : : : + : windows 32 x86 static shared store ; + + configure-specific $(project) : $(root) : arm64-uwp : : : + : windows 64 arm shared shared store ; + configure-specific $(project) : $(root) : arm64-uwp-static-md : : : + : windows 64 arm static shared store ; + + configure-specific $(project) : $(root) : arm-uwp : : : + : windows 32 arm shared shared store ; + configure-specific $(project) : $(root) : arm-uwp-static-md : : : + : windows 32 arm static shared store ; + + # OSX + configure-specific $(project) : $(root) : x64-osx : : : + : darwin 64 x86 static shared ; + configure-specific $(project) : $(root) : x64-osx-dynamic : : : + : darwin 64 x86 shared shared ; + + configure-specific $(project) : $(root) : arm64-osx-dynamic : : : + : darwin 64 arm shared shared ; + configure-specific $(project) : $(root) : arm64-osx : : : + : darwin 64 arm static shared ; + + # Android + configure-specific $(project) : $(root) : x64-android : : : + : android 64 x86 static static ; + + configure-specific $(project) : $(root) : x86-android : : : + : android 32 x86 static static ; + + configure-specific $(project) : $(root) : arm64-android : : : + : android 64 arm static static ; + + configure-specific $(project) : $(root) : arm-android : : : + : android 32 arm static static ; + + # iPhone + configure-specific $(project) : $(root) : arm-ios : : : + : iphone 32 arm static shared ; + + configure-specific $(project) : $(root) : arm64-ios : : : + : iphone 64 arm static shared ; + + configure-specific $(project) : $(root) : x64-ios : : : + : iphone 64 x86 static shared ; + + configure-specific $(project) : $(root) : x86-ios : : : + : iphone 32 x86 static shared ; + + # FreeBSD + configure-specific $(project) : $(root) : x64-freebsd : : : + : freebsd 64 static shared ; + + configure-specific $(project) : $(root) : x86-freebsd : : : + : freebsd 32 static shared ; + + # OpenBSD + configure-specific $(project) : $(root) : x64-openbsd : : : + : openbsd 64 static shared ; + +} + +local rule configure-specific ( project : root ? : triplet ? : prefix ? + : incdir ? : libdir ? : condition + ) +{ + local with-debug ; + if ! $(incdir) || ! $(libdir) + { + if ! $(prefix) + { + prefix = [ deduce-prefix $(root) : $(triplet) ] ; + } + incdir ?= [ path.join $(prefix) include ] ; + libdir ?= [ path.join $(prefix) lib ] ; + if ! [ property.select : $(condition) ] + { + with-debug = true ; + } + } + + if $(with-debug) + { + declare-target $(project) : $(incdir) : $(libdir) + : $(condition) release ; + declare-target $(project) : $(incdir) + : [ path.join $(prefix) debug/lib ] + : $(condition) debug ; + } + else + { + declare-target $(project) : $(incdir) : $(libdir) : $(condition) ; + } +} + +local rule declare-target ( project : incdir : libdir : condition * ) +{ + if $(.debug) + { + echo "notice: [vcpkg] configuring with" $(incdir) + $(libdir) ; + } + + targets.main-target-alternative + [ new alias-target-class prefix + : $(project) + : + : [ property-set.create $(condition) ] + : + : [ property-set.create $(incdir) $(libdir) ] + ] ; +} + +local rule deduce-prefix ( root ? : triplet ? ) +{ + if ! $(root) + { + root = [ deduce-root ] ; + } + + triplet ?= [ os.environ VCPKG_DEFAULT_TRIPLET ] ; + if ! $(triplet) && $(root) + { + local ignored = [ path.join $(root) vcpkg ] ; + for local p in [ path.glob $(root) : * ] + { + if $(p) != $(ignored) + { + triplet += $(p:B) ; + } + } + if $(triplet[2]) + { + triplet = ; + } + } + + if ! $(root) || ! $(triplet) + { + errors.user-error "could not initialize vcpkg module:" + "vcpkg installation could not be deduced" ; + } + + return [ path.join $(root) $(triplet) ] ; +} + +local rule deduce-root ( ) +{ + # attempt to locate vcpkg installation using environment variable + local root = [ os.environ VCPKG_ROOT ] ; + if $(root) + { + root = [ path.join [ path.make $(root) ] installed ] ; + } + else + { + # check if Manifest mode is used + local roots = . [ path.all-parents . ] ; + while $(roots) && ! $(root) + { + if [ path.glob $(roots[1]) : vcpkg.json ] + { + root = $(roots[1])/vcpkg_installed ; + } + roots = $(roots[2-]) ; + } + } + + return $(root) ; +} From b1d34c21e087b1da631988b6ed6e5befd45b58d3 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Fri, 28 Apr 2023 13:02:36 +0300 Subject: [PATCH 3/4] pkg-config support in vcpkg --- src/tools/pkg-config.jam | 70 +++++++++++++++++++++++++++++++++++----- src/tools/vcpkg.jam | 3 ++ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/tools/pkg-config.jam b/src/tools/pkg-config.jam index 9565f8fed2..276ecd6c23 100644 --- a/src/tools/pkg-config.jam +++ b/src/tools/pkg-config.jam @@ -13,6 +13,7 @@ import feature ; import os ; import param ; import project ; +import property-set ; import regex ; import sequence ; import string ; @@ -96,7 +97,8 @@ pkg-config.import my-package |# # end::doc[] -rule import +# will be re-exported as 'import' +local rule do-import ( target-name : sources * : requirements * @@ -127,7 +129,7 @@ with the `using` rule: [source, jam] ---- -using pkg-config : [config] : [command] ... : [ options ] ... ; +using pkg-config : [config] : [command] ... : [ options ] ... : [condition] ... ; ---- @@ -149,20 +151,57 @@ using pkg-config : [config] : [command] ... : [ options ] ... ; multiple occurences are allowed. * ``: adds a variable definition argument to command invocation; multiple occurences are allowed. +* `condition`: properties that distinguish this configuration. |# # end::doc[] -rule init ( config ? : command * : options * ) +rule init ( config ? : command * : options * : condition * ) { - config ?= [ default-config ] ; + echo using pkg-config $(config) ":" $(command) ":" $(options) ; + + if ! $(.initialized) + { + .initialized = true ; + + project.initialize $(__name__) ; + project $(__name__) ; + + # project.initialize replaces import rule in this module, so we + # put it back + IMPORT $(__name__) : do-import : $(__name__) : import ; + EXPORT $(__name__) : import ; + } + + if ! $(config) + { + config = [ default-config ] ; + if $(config) in [ $(.configs).used ] + { + config = ; + } + } + if ! $(config) && ! $(condition) + { + return ; + } local tool = [ os.environ PKG_CONFIG ] ; tool ?= pkg-config ; - command = - [ common.get-invocation-command pkg-config : $(tool) : $(command) ] ; + command = [ common.get-invocation-command pkg-config : $(tool) + : $(command) ] ; configure $(config) : $(command) : $(options) ; $(.configs).use $(config) ; + + local project = [ project.target $(__name__) ] ; + targets.main-target-alternative + [ new alias-target-class prefix + : $(project) + : + : [ property-set.create $(condition) ] + : + : [ property-set.create $(config) ] + ] ; } @@ -223,7 +262,7 @@ class pkg-config-target : alias-target-class rule construct ( name : sources * : property-set ) { - local config = [ $(property-set).get ] ; + local config = [ get-config $(property-set) ] ; local args = [ common-arguments $(name) : $(property-set) ] ; return [ property-set.create @@ -234,7 +273,7 @@ class pkg-config-target : alias-target-class rule version ( property-set ) { - local config = [ $(property-set).get ] ; + local config = [ get-config $(property-set) ] ; local args = [ common-arguments [ name ] : $(property-set) ] ; local version = [ pkg-config.run $(config) : --modversion $(args) ] ; return [ regex.split $(version) "\\." ] ; @@ -300,6 +339,21 @@ class pkg-config-target : alias-target-class local flags = [ pkg-config.run $(config) : --cflags $(args) ] ; return $(flags) ; } + + local rule get-config ( property-set ) + { + local result = [ $(property-set).get ] ; + if $(result) + { + return $(result) ; + } + + + local project = [ project.target pkg-config ] ; + property-set = [ targets.generate-from-reference /pkg-config//prefix + : $(project) : $(property-set) ] ; + return [ $(property-set[1]).get ] ; + } } diff --git a/src/tools/vcpkg.jam b/src/tools/vcpkg.jam index c75ac34a53..8fbf094e68 100644 --- a/src/tools/vcpkg.jam +++ b/src/tools/vcpkg.jam @@ -334,6 +334,9 @@ local rule declare-target ( project : incdir : libdir : condition * ) : : [ property-set.create $(incdir) $(libdir) ] ] ; + local pkg-config-libdir = [ path.join $(libdir) pkgconfig ] ; + using pkg-config : [ property-set.create $(condition) ] : + : $(pkg-config-libdir) ; } local rule deduce-prefix ( root ? : triplet ? ) From e5ce2f0e81973f3ad5bd4163960210d59d341ee6 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Fri, 28 Apr 2023 14:14:04 +0300 Subject: [PATCH 4/4] allow override external targets with other targets --- src/tools/external.jam | 53 +++++++++++++++++++++++++++++++++++++++- src/tools/pkg-config.jam | 2 -- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/tools/external.jam b/src/tools/external.jam index c014a19bfa..9f0e4d84da 100644 --- a/src/tools/external.jam +++ b/src/tools/external.jam @@ -61,6 +61,12 @@ Allowed options: * ``: should be in the form `LIBRARY=NAME`; overrides the searched name for library `LIBRARY` with the name `NAME`. Multiple instances (even for the same library) are allowed. +* `` should be in the form `LIBRARY=TARGET`, where `TARGET` should be a + target reference, or `LIBRARY`, in which case the current project should + contain a target called `LIBRARY`; target to use instead of creating a new + target for `LIBRARY`. This can be useful if the user wants a non-standard + method of locating the library (e.g. <<_pkg_config>>), or wants to build + the library manually. One instance per `LIBRARY` is allowed. The rule supports keyword arguments. @@ -200,6 +206,18 @@ rule init ( options * : condition * ) } ---- +[[bbv2.tools.external.target_override]] +== Overriding with other targets + +`configure` rule can be used to override the created target with another +target. One particular use case is overriding with a target created by +`pkg-config` module. + +[source,jam] +---- +pkg-config.import sqlite3 ; +external.configure sqlite3 : sqlite3 ; +---- |# # end::doc[] local rule declare-target ( tgt-id : sources * : options * : header-only ? ) @@ -271,7 +289,16 @@ local rule declare-target ( tgt-id : sources * : options * : header-only ? ) : $(user-options) ] ; } + local target-ref = [ get-target $(tgt-name) : $(user-options) ] ; + if $(target-ref) + { + $(lib-tgt).set-target $(target-ref) ; + $(lib-tgt).set-caller [ $(configs).get $(config) : caller ] ; + } + targets.main-target-alternative $(lib-tgt) ; + + $(configs).set $(config) : $(registered-targets) $(tgt-name) ; } } @@ -291,6 +318,23 @@ local rule lib-name ( lib-name : options * : user-options * ) return $(result) ; } +local rule get-target ( lib-name : user-options * ) +{ + local result ; + for local opt in $(user-options) + { + if $(opt:G) != { continue ; } + if $(opt:G=) = $(lib-name) + { + result += $(lib-name) ; + } + + result += [ MATCH ^$(lib-name)=(.*) : $(opt:G=) ] + [ MATCH ^$(lib-name)$ : $(opt:G=) ] ; + } + return $(result) ; +} + local rule get-configs ( proj-name ) { local configs = .configs-$(proj-name) ; @@ -316,6 +360,7 @@ local rule register-config ( proj-name : configs : options * : condition ) $(configs).register $(condition) ; $(configs).set $(condition) : options : $(options) ; $(configs).set $(condition) : condition : $(condition) ; + $(configs).set $(condition) : caller : [ project.current ] ; $(configs).set $(condition) : targets ; $(configs).use $(condition) ; @@ -388,6 +433,11 @@ class external-library : ac-library self.sources = $(sources) ; } + rule set-caller ( caller ) + { + self.caller = $(caller) ; + } + rule compute-usage-requirements ( subvariant ) { local base = [ basic-target.compute-usage-requirements $(subvariant) ] ; @@ -398,7 +448,8 @@ class external-library : ac-library { if $(self.target) { - return [ $(self.target).generate $(property-set) ] $(sources) ; + return [ targets.generate-from-reference $(self.target) + : $(self.caller) : $(property-set) ] ; } local proj = [ project ] ; diff --git a/src/tools/pkg-config.jam b/src/tools/pkg-config.jam index 276ecd6c23..32a9ade5e3 100644 --- a/src/tools/pkg-config.jam +++ b/src/tools/pkg-config.jam @@ -157,8 +157,6 @@ using pkg-config : [config] : [command] ... : [ options ] ... : [condition] ... rule init ( config ? : command * : options * : condition * ) { - echo using pkg-config $(config) ":" $(command) ":" $(options) ; - if ! $(.initialized) { .initialized = true ;