From 131b504076346982389cd2b695f25110930bd0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Ferdinand=20Rivera=20Morell?= Date: Fri, 9 Jun 2023 21:30:36 -0500 Subject: [PATCH] Add automatic project searching. This adds an automatic, but controlled, method for finding, declaring, and loading of unknown rooted project-id references. --- doc/src/tasks.adoc | 97 +++++++++++++++++++++++++++++- src/build/project.jam | 136 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 231 insertions(+), 2 deletions(-) diff --git a/doc/src/tasks.adoc b/doc/src/tasks.adoc index 53152af569..d56bcc4727 100644 --- a/doc/src/tasks.adoc +++ b/doc/src/tasks.adoc @@ -670,7 +670,7 @@ for details. [[bbv2.tasks.packagemanagers]] == Package Managers -B2 support automatic, or manual, loading of generated build files +B2 supports automatic, or manual, loading of generated build files from package managers. For example using the Conan package manager which generates `conanbuildinfo.jam` files B2 will load that files automatically when it loads the project at the same location. The included file can @@ -726,3 +726,98 @@ managers. Currently the supported ones are: * Conan (`conan`): currently supports the link:https://docs.conan.io/en/latest/reference/generators/b2.html[`b2 generator`]. + +[[bbv2.tasks.projectsearch]] +== Searching For Projects + +B2 supports automatic searching for referenced global projects. For example, +if you have references to `/boost/predef` with some minimal configuration B2 +can find the B2 project for it and automatically resolve the reference. The +searching supports two modes: finding regular B2 project directories, and +package/config style loading of single jam files. + +[[bbv2.tasks.projectsearch.path]] +=== Search Path + +To control which and where projects are found one can use different methods: + +* `B2_PROJECT_PATH` environment variable. +* `--project-search` command line argument. +* `rule project-search` project rule. + +The search path in both `B2_PROJECT_PATH` and `--project-search` specifies a +key-value list of _project-id_ and _path_. The parts of that key-value list, as +the name indicates, is a path delimiter separated list. For example if we had +these two projects we wanted to find: `/zlib` and `/boost/asio` the search paths +could look like: + +[horizontal] +Linux:: +`/zlib:/usr/local/share/zlib:/boost/asio:/home/user/external/boost-1.81/libs/asio` +Windows, VxWorks:: +`/zlib;C:/Dev/zlib;/boost/asio;C:Dev/boost-1.81/libs/asio` +VMS:: +`/zlib,X:external.zlib,/boost/asio,X:external.boost181.libs.asio` + +The _project-id_ in the search path specification maps that project root to the +indicated _path_. Which B2 will use to search for any projects and sub-projects +with that _project-id_ root. + +[[bbv2.tasks.projectsearch.process]] +=== Search Process + +Regardless of how the search path is specified, how the search happens is the +same. Searching involves either searching for a B2 project directory, i.e. +a directory containing a jamfile, or searching for a specially named `*.jam` +file to include (similar to how the <> support includes +jam files). + +For a given _project-id_ of the form `/d1/d2/../dn` we search for the following, +in this order: + +. The project at `d1/d2/../dn` in any path registered for the `/` root. +. The project at `dn` in any path registered for the `/d1/d2/../dn-1` root. +. The jamfile `dn.jam` in any path registered for the `/d1/d2/../dn-1` root. +. The project at `dn-1_dn` in any path registered for the `/d1/d2/../dn-2` root. +. The jamfile `dn-1_dn.jam` in any path registered for the `/d1/d2/../dn-2` + root. +. And so on until it searches for the project `d1_d2_.._dn` in any path + registered for the `/` root. +. And for the jamfile `d1_d2_.._dn.jam` in any path registered for the `/` root. + +For example, with this search paths: + +* `/boost`: `/usr/share/boost-1.81.0`, `/home/user/boost-dev/libs` +* `/`: `/usr/share/b2/external` + +And given the `/boost/core` _project-id_ to resolve, we search for: + +. `/usr/share/b2/external/boost/core/` +. `/usr/share/boost-1.81.0/core/` +. `/home/user/boost-dev/libs/core/` +. `/usr/share/boost-1.81.0/core.jam` +. `/home/user/boost-dev/libs/core.jam` +. `/usr/share/boost-1.81.0/boost_core/` +. `/home/user/boost-dev/libs/boost_core/` +. `/usr/share/boost-1.81.0/boost_core.jam` +. `/home/user/boost-dev/libs/boost_core.jam` +. `/usr/share/b2/external/boost_core.jam` + +The first project jamfile will be assigned to the _project-id_. Or the first +`*.jam` file found will be loaded. + +[[bbv2.tasks.projectsearch.loading]] +=== Loading Process + +Depending on whether a project jamfile or `*.jam` file determines how the +project is loaded. + +When loading a project jamfile with a _project-id_ and _path_ it is equivalent +to calling `use-project _project-id_ : _path_ ;` from the context of the project +that has the reference. + +When loading a `*.jam` file as the _path_ it is equivalent to calling: +`use-packages _path_ ;` from the context of the project that has the reference. +In this case it means that the file will be loaded as part of the referenced +project and hence any bare targets or information it declares will be part of +the project. diff --git a/src/build/project.jam b/src/build/project.jam index 095a879a6d..27ddba0655 100644 --- a/src/build/project.jam +++ b/src/build/project.jam @@ -36,13 +36,16 @@ import "class" : new ; import modules ; +import os ; import path ; import print ; import property-set ; +import regex ; import sequence ; .debug-loading = [ MATCH ^(--debug-loading)$ : [ modules.peek : ARGV ] ] ; +.debug-project-search = [ MATCH ^(--debug-project-search)$ : [ modules.peek : ARGV ] ] ; # Loads the Jamfile at the given location. After loading, project global file @@ -141,12 +144,42 @@ rule load-parent ( location ) # rule find ( name : current-location ) { + name = [ path.normalize-all $(name) ] ; local project-module ; # Try interpreting name as project id. if [ path.is-rooted $(name) ] { project-module = $($(name).jamfile-module) ; + if ! $(project-module) + { + # Not immediately found as a project. Try and search for a project + # we can import. + local root-and-jamfile = [ search $(name) ] ; + local root = $(root-and-jamfile[1]) ; + local jamfile = $(root-and-jamfile[2]) ; + if $(root) + { + local caller-project = [ CALLER_MODULE ] ; + local caller-module = [ $(caller-project).project-module ] ; + if $(jamfile) + { + modules.call-in $(caller-module) + : use-packages $(jamfile) ; + } + else + { + if $(.debug-project-search) + { + ECHO Using project '$(name)' from '$(root)' ; + } + modules.call-in $(caller-module) + : use-project $(name) : $(root) ; + load-used-projects $(caller-module) ; + } + } + project-module = $($(name).jamfile-module) ; + } } if ! $(project-module) @@ -177,6 +210,96 @@ rule find ( name : current-location ) } +B2_PROJECT_PATH = [ modules.peek : B2_PROJECT_PATH ] + [ regex.split-list + [ MATCH ^--project-search=(.*)$ : [ modules.peek : ARGV ] ] + : [ os.path-separator ] ] ; +{ + while $(B2_PROJECT_PATH) + { + local root = $(B2_PROJECT_PATH[1]) ; + local path = [ path.make $(B2_PROJECT_PATH[2]) ] ; + B2_PROJECT_PATH = $(B2_PROJECT_PATH[3-]) ; + if $(root) && $(path) + { + path = [ path.root $(path) [ path.pwd ] ] ; + .search-path.$(root) += $(path) ; + } + } +} + + +rule add-project-search ( root : search-paths + ) +{ + for local search-path in $(search-paths) + { + if ! [ path.is-rooted $(search-path) ] + { + local prj = [ current ] ; + search-path = [ path.join [ $(prj).location ] $(search-path) ] ; + search-path = [ path.root $(search-path) [ path.pwd ] ] ; + } + .search-path.$(root) += $(search-path) ; + } +} + + +local rule search ( name ) +{ + if [ path.is-rooted $(name) ] + { + # Check for a regular B2 project relative to the search path and + # project subdir. + for local dir in "$(.search-path./)" + { + dir = $(dir)$(name) ; + local jamfile = [ path.glob $(dir) : $(JAMROOT) $(JAMFILE) ] ; + if $(jamfile) + { + return $(dir) ; + } + } + # Do searching for various dir names and jamfiles based on the built + # basename __[.jam]. + local base = [ path.basename $(name) ] ; + local root = [ path.parent $(name) ] ; + while $(base) + { + # Check for a regular B2 project in the search path and subdir base. + for local dir in $(.search-path.$(root)) + { + dir = [ path.join $(dir) $(base) ] ; + local jamfile = [ path.glob $(dir) : $(JAMROOT) $(JAMFILE) ] ; + if $(jamfile) + { + return $(dir) ; + } + } + # Check for a .jam to include. + for local dir in $(.search-path.$(root)) + { + local jamfile = [ path.glob $(dir) : $(base:L).jam ] ; + if $(jamfile) + { + return $(dir) $(jamfile[1]) ; + } + } + if [ path.has-parent $(root) ] + { + base = [ path.basename $(root) ] $(base) ; + base = $(base:L:J=_) ; + root = [ path.parent $(root) ] ; + } + else + { + base = ; + root = ; + } + } + } +} + + # Returns the name of the module corresponding to 'jamfile-location'. If no # module corresponds to that location yet, associates the default module name # with that location. @@ -322,7 +445,7 @@ rule load-package-manager-build-info ( ) if $(pm) && ! ( $(pm-tag) in $(.package-manager-build-info) ) { .package-manager-build-info += $(pm-tag) ; - # We found a matching builf info to load, but we have to be careful + # We found a matching build info to load, but we have to be careful # as the loading can affect the current project since it can define # sub-projects. Hence we save and restore the current project. local saved-project = $(.current-project) ; @@ -619,6 +742,9 @@ rule initialize ( { .first-project-root = $(module-name) ; } + # Set the default build-dir so that we don't get build results + # spread out everywhere. + # $(attributes).set build-dir : ".b2/bin" ; } local parent ; @@ -1418,4 +1544,12 @@ module project-rules ] ; } } + + # Defines search paths for resolving global, i.e. rooted, project references. + rule project-search ( root : search-paths + ) + { + import path ; + import project ; + project.add-project-search $(root) : $(search-paths) ; + } }