blob: b4d2227e6bed9950e71e6e2671abe3233e3e1c1b [file] [log] [blame]
# Copyright 2002, 2003 Dave Abrahams
# Copyright 2002, 2005, 2006 Rene Rivera
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
# Implements project representation and loading. Each project is represented by:
# - a module where all the Jamfile content live.
# - an instance of 'project-attributes' class.
# (given a module name, can be obtained using the 'attributes' rule)
# - an instance of 'project-target' class (from targets.jam)
# (given a module name, can be obtained using the 'target' rule)
#
# Typically, projects are created as result of loading a Jamfile, which is done
# by rules 'load' and 'initialize', below. First, module for Jamfile is loaded
# and new project-attributes instance is created. Some rules necessary for
# project are added to the module (see 'project-rules' module) at the bottom of
# this file. Default project attributes are set (inheriting attributes of parent
# project, if it exists). After that the Jamfile is read. It can declare its own
# attributes using the 'project' rule which will be combined with any already
# set attributes.
#
# The 'project' rule can also declare a project id which will be associated with
# the project module.
#
# There can also be 'standalone' projects. They are created by calling
# 'initialize' on an arbitrary module and not specifying their location. After
# the call, the module can call the 'project' rule, declare main targets and
# behave as a regular project except that, since it is not associated with any
# location, it should not declare targets that are not prebuilt.
#
# The list of all loaded Jamfile is stored in the .project-locations variable.
# It is possible to obtain a module name for a location using the 'module-name'
# rule. Standalone projects are not recorded and can only be referenced using
# their project id.
import "class" : new ;
import errors ;
import modules ;
import path ;
import print ;
import property-set ;
import sequence ;
# Loads the Jamfile at the given location. After loading, project global file
# and Jamfiles needed by the requested one will be loaded recursively. If the
# Jamfile at that location is loaded already, does nothing. Returns the project
# module for the Jamfile.
#
rule load ( jamfile-location )
{
if --debug-loading in [ modules.peek : ARGV ]
{
ECHO "Loading Jamfile at" '$(jamfile-location)' ;
}
local module-name = [ module-name $(jamfile-location) ] ;
# If Jamfile is already loaded, don't try again.
if ! $(module-name) in $(.jamfile-modules)
{
load-jamfile $(jamfile-location) : $(module-name) ;
# We want to make sure that child project are loaded only after parent
# projects. In particular, because parent projects define attributes
# which are inherited by children, and we don't want children to be
# loaded before parent has defined everything.
#
# While "build-project" and "use-project" can potentially refer to child
# projects from parent projects, we don't immediately load child
# projects when seeing those attributes. Instead, we record the minimal
# information to be used only later.
load-used-projects $(module-name) ;
}
return $(module-name) ;
}
rule load-used-projects ( module-name )
{
local used = [ modules.peek $(module-name) : .used-projects ] ;
local location = [ attribute $(module-name) location ] ;
import project ;
while $(used)
{
local id = $(used[1]) ;
local where = $(used[2]) ;
project.use $(id) : [ path.root [ path.make $(where) ] $(location) ] ;
used = $(used[3-]) ;
}
}
# Note the use of character groups, as opposed to listing 'Jamroot' and
# 'jamroot'. With the latter, we would get duplicate matches on Windows and
# would have to eliminate duplicates.
JAMROOT ?= [ modules.peek : JAMROOT ] ;
JAMROOT ?= project-root.jam [Jj]amroot [Jj]amroot.jam ;
# Loads parent of Jamfile at 'location'. Issues an error if nothing is found.
#
rule load-parent ( location )
{
local found = [ path.glob-in-parents $(location) : $(JAMROOT) $(JAMFILE) ] ;
if ! $(found)
{
ECHO error: Could not find parent for project at '$(location)' ;
EXIT error: Did not find Jamfile.jam or Jamroot.jam in any parent
directory. ;
}
return [ load $(found[1]:D) ] ;
}
# Makes the specified 'module' act as if it were a regularly loaded Jamfile at
# 'location'. Reports an error if a Jamfile has already been loaded for that
# location.
#
rule act-as-jamfile ( module : location )
{
if [ module-name $(location) ] in $(.jamfile-modules)
{
errors.error "Jamfile was already loaded for '$(location)'" ;
}
# Set up non-default mapping from location to module.
.module.$(location) = $(module) ;
# Add the location to the list of project locations so that we don't try to
# reload the same Jamfile in the future.
.jamfile-modules += [ module-name $(location) ] ;
initialize $(module) : $(location) ;
}
# Returns the project module corresponding to the given project-id or plain
# directory name. Returns nothing if such a project can not be found.
#
rule find ( name : current-location )
{
local project-module ;
# Try interpreting name as project id.
if [ path.is-rooted $(name) ]
{
project-module = $($(name).jamfile-module) ;
}
if ! $(project-module)
{
local location = [ path.root [ path.make $(name) ] $(current-location) ]
;
# If no project is registered for the given location, try to load it.
# First see if we have a Jamfile. If not, then see if we might have a
# project root willing to act as a Jamfile. In that case, project root
# must be placed in the directory referred by id.
project-module = [ module-name $(location) ] ;
if ! $(project-module) in $(.jamfile-modules)
{
if [ path.glob $(location) : $(JAMROOT) $(JAMFILE) ]
{
project-module = [ load $(location) ] ;
}
else
{
project-module = ;
}
}
}
return $(project-module) ;
}
# 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.
#
rule module-name ( jamfile-location )
{
if ! $(.module.$(jamfile-location))
{
# Root the path, so that locations are always unambiguous. Without this,
# we can't decide if '../../exe/program1' and '.' are the same paths.
jamfile-location = [ path.root $(jamfile-location) [ path.pwd ] ] ;
.module.$(jamfile-location) = Jamfile<$(jamfile-location)> ;
}
return $(.module.$(jamfile-location)) ;
}
# Default patterns to search for the Jamfiles to use for build declarations.
#
JAMFILE = [ modules.peek : JAMFILE ] ;
JAMFILE ?= [Bb]uild.jam [Jj]amfile.v2 [Jj]amfile [Jj]amfile.jam ;
# Find the Jamfile at the given location. This returns the exact names of all
# the Jamfiles in the given directory. The optional parent-root argument causes
# this to search not the given directory but the ones above it up to the
# directory given in it.
#
rule find-jamfile (
dir # The directory(s) to look for a Jamfile.
parent-root ? # Optional flag indicating to search for the parent Jamfile.
: no-errors ?
)
{
# Glob for all the possible Jamfiles according to the match pattern.
#
local jamfile-glob = ;
if $(parent-root)
{
if ! $(.parent-jamfile.$(dir))
{
.parent-jamfile.$(dir) = [ path.glob-in-parents $(dir) : $(JAMFILE)
] ;
}
jamfile-glob = $(.parent-jamfile.$(dir)) ;
}
else
{
if ! $(.jamfile.$(dir))
{
.jamfile.$(dir) = [ path.glob $(dir) : $(JAMFILE) ] ;
}
jamfile-glob = $(.jamfile.$(dir)) ;
}
local jamfile-to-load = $(jamfile-glob) ;
# Multiple Jamfiles found in the same place. Warn about this and ensure we
# use only one of them. As a temporary convenience measure, if there is
# Jamfile.v2 among found files, suppress the warning and use it.
#
if $(jamfile-to-load[2-])
{
local v2-jamfiles = [ MATCH (.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam) : $(jamfile-to-load) ] ;
if $(v2-jamfiles) && ! $(v2-jamfiles[2])
{
jamfile-to-load = $(v2-jamfiles) ;
}
else
{
local jamfile = [ path.basename $(jamfile-to-load[1]) ] ;
ECHO "warning: Found multiple Jamfiles at '"$(dir)"'!"
"Loading the first one: '$(jamfile)'." ;
}
jamfile-to-load = $(jamfile-to-load[1]) ;
}
# Could not find it, error.
#
if ! $(no-errors) && ! $(jamfile-to-load)
{
errors.error Unable to load Jamfile.
: Could not find a Jamfile in directory '$(dir)'.
: Attempted to find it with pattern '"$(JAMFILE:J=" ")"'.
: Please consult the documentation at 'http://www.boost.org'. ;
}
return $(jamfile-to-load) ;
}
# Load a Jamfile at the given directory. Returns nothing. Will attempt to load
# the file as indicated by the JAMFILE patterns. Effect of calling this rule
# twice with the same 'dir' is undefined.
#
local rule load-jamfile (
dir # The directory of the project Jamfile.
: jamfile-module
)
{
# See if the Jamfile is where it should be.
#
local jamfile-to-load = [ path.glob $(dir) : $(JAMROOT) ] ;
if ! $(jamfile-to-load)
{
jamfile-to-load = [ find-jamfile $(dir) ] ;
}
if $(jamfile-to-load[2])
{
errors.error "Multiple Jamfiles found at '$(dir)'"
: "Filenames are: " $(jamfile-to-load:D=) ;
}
# Initialize the Jamfile module before loading.
#
initialize $(jamfile-module) : [ path.parent $(jamfile-to-load) ]
: $(jamfile-to-load:BS) ;
local saved-project = $(.current-project) ;
# Now load the Jamfile in it's own context. Initialization might have loaded
# parent Jamfiles, which might have loaded the current Jamfile with
# use-project. Do a final check to make sure it's not loaded already.
if ! $(jamfile-module) in $(.jamfile-modules)
{
.jamfile-modules += $(jamfile-module) ;
mark-as-user $(jamfile-module) ;
modules.load $(jamfile-module) : [ path.native $(jamfile-to-load) ] : . ;
if [ MATCH ($(JAMROOT)) : $(jamfile-to-load:BS) ]
{
jamfile = [ find-jamfile $(dir) : no-errors ] ;
if $(jamfile)
{
load-aux $(jamfile-module) : [ path.native $(jamfile) ] ;
}
}
}
# Now do some checks.
if $(.current-project) != $(saved-project)
{
errors.error "The value of the .current-project variable has magically"
: "changed after loading a Jamfile. This means some of the targets"
: "might be defined in the wrong project."
: "after loading" $(jamfile-module)
: "expected value" $(saved-project)
: "actual value" $(.current-project) ;
}
if $(.global-build-dir)
{
local id = [ attribute $(jamfile-module) id ] ;
local project-root = [ attribute $(jamfile-module) project-root ] ;
local location = [ attribute $(jamfile-module) location ] ;
if $(location) && $(project-root) = $(dir)
{
# This is Jamroot.
if ! $(id)
{
ECHO "warning: the --build-dir option was specified" ;
ECHO "warning: but Jamroot at '$(dir)'" ;
ECHO "warning: specified no project id" ;
ECHO "warning: the --build-dir option will be ignored" ;
}
}
}
}
rule mark-as-user ( module-name )
{
if USER_MODULE in [ RULENAMES ]
{
USER_MODULE $(module-name) ;
}
}
rule load-aux ( module-name : file )
{
mark-as-user $(module-name) ;
module $(module-name)
{
include $(2) ;
local rules = [ RULENAMES $(1) ] ;
IMPORT $(1) : $(rules) : $(1) : $(1).$(rules) ;
}
}
.global-build-dir = [ MATCH --build-dir=(.*) : [ modules.peek : ARGV ] ] ;
if $(.global-build-dir)
{
# If the option is specified several times, take the last value.
.global-build-dir = [ path.make $(.global-build-dir[-1]) ] ;
}
# Initialize the module for a project.
#
rule initialize (
module-name # The name of the project module.
: location ? # The location (directory) of the project to initialize. If
# not specified, a standalone project will be initialized.
: basename ?
)
{
if --debug-loading in [ modules.peek : ARGV ]
{
ECHO "Initializing project '$(module-name)'" ;
}
# TODO: need to consider if standalone projects can do anything but define
# prebuilt targets. If so, we need to give it a more sensible "location", so
# that source paths are correct.
location ?= "" ;
# Create the module for the Jamfile first.
module $(module-name)
{
}
$(module-name).attributes = [ new project-attributes $(location)
$(module-name) ] ;
local attributes = $($(module-name).attributes) ;
if $(location)
{
$(attributes).set source-location : [ path.make $(location) ] : exact ;
}
else if ! $(module-name) in test-config site-config user-config project-config
{
# This is a standalone project with known location. Set source location
# so that it can declare targets. This is intended so that you can put
# a .jam file in your sources and use it via 'using'. Standard modules
# (in 'tools' subdir) may not assume source dir is set.
local s = [ modules.binding $(module-name) ] ;
if ! $(s)
{
errors.error "Could not determine project location $(module-name)" ;
}
$(attributes).set source-location : $(s:D) : exact ;
}
$(attributes).set requirements : [ property-set.empty ] : exact ;
$(attributes).set usage-requirements : [ property-set.empty ] : exact ;
# Import rules common to all project modules from project-rules module,
# defined at the end of this file.
modules.clone-rules project-rules $(module-name) ;
local jamroot ;
local parent-module ;
if $(module-name) = test-config
{
# No parent.
}
else if $(module-name) = site-config
{
parent-module = test-config ;
}
else if $(module-name) = user-config
{
parent-module = site-config ;
}
else if $(module-name) = project-config
{
parent-module = user-config ;
}
else
{
# We search for parent/project-root only if Jamfile was specified, i.e.
# if the project is not standalone.
if $(location) && ! [ MATCH ($(JAMROOT)) : $(basename) ]
{
parent-module = [ load-parent $(location) ] ;
}
else
{
# It's either jamroot or standalone project. If it's jamroot,
# inherit from user-config.
if $(location)
{
# If project-config module exist, inherit from it.
if $(project-config.attributes)
{
parent-module = project-config ;
}
else
{
parent-module = user-config ;
}
jamroot = true ;
}
}
}
if $(parent-module)
{
inherit-attributes $(module-name) : $(parent-module) ;
$(attributes).set parent-module : $(parent-module) : exact ;
}
if $(jamroot)
{
$(attributes).set project-root : $(location) : exact ;
}
local parent ;
if $(parent-module)
{
parent = [ target $(parent-module) ] ;
}
if ! $(.target.$(module-name))
{
.target.$(module-name) = [ new project-target $(module-name)
: $(module-name) $(parent)
: [ attribute $(module-name) requirements ] ] ;
if --debug-loading in [ modules.peek : ARGV ]
{
ECHO "Assigned project target" $(.target.$(module-name))
"to '$(module-name)'" ;
}
}
.current-project = [ target $(module-name) ] ;
}
# Make 'project-module' inherit attributes of project root and parent module.
#
rule inherit-attributes ( project-module : parent-module )
{
local attributes = $($(project-module).attributes) ;
local pattributes = [ attributes $(parent-module) ] ;
# Parent module might be locationless configuration module.
if [ modules.binding $(parent-module) ]
{
$(attributes).set parent : [ path.parent
[ path.make [ modules.binding $(parent-module) ] ] ] ;
}
local v = [ $(pattributes).get project-root ] ;
$(attributes).set project-root : $(v) : exact ;
$(attributes).set default-build
: [ $(pattributes).get default-build ] ;
$(attributes).set requirements
: [ $(pattributes).get requirements ] : exact ;
$(attributes).set usage-requirements
: [ $(pattributes).get usage-requirements ] : exact ;
local parent-build-dir = [ $(pattributes).get build-dir ] ;
if $(parent-build-dir)
{
# Have to compute relative path from parent dir to our dir. Convert both
# paths to absolute, since we cannot find relative path from ".." to
# ".".
local location = [ attribute $(project-module) location ] ;
local parent-location = [ attribute $(parent-module) location ] ;
local pwd = [ path.pwd ] ;
local parent-dir = [ path.root $(parent-location) $(pwd) ] ;
local our-dir = [ path.root $(location) $(pwd) ] ;
$(attributes).set build-dir : [ path.join $(parent-build-dir)
[ path.relative $(our-dir) $(parent-dir) ] ] : exact ;
}
}
# Associate the given id with the given project module.
#
rule register-id ( id : module )
{
$(id).jamfile-module = $(module) ;
}
# Class keeping all the attributes of a project.
#
# The standard attributes are "id", "location", "project-root", "parent"
# "requirements", "default-build", "source-location" and "projects-to-build".
#
class project-attributes
{
import property ;
import property-set ;
import errors ;
import path ;
import print ;
import sequence ;
import project ;
rule __init__ ( location project-module )
{
self.location = $(location) ;
self.project-module = $(project-module) ;
}
# Set the named attribute from the specification given by the user. The
# value actually set may be different.
#
rule set ( attribute : specification *
: exact ? # Sets value from 'specification' without any processing.
)
{
if $(exact)
{
self.$(attribute) = $(specification) ;
}
else if $(attribute) = "requirements"
{
local result = [ property-set.refine-from-user-input
$(self.requirements) : $(specification)
: $(self.project-module) : $(self.location) ] ;
if $(result[1]) = "@error"
{
errors.error Requirements for project at '$(self.location)'
conflict with parent's. : Explanation: $(result[2-]) ;
}
else
{
self.requirements = $(result) ;
}
}
else if $(attribute) = "usage-requirements"
{
local unconditional ;
for local p in $(specification)
{
local split = [ property.split-conditional $(p) ] ;
split ?= nothing $(p) ;
unconditional += $(split[2]) ;
}
local non-free = [ property.remove free : $(unconditional) ] ;
if $(non-free)
{
errors.error usage-requirements $(specification) have non-free
properties $(non-free) ;
}
local t = [ property.translate-paths $(specification)
: $(self.location) ] ;
if $(self.usage-requirements)
{
self.usage-requirements = [ property-set.create
[ $(self.usage-requirements).raw ] $(t) ] ;
}
else
{
self.usage-requirements = [ property-set.create $(t) ] ;
}
}
else if $(attribute) = "default-build"
{
self.default-build = [ property.make $(specification) ] ;
}
else if $(attribute) = "source-location"
{
self.source-location = ;
for local src-path in $(specification)
{
self.source-location += [ path.root [ path.make $(src-path) ]
$(self.location) ] ;
}
}
else if $(attribute) = "build-dir"
{
self.build-dir = [ path.root
[ path.make $(specification) ] $(self.location) ] ;
}
else if $(attribute) = "id"
{
id = [ path.root $(specification) / ] ;
project.register-id $(id) : $(self.project-module) ;
self.id = $(id) ;
}
else if ! $(attribute) in "default-build" "location" "parent"
"projects-to-build" "project-root" "source-location"
{
errors.error Invalid project attribute '$(attribute)' specified for
project at '$(self.location)' ;
}
else
{
self.$(attribute) = $(specification) ;
}
}
# Returns the value of the given attribute.
#
rule get ( attribute )
{
return $(self.$(attribute)) ;
}
# Prints the project attributes.
#
rule print ( )
{
local id = $(self.id) ; id ?= (none) ;
local parent = $(self.parent) ; parent ?= (none) ;
print.section "'"$(id)"'" ;
print.list-start ;
print.list-item "Parent project:" $(parent) ;
print.list-item "Requirements:" [ $(self.requirements).raw ] ;
print.list-item "Default build:" $(self.default-build) ;
print.list-item "Source location:" $(self.source-location) ;
print.list-item "Projects to build:"
[ sequence.insertion-sort $(self.projects-to-build) ] ;
print.list-end ;
}
}
# Returns the project which is currently being loaded.
#
rule current ( )
{
return $(.current-project) ;
}
# Temporarily changes the current project to 'project'. Should be followed by
# 'pop-current'.
#
rule push-current ( project )
{
.saved-current-project += $(.current-project) ;
.current-project = $(project) ;
}
rule pop-current ( )
{
.current-project = $(.saved-current-project[-1]) ;
.saved-current-project = $(.saved-current-project[1--2]) ;
}
# Returns the project-attribute instance for the specified Jamfile module.
#
rule attributes ( project )
{
return $($(project).attributes) ;
}
# Returns the value of the specified attribute in the specified Jamfile module.
#
rule attribute ( project attribute )
{
return [ $($(project).attributes).get $(attribute) ] ;
}
# Returns the project target corresponding to the 'project-module'.
#
rule target ( project-module )
{
if ! $(.target.$(project-module))
{
.target.$(project-module) = [ new project-target $(project-module)
: $(project-module)
: [ attribute $(project-module) requirements ] ] ;
}
return $(.target.$(project-module)) ;
}
# Use/load a project.
#
rule use ( id : location )
{
local saved-project = $(.current-project) ;
local project-module = [ project.load $(location) ] ;
local declared-id = [ project.attribute $(project-module) id ] ;
if ! $(declared-id) || $(declared-id) != $(id)
{
# The project at 'location' either has no id or that id is not equal to
# the 'id' parameter.
if $($(id).jamfile-module) && ( $($(id).jamfile-module) !=
$(project-module) )
{
errors.user-error Attempt to redeclare already existing project id
'$(id)'
location '$(location)' ;
}
$(id).jamfile-module = $(project-module) ;
}
.current-project = $(saved-project) ;
}
# Defines a Boost.Build extension project. Such extensions usually contain
# library targets and features that can be used by many people. Even though
# extensions are really projects, they can be initialized as a module would be
# with the "using" (project.project-rules.using) mechanism.
#
rule extension ( id : options * : * )
{
# The caller is a standalone module for the extension.
local mod = [ CALLER_MODULE ] ;
# We need to do the rest within the extension module.
module $(mod)
{
import path ;
# Find the root project.
local root-project = [ project.current ] ;
root-project = [ $(root-project).project-module ] ;
while
[ project.attribute $(root-project) parent-module ] &&
[ project.attribute $(root-project) parent-module ] != user-config
{
root-project = [ project.attribute $(root-project) parent-module ] ;
}
# Create the project data, and bring in the project rules into the
# module.
project.initialize $(__name__) : [ path.join [ project.attribute
$(root-project) location ] ext $(1:L) ] ;
# Create the project itself, i.e. the attributes. All extensions are
# created in the "/ext" project space.
project /ext/$(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) :
$(9) ;
local attributes = [ project.attributes $(__name__) ] ;
# Inherit from the root project of whomever is defining us.
project.inherit-attributes $(__name__) : $(root-project) ;
$(attributes).set parent-module : $(root-project) : exact ;
}
}
rule glob-internal ( project : wildcards + : excludes * : rule-name )
{
local location = [ $(project).get source-location ] ;
local result ;
local paths = [ path.$(rule-name) $(location) :
[ sequence.transform path.make : $(wildcards) ] :
[ sequence.transform path.make : $(excludes) ] ] ;
if $(wildcards:D) || $(rule-name) != glob
{
# The paths we have found are relative to the current directory, but the
# names specified in the sources list are assumed to be relative to the
# source directory of the corresponding project. So, just make the names
# absolute.
for local p in $(paths)
{
# If the path is below source location, use relative path.
# Otherwise, use full path just to avoid any ambiguities.
local rel = [ path.relative $(p) $(location) : no-error ] ;
if $(rel) = not-a-child
{
result += [ path.root $(p) [ path.pwd ] ] ;
}
else
{
result += $(rel) ;
}
}
}
else
{
# There were no wildcards in the directory path, so the files are all in
# the source directory of the project. Just drop the directory, instead
# of making paths absolute.
result = $(paths:D="") ;
}
return $(result) ;
}
# This module defines rules common to all projects.
#
module project-rules
{
rule using ( toolset-module : * )
{
import toolset ;
import modules ;
import project ;
# Temporarily change the search path so the module referred to by
# 'using' can be placed in the same directory as Jamfile. User will
# expect the module to be found even though the directory is not in
# BOOST_BUILD_PATH.
local x = [ modules.peek : BOOST_BUILD_PATH ] ;
local caller = [ modules.binding $(__name__) ] ;
modules.poke : BOOST_BUILD_PATH : $(caller:D) $(x) ;
toolset.using $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
modules.poke : BOOST_BUILD_PATH : $(x) ;
# The above might have clobbered .current-project. Restore the correct
# value.
modules.poke project : .current-project
: [ project.target $(__name__) ] ;
}
import modules ;
rule import ( * : * : * )
{
modules.import project ;
local caller = [ CALLER_MODULE ] ;
local saved = [ modules.peek project : .current-project ] ;
module $(caller)
{
modules.import $(1) : $(2) : $(3) ;
}
modules.poke project : .current-project : $(saved) ;
}
rule project ( id ? : options * : * )
{
import errors ;
import path ;
import project ;
local attributes = [ project.attributes $(__name__) ] ;
if $(id)
{
$(attributes).set id : $(id) ;
}
local explicit-build-dir ;
for n in 2 3 4 5 6 7 8 9
{
local option = $($(n)) ;
if $(option)
{
$(attributes).set $(option[1]) : $(option[2-]) ;
}
if $(option[1]) = "build-dir"
{
explicit-build-dir = [ path.make $(option[2-]) ] ;
}
}
# If '--build-dir' is specified, change the build dir for the project.
local global-build-dir =
[ modules.peek project : .global-build-dir ] ;
if $(global-build-dir)
{
local location = [ $(attributes).get location ] ;
# Project with an empty location is a 'standalone' project such as
# user-config or qt. It has no build dir. If we try to set build dir
# for user-config, we shall then try to inherit it, with either
# weird or wrong consequences.
if $(location) && $(location) = [ $(attributes).get project-root ]
{
# Re-read the project id, since it might have been changed in
# the project's attributes.
id = [ $(attributes).get id ] ;
# This is Jamroot.
if $(id)
{
if $(explicit-build-dir) &&
[ path.is-rooted $(explicit-build-dir) ]
{
errors.user-error Absolute directory specified via
'build-dir' project attribute : Do not know how to
combine that with the --build-dir option. ;
}
# Strip the leading slash from id.
local rid = [ MATCH /(.*) : $(id) ] ;
local p = [ path.join
$(global-build-dir) $(rid) $(explicit-build-dir) ] ;
$(attributes).set build-dir : $(p) : exact ;
}
}
else
{
# Not Jamroot.
if $(explicit-build-dir)
{
errors.user-error When --build-dir is specified, the
'build-dir' project : attribute is allowed only for
top-level 'project' invocations ;
}
}
}
}
# Declare and set a project global constant. Project global constants are
# normal variables but should not be changed. They are applied to every
# child Jamfile.
#
rule constant (
name # Variable name of the constant.
: value + # Value of the constant.
)
{
import project ;
local p = [ project.target $(__name__) ] ;
$(p).add-constant $(name) : $(value) ;
}
# Declare and set a project global constant, whose value is a path. The path
# is adjusted to be relative to the invocation directory. The given value
# path is taken to be either absolute, or relative to this project root.
#
rule path-constant (
name # Variable name of the constant.
: value + # Value of the constant.
)
{
import project ;
local p = [ project.target $(__name__) ] ;
$(p).add-constant $(name) : $(value) : path ;
}
rule use-project ( id : where )
{
# See comment in 'load' for explanation.
.used-projects += $(id) $(where) ;
}
rule build-project ( dir )
{
import project ;
local attributes = [ project.attributes $(__name__) ] ;
local now = [ $(attributes).get projects-to-build ] ;
$(attributes).set projects-to-build : $(now) $(dir) ;
}
rule explicit ( target-names * )
{
import project ;
# If 'explicit' is used in a helper rule defined in Jamroot and
# inherited by children, then most of the time we want 'explicit' to
# operate on the Jamfile where the helper rule is invoked.
local t = [ project.current ] ;
for local n in $(target-names)
{
$(t).mark-target-as-explicit $(n) ;
}
}
rule always ( target-names * )
{
import project ;
local t = [ project.current ] ;
for local n in $(target-names)
{
$(t).mark-target-as-always $(n) ;
}
}
rule glob ( wildcards + : excludes * )
{
import project ;
return [ project.glob-internal [ project.current ] : $(wildcards) :
$(excludes) : glob ] ;
}
rule glob-tree ( wildcards + : excludes * )
{
import project ;
if $(wildcards:D) || $(excludes:D)
{
errors.user-error The patterns to 'glob-tree' may not include
directory ;
}
return [ project.glob-internal [ project.current ] : $(wildcards) :
$(excludes) : glob-tree ] ;
}
# Calculates conditional requirements for multiple requirements at once.
# This is a shorthand to reduce duplication and to keep an inline
# declarative syntax. For example:
#
# lib x : x.cpp : [ conditional <toolset>gcc <variant>debug :
# <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ;
#
rule conditional ( condition + : requirements * )
{
local condition = $(condition:J=,) ;
if [ MATCH (:) : $(condition) ]
{
return $(condition)$(requirements) ;
}
else
{
return $(condition):$(requirements) ;
}
}
rule option ( name : value )
{
if $(__name__) != site-config && $(__name__) != user-config && $(__name__) != project-config
{
import errors ;
errors.error "The 'option' rule may be used only in site-config or user-config" ;
}
import option ;
option.set $(name) : $(value) ;
}
}