early-access version 3088

This commit is contained in:
pineappleEA
2022-11-05 15:35:56 +01:00
parent 4e4fc25ce3
commit b601909c6d
35519 changed files with 5996896 additions and 860 deletions

View File

@@ -0,0 +1,326 @@
# Copyright (c) 2010 Vladimir Prus.
# Copyright (c) 2013 Steven Watanabe
# Copyright (c) 2021 Rene Ferdinand Rivera Morell
#
# Use, modification and distribution is subject to the Boost Software
# License Version 1.0. (See accompanying file LICENSE.txt or
# https://www.bfgroup.xyz/b2/LICENSE.txt)
import property-set ;
import path ;
import modules ;
import "class" ;
import errors ;
import configure ;
import feature ;
import project ;
import virtual-target ;
import generators ;
import property ;
import print ;
import regex ;
project.initialize $(__name__) ;
.project = [ project.current ] ;
project ac ;
feature.feature ac.print-text : : free ;
rule generate-include ( target : sources * : properties * )
{
print.output $(target) ;
local text = [ property.select <ac.print-text> : $(properties) ] ;
if $(text)
{
print.text $(text:G=) : true ;
}
else
{
local header = [ property.select <include> : $(properties) ] ;
print.text "#include <$(header:G=)>\n" : true ;
}
}
rule generate-main ( target : sources * : properties * )
{
print.output $(target) ;
print.text "int main() {}" : true ;
}
rule find-include-path ( properties : header : provided-path ? : test-source ? )
{
if $(provided-path) && [ path.exists [ path.root $(header) $(provided-path) ] ]
{
return $(provided-path) ;
}
else
{
local a = [ class.new action : ac.generate-include : [ property-set.create <include>$(header) <ac.print-text>$(test-source) ] ] ;
# Create a new CPP target named after the header.
# Replace dots (".") in target basename for portability.
local basename = [ regex.replace $(header:D=) "[.]" "_" ] ;
local header-target = $(header:S=:B=$(basename)) ;
local cpp = [ class.new file-target $(header-target:S=.cpp) exact : CPP : $(.project) : $(a) ] ;
cpp = [ virtual-target.register $(cpp) ] ;
$(cpp).root true ;
local result = [ generators.construct $(.project) $(header-target) : OBJ : $(properties) : $(cpp) : true ] ;
configure.maybe-force-rebuild $(result[2-]) ;
local jam-targets ;
for local t in $(result[2-])
{
jam-targets += [ $(t).actualize ] ;
}
if [ UPDATE_NOW $(jam-targets) : [ modules.peek configure : .log-fd ]
: ignore-minus-n ]
{
return %default ;
}
}
}
rule construct-library ( name : property-set : provided-path ? )
{
local lib-props = [ $(property-set).add-raw <name>$(name) <search>$(provided-path) ] ;
return [ generators.construct $(.project) lib-$(name)
: SEARCHED_LIB : $(lib-props) : : true ] ;
}
rule find-library ( properties : names + : provided-path ? )
{
local result ;
if [ $(properties).get <link> ] = shared
{
link-opts = <link>shared <link>static ;
}
else
{
link-opts = <link>static <link>shared ;
}
while $(link-opts)
{
local names-iter = $(names) ;
properties = [ $(properties).refine [ property-set.create $(link-opts[1]) ] ] ;
while $(names-iter)
{
local name = $(names-iter[1]) ;
local lib = [ construct-library $(name) : $(properties) : $(provided-path) ] ;
local a = [ class.new action : ac.generate-main :
[ property-set.empty ] ] ;
local main.cpp = [ virtual-target.register
[ class.new file-target main-$(name).cpp exact : CPP : $(.project) : $(a) ] ] ;
$(main.cpp).root true ;
local test = [ generators.construct $(.project) $(name) : EXE
: [ $(properties).add $(lib[1]) ] : $(main.cpp) $(lib[2-])
: true ] ;
configure.maybe-force-rebuild $(test[2-]) ;
local jam-targets ;
for t in $(test[2-])
{
jam-targets += [ $(t).actualize ] ;
}
if [ UPDATE_NOW $(jam-targets) : [ modules.peek configure : .log-fd ]
: ignore-minus-n ]
{
result = $(name) $(link-opts[1]) ;
names-iter = ; link-opts = ; # break
}
names-iter = $(names-iter[2-]) ;
}
link-opts = $(link-opts[2-]) ;
}
return $(result) ;
}
class ac-library : basic-target
{
import errors ;
import indirect ;
import virtual-target ;
import ac ;
import configure ;
import config-cache ;
import os ;
rule __init__ ( name : project : requirements * : include-path ? : library-path ? : library-name ? )
{
basic-target.__init__ $(name) : $(project) : : $(requirements) ;
reconfigure $(include-path) : $(library-path) : $(library-name) ;
}
rule set-header ( header )
{
self.header = $(header) ;
}
rule set-default-names ( names + )
{
self.default-names = $(names) ;
}
rule set-header-test ( source )
{
self.header-test = $(source) ;
}
rule reconfigure ( include-path ? : library-path ? : library-name ? )
{
if $(include-path) || $(library-path) || $(library-name)
{
check-not-configured ;
self.include-path = $(include-path) ;
self.library-path = $(library-path) ;
self.library-name = $(library-name) ;
}
}
rule set-target ( target )
{
check-not-configured ;
self.target = $(target) ;
}
rule check-not-configured ( )
{
if $(self.include-path) || $(self.library-path) || $(self.library-name) || $(self.target)
{
errors.user-error [ name ] "is already configured" ;
}
}
rule construct ( name : sources * : property-set )
{
if $(self.target)
{
return [ $(self.target).generate $(property-set) ] ;
}
else
{
local use-environment ;
if ! $(self.library-name) && ! $(self.include-path) && ! $(self.library-path)
{
use-environment = true ;
}
local libnames = $(self.library-name) ;
if ! $(libnames) && $(use-environment)
{
libnames = [ os.environ $(name:U)_NAME ] ;
# Backward compatibility only.
libnames ?= [ os.environ $(name:U)_BINARY ] ;
}
libnames ?= $(self.default-names) ;
local include-path = $(self.include-path) ;
if ! $(include-path) && $(use-environment)
{
include-path = [ os.environ $(name:U)_INCLUDE ] ;
}
local library-path = $(self.library-path) ;
if ! $(library-path) && $(use-environment)
{
library-path = [ os.environ $(name:U)_LIBRARY_PATH ] ;
# Backwards compatibility only
library-path ?= [ os.environ $(name:U)_LIBPATH ] ;
}
local relevant = [ property.select [ configure.get-relevant-features ] <link> :
[ $(property-set).raw ] ] ;
local min = [ property.as-path [ SORT [ feature.minimize $(relevant) ] ] ] ;
local key = ac-library-$(name)-$(relevant:J=-) ;
local lookup = [ config-cache.get $(key) ] ;
if $(lookup)
{
if $(lookup) = missing
{
configure.log-library-search-result $(name) : "no (cached)" $(min) ;
return [ property-set.empty ] ;
}
else
{
local includes = $(lookup[1]) ;
if $(includes) = %default
{
includes = ;
}
local library = [ ac.construct-library $(lookup[2]) :
[ $(property-set).refine [ property-set.create $(lookup[3]) ] ] : $(library-path) ] ;
configure.log-library-search-result $(name) : "yes (cached)" $(min) ;
return [ $(library[1]).add-raw <include>$(includes) ] $(library[2-]) ;
}
}
else
{
local includes = [ ac.find-include-path $(property-set) : $(self.header) : $(include-path) : $(self.header-test) ] ;
local library = [ ac.find-library $(property-set) : $(libnames) : $(library-path) ] ;
if $(includes) && $(library)
{
config-cache.set $(key) : $(includes) $(library) ;
if $(includes) = %default
{
includes = ;
}
library = [ ac.construct-library $(library[1]) :
[ $(property-set).refine [ property-set.create $(library[2]) ] ] : $(library-path) ] ;
configure.log-library-search-result $(name) : "yes" $(min) ;
return [ $(library[1]).add-raw <include>$(includes) ] $(library[2-]) ;
}
else
{
config-cache.set $(key) : missing ;
configure.log-library-search-result $(name) : "no" $(min) ;
return [ property-set.empty ] ;
}
}
}
}
}
class check-library-worker
{
import property-set ;
import targets ;
import property ;
rule __init__ ( target : true-properties * : false-properties * )
{
self.target = $(target) ;
self.true-properties = $(true-properties) ;
self.false-properties = $(false-properties) ;
}
rule check ( properties * )
{
local choosen ;
local t = [ targets.current ] ;
local p = [ $(t).project ] ;
local ps = [ property-set.create $(properties) ] ;
ps = [ $(ps).propagated ] ;
local generated =
[ targets.generate-from-reference $(self.target) : $(p) : $(ps) ] ;
if $(generated[2])
{
choosen = $(self.true-properties) ;
}
else
{
choosen = $(self.false-properties) ;
}
return [ property.evaluate-conditionals-in-context $(choosen) :
$(properties) ] ;
}
}
rule check-library ( target : true-properties * : false-properties * )
{
local instance = [ class.new check-library-worker $(target) :
$(true-properties) : $(false-properties) ] ;
return <conditional>@$(instance).check
[ property.evaluate-conditional-relevance
$(true-properties) $(false-properties)
: [ configure.get-relevant-features ] <link> ] ;
}

View File

@@ -0,0 +1,82 @@
# Copyright 2003, 2004, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.bfgroup.xyz/b2/LICENSE.txt)
# This module defines the 'alias' rule and the associated target class.
#
# Alias is just a main target which returns its source targets without any
# processing. For example:
#
# alias bin : hello test_hello ;
# alias lib : helpers xml_parser ;
#
# Another important use of 'alias' is to conveniently group source files:
#
# alias platform-src : win.cpp : <os>NT ;
# alias platform-src : linux.cpp : <os>LINUX ;
# exe main : main.cpp platform-src ;
#
# Lastly, it is possible to create a local alias for some target, with different
# properties:
#
# alias big_lib : : @/external_project/big_lib/<link>static ;
#
import "class" : new ;
import param ;
import project ;
import property-set ;
import targets ;
class alias-target-class : basic-target
{
rule __init__ ( name : project : sources * : requirements *
: default-build * : usage-requirements * )
{
basic-target.__init__ $(name) : $(project) : $(sources) :
$(requirements) : $(default-build) : $(usage-requirements) ;
}
rule construct ( name : source-targets * : property-set )
{
return [ property-set.empty ] $(source-targets) ;
}
rule compute-usage-requirements ( subvariant )
{
local base = [ basic-target.compute-usage-requirements $(subvariant) ] ;
return [ $(base).add [ $(subvariant).sources-usage-requirements ] ] ;
}
rule skip-from-usage-requirements ( )
{
}
}
# Declares the 'alias' target. It will process its sources virtual-targets by
# returning them unaltered as its own constructed virtual-targets.
#
rule alias ( name : sources * : requirements * : default-build * :
usage-requirements * )
{
param.handle-named-params
sources requirements default-build usage-requirements ;
local project = [ project.current ] ;
targets.main-target-alternative
[ new alias-target-class $(name) : $(project)
: [ targets.main-target-sources $(sources) : $(name) : no-renaming ]
: [ targets.main-target-requirements $(requirements) : $(project) ]
: [ targets.main-target-default-build $(default-build) : $(project)
]
: [ targets.main-target-usage-requirements $(usage-requirements) :
$(project) ]
] ;
}
IMPORT $(__name__) : alias : : alias ;

View File

@@ -0,0 +1,75 @@
# Copyright 2003, 2004, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
# Status: ported (danielw)
# Base revision: 56043
# This module defines the 'alias' rule and associated class.
#
# Alias is just a main target which returns its source targets without any
# processing. For example::
#
# alias bin : hello test_hello ;
# alias lib : helpers xml_parser ;
#
# Another important use of 'alias' is to conveniently group source files::
#
# alias platform-src : win.cpp : <os>NT ;
# alias platform-src : linux.cpp : <os>LINUX ;
# exe main : main.cpp platform-src ;
#
# Lastly, it's possible to create local alias for some target, with different
# properties::
#
# alias big_lib : : @/external_project/big_lib/<link>static ;
#
import targets
import property_set
from b2.manager import get_manager
from b2.util import metatarget, is_iterable_typed
class AliasTarget(targets.BasicTarget):
def __init__(self, *args):
targets.BasicTarget.__init__(self, *args)
def construct(self, name, source_targets, properties):
if __debug__:
from .virtual_target import VirtualTarget
assert isinstance(name, basestring)
assert is_iterable_typed(source_targets, VirtualTarget)
assert isinstance(properties, property_set.PropertySet)
return [property_set.empty(), source_targets]
def compute_usage_requirements(self, subvariant):
if __debug__:
from .virtual_target import Subvariant
assert isinstance(subvariant, Subvariant)
base = targets.BasicTarget.compute_usage_requirements(self, subvariant)
# Add source's usage requirement. If we don't do this, "alias" does not
# look like 100% alias.
return base.add(subvariant.sources_usage_requirements())
@metatarget
def alias(name, sources=[], requirements=[], default_build=[], usage_requirements=[]):
assert isinstance(name, basestring)
assert is_iterable_typed(sources, basestring)
assert is_iterable_typed(requirements, basestring)
assert is_iterable_typed(default_build, basestring)
assert is_iterable_typed(usage_requirements, basestring)
project = get_manager().projects().current()
targets = get_manager().targets()
targets.main_target_alternative(AliasTarget(
name, project,
targets.main_target_sources(sources, name, no_renaming=True),
targets.main_target_requirements(requirements or [], project),
targets.main_target_default_build(default_build, project),
targets.main_target_usage_requirements(usage_requirements or [], project)))
# Declares the 'alias' target. It will build sources, and return them unaltered.
get_manager().projects().add_rule("alias", alias)

View File

@@ -0,0 +1,418 @@
# Copyright 2002 Dave Abrahams
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
import "class" : new ;
import sequence ;
import set ;
import regex ;
import feature ;
import property ;
import container ;
import string ;
# Transform property-set by applying f to each component property.
#
local rule apply-to-property-set ( f property-set )
{
local properties = [ feature.split $(property-set) ] ;
return [ string.join [ $(f) $(properties) ] : / ] ;
}
# Expand the given build request by combining all property-sets which do not
# specify conflicting non-free features. Expects all the project files to
# already be loaded.
#
rule expand-no-defaults ( property-sets * )
{
# First make all features and subfeatures explicit.
local expanded-property-sets = [ sequence.transform apply-to-property-set
feature.expand-subfeatures : $(property-sets) ] ;
# Now combine all of the expanded property-sets
local product = [ x-product $(expanded-property-sets) : $(feature-space) ] ;
return $(product) ;
}
# Update the list of expected conflicts based on the new
# features.
#
local rule remove-conflicts ( conflicts * : features * )
{
local result ;
for local c in $(conflicts)
{
if ! [ set.intersection [ regex.split $(c) "/" ] : $(features) ]
{
result += $(c) ;
}
}
return $(result) ;
}
# Implementation of x-product, below. Expects all the project files to already
# be loaded.
#
local rule x-product-aux ( property-sets + )
{
local result ;
local p = [ feature.split $(property-sets[1]) ] ;
local f = [ set.difference $(p:G) : [ feature.free-features ] ] ;
local seen ;
local extra-conflicts ;
# No conflict with things used at a higher level?
if ! [ set.intersection $(f) : $(x-product-used) ]
{
local x-product-seen ;
local x-product-conflicts =
[ remove-conflicts $(x-product-conflicts) : $(f) ] ;
{
# Do not mix in any conflicting features.
local x-product-used = $(x-product-used) $(f) ;
if $(property-sets[2])
{
local rest = [ x-product-aux $(property-sets[2-]) ] ;
result = $(property-sets[1])/$(rest) ;
}
if ! $(x-product-conflicts)
{
result ?= $(property-sets[1]) ;
}
}
# If we did not encounter a conflicting feature lower down, do not
# recurse again.
if ! [ set.intersection $(f) : $(x-product-seen) ]
|| [ remove-conflicts $(x-product-conflicts) : $(x-product-seen) ]
{
property-sets = ;
}
else
{
# A property is only allowed to be absent if it conflicts
# with either a higher or lower layer. We don't need to
# bother setting this if we already know that we don't need
# to recurse again.
extra-conflicts = $(f:J=/) ;
}
seen = $(x-product-seen) ;
}
if $(property-sets[2])
{
# Lower layers expansion must conflict with this
local x-product-conflicts = $(x-product-conflicts) $(extra-conflicts) ;
result += [ x-product-aux $(property-sets[2-]) ] ;
}
# Note that we have seen these features so that higher levels will recurse
# again without them set.
x-product-seen += $(f) $(seen) ;
return $(result) ;
}
# Return the cross-product of all elements of property-sets, less any that would
# contain conflicting values for single-valued features. Expects all the project
# files to already be loaded.
#
# Formal definition:
# Returns all maximum non-conflicting subsets of property-sets.
# The result is a list of all property-sets p such that
# 1. p is composed by joining a subset of property-sets without removing
# duplicates
# 2. p contains at most one instance of every single-valued feature
# 3. Adding any additional element of property-sets to p be would
# violate (2)
local rule x-product ( property-sets * )
{
if $(property-sets).non-empty
{
# Prepare some "scoped globals" that can be used by the implementation
# function, x-product-aux.
local x-product-seen x-product-used x-product-conflicts ;
return [ x-product-aux $(property-sets) : $(feature-space) ] ;
}
# Otherwise return empty.
}
# Returns true if either 'v' or the part of 'v' before the first '-' symbol is
# an implicit value. Expects all the project files to already be loaded.
#
local rule looks-like-implicit-value ( v )
{
if [ feature.is-implicit-value $(v) ]
{
return true ;
}
else
{
local split = [ regex.split $(v) - ] ;
if [ feature.is-implicit-value $(split[1]) ]
{
return true ;
}
}
}
# Takes the command line tokens (such as taken from the ARGV rule) and
# constructs a build request from them. Returns a vector of two vectors (where
# "vector" means container.jam's "vector"). First is the set of targets
# specified in the command line, and second is the set of requested build
# properties. Expects all the project files to already be loaded.
#
rule from-command-line ( command-line * )
{
local targets ;
local properties ;
command-line = $(command-line[2-]) ;
local skip-next = ;
for local e in $(command-line)
{
if $(skip-next)
{
skip-next = ;
}
else if ! [ MATCH ^(-) : $(e) ]
{
# Build request spec either has "=" in it or completely consists of
# implicit feature values.
local fs = feature-space ;
if [ MATCH "(.*=.*)" : $(e) ]
|| [ looks-like-implicit-value $(e:D=) : $(feature-space) ]
{
properties += $(e) ;
}
else if $(e)
{
targets += $(e) ;
}
}
else if [ MATCH "^(-[-ldjfsto])$" : $(e) ]
{
skip-next = true ;
}
}
return [ new vector
[ new vector $(targets) ]
[ new vector $(properties) ] ] ;
}
# Converts a list of elements of command line build request specification into internal
# form. Expects all the project files to already be loaded.
#
rule convert-command-line-elements ( elements * )
{
local result ;
for local e in $(elements)
{
result += [ convert-command-line-element $(e) ] ;
}
return $(result) ;
}
# Converts one element of command line build request specification into internal
# form.
local rule convert-command-line-element ( e )
{
local result ;
local parts = [ regex.split $(e) "/" ] ;
while $(parts)
{
local p = $(parts[1]) ;
local m = [ MATCH "([^=]*)=(.*)" : $(p) ] ;
local lresult ;
local feature ;
local values ;
if $(m)
{
feature = $(m[1]) ;
values = [ regex.split $(m[2]) "," ] ;
lresult = <$(feature)>$(values) ;
}
else
{
lresult = [ regex.split $(p) "," ] ;
}
if $(feature) && free in [ feature.attributes <$(feature)> ]
{
# If we have free feature, then the value is everything
# until the end of the command line token. Slashes in
# the following string are not taked to mean separation
# of properties. Commas are also not interpreted specially.
values = $(values:J=,) ;
values = $(values) $(parts[2-]) ;
values = $(values:J=/) ;
lresult = ;
# Optional free features will ignore empty value arguments.
if optional in [ feature.attributes <$(feature)> ]
{
for local v in $(values)
{
if $(v)
{
lresult += <$(feature)>$(v) ;
}
}
}
else
{
lresult = <$(feature)>$(values) ;
}
parts = ;
}
if ! [ MATCH (.*-.*) : $(p) ]
{
# property.validate cannot handle subfeatures, so we avoid the check
# here.
for local p in $(lresult)
{
property.validate $(p) : $(feature-space) ;
}
}
if $(lresult)
{
if ! $(result)
{
result = $(lresult) ;
}
else
{
result = $(result)/$(lresult) ;
}
}
parts = $(parts[2-]) ;
}
return $(result) ;
}
rule __test__ ( )
{
import assert ;
import feature ;
feature.prepare-test build-request-test-temp ;
import build-request ;
import build-request : expand-no-defaults : build-request.expand-no-defaults ;
import errors : try catch ;
import feature : feature subfeature ;
feature toolset : gcc msvc borland : implicit ;
subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
3.0 3.0.1 3.0.2 : optional ;
feature variant : debug release : implicit composite ;
feature inlining : on off ;
feature "include" : : free ;
feature stdlib : native stlport : implicit ;
feature runtime-link : dynamic static : symmetric ;
# Empty build requests should expand to empty.
assert.result
: build-request.expand-no-defaults ;
assert.result
<toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug
<toolset>msvc/<stdlib>stlport/<variant>debug
<toolset>msvc/<variant>debug
: build-request.expand-no-defaults gcc-3.0.1/stlport msvc/stlport msvc debug ;
assert.result
<toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug
<toolset>msvc/<variant>debug
<variant>debug/<toolset>msvc/<stdlib>stlport
: build-request.expand-no-defaults gcc-3.0.1/stlport msvc debug msvc/stlport ;
assert.result
<toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug/<inlining>off
<toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>release/<inlining>off
: build-request.expand-no-defaults gcc-3.0.1/stlport debug release <inlining>off ;
assert.result
<include>a/b/c/<toolset>gcc/<toolset-gcc:version>3.0.1/<stdlib>stlport/<variant>debug/<include>x/y/z
<include>a/b/c/<toolset>msvc/<stdlib>stlport/<variant>debug/<include>x/y/z
<include>a/b/c/<toolset>msvc/<variant>debug/<include>x/y/z
: build-request.expand-no-defaults <include>a/b/c gcc-3.0.1/stlport msvc/stlport msvc debug <include>x/y/z ;
local r ;
try ;
{
r = [ build-request.from-command-line bjam gcc/debug runtime-link=dynamic/static ] ;
build-request.convert-command-line-elements [ $(r).get-at 2 ] ;
}
catch \"static\" is not an implicit feature value ;
r = [ build-request.from-command-line bjam debug runtime-link=dynamic ] ;
assert.equal [ $(r).get-at 1 ] : ;
assert.equal [ $(r).get-at 2 ] : debug runtime-link=dynamic ;
assert.equal
[ build-request.convert-command-line-elements debug runtime-link=dynamic ]
: debug <runtime-link>dynamic ;
r = [ build-request.from-command-line bjam -d2 --debug debug target runtime-link=dynamic ] ;
assert.equal [ $(r).get-at 1 ] : target ;
assert.equal [ $(r).get-at 2 ] : debug runtime-link=dynamic ;
assert.equal
[ build-request.convert-command-line-elements debug runtime-link=dynamic ]
: debug <runtime-link>dynamic ;
r = [ build-request.from-command-line bjam debug runtime-link=dynamic,static ] ;
assert.equal [ $(r).get-at 1 ] : ;
assert.equal [ $(r).get-at 2 ] : debug runtime-link=dynamic,static ;
assert.equal
[ build-request.convert-command-line-elements debug runtime-link=dynamic,static ]
: debug <runtime-link>dynamic <runtime-link>static ;
r = [ build-request.from-command-line bjam debug gcc/runtime-link=dynamic,static ] ;
assert.equal [ $(r).get-at 1 ] : ;
assert.equal [ $(r).get-at 2 ] : debug gcc/runtime-link=dynamic,static ;
assert.equal
[ build-request.convert-command-line-elements debug gcc/runtime-link=dynamic,static ]
: debug gcc/<runtime-link>dynamic gcc/<runtime-link>static ;
r = [ build-request.from-command-line bjam msvc gcc,borland/runtime-link=static ] ;
assert.equal [ $(r).get-at 1 ] : ;
assert.equal [ $(r).get-at 2 ] : msvc gcc,borland/runtime-link=static ;
assert.equal
[ build-request.convert-command-line-elements msvc gcc,borland/runtime-link=static ]
: msvc gcc/<runtime-link>static borland/<runtime-link>static ;
r = [ build-request.from-command-line bjam gcc-3.0 ] ;
assert.equal [ $(r).get-at 1 ] : ;
assert.equal [ $(r).get-at 2 ] : gcc-3.0 ;
assert.equal
[ build-request.convert-command-line-elements gcc-3.0 ]
: gcc-3.0 ;
feature.finish-test build-request-test-temp ;
}

View File

@@ -0,0 +1,222 @@
# Status: being ported by Vladimir Prus
# TODO: need to re-compare with mainline of .jam
# Base revision: 40480
#
# (C) Copyright David Abrahams 2002. Permission to copy, use, modify, sell and
# distribute this software is granted provided this copyright notice appears in
# all copies. This software is provided "as is" without express or implied
# warranty, and with no claim as to its suitability for any purpose.
import b2.build.feature
feature = b2.build.feature
from b2.util.utility import *
from b2.util import is_iterable_typed
import b2.build.property_set as property_set
def expand_no_defaults (property_sets):
""" Expand the given build request by combining all property_sets which don't
specify conflicting non-free features.
"""
assert is_iterable_typed(property_sets, property_set.PropertySet)
# First make all features and subfeatures explicit
expanded_property_sets = [ps.expand_subfeatures() for ps in property_sets]
# Now combine all of the expanded property_sets
product = __x_product (expanded_property_sets)
return [property_set.create(p) for p in product]
def __x_product (property_sets):
""" Return the cross-product of all elements of property_sets, less any
that would contain conflicting values for single-valued features.
"""
assert is_iterable_typed(property_sets, property_set.PropertySet)
x_product_seen = set()
return __x_product_aux (property_sets, x_product_seen)[0]
def __x_product_aux (property_sets, seen_features):
"""Returns non-conflicting combinations of property sets.
property_sets is a list of PropertySet instances. seen_features is a set of Property
instances.
Returns a tuple of:
- list of lists of Property instances, such that within each list, no two Property instance
have the same feature, and no Property is for feature in seen_features.
- set of features we saw in property_sets
"""
assert is_iterable_typed(property_sets, property_set.PropertySet)
assert isinstance(seen_features, set)
if not property_sets:
return ([], set())
properties = property_sets[0].all()
these_features = set()
for p in property_sets[0].non_free():
these_features.add(p.feature)
# Note: the algorithm as implemented here, as in original Jam code, appears to
# detect conflicts based on features, not properties. For example, if command
# line build request say:
#
# <a>1/<b>1 c<1>/<b>1
#
# It will decide that those two property sets conflict, because they both specify
# a value for 'b' and will not try building "<a>1 <c1> <b1>", but rather two
# different property sets. This is a topic for future fixing, maybe.
if these_features & seen_features:
(inner_result, inner_seen) = __x_product_aux(property_sets[1:], seen_features)
return (inner_result, inner_seen | these_features)
else:
result = []
(inner_result, inner_seen) = __x_product_aux(property_sets[1:], seen_features | these_features)
if inner_result:
for inner in inner_result:
result.append(properties + inner)
else:
result.append(properties)
if inner_seen & these_features:
# Some of elements in property_sets[1:] conflict with elements of property_sets[0],
# Try again, this time omitting elements of property_sets[0]
(inner_result2, inner_seen2) = __x_product_aux(property_sets[1:], seen_features)
result.extend(inner_result2)
return (result, inner_seen | these_features)
def looks_like_implicit_value(v):
"""Returns true if 'v' is either implicit value, or
the part before the first '-' symbol is implicit value."""
assert isinstance(v, basestring)
if feature.is_implicit_value(v):
return 1
else:
split = v.split("-")
if feature.is_implicit_value(split[0]):
return 1
return 0
def from_command_line(command_line):
"""Takes the command line tokens (such as taken from ARGV rule)
and constructs build request from it. Returns a list of two
lists. First is the set of targets specified in the command line,
and second is the set of requested build properties."""
assert is_iterable_typed(command_line, basestring)
targets = []
properties = []
for e in command_line:
if e[:1] != "-":
# Build request spec either has "=" in it, or completely
# consists of implicit feature values.
if e.find("=") != -1 or looks_like_implicit_value(e.split("/")[0]):
properties.append(e)
elif e:
targets.append(e)
return [targets, properties]
# Converts one element of command line build request specification into
# internal form.
def convert_command_line_element(e):
assert isinstance(e, basestring)
result = None
parts = e.split("/")
for p in parts:
m = p.split("=")
if len(m) > 1:
feature = m[0]
values = m[1].split(",")
lresult = [("<%s>%s" % (feature, v)) for v in values]
else:
lresult = p.split(",")
if p.find('-') == -1:
# FIXME: first port property.validate
# property.validate cannot handle subfeatures,
# so we avoid the check here.
#for p in lresult:
# property.validate(p)
pass
if not result:
result = lresult
else:
result = [e1 + "/" + e2 for e1 in result for e2 in lresult]
return [property_set.create(b2.build.feature.split(r)) for r in result]
###
### rule __test__ ( )
### {
### import assert feature ;
###
### feature.prepare-test build-request-test-temp ;
###
### import build-request ;
### import build-request : expand_no_defaults : build-request.expand_no_defaults ;
### import errors : try catch ;
### import feature : feature subfeature ;
###
### feature toolset : gcc msvc borland : implicit ;
### subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
### 3.0 3.0.1 3.0.2 : optional ;
###
### feature variant : debug release : implicit composite ;
### feature inlining : on off ;
### feature "include" : : free ;
###
### feature stdlib : native stlport : implicit ;
###
### feature runtime-link : dynamic static : symmetric ;
###
###
### local r ;
###
### r = [ build-request.from-command-line bjam debug runtime-link=dynamic ] ;
### assert.equal [ $(r).get-at 1 ] : ;
### assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic ;
###
### try ;
### {
###
### build-request.from-command-line bjam gcc/debug runtime-link=dynamic/static ;
### }
### catch \"static\" is not a value of an implicit feature ;
###
###
### r = [ build-request.from-command-line bjam -d2 --debug debug target runtime-link=dynamic ] ;
### assert.equal [ $(r).get-at 1 ] : target ;
### assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic ;
###
### r = [ build-request.from-command-line bjam debug runtime-link=dynamic,static ] ;
### assert.equal [ $(r).get-at 1 ] : ;
### assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic <runtime-link>static ;
###
### r = [ build-request.from-command-line bjam debug gcc/runtime-link=dynamic,static ] ;
### assert.equal [ $(r).get-at 1 ] : ;
### assert.equal [ $(r).get-at 2 ] : debug gcc/<runtime-link>dynamic
### gcc/<runtime-link>static ;
###
### r = [ build-request.from-command-line bjam msvc gcc,borland/runtime-link=static ] ;
### assert.equal [ $(r).get-at 1 ] : ;
### assert.equal [ $(r).get-at 2 ] : msvc gcc/<runtime-link>static
### borland/<runtime-link>static ;
###
### r = [ build-request.from-command-line bjam gcc-3.0 ] ;
### assert.equal [ $(r).get-at 1 ] : ;
### assert.equal [ $(r).get-at 2 ] : gcc-3.0 ;
###
### feature.finish-test build-request-test-temp ;
### }
###
###

View File

@@ -0,0 +1,78 @@
# Copyright 2012 Steven Watanabe
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
import modules ;
import errors ;
import regex ;
import path ;
import project ;
import os ;
rule get ( name )
{
return $(.vars.$(name)) ;
}
rule set ( name : value * )
{
.all-vars += $(name) ;
.vars.$(name) = $(value) ;
}
rule save ( )
{
if $(.cache-file)
{
local cache-file-native = [ path.native $(.cache-file) ] ;
local target = <new-cache-file>$(cache-file-native) ;
local contents = "# Automatically generated by B2.\n# Do not edit.\n\nmodule config-cache {\n" ;
for local var in $(.all-vars)
{
local transformed ;
for local value in $(.vars.$(var))
{
transformed += [ regex.escape $(value) : \"\\ : \\ ] ;
}
local quoted = \"$(transformed)\" ;
contents += " set \"$(var)\" : $(quoted:J= ) ;\n" ;
}
contents += "}\n" ;
FILE_CONTENTS on $(target) = $(contents) ;
ALWAYS $(target) ;
config-cache.write $(target) ;
UPDATE_NOW $(target) : [ modules.peek configure : .log-fd ] : ignore-minus-n ;
import common ;
common.Clean clean-all : $(target) ;
}
}
actions write
{
@($(STDOUT):E=$(FILE_CONTENTS:J=)) > "$(<)"
}
if [ os.name ] = VMS
{
actions write
{
@($(STDOUT):E=$(FILE_CONTENTS:J=)) | TYPE SYS$INPUT /OUT=$(<:W)
}
}
rule load ( cache-file )
{
if $(.cache-file)
{
errors.error duplicate load of cache file ;
}
cache-file = [ path.native $(cache-file) ] ;
if [ path.exists $(cache-file) ] && ! ( --reconfigure in [ modules.peek : ARGV ] )
{
FILE_CONTENTS on <old-cache-file>$(cache-file) = "" ;
config-cache.write <old-cache-file>$(cache-file) ;
UPDATE_NOW <old-cache-file>$(cache-file) : [ modules.peek configure : .log-fd ] ;
include <old-cache-file>$(cache-file) ;
}
.cache-file = $(cache-file) ;
}

View File

@@ -0,0 +1,629 @@
# Copyright (c) 2010 Vladimir Prus.
# Copyright 2017-2021 Rene Ferdinand Rivera Morell
#
# Use, modification and distribution is subject to the Boost Software
# License Version 1.0. (See accompanying file LICENSE.txt or
# https://www.bfgroup.xyz/b2/LICENSE.txt)
# This module defines function to help with two main tasks:
#
# - Discovering build-time configuration for the purposes of adjusting the build
# process.
# - Reporting what is built, and how it is configured.
import "class" : new ;
import common ;
import indirect ;
import path ;
import project ;
import property ;
import property-set ;
import targets ;
import config-cache ;
import feature ;
import modules ;
import sequence ;
import utility ;
import virtual-target ;
rule log-summary ( )
{
}
.width = 30 ;
rule set-width ( width )
{
.width = $(width) ;
}
# Declare that the components specified by the parameter exist.
#
rule register-components ( components * )
{
.components += $(components) ;
}
# Declare that the components specified by the parameters will be built.
#
rule components-building ( components * )
{
.built-components += $(components) ;
}
# Report something about component configuration that the user should better
# know.
#
rule log-component-configuration ( component : message )
{
# FIXME: Implement per-property-set logs.
.component-logs.$(component) += $(message) ;
}
.variant_index = 0 ;
.nl = "\n" ;
.check_notes = ;
rule log-check-result ( result variant ? )
{
if ! $(.announced-checks)
{
ECHO "Performing configuration checks\n" ;
.announced-checks = 1 ;
}
if $(variant)
{
if $(.variant_index.$(variant))
{
result = "$(result) [$(.variant_index.$(variant))]" ;
}
else
{
.variant_index = [ CALC $(.variant_index) + 1 ] ;
.variant_index.$(variant) = $(.variant_index) ;
result = "$(result) [$(.variant_index.$(variant))]" ;
.check_notes += "[$(.variant_index.$(variant))] $(variant)" ;
}
}
# else
# {
# result = "$(result) [?]" ;
# }
ECHO $(result) ;
# FIXME: Unfinished code. Nothing seems to set .check-results at the moment.
#.check-results += $(result) ;
}
rule log-library-search-result ( library : result variant ? )
{
local x = [ PAD " - $(library)" : $(.width) ] ;
log-check-result "$(x) : $(result)" $(variant) ;
}
rule print-component-configuration ( )
{
# FIXME: See what was intended with this initial assignment.
# local c = [ sequence.unique $(.components) ] ;
ECHO "\nComponent configuration:\n" ;
local c ;
for c in $(.components)
{
local s ;
if $(c) in $(.built-components)
{
s = "building" ;
}
else
{
s = "not building" ;
}
ECHO [ PAD " - $(c)" : $(.width) ] ": $(s)" ;
for local m in $(.component-logs.$(c))
{
ECHO " -" $(m) ;
}
}
ECHO ;
}
rule print-configure-checks-summary ( )
{
if $(.check_notes)
{
ECHO ;
for local l in $(.check_notes) { ECHO $(l) ; }
}
# FIXME: The problem with this approach is that the user sees the checks
# summary when all checks are done, and has no progress reporting while the
# checks are being executed.
if $(.check-results)
{
ECHO "Configuration checks summary\n" ;
for local r in $(.check-results)
{
ECHO $(r) ;
}
ECHO ;
}
}
if --reconfigure in [ modules.peek : ARGV ]
{
.reconfigure = true ;
}
# Handle the --reconfigure option
rule maybe-force-rebuild ( targets * )
{
if $(.reconfigure)
{
local all-targets ;
for local t in $(targets)
{
all-targets += [ virtual-target.traverse $(t) ] ;
}
for local t in [ sequence.unique $(all-targets) ]
{
$(t).always ;
}
}
}
# Attempts to build a set of virtual targets
rule try-build ( targets * : ps : what : retry ? )
{
local cache-props = [ $(ps).raw ] ;
local cache-name = $(what) $(cache-props) ;
cache-name = $(cache-name:J=-) ;
local value = [ config-cache.get $(cache-name) ] ;
local cache-min = [ property.as-path [ SORT [ feature.minimize $(cache-props) ] ] ] ;
local result ;
local jam-targets ;
maybe-force-rebuild $(targets) ;
for local t in $(targets)
{
jam-targets += [ $(t).actualize ] ;
}
local x ;
if $(value)
{
x = [ PAD " - $(what)" : $(.width) ] ;
if $(value) = true
{
.$(what)-supported.$(ps) = yes ;
result = true ;
x = "$(x) : yes (cached)" ;
}
else
{
x = "$(x) : no (cached)" ;
}
}
else if ! UPDATE_NOW in [ RULENAMES ]
{
# Cannot determine. Assume existence.
}
else
{
x = [ PAD " - $(what)" : $(.width) ] ;
if [ UPDATE_NOW $(jam-targets) :
$(.log-fd) : ignore-minus-n : ignore-minus-q ]
{
.$(what)-supported.$(ps) = yes ;
result = true ;
x = "$(x) : yes" ;
}
else
{
x = "$(x) : no" ;
}
}
if $(x)
{
log-check-result "$(x)" "$(cache-min:J= )" ;
}
if ! $(value)
{
if $(result)
{
config-cache.set $(cache-name) : true ;
}
else
{
config-cache.set $(cache-name) : false ;
}
}
return $(result) ;
}
# Attempts to build several sets of virtual targets. Returns the
# the index of the first set that builds.
rule try-find-build ( ps : what : * )
{
local args = 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;
# The outer layer only needs to check $(what), but we
# also need to check the individual elements, in case
# the set of targets has changed since the last build.
local cache-props = [ $(ps).raw ] ;
local cache-name = $(what) $($(args)[1]) $(cache-props) ;
cache-name = $(cache-name:J=-) ;
local value = [ config-cache.get $(cache-name) ] ;
local cache-min = [ property.as-path [ SORT [ feature.minimize $(cache-props) ] ] ] ;
local result ;
local jam-targets ;
maybe-force-rebuild $($(args)[2-]) ;
# Make sure that the targets are always actualized,
# even if the result is cached. This is needed to
# allow clean-all to find them and also to avoid
# unintentional behavior changes.
for local t in $($(args)[2-])
{
$(t).actualize ;
}
if $(value)
{
local none = none ; # What to show when the argument
local name = $(value) ;
if $(name) != none
{
name = [ CALC $(name) + 2 ] ;
}
local x = [ PAD " - $(what)" : $(.width) ] ;
local y = [ PAD $($(name)[1]) : 3 ] ;
result = $(value) ;
log-check-result "$(x) : $(y) (cached)" "$(cache-min:J= )" ;
}
else
{
local x = [ PAD " - $(what)" : $(.width) ] ;
for local i in $(args)
{
if ! $($(i)[1])
{
break ;
}
local jam-targets ;
for local t in $($(i)[2-])
{
jam-targets += [ $(t).actualize ] ;
}
if [ UPDATE_NOW $(jam-targets) :
$(.log-fd) : ignore-minus-n : ignore-minus-q ]
{
result = [ CALC $(i) - 2 ] ;
log-check-result "$(x) : $($(i)[1])" "$(cache-min:J= )" ;
break ;
}
}
if ! $(result)
{
log-check-result "$(x) : none" "$(cache-min:J= )" ;
result = none ;
}
}
if ! $(value)
{
if $(result)
{
config-cache.set $(cache-name) : $(result) ;
}
else
{
config-cache.set $(cache-name) : $(result) ;
}
}
if $(result) != none
{
return $(result) ;
}
}
# Attempt to build a metatarget named by 'metatarget-reference'
# in context of 'project' with properties 'ps'.
# Returns non-empty value if build is OK.
rule builds-raw ( metatarget-reference : project : ps : what : retry ? )
{
local result ;
if ! $(retry) && ! $(.$(what)-tested.$(ps))
{
.$(what)-tested.$(ps) = true ;
local targets = [ targets.generate-from-reference
$(metatarget-reference) : $(project) : $(ps) ] ;
result = [ try-build $(targets[2-]) : $(ps) : $(what) : $(retry) ] ;
.$(what)-supported.$(ps) = $(result) ;
return $(result) ;
}
else
{
return $(.$(what)-supported.$(ps)) ;
}
}
# Attempt to build a metatarget named by 'metatarget-reference'
# in context of 'project' with properties 'ps'.
# Returns the 1-based index of the first target
# that builds.
rule find-builds-raw ( project : ps : what : * )
{
local result ;
local args = 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;
if ! $(.$(what)-tested.$(ps))
{
.$(what)-tested.$(ps) = true ;
local targets.$(i) what.$(i) ;
for local i in $(args)
{
if ! $($(i))
{
break ;
}
targets.$(i) = [ targets.generate-from-reference
$($(i)[1]) : $(project) : $(ps) ] ;
# ignore usage requirements
targets.$(i) = $(targets.$(i)[2-]) ;
if $($(i)[2])
{
what.$(i) = $($(i)[2]) ;
}
else
{
local t = [ targets.resolve-reference
$($(i)[1]) : $(project) ] ;
what.$(i) = [ $(t[1]).name ] ;
}
}
result = [ try-find-build $(ps) : $(what)
: $(what.4) $(targets.4)
: $(what.5) $(targets.5)
: $(what.6) $(targets.6)
: $(what.7) $(targets.7)
: $(what.8) $(targets.8)
: $(what.9) $(targets.9)
: $(what.10) $(targets.10)
: $(what.11) $(targets.11)
: $(what.12) $(targets.12)
: $(what.13) $(targets.13)
: $(what.14) $(targets.14)
: $(what.15) $(targets.15)
: $(what.16) $(targets.16)
: $(what.17) $(targets.17)
: $(what.18) $(targets.18)
: $(what.19) $(targets.19) ] ;
.$(what)-result.$(ps) = $(result) ;
return $(result) ;
}
else
{
return $(.$(what)-result.$(ps)) ;
}
}
rule get-relevant-features ( properties * )
{
local ps-full = [ property-set.create $(properties) ] ;
local ps-base = [ property-set.create [ $(ps-full).base ] ] ;
local ps-min = [ feature.expand-subfeatures [ feature.minimize
[ $(ps-base).raw ] ] ] ;
local ps-relevant = [ property-set.create $(ps-min) ] ;
return [ $(ps-relevant).raw ] ;
}
rule builds ( metatarget-reference : properties * : what ? : retry ? )
{
local relevant = [ get-relevant-features $(properties) ] ;
local ps = [ property-set.create $(relevant) ] ;
local t = [ targets.current ] ;
local p = [ $(t).project ] ;
if ! $(what)
{
local resolved = [ targets.resolve-reference $(metatarget-reference) : $(p) ] ;
local name = [ $(resolved[1]).name ] ;
what = "$(name) builds" ;
}
return [ builds-raw $(metatarget-reference) : $(p) : $(ps) : $(what) :
$(retry) ] ;
}
rule find-builds ( what : properties * : * )
{
local relevant = [ get-relevant-features $(properties) ] ;
local ps = [ property-set.create $(relevant) ] ;
local t = [ targets.current ] ;
local p = [ $(t).project ] ;
return [ find-builds-raw $(p) : $(ps) : $(what) :
$(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) :
$(10) : $(11) : $(12) : $(13) : $(14) : $(15) :
$(16) : $(17) : $(18) ] ;
}
# Called by B2 startup code to specify the file to receive the
# configuration check results. Should never be called by user code.
#
rule set-log-file ( log-file )
{
path.makedirs [ path.parent $(log-file) ] ;
.log-fd = [ FILE_OPEN [ path.native $(log-file) ] : "w" ] ;
if ! $(.log-fd)
{
ECHO "warning:" failed to open log file $(log-file) for writing ;
}
}
# Frontend rules
class check-target-builds-worker
{
import configure ;
import property-set ;
import targets ;
import project ;
import property ;
rule __init__ ( target message ? : true-properties * : false-properties * )
{
local project = [ project.current ] ;
self.target = $(target) ;
self.message = $(message) ;
self.true-properties =
[ configure.translate-properties $(true-properties) : $(project) ] ;
self.false-properties =
[ configure.translate-properties $(false-properties) : $(project) ] ;
}
rule check ( properties * )
{
local choosen ;
if [ configure.builds $(self.target) : $(properties) : $(self.message) ]
{
choosen = $(self.true-properties) ;
}
else
{
choosen = $(self.false-properties) ;
}
return [ property.evaluate-conditionals-in-context $(choosen) :
$(properties) ] ;
}
}
class configure-choose-worker
{
import configure ;
import property ;
import project ;
rule __init__ ( message : * )
{
local project = [ project.current ] ;
self.message = $(message) ;
for i in 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{
local name = [ CALC $(i) - 1 ] ;
self.targets.$(name) = $($(i)[1]) ;
if ! $($(i)[2]:G) # Check whether the second argument is a property
{
self.what.$(name) = $($(i)[2]) ;
self.props.$(name) = $($(i)[3-]) ;
}
else
{
self.props.$(name) = $($(i)[2-]) ;
}
self.props.$(name) = [ configure.translate-properties
$(self.props.$(name)) : $(project) ] ;
}
}
rule all-properties ( )
{
local i = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ;
return $(self.props.$(i)) ;
}
rule check ( properties * )
{
local i = [ configure.find-builds $(self.message) : $(properties)
: $(self.targets.1) $(self.what.1)
: $(self.targets.2) $(self.what.2)
: $(self.targets.3) $(self.what.3)
: $(self.targets.4) $(self.what.4)
: $(self.targets.5) $(self.what.5)
: $(self.targets.6) $(self.what.6)
: $(self.targets.7) $(self.what.7)
: $(self.targets.8) $(self.what.8)
: $(self.targets.9) $(self.what.9)
: $(self.targets.10) $(self.what.10)
: $(self.targets.11) $(self.what.11)
: $(self.targets.12) $(self.what.12)
: $(self.targets.13) $(self.what.13)
: $(self.targets.14) $(self.what.14)
: $(self.targets.15) $(self.what.15)
: $(self.targets.16) $(self.what.16)
: $(self.targets.17) $(self.what.17) ] ;
if $(self.props.$(i))
{
return [ property.evaluate-conditionals-in-context $(self.props.$(i)) : $(properties) ] ;
}
}
}
rule translate-properties ( properties * : project ? )
{
if $(project) && [ $(project).location ]
{
local location = [ $(project).location ] ;
local m = [ $(project).project-module ] ;
local project-id = [ project.attribute $(m) id ] ;
project-id ?= [ path.root $(location) [ path.pwd ] ] ;
return [ property.translate $(properties)
: $(project-id) : $(location) : $(m) ] ;
}
else
{
return $(properties) ;
}
}
rule check-target-builds ( target message ? : true-properties * :
false-properties * )
{
local instance = [ new check-target-builds-worker $(target) $(message) :
$(true-properties) : $(false-properties) ] ;
local rulename = [ indirect.make check : $(instance) ] ;
return <conditional>@$(rulename)
[ property.evaluate-conditional-relevance
$(true-properties) $(false-properties) ] ;
}
# Usage:
# [ configure.choose "architecture"
# : /config//x86 x86 <architecture>x86
# : /config//mips mips <architecture>mips
# ]
rule choose ( message : * )
{
local instance = [ new configure-choose-worker $(message)
: $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9)
: $(10) : $(11) : $(12) : $(13) : $(14) : $(15) : $(16)
: $(17) : $(18) : $(19) ] ;
local rulename = [ indirect.make check : $(instance) ] ;
return <conditional>@$(rulename)
[ property.evaluate-conditional-relevance
[ $(instance).all-properties ] ] ;
}
IMPORT $(__name__) : check-target-builds : : check-target-builds ;

View File

@@ -0,0 +1,176 @@
# Status: ported.
# Base revision: 64488
#
# Copyright (c) 2010 Vladimir Prus.
#
# Use, modification and distribution is subject to the Boost Software
# License Version 1.0. (See accompanying file LICENSE.txt or
# https://www.bfgroup.xyz/b2/LICENSE.txt)
# This module defines function to help with two main tasks:
#
# - Discovering build-time configuration for the purposes of adjusting
# build process.
# - Reporting what is built, and how it is configured.
import b2.build.property as property
import b2.build.property_set as property_set
from b2.build import targets as targets_
from b2.manager import get_manager
from b2.util.sequence import unique
from b2.util import bjam_signature, value_to_jam, is_iterable
import bjam
import os
__width = 30
def set_width(width):
global __width
__width = 30
__components = []
__built_components = []
__component_logs = {}
__announced_checks = False
__log_file = None
__log_fd = -1
def register_components(components):
"""Declare that the components specified by the parameter exist."""
assert is_iterable(components)
__components.extend(components)
def components_building(components):
"""Declare that the components specified by the parameters will be build."""
assert is_iterable(components)
__built_components.extend(components)
def log_component_configuration(component, message):
"""Report something about component configuration that the user should better know."""
assert isinstance(component, basestring)
assert isinstance(message, basestring)
__component_logs.setdefault(component, []).append(message)
def log_check_result(result):
assert isinstance(result, basestring)
global __announced_checks
if not __announced_checks:
print "Performing configuration checks"
__announced_checks = True
print result
def log_library_search_result(library, result):
assert isinstance(library, basestring)
assert isinstance(result, basestring)
log_check_result((" - %(library)s : %(result)s" % locals()).rjust(__width))
def print_component_configuration():
print "\nComponent configuration:"
for c in __components:
if c in __built_components:
s = "building"
else:
s = "not building"
message = " - %s)" % c
message = message.rjust(__width)
message += " : " + s
for m in __component_logs.get(c, []):
print " -" + m
print ""
__builds_cache = {}
def builds(metatarget_reference, project, ps, what):
# Attempt to build a metatarget named by 'metatarget-reference'
# in context of 'project' with properties 'ps'.
# Returns non-empty value if build is OK.
assert isinstance(metatarget_reference, basestring)
assert isinstance(project, targets_.ProjectTarget)
assert isinstance(ps, property_set.PropertySet)
assert isinstance(what, basestring)
result = []
existing = __builds_cache.get((what, ps), None)
if existing is None:
result = False
__builds_cache[(what, ps)] = False
targets = targets_.generate_from_reference(
metatarget_reference, project, ps).targets()
jam_targets = []
for t in targets:
jam_targets.append(t.actualize())
x = (" - %s" % what).rjust(__width)
if bjam.call("UPDATE_NOW", jam_targets, str(__log_fd), "ignore-minus-n"):
__builds_cache[(what, ps)] = True
result = True
log_check_result("%s: yes" % x)
else:
log_check_result("%s: no" % x)
return result
else:
return existing
def set_log_file(log_file_name):
assert isinstance(log_file_name, basestring)
# Called by Boost.Build startup code to specify name of a file
# that will receive results of configure checks. This
# should never be called by users.
global __log_file, __log_fd
dirname = os.path.dirname(log_file_name)
if not os.path.exists(dirname):
os.makedirs(dirname)
# Make sure to keep the file around, so that it's not
# garbage-collected and closed
__log_file = open(log_file_name, "w")
__log_fd = __log_file.fileno()
# Frontend rules
class CheckTargetBuildsWorker:
def __init__(self, target, true_properties, false_properties):
self.target = target
self.true_properties = property.create_from_strings(true_properties, True)
self.false_properties = property.create_from_strings(false_properties, True)
def check(self, ps):
assert isinstance(ps, property_set.PropertySet)
# FIXME: this should not be hardcoded. Other checks might
# want to consider different set of features as relevant.
toolset = ps.get('toolset')[0]
toolset_version_property = "<toolset-" + toolset + ":version>" ;
relevant = ps.get_properties('target-os') + \
ps.get_properties("toolset") + \
ps.get_properties(toolset_version_property) + \
ps.get_properties("address-model") + \
ps.get_properties("architecture")
rps = property_set.create(relevant)
t = get_manager().targets().current()
p = t.project()
if builds(self.target, p, rps, "%s builds" % self.target):
choosen = self.true_properties
else:
choosen = self.false_properties
return property.evaluate_conditionals_in_context(choosen, ps)
@bjam_signature((["target"], ["true_properties", "*"], ["false_properties", "*"]))
def check_target_builds(target, true_properties, false_properties):
worker = CheckTargetBuildsWorker(target, true_properties, false_properties)
value = value_to_jam(worker.check)
return "<conditional>" + value
get_manager().projects().add_rule("check-target-builds", check_target_builds)

View File

@@ -0,0 +1,246 @@
# Copyright Pedro Ferreira 2005.
# Copyright Vladimir Prus 2007.
# Distributed under the Boost
# Software License, Version 1.0. (See accompanying
# file LICENSE.txt or copy at https://www.bfgroup.xyz/b2/LICENSE.txt)
bjam_interface = __import__('bjam')
import operator
import re
import b2.build.property_set as property_set
from b2.util import set_jam_action, is_iterable
class BjamAction(object):
"""Class representing bjam action defined from Python."""
def __init__(self, action_name, function, has_command=False):
assert isinstance(action_name, basestring)
assert callable(function) or function is None
self.action_name = action_name
self.function = function
self.has_command = has_command
def __call__(self, targets, sources, property_set_):
assert is_iterable(targets)
assert is_iterable(sources)
assert isinstance(property_set_, property_set.PropertySet)
if self.has_command:
# Bjam actions defined from Python have only the command
# to execute, and no associated jam procedural code. So
# passing 'property_set' to it is not necessary.
bjam_interface.call("set-update-action", self.action_name,
targets, sources, [])
if self.function:
self.function(targets, sources, property_set_)
class BjamNativeAction(BjamAction):
"""Class representing bjam action defined by Jam code.
We still allow to associate a Python callable that will
be called when this action is installed on any target.
"""
def __call__(self, targets, sources, property_set_):
assert is_iterable(targets)
assert is_iterable(sources)
assert isinstance(property_set_, property_set.PropertySet)
if self.function:
self.function(targets, sources, property_set_)
p = []
if property_set:
p = property_set_.raw()
set_jam_action(self.action_name, targets, sources, p)
action_modifiers = {"updated": 0x01,
"together": 0x02,
"ignore": 0x04,
"quietly": 0x08,
"piecemeal": 0x10,
"existing": 0x20}
class Engine:
""" The abstract interface to a build engine.
For now, the naming of targets, and special handling of some
target variables like SEARCH and LOCATE make this class coupled
to bjam engine.
"""
def __init__ (self):
self.actions = {}
def add_dependency (self, targets, sources):
"""Adds a dependency from 'targets' to 'sources'
Both 'targets' and 'sources' can be either list
of target names, or a single target name.
"""
if isinstance (targets, str):
targets = [targets]
if isinstance (sources, str):
sources = [sources]
assert is_iterable(targets)
assert is_iterable(sources)
for target in targets:
for source in sources:
self.do_add_dependency (target, source)
def get_target_variable(self, targets, variable):
"""Gets the value of `variable` on set on the first target in `targets`.
Args:
targets (str or list): one or more targets to get the variable from.
variable (str): the name of the variable
Returns:
the value of `variable` set on `targets` (list)
Example:
>>> ENGINE = get_manager().engine()
>>> ENGINE.set_target_variable(targets, 'MY-VAR', 'Hello World')
>>> ENGINE.get_target_variable(targets, 'MY-VAR')
['Hello World']
Equivalent Jam code:
MY-VAR on $(targets) = "Hello World" ;
echo [ on $(targets) return $(MY-VAR) ] ;
"Hello World"
"""
if isinstance(targets, str):
targets = [targets]
assert is_iterable(targets)
assert isinstance(variable, basestring)
return bjam_interface.call('get-target-variable', targets, variable)
def set_target_variable (self, targets, variable, value, append=0):
""" Sets a target variable.
The 'variable' will be available to bjam when it decides
where to generate targets, and will also be available to
updating rule for that 'taret'.
"""
if isinstance (targets, str):
targets = [targets]
if isinstance(value, str):
value = [value]
assert is_iterable(targets)
assert isinstance(variable, basestring)
assert is_iterable(value)
if targets:
if append:
bjam_interface.call("set-target-variable", targets, variable, value, "true")
else:
bjam_interface.call("set-target-variable", targets, variable, value)
def set_update_action (self, action_name, targets, sources, properties=None):
""" Binds a target to the corresponding update action.
If target needs to be updated, the action registered
with action_name will be used.
The 'action_name' must be previously registered by
either 'register_action' or 'register_bjam_action'
method.
"""
if isinstance(targets, str):
targets = [targets]
if isinstance(sources, str):
sources = [sources]
if properties is None:
properties = property_set.empty()
assert isinstance(action_name, basestring)
assert is_iterable(targets)
assert is_iterable(sources)
assert(isinstance(properties, property_set.PropertySet))
self.do_set_update_action (action_name, targets, sources, properties)
def register_action (self, action_name, command='', bound_list = [], flags = [],
function = None):
"""Creates a new build engine action.
Creates on bjam side an action named 'action_name', with
'command' as the command to be executed, 'bound_variables'
naming the list of variables bound when the command is executed
and specified flag.
If 'function' is not None, it should be a callable taking three
parameters:
- targets
- sources
- instance of the property_set class
This function will be called by set_update_action, and can
set additional target variables.
"""
assert isinstance(action_name, basestring)
assert isinstance(command, basestring)
assert is_iterable(bound_list)
assert is_iterable(flags)
assert function is None or callable(function)
bjam_flags = reduce(operator.or_,
(action_modifiers[flag] for flag in flags), 0)
# We allow command to be empty so that we can define 'action' as pure
# python function that would do some conditional logic and then relay
# to other actions.
assert command or function
if command:
bjam_interface.define_action(action_name, command, bound_list, bjam_flags)
self.actions[action_name] = BjamAction(
action_name, function, has_command=bool(command))
def register_bjam_action (self, action_name, function=None):
"""Informs self that 'action_name' is declared in bjam.
From this point, 'action_name' is a valid argument to the
set_update_action method. The action_name should be callable
in the global module of bjam.
"""
# We allow duplicate calls to this rule for the same
# action name. This way, jamfile rules that take action names
# can just register them without specially checking if
# action is already registered.
assert isinstance(action_name, basestring)
assert function is None or callable(function)
if action_name not in self.actions:
self.actions[action_name] = BjamNativeAction(action_name, function)
# Overridables
def do_set_update_action (self, action_name, targets, sources, property_set_):
assert isinstance(action_name, basestring)
assert is_iterable(targets)
assert is_iterable(sources)
assert isinstance(property_set_, property_set.PropertySet)
action = self.actions.get(action_name)
if not action:
raise Exception("No action %s was registered" % action_name)
action(targets, sources, property_set_)
def do_set_target_variable (self, target, variable, value, append):
assert isinstance(target, basestring)
assert isinstance(variable, basestring)
assert is_iterable(value)
assert isinstance(append, int) # matches bools
if append:
bjam_interface.call("set-target-variable", target, variable, value, "true")
else:
bjam_interface.call("set-target-variable", target, variable, value)
def do_add_dependency (self, target, source):
assert isinstance(target, basestring)
assert isinstance(source, basestring)
bjam_interface.call("DEPENDS", target, source)

View File

@@ -0,0 +1,135 @@
# Status: being written afresh by Vladimir Prus
# Copyright 2007 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
# This file is supposed to implement error reporting for Boost.Build.
# Experience with jam version has shown that printing full backtrace
# on each error is buffling. Further, for errors printed after parsing --
# during target building, the stacktrace does not even mention what
# target is being built.
# This module implements explicit contexts -- where other code can
# communicate which projects/targets are being built, and error
# messages will show those contexts. For programming errors,
# Python assertions are to be used.
import bjam
import traceback
import sys
def format(message, prefix=""):
parts = str(message).split("\n")
return "\n".join(prefix+p for p in parts)
class Context:
def __init__(self, message, nested=None):
self.message_ = message
self.nested_ = nested
def report(self, indent=""):
print indent + " -", self.message_
if self.nested_:
print indent + " declared at:"
for n in self.nested_:
n.report(indent + " ")
class JamfileContext:
def __init__(self):
raw = bjam.backtrace()
self.raw_ = raw
def report(self, indent=""):
for r in self.raw_:
print indent + " - %s:%s" % (r[0], r[1])
class ExceptionWithUserContext(Exception):
def __init__(self, message, context,
original_exception=None, original_tb=None, stack=None):
Exception.__init__(self, message)
self.context_ = context
self.original_exception_ = original_exception
self.original_tb_ = original_tb
self.stack_ = stack
def report(self):
print "error:", self.args[0]
if self.original_exception_:
print format(str(self.original_exception_), " ")
print
print " error context (most recent first):"
for c in self.context_[::-1]:
c.report()
print
if "--stacktrace" in bjam.variable("ARGV"):
if self.original_tb_:
traceback.print_tb(self.original_tb_)
elif self.stack_:
for l in traceback.format_list(self.stack_):
print l,
else:
print " use the '--stacktrace' option to get Python stacktrace"
print
def user_error_checkpoint(callable):
def wrapper(self, *args):
errors = self.manager().errors()
try:
return callable(self, *args)
except ExceptionWithUserContext, e:
raise
except Exception, e:
errors.handle_stray_exception(e)
finally:
errors.pop_user_context()
return wrapper
class Errors:
def __init__(self):
self.contexts_ = []
self._count = 0
def count(self):
return self._count
def push_user_context(self, message, nested=None):
self.contexts_.append(Context(message, nested))
def pop_user_context(self):
del self.contexts_[-1]
def push_jamfile_context(self):
self.contexts_.append(JamfileContext())
def pop_jamfile_context(self):
del self.contexts_[-1]
def capture_user_context(self):
return self.contexts_[:]
def handle_stray_exception(self, e):
raise ExceptionWithUserContext("unexpected exception", self.contexts_[:],
e, sys.exc_info()[2])
def __call__(self, message):
self._count = self._count + 1
raise ExceptionWithUserContext(message, self.contexts_[:],
stack=traceback.extract_stack())
def nearest_user_location():
"""
Returns:
tuple: the filename and line number of the nearest user location
"""
bt = bjam.backtrace()
if not bt:
return None
last = bt[-1]
return last[0], last[1]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,914 @@
# Status: ported, except for unit tests.
# Base revision: 64488
#
# Copyright 2001, 2002, 2003 Dave Abrahams
# Copyright 2002, 2006 Rene Rivera
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
import re
from b2.manager import get_manager
from b2.util import utility, bjam_signature, is_iterable_typed
import b2.util.set
from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, to_seq
from b2.exceptions import *
__re_split_subfeatures = re.compile ('<(.*):(.*)>')
__re_no_hyphen = re.compile ('^([^:]+)$')
__re_slash_or_backslash = re.compile (r'[\\/]')
VALID_ATTRIBUTES = {
'implicit',
'composite',
'optional',
'symmetric',
'free',
'incidental',
'path',
'dependency',
'propagated',
'link-incompatible',
'subfeature',
'order-sensitive'
}
class Feature(object):
def __init__(self, name, values, attributes):
assert isinstance(name, basestring)
assert is_iterable_typed(values, basestring)
assert is_iterable_typed(attributes, basestring)
self.name = name
self.values = values
self.default = None
self.subfeatures = []
self.parent = None
self.attributes_string_list = []
self._hash = hash(self.name)
for attr in attributes:
self.attributes_string_list.append(attr)
attr = attr.replace("-", "_")
setattr(self, attr, True)
def add_values(self, values):
assert is_iterable_typed(values, basestring)
self.values.extend(values)
def set_default(self, value):
assert isinstance(value, basestring)
for attr in ('free', 'optional'):
if getattr(self, attr):
get_manager().errors()('"{}" feature "<{}>" cannot have a default value.'
.format(attr, self.name))
self.default = value
def add_subfeature(self, name):
assert isinstance(name, Feature)
self.subfeatures.append(name)
def set_parent(self, feature, value):
assert isinstance(feature, Feature)
assert isinstance(value, basestring)
self.parent = (feature, value)
def __hash__(self):
return self._hash
def __str__(self):
return self.name
def reset ():
""" Clear the module state. This is mainly for testing purposes.
"""
global __all_attributes, __all_features, __implicit_features, __composite_properties
global __subfeature_from_value, __all_top_features, __free_features
global __all_subfeatures
# sets the default value of False for each valid attribute
for attr in VALID_ATTRIBUTES:
setattr(Feature, attr.replace("-", "_"), False)
# A map containing all features. The key is the feature name.
# The value is an instance of Feature class.
__all_features = {}
# All non-subfeatures.
__all_top_features = []
# Maps valus to the corresponding implicit feature
__implicit_features = {}
# A map containing all composite properties. The key is a Property instance,
# and the value is a list of Property instances
__composite_properties = {}
# Maps a value to the corresponding subfeature name.
__subfeature_from_value = {}
# All free features
__free_features = []
__all_subfeatures = []
reset ()
def enumerate ():
""" Returns an iterator to the features map.
"""
return __all_features.iteritems ()
def get(name):
"""Return the Feature instance for the specified name.
Throws if no feature by such name exists
"""
assert isinstance(name, basestring)
return __all_features[name]
# FIXME: prepare-test/finish-test?
@bjam_signature((["name"], ["values", "*"], ["attributes", "*"]))
def feature (name, values, attributes = []):
""" Declares a new feature with the given name, values, and attributes.
name: the feature name
values: a sequence of the allowable values - may be extended later with feature.extend
attributes: a sequence of the feature's attributes (e.g. implicit, free, propagated, ...)
"""
__validate_feature_attributes (name, attributes)
feature = Feature(name, [], attributes)
__all_features[name] = feature
# Temporary measure while we have not fully moved from 'gristed strings'
__all_features["<" + name + ">"] = feature
name = add_grist(name)
if 'subfeature' in attributes:
__all_subfeatures.append(name)
else:
__all_top_features.append(feature)
extend (name, values)
# FIXME: why his is needed.
if 'free' in attributes:
__free_features.append (name)
return feature
@bjam_signature((["feature"], ["value"]))
def set_default (feature, value):
""" Sets the default value of the given feature, overriding any previous default.
feature: the name of the feature
value: the default value to assign
"""
f = __all_features[feature]
bad_attribute = None
if f.free:
bad_attribute = "free"
elif f.optional:
bad_attribute = "optional"
if bad_attribute:
raise InvalidValue ("%s property %s cannot have a default" % (bad_attribute, f.name))
if value not in f.values:
raise InvalidValue ("The specified default value, '%s' is invalid.\n" % value + "allowed values are: %s" % f.values)
f.set_default(value)
def defaults(features):
""" Returns the default property values for the given features.
"""
assert is_iterable_typed(features, Feature)
# FIXME: should merge feature and property modules.
from . import property
result = []
for f in features:
if not f.free and not f.optional and f.default:
result.append(property.Property(f, f.default))
return result
def valid (names):
""" Returns true iff all elements of names are valid features.
"""
if isinstance(names, str):
names = [names]
assert is_iterable_typed(names, basestring)
return all(name in __all_features for name in names)
def attributes (feature):
""" Returns the attributes of the given feature.
"""
assert isinstance(feature, basestring)
return __all_features[feature].attributes_string_list
def values (feature):
""" Return the values of the given feature.
"""
assert isinstance(feature, basestring)
validate_feature (feature)
return __all_features[feature].values
def is_implicit_value (value_string):
""" Returns true iff 'value_string' is a value_string
of an implicit feature.
"""
assert isinstance(value_string, basestring)
if value_string in __implicit_features:
return __implicit_features[value_string]
v = value_string.split('-')
if v[0] not in __implicit_features:
return False
feature = __implicit_features[v[0]]
for subvalue in (v[1:]):
if not __find_implied_subfeature(feature, subvalue, v[0]):
return False
return True
def implied_feature (implicit_value):
""" Returns the implicit feature associated with the given implicit value.
"""
assert isinstance(implicit_value, basestring)
components = implicit_value.split('-')
if components[0] not in __implicit_features:
raise InvalidValue ("'%s' is not a value of an implicit feature" % implicit_value)
return __implicit_features[components[0]]
def __find_implied_subfeature (feature, subvalue, value_string):
assert isinstance(feature, Feature)
assert isinstance(subvalue, basestring)
assert isinstance(value_string, basestring)
try:
return __subfeature_from_value[feature][value_string][subvalue]
except KeyError:
return None
# Given a feature and a value of one of its subfeatures, find the name
# of the subfeature. If value-string is supplied, looks for implied
# subfeatures that are specific to that value of feature
# feature # The main feature name
# subvalue # The value of one of its subfeatures
# value-string # The value of the main feature
def implied_subfeature (feature, subvalue, value_string):
assert isinstance(feature, Feature)
assert isinstance(subvalue, basestring)
assert isinstance(value_string, basestring)
result = __find_implied_subfeature (feature, subvalue, value_string)
if not result:
raise InvalidValue ("'%s' is not a known subfeature value of '%s%s'" % (subvalue, feature, value_string))
return result
def validate_feature (name):
""" Checks if all name is a valid feature. Otherwise, raises an exception.
"""
assert isinstance(name, basestring)
if name not in __all_features:
raise InvalidFeature ("'%s' is not a valid feature name" % name)
else:
return __all_features[name]
# Uses Property
def __expand_subfeatures_aux (property_, dont_validate = False):
""" Helper for expand_subfeatures.
Given a feature and value, or just a value corresponding to an
implicit feature, returns a property set consisting of all component
subfeatures and their values. For example:
expand_subfeatures <toolset>gcc-2.95.2-linux-x86
-> <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
equivalent to:
expand_subfeatures gcc-2.95.2-linux-x86
feature: The name of the feature, or empty if value corresponds to an implicit property
value: The value of the feature.
dont_validate: If True, no validation of value string will be done.
"""
from . import property # no __debug__ since Property is used elsewhere
assert isinstance(property_, property.Property)
assert isinstance(dont_validate, int) # matches bools
f = property_.feature
v = property_.value
if not dont_validate:
validate_value_string(f, v)
components = v.split ("-")
v = components[0]
result = [property.Property(f, components[0])]
subvalues = components[1:]
while len(subvalues) > 0:
subvalue = subvalues [0] # pop the head off of subvalues
subvalues = subvalues [1:]
subfeature = __find_implied_subfeature (f, subvalue, v)
# If no subfeature was found, reconstitute the value string and use that
if not subfeature:
return [property.Property(f, '-'.join(components))]
result.append(property.Property(subfeature, subvalue))
return result
def expand_subfeatures(properties, dont_validate = False):
"""
Make all elements of properties corresponding to implicit features
explicit, and express all subfeature values as separate properties
in their own right. For example, the property
gcc-2.95.2-linux-x86
might expand to
<toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
properties: A sequence with elements of the form
<feature>value-string or just value-string in the
case of implicit features.
: dont_validate: If True, no validation of value string will be done.
"""
if __debug__:
from .property import Property
assert is_iterable_typed(properties, Property)
assert isinstance(dont_validate, int) # matches bools
result = []
for p in properties:
# Don't expand subfeatures in subfeatures
if p.feature.subfeature:
result.append (p)
else:
result.extend(__expand_subfeatures_aux (p, dont_validate))
return result
# rule extend was defined as below:
# Can be called three ways:
#
# 1. extend feature : values *
# 2. extend <feature> subfeature : values *
# 3. extend <feature>value-string subfeature : values *
#
# * Form 1 adds the given values to the given feature
# * Forms 2 and 3 add subfeature values to the given feature
# * Form 3 adds the subfeature values as specific to the given
# property value-string.
#
#rule extend ( feature-or-property subfeature ? : values * )
#
# Now, the specific rule must be called, depending on the desired operation:
# extend_feature
# extend_subfeature
@bjam_signature([['name'], ['values', '*']])
def extend (name, values):
""" Adds the given values to the given feature.
"""
assert isinstance(name, basestring)
assert is_iterable_typed(values, basestring)
name = add_grist (name)
__validate_feature (name)
feature = __all_features [name]
if feature.implicit:
for v in values:
if v in __implicit_features:
raise BaseException ("'%s' is already associated with the feature '%s'" % (v, __implicit_features [v]))
__implicit_features[v] = feature
if values and not feature.values and not(feature.free or feature.optional):
# This is the first value specified for this feature,
# take it as default value
feature.set_default(values[0])
feature.add_values(values)
def validate_value_string (f, value_string):
""" Checks that value-string is a valid value-string for the given feature.
"""
assert isinstance(f, Feature)
assert isinstance(value_string, basestring)
if f.free or value_string in f.values:
return
values = [value_string]
if f.subfeatures:
if not value_string in f.values and \
not value_string in f.subfeatures:
values = value_string.split('-')
# An empty value is allowed for optional features
if not values[0] in f.values and \
(values[0] or not f.optional):
raise InvalidValue ("'%s' is not a known value of feature '%s'\nlegal values: '%s'" % (values [0], f.name, f.values))
for v in values [1:]:
# this will validate any subfeature values in value-string
implied_subfeature(f, v, values[0])
""" Extends the given subfeature with the subvalues. If the optional
value-string is provided, the subvalues are only valid for the given
value of the feature. Thus, you could say that
<target-platform>mingw is specific to <toolset>gcc-2.95.2 as follows:
extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ;
feature: The feature whose subfeature is being extended.
value-string: If supplied, specifies a specific value of the
main feature for which the new subfeature values
are valid.
subfeature: The name of the subfeature.
subvalues: The additional values of the subfeature being defined.
"""
def extend_subfeature (feature_name, value_string, subfeature_name, subvalues):
assert isinstance(feature_name, basestring)
assert isinstance(value_string, basestring)
assert isinstance(subfeature_name, basestring)
assert is_iterable_typed(subvalues, basestring)
feature = validate_feature(feature_name)
if value_string:
validate_value_string(feature, value_string)
subfeature_name = feature_name + '-' + __get_subfeature_name (subfeature_name, value_string)
extend(subfeature_name, subvalues) ;
subfeature = __all_features[subfeature_name]
if value_string == None: value_string = ''
if feature not in __subfeature_from_value:
__subfeature_from_value[feature] = {}
if value_string not in __subfeature_from_value[feature]:
__subfeature_from_value[feature][value_string] = {}
for subvalue in subvalues:
__subfeature_from_value [feature][value_string][subvalue] = subfeature
@bjam_signature((["feature_name", "value_string", "?"], ["subfeature"],
["subvalues", "*"], ["attributes", "*"]))
def subfeature (feature_name, value_string, subfeature, subvalues, attributes = []):
""" Declares a subfeature.
feature_name: Root feature that is not a subfeature.
value_string: An optional value-string specifying which feature or
subfeature values this subfeature is specific to,
if any.
subfeature: The name of the subfeature being declared.
subvalues: The allowed values of this subfeature.
attributes: The attributes of the subfeature.
"""
parent_feature = validate_feature (feature_name)
# Add grist to the subfeature name if a value-string was supplied
subfeature_name = __get_subfeature_name (subfeature, value_string)
if subfeature_name in __all_features[feature_name].subfeatures:
message = "'%s' already declared as a subfeature of '%s'" % (subfeature, feature_name)
message += " specific to '%s'" % value_string
raise BaseException (message)
# First declare the subfeature as a feature in its own right
f = feature (feature_name + '-' + subfeature_name, subvalues, attributes + ['subfeature'])
f.set_parent(parent_feature, value_string)
parent_feature.add_subfeature(f)
# Now make sure the subfeature values are known.
extend_subfeature (feature_name, value_string, subfeature, subvalues)
@bjam_signature((["composite_property_s"], ["component_properties_s", "*"]))
def compose (composite_property_s, component_properties_s):
""" Sets the components of the given composite property.
All parameters are <feature>value strings
"""
from . import property
component_properties_s = to_seq (component_properties_s)
composite_property = property.create_from_string(composite_property_s)
f = composite_property.feature
if len(component_properties_s) > 0 and isinstance(component_properties_s[0], property.Property):
component_properties = component_properties_s
else:
component_properties = [property.create_from_string(p) for p in component_properties_s]
if not f.composite:
raise BaseException ("'%s' is not a composite feature" % f)
if property in __composite_properties:
raise BaseException ('components of "%s" already set: %s' % (composite_property, str (__composite_properties[composite_property])))
if composite_property in component_properties:
raise BaseException ('composite property "%s" cannot have itself as a component' % composite_property)
__composite_properties[composite_property] = component_properties
def expand_composite(property_):
if __debug__:
from .property import Property
assert isinstance(property_, Property)
result = [ property_ ]
if property_ in __composite_properties:
for p in __composite_properties[property_]:
result.extend(expand_composite(p))
return result
@bjam_signature((['feature'], ['properties', '*']))
def get_values (feature, properties):
""" Returns all values of the given feature specified by the given property set.
"""
if feature[0] != '<':
feature = '<' + feature + '>'
result = []
for p in properties:
if get_grist (p) == feature:
result.append (replace_grist (p, ''))
return result
def free_features ():
""" Returns all free features.
"""
return __free_features
def expand_composites (properties):
""" Expand all composite properties in the set so that all components
are explicitly expressed.
"""
if __debug__:
from .property import Property
assert is_iterable_typed(properties, Property)
explicit_features = set(p.feature for p in properties)
result = []
# now expand composite features
for p in properties:
expanded = expand_composite(p)
for x in expanded:
if not x in result:
f = x.feature
if f.free:
result.append (x)
elif not x in properties: # x is the result of expansion
if not f in explicit_features: # not explicitly-specified
if any(r.feature == f for r in result):
raise FeatureConflict(
"expansions of composite features result in "
"conflicting values for '%s'\nvalues: '%s'\none contributing composite property was '%s'" %
(f.name, [r.value for r in result if r.feature == f] + [x.value], p))
else:
result.append (x)
elif any(r.feature == f for r in result):
raise FeatureConflict ("explicitly-specified values of non-free feature '%s' conflict\n"
"existing values: '%s'\nvalue from expanding '%s': '%s'" % (f,
[r.value for r in result if r.feature == f], p, x.value))
else:
result.append (x)
return result
# Uses Property
def is_subfeature_of (parent_property, f):
""" Return true iff f is an ordinary subfeature of the parent_property's
feature, or if f is a subfeature of the parent_property's feature
specific to the parent_property's value.
"""
if __debug__:
from .property import Property
assert isinstance(parent_property, Property)
assert isinstance(f, Feature)
if not f.subfeature:
return False
p = f.parent
if not p:
return False
parent_feature = p[0]
parent_value = p[1]
if parent_feature != parent_property.feature:
return False
if parent_value and parent_value != parent_property.value:
return False
return True
def __is_subproperty_of (parent_property, p):
""" As is_subfeature_of, for subproperties.
"""
if __debug__:
from .property import Property
assert isinstance(parent_property, Property)
assert isinstance(p, Property)
return is_subfeature_of (parent_property, p.feature)
# Returns true iff the subvalue is valid for the feature. When the
# optional value-string is provided, returns true iff the subvalues
# are valid for the given value of the feature.
def is_subvalue(feature, value_string, subfeature, subvalue):
assert isinstance(feature, basestring)
assert isinstance(value_string, basestring)
assert isinstance(subfeature, basestring)
assert isinstance(subvalue, basestring)
if not value_string:
value_string = ''
try:
return __subfeature_from_value[feature][value_string][subvalue] == subfeature
except KeyError:
return False
# Uses Property
def expand (properties):
""" Given a property set which may consist of composite and implicit
properties and combined subfeature values, returns an expanded,
normalized property set with all implicit features expressed
explicitly, all subfeature values individually expressed, and all
components of composite properties expanded. Non-free features
directly expressed in the input properties cause any values of
those features due to composite feature expansion to be dropped. If
two values of a given non-free feature are directly expressed in the
input, an error is issued.
"""
if __debug__:
from .property import Property
assert is_iterable_typed(properties, Property)
expanded = expand_subfeatures(properties)
return expand_composites (expanded)
# Accepts list of Property objects
def add_defaults (properties):
""" Given a set of properties, add default values for features not
represented in the set.
Note: if there's there's ordinary feature F1 and composite feature
F2, which includes some value for F1, and both feature have default values,
then the default value of F1 will be added, not the value in F2. This might
not be right idea: consider
feature variant : debug ... ;
<variant>debug : .... <runtime-debugging>on
feature <runtime-debugging> : off on ;
Here, when adding default for an empty property set, we'll get
<variant>debug <runtime_debugging>off
and that's kind of strange.
"""
if __debug__:
from .property import Property
assert is_iterable_typed(properties, Property)
# create a copy since properties will be modified
result = list(properties)
# We don't add default for conditional properties. We don't want
# <variant>debug:<define>DEBUG to be takes as specified value for <variant>
handled_features = set(p.feature for p in properties if not p.condition)
missing_top = [f for f in __all_top_features if not f in handled_features]
more = defaults(missing_top)
result.extend(more)
handled_features.update(p.feature for p in more)
# Add defaults for subfeatures of features which are present
for p in result[:]:
subfeatures = [s for s in p.feature.subfeatures if not s in handled_features]
more = defaults(__select_subfeatures(p, subfeatures))
handled_features.update(h.feature for h in more)
result.extend(more)
return result
def minimize (properties):
""" Given an expanded property set, eliminate all redundancy: properties
which are elements of other (composite) properties in the set will
be eliminated. Non-symmetric properties equal to default values will be
eliminated, unless the override a value from some composite property.
Implicit properties will be expressed without feature
grist, and sub-property values will be expressed as elements joined
to the corresponding main property.
"""
if __debug__:
from .property import Property
assert is_iterable_typed(properties, Property)
# remove properties implied by composite features
components = []
component_features = set()
for property in properties:
if property in __composite_properties:
cs = __composite_properties[property]
components.extend(cs)
component_features.update(c.feature for c in cs)
properties = b2.util.set.difference (properties, components)
# handle subfeatures and implicit features
# move subfeatures to the end of the list
properties = [p for p in properties if not p.feature.subfeature] +\
[p for p in properties if p.feature.subfeature]
result = []
while properties:
p = properties[0]
f = p.feature
# locate all subproperties of $(x[1]) in the property set
subproperties = [x for x in properties if is_subfeature_of(p, x.feature)]
if subproperties:
# reconstitute the joined property name
subproperties.sort ()
joined = b2.build.property.Property(p.feature, p.value + '-' + '-'.join ([sp.value for sp in subproperties]))
result.append(joined)
properties = b2.util.set.difference(properties[1:], subproperties)
else:
# eliminate properties whose value is equal to feature's
# default and which are not symmetric and which do not
# contradict values implied by composite properties.
# since all component properties of composites in the set
# have been eliminated, any remaining property whose
# feature is the same as a component of a composite in the
# set must have a non-redundant value.
if p.value != f.default or f.symmetric or f in component_features:
result.append (p)
properties = properties[1:]
return result
def split (properties):
""" Given a property-set of the form
v1/v2/...vN-1/<fN>vN/<fN+1>vN+1/...<fM>vM
Returns
v1 v2 ... vN-1 <fN>vN <fN+1>vN+1 ... <fM>vM
Note that vN...vM may contain slashes. This is resilient to the
substitution of backslashes for slashes, since Jam, unbidden,
sometimes swaps slash direction on NT.
"""
assert isinstance(properties, basestring)
def split_one (properties):
pieces = re.split (__re_slash_or_backslash, properties)
result = []
for x in pieces:
if not get_grist (x) and len (result) > 0 and get_grist (result [-1]):
result = result [0:-1] + [ result [-1] + '/' + x ]
else:
result.append (x)
return result
if isinstance (properties, str):
return split_one (properties)
result = []
for p in properties:
result += split_one (p)
return result
def compress_subproperties (properties):
""" Combine all subproperties into their parent properties
Requires: for every subproperty, there is a parent property. All
features are explicitly expressed.
This rule probably shouldn't be needed, but
build-request.expand-no-defaults is being abused for unintended
purposes and it needs help
"""
from .property import Property
assert is_iterable_typed(properties, Property)
result = []
matched_subs = set()
all_subs = set()
for p in properties:
f = p.feature
if not f.subfeature:
subs = [x for x in properties if is_subfeature_of(p, x.feature)]
if subs:
matched_subs.update(subs)
subvalues = '-'.join (sub.value for sub in subs)
result.append(Property(
p.feature, p.value + '-' + subvalues,
p.condition))
else:
result.append(p)
else:
all_subs.add(p)
# TODO: this variables are used just for debugging. What's the overhead?
assert all_subs == matched_subs
return result
######################################################################################
# Private methods
def __select_subproperties (parent_property, properties):
if __debug__:
from .property import Property
assert is_iterable_typed(properties, Property)
assert isinstance(parent_property, Property)
return [ x for x in properties if __is_subproperty_of (parent_property, x) ]
def __get_subfeature_name (subfeature, value_string):
assert isinstance(subfeature, basestring)
assert isinstance(value_string, basestring) or value_string is None
if value_string == None:
prefix = ''
else:
prefix = value_string + ':'
return prefix + subfeature
def __validate_feature_attributes (name, attributes):
assert isinstance(name, basestring)
assert is_iterable_typed(attributes, basestring)
for attribute in attributes:
if attribute not in VALID_ATTRIBUTES:
raise InvalidAttribute ("unknown attributes: '%s' in feature declaration: '%s'" % (str (b2.util.set.difference (attributes, __all_attributes)), name))
if name in __all_features:
raise AlreadyDefined ("feature '%s' already defined" % name)
elif 'implicit' in attributes and 'free' in attributes:
raise InvalidAttribute ("free features cannot also be implicit (in declaration of feature '%s')" % name)
elif 'free' in attributes and 'propagated' in attributes:
raise InvalidAttribute ("free features cannot also be propagated (in declaration of feature '%s')" % name)
def __validate_feature (feature):
""" Generates an error if the feature is unknown.
"""
assert isinstance(feature, basestring)
if feature not in __all_features:
raise BaseException ('unknown feature "%s"' % feature)
def __select_subfeatures (parent_property, features):
""" Given a property, return the subset of features consisting of all
ordinary subfeatures of the property's feature, and all specific
subfeatures of the property's feature which are conditional on the
property's value.
"""
if __debug__:
from .property import Property
assert isinstance(parent_property, Property)
assert is_iterable_typed(features, Feature)
return [f for f in features if is_subfeature_of (parent_property, f)]
# FIXME: copy over tests.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,604 @@
# Copyright 2003 Dave Abrahams
# Copyright 2003, 2004, 2005, 2006 Vladimir Prus
# 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 "class" : new ;
import feature ;
import indirect ;
import path ;
import project ;
import property ;
import sequence ;
import set ;
import option ;
# Class for storing a set of properties.
#
# There is 1<->1 correspondence between identity and value. No two instances
# of the class are equal. To maintain this property, the 'property-set.create'
# rule should be used to create new instances. Instances are immutable.
#
# Each property is classified with regard to its effect on build results.
# Incidental properties have no effect on build results, from B2's
# point of view. Others are either free, or non-free and we refer to non-free
# ones as 'base'. Each property belongs to exactly one of those categories.
#
# It is possible to get a list of properties belonging to each category as
# well as a list of properties with a specific attribute.
#
# Several operations, like and refine and as-path are provided. They all use
# caching whenever possible.
#
class property-set
{
import errors ;
import feature ;
import modules ;
import path ;
import property ;
import property-set ;
import set ;
rule __init__ ( raw-properties * )
{
self.raw = $(raw-properties) ;
for local p in $(raw-properties)
{
if ! $(p:G)
{
errors.error "Invalid property: '$(p)'" ;
}
}
}
# Returns Jam list of stored properties.
#
rule raw ( )
{
return $(self.raw) ;
}
rule str ( )
{
return "[" $(self.raw) "]" ;
}
# Returns properties that are neither incidental nor free.
#
rule base ( )
{
if ! $(self.base-initialized)
{
init-base ;
}
return $(self.base) ;
}
# Returns free properties which are not incidental.
#
rule free ( )
{
if ! $(self.base-initialized)
{
init-base ;
}
return $(self.free) ;
}
# Returns relevant base properties. This is used for computing
# target paths, so it must return the expanded set of relevant
# properties.
#
rule base-relevant ( )
{
if ! $(self.relevant-initialized)
{
init-relevant ;
}
return $(self.base-relevant) ;
}
# Returns all properties marked as relevant by features-ps
# Does not attempt to expand features-ps in any way, as
# this matches what virtual-target.register needs.
#
rule relevant ( features-ps )
{
if ! $(self.relevant.$(features-ps))
{
local result ;
local features = [ $(features-ps).get <relevant> ] ;
features = <$(features)> ;
local ignore-relevance = [ modules.peek
property-set : .ignore-relevance ] ;
for local p in $(self.raw)
{
if $(ignore-relevance) || $(p:G) in $(features)
{
local att = [ feature.attributes $(p:G) ] ;
if ! ( incidental in $(att) )
{
result += $(p) ;
}
}
}
self.relevant.$(features-ps) = [ property-set.create $(result) ] ;
}
return $(self.relevant.$(features-ps)) ;
}
# Returns dependency properties.
#
rule dependency ( )
{
if ! $(self.dependency-initialized)
{
init-dependency ;
}
return $(self.dependency) ;
}
rule non-dependency ( )
{
if ! $(self.dependency-initialized)
{
init-dependency ;
}
return $(self.non-dependency) ;
}
rule conditional ( )
{
if ! $(self.conditional-initialized)
{
init-conditional ;
}
return $(self.conditional) ;
}
rule non-conditional ( )
{
if ! $(self.conditional-initialized)
{
init-conditional ;
}
return $(self.non-conditional) ;
}
# Returns incidental properties.
#
rule incidental ( )
{
if ! $(self.base-initialized)
{
init-base ;
}
return $(self.incidental) ;
}
rule refine ( ps )
{
if ! $(self.refined.$(ps))
{
local r = [ property.refine $(self.raw) : [ $(ps).raw ] ] ;
if $(r[1]) != "@error"
{
self.refined.$(ps) = [ property-set.create $(r) ] ;
}
else
{
self.refined.$(ps) = $(r) ;
}
}
return $(self.refined.$(ps)) ;
}
rule expand ( )
{
if ! $(self.expanded)
{
self.expanded = [ property-set.create [ feature.expand $(self.raw) ]
] ;
}
return $(self.expanded) ;
}
rule expand-composites ( )
{
if ! $(self.composites)
{
self.composites = [ property-set.create
[ feature.expand-composites $(self.raw) ] ] ;
}
return $(self.composites) ;
}
rule evaluate-conditionals ( context ? )
{
context ?= $(__name__) ;
if ! $(self.evaluated.$(context))
{
self.evaluated.$(context) = [ property-set.create
[ property.evaluate-conditionals-in-context $(self.raw) : [
$(context).raw ] ] ] ;
}
return $(self.evaluated.$(context)) ;
}
rule propagated ( )
{
if ! $(self.propagated-ps)
{
local result ;
for local p in $(self.raw)
{
if propagated in [ feature.attributes $(p:G) ]
{
result += $(p) ;
}
}
self.propagated-ps = [ property-set.create $(result) ] ;
}
return $(self.propagated-ps) ;
}
rule add-defaults ( )
{
if ! $(self.defaults)
{
self.defaults = [ property-set.create
[ feature.add-defaults $(self.raw) ] ] ;
}
return $(self.defaults) ;
}
rule as-path ( )
{
if ! $(self.as-path)
{
self.as-path = [ property.as-path [ base-relevant ] ] ;
}
return $(self.as-path) ;
}
# Computes the path to be used for a target with the given properties.
# Returns a list of
# - the computed path
# - if the path is relative to the build directory, a value of 'true'.
#
rule target-path ( )
{
if ! $(self.target-path)
{
# The <location> feature can be used to explicitly change the
# location of generated targets.
local l = [ get <location> ] ;
if $(l)
{
self.target-path = $(l) ;
}
else
{
local p = [ property-set.hash-maybe [ as-path ] ] ;
# A real ugly hack. Boost regression test system requires
# specific target paths, and it seems that changing it to handle
# other directory layout is really hard. For that reason, we
# teach V2 to do the things regression system requires. The
# value of '<location-prefix>' is prepended to the path.
local prefix = [ get <location-prefix> ] ;
if $(prefix)
{
self.target-path = [ path.join $(prefix) $(p) ] ;
}
else
{
self.target-path = $(p) ;
}
if ! $(self.target-path)
{
self.target-path = . ;
}
# The path is relative to build dir.
self.target-path += true ;
}
}
return $(self.target-path) ;
}
rule add ( ps )
{
if ! $(self.added.$(ps))
{
self.added.$(ps) = [ property-set.create $(self.raw) [ $(ps).raw ] ]
;
}
return $(self.added.$(ps)) ;
}
rule add-raw ( properties * )
{
return [ add [ property-set.create $(properties) ] ] ;
}
# Returns all values of 'feature'.
#
rule get ( feature )
{
if ! $(self.map-built)
{
# For each feature, create a member var and assign all values to it.
# Since all regular member vars start with 'self', there will be no
# conflicts between names.
self.map-built = true ;
for local v in $(self.raw)
{
$(v:G) += $(v:G=) ;
}
}
return $($(feature)) ;
}
# Returns true if the property-set contains all the
# specified properties.
#
rule contains-raw ( properties * )
{
if $(properties) in $(self.raw)
{
return true ;
}
}
# Returns true if the property-set has values for
# all the specified features
#
rule contains-features ( features * )
{
if $(features) in $(self.raw:G)
{
return true ;
}
}
# private
rule init-base ( )
{
for local p in $(self.raw)
{
local att = [ feature.attributes $(p:G) ] ;
# A feature can be both incidental and free, in which case we add it
# to incidental.
if incidental in $(att)
{
self.incidental += $(p) ;
}
else if free in $(att)
{
self.free += $(p) ;
}
else
{
self.base += $(p) ;
}
}
self.base-initialized = true ;
}
rule init-relevant ( )
{
local relevant-features = [ get <relevant> ] ;
relevant-features = [ feature.expand-relevant $(relevant-features) ] ;
relevant-features = <$(relevant-features)> ;
ignore-relevance = [ modules.peek property-set : .ignore-relevance ] ;
for local p in $(self.raw)
{
if $(ignore-relevance) || $(p:G) in $(relevant-features)
{
local att = [ feature.attributes $(p:G) ] ;
if ! ( incidental in $(att) )
{
self.relevant += $(p) ;
if ! ( free in $(att) )
{
self.base-relevant += $(p) ;
}
}
}
}
self.relevant-initialized = true ;
}
rule init-dependency ( )
{
for local p in $(self.raw)
{
if dependency in [ feature.attributes $(p:G) ]
{
self.dependency += $(p) ;
}
else
{
self.non-dependency += $(p) ;
}
}
self.dependency-initialized = true ;
}
rule init-conditional ( )
{
for local p in $(self.raw)
{
# TODO: Note that non-conditional properties may contain colon (':')
# characters as well, e.g. free or indirect properties. Indirect
# properties for example contain a full Jamfile path in their value
# which on Windows file systems contains ':' as the drive separator.
if ( [ MATCH "(:)" : $(p:G=) ] && ! ( free in [ feature.attributes $(p:G) ] ) ) || $(p:G) = <conditional>
{
self.conditional += $(p) ;
}
else
{
self.non-conditional += $(p) ;
}
}
self.conditional-initialized = true ;
}
}
# This is a temporary measure to help users work around
# any problems. Remove it once we've verified that
# everything works.
if --ignore-relevance in [ modules.peek : ARGV ]
{
.ignore-relevance = true ;
}
# Creates a new 'property-set' instance for the given raw properties or returns
# an already existing ones.
#
rule create ( raw-properties * )
{
raw-properties = [ sequence.unique
[ sequence.insertion-sort $(raw-properties) ] ] ;
local key = $(raw-properties:J=-:E=) ;
if ! $(.ps.$(key))
{
.ps.$(key) = [ new property-set $(raw-properties) ] ;
}
return $(.ps.$(key)) ;
}
NATIVE_RULE property-set : create ;
if [ HAS_NATIVE_RULE class@property-set : get : 1 ]
{
NATIVE_RULE class@property-set : get ;
}
if [ HAS_NATIVE_RULE class@property-set : contains-features : 1 ]
{
NATIVE_RULE class@property-set : contains-features ;
}
# Creates a new 'property-set' instance after checking that all properties are
# valid and converting implicit properties into gristed form.
#
rule create-with-validation ( raw-properties * )
{
property.validate $(raw-properties) ;
return [ create [ property.make $(raw-properties) ] ] ;
}
# Creates a property-set from the input given by the user, in the context of
# 'jamfile-module' at 'location'.
#
rule create-from-user-input ( raw-properties * : jamfile-module location )
{
local project-id = [ project.attribute $(jamfile-module) id ] ;
project-id ?= [ path.root $(location) [ path.pwd ] ] ;
return [ property-set.create [ property.translate $(raw-properties)
: $(project-id) : $(location) : $(jamfile-module) ] ] ;
}
# Refines requirements with requirements provided by the user. Specially handles
# "-<property>value" syntax in specification to remove given requirements.
# - parent-requirements -- property-set object with requirements to refine.
# - specification -- string list of requirements provided by the user.
# - project-module -- module to which context indirect features will be
# bound.
# - location -- path to which path features are relative.
#
rule refine-from-user-input ( parent-requirements : specification * :
project-module : location )
{
if ! $(specification)
{
return $(parent-requirements) ;
}
else
{
local add-requirements ;
local remove-requirements ;
for local r in $(specification)
{
local m = [ MATCH "^-(.*)" : $(r) ] ;
if $(m)
{
remove-requirements += $(m) ;
}
else
{
add-requirements += $(r) ;
}
}
if $(remove-requirements)
{
# Need to create a property set, so that path features and indirect
# features are translated just like they are in project
# requirements.
local ps = [ property-set.create-from-user-input
$(remove-requirements) : $(project-module) $(location) ] ;
parent-requirements = [ property-set.create
[ set.difference
[ indirect.difference
[ $(parent-requirements).raw ] : [ $(ps).raw ] ]
: [ $(ps).raw ]
] ] ;
specification = $(add-requirements) ;
}
local requirements = [ property-set.create-from-user-input
$(specification) : $(project-module) $(location) ] ;
return [ $(parent-requirements).refine $(requirements) ] ;
}
}
# Returns a property-set with an empty set of properties.
#
rule empty ( )
{
if ! $(.empty)
{
.empty = [ create ] ;
}
return $(.empty) ;
}
if [ option.get hash : : yes ] = yes
{
rule hash-maybe ( path ? )
{
path ?= "" ;
return [ MD5 $(path) ] ;
}
}
else
{
rule hash-maybe ( path ? )
{
return $(path) ;
}
}
rule __test__ ( )
{
import errors : try catch ;
try ;
create invalid-property ;
catch "Invalid property: 'invalid-property'" ;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,750 @@
# Status: ported, except for tests.
# Base revision: 64070
#
# Copyright 2001, 2002, 2003 Dave Abrahams
# Copyright 2006 Rene Rivera
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
import re
import sys
from functools import total_ordering
from b2.util.utility import *
from b2.build import feature
from b2.util import sequence, qualify_jam_action, is_iterable_typed
import b2.util.set
from b2.manager import get_manager
__re_two_ampersands = re.compile ('&&')
__re_comma = re.compile (',')
__re_split_condition = re.compile ('(.*):(<.*)')
__re_split_conditional = re.compile (r'(.+):<(.+)')
__re_colon = re.compile (':')
__re_has_condition = re.compile (r':<')
__re_separate_condition_and_property = re.compile (r'(.*):(<.*)')
_not_applicable_feature='not-applicable-in-this-context'
feature.feature(_not_applicable_feature, [], ['free'])
__abbreviated_paths = False
class PropertyMeta(type):
"""
This class exists to implement the isinstance() and issubclass()
hooks for the Property class. Since we've introduce the concept of
a LazyProperty, isinstance(p, Property) will fail when p is a LazyProperty.
Implementing both __instancecheck__ and __subclasscheck__ will allow
LazyProperty instances to pass the isinstance() and issubclass check for
the Property class.
Additionally, the __call__ method intercepts the call to the Property
constructor to ensure that calling Property with the same arguments
will always return the same Property instance.
"""
_registry = {}
current_id = 1
def __call__(mcs, f, value, condition=None):
"""
This intercepts the call to the Property() constructor.
This exists so that the same arguments will always return the same Property
instance. This allows us to give each instance a unique ID.
"""
from b2.build.feature import Feature
if not isinstance(f, Feature):
f = feature.get(f)
if condition is None:
condition = []
key = (f, value) + tuple(sorted(condition))
if key not in mcs._registry:
instance = super(PropertyMeta, mcs).__call__(f, value, condition)
mcs._registry[key] = instance
return mcs._registry[key]
@staticmethod
def check(obj):
return (hasattr(obj, 'feature') and
hasattr(obj, 'value') and
hasattr(obj, 'condition'))
def __instancecheck__(self, instance):
return self.check(instance)
def __subclasscheck__(self, subclass):
return self.check(subclass)
@total_ordering
class Property(object):
__slots__ = ('feature', 'value', 'condition', '_to_raw', '_hash', 'id')
__metaclass__ = PropertyMeta
def __init__(self, f, value, condition=None):
assert(f.free or ':' not in value)
if condition is None:
condition = []
self.feature = f
self.value = value
self.condition = condition
self._hash = hash((self.feature, self.value) + tuple(sorted(self.condition)))
self.id = PropertyMeta.current_id
# increment the id counter.
# this allows us to take a list of Property
# instances and use their unique integer ID
# to create a key for PropertySet caching. This is
# much faster than string comparison.
PropertyMeta.current_id += 1
condition_str = ''
if condition:
condition_str = ",".join(str(p) for p in self.condition) + ':'
self._to_raw = '{}<{}>{}'.format(condition_str, f.name, value)
def to_raw(self):
return self._to_raw
def __str__(self):
return self._to_raw
def __hash__(self):
return self._hash
def __eq__(self, other):
return self._hash == other._hash
def __lt__(self, other):
return (self.feature.name, self.value) < (other.feature.name, other.value)
@total_ordering
class LazyProperty(object):
def __init__(self, feature_name, value, condition=None):
if condition is None:
condition = []
self.__property = Property(
feature.get(_not_applicable_feature), feature_name + value, condition=condition)
self.__name = feature_name
self.__value = value
self.__condition = condition
self.__feature = None
def __getattr__(self, item):
if self.__feature is None:
try:
self.__feature = feature.get(self.__name)
self.__property = Property(self.__feature, self.__value, self.__condition)
except KeyError:
pass
return getattr(self.__property, item)
def __hash__(self):
return hash(self.__property)
def __str__(self):
return self.__property._to_raw
def __eq__(self, other):
return self.__property == other
def __lt__(self, other):
return (self.feature.name, self.value) < (other.feature.name, other.value)
def create_from_string(s, allow_condition=False,allow_missing_value=False):
assert isinstance(s, basestring)
assert isinstance(allow_condition, bool)
assert isinstance(allow_missing_value, bool)
condition = []
import types
if not isinstance(s, types.StringType):
print type(s)
if __re_has_condition.search(s):
if not allow_condition:
raise BaseException("Conditional property is not allowed in this context")
m = __re_separate_condition_and_property.match(s)
condition = m.group(1)
s = m.group(2)
# FIXME: break dependency cycle
from b2.manager import get_manager
if condition:
condition = [create_from_string(x) for x in condition.split(',')]
feature_name = get_grist(s)
if not feature_name:
if feature.is_implicit_value(s):
f = feature.implied_feature(s)
value = s
p = Property(f, value, condition=condition)
else:
raise get_manager().errors()("Invalid property '%s' -- unknown feature" % s)
else:
value = get_value(s)
if not value and not allow_missing_value:
get_manager().errors()("Invalid property '%s' -- no value specified" % s)
if feature.valid(feature_name):
p = Property(feature.get(feature_name), value, condition=condition)
else:
# In case feature name is not known, it is wrong to do a hard error.
# Feature sets change depending on the toolset. So e.g.
# <toolset-X:version> is an unknown feature when using toolset Y.
#
# Ideally we would like to ignore this value, but most of
# Boost.Build code expects that we return a valid Property. For this
# reason we use a sentinel <not-applicable-in-this-context> feature.
#
# The underlying cause for this problem is that python port Property
# is more strict than its Jam counterpart and must always reference
# a valid feature.
p = LazyProperty(feature_name, value, condition=condition)
return p
def create_from_strings(string_list, allow_condition=False):
assert is_iterable_typed(string_list, basestring)
return [create_from_string(s, allow_condition) for s in string_list]
def reset ():
""" Clear the module state. This is mainly for testing purposes.
"""
global __results
# A cache of results from as_path
__results = {}
reset ()
def set_abbreviated_paths(on=True):
global __abbreviated_paths
if on == 'off':
on = False
on = bool(on)
__abbreviated_paths = on
def get_abbreviated_paths():
return __abbreviated_paths or '--abbreviated-paths' in sys.argv
def path_order (x, y):
""" Helper for as_path, below. Orders properties with the implicit ones
first, and within the two sections in alphabetical order of feature
name.
"""
if x == y:
return 0
xg = get_grist (x)
yg = get_grist (y)
if yg and not xg:
return -1
elif xg and not yg:
return 1
else:
if not xg:
x = feature.expand_subfeatures([x])
y = feature.expand_subfeatures([y])
if x < y:
return -1
elif x > y:
return 1
else:
return 0
def identify(string):
return string
# Uses Property
def refine (properties, requirements):
""" Refines 'properties' by overriding any non-free properties
for which a different value is specified in 'requirements'.
Conditional requirements are just added without modification.
Returns the resulting list of properties.
"""
assert is_iterable_typed(properties, Property)
assert is_iterable_typed(requirements, Property)
# The result has no duplicates, so we store it in a set
result = set()
# Records all requirements.
required = {}
# All the elements of requirements should be present in the result
# Record them so that we can handle 'properties'.
for r in requirements:
# Don't consider conditional requirements.
if not r.condition:
required[r.feature] = r
for p in properties:
# Skip conditional properties
if p.condition:
result.add(p)
# No processing for free properties
elif p.feature.free:
result.add(p)
else:
if p.feature in required:
result.add(required[p.feature])
else:
result.add(p)
return sequence.unique(list(result) + requirements)
def translate_paths (properties, path):
""" Interpret all path properties in 'properties' as relative to 'path'
The property values are assumed to be in system-specific form, and
will be translated into normalized form.
"""
assert is_iterable_typed(properties, Property)
result = []
for p in properties:
if p.feature.path:
values = __re_two_ampersands.split(p.value)
new_value = "&&".join(os.path.normpath(os.path.join(path, v)) for v in values)
if new_value != p.value:
result.append(Property(p.feature, new_value, p.condition))
else:
result.append(p)
else:
result.append (p)
return result
def translate_indirect(properties, context_module):
"""Assumes that all feature values that start with '@' are
names of rules, used in 'context-module'. Such rules can be
either local to the module or global. Qualified local rules
with the name of the module."""
assert is_iterable_typed(properties, Property)
assert isinstance(context_module, basestring)
result = []
for p in properties:
if p.value[0] == '@':
q = qualify_jam_action(p.value[1:], context_module)
get_manager().engine().register_bjam_action(q)
result.append(Property(p.feature, '@' + q, p.condition))
else:
result.append(p)
return result
def validate (properties):
""" Exit with error if any of the properties is not valid.
properties may be a single property or a sequence of properties.
"""
if isinstance(properties, Property):
properties = [properties]
assert is_iterable_typed(properties, Property)
for p in properties:
__validate1(p)
def expand_subfeatures_in_conditions (properties):
assert is_iterable_typed(properties, Property)
result = []
for p in properties:
if not p.condition:
result.append(p)
else:
expanded = []
for c in p.condition:
# It common that condition includes a toolset which
# was never defined, or mentiones subfeatures which
# were never defined. In that case, validation will
# only produce an spirious error, so don't validate.
expanded.extend(feature.expand_subfeatures ([c], True))
# we need to keep LazyProperties lazy
if isinstance(p, LazyProperty):
value = p.value
feature_name = get_grist(value)
value = value.replace(feature_name, '')
result.append(LazyProperty(feature_name, value, condition=expanded))
else:
result.append(Property(p.feature, p.value, expanded))
return result
# FIXME: this should go
def split_conditional (property):
""" If 'property' is conditional property, returns
condition and the property, e.g
<variant>debug,<toolset>gcc:<inlining>full will become
<variant>debug,<toolset>gcc <inlining>full.
Otherwise, returns empty string.
"""
assert isinstance(property, basestring)
m = __re_split_conditional.match (property)
if m:
return (m.group (1), '<' + m.group (2))
return None
def select (features, properties):
""" Selects properties which correspond to any of the given features.
"""
assert is_iterable_typed(properties, basestring)
result = []
# add any missing angle brackets
features = add_grist (features)
return [p for p in properties if get_grist(p) in features]
def validate_property_sets (sets):
if __debug__:
from .property_set import PropertySet
assert is_iterable_typed(sets, PropertySet)
for s in sets:
validate(s.all())
def evaluate_conditionals_in_context (properties, context):
""" Removes all conditional properties which conditions are not met
For those with met conditions, removes the condition. Properties
in conditions are looked up in 'context'
"""
if __debug__:
from .property_set import PropertySet
assert is_iterable_typed(properties, Property)
assert isinstance(context, PropertySet)
base = []
conditional = []
for p in properties:
if p.condition:
conditional.append (p)
else:
base.append (p)
result = base[:]
for p in conditional:
# Evaluate condition
# FIXME: probably inefficient
if all(x in context for x in p.condition):
result.append(Property(p.feature, p.value))
return result
def change (properties, feature, value = None):
""" Returns a modified version of properties with all values of the
given feature replaced by the given value.
If 'value' is None the feature will be removed.
"""
assert is_iterable_typed(properties, basestring)
assert isinstance(feature, basestring)
assert isinstance(value, (basestring, type(None)))
result = []
feature = add_grist (feature)
for p in properties:
if get_grist (p) == feature:
if value:
result.append (replace_grist (value, feature))
else:
result.append (p)
return result
################################################################
# Private functions
def __validate1 (property):
""" Exit with error if property is not valid.
"""
assert isinstance(property, Property)
msg = None
if not property.feature.free:
feature.validate_value_string (property.feature, property.value)
###################################################################
# Still to port.
# Original lines are prefixed with "# "
#
#
# import utility : ungrist ;
# import sequence : unique ;
# import errors : error ;
# import feature ;
# import regex ;
# import sequence ;
# import set ;
# import path ;
# import assert ;
#
#
# rule validate-property-sets ( property-sets * )
# {
# for local s in $(property-sets)
# {
# validate [ feature.split $(s) ] ;
# }
# }
#
def remove(attributes, properties):
"""Returns a property sets which include all the elements
in 'properties' that do not have attributes listed in 'attributes'."""
if isinstance(attributes, basestring):
attributes = [attributes]
assert is_iterable_typed(attributes, basestring)
assert is_iterable_typed(properties, basestring)
result = []
for e in properties:
attributes_new = feature.attributes(get_grist(e))
has_common_features = 0
for a in attributes_new:
if a in attributes:
has_common_features = 1
break
if not has_common_features:
result += e
return result
def take(attributes, properties):
"""Returns a property set which include all
properties in 'properties' that have any of 'attributes'."""
assert is_iterable_typed(attributes, basestring)
assert is_iterable_typed(properties, basestring)
result = []
for e in properties:
if b2.util.set.intersection(attributes, feature.attributes(get_grist(e))):
result.append(e)
return result
def translate_dependencies(properties, project_id, location):
assert is_iterable_typed(properties, Property)
assert isinstance(project_id, basestring)
assert isinstance(location, basestring)
result = []
for p in properties:
if not p.feature.dependency:
result.append(p)
else:
v = p.value
m = re.match("(.*)//(.*)", v)
if m:
rooted = m.group(1)
if rooted[0] == '/':
# Either project id or absolute Linux path, do nothing.
pass
else:
rooted = os.path.join(os.getcwd(), location, rooted)
result.append(Property(p.feature, rooted + "//" + m.group(2), p.condition))
elif os.path.isabs(v):
result.append(p)
else:
result.append(Property(p.feature, project_id + "//" + v, p.condition))
return result
class PropertyMap:
""" Class which maintains a property set -> string mapping.
"""
def __init__ (self):
self.__properties = []
self.__values = []
def insert (self, properties, value):
""" Associate value with properties.
"""
assert is_iterable_typed(properties, basestring)
assert isinstance(value, basestring)
self.__properties.append(properties)
self.__values.append(value)
def find (self, properties):
""" Return the value associated with properties
or any subset of it. If more than one
subset has value assigned to it, return the
value for the longest subset, if it's unique.
"""
assert is_iterable_typed(properties, basestring)
return self.find_replace (properties)
def find_replace(self, properties, value=None):
assert is_iterable_typed(properties, basestring)
assert isinstance(value, (basestring, type(None)))
matches = []
match_ranks = []
for i in range(0, len(self.__properties)):
p = self.__properties[i]
if b2.util.set.contains (p, properties):
matches.append (i)
match_ranks.append(len(p))
best = sequence.select_highest_ranked (matches, match_ranks)
if not best:
return None
if len (best) > 1:
raise NoBestMatchingAlternative ()
best = best [0]
original = self.__values[best]
if value:
self.__values[best] = value
return original
# local rule __test__ ( )
# {
# import errors : try catch ;
# import feature ;
# import feature : feature subfeature compose ;
#
# # local rules must be explicitly re-imported
# import property : path-order ;
#
# feature.prepare-test property-test-temp ;
#
# feature toolset : gcc : implicit symmetric ;
# subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
# 3.0 3.0.1 3.0.2 : optional ;
# feature define : : free ;
# feature runtime-link : dynamic static : symmetric link-incompatible ;
# feature optimization : on off ;
# feature variant : debug release : implicit composite symmetric ;
# feature rtti : on off : link-incompatible ;
#
# compose <variant>debug : <define>_DEBUG <optimization>off ;
# compose <variant>release : <define>NDEBUG <optimization>on ;
#
# import assert ;
# import "class" : new ;
#
# validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ;
#
# assert.result <toolset>gcc <rtti>off <define>FOO
# : refine <toolset>gcc <rtti>off
# : <define>FOO
# : $(test-space)
# ;
#
# assert.result <toolset>gcc <optimization>on
# : refine <toolset>gcc <optimization>off
# : <optimization>on
# : $(test-space)
# ;
#
# assert.result <toolset>gcc <rtti>off
# : refine <toolset>gcc : <rtti>off : $(test-space)
# ;
#
# assert.result <toolset>gcc <rtti>off <rtti>off:<define>FOO
# : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO
# : $(test-space)
# ;
#
# assert.result <toolset>gcc:<define>foo <toolset>gcc:<define>bar
# : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar
# : $(test-space)
# ;
#
# assert.result <define>MY_RELEASE
# : evaluate-conditionals-in-context
# <variant>release,<rtti>off:<define>MY_RELEASE
# : <toolset>gcc <variant>release <rtti>off
#
# ;
#
# try ;
# validate <feature>value : $(test-space) ;
# catch "Invalid property '<feature>value': unknown feature 'feature'." ;
#
# try ;
# validate <rtti>default : $(test-space) ;
# catch \"default\" is not a known value of feature <rtti> ;
#
# validate <define>WHATEVER : $(test-space) ;
#
# try ;
# validate <rtti> : $(test-space) ;
# catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;
#
# try ;
# validate value : $(test-space) ;
# catch "value" is not a value of an implicit feature ;
#
#
# assert.result <rtti>on
# : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;
#
# assert.result <include>a
# : select include : <include>a <toolset>gcc ;
#
# assert.result <include>a
# : select include bar : <include>a <toolset>gcc ;
#
# assert.result <include>a <toolset>gcc
# : select include <bar> <toolset> : <include>a <toolset>gcc ;
#
# assert.result <toolset>kylix <include>a
# : change <toolset>gcc <include>a : <toolset> kylix ;
#
# # Test ordinary properties
# assert.result
# : split-conditional <toolset>gcc
# ;
#
# # Test properties with ":"
# assert.result
# : split-conditional <define>FOO=A::B
# ;
#
# # Test conditional feature
# assert.result <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO
# : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO
# ;
#
# feature.finish-test property-test-temp ;
# }
#

View File

@@ -0,0 +1,498 @@
# Status: ported.
# Base revision: 40480
# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
# distribute this software is granted provided this copyright notice appears in
# all copies. This software is provided "as is" without express or implied
# warranty, and with no claim as to its suitability for any purpose.
import hashlib
import bjam
from b2.util.utility import *
import property, feature
import b2.build.feature
from b2.exceptions import *
from b2.build.property import get_abbreviated_paths
from b2.util.sequence import unique
from b2.util.set import difference
from b2.util import cached, abbreviate_dashed, is_iterable_typed
from b2.manager import get_manager
def reset ():
""" Clear the module state. This is mainly for testing purposes.
"""
global __cache
# A cache of property sets
# TODO: use a map of weak refs?
__cache = {}
reset ()
def create (raw_properties = []):
""" Creates a new 'PropertySet' instance for the given raw properties,
or returns an already existing one.
"""
assert (is_iterable_typed(raw_properties, property.Property)
or is_iterable_typed(raw_properties, basestring))
# FIXME: propagate to callers.
if len(raw_properties) > 0 and isinstance(raw_properties[0], property.Property):
x = raw_properties
else:
x = [property.create_from_string(ps) for ps in raw_properties]
# These two lines of code are optimized to the current state
# of the Property class. Since this function acts as the caching
# frontend to the PropertySet class modifying these two lines
# could have a severe performance penalty. Be careful.
# It would be faster to sort by p.id, but some projects may rely
# on the fact that the properties are ordered alphabetically. So,
# we maintain alphabetical sorting so as to maintain backward compatibility.
x = sorted(set(x), key=lambda p: (p.feature.name, p.value, p.condition))
key = tuple(p.id for p in x)
if key not in __cache:
__cache [key] = PropertySet(x)
return __cache [key]
def create_with_validation (raw_properties):
""" Creates new 'PropertySet' instances after checking
that all properties are valid and converting implicit
properties into gristed form.
"""
assert is_iterable_typed(raw_properties, basestring)
properties = [property.create_from_string(s) for s in raw_properties]
property.validate(properties)
return create(properties)
def empty ():
""" Returns PropertySet with empty set of properties.
"""
return create ()
def create_from_user_input(raw_properties, jamfile_module, location):
"""Creates a property-set from the input given by the user, in the
context of 'jamfile-module' at 'location'"""
assert is_iterable_typed(raw_properties, basestring)
assert isinstance(jamfile_module, basestring)
assert isinstance(location, basestring)
properties = property.create_from_strings(raw_properties, True)
properties = property.translate_paths(properties, location)
properties = property.translate_indirect(properties, jamfile_module)
project_id = get_manager().projects().attributeDefault(jamfile_module, 'id', None)
if not project_id:
project_id = os.path.abspath(location)
properties = property.translate_dependencies(properties, project_id, location)
properties = property.expand_subfeatures_in_conditions(properties)
return create(properties)
def refine_from_user_input(parent_requirements, specification, jamfile_module,
location):
"""Refines requirements with requirements provided by the user.
Specially handles "-<property>value" syntax in specification
to remove given requirements.
- parent-requirements -- property-set object with requirements
to refine
- specification -- string list of requirements provided by the use
- project-module -- the module to which context indirect features
will be bound.
- location -- the path to which path features are relative."""
assert isinstance(parent_requirements, PropertySet)
assert is_iterable_typed(specification, basestring)
assert isinstance(jamfile_module, basestring)
assert isinstance(location, basestring)
if not specification:
return parent_requirements
add_requirements = []
remove_requirements = []
for r in specification:
if r[0] == '-':
remove_requirements.append(r[1:])
else:
add_requirements.append(r)
if remove_requirements:
# Need to create property set, so that path features
# and indirect features are translated just like they
# are in project requirements.
ps = create_from_user_input(remove_requirements,
jamfile_module, location)
parent_requirements = create(difference(parent_requirements.all(),
ps.all()))
specification = add_requirements
requirements = create_from_user_input(specification,
jamfile_module, location)
return parent_requirements.refine(requirements)
class PropertySet:
""" Class for storing a set of properties.
- there's 1<->1 correspondence between identity and value. No
two instances of the class are equal. To maintain this property,
the 'PropertySet.create' rule should be used to create new instances.
Instances are immutable.
- each property is classified with regard to it's effect on build
results. Incidental properties have no effect on build results, from
Boost.Build point of view. Others are either free, or non-free, which we
call 'base'. Each property belong to exactly one of those categories and
it's possible to get list of properties in each category.
In addition, it's possible to get list of properties with specific
attribute.
- several operations, like and refine and as_path are provided. They all use
caching whenever possible.
"""
def __init__ (self, properties=None):
if properties is None:
properties = []
assert is_iterable_typed(properties, property.Property)
self.all_ = properties
self._all_set = {p.id for p in properties}
self.incidental_ = []
self.free_ = []
self.base_ = []
self.dependency_ = []
self.non_dependency_ = []
self.conditional_ = []
self.non_conditional_ = []
self.propagated_ = []
self.link_incompatible = []
# A cache of refined properties.
self.refined_ = {}
# A cache of property sets created by adding properties to this one.
self.added_ = {}
# Cache for the default properties.
self.defaults_ = None
# Cache for the expanded properties.
self.expanded_ = None
# Cache for the expanded composite properties
self.composites_ = None
# Cache for property set with expanded subfeatures
self.subfeatures_ = None
# Cache for the property set containing propagated properties.
self.propagated_ps_ = None
# A map of features to its values.
self.feature_map_ = None
# A tuple (target path, is relative to build directory)
self.target_path_ = None
self.as_path_ = None
# A cache for already evaluated sets.
self.evaluated_ = {}
# stores the list of LazyProperty instances.
# these are being kept separate from the normal
# Property instances so that when this PropertySet
# tries to return one of its attributes, it
# will then try to evaluate the LazyProperty instances
# first before returning.
self.lazy_properties = []
for p in properties:
f = p.feature
if isinstance(p, property.LazyProperty):
self.lazy_properties.append(p)
# A feature can be both incidental and free,
# in which case we add it to incidental.
elif f.incidental:
self.incidental_.append(p)
elif f.free:
self.free_.append(p)
else:
self.base_.append(p)
if p.condition:
self.conditional_.append(p)
else:
self.non_conditional_.append(p)
if f.dependency:
self.dependency_.append (p)
elif not isinstance(p, property.LazyProperty):
self.non_dependency_.append (p)
if f.propagated:
self.propagated_.append(p)
if f.link_incompatible:
self.link_incompatible.append(p)
def all(self):
return self.all_
def raw (self):
""" Returns the list of stored properties.
"""
# create a new list due to the LazyProperties.
# this gives them a chance to evaluate to their
# true Property(). This approach is being
# taken since calculations should not be using
# PropertySet.raw()
return [p._to_raw for p in self.all_]
def __str__(self):
return ' '.join(p._to_raw for p in self.all_)
def base (self):
""" Returns properties that are neither incidental nor free.
"""
result = [p for p in self.lazy_properties
if not(p.feature.incidental or p.feature.free)]
result.extend(self.base_)
return result
def free (self):
""" Returns free properties which are not dependency properties.
"""
result = [p for p in self.lazy_properties
if not p.feature.incidental and p.feature.free]
result.extend(self.free_)
return result
def non_free(self):
return self.base() + self.incidental()
def dependency (self):
""" Returns dependency properties.
"""
result = [p for p in self.lazy_properties if p.feature.dependency]
result.extend(self.dependency_)
return self.dependency_
def non_dependency (self):
""" Returns properties that are not dependencies.
"""
result = [p for p in self.lazy_properties if not p.feature.dependency]
result.extend(self.non_dependency_)
return result
def conditional (self):
""" Returns conditional properties.
"""
return self.conditional_
def non_conditional (self):
""" Returns properties that are not conditional.
"""
return self.non_conditional_
def incidental (self):
""" Returns incidental properties.
"""
result = [p for p in self.lazy_properties if p.feature.incidental]
result.extend(self.incidental_)
return result
def refine (self, requirements):
""" Refines this set's properties using the requirements passed as an argument.
"""
assert isinstance(requirements, PropertySet)
if requirements not in self.refined_:
r = property.refine(self.all_, requirements.all_)
self.refined_[requirements] = create(r)
return self.refined_[requirements]
def expand (self):
if not self.expanded_:
expanded = feature.expand(self.all_)
self.expanded_ = create(expanded)
return self.expanded_
def expand_subfeatures(self):
if not self.subfeatures_:
self.subfeatures_ = create(feature.expand_subfeatures(self.all_))
return self.subfeatures_
def evaluate_conditionals(self, context=None):
assert isinstance(context, (PropertySet, type(None)))
if not context:
context = self
if context not in self.evaluated_:
# FIXME: figure why the call messes up first parameter
self.evaluated_[context] = create(
property.evaluate_conditionals_in_context(self.all(), context))
return self.evaluated_[context]
def propagated (self):
if not self.propagated_ps_:
self.propagated_ps_ = create (self.propagated_)
return self.propagated_ps_
def add_defaults (self):
# FIXME: this caching is invalidated when new features
# are declare inside non-root Jamfiles.
if not self.defaults_:
expanded = feature.add_defaults(self.all_)
self.defaults_ = create(expanded)
return self.defaults_
def as_path (self):
if not self.as_path_:
def path_order (p1, p2):
i1 = p1.feature.implicit
i2 = p2.feature.implicit
if i1 != i2:
return i2 - i1
else:
return cmp(p1.feature.name, p2.feature.name)
# trim redundancy
properties = feature.minimize(self.base_)
# sort according to path_order
properties.sort (path_order)
components = []
for p in properties:
f = p.feature
if f.implicit:
components.append(p.value)
else:
value = f.name.replace(':', '-') + "-" + p.value
if property.get_abbreviated_paths():
value = abbreviate_dashed(value)
components.append(value)
self.as_path_ = '/'.join(components)
return self.as_path_
def target_path (self):
""" Computes the target path that should be used for
target with these properties.
Returns a tuple of
- the computed path
- if the path is relative to build directory, a value of
'true'.
"""
if not self.target_path_:
# The <location> feature can be used to explicitly
# change the location of generated targets
l = self.get ('<location>')
if l:
computed = l[0]
is_relative = False
else:
p = self.as_path()
if hash_maybe:
p = hash_maybe(p)
# Really, an ugly hack. Boost regression test system requires
# specific target paths, and it seems that changing it to handle
# other directory layout is really hard. For that reason,
# we teach V2 to do the things regression system requires.
# The value o '<location-prefix>' is predended to the path.
prefix = self.get ('<location-prefix>')
if prefix:
if len (prefix) > 1:
raise AlreadyDefined ("Two <location-prefix> properties specified: '%s'" % prefix)
computed = os.path.join(prefix[0], p)
else:
computed = p
if not computed:
computed = "."
is_relative = True
self.target_path_ = (computed, is_relative)
return self.target_path_
def add (self, ps):
""" Creates a new property set containing the properties in this one,
plus the ones of the property set passed as argument.
"""
assert isinstance(ps, PropertySet)
if ps not in self.added_:
self.added_[ps] = create(self.all_ + ps.all())
return self.added_[ps]
def add_raw (self, properties):
""" Creates a new property set containing the properties in this one,
plus the ones passed as argument.
"""
return self.add (create (properties))
def get (self, feature):
""" Returns all values of 'feature'.
"""
if type(feature) == type([]):
feature = feature[0]
if not isinstance(feature, b2.build.feature.Feature):
feature = b2.build.feature.get(feature)
assert isinstance(feature, b2.build.feature.Feature)
if self.feature_map_ is None:
self.feature_map_ = {}
for v in self.all_:
if v.feature not in self.feature_map_:
self.feature_map_[v.feature] = []
self.feature_map_[v.feature].append(v.value)
return self.feature_map_.get(feature, [])
@cached
def get_properties(self, feature):
"""Returns all contained properties associated with 'feature'"""
if not isinstance(feature, b2.build.feature.Feature):
feature = b2.build.feature.get(feature)
assert isinstance(feature, b2.build.feature.Feature)
result = []
for p in self.all_:
if p.feature == feature:
result.append(p)
return result
def __contains__(self, item):
return item.id in self._all_set
def hash(p):
m = hashlib.md5()
m.update(p)
return m.hexdigest()
hash_maybe = hash if "--hash" in bjam.variable("ARGV") else None

View File

@@ -0,0 +1,11 @@
Copyright 2001, 2002 Dave Abrahams
Copyright 2002 Vladimir Prus
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
Development code for new build system. To run unit tests for jam code, execute:
bjam --debug --build-system=test
Comprehensive tests require Python. See ../test/readme.txt

View File

@@ -0,0 +1,163 @@
# Copyright 2003 Dave Abrahams
# Copyright 2002, 2003, 2004, 2005 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.bfgroup.xyz/b2/LICENSE.txt)
# Implements scanners: objects computing implicit dependencies for files, such
# as includes in C++.
#
# A scanner has a regular expression used to find the dependencies, some data
# needed to interpret those dependencies (e.g., include paths), and code which
# establishing needed relationships between actual jam targets.
#
# Scanner objects are created by actions when they try to actualize virtual
# targets, passed to the virtual-target.actualize() method and are then
# associated with actual targets. It is possible to use several scanners for a
# single virtual-target. For example, a single source file might be compiled
# twice - each time using a different include path. In this case, two separate
# actual targets will be created, each having a scanner of its own.
#
# Typically, scanners are created from target type and the action's properties,
# using the rule 'get' in this module. Directly creating scanners is not
# recommended, as it might create multiple equvivalent but different instances,
# and lead to unnecessary actual target duplication. However, actions can also
# create scanners in a special way, instead of relying on just the target type.
import "class" : new ;
import property ;
import property-set ;
import virtual-target ;
# Base scanner class.
#
class scanner
{
rule __init__ ( )
{
}
# Returns a pattern to use for scanning.
#
rule pattern ( )
{
import errors : error : errors.error ;
errors.error "method must be overridden" ;
}
# Establish necessary relationship between targets, given an actual target
# being scanned and a list of pattern matches in that file.
#
rule process ( target : matches * )
{
import errors : error : errors.error ;
errors.error "method must be overridden" ;
}
}
# Registers a new generator class, specifying a set of properties relevant to
# this scanner. Constructor for that class should have one parameter: a list of
# properties.
#
rule register ( scanner-class : relevant-properties * )
{
.registered += $(scanner-class) ;
.relevant-properties.$(scanner-class) = $(relevant-properties) ;
}
# Common scanner class, usable when there is only one kind of includes (unlike
# C, where "" and <> includes have different search paths).
#
class common-scanner : scanner
{
import scanner ;
rule __init__ ( includes * )
{
scanner.__init__ ;
self.includes = $(includes) ;
}
rule process ( target : matches * : binding )
{
local target_path = [ NORMALIZE_PATH $(binding:D) ] ;
NOCARE $(matches) ;
INCLUDES $(target) : $(matches) ;
SEARCH on $(matches) = $(target_path) $(self.includes:G=) ;
ISFILE $(matches) ;
scanner.propagate $(__name__) : $(matches) : $(target) ;
}
}
# Returns an instance of a previously registered scanner, with the specified
# properties.
#
rule get ( scanner-class : property-set )
{
if ! $(scanner-class) in $(.registered)
{
import errors ;
errors.error "attempt to get an unregistered scanner" ;
}
local r = $(.rv-cache.$(property-set)) ;
if ! $(r)
{
r = [ property-set.create
[ property.select $(.relevant-properties.$(scanner-class)) :
[ $(property-set).raw ] ] ] ;
.rv-cache.$(property-set) = $(r) ;
}
if ! $(scanner.$(scanner-class).$(r:J=-))
{
local s = [ new $(scanner-class) [ $(r).raw ] ] ;
scanner.$(scanner-class).$(r:J=-) = $(s) ;
}
return $(scanner.$(scanner-class).$(r:J=-)) ;
}
# Installs the specified scanner on the actual target 'target'.
#
rule install ( scanner : target )
{
HDRSCAN on $(target) = [ $(scanner).pattern ] ;
SCANNER on $(target) = $(scanner) ;
HDRRULE on $(target) = scanner.hdrrule ;
# Scanner reflects differences in properties affecting binding of 'target',
# which will be known when processing includes for it, and give information
# on how to interpret different include types (e.g. quoted vs. those in
# angle brackets in C files).
HDRGRIST on $(target) = $(scanner) ;
}
# Propagate scanner settings from 'including-target' to 'targets'.
#
rule propagate ( scanner : targets * : including-target )
{
HDRSCAN on $(targets) = [ on $(including-target) return $(HDRSCAN) ] ;
SCANNER on $(targets) = $(scanner) ;
HDRRULE on $(targets) = scanner.hdrrule ;
HDRGRIST on $(targets) = [ on $(including-target) return $(HDRGRIST) ] ;
}
rule hdrrule ( target : matches * : binding )
{
local scanner = [ on $(target) return $(SCANNER) ] ;
$(scanner).process $(target) : $(matches) : $(binding) ;
}
# hdrrule must be available at global scope so it can be invoked by header
# scanning.
#
IMPORT scanner : hdrrule : : scanner.hdrrule ;

View File

@@ -0,0 +1,167 @@
# Status: ported.
# Base revision: 45462
#
# Copyright 2003 Dave Abrahams
# Copyright 2002, 2003, 2004, 2005 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
# Implements scanners: objects that compute implicit dependencies for
# files, such as includes in C++.
#
# Scanner has a regular expression used to find dependencies, some
# data needed to interpret those dependencies (for example, include
# paths), and a code which actually established needed relationship
# between actual jam targets.
#
# Scanner objects are created by actions, when they try to actualize
# virtual targets, passed to 'virtual-target.actualize' method and are
# then associated with actual targets. It is possible to use
# several scanners for a virtual-target. For example, a single source
# might be used by to compile actions, with different include paths.
# In this case, two different actual targets will be created, each
# having scanner of its own.
#
# Typically, scanners are created from target type and action's
# properties, using the rule 'get' in this module. Directly creating
# scanners is not recommended, because it might create many equvivalent
# but different instances, and lead in unneeded duplication of
# actual targets. However, actions can also create scanners in a special
# way, instead of relying on just target type.
import property
import bjam
import os
from b2.manager import get_manager
from b2.util import is_iterable_typed
def reset ():
""" Clear the module state. This is mainly for testing purposes.
"""
global __scanners, __rv_cache, __scanner_cache
# Maps registered scanner classes to relevant properties
__scanners = {}
# A cache of scanners.
# The key is: class_name.properties_tag, where properties_tag is the concatenation
# of all relevant properties, separated by '-'
__scanner_cache = {}
reset ()
def register(scanner_class, relevant_properties):
""" Registers a new generator class, specifying a set of
properties relevant to this scanner. Ctor for that class
should have one parameter: list of properties.
"""
assert issubclass(scanner_class, Scanner)
assert isinstance(relevant_properties, basestring)
__scanners[str(scanner_class)] = relevant_properties
def registered(scanner_class):
""" Returns true iff a scanner of that class is registered
"""
return str(scanner_class) in __scanners
def get(scanner_class, properties):
""" Returns an instance of previously registered scanner
with the specified properties.
"""
assert issubclass(scanner_class, Scanner)
assert is_iterable_typed(properties, basestring)
scanner_name = str(scanner_class)
if not registered(scanner_name):
raise BaseException ("attempt to get unregistered scanner: %s" % scanner_name)
relevant_properties = __scanners[scanner_name]
r = property.select(relevant_properties, properties)
scanner_id = scanner_name + '.' + '-'.join(r)
if scanner_id not in __scanner_cache:
__scanner_cache[scanner_id] = scanner_class(r)
return __scanner_cache[scanner_id]
class Scanner:
""" Base scanner class.
"""
def __init__ (self):
pass
def pattern (self):
""" Returns a pattern to use for scanning.
"""
raise BaseException ("method must be overridden")
def process (self, target, matches, binding):
""" Establish necessary relationship between targets,
given actual target being scanned, and a list of
pattern matches in that file.
"""
raise BaseException ("method must be overridden")
# Common scanner class, which can be used when there's only one
# kind of includes (unlike C, where "" and <> includes have different
# search paths).
class CommonScanner(Scanner):
def __init__ (self, includes):
Scanner.__init__(self)
self.includes = includes
def process(self, target, matches, binding):
target_path = os.path.normpath(os.path.dirname(binding[0]))
bjam.call("mark-included", target, matches)
get_manager().engine().set_target_variable(matches, "SEARCH",
[target_path] + self.includes)
get_manager().scanners().propagate(self, matches)
class ScannerRegistry:
def __init__ (self, manager):
self.manager_ = manager
self.count_ = 0
self.exported_scanners_ = {}
def install (self, scanner, target, vtarget):
""" Installs the specified scanner on actual target 'target'.
vtarget: virtual target from which 'target' was actualized.
"""
assert isinstance(scanner, Scanner)
assert isinstance(target, basestring)
assert isinstance(vtarget, basestring)
engine = self.manager_.engine()
engine.set_target_variable(target, "HDRSCAN", scanner.pattern())
if scanner not in self.exported_scanners_:
exported_name = "scanner_" + str(self.count_)
self.count_ = self.count_ + 1
self.exported_scanners_[scanner] = exported_name
bjam.import_rule("", exported_name, scanner.process)
else:
exported_name = self.exported_scanners_[scanner]
engine.set_target_variable(target, "HDRRULE", exported_name)
# scanner reflects difference in properties affecting
# binding of 'target', which will be known when processing
# includes for it, will give information on how to
# interpret quoted includes.
engine.set_target_variable(target, "HDRGRIST", str(id(scanner)))
pass
def propagate(self, scanner, targets):
assert isinstance(scanner, Scanner)
assert is_iterable_typed(targets, basestring) or isinstance(targets, basestring)
engine = self.manager_.engine()
engine.set_target_variable(targets, "HDRSCAN", scanner.pattern())
engine.set_target_variable(targets, "HDRRULE",
self.exported_scanners_[scanner])
engine.set_target_variable(targets, "HDRGRIST", str(id(scanner)))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,703 @@
# Copyright 2003 Dave Abrahams
# Copyright 2005 Rene Rivera
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at
# https://www.bfgroup.xyz/b2/LICENSE.txt)
# Support for toolset definition.
import errors ;
import feature ;
import generators ;
import numbers ;
import path ;
import property ;
import regex ;
import sequence ;
import set ;
import property-set ;
import order ;
import "class" : new ;
import utility ;
.flag-no = 1 ;
.ignore-requirements = ;
# This is used only for testing, to make sure we do not get random extra
# elements in paths.
if --ignore-toolset-requirements in [ modules.peek : ARGV ]
{
.ignore-requirements = 1 ;
}
# Initializes an additional toolset-like module. First load the 'toolset-module'
# and then calls its 'init' rule with trailing arguments.
#
rule using ( toolset-module : * )
{
import $(toolset-module) ;
$(toolset-module).init $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9)
;
}
# Expands subfeatures in each property sets, e.g. '<toolset>gcc-3.2' will be
# converted to '<toolset>gcc/<toolset-version>3.2'.
#
local rule normalize-condition ( property-sets * )
{
local result ;
for local p in $(property-sets)
{
local split = [ feature.split $(p) ] ;
local expanded = [ feature.expand-subfeatures [ feature.split $(p) ] ] ;
result += $(expanded:J=/) ;
}
return $(result) ;
}
# Specifies if the 'flags' rule should check that the invoking module is the
# same as the module we are setting the flag for. 'v' can be either 'checked' or
# 'unchecked'. Subsequent call to 'pop-checking-for-flags-module' will restore
# the setting that was in effect before calling this rule.
#
rule push-checking-for-flags-module ( v )
{
.flags-module-checking = $(v) $(.flags-module-checking) ;
}
rule pop-checking-for-flags-module ( )
{
.flags-module-checking = $(.flags-module-checking[2-]) ;
}
# Specifies features that are referenced by the action rule.
# This is necessary in order to detect that these features
# are relevant.
#
rule uses-features ( rule-or-module : features * : unchecked ? )
{
local caller = [ CALLER_MODULE ] ;
if ! [ MATCH ".*([.]).*" : $(rule-or-module) ]
&& [ MATCH "(Jamfile<.*)" : $(caller) ]
{
# Unqualified rule name, used inside Jamfile. Most likely used with
# 'make' or 'notfile' rules. This prevents setting flags on the entire
# Jamfile module (this will be considered as rule), but who cares?
# Probably, 'flags' rule should be split into 'flags' and
# 'flags-on-module'.
rule-or-module = $(caller).$(rule-or-module) ;
}
else
{
local module_ = [ MATCH "([^.]*).*" : $(rule-or-module) ] ;
if $(unchecked) != unchecked
&& $(.flags-module-checking[1]) != unchecked
&& $(module_) != $(caller)
{
errors.error "Module $(caller) attempted to set flags for module $(module_)" ;
}
}
.uses-features.$(rule-or-module) += $(features) ;
}
# Specifies the flags (variables) that must be set on targets under certain
# conditions, described by arguments.
#
rule flags (
rule-or-module # If contains a dot, should be a rule name. The flags will
# be applied when that rule is used to set up build
# actions.
#
# If does not contain dot, should be a module name. The
# flag will be applied for all rules in that module. If
# module for rule is different from the calling module, an
# error is issued.
variable-name # Variable that should be set on target.
condition * : # A condition when this flag should be applied. Should be a
# set of property sets. If one of those property sets is
# contained in the build properties, the flag will be used.
# Implied values are not allowed: "<toolset>gcc" should be
# used, not just "gcc". Subfeatures, like in
# "<toolset>gcc-3.2" are allowed. If left empty, the flag
# will be used unconditionally.
#
# Property sets may use value-less properties ('<a>' vs.
# '<a>value') to match absent properties. This allows to
# separately match:
#
# <architecture>/<address-model>64
# <architecture>ia64/<address-model>
#
# Where both features are optional. Without this syntax
# we would be forced to define "default" values.
values * : # The value to add to variable. If <feature> is specified,
# then the value of 'feature' will be added.
unchecked ? # If value 'unchecked' is passed, will not test that flags
# are set for the calling module.
: hack-hack ? # For
# flags rule OPTIONS <cxx-abi> : -model ansi
# Treat <cxx-abi> as condition
# FIXME: ugly hack.
)
{
local caller = [ CALLER_MODULE ] ;
if ! [ MATCH ".*([.]).*" : $(rule-or-module) ]
&& [ MATCH "(Jamfile<.*)" : $(caller) ]
{
# Unqualified rule name, used inside Jamfile. Most likely used with
# 'make' or 'notfile' rules. This prevents setting flags on the entire
# Jamfile module (this will be considered as rule), but who cares?
# Probably, 'flags' rule should be split into 'flags' and
# 'flags-on-module'.
rule-or-module = $(caller).$(rule-or-module) ;
}
else
{
local module_ = [ MATCH "([^.]*).*" : $(rule-or-module) ] ;
if $(unchecked) != unchecked
&& $(.flags-module-checking[1]) != unchecked
&& $(module_) != $(caller)
{
errors.error "Module $(caller) attempted to set flags for module $(module_)" ;
}
}
if $(condition) && ! $(condition:G=) && ! $(hack-hack)
{
# We have condition in the form '<feature>', that is, without value.
# That is an older syntax:
# flags gcc.link RPATH <dll-path> ;
# for compatibility, convert it to
# flags gcc.link RPATH : <dll-path> ;
values = $(condition) ;
condition = ;
}
if $(condition)
{
property.validate-property-sets $(condition) ;
condition = [ normalize-condition $(condition) ] ;
}
add-flag $(rule-or-module) : $(variable-name) : $(condition) : $(values) ;
}
# Adds a new flag setting with the specified values. Does no checking.
#
local rule add-flag ( rule-or-module : variable-name : condition * : values * )
{
.$(rule-or-module).flags += $(.flag-no) ;
# Store all flags for a module.
local module_ = [ MATCH "([^.]*).*" : $(rule-or-module) ] ;
.module-flags.$(module_) += $(.flag-no) ;
# Store flag-no -> rule-or-module mapping.
.rule-or-module.$(.flag-no) = $(rule-or-module) ;
.$(rule-or-module).variable.$(.flag-no) += $(variable-name) ;
.$(rule-or-module).values.$(.flag-no) += $(values) ;
.$(rule-or-module).condition.$(.flag-no) += $(condition) ;
.flag-no = [ numbers.increment $(.flag-no) ] ;
}
# Returns the first element of 'property-sets' which is a subset of
# 'properties' or an empty list if no such element exists.
#
rule find-property-subset ( property-sets * : properties * )
{
# Cut property values off.
local prop-keys = $(properties:G) ;
local result ;
for local s in $(property-sets)
{
if ! $(result)
{
# Handle value-less properties like '<architecture>' (compare with
# '<architecture>x86').
local set = [ feature.split $(s) ] ;
# Find the set of features that
# - have no property specified in required property set
# - are omitted in the build property set.
local default-props ;
for local i in $(set)
{
# If $(i) is a value-less property it should match default value
# of an optional property. See the first line in the example
# below:
#
# property set properties result
# <a> <b>foo <b>foo match
# <a> <b>foo <a>foo <b>foo no match
# <a>foo <b>foo <b>foo no match
# <a>foo <b>foo <a>foo <b>foo match
if ! ( $(i:G=) || ( $(i:G) in $(prop-keys) ) )
{
default-props += $(i) ;
}
}
if $(set) in $(properties) $(default-props)
{
result = $(s) ;
}
}
}
return $(result) ;
}
# Returns a value to be added to some flag for some target based on the flag's
# value definition and the given target's property set.
#
rule handle-flag-value ( value * : properties * )
{
local result ;
if $(value:G)
{
local matches = [ property.select $(value) : $(properties) ] ;
local order ;
for local p in $(matches)
{
local att = [ feature.attributes $(p:G) ] ;
if dependency in $(att)
{
# The value of a dependency feature is a target and needs to be
# actualized.
result += [ $(p:G=).actualize ] ;
}
else if path in $(att) || free in $(att)
{
local values ;
# Treat features with && in the value specially -- each
# &&-separated element is considered a separate value. This is
# needed to handle searched libraries or include paths, which
# may need to be in a specific order.
if ! [ MATCH (&&) : $(p:G=) ]
{
values = $(p:G=) ;
}
else
{
values = [ regex.split $(p:G=) "&&" ] ;
}
if path in $(att)
{
values = [ sequence.transform path.native : $(values) ] ;
}
result += $(values) ;
if $(values[2])
{
if ! $(order)
{
order = [ new order ] ;
}
local prev ;
for local v in $(values)
{
if $(prev)
{
$(order).add-pair $(prev) $(v) ;
}
prev = $(v) ;
}
}
}
else
{
result += $(p:G=) ;
}
}
if $(order)
{
result = [ $(order).order [ sequence.unique $(result) : stable ] ] ;
DELETE_MODULE $(order) ;
}
}
else
{
result += $(value) ;
}
return $(result) ;
}
# Given a rule name and a property set, returns a list of interleaved variables
# names and values which must be set on targets for that rule/property-set
# combination.
#
rule set-target-variables-aux ( rule-or-module : property-set )
{
local result ;
properties = [ $(property-set).raw ] ;
for local f in $(.$(rule-or-module).flags)
{
local variable = $(.$(rule-or-module).variable.$(f)) ;
local condition = $(.$(rule-or-module).condition.$(f)) ;
local values = $(.$(rule-or-module).values.$(f)) ;
if ! $(condition) ||
[ find-property-subset $(condition) : $(properties) ]
{
local processed ;
for local v in $(values)
{
# The value might be <feature-name> so needs special treatment.
processed += [ handle-flag-value $(v) : $(properties) ] ;
}
for local r in $(processed)
{
result += $(variable) $(r) ;
}
}
}
# Strip away last dot separated part and recurse.
local next = [ MATCH "^(.+)\\.([^\\.])*" : $(rule-or-module) ] ;
if $(next)
{
result += [ set-target-variables-aux $(next[1]) : $(property-set) ] ;
}
return $(result) ;
}
rule relevant-features ( rule-or-module )
{
local result ;
if ! $(.relevant-features.$(rule-or-module))
{
for local f in $(.$(rule-or-module).flags)
{
local condition = $(.$(rule-or-module).condition.$(f)) ;
local values = $(.$(rule-or-module).values.$(f)) ;
for local c in $(condition)
{
for local p in [ feature.split $(c) ]
{
if $(p:G)
{
result += $(p:G) ;
}
else
{
local temp = [ feature.expand-subfeatures $(p) ] ;
result += $(temp:G) ;
}
}
}
for local v in $(values)
{
if $(v:G)
{
result += $(v:G) ;
}
}
}
# Strip away last dot separated part and recurse.
local next = [ MATCH "^(.+)\\.([^\\.])*" : $(rule-or-module) ] ;
if $(next)
{
result += [ relevant-features $(next[1]) ] ;
}
result = [ sequence.unique $(result) ] ;
if $(result[1]) = ""
{
result = $(result) ;
}
.relevant-features.$(rule-or-module) = $(result) ;
return $(result) ;
}
else
{
return $(.relevant-features.$(rule-or-module)) ;
}
}
# Returns a list of all the features which were
# passed to uses-features.
local rule used-features ( rule-or-module )
{
if ! $(.used-features.$(rule-or-module))
{
local result = $(.uses-features.$(rule-or-module)) ;
# Strip away last dot separated part and recurse.
local next = [ MATCH "^(.+)\\.([^\\.])*" : $(rule-or-module) ] ;
if $(next)
{
result += [ used-features $(next[1]) ] ;
}
result = [ sequence.unique $(result) ] ;
if $(result[1]) = ""
{
result = $(result) ;
}
.used-features.$(rule-or-module) = $(result) ;
return $(result) ;
}
else
{
return $(.used-features.$(rule-or-module)) ;
}
}
rule filter-property-set ( rule-or-module : property-set )
{
local key = .filtered.property-set.$(rule-or-module).$(property-set) ;
if ! $($(key))
{
local relevant = [ relevant-features $(rule-or-module) ] ;
local result ;
for local p in [ $(property-set).raw ]
{
if $(p:G) in $(relevant)
{
result += $(p) ;
}
}
$(key) = [ property-set.create $(result) ] ;
}
return $($(key)) ;
}
rule set-target-variables ( rule-or-module targets + : property-set )
{
property-set = [ filter-property-set $(rule-or-module) : $(property-set) ] ;
local key = .stv.$(rule-or-module).$(property-set) ;
local settings = $($(key)) ;
if ! $(settings)
{
settings = [ set-target-variables-aux $(rule-or-module) :
$(property-set) ] ;
if ! $(settings)
{
settings = none ;
}
$(key) = $(settings) ;
}
if $(settings) != none
{
local var-name = ;
for local name-or-value in $(settings)
{
if $(var-name)
{
$(var-name) on $(targets) += $(name-or-value) ;
var-name = ;
}
else
{
var-name = $(name-or-value) ;
}
}
}
}
# Returns a property-set indicating which features are relevant
# for the given rule.
#
rule relevant ( rule-name )
{
if ! $(.relevant-features-ps.$(rule-name))
{
local features = [ sequence.transform utility.ungrist :
[ relevant-features $(rule-name) ]
[ used-features $(rule-name) ] ] ;
.relevant-features-ps.$(rule-name) =
[ property-set.create <relevant>$(features) ] ;
}
return $(.relevant-features-ps.$(rule-name)) ;
}
# Make toolset 'toolset', defined in a module of the same name, inherit from
# 'base'.
# 1. The 'init' rule from 'base' is imported into 'toolset' with full name.
# Another 'init' is called, which forwards to the base one.
# 2. All generators from 'base' are cloned. The ids are adjusted and <toolset>
# property in requires is adjusted too.
# 3. All flags are inherited.
# 4. All rules are imported.
#
rule inherit ( toolset : base )
{
import $(base) ;
inherit-generators $(toolset) : $(base) ;
inherit-flags $(toolset) : $(base) ;
inherit-rules $(toolset) : $(base) ;
}
rule inherit-generators ( toolset properties * : base : generators-to-ignore * )
{
properties ?= <toolset>$(toolset) ;
local base-generators = [ generators.generators-for-toolset $(base) ] ;
for local g in $(base-generators)
{
local id = [ $(g).id ] ;
if ! $(id) in $(generators-to-ignore)
{
# Some generator names have multiple periods in their name, so
# $(id:B=$(toolset)) does not generate the right new-id name. E.g.
# if id = gcc.compile.c++ then $(id:B=darwin) = darwin.c++, which is
# not what we want. Manually parse the base and suffix. If there is
# a better way to do this, I would love to see it. See also the
# register() rule in the generators module.
local base = $(id) ;
local suffix = "" ;
while $(base:S)
{
suffix = $(base:S)$(suffix) ;
base = $(base:B) ;
}
local new-id = $(toolset)$(suffix) ;
generators.register [ $(g).clone $(new-id) : $(properties) ] ;
}
}
}
# Brings all flag definitions from the 'base' toolset into the 'toolset'
# toolset. Flag definitions whose conditions make use of properties in
# 'prohibited-properties' are ignored. Do not confuse property and feature, for
# example <debug-symbols>on and <debug-symbols>off, so blocking one of them does
# not block the other one.
#
# The flag conditions are not altered at all, so if a condition includes a name,
# or version of a base toolset, it will not ever match the inheriting toolset.
# When such flag settings must be inherited, define a rule in base toolset
# module and call it as needed.
#
rule inherit-flags ( toolset : base : prohibited-properties * : prohibited-vars * )
{
for local f in $(.module-flags.$(base))
{
local rule-or-module = $(.rule-or-module.$(f)) ;
if ( [ set.difference
$(.$(rule-or-module).condition.$(f)) :
$(prohibited-properties) ]
|| ! $(.$(rule-or-module).condition.$(f))
) && ( ! $(.$(rule-or-module).variable.$(f)) in $(prohibited-vars) )
{
local rule_ = [ MATCH "[^.]*\.(.*)" : $(rule-or-module) ] ;
local new-rule-or-module ;
if $(rule_)
{
new-rule-or-module = $(toolset).$(rule_) ;
}
else
{
new-rule-or-module = $(toolset) ;
}
add-flag
$(new-rule-or-module)
: $(.$(rule-or-module).variable.$(f))
: $(.$(rule-or-module).condition.$(f))
: $(.$(rule-or-module).values.$(f)) ;
}
}
}
rule inherit-rules ( toolset : base : localize ? )
{
# It appears that "action" creates a local rule.
local base-generators = [ generators.generators-for-toolset $(base) ] ;
local rules ;
for local g in $(base-generators)
{
rules += [ MATCH "[^.]*\.(.*)" : [ $(g).rule-name ] ] ;
}
rules = [ sequence.unique $(rules) ] ;
IMPORT $(base) : $(rules) : $(toolset) : $(rules) : $(localize) ;
IMPORT $(toolset) : $(rules) : : $(toolset).$(rules) ;
}
.requirements = [ property-set.empty ] ;
# Return the list of global 'toolset requirements'. Those requirements will be
# automatically added to the requirements of any main target.
#
rule requirements ( )
{
return $(.requirements) ;
}
# Adds elements to the list of global 'toolset requirements'. The requirements
# will be automatically added to the requirements for all main targets, as if
# they were specified literally. For best results, all requirements added should
# be conditional or indirect conditional.
#
rule add-requirements ( requirements * )
{
if ! $(.ignore-requirements)
{
requirements = [ property.translate-indirect $(requirements) : [ CALLER_MODULE ] ] ;
requirements = [ property.expand-subfeatures-in-conditions $(requirements) ] ;
requirements = [ property.make $(requirements) ] ;
.requirements = [ $(.requirements).add-raw $(requirements) ] ;
}
}
# Returns the global toolset defaults.
#
.defaults = [ property-set.empty ] ;
rule defaults ( )
{
return $(.defaults) ;
}
# Add elements to the list of global toolset defaults. These properties
# should be conditional and will override the default value of the feature.
# Do not use this for non-conditionals. Use feature.set-default instead.
#
rule add-defaults ( properties * )
{
if ! $(.ignore-requirements)
{
properties = [ property.translate-indirect $(properties) : [ CALLER_MODULE ] ] ;
properties = [ property.expand-subfeatures-in-conditions $(properties) ] ;
properties = [ property.make $(properties) ] ;
.defaults = [ $(.defaults).add-raw $(properties) ] ;
}
}
rule __test__ ( )
{
import assert ;
local p = <b>0 <c>1 <d>2 <e>3 <f>4 ;
assert.result <c>1/<d>2/<e>3 : find-property-subset <c>1/<d>2/<e>3 <a>0/<b>0/<c>1 <d>2/<e>5 <a>9 : $(p) ;
assert.result : find-property-subset <a>0/<b>0/<c>9/<d>9/<e>5 <a>9 : $(p) ;
local p-set = <a>/<b> <a>0/<b> <a>/<b>1 <a>0/<b>1 ;
assert.result <a>/<b> : find-property-subset $(p-set) : ;
assert.result <a>0/<b> : find-property-subset $(p-set) : <a>0 <c>2 ;
assert.result <a>/<b>1 : find-property-subset $(p-set) : <b>1 <c>2 ;
assert.result <a>0/<b>1 : find-property-subset $(p-set) : <a>0 <b>1 ;
}

View File

@@ -0,0 +1,417 @@
# Status: being ported by Vladimir Prus
# Base revision: 40958
#
# Copyright 2003 Dave Abrahams
# Copyright 2005 Rene Rivera
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
""" Support for toolset definition.
"""
import sys
import feature, property, generators, property_set
import b2.util.set
import bjam
from b2.util import cached, qualify_jam_action, is_iterable_typed, is_iterable
from b2.util.utility import *
from b2.util import bjam_signature, sequence
from b2.manager import get_manager
__re_split_last_segment = re.compile (r'^(.+)\.([^\.])*')
__re_two_ampersands = re.compile ('(&&)')
__re_first_segment = re.compile ('([^.]*).*')
__re_first_group = re.compile (r'[^.]*\.(.*)')
_ignore_toolset_requirements = '--ignore-toolset-requirements' not in sys.argv
# Flag is a mechanism to set a value
# A single toolset flag. Specifies that when certain
# properties are in build property set, certain values
# should be appended to some variable.
#
# A flag applies to a specific action in specific module.
# The list of all flags for a module is stored, and each
# flag further contains the name of the rule it applies
# for,
class Flag:
def __init__(self, variable_name, values, condition, rule = None):
assert isinstance(variable_name, basestring)
assert is_iterable(values) and all(
isinstance(v, (basestring, type(None))) for v in values)
assert is_iterable_typed(condition, property_set.PropertySet)
assert isinstance(rule, (basestring, type(None)))
self.variable_name = variable_name
self.values = values
self.condition = condition
self.rule = rule
def __str__(self):
return("Flag(" + str(self.variable_name) + ", " + str(self.values) +\
", " + str(self.condition) + ", " + str(self.rule) + ")")
def reset ():
""" Clear the module state. This is mainly for testing purposes.
"""
global __module_flags, __flags, __stv
# Mapping from module name to a list of all flags that apply
# to either that module directly, or to any rule in that module.
# Each element of the list is Flag instance.
# So, for module named xxx this might contain flags for 'xxx',
# for 'xxx.compile', for 'xxx.compile.c++', etc.
__module_flags = {}
# Mapping from specific rule or module name to a list of Flag instances
# that apply to that name.
# Say, it might contain flags for 'xxx.compile.c++'. If there are
# entries for module name 'xxx', they are flags for 'xxx' itself,
# not including any rules in that module.
__flags = {}
# A cache for variable settings. The key is generated from the rule name and the properties.
__stv = {}
reset ()
# FIXME: --ignore-toolset-requirements
def using(toolset_module, *args):
if isinstance(toolset_module, (list, tuple)):
toolset_module = toolset_module[0]
loaded_toolset_module= get_manager().projects().load_module(toolset_module, [os.getcwd()]);
loaded_toolset_module.init(*args)
# FIXME push-checking-for-flags-module ....
# FIXME: investigate existing uses of 'hack-hack' parameter
# in jam code.
@bjam_signature((["rule_or_module", "variable_name", "condition", "*"],
["values", "*"]))
def flags(rule_or_module, variable_name, condition, values = []):
""" Specifies the flags (variables) that must be set on targets under certain
conditions, described by arguments.
rule_or_module: If contains dot, should be a rule name.
The flags will be applied when that rule is
used to set up build actions.
If does not contain dot, should be a module name.
The flags will be applied for all rules in that
module.
If module for rule is different from the calling
module, an error is issued.
variable_name: Variable that should be set on target
condition A condition when this flag should be applied.
Should be set of property sets. If one of
those property sets is contained in build
properties, the flag will be used.
Implied values are not allowed:
"<toolset>gcc" should be used, not just
"gcc". Subfeatures, like in "<toolset>gcc-3.2"
are allowed. If left empty, the flag will
always used.
Property sets may use value-less properties
('<a>' vs. '<a>value') to match absent
properties. This allows to separately match
<architecture>/<address-model>64
<architecture>ia64/<address-model>
Where both features are optional. Without this
syntax we'd be forced to define "default" value.
values: The value to add to variable. If <feature>
is specified, then the value of 'feature'
will be added.
"""
assert isinstance(rule_or_module, basestring)
assert isinstance(variable_name, basestring)
assert is_iterable_typed(condition, basestring)
assert is_iterable(values) and all(isinstance(v, (basestring, type(None))) for v in values)
caller = bjam.caller()
if not '.' in rule_or_module and caller and caller[:-1].startswith("Jamfile"):
# Unqualified rule name, used inside Jamfile. Most likely used with
# 'make' or 'notfile' rules. This prevents setting flags on the entire
# Jamfile module (this will be considered as rule), but who cares?
# Probably, 'flags' rule should be split into 'flags' and
# 'flags-on-module'.
rule_or_module = qualify_jam_action(rule_or_module, caller)
else:
# FIXME: revive checking that we don't set flags for a different
# module unintentionally
pass
if condition and not replace_grist (condition, ''):
# We have condition in the form '<feature>', that is, without
# value. That's a previous syntax:
#
# flags gcc.link RPATH <dll-path> ;
# for compatibility, convert it to
# flags gcc.link RPATH : <dll-path> ;
values = [ condition ]
condition = None
if condition:
transformed = []
for c in condition:
# FIXME: 'split' might be a too raw tool here.
pl = [property.create_from_string(s,False,True) for s in c.split('/')]
pl = feature.expand_subfeatures(pl);
transformed.append(property_set.create(pl))
condition = transformed
property.validate_property_sets(condition)
__add_flag (rule_or_module, variable_name, condition, values)
def set_target_variables (manager, rule_or_module, targets, ps):
"""
"""
assert isinstance(rule_or_module, basestring)
assert is_iterable_typed(targets, basestring)
assert isinstance(ps, property_set.PropertySet)
settings = __set_target_variables_aux(manager, rule_or_module, ps)
if settings:
for s in settings:
for target in targets:
manager.engine ().set_target_variable (target, s [0], s[1], True)
def find_satisfied_condition(conditions, ps):
"""Returns the first element of 'property-sets' which is a subset of
'properties', or an empty list if no such element exists."""
assert is_iterable_typed(conditions, property_set.PropertySet)
assert isinstance(ps, property_set.PropertySet)
for condition in conditions:
found_all = True
for i in condition.all():
if i.value:
found = i.value in ps.get(i.feature)
else:
# Handle value-less properties like '<architecture>' (compare with
# '<architecture>x86').
# If $(i) is a value-less property it should match default
# value of an optional property. See the first line in the
# example below:
#
# property set properties result
# <a> <b>foo <b>foo match
# <a> <b>foo <a>foo <b>foo no match
# <a>foo <b>foo <b>foo no match
# <a>foo <b>foo <a>foo <b>foo match
found = not ps.get(i.feature)
found_all = found_all and found
if found_all:
return condition
return None
def register (toolset):
""" Registers a new toolset.
"""
assert isinstance(toolset, basestring)
feature.extend('toolset', [toolset])
def inherit_generators (toolset, properties, base, generators_to_ignore = []):
assert isinstance(toolset, basestring)
assert is_iterable_typed(properties, basestring)
assert isinstance(base, basestring)
assert is_iterable_typed(generators_to_ignore, basestring)
if not properties:
properties = [replace_grist (toolset, '<toolset>')]
base_generators = generators.generators_for_toolset(base)
for g in base_generators:
id = g.id()
if not id in generators_to_ignore:
# Some generator names have multiple periods in their name, so
# $(id:B=$(toolset)) doesn't generate the right new_id name.
# e.g. if id = gcc.compile.c++, $(id:B=darwin) = darwin.c++,
# which is not what we want. Manually parse the base and suffix
# (if there's a better way to do this, I'd love to see it.)
# See also register in module generators.
(base, suffix) = split_action_id(id)
new_id = toolset + '.' + suffix
generators.register(g.clone(new_id, properties))
def inherit_flags(toolset, base, prohibited_properties = []):
"""Brings all flag definitions from the 'base' toolset into the 'toolset'
toolset. Flag definitions whose conditions make use of properties in
'prohibited-properties' are ignored. Don't confuse property and feature, for
example <debug-symbols>on and <debug-symbols>off, so blocking one of them does
not block the other one.
The flag conditions are not altered at all, so if a condition includes a name,
or version of a base toolset, it won't ever match the inheriting toolset. When
such flag settings must be inherited, define a rule in base toolset module and
call it as needed."""
assert isinstance(toolset, basestring)
assert isinstance(base, basestring)
assert is_iterable_typed(prohibited_properties, basestring)
for f in __module_flags.get(base, []):
if not f.condition or b2.util.set.difference(f.condition, prohibited_properties):
match = __re_first_group.match(f.rule)
rule_ = None
if match:
rule_ = match.group(1)
new_rule_or_module = ''
if rule_:
new_rule_or_module = toolset + '.' + rule_
else:
new_rule_or_module = toolset
__add_flag (new_rule_or_module, f.variable_name, f.condition, f.values)
def inherit_rules(toolset, base):
engine = get_manager().engine()
new_actions = {}
for action_name, action in engine.actions.iteritems():
module, id = split_action_id(action_name)
if module == base:
new_action_name = toolset + '.' + id
# make sure not to override any existing actions
# that may have been declared already
if new_action_name not in engine.actions:
new_actions[new_action_name] = action
engine.actions.update(new_actions)
######################################################################################
# Private functions
@cached
def __set_target_variables_aux (manager, rule_or_module, ps):
""" Given a rule name and a property set, returns a list of tuples of
variables names and values, which must be set on targets for that
rule/properties combination.
"""
assert isinstance(rule_or_module, basestring)
assert isinstance(ps, property_set.PropertySet)
result = []
for f in __flags.get(rule_or_module, []):
if not f.condition or find_satisfied_condition (f.condition, ps):
processed = []
for v in f.values:
# The value might be <feature-name> so needs special
# treatment.
processed += __handle_flag_value (manager, v, ps)
for r in processed:
result.append ((f.variable_name, r))
# strip away last dot separated part and recurse.
next = __re_split_last_segment.match(rule_or_module)
if next:
result.extend(__set_target_variables_aux(
manager, next.group(1), ps))
return result
def __handle_flag_value (manager, value, ps):
assert isinstance(value, basestring)
assert isinstance(ps, property_set.PropertySet)
result = []
if get_grist (value):
f = feature.get(value)
values = ps.get(f)
for value in values:
if f.dependency:
# the value of a dependency feature is a target
# and must be actualized
result.append(value.actualize())
elif f.path or f.free:
# Treat features with && in the value
# specially -- each &&-separated element is considered
# separate value. This is needed to handle searched
# libraries, which must be in specific order.
if not __re_two_ampersands.search(value):
result.append(value)
else:
result.extend(value.split ('&&'))
else:
result.append (value)
else:
result.append (value)
return sequence.unique(result, stable=True)
def __add_flag (rule_or_module, variable_name, condition, values):
""" Adds a new flag setting with the specified values.
Does no checking.
"""
assert isinstance(rule_or_module, basestring)
assert isinstance(variable_name, basestring)
assert is_iterable_typed(condition, property_set.PropertySet)
assert is_iterable(values) and all(
isinstance(v, (basestring, type(None))) for v in values)
f = Flag(variable_name, values, condition, rule_or_module)
# Grab the name of the module
m = __re_first_segment.match (rule_or_module)
assert m
module = m.group(1)
__module_flags.setdefault(module, []).append(f)
__flags.setdefault(rule_or_module, []).append(f)
__requirements = []
def requirements():
"""Return the list of global 'toolset requirements'.
Those requirements will be automatically added to the requirements of any main target."""
return __requirements
def add_requirements(requirements):
"""Adds elements to the list of global 'toolset requirements'. The requirements
will be automatically added to the requirements for all main targets, as if
they were specified literally. For best results, all requirements added should
be conditional or indirect conditional."""
assert is_iterable_typed(requirements, basestring)
if _ignore_toolset_requirements:
__requirements.extend(requirements)
# Make toolset 'toolset', defined in a module of the same name,
# inherit from 'base'
# 1. The 'init' rule from 'base' is imported into 'toolset' with full
# name. Another 'init' is called, which forwards to the base one.
# 2. All generators from 'base' are cloned. The ids are adjusted and
# <toolset> property in requires is adjusted too
# 3. All flags are inherited
# 4. All rules are imported.
def inherit(toolset, base):
assert isinstance(toolset, basestring)
assert isinstance(base, basestring)
get_manager().projects().load_module(base, ['.']);
inherit_generators(toolset, [], base)
inherit_flags(toolset, base)
inherit_rules(toolset, base)

View File

@@ -0,0 +1,410 @@
# Copyright 2002, 2003 Dave Abrahams
# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
# Deals with target type declaration and defines target class which supports
# typed targets.
import "class" : new ;
import feature ;
import generators : * ;
import os ;
import param ;
import project ;
import property ;
import scanner ;
# The following import would create a circular dependency:
# project -> project-root -> builtin -> type -> targets -> project
# import targets ;
# The feature is optional so it would never get added implicitly. It is used
# only for internal purposes and in all cases we want to use it explicitly.
feature.feature target-type : : composite optional ;
feature.feature main-target-type : : optional incidental ;
feature.feature base-target-type : : composite optional free ;
# Registers a target type, possible derived from a 'base-type'. Providing a list
# of 'suffixes' here is a shortcut for separately calling the register-suffixes
# rule with the given suffixes and the set-generated-target-suffix rule with the
# first given suffix.
#
rule register ( type : suffixes * : base-type ? )
{
# Type names cannot contain hyphens, because when used as feature-values
# they would be interpreted as composite features which need to be
# decomposed.
switch $(type)
{
case *-* :
import errors ;
errors.error "type name \"$(type)\" contains a hyphen" ;
}
if $(type) in $(.types)
{
import errors ;
errors.error "Type $(type) is already registered." ;
}
if $(base-type) && ! $(base-type) in $(.types)
{
import errors ;
errors.error "Type $(base-type) is not registered." ;
}
{
.types += $(type) ;
.base.$(type) = $(base-type) ;
.derived.$(base-type) += $(type) ;
.bases.$(type) = $(type) $(.bases.$(base-type)) ;
# Store suffixes for generated targets.
.suffixes.$(type) = [ new property-map ] ;
# Store prefixes for generated targets (e.g. "lib" for library).
.prefixes.$(type) = [ new property-map ] ;
if $(suffixes)-is-defined
{
# Specify mapping from suffixes to type.
register-suffixes $(suffixes) : $(type) ;
# By default generated targets of 'type' will use the first of
#'suffixes'. This may be overridden.
set-generated-target-suffix $(type) : : $(suffixes[1]) ;
}
feature.extend target-type : $(type) ;
feature.extend main-target-type : $(type) ;
feature.extend base-target-type : $(type) ;
feature.compose <target-type>$(type) : $(base-type:G=<base-target-type>) ;
feature.compose <base-target-type>$(type) : <base-target-type>$(base-type) ;
# We used to declare the main target rule only when a 'main' parameter
# has been specified. However, it is hard to decide that a type will
# *never* need a main target rule and so from time to time we needed to
# make yet another type 'main'. So now a main target rule is defined for
# each type.
main-rule-name = [ type-to-rule-name $(type) ] ;
.main-target-type.$(main-rule-name) = $(type) ;
IMPORT $(__name__) : main-target-rule : : $(main-rule-name) ;
# Adding a new derived type affects generator selection so we need to
# make the generator selection module update any of its cached
# information related to a new derived type being defined.
generators.update-cached-information-with-a-new-type $(type) ;
}
}
# Given a type, returns the name of the main target rule which creates targets
# of that type.
#
rule type-to-rule-name ( type )
{
# Lowercase everything. Convert underscores to dashes.
import regex ;
local n = [ regex.split $(type:L) "_" ] ;
return $(n:J=-) ;
}
# Given a main target rule name, returns the type for which it creates targets.
#
rule type-from-rule-name ( rule-name )
{
return $(.main-target-type.$(rule-name)) ;
}
# Specifies that files with suffix from 'suffixes' be recognized as targets of
# type 'type'. Issues an error if a different type is already specified for any
# of the suffixes.
#
rule register-suffixes ( suffixes + : type )
{
for local s in $(suffixes)
{
if ! $(.type.$(s))
{
.type.$(s) = $(type) ;
}
else if $(.type.$(s)) != $(type)
{
import errors ;
errors.error Attempting to specify multiple types for suffix
\"$(s)\" : "Old type $(.type.$(s)), New type $(type)" ;
}
}
}
# Returns true iff type has been registered.
#
rule registered ( type )
{
if $(type) in $(.types)
{
return true ;
}
}
# Issues an error if 'type' is unknown.
#
rule validate ( type )
{
if ! [ registered $(type) ]
{
import errors ;
errors.error "Unknown target type $(type)" ;
}
}
# Sets a scanner class that will be used for this 'type'.
#
rule set-scanner ( type : scanner )
{
validate $(type) ;
.scanner.$(type) = $(scanner) ;
}
# Returns a scanner instance appropriate to 'type' and 'properties'.
#
rule get-scanner ( type : property-set )
{
if $(.scanner.$(type))
{
return [ scanner.get $(.scanner.$(type)) : $(property-set) ] ;
}
}
# Returns a base type for the given type or nothing in case the given type is
# not derived.
#
rule base ( type )
{
return $(.base.$(type)) ;
}
# Returns the given type and all of its base types in order of their distance
# from type.
#
rule all-bases ( type )
{
return $(.bases.$(type)) ;
}
# Returns the given type and all of its derived types in order of their distance
# from type.
#
rule all-derived ( type )
{
local result = $(type) ;
for local d in $(.derived.$(type))
{
result += [ all-derived $(d) ] ;
}
return $(result) ;
}
# Returns true if 'type' is equal to 'base' or has 'base' as its direct or
# indirect base.
#
rule is-derived ( type base )
{
if $(base) in $(.bases.$(type))
{
return true ;
}
}
# Returns true if 'type' is either derived from or is equal to 'base'.
#
# TODO: It might be that is-derived and is-subtype were meant to be different
# rules - one returning true for type = base and one not, but as currently
# implemented they are actually the same. Clean this up.
#
rule is-subtype ( type base )
{
return [ is-derived $(type) $(base) ] ;
}
# Sets a file suffix to be used when generating a target of 'type' with the
# specified properties. Can be called with no properties if no suffix has
# already been specified for the 'type'. The 'suffix' parameter can be an empty
# string ("") to indicate that no suffix should be used.
#
# Note that this does not cause files with 'suffix' to be automatically
# recognized as being of 'type'. Two different types can use the same suffix for
# their generated files but only one type can be auto-detected for a file with
# that suffix. User should explicitly specify which one using the
# register-suffixes rule.
#
rule set-generated-target-suffix ( type : properties * : suffix )
{
set-generated-target-ps suffix : $(type) : $(properties) : $(suffix) ;
}
# Change the suffix previously registered for this type/properties combination.
# If suffix is not yet specified, sets it.
#
rule change-generated-target-suffix ( type : properties * : suffix )
{
change-generated-target-ps suffix : $(type) : $(properties) : $(suffix) ;
}
# Returns the suffix used when generating a file of 'type' with the given
# properties.
#
rule generated-target-suffix ( type : property-set )
{
return [ generated-target-ps suffix : $(type) : $(property-set) ] ;
}
# Sets a target prefix that should be used when generating targets of 'type'
# with the specified properties. Can be called with empty properties if no
# prefix for 'type' has been specified yet.
#
# The 'prefix' parameter can be empty string ("") to indicate that no prefix
# should be used.
#
# Usage example: library names use the "lib" prefix on unix.
#
rule set-generated-target-prefix ( type : properties * : prefix )
{
set-generated-target-ps prefix : $(type) : $(properties) : $(prefix) ;
}
# Change the prefix previously registered for this type/properties combination.
# If prefix is not yet specified, sets it.
#
rule change-generated-target-prefix ( type : properties * : prefix )
{
change-generated-target-ps prefix : $(type) : $(properties) : $(prefix) ;
}
rule generated-target-prefix ( type : property-set )
{
return [ generated-target-ps prefix : $(type) : $(property-set) ] ;
}
# Common rules for prefix/suffix provisioning follow.
local rule set-generated-target-ps ( ps : type : properties * : psval )
{
$(.$(ps)es.$(type)).insert $(properties) : $(psval) ;
}
local rule change-generated-target-ps ( ps : type : properties * : psval )
{
local prev = [ $(.$(ps)es.$(type)).find-replace $(properties) : $(psval) ] ;
if ! $(prev)
{
set-generated-target-ps $(ps) : $(type) : $(properties) : $(psval) ;
}
}
# Returns either prefix or suffix (as indicated by 'ps') that should be used
# when generating a target of 'type' with the specified properties. Parameter
# 'ps' can be either "prefix" or "suffix". If no prefix/suffix is specified for
# 'type', returns prefix/suffix for base type, if any.
#
local rule generated-target-ps ( ps : type : property-set )
{
local result ;
local found ;
while $(type) && ! $(found)
{
result = [ $(.$(ps)es.$(type)).find $(property-set) ] ;
# If the prefix/suffix is explicitly set to an empty string, we consider
# prefix/suffix to be found. If we were not to compare with "", there
# would be no way to specify an empty prefix/suffix.
if $(result)-is-defined
{
found = true ;
}
type = $(.base.$(type)) ;
}
if $(result) = ""
{
result = ;
}
return $(result) ;
}
# Returns file type given its name. If there are several dots in filename, tries
# each suffix. E.g. for name of "file.so.1.2" suffixes "2", "1", and "so" will
# be tried.
#
rule type ( filename )
{
if [ os.name ] in NT CYGWIN
{
filename = $(filename:L) ;
}
local type ;
while ! $(type) && $(filename:S)
{
local suffix = $(filename:S) ;
type = $(.type$(suffix)) ;
filename = $(filename:S=) ;
}
return $(type) ;
}
# Rule used to construct all main targets. Note that this rule gets imported
# into the global namespace under different alias names and the exact target
# type to construct is selected based on the alias used to actually invoke this
# rule.
#
rule main-target-rule ( name : sources * : requirements * : default-build * :
usage-requirements * )
{
param.handle-named-params
sources requirements default-build usage-requirements ;
# First discover the required target type based on the exact alias used to
# invoke this rule.
local bt = [ BACKTRACE 1 ] ;
local rulename = $(bt[4]) ;
local target-type = [ type-from-rule-name $(rulename) ] ;
# This is a circular module dependency and so must be imported here.
import targets ;
return [ targets.create-typed-target $(target-type) : [ project.current ] :
$(name) : $(sources) : $(requirements) : $(default-build) :
$(usage-requirements) ] ;
}
rule __test__ ( )
{
import assert ;
# TODO: Add tests for all the is-derived, is-base & related type relation
# checking rules.
}

View File

@@ -0,0 +1,381 @@
# Status: ported.
# Base revision: 45462.
# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
# distribute this software is granted provided this copyright notice appears in
# all copies. This software is provided "as is" without express or implied
# warranty, and with no claim as to its suitability for any purpose.
import re
import os
import os.path
from b2.util.utility import replace_grist, os_name
from b2.exceptions import *
from b2.build import feature, property, scanner
from b2.util import bjam_signature, is_iterable_typed
__re_hyphen = re.compile ('-')
def __register_features ():
""" Register features need by this module.
"""
# The feature is optional so that it is never implicitly added.
# It's used only for internal purposes, and in all cases we
# want to explicitly use it.
feature.feature ('target-type', [], ['composite', 'optional'])
feature.feature ('main-target-type', [], ['optional', 'incidental'])
feature.feature ('base-target-type', [], ['composite', 'optional', 'free'])
def reset ():
""" Clear the module state. This is mainly for testing purposes.
Note that this must be called _after_ resetting the module 'feature'.
"""
global __prefixes_suffixes, __suffixes_to_types, __types, __rule_names_to_types, __target_suffixes_cache
__register_features ()
# Stores suffixes for generated targets.
__prefixes_suffixes = [property.PropertyMap(), property.PropertyMap()]
# Maps suffixes to types
__suffixes_to_types = {}
# A map with all the registered types, indexed by the type name
# Each entry is a dictionary with following values:
# 'base': the name of base type or None if type has no base
# 'derived': a list of names of type which derive from this one
# 'scanner': the scanner class registered for this type, if any
__types = {}
# Caches suffixes for targets with certain properties.
__target_suffixes_cache = {}
reset ()
@bjam_signature((["type"], ["suffixes", "*"], ["base_type", "?"]))
def register (type, suffixes = [], base_type = None):
""" Registers a target type, possibly derived from a 'base-type'.
If 'suffixes' are provided, they list all the suffixes that mean a file is of 'type'.
Also, the first element gives the suffix to be used when constructing and object of
'type'.
type: a string
suffixes: None or a sequence of strings
base_type: None or a string
"""
# Type names cannot contain hyphens, because when used as
# feature-values they will be interpreted as composite features
# which need to be decomposed.
if __re_hyphen.search (type):
raise BaseException ('type name "%s" contains a hyphen' % type)
# it's possible for a type to be registered with a
# base type that hasn't been registered yet. in the
# check for base_type below and the following calls to setdefault()
# the key `type` will be added to __types. When the base type
# actually gets registered, it would fail after the simple check
# of "type in __types"; thus the check for "'base' in __types[type]"
if type in __types and 'base' in __types[type]:
raise BaseException ('Type "%s" is already registered.' % type)
entry = __types.setdefault(type, {})
entry['base'] = base_type
entry.setdefault('derived', [])
entry.setdefault('scanner', None)
if base_type:
__types.setdefault(base_type, {}).setdefault('derived', []).append(type)
if len (suffixes) > 0:
# Generated targets of 'type' will use the first of 'suffixes'
# (this may be overridden)
set_generated_target_suffix (type, [], suffixes [0])
# Specify mapping from suffixes to type
register_suffixes (suffixes, type)
feature.extend('target-type', [type])
feature.extend('main-target-type', [type])
feature.extend('base-target-type', [type])
if base_type:
feature.compose ('<target-type>' + type, [replace_grist (base_type, '<base-target-type>')])
feature.compose ('<base-target-type>' + type, ['<base-target-type>' + base_type])
import b2.build.generators as generators
# Adding a new derived type affects generator selection so we need to
# make the generator selection module update any of its cached
# information related to a new derived type being defined.
generators.update_cached_information_with_a_new_type(type)
# FIXME: resolving recursive dependency.
from b2.manager import get_manager
get_manager().projects().project_rules().add_rule_for_type(type)
# FIXME: quick hack.
def type_from_rule_name(rule_name):
assert isinstance(rule_name, basestring)
return rule_name.upper().replace("-", "_")
def register_suffixes (suffixes, type):
""" Specifies that targets with suffix from 'suffixes' have the type 'type'.
If a different type is already specified for any of syffixes, issues an error.
"""
assert is_iterable_typed(suffixes, basestring)
assert isinstance(type, basestring)
for s in suffixes:
if s in __suffixes_to_types:
old_type = __suffixes_to_types [s]
if old_type != type:
raise BaseException ('Attempting to specify type for suffix "%s"\nOld type: "%s", New type "%s"' % (s, old_type, type))
else:
__suffixes_to_types [s] = type
def registered (type):
""" Returns true iff type has been registered.
"""
assert isinstance(type, basestring)
return type in __types
def validate (type):
""" Issues an error if 'type' is unknown.
"""
assert isinstance(type, basestring)
if not registered (type):
raise BaseException ("Unknown target type '%s'" % type)
def set_scanner (type, scanner):
""" Sets a scanner class that will be used for this 'type'.
"""
if __debug__:
from .scanner import Scanner
assert isinstance(type, basestring)
assert issubclass(scanner, Scanner)
validate (type)
__types [type]['scanner'] = scanner
def get_scanner (type, prop_set):
""" Returns a scanner instance appropriate to 'type' and 'property_set'.
"""
if __debug__:
from .property_set import PropertySet
assert isinstance(type, basestring)
assert isinstance(prop_set, PropertySet)
if registered (type):
scanner_type = __types [type]['scanner']
if scanner_type:
return scanner.get (scanner_type, prop_set.raw ())
pass
return None
def base(type):
"""Returns a base type for the given type or nothing in case the given type is
not derived."""
assert isinstance(type, basestring)
return __types[type]['base']
def all_bases (type):
""" Returns type and all of its bases, in the order of their distance from type.
"""
assert isinstance(type, basestring)
result = []
while type:
result.append (type)
type = __types [type]['base']
return result
def all_derived (type):
""" Returns type and all classes that derive from it, in the order of their distance from type.
"""
assert isinstance(type, basestring)
result = [type]
for d in __types [type]['derived']:
result.extend (all_derived (d))
return result
def is_derived (type, base):
""" Returns true if 'type' is 'base' or has 'base' as its direct or indirect base.
"""
assert isinstance(type, basestring)
assert isinstance(base, basestring)
# TODO: this isn't very efficient, especially for bases close to type
if base in all_bases (type):
return True
else:
return False
def is_subtype (type, base):
""" Same as is_derived. Should be removed.
"""
assert isinstance(type, basestring)
assert isinstance(base, basestring)
# TODO: remove this method
return is_derived (type, base)
@bjam_signature((["type"], ["properties", "*"], ["suffix"]))
def set_generated_target_suffix (type, properties, suffix):
""" Sets a target suffix that should be used when generating target
of 'type' with the specified properties. Can be called with
empty properties if no suffix for 'type' was specified yet.
This does not automatically specify that files 'suffix' have
'type' --- two different types can use the same suffix for
generating, but only one type should be auto-detected for
a file with that suffix. User should explicitly specify which
one.
The 'suffix' parameter can be empty string ("") to indicate that
no suffix should be used.
"""
assert isinstance(type, basestring)
assert is_iterable_typed(properties, basestring)
assert isinstance(suffix, basestring)
set_generated_target_ps(1, type, properties, suffix)
def change_generated_target_suffix (type, properties, suffix):
""" Change the suffix previously registered for this type/properties
combination. If suffix is not yet specified, sets it.
"""
assert isinstance(type, basestring)
assert is_iterable_typed(properties, basestring)
assert isinstance(suffix, basestring)
change_generated_target_ps(1, type, properties, suffix)
def generated_target_suffix(type, properties):
if __debug__:
from .property_set import PropertySet
assert isinstance(type, basestring)
assert isinstance(properties, PropertySet)
return generated_target_ps(1, type, properties)
@bjam_signature((["type"], ["properties", "*"], ["prefix"]))
def set_generated_target_prefix(type, properties, prefix):
"""
Sets a file prefix to be used when generating a target of 'type' with the
specified properties. Can be called with no properties if no prefix has
already been specified for the 'type'. The 'prefix' parameter can be an empty
string ("") to indicate that no prefix should be used.
Note that this does not cause files with 'prefix' to be automatically
recognized as being of 'type'. Two different types can use the same prefix for
their generated files but only one type can be auto-detected for a file with
that prefix. User should explicitly specify which one using the
register-prefixes rule.
Usage example: library names use the "lib" prefix on unix.
"""
set_generated_target_ps(0, type, properties, prefix)
# Change the prefix previously registered for this type/properties combination.
# If prefix is not yet specified, sets it.
def change_generated_target_prefix(type, properties, prefix):
assert isinstance(type, basestring)
assert is_iterable_typed(properties, basestring)
assert isinstance(prefix, basestring)
change_generated_target_ps(0, type, properties, prefix)
def generated_target_prefix(type, properties):
if __debug__:
from .property_set import PropertySet
assert isinstance(type, basestring)
assert isinstance(properties, PropertySet)
return generated_target_ps(0, type, properties)
def set_generated_target_ps(is_suffix, type, properties, val):
assert isinstance(is_suffix, (int, bool))
assert isinstance(type, basestring)
assert is_iterable_typed(properties, basestring)
assert isinstance(val, basestring)
properties.append ('<target-type>' + type)
__prefixes_suffixes[is_suffix].insert (properties, val)
def change_generated_target_ps(is_suffix, type, properties, val):
assert isinstance(is_suffix, (int, bool))
assert isinstance(type, basestring)
assert is_iterable_typed(properties, basestring)
assert isinstance(val, basestring)
properties.append ('<target-type>' + type)
prev = __prefixes_suffixes[is_suffix].find_replace(properties, val)
if not prev:
set_generated_target_ps(is_suffix, type, properties, val)
# Returns either prefix or suffix (as indicated by 'is_suffix') that should be used
# when generating a target of 'type' with the specified properties.
# If no prefix/suffix is specified for 'type', returns prefix/suffix for
# base type, if any.
def generated_target_ps_real(is_suffix, type, properties):
assert isinstance(is_suffix, (int, bool))
assert isinstance(type, basestring)
assert is_iterable_typed(properties, basestring)
result = ''
found = False
while type and not found:
result = __prefixes_suffixes[is_suffix].find (['<target-type>' + type] + properties)
# Note that if the string is empty (""), but not null, we consider
# suffix found. Setting prefix or suffix to empty string is fine.
if result is not None:
found = True
type = __types [type]['base']
if not result:
result = ''
return result
def generated_target_ps(is_suffix, type, prop_set):
""" Returns suffix that should be used when generating target of 'type',
with the specified properties. If not suffix were specified for
'type', returns suffix for base type, if any.
"""
if __debug__:
from .property_set import PropertySet
assert isinstance(is_suffix, (int, bool))
assert isinstance(type, basestring)
assert isinstance(prop_set, PropertySet)
key = (is_suffix, type, prop_set)
v = __target_suffixes_cache.get(key, None)
if not v:
v = generated_target_ps_real(is_suffix, type, prop_set.raw())
__target_suffixes_cache [key] = v
return v
def type(filename):
""" Returns file type given it's name. If there are several dots in filename,
tries each suffix. E.g. for name of "file.so.1.2" suffixes "2", "1", and
"so" will be tried.
"""
assert isinstance(filename, basestring)
while 1:
filename, suffix = os.path.splitext (filename)
if not suffix: return None
suffix = suffix[1:]
if suffix in __suffixes_to_types:
return __suffixes_to_types[suffix]
# NOTE: moved from tools/types/register
def register_type (type, suffixes, base_type = None, os = []):
""" Register the given type on the specified OSes, or on remaining OSes
if os is not specified. This rule is injected into each of the type
modules for the sake of convenience.
"""
assert isinstance(type, basestring)
assert is_iterable_typed(suffixes, basestring)
assert isinstance(base_type, basestring) or base_type is None
assert is_iterable_typed(os, basestring)
if registered (type):
return
if not os or os_name () in os:
register (type, suffixes, base_type)

View File

@@ -0,0 +1,225 @@
# Copyright 2021 Nikita Kniazev
# Copyright 2002, 2003, 2004, 2006 Vladimir Prus
# Copyright 2008, 2012 Jurko Gospodnetic
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
import numbers ;
# Mirror engine JAM_VERSION
.major = "4" ;
.minor = "7" ;
rule boost-build ( )
{
return "$(.major).$(.minor)-git" ;
}
rule print ( )
{
if [ verify-engine-version ]
{
ECHO "B2" [ boost-build ] ;
}
}
rule verify-engine-version ( )
{
local v = [ modules.peek : JAM_VERSION ] ;
if $(v[1]) != $(.major) || $(v[2]) != $(.minor)
{
local argv = [ modules.peek : ARGV ] ;
local e = $(argv[1]) ;
local l = [ modules.binding version ] ;
l = $(l:D) ;
l = $(l:D) ;
ECHO "warning: mismatched versions of B2 engine and core" ;
ECHO "warning: B2 engine ($(e)) is $(v:J=.)" ;
ECHO "warning: B2 core (at $(l)) is" [ boost-build ] ;
}
else
{
return true ;
}
}
# Utility rule for testing whether all elements in a sequence are equal to 0.
#
local rule is-all-zeroes ( sequence * )
{
local result = "true" ;
for local e in $(sequence)
{
if $(e) != "0"
{
result = "" ;
}
}
return $(result) ;
}
# Returns "true" if the first version is less than the second one.
#
rule version-less ( lhs + : rhs + )
{
numbers.check $(lhs) ;
numbers.check $(rhs) ;
local done ;
local result ;
while ! $(done) && $(lhs) && $(rhs)
{
if [ numbers.less $(lhs[1]) $(rhs[1]) ]
{
done = "true" ;
result = "true" ;
}
else if [ numbers.less $(rhs[1]) $(lhs[1]) ]
{
done = "true" ;
}
else
{
lhs = $(lhs[2-]) ;
rhs = $(rhs[2-]) ;
}
}
if ( ! $(done) && ! $(lhs) && ! [ is-all-zeroes $(rhs) ] )
{
result = "true" ;
}
return $(result) ;
}
# Returns "true" if the required version is compatible with the having one.
# This uses sematic versioning where (major.x.y) is compatible with
# (major.n.m) and (major.x.z). And is incompatible for other values.
#
rule version-compatible ( req + : has + )
{
numbers.check $(req) ;
numbers.check $(has) ;
if $(req) = $(has)
{
return true ;
}
while $(req) && [ numbers.equal $(req[1]) $(has[1]:E=0) ]
{
req = $(req[2-]) ;
has = $(has[2-]) ;
}
if $(req)
{
return ;
}
return true ;
}
# Returns "true" if the current JAM version version is at least the given
# version.
#
rule check-jam-version ( version + )
{
local version-tag = $(version:J=.) ;
if ! $(version-tag)
{
import errors ;
errors.error Invalid version "specifier:" : $(version:E="(undefined)") ;
}
if ! $(.jam-version-check.$(version-tag))-is-defined
{
local jam-version = [ modules.peek : JAM_VERSION ] ;
if ! $(jam-version)
{
import errors ;
errors.error "Unable to deduce Boost Jam version. Your Boost Jam"
"installation is most likely terribly outdated." ;
}
.jam-version-check.$(version-tag) = "true" ;
if [ version-less [ modules.peek : JAM_VERSION ] : $(version) ]
{
.jam-version-check.$(version-tag) = "" ;
}
}
return $(.jam-version-check.$(version-tag)) ;
}
rule __test__ ( )
{
import assert ;
local jam-version = [ modules.peek : JAM_VERSION ] ;
local future-version = $(jam-version) ;
future-version += "1" ;
assert.true check-jam-version $(jam-version) ;
assert.false check-jam-version $(future-version) ;
assert.true version-less 0 : 1 ;
assert.false version-less 0 : 0 ;
assert.true version-less 1 : 2 ;
assert.false version-less 1 : 1 ;
assert.false version-less 2 : 1 ;
assert.true version-less 3 1 20 : 3 4 10 ;
assert.false version-less 3 1 10 : 3 1 10 ;
assert.false version-less 3 4 10 : 3 1 20 ;
assert.true version-less 3 1 20 5 1 : 3 4 10 ;
assert.false version-less 3 1 10 5 1 : 3 1 10 ;
assert.false version-less 3 4 10 5 1 : 3 1 20 ;
assert.true version-less 3 1 20 : 3 4 10 5 1 ;
assert.true version-less 3 1 10 : 3 1 10 5 1 ;
assert.false version-less 3 4 10 : 3 1 20 5 1 ;
assert.false version-less 3 1 10 : 3 1 10 0 0 ;
assert.false version-less 3 1 10 0 0 : 3 1 10 ;
assert.false version-less 3 1 10 0 : 3 1 10 0 0 ;
assert.false version-less 3 1 10 0 : 03 1 10 0 0 ;
assert.false version-less 03 1 10 0 : 3 1 10 0 0 ;
# TODO: Add tests for invalid input data being sent to version-less.
assert.true version-compatible 4 : 4 ;
assert.true version-compatible 4 : 4 9 ;
assert.false version-compatible 4 9 : 4 ;
assert.true version-compatible 4 9 : 4 9 ;
assert.false version-compatible 4 9 1 : 4 9 ;
assert.true version-compatible 4 9 1 : 4 9 1 ;
assert.false version-compatible 4 8 : 4 9 ;
assert.false version-compatible 4 8 1 : 4 9 ;
assert.false version-compatible 4 8 1 : 4 9 1 ;
assert.true version-compatible 5 : 5 ;
assert.false version-compatible 5 : 4 ;
assert.false version-compatible 5 : 4 9 ;
assert.false version-compatible 5 1 : 5 ;
assert.true version-compatible 5 1 : 5 1 ;
assert.false version-compatible 5 1 : 5 2 ;
assert.false version-compatible 5 1 1 : 5 ;
assert.false version-compatible 5 1 1 : 5 1 ;
assert.false version-compatible 5 2 : 5 ;
assert.false version-compatible 5 2 : 5 1 ;
assert.true version-compatible 5 2 : 5 2 ;
assert.true version-compatible 4 : 4 0 ;
assert.true version-compatible 4 0 : 4 ;
assert.true version-compatible 04 : 4 ;
assert.true version-compatible 04 : 04 ;
assert.true version-compatible 04 : 4 ;
assert.true version-compatible 04 00 : 04 ;
assert.true version-compatible 04 : 04 00 ;
}

View File

@@ -0,0 +1,38 @@
import os
import sys
import bjam
from b2.manager import get_manager
MANAGER = get_manager()
ERROR_HANDLER = MANAGER.errors()
_major = "2015"
_minor = "07"
def boost_build():
return "{}.{}-git".format(_major, _minor)
def verify_engine_version():
major, minor, _ = v = bjam.variable('JAM_VERSION')
if major != _major or minor != _minor:
from textwrap import dedent
engine = sys.argv[0]
core = os.path.dirname(os.path.dirname(__file__))
print dedent("""\
warning: mismatched version of Boost.Build engine core
warning: Boost.Build engine "{}" is "{}"
warning: Boost.Build core at {} is {}
""".format(engine, '.'.join(v), core, boost_build()))
return False
return True
def report():
if verify_engine_version():
print "Boost.Build " + boost_build()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff