Skip to content

Commit

Permalink
Add automatic project searching.
Browse files Browse the repository at this point in the history
This adds an automatic, but controlled, method for finding, declaring,
and loading of unknown rooted project-id references.
  • Loading branch information
grafikrobot authored Jun 10, 2023
1 parent 1ae2e37 commit 131b504
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 2 deletions.
97 changes: 96 additions & 1 deletion doc/src/tasks.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <<Package Managers>> 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/<jamfile>`
. `/usr/share/boost-1.81.0/core/<jamfile>`
. `/home/user/boost-dev/libs/core/<jamfile>`
. `/usr/share/boost-1.81.0/core.jam`
. `/home/user/boost-dev/libs/core.jam`
. `/usr/share/boost-1.81.0/boost_core/<jamfile>`
. `/home/user/boost-dev/libs/boost_core/<jamfile>`
. `/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.
136 changes: 135 additions & 1 deletion src/build/project.jam
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 <a>_<b>_<c>[.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 <base>.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.
Expand Down Expand Up @@ -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) ;
Expand Down Expand Up @@ -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 ;
Expand Down Expand Up @@ -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) ;
}
}

0 comments on commit 131b504

Please sign in to comment.