diff options
author | Kenneth Heafield <github@kheafield.com> | 2012-05-12 14:01:52 -0400 |
---|---|---|
committer | Kenneth Heafield <github@kheafield.com> | 2012-05-12 14:01:52 -0400 |
commit | 1a3cb9d9b0ab24d21d7e4edb70bb4a939f621082 (patch) | |
tree | 96f5cbfad3cbb0b8e89c26d6fa2e1a72a9039439 /jam-files/boost-build/build | |
parent | dba1128114d68ed46cdea98ecb887c7657a78474 (diff) |
Give in and copy bjam into cdec source code
Diffstat (limited to 'jam-files/boost-build/build')
34 files changed, 19696 insertions, 0 deletions
diff --git a/jam-files/boost-build/build/__init__.py b/jam-files/boost-build/build/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/jam-files/boost-build/build/__init__.py diff --git a/jam-files/boost-build/build/ac.jam b/jam-files/boost-build/build/ac.jam new file mode 100644 index 00000000..6768f358 --- /dev/null +++ b/jam-files/boost-build/build/ac.jam @@ -0,0 +1,198 @@ +# Copyright (c) 2010 Vladimir Prus. +# +# Use, modification and distribution is subject to the Boost Software +# License Version 1.0. (See accompanying file LICENSE_1_0.txt or +# http://www.boost.org/LICENSE_1_0.txt) + +import property-set ; +import path ; +import modules ; +import "class" ; +import errors ; +import configure ; + +rule find-include-path ( variable : properties : header + : provided-path ? ) +{ + # FIXME: document which properties affect this function by + # default. + local target-os = [ $(properties).get <target-os> ] ; + properties = [ property-set.create <target-os>$(toolset) ] ; + if $($(variable)-$(properties)) + { + return $($(variable)-$(properties)) ; + } + else + { + provided-path ?= [ modules.peek : $(variable) ] ; + includes = $(provided-path) ; + includes += [ $(properties).get <include> ] ; + if [ $(properties).get <target-os> ] != windows + { + # FIXME: use sysroot + includes += /usr/include ; + } + + local result ; + while ! $(result) && $(includes) + { + local f = [ path.root $(header) $(includes[1]) ] ; + ECHO "Checking " $(f) ; + if [ path.exists $(f) ] + { + result = $(includes[1]) ; + } + else if $(provided-path) + { + errors.user-error "Could not find header" $(header) + : "in the user-specified directory" $(provided-path) ; + } + includes = $(includes[2-]) ; + } + $(variable)-$(properties) = $(result) ; + return $(result) ; + } +} + +rule find-library ( variable : properties : names + : provided-path ? ) +{ + local target-os = [ $(properties).get <target-os> ] ; + properties = [ property-set.create <target-os>$(toolset) ] ; + if $($(variable)-$(properties)) + { + return $($(variable)-$(properties)) ; + } + else + { + provided-path ?= [ modules.peek : $(variable) ] ; + paths = $(provided-path) ; + paths += [ $(properties).get <library-path> ] ; + if [ $(properties).get <target-os> ] != windows + { + paths += /usr/lib /usr/lib32 /usr/lib64 ; + } + + local result ; + while ! $(result) && $(paths) + { + while ! $(result) && $(names) + { + local f ; + if $(target-os) = windows + { + f = $(paths[1])/$(names[1]).lib ; + if [ path.exists $(f) ] + { + result = $(f) ; + } + } + else + { + # FIXME: check for .a as well, depending on + # the 'link' feature. + f = $(paths[1])/lib$(names[1]).so ; + ECHO "CHECKING $(f) " ; + if [ path.exists $(f) ] + { + result = $(f) ; + } + } + if ! $(result) && $(provided-path) + { + errors.user-error "Could not find either of: " $(names) + : "in the user-specified directory" $(provided-path) ; + + } + names = $(names[2-]) ; + } + paths = $(paths[2-]) ; + } + $(variable)-$(properties) = $(result) ; + return $(result) ; + } +} + +class ac-library : basic-target +{ + import errors ; + import indirect ; + import virtual-target ; + import ac ; + import configure ; + + rule __init__ ( name : project : * : * ) + { + basic-target.__init__ $(name) : $(project) : $(sources) + : $(requirements) ; + + reconfigure $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + } + + rule set-header ( header ) + { + self.header = $(header) ; + } + + rule set-default-names ( names + ) + { + self.default-names = $(names) ; + } + + rule reconfigure ( * : * ) + { + ECHO "XXX" $(1) ; + if ! $(1) + { + # This is 'using xxx ;'. Nothing to configure, really. + } + else + { + for i in 1 2 3 4 5 6 7 8 9 + { + # FIXME: this naming is inconsistent with XXX_INCLUDE/XXX_LIBRARY + if ! ( $($(i)[1]) in root include-path library-path library-name condition ) + { + errors.user-error "Invalid named parameter" $($(i)[1]) ; + } + local name = $($(i)[1]) ; + local value = $($(i)[2-]) ; + if $($(name)) && $($(name)) != $(value) + { + errors.user-error "Attempt to change value of '$(name)'" ; + } + $(name) = $(value) ; + } + + include-path ?= $(root)/include ; + library-path ?= $(root)/lib ; + } + } + + rule construct ( name : sources * : property-set ) + { + # FIXME: log results. + local libnames = $(library-name) ; + if ! $(libnames) && ! $(include-path) && ! $(library-path) + { + libnames = [ modules.peek : $(name:U)_NAME ] ; + # Backward compatibility only. + libnames ?= [ modules.peek : $(name:U)_BINARY ] ; + } + libnames ?= $(self.default-names) ; + + local includes = [ + ac.find-include-path $(name:U)_INCLUDE : $(property-set) : $(self.header) : $(include-path) ] ; + local library = [ ac.find-library $(name:U)_LIBRARY : $(property-set) : $(libnames) : $(library-path) ] ; + if $(includes) && $(library) + { + library = [ virtual-target.from-file $(library) : . : $(self.project) ] ; + configure.log-library-search-result $(name) : "found" ; + return [ property-set.create <include>$(includes) <source>$(library) ] ; + } + else + { + configure.log-library-search-result $(name) : "no found" ; + } + } +} + diff --git a/jam-files/boost-build/build/alias.jam b/jam-files/boost-build/build/alias.jam new file mode 100644 index 00000000..48019cb9 --- /dev/null +++ b/jam-files/boost-build/build/alias.jam @@ -0,0 +1,73 @@ +# Copyright 2003, 2004, 2006 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# 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 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 ] ] ; + } +} + + +# 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 * ) +{ + 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 ; diff --git a/jam-files/boost-build/build/alias.py b/jam-files/boost-build/build/alias.py new file mode 100644 index 00000000..575e5360 --- /dev/null +++ b/jam-files/boost-build/build/alias.py @@ -0,0 +1,63 @@ +# Copyright 2003, 2004, 2006 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# 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 + +class AliasTarget(targets.BasicTarget): + + def __init__(self, *args): + targets.BasicTarget.__init__(self, *args) + + def construct(self, name, source_targets, properties): + return [property_set.empty(), source_targets] + + def compute_usage_requirements(self, 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=[]): + + 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) + diff --git a/jam-files/boost-build/build/build-request.jam b/jam-files/boost-build/build/build-request.jam new file mode 100644 index 00000000..8a1f7b0e --- /dev/null +++ b/jam-files/boost-build/build/build-request.jam @@ -0,0 +1,322 @@ +# Copyright 2002 Dave Abrahams +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +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) ; +} + + +# 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 ; + # No conflict with things used at a higher level? + if ! [ set.intersection $(f) : $(x-product-used) ] + { + local x-product-seen ; + { + # 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-]) : $(feature-space) ] ; + result = $(property-sets[1])/$(rest) ; + } + + 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) ] + { + property-sets = ; + } + + seen = $(x-product-seen) ; + } + + if $(property-sets[2]) + { + result += [ x-product-aux $(property-sets[2-]) : $(feature-space) ] ; + } + + # 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. +# +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 ; + 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 += [ convert-command-line-element $(e) : + $(feature-space) ] ; + } + else + { + targets += $(e) ; + } + } + else if [ MATCH "^(-[-ldjfsto])$" : $(e) ] + { + skip-next = true ; + } + } + return [ new vector + [ new vector $(targets) ] + [ new vector $(properties) ] ] ; +} + + +# Converts one element of command line build request specification into internal +# form. Expects all the project files to already be loaded. +# +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 = <$(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 ! $(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 ; + + 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 ; +} diff --git a/jam-files/boost-build/build/build_request.py b/jam-files/boost-build/build/build_request.py new file mode 100644 index 00000000..cc9f2400 --- /dev/null +++ b/jam-files/boost-build/build/build_request.py @@ -0,0 +1,216 @@ +# 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 * +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. + """ + # 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. + """ + 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 + """ + 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.""" + 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.""" + + targets = [] + properties = [] + + for e in command_line: + if e[0] != "-": + # 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 += convert_command_line_element(e) + else: + targets.append(e) + + return [targets, properties] + +# Converts one element of command line build request specification into +# internal form. +def convert_command_line_element(e): + + 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 ; +### } +### +### diff --git a/jam-files/boost-build/build/configure.jam b/jam-files/boost-build/build/configure.jam new file mode 100644 index 00000000..14c1328a --- /dev/null +++ b/jam-files/boost-build/build/configure.jam @@ -0,0 +1,237 @@ +# Copyright (c) 2010 Vladimir Prus. +# +# Use, modification and distribution is subject to the Boost Software +# License Version 1.0. (See accompanying file LICENSE_1_0.txt or +# http://www.boost.org/LICENSE_1_0.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 targets ; +import errors ; +import targets ; +import sequence ; +import property ; +import property-set ; +import "class" : new ; +import common ; +import path ; + +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 build. +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) ; +} + + + +rule log-check-result ( result ) +{ + if ! $(.announced-checks) + { + ECHO "Performing configuration checks\n" ; + .announced-checks = 1 ; + } + + ECHO $(result) ; + #.check-results += $(result) ; +} + +rule log-library-search-result ( library : result ) +{ + local x = [ PAD " - $(library) : $(result)" : $(.width) ] ; + log-check-result "$(x)" ; +} + +rule print-component-configuration ( ) +{ + local c = [ sequence.unique $(.components) ] ; + + ECHO "\nComponent configuration:\n" ; + 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 ( ) +{ + # FIXME: the problem with that approach is tha + # the user sees 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 ; + } +} + +# 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) ] ; + + local jam-targets ; + for local t in $(targets[2-]) + { + jam-targets += [ $(t).actualize ] ; + } + + if ! UPDATE_NOW in [ RULENAMES ] + { + # Cannot determine. Assume existance. + } + else + { + local x = [ PAD " - $(what)" : $(.width) ] ; + if [ UPDATE_NOW $(jam-targets) : + $(.log-fd) : ignore-minus-n : ignore-minus-q ] + { + .$(what)-supported.$(ps) = yes ; + result = true ; + log-check-result "$(x) : yes" ; + } + else + { + log-check-result "$(x) : no" ; + } + } + return $(result) ; + } + else + { + return $(.$(what)-supported.$(ps)) ; + } +} + +rule builds ( metatarget-reference : properties * : what ? : retry ? ) +{ + what ?= "$(metatarget-reference) builds" ; + + # FIXME: this should not be hardcoded. Other checks might + # want to consider different set of features as relevant. + local toolset = [ property.select <toolset> : $(properties) ] ; + local toolset-version-property = "<toolset-$(toolset:G=):version>" ; + local relevant = [ property.select <target-os> <toolset> $(toolset-version-property) + <address-model> <architecture> + : $(properties) ] ; + local ps = [ property-set.create $(relevant) ] ; + local t = [ targets.current ] ; + local p = [ $(t).project ] ; + + return [ builds-raw $(metatarget-reference) : $(p) : $(ps) : $(what) : $(retry) ] ; +} + + +# 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. +rule set-log-file ( log-file ) +{ + path.makedirs [ path.parent $(log-file) ] ; + + .log-fd = [ FILE_OPEN $(log-file) : "w" ] ; +} + +# Frontend rules + +class check-target-builds-worker +{ + import configure ; + import property-set ; + import targets ; + import property ; + + rule __init__ ( target message ? : true-properties * : false-properties * ) + { + self.target = $(target) ; + self.message = $(message) ; + self.true-properties = $(true-properties) ; + self.false-properties = $(false-properties) ; + } + + 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) ] ; + } +} + + +rule check-target-builds ( target message ? : true-properties * : false-properties * ) +{ + local instance = [ new check-target-builds-worker $(target) $(message) : $(true-properties) + : $(false-properties) ] ; + return <conditional>@$(instance).check ; +} + +IMPORT $(__name__) : check-target-builds : : check-target-builds ; + + diff --git a/jam-files/boost-build/build/configure.py b/jam-files/boost-build/build/configure.py new file mode 100644 index 00000000..0426832c --- /dev/null +++ b/jam-files/boost-build/build/configure.py @@ -0,0 +1,164 @@ +# Status: ported. +# Base revison: 64488 +# +# Copyright (c) 2010 Vladimir Prus. +# +# Use, modification and distribution is subject to the Boost Software +# License Version 1.0. (See accompanying file LICENSE_1_0.txt or +# http://www.boost.org/LICENSE_1_0.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 + +import b2.build.targets + +from b2.manager import get_manager +from b2.util.sequence import unique +from b2.util import bjam_signature, value_to_jam + +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.""" + __components.extend(components) + +def components_building(components): + """Declare that the components specified by the parameters will be build.""" + __built_components.extend(components) + +def log_component_configuration(component, message): + """Report something about component configuration that the user should better know.""" + __component_logs.setdefault(component, []).append(message) + +def log_check_result(result): + global __announced_checks + if not __announced_checks: + print "Performing configuration checks" + __announced_checks = True + + print result + +def log_library_search_result(library, result): + 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. + + result = [] + + existing = __builds_cache.get((what, ps), None) + if existing is None: + + result = False + __builds_cache[(what, ps)] = False + + targets = b2.build.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): + # 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): + + # 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) + + diff --git a/jam-files/boost-build/build/engine.py b/jam-files/boost-build/build/engine.py new file mode 100644 index 00000000..be9736e0 --- /dev/null +++ b/jam-files/boost-build/build/engine.py @@ -0,0 +1,172 @@ +# Copyright Pedro Ferreira 2005. +# Copyright Vladimir Prus 2007. +# 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) + +bjam_interface = __import__('bjam') + +import operator +import re + +import b2.build.property_set as property_set +import b2.util + +class BjamAction: + """Class representing bjam action defined from Python.""" + + def __init__(self, action_name, function): + self.action_name = action_name + self.function = function + + def __call__(self, targets, sources, property_set): + if self.function: + self.function(targets, sources, property_set) + + # 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, []) + +class BjamNativeAction: + """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 __init__(self, action_name, function): + self.action_name = action_name + self.function = function + + def __call__(self, targets, sources, property_set): + if self.function: + self.function(targets, sources, property_set) + + p = [] + if property_set: + p = property_set.raw() + + b2.util.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] + + for target in targets: + for source in sources: + self.do_add_dependency (target, source) + + 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] + + for target in targets: + self.do_set_target_variable (target, variable, value, append) + + def set_update_action (self, action_name, targets, sources, properties=property_set.empty()): + """ 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. + """ + assert(isinstance(properties, property_set.PropertySet)) + if isinstance (targets, str): + targets = [targets] + 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. + """ + if self.actions.has_key(action_name): + raise "Bjam action %s is already defined" % action_name + + assert(isinstance(flags, list)) + + bjam_flags = reduce(operator.or_, + (action_modifiers[flag] for flag in flags), 0) + + bjam_interface.define_action(action_name, command, bound_list, bjam_flags) + + self.actions[action_name] = BjamAction(action_name, function) + + 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. + if not self.actions.has_key(action_name): + self.actions[action_name] = BjamNativeAction(action_name, function) + + # Overridables + + + def do_set_update_action (self, action_name, targets, sources, property_set): + 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): + 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): + bjam_interface.call("DEPENDS", target, source) + + diff --git a/jam-files/boost-build/build/errors.py b/jam-files/boost-build/build/errors.py new file mode 100644 index 00000000..d9dceefe --- /dev/null +++ b/jam-files/boost-build/build/errors.py @@ -0,0 +1,127 @@ +# Status: being written afresh by Vladimir Prus + +# Copyright 2007 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# 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()) + + + + diff --git a/jam-files/boost-build/build/feature.jam b/jam-files/boost-build/build/feature.jam new file mode 100644 index 00000000..6f54adef --- /dev/null +++ b/jam-files/boost-build/build/feature.jam @@ -0,0 +1,1335 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +import assert : * ; +import "class" : * ; +import errors : lol->list ; +import indirect ; +import modules ; +import regex ; +import sequence ; +import set ; +import utility ; + + +local rule setup ( ) +{ + .all-attributes = + implicit + composite + optional + symmetric + free + incidental + path + dependency + propagated + link-incompatible + subfeature + order-sensitive + ; + + .all-features = ; + .all-subfeatures = ; + .all-top-features = ; # non-subfeatures + .all-implicit-values = ; +} +setup ; + + +# Prepare a fresh space to test in by moving all global variable settings into +# the given temporary module and erasing them here. +# +rule prepare-test ( temp-module ) +{ + DELETE_MODULE $(temp-module) ; + + # Transfer globals to temp-module. + for local v in [ VARNAMES feature ] + { + if [ MATCH (\\.) : $(v) ] + { + modules.poke $(temp-module) : $(v) : $($(v)) ; + $(v) = ; + } + } + setup ; +} + + +# Clear out all global variables and recover all variables from the given +# temporary module. +# +rule finish-test ( temp-module ) +{ + # Clear globals. + for local v in [ VARNAMES feature ] + { + if [ MATCH (\\.) : $(v) ] + { + $(v) = ; + } + } + + for local v in [ VARNAMES $(temp-module) ] + { + $(v) = [ modules.peek $(temp-module) : $(v) ] ; + } + DELETE_MODULE $(temp-module) ; +} + + +# Transform features by bracketing any elements which are not already bracketed +# by "<>". +# +local rule grist ( features * ) +{ + local empty = "" ; + return $(empty:G=$(features)) ; +} + + +# Declare a new feature with the given name, values, and attributes. +# +rule feature ( + name # Feature name. + : values * # Allowable values - may be extended later using feature.extend. + : attributes * # Feature attributes (e.g. implicit, free, propagated...). +) +{ + name = [ grist $(name) ] ; + + local error ; + + # Check for any unknown attributes. + if ! ( $(attributes) in $(.all-attributes) ) + { + error = unknown attributes: + [ set.difference $(attributes) : $(.all-attributes) ] ; + } + else if $(name) in $(.all-features) + { + error = feature already defined: ; + } + else if implicit in $(attributes) && free in $(attributes) + { + error = free features cannot also be implicit ; + } + else if free in $(attributes) && propagated in $(attributes) + { + error = free features cannot be propagated ; + } + else + { + local m = [ MATCH (.*=.*) : $(values) ] ; + if $(m[1]) + { + error = "feature value may not contain '='" ; + } + } + + if $(error) + { + errors.error $(error) + : "in" feature declaration: + : feature [ lol->list $(1) : $(2) : $(3) ] ; + } + + $(name).values ?= ; + $(name).attributes = $(attributes) ; + $(name).subfeatures ?= ; + $(attributes).features += $(name) ; + + .all-features += $(name) ; + if subfeature in $(attributes) + { + .all-subfeatures += $(name) ; + } + else + { + .all-top-features += $(name) ; + } + extend $(name) : $(values) ; +} + + +# Sets the default value of the given feature, overriding any previous default. +# +rule set-default ( feature : value ) +{ + local f = [ grist $(feature) ] ; + local a = $($(f).attributes) ; + local bad-attribute = ; + if free in $(a) + { + bad-attribute = free ; + } + else if optional in $(a) + { + bad-attribute = optional ; + } + if $(bad-attribute) + { + errors.error "$(bad-attribute) property $(f) cannot have a default." ; + } + if ! $(value) in $($(f).values) + { + errors.error "The specified default value, '$(value)' is invalid" + : "allowed values are: " $($(f).values) ; + } + $(f).default = $(value) ; +} + + +# Returns the default property values for the given features. +# +rule defaults ( features * ) +{ + local result ; + for local f in $(features) + { + local gf = $(:E=:G=$(f)) ; + local a = $($(gf).attributes) ; + if ( free in $(a) ) || ( optional in $(a) ) + { + } + else + { + result += $(gf)$($(gf).default) ; + } + } + return $(result) ; +} + + +# Returns true iff all 'names' elements are valid features. +# +rule valid ( names + ) +{ + if $(names) in $(.all-features) + { + return true ; + } +} + + +# Returns the attibutes of the given feature. +# +rule attributes ( feature ) +{ + return $($(:E=:G=$(feature)).attributes) ; +} + + +# Returns the values of the given feature. +# +rule values ( feature ) +{ + return $($(:E=:G=$(feature)).values) ; +} + + +# Returns true iff 'value-string' is a value-string of an implicit feature. +# +rule is-implicit-value ( value-string ) +{ + local v = [ regex.split $(value-string) - ] ; + local failed ; + if ! $(v[1]) in $(.all-implicit-values) + { + failed = true ; + } + else + { + local feature = $($(v[1]).implicit-feature) ; + for local subvalue in $(v[2-]) + { + if ! [ find-implied-subfeature $(feature) $(subvalue) : $(v[1]) ] + { + failed = true ; + } + } + } + + if ! $(failed) + { + return true ; + } +} + + +# Returns the implicit feature associated with the given implicit value. +# +rule implied-feature ( implicit-value ) +{ + local components = [ regex.split $(implicit-value) "-" ] ; + + local feature = $($(components[1]).implicit-feature) ; + if ! $(feature) + { + errors.error \"$(implicit-value)\" is not a value of an implicit feature ; + feature = "" ; # Keep testing happy; it expects a result. + } + return $(feature) ; +} + + +local rule find-implied-subfeature ( feature subvalue : value-string ? ) +{ + # Feature should be of the form <feature-name>. + if $(feature) != $(feature:G) + { + errors.error invalid feature $(feature) ; + } + + return $($(feature)$(value-string:E="")<>$(subvalue).subfeature) ; +} + + +# 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 +# +rule implied-subfeature ( + feature # The main feature name. + subvalue # The value of one of its subfeatures. + : value-string ? # The value of the main feature. +) +{ + local subfeature = [ find-implied-subfeature $(feature) $(subvalue) + : $(value-string) ] ; + if ! $(subfeature) + { + value-string ?= "" ; + errors.error \"$(subvalue)\" is not a known subfeature value of + $(feature)$(value-string) ; + } + return $(subfeature) ; +} + + +# Generate an error if the feature is unknown. +# +local rule validate-feature ( feature ) +{ + if ! $(feature) in $(.all-features) + { + errors.error unknown feature \"$(feature)\" ; + } +} + + +# Given a feature and its value or just a value corresponding to an implicit +# feature, returns a property set consisting of all component subfeatures and +# their values. For example all the following calls: +# +# expand-subfeatures-aux <toolset>gcc-2.95.2-linux-x86 +# expand-subfeatures-aux gcc-2.95.2-linux-x86 +# +# return: +# +# <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86 +# +local rule expand-subfeatures-aux ( + feature ? # Feature name or empty if value corresponds to an + # implicit property. + : value # Feature value. + : dont-validate ? # If set, no value string validation will be done. +) +{ + if $(feature) + { + feature = $(feature) ; + } + + if ! $(feature) + { + feature = [ implied-feature $(value) ] ; + } + else + { + validate-feature $(feature) ; + } + if ! $(dont-validate) + { + validate-value-string $(feature) $(value) ; + } + + local components = [ regex.split $(value) "-" ] ; + + # Get the top-level feature's value. + local value = $(components[1]:G=) ; + + local result = $(components[1]:G=$(feature)) ; + + local subvalues = $(components[2-]) ; + while $(subvalues) + { + local subvalue = $(subvalues[1]) ; # Pop the head off of subvalues. + subvalues = $(subvalues[2-]) ; + + local subfeature = [ find-implied-subfeature $(feature) $(subvalue) : + $(value) ] ; + + # If no subfeature was found reconstitute the value string and use that. + if ! $(subfeature) + { + result = $(components:J=-) ; + result = $(result:G=$(feature)) ; + subvalues = ; # Stop looping. + } + else + { + local f = [ MATCH ^<(.*)>$ : $(feature) ] ; + result += $(subvalue:G=$(f)-$(subfeature)) ; + } + } + + return $(result) ; +} + + +# Make all elements of properties corresponding to implicit features explicit, +# and express all subfeature values as separate properties in their own right. +# For example, all of the following properties +# +# gcc-2.95.2-linux-x86 +# <toolset>gcc-2.95.2-linux-x86 +# +# might expand to +# +# <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86 +# +rule expand-subfeatures ( + properties * # Property set with elements of the form + # <feature>value-string or just value-string in the case + # of implicit features. + : dont-validate ? +) +{ + local result ; + for local p in $(properties) + { + # Don't expand subfeatures in subfeatures + if ! [ MATCH "(:)" : $(p:G) ] + { + result += [ expand-subfeatures-aux $(p:G) : $(p:G=) : $(dont-validate) ] ; + } + else + { + result += $(p) ; + } + } + return $(result) ; +} + + +# Helper for extend, below. Handles the feature case. +# +local rule extend-feature ( feature : values * ) +{ + feature = [ grist $(feature) ] ; + validate-feature $(feature) ; + if implicit in $($(feature).attributes) + { + for local v in $(values) + { + if $($(v).implicit-feature) + { + errors.error $(v) is already associated with the \"$($(v).implicit-feature)\" feature ; + } + $(v).implicit-feature = $(feature) ; + } + + .all-implicit-values += $(values) ; + } + if ! $($(feature).values) + { + # This is the first value specified for this feature so make it be the + # default. + $(feature).default = $(values[1]) ; + } + $(feature).values += $(values) ; +} + + +# Checks that value-string is a valid value-string for the given feature. +# +rule validate-value-string ( feature value-string ) +{ + if ! ( + free in $($(feature).attributes) + || ( $(value-string) in $(feature).values ) + ) + { + local values = $(value-string) ; + + if $($(feature).subfeatures) + { + if ! ( $(value-string) in $($(feature).values) ) + && ! ( $(value-string) in $($(feature).subfeatures) ) + { + values = [ regex.split $(value-string) - ] ; + } + } + + if ! ( $(values[1]) in $($(feature).values) ) && + + # An empty value is allowed for optional features. + ( $(values[1]) || ! ( optional in $($(feature).attributes) ) ) + { + errors.error \"$(values[1])\" is not a known value of feature $(feature) + : legal values: \"$($(feature).values)\" ; + } + + for local v in $(values[2-]) + { + # This will validate any subfeature values in value-string. + implied-subfeature $(feature) $(v) : $(values[1]) ; + } + } +} + + +# A helper that computes: +# * name(s) of module-local variable(s) used to record the correspondence +# between subvalue(s) and a subfeature +# * value of that variable when such a subfeature/subvalue has been defined and +# returns a list consisting of the latter followed by the former. +# +local rule subvalue-var ( + feature # Main feature name. + value-string ? # If supplied, specifies a specific value of the main + # feature for which the subfeature values are valid. + : subfeature # Subfeature name. + : subvalues * # Subfeature values. +) +{ + feature = [ grist $(feature) ] ; + validate-feature $(feature) ; + if $(value-string) + { + validate-value-string $(feature) $(value-string) ; + } + + local subfeature-name = [ get-subfeature-name $(subfeature) $(value-string) ] ; + + return $(subfeature-name) + $(feature)$(value-string:E="")<>$(subvalues).subfeature ; +} + + +# 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 ; +# +rule extend-subfeature ( + 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 # Subfeature name. + : subvalues * # Additional subfeature values. +) +{ + local subfeature-vars = [ subvalue-var $(feature) $(value-string) + : $(subfeature) : $(subvalues) ] ; + + local f = [ utility.ungrist [ grist $(feature) ] ] ; + extend $(f)-$(subfeature-vars[1]) : $(subvalues) ; + + # Provide a way to get from the given feature or property and subfeature + # value to the subfeature name. + $(subfeature-vars[2-]) = $(subfeature-vars[1]) ; +} + + +# Returns true iff the subvalues are 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. +# +rule is-subvalue ( feature : value-string ? : subfeature : subvalue ) +{ + local subfeature-vars = [ subvalue-var $(feature) $(value-string) + : $(subfeature) : $(subvalue) ] ; + + if $($(subfeature-vars[2])) = $(subfeature-vars[1]) + { + return true ; + } +} + + +# 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 * ) +{ + local feature ; # If a property was specified this is its feature. + local value-string ; # E.g., the gcc-2.95-2 part of <toolset>gcc-2.95.2. + + # If a property was specified. + if $(feature-or-property:G) && $(feature-or-property:G=) + { + # Extract the feature and value-string, if any. + feature = $(feature-or-property:G) ; + value-string = $(feature-or-property:G=) ; + } + else + { + feature = [ grist $(feature-or-property) ] ; + } + + # Dispatch to the appropriate handler. + if $(subfeature) + { + extend-subfeature $(feature) $(value-string) : $(subfeature) + : $(values) ; + } + else + { + # If no subfeature was specified, we do not expect to see a + # value-string. + if $(value-string) + { + errors.error can only specify a property as the first argument when + extending a subfeature + : usage: + : " extend" feature ":" values... + : " | extend" <feature>value-string subfeature ":" values... + ; + } + + extend-feature $(feature) : $(values) ; + } +} + + +local rule get-subfeature-name ( subfeature value-string ? ) +{ + local prefix = $(value-string): ; + return $(prefix:E="")$(subfeature) ; +} + + +# Declares a subfeature. +# +rule subfeature ( + feature # Root feature that is not a subfeature. + value-string ? # A 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. +) +{ + feature = [ grist $(feature) ] ; + validate-feature $(feature) ; + + # Add grist to the subfeature name if a value-string was supplied. + local subfeature-name = [ get-subfeature-name $(subfeature) $(value-string) ] ; + + if $(subfeature-name) in $($(feature).subfeatures) + { + errors.error \"$(subfeature)\" already declared as a subfeature of \"$(feature)\" + "specific to "$(value-string) ; + } + $(feature).subfeatures += $(subfeature-name) ; + + # First declare the subfeature as a feature in its own right. + local f = [ utility.ungrist $(feature) ] ; + feature $(f)-$(subfeature-name) : $(subvalues) : $(attributes) subfeature ; + + # Now make sure the subfeature values are known. + extend-subfeature $(feature) $(value-string) : $(subfeature) : $(subvalues) ; +} + + +# Set components of the given composite property. +# +rule compose ( composite-property : component-properties * ) +{ + local feature = $(composite-property:G) ; + if ! ( composite in [ attributes $(feature) ] ) + { + errors.error "$(feature)" is not a composite feature ; + } + + $(composite-property).components ?= ; + if $($(composite-property).components) + { + errors.error components of "$(composite-property)" already set: + $($(composite-property).components) ; + } + + if $(composite-property) in $(component-properties) + { + errors.error composite property "$(composite-property)" cannot have itself as a component ; + } + $(composite-property).components = $(component-properties) ; +} + + +local rule expand-composite ( property ) +{ + return $(property) + [ sequence.transform expand-composite : $($(property).components) ] ; +} + + +# Return all values of the given feature specified by the given property set. +# +rule get-values ( feature : properties * ) +{ + local result ; + + feature = $(:E=:G=$(feature)) ; # Add <> if necessary. + for local p in $(properties) + { + if $(p:G) = $(feature) + { + # Use MATCH instead of :G= to get the value, in order to preserve + # the value intact instead of having bjam treat it as a decomposable + # path. + result += [ MATCH ">(.*)" : $(p) ] ; + } + } + return $(result) ; +} + + +rule free-features ( ) +{ + return $(free.features) ; +} + + +# Expand all composite properties in the set so that all components are +# explicitly expressed. +# +rule expand-composites ( properties * ) +{ + local explicit-features = $(properties:G) ; + local result ; + + # Now expand composite features. + for local p in $(properties) + { + local expanded = [ expand-composite $(p) ] ; + + for local x in $(expanded) + { + if ! $(x) in $(result) + { + local f = $(x:G) ; + + if $(f) in $(free.features) + { + result += $(x) ; + } + else if ! $(x) in $(properties) # x is the result of expansion + { + if ! $(f) in $(explicit-features) # not explicitly-specified + { + if $(f) in $(result:G) + { + errors.error expansions of composite features result + in conflicting values for $(f) + : values: [ get-values $(f) : $(result) ] $(x:G=) + : one contributing composite property was $(p) ; + } + else + { + result += $(x) ; + } + } + } + else if $(f) in $(result:G) + { + errors.error explicitly-specified values of non-free feature + $(f) conflict : + "existing values:" [ get-values $(f) : $(properties) ] : + "value from expanding " $(p) ":" $(x:G=) ; + } + else + { + result += $(x) ; + } + } + } + } + return $(result) ; +} + + +# 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. +# +local rule is-subfeature-of ( parent-property f ) +{ + if subfeature in $($(f).attributes) + { + local specific-subfeature = [ MATCH <(.*):(.*)> : $(f) ] ; + if $(specific-subfeature) + { + # The feature has the form <topfeature-topvalue:subfeature>, e.g. + # <toolset-msvc:version>. + local feature-value = [ split-top-feature $(specific-subfeature[1]) + ] ; + if <$(feature-value[1])>$(feature-value[2]) = $(parent-property) + { + return true ; + } + } + else + { + # The feature has the form <topfeature-subfeature>, e.g. + # <toolset-version> + local top-sub = [ split-top-feature [ utility.ungrist $(f) ] ] ; + if $(top-sub[2]) && <$(top-sub[1])> = $(parent-property:G) + { + return true ; + } + } + } +} + + +# As for is-subfeature-of but for subproperties. +# +local rule is-subproperty-of ( parent-property p ) +{ + return [ is-subfeature-of $(parent-property) $(p:G) ] ; +} + + +# 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. +# +local rule select-subfeatures ( parent-property : features * ) +{ + return [ sequence.filter is-subfeature-of $(parent-property) : $(features) ] ; +} + + +# As for select-subfeatures but for subproperties. +# +local rule select-subproperties ( parent-property : properties * ) +{ + return [ sequence.filter is-subproperty-of $(parent-property) : $(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. +# +rule expand ( properties * ) +{ + local expanded = [ expand-subfeatures $(properties) ] ; + return [ expand-composites $(expanded) ] ; +} + + +# Helper rule for minimize. Returns true iff property's feature is present in +# the contents of the variable named by feature-set-var. +# +local rule in-features ( feature-set-var property ) +{ + if $(property:G) in $($(feature-set-var)) + { + return true ; + } +} + + +# Helper rule for minimize. Returns the list with the same properties, but with +# all subfeatures moved to the end of the list. +# +local rule move-subfeatures-to-the-end ( properties * ) +{ + local x1 ; + local x2 ; + for local p in $(properties) + { + if subfeature in $($(p:G).attributes) + { + x2 += $(p) ; + } + else + { + x1 += $(p) ; + } + } + return $(x1) $(x2) ; +} + + +# Given an expanded property set, eliminate all redundancy: properties that are +# elements of other (composite) properties in the set will be eliminated. +# Non-symmetric properties equal to default values will be eliminated unless +# they 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. +# +rule minimize ( properties * ) +{ + # Precondition checking + local implicits = [ set.intersection $(p:G=) : $(p:G) ] ; + if $(implicits) + { + errors.error minimize requires an expanded property set, but + \"$(implicits[1])\" appears to be the value of an un-expanded + implicit feature ; + } + + # Remove properties implied by composite features. + local components = $($(properties).components) ; + local x = [ set.difference $(properties) : $(components) ] ; + + # Handle subfeatures and implicit features. + x = [ move-subfeatures-to-the-end $(x) ] ; + local result ; + while $(x) + { + local p fullp = $(x[1]) ; + local f = $(p:G) ; + local v = $(p:G=) ; + + # Eliminate features in implicit properties. + if implicit in [ attributes $(f) ] + { + p = $(v) ; + } + + # Locate all subproperties of $(x[1]) in the property set. + local subproperties = [ select-subproperties $(fullp) : $(x) ] ; + if $(subproperties) + { + # Reconstitute the joined property name. + local sorted = [ sequence.insertion-sort $(subproperties) ] ; + result += $(p)-$(sorted:G="":J=-) ; + + x = [ set.difference $(x[2-]) : $(subproperties) ] ; + } + else + { + # Eliminate properties whose value is equal to feature's default, + # 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 $(fullp) != [ defaults $(f) ] + || symmetric in [ attributes $(f) ] + || $(fullp:G) in $(components:G) + { + result += $(p) ; + } + + x = $(x[2-]) ; + } + } + return $(result) ; +} + + +# Combine all subproperties into their parent properties +# +# Requires: for every subproperty, there is a parent property. All features are +# explicitly expressed. +# +# This rule probably should not be needed, but build-request.expand-no-defaults +# is being abused for unintended purposes and it needs help. +# +rule compress-subproperties ( properties * ) +{ + local all-subs ; + local matched-subs ; + local result ; + + for local p in $(properties) + { + if ! $(p:G) + { + # Expecting fully-gristed properties. + assert.variable-not-empty p:G ; + } + + if ! subfeature in $($(p:G).attributes) + { + local subs = [ sequence.insertion-sort + [ sequence.filter is-subproperty-of $(p) : $(properties) ] ] ; + + matched-subs += $(subs) ; + + local subvalues = -$(subs:G=:J=-) ; + subvalues ?= "" ; + result += $(p)$(subvalues) ; + } + else + { + all-subs += $(p) ; + } + } + assert.result true : set.equal $(all-subs) : $(matched-subs) ; + return $(result) ; +} + + +# Given an ungristed string, finds the longest prefix which is a top-level +# feature name followed by a dash, and return a pair consisting of the parts +# before and after that dash. More interesting than a simple split because +# feature names may contain dashes. +# +local rule split-top-feature ( feature-plus ) +{ + local e = [ regex.split $(feature-plus) - ] ; + local f = $(e[1]) ; + local v ; + while $(e) + { + if <$(f)> in $(.all-top-features) + { + v = $(f) $(e[2-]:J=-) ; + } + e = $(e[2-]) ; + f = $(f)-$(e[1]) ; + } + return $(v) ; +} + + +# Given a set of properties, add default values for features not represented in +# the set. +# +# Note: if there's an ordinary feature F1 and a composite feature F2 which +# includes some value for F1 and both feature have default values then the +# default value of F1 will be added (as opposed to the value in F2). This might +# not be the right idea, e.g. 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. +# +rule add-defaults ( properties * ) +{ + for local v in $(properties:G=) + { + if $(v) in $(properties) + { + errors.error add-defaults requires explicitly specified features, + but \"$(v)\" appears to be the value of an un-expanded implicit + feature ; + } + } + # We don't add default for elements with ":" inside. This catches: + # 1. Conditional properties --- we don't want <variant>debug:<define>DEBUG + # to be takes as specified value for <variant> + # 2. Free properties with ":" in values. We don't care, since free + # properties don't have defaults. + local xproperties = [ MATCH "^([^:]+)$" : $(properties) ] ; + local missing-top = [ set.difference $(.all-top-features) : $(xproperties:G) ] ; + local more = [ defaults $(missing-top) ] ; + properties += $(more) ; + xproperties += $(more) ; + + # Add defaults for subfeatures of features which are present. + for local p in $(xproperties) + { + local s = $($(p:G).subfeatures) ; + local f = [ utility.ungrist $(p:G) ] ; + local missing-subs = [ set.difference <$(f)-$(s)> : $(properties:G) ] ; + properties += [ defaults [ select-subfeatures $(p) : $(missing-subs) ] ] ; + } + + return $(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 needs to be resilient to the +# substitution of backslashes for slashes, since Jam, unbidden, sometimes swaps +# slash direction on NT. +# +rule split ( property-set ) +{ + local pieces = [ regex.split $(property-set) [\\/] ] ; + local result ; + + for local x in $(pieces) + { + if ( ! $(x:G) ) && $(result[-1]:G) + { + result = $(result[1--2]) $(result[-1])/$(x) ; + } + else + { + result += $(x) ; + } + } + + return $(result) ; +} + + +# Tests of module feature. +# +rule __test__ ( ) +{ + # Use a fresh copy of the feature module. + prepare-test feature-test-temp ; + + import assert ; + import errors : try catch ; + + # These are local rules and so must be explicitly reimported into the + # testing module. + import feature : extend-feature validate-feature select-subfeatures ; + + feature toolset : gcc : implicit ; + feature define : : free ; + feature runtime-link : dynamic static : symmetric ; + feature optimization : on off ; + feature variant : debug release profile : implicit composite symmetric ; + feature stdlib : native stlport ; + feature magic : : free ; + + compose <variant>debug : <define>_DEBUG <optimization>off ; + compose <variant>release : <define>NDEBUG <optimization>on ; + + assert.result dynamic static : values <runtime-link> ; + assert.result dynamic static : values runtime-link ; + + try ; + { + compose <variant>profile : <variant>profile ; + } + catch composite property <variant>profile cannot have itself as a component ; + + extend-feature toolset : msvc metrowerks ; + subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4 3.0 3.0.1 3.0.2 ; + + assert.true is-subvalue toolset : gcc : version : 2.95.3 ; + assert.false is-subvalue toolset : gcc : version : 1.1 ; + + assert.false is-subvalue toolset : msvc : version : 2.95.3 ; + assert.false is-subvalue toolset : : version : yabba ; + + feature yabba ; + subfeature yabba : version : dabba ; + assert.true is-subvalue yabba : : version : dabba ; + + subfeature toolset gcc : platform : linux cygwin : optional ; + + assert.result <toolset-gcc:version> + : select-subfeatures <toolset>gcc + : <toolset-gcc:version> + <toolset-msvc:version> + <toolset-version> + <stdlib> ; + + subfeature stdlib : version : 3 4 : optional ; + + assert.result <stdlib-version> + : select-subfeatures <stdlib>native + : <toolset-gcc:version> + <toolset-msvc:version> + <toolset-version> + <stdlib-version> ; + + assert.result <toolset>gcc <toolset-gcc:version>3.0.1 + : expand-subfeatures <toolset>gcc-3.0.1 ; + + assert.result <toolset>gcc <toolset-gcc:version>3.0.1 <toolset-gcc:platform>linux + : expand-subfeatures <toolset>gcc-3.0.1-linux ; + + assert.result <toolset>gcc <toolset-gcc:version>3.0.1 + : expand <toolset>gcc <toolset-gcc:version>3.0.1 ; + + assert.result <define>foo=x-y + : expand-subfeatures <define>foo=x-y ; + + assert.result <toolset>gcc <toolset-gcc:version>3.0.1 + : expand-subfeatures gcc-3.0.1 ; + + assert.result a c e + : get-values <x> : <x>a <y>b <x>c <y>d <x>e ; + + assert.result <toolset>gcc <toolset-gcc:version>3.0.1 + <variant>debug <define>_DEBUG <optimization>on + : expand gcc-3.0.1 debug <optimization>on ; + + assert.result <variant>debug <define>_DEBUG <optimization>on + : expand debug <optimization>on ; + + assert.result <optimization>on <variant>debug <define>_DEBUG + : expand <optimization>on debug ; + + assert.result <runtime-link>dynamic <optimization>on + : defaults <runtime-link> <define> <optimization> ; + + # Make sure defaults is resilient to missing grist. + assert.result <runtime-link>dynamic <optimization>on + : defaults runtime-link define optimization ; + + feature dummy : dummy1 dummy2 ; + subfeature dummy : subdummy : x y z : optional ; + + feature fu : fu1 fu2 : optional ; + subfeature fu : subfu : x y z : optional ; + subfeature fu : subfu2 : q r s ; + + assert.result optional : attributes <fu> ; + assert.result optional : attributes fu ; + + assert.result <runtime-link>static <define>foobar <optimization>on + <toolset>gcc:<define>FOO <toolset>gcc <variant>debug <stdlib>native + <dummy>dummy1 <toolset-gcc:version>2.95.2 + : add-defaults <runtime-link>static <define>foobar <optimization>on + <toolset>gcc:<define>FOO ; + + assert.result <runtime-link>static <define>foobar <optimization>on + <toolset>gcc:<define>FOO <fu>fu1 <toolset>gcc <variant>debug + <stdlib>native <dummy>dummy1 <fu-subfu2>q <toolset-gcc:version>2.95.2 + : add-defaults <runtime-link>static <define>foobar <optimization>on + <toolset>gcc:<define>FOO <fu>fu1 ; + + set-default <runtime-link> : static ; + assert.result <runtime-link>static : defaults <runtime-link> ; + + assert.result gcc-3.0.1 debug <optimization>on + : minimize [ expand gcc-3.0.1 debug <optimization>on <stdlib>native ] ; + + assert.result gcc-3.0.1 debug <runtime-link>dynamic + : minimize + [ expand gcc-3.0.1 debug <optimization>off <runtime-link>dynamic ] ; + + assert.result gcc-3.0.1 debug + : minimize [ expand gcc-3.0.1 debug <optimization>off ] ; + + assert.result debug <optimization>on + : minimize [ expand debug <optimization>on ] ; + + assert.result gcc-3.0 + : minimize <toolset>gcc <toolset-gcc:version>3.0 ; + + assert.result gcc-3.0 + : minimize <toolset-gcc:version>3.0 <toolset>gcc ; + + assert.result <x>y/z <a>b/c <d>e/f + : split <x>y/z/<a>b/c/<d>e/f ; + + assert.result <x>y/z <a>b/c <d>e/f + : split <x>y\\z\\<a>b\\c\\<d>e\\f ; + + assert.result a b c <d>e/f/g <h>i/j/k + : split a/b/c/<d>e/f/g/<h>i/j/k ; + + assert.result a b c <d>e/f/g <h>i/j/k + : split a\\b\\c\\<d>e\\f\\g\\<h>i\\j\\k ; + + # Test error checking. + + try ; + { + expand release <optimization>off <optimization>on ; + } + catch explicitly-specified values of non-free feature <optimization> conflict ; + + try ; + { + validate-feature <foobar> ; + } + catch unknown feature ; + + validate-value-string <toolset> gcc ; + validate-value-string <toolset> gcc-3.0.1 ; + + try ; + { + validate-value-string <toolset> digital_mars ; + } + catch \"digital_mars\" is not a known value of <toolset> ; + + try ; + { + feature foobar : : baz ; + } + catch unknown attributes: baz ; + + feature feature1 ; + try ; + { + feature feature1 ; + } + catch feature already defined: ; + + try ; + { + feature feature2 : : free implicit ; + } + catch free features cannot also be implicit ; + + try ; + { + feature feature3 : : free propagated ; + } + catch free features cannot be propagated ; + + try ; + { + implied-feature lackluster ; + } + catch \"lackluster\" is not a value of an implicit feature ; + + try ; + { + implied-subfeature <toolset> 3.0.1 ; + } + catch \"3.0.1\" is not a known subfeature value of <toolset> ; + + try ; + { + implied-subfeature <toolset> not-a-version : gcc ; + } + catch \"not-a-version\" is not a known subfeature value of <toolset>gcc ; + + # Leave a clean copy of the features module behind. + finish-test feature-test-temp ; +} diff --git a/jam-files/boost-build/build/feature.py b/jam-files/boost-build/build/feature.py new file mode 100644 index 00000000..315a18e9 --- /dev/null +++ b/jam-files/boost-build/build/feature.py @@ -0,0 +1,905 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +import re + +from b2.util import utility, bjam_signature +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'[\\/]') + +class Feature(object): + + # Map from string attribute names to integers bit flags. + # This will be initialized after declaration of the class. + _attribute_name_to_integer = {} + + def __init__(self, name, values, attributes): + self._name = name + self._values = values + self._default = None + self._attributes = 0 + for a in attributes: + self._attributes = self._attributes | Feature._attribute_name_to_integer[a] + self._attributes_string_list = attributes + self._subfeatures = [] + self._parent = None + + def name(self): + return self._name + + def values(self): + return self._values + + def add_values(self, values): + self._values.extend(values) + + def attributes(self): + return self._attributes + + def set_default(self, value): + self._default = value + + def default(self): + return self._default + + # FIXME: remove when we fully move to using classes for features/properties + def attributes_string_list(self): + return self._attributes_string_list + + def subfeatures(self): + return self._subfeatures + + def add_subfeature(self, name): + self._subfeatures.append(name) + + def parent(self): + """For subfeatures, return pair of (parent_feature, value). + + Value may be None if this subfeature is not specific to any + value of the parent feature. + """ + return self._parent + + def set_parent(self, feature, value): + self._parent = (feature, value) + + 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 __features_with_attributes, __subfeature_from_value, __all_top_features, __free_features + global __all_subfeatures + + # The list with all attribute names. + __all_attributes = [ 'implicit', + 'composite', + 'optional', + 'symmetric', + 'free', + 'incidental', + 'path', + 'dependency', + 'propagated', + 'link-incompatible', + 'subfeature', + 'order-sensitive' + ] + i = 1 + for a in __all_attributes: + setattr(Feature, a.upper(), i) + Feature._attribute_name_to_integer[a] = i + def probe(self, flag=i): + return getattr(self, "_attributes") & flag + setattr(Feature, a.replace("-", "_"), probe) + i = i << 1 + + # 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 = {} + + __features_with_attributes = {} + for attribute in __all_attributes: + __features_with_attributes [attribute] = [] + + # 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 + """ + 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 + + for attribute in attributes: + __features_with_attributes [attribute].append (name) + + 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] + attributes = f.attributes() + bad_attribute = None + + if attributes & Feature.FREE: + bad_attribute = "free" + elif attributes & Feature.OPTIONAL: + bad_attribute = "optional" + + if bad_attribute: + raise InvalidValue ("%s property %s cannot have a default" % (bad_attribute, feature.name())) + + if not value in f.values(): + raise InvalidValue ("The specified default value, '%s' is invalid.\n" % value + "allowed values are: %s" % values) + + f.set_default(value) + +def defaults(features): + """ Returns the default property values for the given features. + """ + # FIXME: should merge feature and property modules. + 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. + """ + def valid_one (name): return __all_features.has_key (name) + + if isinstance (names, str): + return valid_one (names) + else: + return [ valid_one (name) for name in names ] + +def attributes (feature): + """ Returns the attributes of the given feature. + """ + return __all_features[feature].attributes_string_list() + +def values (feature): + """ Return the values of the given feature. + """ + 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. + """ + + if __implicit_features.has_key(value_string): + return __implicit_features[value_string] + + v = value_string.split('-') + + if not __implicit_features.has_key(v[0]): + 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. + """ + components = implicit_value.split('-') + + if not __implicit_features.has_key(components[0]): + 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): + + #if value_string == None: value_string = '' + + if not __subfeature_from_value.has_key(feature) \ + or not __subfeature_from_value[feature].has_key(value_string) \ + or not __subfeature_from_value[feature][value_string].has_key (subvalue): + return None + + return __subfeature_from_value[feature][value_string][subvalue] + +# 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): + 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. + """ + if not __all_features.has_key(name): + raise InvalidFeature ("'%s' is not a valid feature name" % name) + else: + return __all_features[name] + +def valid (names): + """ Returns true iff all elements of names are valid features. + """ + def valid_one (name): return __all_features.has_key (name) + + if isinstance (names, str): + return valid_one (names) + else: + return [ valid_one (name) for name in names ] + +# 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. + """ + f = property.feature() + v = property.value() + if not dont_validate: + validate_value_string(f, v) + + components = v.split ("-") + + v = components[0] + + import property + + 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. + """ + 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 + +def extend (name, values): + """ Adds the given values to the given feature. + """ + name = add_grist (name) + __validate_feature (name) + feature = __all_features [name] + + if feature.implicit(): + for v in values: + if __implicit_features.has_key(v): + raise BaseException ("'%s' is already associated with the feature '%s'" % (v, __implicit_features [v])) + + __implicit_features[v] = feature + + if len (feature.values()) == 0 and len (values) > 0: + # 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. + """ + 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], feature, 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 specifc 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): + + 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 not __subfeature_from_value.has_key(feature): + __subfeature_from_value [feature] = {} + + if not __subfeature_from_value[feature].has_key(value_string): + __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 paremeters are <feature>value strings + """ + 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 __composite_properties.has_key(property): + 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): + result = [ property ] + if __composite_properties.has_key(property): + for p in __composite_properties[property]: + result.extend(expand_composite(p)) + return result + + +def get_values (feature, properties): + """ Returns all values of the given feature specified by the given property set. + """ + 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. + """ + 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 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. + """ + 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): + + if not value_string: + value_string = '' + + if not __subfeature_from_value.has_key(feature): + return False + + if not __subfeature_from_value[feature].has_key(value_string): + return False + + if not __subfeature_from_value[feature][value_string].has_key(subvalue): + return False + + if __subfeature_from_value[feature][value_string][subvalue]\ + != subfeature: + return False + + return True + +def implied_subfeature (feature, subvalue, value_string): + 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 + + +# 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. + """ + 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. + """ + result = [x for x in properties] + + handled_features = set() + for p in 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> + if not p.condition(): + handled_features.add(p.feature()) + + missing_top = [f for f in __all_top_features if not f in handled_features] + more = defaults(missing_top) + result.extend(more) + for p in more: + handled_features.add(p.feature()) + + # Add defaults for subfeatures of features which are present + for p in result[:]: + s = p.feature().subfeatures() + more = defaults([s for s in p.feature().subfeatures() if not s in handled_features]) + for p in more: + handled_features.add(p.feature()) + 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. + """ + + # remove properties implied by composite features + components = [] + for property in properties: + if __composite_properties.has_key (property): + components.extend(__composite_properties[property]) + 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 = __select_subproperties (p, properties) + + 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(): + result.append (p) + #\ + #or get_grist (fullp) in get_grist (components): + # FIXME: restore above + + + 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. + """ + + 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 + """ + result = [] + matched_subs = set() + all_subs = set() + for p in properties: + f = p.feature() + + if not f.subfeature(): + subs = __select_subproperties (p, properties) + if subs: + + matched_subs.update(subs) + + subvalues = '-'.join (sub.value() for sub in subs) + result.append(b2.build.property.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): + return [ x for x in properties if __is_subproperty_of (parent_property, x) ] + +def __get_subfeature_name (subfeature, value_string): + if value_string == None: + prefix = '' + else: + prefix = value_string + ':' + + return prefix + subfeature + + +def __validate_feature_attributes (name, attributes): + for attribute in attributes: + if not attribute in __all_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. + """ + if not __all_features.has_key (feature): + 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. + """ + return [f for f in features if is_subfeature_of (parent_property, f)] + +# FIXME: copy over tests. diff --git a/jam-files/boost-build/build/generators.jam b/jam-files/boost-build/build/generators.jam new file mode 100644 index 00000000..1515525f --- /dev/null +++ b/jam-files/boost-build/build/generators.jam @@ -0,0 +1,1408 @@ +# Copyright Vladimir Prus 2002. +# Copyright Rene Rivera 2006. +# +# 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) + +# Manages 'generators' --- objects which can do transformation between different +# target types and contain algorithm for finding transformation from sources to +# targets. +# +# The main entry point to this module is generators.construct rule. It is given +# a list of source targets, desired target type and a set of properties. It +# starts by selecting 'viable generators', which have any chances of producing +# the desired target type with the required properties. Generators are ranked +# and a set of the most specific ones is selected. +# +# The most specific generators have their 'run' methods called, with the +# properties and list of sources. Each one selects a target which can be +# directly consumed, and tries to convert the remaining ones to the types it can +# consume. This is done by recursively calling 'construct' with all consumable +# types. +# +# If the generator has collected all the targets it needs, it creates targets +# corresponding to result, and returns it. When all generators have been run, +# results of one of them are selected and returned as a result. +# +# It is quite possible for 'construct' to return more targets that it was asked +# for. For example, if it were asked to generate a target of type EXE, but the +# only found generator produces both EXE and TDS (file with debug) information. +# The extra target will be returned. +# +# Likewise, when generator tries to convert sources to consumable types, it can +# get more targets that it was asked for. The question is what to do with extra +# targets. Boost.Build attempts to convert them to requested types, and attempts +# that as early as possible. Specifically, this is done after invoking each +# generator. TODO: An example is needed to document the rationale for trying +# extra target conversion at that point. +# +# In order for the system to be able to use a specific generator instance 'when +# needed', the instance needs to be registered with the system using +# generators.register() or one of its related rules. Unregistered generators may +# only be run explicitly and will not be considered by Boost.Build when when +# converting between given target types. + +import "class" : new ; +import errors ; +import property-set ; +import sequence ; +import set ; +import type ; +import utility ; +import virtual-target ; + + +if "--debug-generators" in [ modules.peek : ARGV ] +{ + .debug = true ; +} + + +# Updated cached viable source target type information as needed after a new +# target type gets defined. This is needed because if a target type is a viable +# source target type for some generator then all of the target type's derived +# target types should automatically be considered as viable source target types +# for the same generator as well. Does nothing if a non-derived target type is +# passed to it. +# +rule update-cached-information-with-a-new-type ( type ) +{ + local base-type = [ type.base $(type) ] ; + if $(base-type) + { + for local g in $(.vstg-cached-generators) + { + if $(base-type) in $(.vstg.$(g)) + { + .vstg.$(g) += $(type) ; + } + } + + for local t in $(.vst-cached-types) + { + if $(base-type) in $(.vst.$(t)) + { + .vst.$(t) += $(type) ; + } + } + } +} + + +# Clears cached viable source target type information except for target types +# and generators with all source types listed as viable. Should be called when +# something invalidates those cached values by possibly causing some new source +# types to become viable. +# +local rule invalidate-extendable-viable-source-target-type-cache ( ) +{ + local generators-with-cached-source-types = $(.vstg-cached-generators) ; + .vstg-cached-generators = ; + for local g in $(generators-with-cached-source-types) + { + if $(.vstg.$(g)) = * + { + .vstg-cached-generators += $(g) ; + } + else + { + .vstg.$(g) = ; + } + } + + local types-with-cached-source-types = $(.vst-cached-types) ; + .vst-cached-types = ; + for local t in $(types-with-cached-source-types) + { + if $(.vst.$(t)) = * + { + .vst-cached-types += $(t) ; + } + else + { + .vst.$(t) = ; + } + } +} + + +# Outputs a debug message if generators debugging is on. Each element of +# 'message' is checked to see if it is a class instance. If so, instead of the +# value, the result of 'str' call is output. +# +local rule generators.dout ( message * ) +{ + if $(.debug) + { + ECHO [ sequence.transform utility.str : $(message) ] ; + } +} + + +local rule indent ( ) +{ + return $(.indent:J="") ; +} + + +local rule increase-indent ( ) +{ + .indent += " " ; +} + + +local rule decrease-indent ( ) +{ + .indent = $(.indent[2-]) ; +} + + +# Models a generator. +# +class generator +{ + import generators : indent increase-indent decrease-indent generators.dout ; + import set ; + import utility ; + import feature ; + import errors ; + import sequence ; + import type ; + import virtual-target ; + import "class" : new ; + import property ; + import path ; + + EXPORT class@generator : indent increase-indent decrease-indent + generators.dout ; + + rule __init__ ( + id # Identifies the generator - should be name + # of the rule which sets up the build + # actions. + + composing ? # Whether generator processes each source + # target in turn, converting it to required + # types. Ordinary generators pass all + # sources together to the recursive + # generators.construct-types call. + + : source-types * # Types that this generator can handle. If + # empty, the generator can consume anything. + + : target-types-and-names + # Types the generator will create and, + # optionally, names for created targets. + # Each element should have the form + # type["(" name-pattern ")"], for example, + # obj(%_x). Generated target name will be + # found by replacing % with the name of + # source, provided an explicit name was not + # specified. + + : requirements * + ) + { + self.id = $(id) ; + self.rule-name = $(id) ; + self.composing = $(composing) ; + self.source-types = $(source-types) ; + self.target-types-and-names = $(target-types-and-names) ; + self.requirements = $(requirements) ; + + for local e in $(target-types-and-names) + { + # Create three parallel lists: one with the list of target types, + # and two other with prefixes and postfixes to be added to target + # name. We use parallel lists for prefix and postfix (as opposed to + # mapping), because given target type might occur several times, for + # example "H H(%_symbols)". + local m = [ MATCH ([^\\(]*)(\\((.*)%(.*)\\))? : $(e) ] ; + self.target-types += $(m[1]) ; + self.name-prefix += $(m[3]:E="") ; + self.name-postfix += $(m[4]:E="") ; + } + + # Note that 'transform' here, is the same as 'for_each'. + sequence.transform type.validate : $(self.source-types) ; + sequence.transform type.validate : $(self.target-types) ; + } + + ################# End of constructor ################# + + rule id ( ) + { + return $(self.id) ; + } + + # Returns the list of target type the generator accepts. + # + rule source-types ( ) + { + return $(self.source-types) ; + } + + # Returns the list of target types that this generator produces. It is + # assumed to be always the same -- i.e. it can not change depending on some + # provided list of sources. + # + rule target-types ( ) + { + return $(self.target-types) ; + } + + # Returns the required properties for this generator. Properties in returned + # set must be present in build properties if this generator is to be used. + # If result has grist-only element, that build properties must include some + # value of that feature. + # + # XXX: remove this method? + # + rule requirements ( ) + { + return $(self.requirements) ; + } + + rule set-rule-name ( rule-name ) + { + self.rule-name = $(rule-name) ; + } + + rule rule-name ( ) + { + return $(self.rule-name) ; + } + + # Returns a true value if the generator can be run with the specified + # properties. + # + rule match-rank ( property-set-to-match ) + { + # See if generator requirements are satisfied by 'properties'. Treat a + # feature name in requirements (i.e. grist-only element), as matching + # any value of the feature. + local all-requirements = [ requirements ] ; + + local property-requirements feature-requirements ; + for local r in $(all-requirements) + { + if $(r:G=) + { + property-requirements += $(r) ; + } + else + { + feature-requirements += $(r) ; + } + } + + local properties-to-match = [ $(property-set-to-match).raw ] ; + if $(property-requirements) in $(properties-to-match) && + $(feature-requirements) in $(properties-to-match:G) + { + return true ; + } + else + { + return ; + } + } + + # Returns another generator which differs from $(self) in + # - id + # - value to <toolset> feature in properties + # + rule clone ( new-id : new-toolset-properties + ) + { + local g = [ new $(__class__) $(new-id) $(self.composing) : + $(self.source-types) : $(self.target-types-and-names) : + # Note: this does not remove any subfeatures of <toolset> which + # might cause problems. + [ property.change $(self.requirements) : <toolset> ] + $(new-toolset-properties) ] ; + return $(g) ; + } + + # Creates another generator that is the same as $(self), except that if + # 'base' is in target types of $(self), 'type' will in target types of the + # new generator. + # + rule clone-and-change-target-type ( base : type ) + { + local target-types ; + for local t in $(self.target-types-and-names) + { + local m = [ MATCH ([^\\(]*)(\\(.*\\))? : $(t) ] ; + if $(m) = $(base) + { + target-types += $(type)$(m[2]:E="") ; + } + else + { + target-types += $(t) ; + } + } + + local g = [ new $(__class__) $(self.id) $(self.composing) : + $(self.source-types) : $(target-types) : $(self.requirements) ] ; + if $(self.rule-name) + { + $(g).set-rule-name $(self.rule-name) ; + } + return $(g) ; + } + + # Tries to invoke this generator on the given sources. Returns a list of + # generated targets (instances of 'virtual-target') and optionally a set of + # properties to be added to the usage-requirements for all the generated + # targets. Returning nothing from run indicates that the generator was + # unable to create the target. + # + rule run + ( + project # Project for which the targets are generated. + name ? # Used when determining the 'name' attribute for all + # generated targets. See the 'generated-targets' method. + : property-set # Desired properties for generated targets. + : sources + # Source targets. + ) + { + generators.dout [ indent ] " ** generator" $(self.id) ; + generators.dout [ indent ] " composing:" $(self.composing) ; + + if ! $(self.composing) && $(sources[2]) && $(self.source-types[2]) + { + errors.error "Unsupported source/source-type combination" ; + } + + # We do not run composing generators if no name is specified. The reason + # is that composing generator combines several targets, which can have + # different names, and it cannot decide which name to give for produced + # target. Therefore, the name must be passed. + # + # This in effect, means that composing generators are runnable only at + # the top-level of a transformation graph, or if their name is passed + # explicitly. Thus, we dissallow composing generators in the middle. For + # example, the transformation CPP -> OBJ -> STATIC_LIB -> RSP -> EXE + # will not be allowed as the OBJ -> STATIC_LIB generator is composing. + if ! $(self.composing) || $(name) + { + run-really $(project) $(name) : $(property-set) : $(sources) ; + } + } + + rule run-really ( project name ? : property-set : sources + ) + { + # Targets that this generator will consume directly. + local consumed = ; + # Targets that can not be consumed and will be returned as-is. + local bypassed = ; + + if $(self.composing) + { + convert-multiple-sources-to-consumable-types $(project) + : $(property-set) : $(sources) : consumed bypassed ; + } + else + { + convert-to-consumable-types $(project) $(name) : $(property-set) + : $(sources) : : consumed bypassed ; + } + + local result ; + if $(consumed) + { + result = [ construct-result $(consumed) : $(project) $(name) : + $(property-set) ] ; + } + + if $(result) + { + generators.dout [ indent ] " SUCCESS: " $(result) ; + } + else + { + generators.dout [ indent ] " FAILURE" ; + } + generators.dout ; + return $(result) ; + } + + # Constructs the dependency graph to be returned by this generator. + # + rule construct-result + ( + consumed + # Already prepared list of consumable targets. + # Composing generators may receive multiple sources + # all of which will have types matching those in + # $(self.source-types). Non-composing generators with + # multiple $(self.source-types) will receive exactly + # len $(self.source-types) sources with types matching + # those in $(self.source-types). And non-composing + # generators with only a single source type may + # receive multiple sources with all of them of the + # type listed in $(self.source-types). + : project name ? + : property-set # Properties to be used for all actions created here. + ) + { + local result ; + # If this is 1->1 transformation, apply it to all consumed targets in + # order. + if ! $(self.source-types[2]) && ! $(self.composing) + { + for local r in $(consumed) + { + result += [ generated-targets $(r) : $(property-set) : + $(project) $(name) ] ; + } + } + else if $(consumed) + { + result += [ generated-targets $(consumed) : $(property-set) : + $(project) $(name) ] ; + } + return $(result) ; + } + + # Determine target name from fullname (maybe including path components) + # Place optional prefix and postfix around basename + # + rule determine-target-name ( fullname : prefix ? : postfix ? ) + { + # See if we need to add directory to the target name. + local dir = $(fullname:D) ; + local name = $(fullname:B) ; + + name = $(prefix:E=)$(name) ; + name = $(name)$(postfix:E=) ; + + if $(dir) && + # Never append '..' to target path. + ! [ MATCH .*(\\.\\.).* : $(dir) ] + && + ! [ path.is-rooted $(dir) ] + { + # Relative path is always relative to the source + # directory. Retain it, so that users can have files + # with the same in two different subdirectories. + name = $(dir)/$(name) ; + } + return $(name) ; + } + + # Determine the name of the produced target from the names of the sources. + # + rule determine-output-name ( sources + ) + { + # The simple case if when a name of source has single dot. Then, we take + # the part before dot. Several dots can be caused by: + # - using source file like a.host.cpp, or + # - a type whose suffix has a dot. Say, we can type 'host_cpp' with + # extension 'host.cpp'. + # In the first case, we want to take the part up to the last dot. In the + # second case -- not sure, but for now take the part up to the last dot + # too. + name = [ utility.basename [ $(sources[1]).name ] ] ; + + for local s in $(sources[2]) + { + local n2 = [ utility.basename [ $(s).name ] ] ; + if $(n2) != $(name) + { + errors.error "$(self.id): source targets have different names: cannot determine target name" ; + } + } + name = [ determine-target-name [ $(sources[1]).name ] ] ; + return $(name) ; + } + + # Constructs targets that are created after consuming 'sources'. The result + # will be the list of virtual-target, which has the same length as the + # 'target-types' attribute and with corresponding types. + # + # When 'name' is empty, all source targets must have the same 'name' + # attribute value, which will be used instead of the 'name' argument. + # + # The 'name' attribute value for each generated target will be equal to + # the 'name' parameter if there is no name pattern for this type. Otherwise, + # the '%' symbol in the name pattern will be replaced with the 'name' + # parameter to obtain the 'name' attribute. + # + # For example, if targets types are T1 and T2 (with name pattern "%_x"), + # suffixes for T1 and T2 are .t1 and .t2, and source is foo.z, then created + # files would be "foo.t1" and "foo_x.t2". The 'name' attribute actually + # determines the basename of a file. + # + # Note that this pattern mechanism has nothing to do with implicit patterns + # in make. It is a way to produce a target whose name is different than the + # name of its source. + # + rule generated-targets ( sources + : property-set : project name ? ) + { + if ! $(name) + { + name = [ determine-output-name $(sources) ] ; + } + + # Assign an action for each target. + local action = [ action-class ] ; + local a = [ class.new $(action) $(sources) : $(self.rule-name) : + $(property-set) ] ; + + # Create generated target for each target type. + local targets ; + local pre = $(self.name-prefix) ; + local post = $(self.name-postfix) ; + for local t in $(self.target-types) + { + local generated-name = $(pre[1])$(name:BS)$(post[1]) ; + generated-name = $(generated-name:R=$(name:D)) ; + pre = $(pre[2-]) ; + post = $(post[2-]) ; + + targets += [ class.new file-target $(generated-name) : $(t) : + $(project) : $(a) ] ; + } + + return [ sequence.transform virtual-target.register : $(targets) ] ; + } + + # Attempts to convert 'sources' to targets of types that this generator can + # handle. The intention is to produce the set of targets that can be used + # when the generator is run. + # + rule convert-to-consumable-types + ( + project name ? + : property-set + : sources + + : only-one ? # Convert 'source' to only one of the source types. If + # there is more that one possibility, report an error. + : consumed-var # Name of the variable which receives all targets which + # can be consumed. + bypassed-var # Name of the variable which receives all targets which + # can not be consumed. + ) + { + # We are likely to be passed 'consumed' and 'bypassed' var names. Use + # '_' to avoid name conflicts. + local _consumed ; + local _bypassed ; + local missing-types ; + + if $(sources[2]) + { + # Do not know how to handle several sources yet. Just try to pass + # the request to other generator. + missing-types = $(self.source-types) ; + } + else + { + consume-directly $(sources) : _consumed : missing-types ; + } + + # No need to search for transformation if some source type has consumed + # source and no more source types are needed. + if $(only-one) && $(_consumed) + { + missing-types = ; + } + + # TODO: we should check that only one source type if create of + # 'only-one' is true. + # TODO: consider if consumed/bypassed separation should be done by + # 'construct-types'. + + if $(missing-types) + { + local transformed = [ generators.construct-types $(project) $(name) + : $(missing-types) : $(property-set) : $(sources) ] ; + + # Add targets of right type to 'consumed'. Add others to 'bypassed'. + # The 'generators.construct' rule has done its best to convert + # everything to the required type. There is no need to rerun it on + # targets of different types. + + # NOTE: ignoring usage requirements. + for local t in $(transformed[2-]) + { + if [ $(t).type ] in $(missing-types) + { + _consumed += $(t) ; + } + else + { + _bypassed += $(t) ; + } + } + } + + _consumed = [ sequence.unique $(_consumed) ] ; + _bypassed = [ sequence.unique $(_bypassed) ] ; + + # Remove elements of '_bypassed' that are in '_consumed'. + + # Suppose the target type of current generator, X is produced from X_1 + # and X_2, which are produced from Y by one generator. When creating X_1 + # from Y, X_2 will be added to 'bypassed'. Likewise, when creating X_2 + # from Y, X_1 will be added to 'bypassed', but they are also in + # 'consumed'. We have to remove them from bypassed, so that generators + # up the call stack do not try to convert them. + + # In this particular case, X_1 instance in 'consumed' and X_1 instance + # in 'bypassed' will be the same: because they have the same source and + # action name, and 'virtual-target.register' will not allow two + # different instances. Therefore, it is OK to use 'set.difference'. + + _bypassed = [ set.difference $(_bypassed) : $(_consumed) ] ; + + $(consumed-var) += $(_consumed) ; + $(bypassed-var) += $(_bypassed) ; + } + + # Converts several files to consumable types. Called for composing + # generators only. + # + rule convert-multiple-sources-to-consumable-types ( project : property-set : + sources * : consumed-var bypassed-var ) + { + # We process each source one-by-one, trying to convert it to a usable + # type. + for local source in $(sources) + { + local _c ; + local _b ; + # TODO: need to check for failure on each source. + convert-to-consumable-types $(project) : $(property-set) : $(source) + : true : _c _b ; + if ! $(_c) + { + generators.dout [ indent ] " failed to convert " $(source) ; + } + $(consumed-var) += $(_c) ; + $(bypassed-var) += $(_b) ; + } + } + + rule consume-directly ( source : consumed-var : missing-types-var ) + { + local real-source-type = [ $(source).type ] ; + + # If there are no source types, we can consume anything. + local source-types = $(self.source-types) ; + source-types ?= $(real-source-type) ; + + for local st in $(source-types) + { + # The 'source' if of the right type already. + if $(real-source-type) = $(st) || [ type.is-derived + $(real-source-type) $(st) ] + { + $(consumed-var) += $(source) ; + } + else + { + $(missing-types-var) += $(st) ; + } + } + } + + # Returns the class to be used to actions. Default implementation returns + # "action". + # + rule action-class ( ) + { + return "action" ; + } +} + + +# Registers a new generator instance 'g'. +# +rule register ( g ) +{ + .all-generators += $(g) ; + + # A generator can produce several targets of the same type. We want unique + # occurrence of that generator in .generators.$(t) in that case, otherwise, + # it will be tried twice and we will get a false ambiguity. + for local t in [ sequence.unique [ $(g).target-types ] ] + { + .generators.$(t) += $(g) ; + } + + # Update the set of generators for toolset. + + # TODO: should we check that generator with this id is not already + # registered. For example, the fop.jam module intentionally declared two + # generators with the same id, so such check will break it. + local id = [ $(g).id ] ; + + # Some generators have multiple periods in their name, so a simple $(id:S=) + # will not generate the right toolset name. E.g. if id = gcc.compile.c++, + # then .generators-for-toolset.$(id:S=) will append to + # .generators-for-toolset.gcc.compile, which is a separate value from + # .generators-for-toolset.gcc. Correcting this makes generator inheritance + # work properly. See also inherit-generators in the toolset module. + local base = $(id) ; + while $(base:S) + { + base = $(base:B) ; + } + .generators-for-toolset.$(base) += $(g) ; + + + # After adding a new generator that can construct new target types, we need + # to clear the related cached viable source target type information for + # constructing a specific target type or using a specific generator. Cached + # viable source target type lists affected by this are those containing any + # of the target types constructed by the new generator or any of their base + # target types. + # + # A more advanced alternative to clearing that cached viable source target + # type information would be to expand it with additional source types or + # even better - mark it as needing to be expanded on next use. + # + # Also see the http://thread.gmane.org/gmane.comp.lib.boost.build/19077 + # mailing list thread for an even more advanced idea of how we could convert + # Boost Build's Jamfile processing, target selection and generator selection + # into separate steps which would prevent these caches from ever being + # invalidated. + # + # For now we just clear all the cached viable source target type information + # that does not simply state 'all types' and may implement a more detailed + # algorithm later on if it becomes needed. + + invalidate-extendable-viable-source-target-type-cache ; +} + + +# Creates a new non-composing 'generator' class instance and registers it. +# Returns the created instance. Rationale: the instance is returned so that it +# is possible to first register a generator and then call its 'run' method, +# bypassing the whole generator selection process. +# +rule register-standard ( id : source-types * : target-types + : requirements * ) +{ + local g = [ new generator $(id) : $(source-types) : $(target-types) : + $(requirements) ] ; + register $(g) ; + return $(g) ; +} + + +# Creates a new composing 'generator' class instance and registers it. +# +rule register-composing ( id : source-types * : target-types + : requirements * + ) +{ + local g = [ new generator $(id) true : $(source-types) : $(target-types) : + $(requirements) ] ; + register $(g) ; + return $(g) ; +} + + +# Returns all generators belonging to the given 'toolset', i.e. whose ids are +# '$(toolset).<something>'. +# +rule generators-for-toolset ( toolset ) +{ + return $(.generators-for-toolset.$(toolset)) ; +} + + +# Make generator 'overrider-id' be preferred to 'overridee-id'. If, when +# searching for generators that could produce a target of a certain type, both +# those generators are among viable generators, the overridden generator is +# immediately discarded. +# +# The overridden generators are discarded immediately after computing the list +# of viable generators but before running any of them. +# +rule override ( overrider-id : overridee-id ) +{ + .override.$(overrider-id) += $(overridee-id) ; +} + + +# Returns a list of source type which can possibly be converted to 'target-type' +# by some chain of generator invocation. +# +# More formally, takes all generators for 'target-type' and returns a union of +# source types for those generators and result of calling itself recursively on +# source types. +# +# Returns '*' in case any type should be considered a viable source type for the +# given type. +# +local rule viable-source-types-real ( target-type ) +{ + local result ; + + # 't0' is the initial list of target types we need to process to get a list + # of their viable source target types. New target types will not be added to + # this list. + local t0 = [ type.all-bases $(target-type) ] ; + + # 't' is the list of target types which have not yet been processed to get a + # list of their viable source target types. This list will get expanded as + # we locate more target types to process. + local t = $(t0) ; + + while $(t) + { + # Find all generators for the current type. Unlike + # 'find-viable-generators' we do not care about the property-set. + local generators = $(.generators.$(t[1])) ; + t = $(t[2-]) ; + + while $(generators) + { + local g = $(generators[1]) ; + generators = $(generators[2-]) ; + + if ! [ $(g).source-types ] + { + # Empty source types -- everything can be accepted. + result = * ; + # This will terminate this loop. + generators = ; + # This will terminate the outer loop. + t = ; + } + + for local source-type in [ $(g).source-types ] + { + if ! $(source-type) in $(result) + { + # If a generator accepts a 'source-type' it will also + # happily accept any type derived from it. + for local n in [ type.all-derived $(source-type) ] + { + if ! $(n) in $(result) + { + # Here there is no point in adding target types to + # the list of types to process in case they are or + # have already been on that list. We optimize this + # check by realizing that we only need to avoid the + # original target type's base types. Other target + # types that are or have been on the list of target + # types to process have been added to the 'result' + # list as well and have thus already been eliminated + # by the previous if. + if ! $(n) in $(t0) + { + t += $(n) ; + } + result += $(n) ; + } + } + } + } + } + } + + return $(result) ; +} + + +# Helper rule, caches the result of 'viable-source-types-real'. +# +rule viable-source-types ( target-type ) +{ + local key = .vst.$(target-type) ; + if ! $($(key)) + { + .vst-cached-types += $(target-type) ; + local v = [ viable-source-types-real $(target-type) ] ; + if ! $(v) + { + v = none ; + } + $(key) = $(v) ; + } + + if $($(key)) != none + { + return $($(key)) ; + } +} + + +# Returns the list of source types, which, when passed to 'run' method of +# 'generator', has some change of being eventually used (probably after +# conversion by other generators). +# +# Returns '*' in case any type should be considered a viable source type for the +# given generator. +# +rule viable-source-types-for-generator-real ( generator ) +{ + local source-types = [ $(generator).source-types ] ; + if ! $(source-types) + { + # If generator does not specify any source types, it might be a special + # generator like builtin.lib-generator which just relays to other + # generators. Return '*' to indicate that any source type is possibly + # OK, since we do not know for sure. + return * ; + } + else + { + local result ; + while $(source-types) + { + local s = $(source-types[1]) ; + source-types = $(source-types[2-]) ; + local viable-sources = [ generators.viable-source-types $(s) ] ; + if $(viable-sources) = * + { + result = * ; + source-types = ; # Terminate the loop. + } + else + { + result += [ type.all-derived $(s) ] $(viable-sources) ; + } + } + return [ sequence.unique $(result) ] ; + } +} + + +# Helper rule, caches the result of 'viable-source-types-for-generator'. +# +local rule viable-source-types-for-generator ( generator ) +{ + local key = .vstg.$(generator) ; + if ! $($(key)) + { + .vstg-cached-generators += $(generator) ; + local v = [ viable-source-types-for-generator-real $(generator) ] ; + if ! $(v) + { + v = none ; + } + $(key) = $(v) ; + } + + if $($(key)) != none + { + return $($(key)) ; + } +} + + +# Returns usage requirements + list of created targets. +# +local rule try-one-generator-really ( project name ? : generator : target-type + : property-set : sources * ) +{ + local targets = + [ $(generator).run $(project) $(name) : $(property-set) : $(sources) ] ; + + local usage-requirements ; + local success ; + + generators.dout [ indent ] returned $(targets) ; + + if $(targets) + { + success = true ; + + if [ class.is-a $(targets[1]) : property-set ] + { + usage-requirements = $(targets[1]) ; + targets = $(targets[2-]) ; + } + else + { + usage-requirements = [ property-set.empty ] ; + } + } + + generators.dout [ indent ] " generator" [ $(generator).id ] " spawned " ; + generators.dout [ indent ] " " $(targets) ; + if $(usage-requirements) + { + generators.dout [ indent ] " with usage requirements:" $(x) ; + } + + if $(success) + { + return $(usage-requirements) $(targets) ; + } +} + + +# Checks if generator invocation can be pruned, because it is guaranteed to +# fail. If so, quickly returns an empty list. Otherwise, calls +# try-one-generator-really. +# +local rule try-one-generator ( project name ? : generator : target-type + : property-set : sources * ) +{ + local source-types ; + for local s in $(sources) + { + source-types += [ $(s).type ] ; + } + local viable-source-types = [ viable-source-types-for-generator $(generator) + ] ; + + if $(source-types) && $(viable-source-types) != * && + ! [ set.intersection $(source-types) : $(viable-source-types) ] + { + local id = [ $(generator).id ] ; + generators.dout [ indent ] " ** generator '$(id)' pruned" ; + #generators.dout [ indent ] "source-types" '$(source-types)' ; + #generators.dout [ indent ] "viable-source-types" '$(viable-source-types)' ; + } + else + { + return [ try-one-generator-really $(project) $(name) : $(generator) : + $(target-type) : $(property-set) : $(sources) ] ; + } +} + + +rule construct-types ( project name ? : target-types + : property-set + : sources + ) +{ + local result ; + local matched-types ; + local usage-requirements = [ property-set.empty ] ; + for local t in $(target-types) + { + local r = [ construct $(project) $(name) : $(t) : $(property-set) : + $(sources) ] ; + if $(r) + { + usage-requirements = [ $(usage-requirements).add $(r[1]) ] ; + result += $(r[2-]) ; + matched-types += $(t) ; + } + } + # TODO: have to introduce parameter controlling if several types can be + # matched and add appropriate checks. + + # TODO: need to review the documentation for 'construct' to see if it should + # return $(source) even if nothing can be done with it. Currents docs seem + # to imply that, contrary to the behaviour. + if $(result) + { + return $(usage-requirements) $(result) ; + } + else + { + return $(usage-requirements) $(sources) ; + } +} + + +# Ensures all 'targets' have their type. If this is not so, exists with error. +# +local rule ensure-type ( targets * ) +{ + for local t in $(targets) + { + if ! [ $(t).type ] + { + errors.error "target" [ $(t).str ] "has no type" ; + } + } +} + + +# Returns generators which can be used to construct target of specified type +# with specified properties. Uses the following algorithm: +# - iterates over requested target-type and all its bases (in the order returned +# by type.all-bases). +# - for each type find all generators that generate that type and whose +# requirements are satisfied by properties. +# - if the set of generators is not empty, returns that set. +# +# Note: this algorithm explicitly ignores generators for base classes if there +# is at least one generator for the requested target-type. +# +local rule find-viable-generators-aux ( target-type : property-set ) +{ + # Select generators that can create the required target type. + local viable-generators = ; + local generator-rank = ; + + import type ; + local t = [ type.all-bases $(target-type) ] ; + + generators.dout [ indent ] find-viable-generators target-type= $(target-type) + property-set= [ $(property-set).as-path ] ; + + # Get the list of generators for the requested type. If no generator is + # registered, try base type, and so on. + local generators ; + while $(t[1]) + { + generators.dout [ indent ] "trying type" $(t[1]) ; + if $(.generators.$(t[1])) + { + generators.dout [ indent ] "there are generators for this type" ; + generators = $(.generators.$(t[1])) ; + + if $(t[1]) != $(target-type) + { + # We are here because there were no generators found for + # target-type but there are some generators for its base type. + # We will try to use them, but they will produce targets of + # base type, not of 'target-type'. So, we clone the generators + # and modify the list of target types. + local generators2 ; + for local g in $(generators) + { + # generators.register adds a generator to the list of + # generators for toolsets, which is a bit strange, but + # should work. That list is only used when inheriting a + # toolset, which should have been done before running + # generators. + generators2 += [ $(g).clone-and-change-target-type $(t[1]) : + $(target-type) ] ; + generators.register $(generators2[-1]) ; + } + generators = $(generators2) ; + } + t = ; + } + t = $(t[2-]) ; + } + + for local g in $(generators) + { + generators.dout [ indent ] "trying generator" [ $(g).id ] "(" [ $(g).source-types ] -> [ $(g).target-types ] ")" ; + + local m = [ $(g).match-rank $(property-set) ] ; + if $(m) + { + generators.dout [ indent ] " is viable" ; + viable-generators += $(g) ; + } + } + + return $(viable-generators) ; +} + + +rule find-viable-generators ( target-type : property-set ) +{ + local key = $(target-type).$(property-set) ; + local l = $(.fv.$(key)) ; + if ! $(l) + { + l = [ find-viable-generators-aux $(target-type) : $(property-set) ] ; + if ! $(l) + { + l = none ; + } + .fv.$(key) = $(l) ; + } + + if $(l) = none + { + l = ; + } + + local viable-generators ; + for local g in $(l) + { + # Avoid trying the same generator twice on different levels. + if ! $(g) in $(.active-generators) + { + viable-generators += $(g) ; + } + else + { + generators.dout [ indent ] " generator " [ $(g).id ] "is active, discaring" ; + } + } + + # Generators which override 'all'. + local all-overrides ; + # Generators which are overriden. + local overriden-ids ; + for local g in $(viable-generators) + { + local id = [ $(g).id ] ; + local this-overrides = $(.override.$(id)) ; + overriden-ids += $(this-overrides) ; + if all in $(this-overrides) + { + all-overrides += $(g) ; + } + } + if $(all-overrides) + { + viable-generators = $(all-overrides) ; + } + local result ; + for local g in $(viable-generators) + { + if ! [ $(g).id ] in $(overriden-ids) + { + result += $(g) ; + } + } + + return $(result) ; +} + + +.construct-stack = ; + + +# Attempts to construct a target by finding viable generators, running them and +# selecting the dependency graph. +# +local rule construct-really ( project name ? : target-type : property-set : + sources * ) +{ + viable-generators = [ find-viable-generators $(target-type) : + $(property-set) ] ; + + generators.dout [ indent ] "*** " [ sequence.length $(viable-generators) ] + " viable generators" ; + + local result ; + local generators-that-succeeded ; + for local g in $(viable-generators) + { + # This variable will be restored on exit from this scope. + local .active-generators = $(g) $(.active-generators) ; + + local r = [ try-one-generator $(project) $(name) : $(g) : $(target-type) + : $(property-set) : $(sources) ] ; + + if $(r) + { + generators-that-succeeded += $(g) ; + if $(result) + { + ECHO "Error: ambiguity found when searching for best transformation" ; + ECHO "Trying to produce type '$(target-type)' from: " ; + for local s in $(sources) + { + ECHO " - " [ $(s).str ] ; + } + ECHO "Generators that succeeded:" ; + for local g in $(generators-that-succeeded) + { + ECHO " - " [ $(g).id ] ; + } + ECHO "First generator produced: " ; + for local t in $(result[2-]) + { + ECHO " - " [ $(t).str ] ; + } + ECHO "Second generator produced: " ; + for local t in $(r[2-]) + { + ECHO " - " [ $(t).str ] ; + } + EXIT ; + } + else + { + result = $(r) ; + } + } + } + + return $(result) ; +} + + +# Attempts to create a target of 'target-type' with 'properties' from 'sources'. +# The 'sources' are treated as a collection of *possible* ingridients, i.e. +# there is no obligation to consume them all. +# +# Returns a list of targets. When this invocation is first instance of +# 'construct' in stack, returns only targets of requested 'target-type', +# otherwise, returns also unused sources and additionally generated targets. +# +# If 'top-level' is set, does not suppress generators that are already +# used in the stack. This may be useful in cases where a generator +# has to build a metatargets -- for example a target corresponding to +# built tool. +# +rule construct ( project name ? : target-type : property-set * : sources * : top-level ? ) +{ + local saved-stack ; + if $(top-level) + { + saved-active = $(.active-generators) ; + .active-generators = ; + } + + if (.construct-stack) + { + ensure-type $(sources) ; + } + + .construct-stack += 1 ; + + increase-indent ; + + if $(.debug) + { + generators.dout [ indent ] "*** construct" $(target-type) ; + + for local s in $(sources) + { + generators.dout [ indent ] " from" $(s) ; + } + generators.dout [ indent ] " properties:" [ $(property-set).raw ] ; + } + + local result = [ construct-really $(project) $(name) : $(target-type) : + $(property-set) : $(sources) ] ; + + decrease-indent ; + + .construct-stack = $(.construct-stack[2-]) ; + + if $(top-level) + { + .active-generators = $(saved-active) ; + } + + return $(result) ; +} + +# Given 'result', obtained from some generator or generators.construct, adds +# 'raw-properties' as usage requirements to it. If result already contains usage +# requirements -- that is the first element of result of an instance of the +# property-set class, the existing usage requirements and 'raw-properties' are +# combined. +# +rule add-usage-requirements ( result * : raw-properties * ) +{ + if $(result) + { + if [ class.is-a $(result[1]) : property-set ] + { + return [ $(result[1]).add-raw $(raw-properties) ] $(result[2-]) ; + } + else + { + return [ property-set.create $(raw-properties) ] $(result) ; + } + } +} + +rule dump ( ) +{ + for local g in $(.all-generators) + { + ECHO [ $(g).id ] ":" [ $(g).source-types ] -> [ $(g).target-types ] ; + } +} + diff --git a/jam-files/boost-build/build/generators.py b/jam-files/boost-build/build/generators.py new file mode 100644 index 00000000..2c59f7ca --- /dev/null +++ b/jam-files/boost-build/build/generators.py @@ -0,0 +1,1089 @@ +# Status: being ported by Vladimir Prus +# Base revision: 48649 +# TODO: replace the logging with dout + +# Copyright Vladimir Prus 2002. +# Copyright Rene Rivera 2006. +# +# 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) + +# Manages 'generators' --- objects which can do transformation between different +# target types and contain algorithm for finding transformation from sources +# to targets. +# +# The main entry point to this module is generators.construct rule. It is given +# a list of source targets, desired target type and a set of properties. +# It starts by selecting 'viable generators', which have any chances of producing +# the desired target type with the required properties. Generators are ranked and +# a set of most specific ones is selected. +# +# The most specific generators have their 'run' methods called, with the properties +# and list of sources. Each one selects target which can be directly consumed, and +# tries to convert the remaining ones to the types it can consume. This is done +# by recursively calling 'construct' with all consumable types. +# +# If the generator has collected all the targets it needs, it creates targets +# corresponding to result, and returns it. When all generators have been run, +# results of one of them are selected and returned as result. +# +# It's quite possible that 'construct' returns more targets that it was asked for. +# For example, it was asked to target type EXE, but the only found generators produces +# both EXE and TDS (file with debug) information. The extra target will be returned. +# +# Likewise, when generator tries to convert sources to consumable types, it can get +# more targets that it was asked for. The question is what to do with extra targets. +# Boost.Build attempts to convert them to requested types, and attempts as early as +# possible. Specifically, this is done after invoking each generator. (Later I'll +# document the rationale for trying extra target conversion at that point). +# +# That early conversion is not always desirable. Suppose a generator got a source of +# type Y and must consume one target of type X_1 and one target of type X_2. +# When converting Y to X_1 extra target of type Y_2 is created. We should not try to +# convert it to type X_1, because if we do so, the generator will get two targets +# of type X_1, and will be at loss as to which one to use. Because of that, the +# 'construct' rule has a parameter, telling if multiple targets can be returned. If +# the parameter is false, conversion of extra targets is not performed. + + +import re +import cStringIO +import os.path + +from virtual_target import Subvariant +import virtual_target, type, property_set, property +from b2.util.logger import * +from b2.util.utility import * +from b2.util import set +from b2.util.sequence import unique +import b2.util.sequence as sequence +from b2.manager import get_manager +import b2.build.type + +def reset (): + """ Clear the module state. This is mainly for testing purposes. + """ + global __generators, __type_to_generators, __generators_for_toolset, __construct_stack + global __overrides, __active_generators + global __viable_generators_cache, __viable_source_types_cache + global __vstg_cached_generators, __vst_cached_types + + __generators = {} + __type_to_generators = {} + __generators_for_toolset = {} + __overrides = {} + + # TODO: can these be global? + __construct_stack = [] + __viable_generators_cache = {} + __viable_source_types_cache = {} + __active_generators = [] + + __vstg_cached_generators = [] + __vst_cached_types = [] + +reset () + +_re_separate_types_prefix_and_postfix = re.compile ('([^\\(]*)(\\((.*)%(.*)\\))?') +_re_match_type = re.compile('([^\\(]*)(\\(.*\\))?') + + +__debug = None +__indent = "" + +def debug(): + global __debug + if __debug is None: + __debug = "--debug-generators" in bjam.variable("ARGV") + return __debug + +def increase_indent(): + global __indent + __indent += " " + +def decrease_indent(): + global __indent + __indent = __indent[0:-4] + + +# Updated cached viable source target type information as needed after a new +# derived target type gets added. This is needed because if a target type is a +# viable source target type for some generator then all of the target type's +# derived target types are automatically viable as source target types for the +# same generator. Does nothing if a non-derived target type is passed to it. +# +def update_cached_information_with_a_new_type(type): + + base_type = b2.build.type.base(type) + + if base_type: + for g in __vstg_cached_generators: + if base_type in __viable_source_types_cache.get(g, []): + __viable_source_types_cache[g].append(type) + + for t in __vst_cached_types: + if base_type in __viable_source_types_cache.get(t, []): + __viable_source_types_cache[t].append(type) + +# Clears cached viable source target type information except for target types +# and generators with all source types listed as viable. Should be called when +# something invalidates those cached values by possibly causing some new source +# types to become viable. +# +def invalidate_extendable_viable_source_target_type_cache(): + + global __vstg_cached_generators + generators_with_cached_source_types = __vstg_cached_generators + __vstg_cached_generators = [] + + for g in generators_with_cached_source_types: + if __viable_source_types_cache.has_key(g): + if __viable_source_types_cache[g] == ["*"]: + __vstg_cached_generators.append(g) + else: + del __viable_source_types_cache[g] + + global __vst_cached_types + types_with_cached_sources_types = __vst_cached_types + __vst_cached_types = [] + for t in types_with_cached_sources_types: + if __viable_source_types_cache.has_key(t): + if __viable_source_types_cache[t] == ["*"]: + __vst_cached_types.append(t) + else: + del __viable_source_types_cache[t] + +def dout(message): + if debug(): + print __indent + message + +class Generator: + """ Creates a generator. + manager: the build manager. + id: identifies the generator + + rule: the rule which sets up build actions. + + composing: whether generator processes each source target in + turn, converting it to required types. + Ordinary generators pass all sources together to + recusrive generators.construct_types call. + + source_types (optional): types that this generator can handle + + target_types_and_names: types the generator will create and, optionally, names for + created targets. Each element should have the form + type["(" name-pattern ")"] + for example, obj(%_x). Name of generated target will be found + by replacing % with the name of source, provided explicit name + was not specified. + + requirements (optional) + + NOTE: all subclasses must have a similar signature for clone to work! + """ + def __init__ (self, id, composing, source_types, target_types_and_names, requirements = []): + assert(not isinstance(source_types, str)) + assert(not isinstance(target_types_and_names, str)) + self.id_ = id + self.composing_ = composing + self.source_types_ = source_types + self.target_types_and_names_ = target_types_and_names + self.requirements_ = requirements + + self.target_types_ = [] + self.name_prefix_ = [] + self.name_postfix_ = [] + + for e in target_types_and_names: + # Create three parallel lists: one with the list of target types, + # and two other with prefixes and postfixes to be added to target + # name. We use parallel lists for prefix and postfix (as opposed + # to mapping), because given target type might occur several times, + # for example "H H(%_symbols)". + m = _re_separate_types_prefix_and_postfix.match (e) + + if not m: + raise BaseException ("Invalid type and name '%s' in declaration of type '%s'" % (e, id)) + + target_type = m.group (1) + if not target_type: target_type = '' + prefix = m.group (3) + if not prefix: prefix = '' + postfix = m.group (4) + if not postfix: postfix = '' + + self.target_types_.append (target_type) + self.name_prefix_.append (prefix) + self.name_postfix_.append (postfix) + + for x in self.source_types_: + type.validate (x) + + for x in self.target_types_: + type.validate (x) + + def clone (self, new_id, new_toolset_properties): + """ Returns another generator which differers from $(self) in + - id + - value to <toolset> feature in properties + """ + return self.__class__ (new_id, + self.composing_, + self.source_types_, + self.target_types_and_names_, + # Note: this does not remove any subfeatures of <toolset> + # which might cause problems + property.change (self.requirements_, '<toolset>') + new_toolset_properties) + + def clone_and_change_target_type(self, base, type): + """Creates another generator that is the same as $(self), except that + if 'base' is in target types of $(self), 'type' will in target types + of the new generator.""" + target_types = [] + for t in self.target_types_and_names_: + m = _re_match_type.match(t) + assert m + + if m.group(1) == base: + if m.group(2): + target_types.append(type + m.group(2)) + else: + target_types.append(type) + else: + target_types.append(t) + + return self.__class__(self.id_, self.composing_, + self.source_types_, + target_types, + self.requirements_) + + + def id(self): + return self.id_ + + def source_types (self): + """ Returns the list of target type the generator accepts. + """ + return self.source_types_ + + def target_types (self): + """ Returns the list of target types that this generator produces. + It is assumed to be always the same -- i.e. it cannot change depending + list of sources. + """ + return self.target_types_ + + def requirements (self): + """ Returns the required properties for this generator. Properties + in returned set must be present in build properties if this + generator is to be used. If result has grist-only element, + that build properties must include some value of that feature. + """ + return self.requirements_ + + def match_rank (self, ps): + """ Returns true if the generator can be run with the specified + properties. + """ + # See if generator's requirements are satisfied by + # 'properties'. Treat a feature name in requirements + # (i.e. grist-only element), as matching any value of the + # feature. + all_requirements = self.requirements () + + property_requirements = [] + feature_requirements = [] + # This uses strings because genenator requirements allow + # the '<feature>' syntax without value and regular validation + # is not happy about that. + for r in all_requirements: + if get_value (r): + property_requirements.append (r) + + else: + feature_requirements.append (r) + + return all(ps.get(get_grist(s)) == [get_value(s)] for s in property_requirements) \ + and all(ps.get(get_grist(s)) for s in feature_requirements) + + def run (self, project, name, prop_set, sources): + """ Tries to invoke this generator on the given sources. Returns a + list of generated targets (instances of 'virtual-target'). + + project: Project for which the targets are generated. + + name: Determines the name of 'name' attribute for + all generated targets. See 'generated_targets' method. + + prop_set: Desired properties for generated targets. + + sources: Source targets. + """ + + if project.manager ().logger ().on (): + project.manager ().logger ().log (__name__, " generator '%s'" % self.id_) + project.manager ().logger ().log (__name__, " composing: '%s'" % self.composing_) + + if not self.composing_ and len (sources) > 1 and len (self.source_types_) > 1: + raise BaseException ("Unsupported source/source_type combination") + + # We don't run composing generators if no name is specified. The reason + # is that composing generator combines several targets, which can have + # different names, and it cannot decide which name to give for produced + # target. Therefore, the name must be passed. + # + # This in effect, means that composing generators are runnable only + # at top-level of transofrmation graph, or if name is passed explicitly. + # Thus, we dissallow composing generators in the middle. For example, the + # transofrmation CPP -> OBJ -> STATIC_LIB -> RSP -> EXE won't be allowed + # (the OBJ -> STATIC_LIB generator is composing) + if not self.composing_ or name: + return self.run_really (project, name, prop_set, sources) + else: + return [] + + def run_really (self, project, name, prop_set, sources): + + # consumed: Targets that this generator will consume directly. + # bypassed: Targets that can't be consumed and will be returned as-is. + + if self.composing_: + (consumed, bypassed) = self.convert_multiple_sources_to_consumable_types (project, prop_set, sources) + else: + (consumed, bypassed) = self.convert_to_consumable_types (project, name, prop_set, sources) + + result = [] + if consumed: + result = self.construct_result (consumed, project, name, prop_set) + result.extend (bypassed) + + if result: + if project.manager ().logger ().on (): + project.manager ().logger ().log (__name__, " SUCCESS: ", result) + + else: + project.manager ().logger ().log (__name__, " FAILURE") + + return result + + def construct_result (self, consumed, project, name, prop_set): + """ Constructs the dependency graph that will be returned by this + generator. + consumed: Already prepared list of consumable targets + If generator requires several source files will contain + exactly len $(self.source_types_) targets with matching types + Otherwise, might contain several targets with the type of + self.source_types_ [0] + project: + name: + prop_set: Properties to be used for all actions create here + """ + result = [] + # If this is 1->1 transformation, apply it to all consumed targets in order. + if len (self.source_types_) < 2 and not self.composing_: + + for r in consumed: + result.extend (self.generated_targets ([r], prop_set, project, name)) + + else: + + if consumed: + result.extend (self.generated_targets (consumed, prop_set, project, name)) + + return result + + def determine_target_name(self, fullname): + # Determine target name from fullname (maybe including path components) + # Place optional prefix and postfix around basename + + dir = os.path.dirname(fullname) + name = os.path.basename(fullname) + + if dir and not ".." in dir and not os.path.isabs(dir): + # Relative path is always relative to the source + # directory. Retain it, so that users can have files + # with the same in two different subdirectories. + name = dir + "/" + name + + return name + + def determine_output_name(self, sources): + """Determine the name of the produced target from the + names of the sources.""" + + # The simple case if when a name + # of source has single dot. Then, we take the part before + # dot. Several dots can be caused by: + # - Using source file like a.host.cpp + # - A type which suffix has a dot. Say, we can + # type 'host_cpp' with extension 'host.cpp'. + # In the first case, we want to take the part till the last + # dot. In the second case -- no sure, but for now take + # the part till the last dot too. + name = os.path.splitext(sources[0].name())[0] + + for s in sources[1:]: + n2 = os.path.splitext(s.name()) + if n2 != name: + get_manager().errors()( + "%s: source targets have different names: cannot determine target name" + % (self.id_)) + + # Names of sources might include directory. We should strip it. + return self.determine_target_name(sources[0].name()) + + + def generated_targets (self, sources, prop_set, project, name): + """ Constructs targets that are created after consuming 'sources'. + The result will be the list of virtual-target, which the same length + as 'target_types' attribute and with corresponding types. + + When 'name' is empty, all source targets must have the same value of + the 'name' attribute, which will be used instead of the 'name' argument. + + The value of 'name' attribute for each generated target will be equal to + the 'name' parameter if there's no name pattern for this type. Otherwise, + the '%' symbol in the name pattern will be replaced with the 'name' parameter + to obtain the 'name' attribute. + + For example, if targets types are T1 and T2(with name pattern "%_x"), suffixes + for T1 and T2 are .t1 and t2, and source if foo.z, then created files would + be "foo.t1" and "foo_x.t2". The 'name' attribute actually determined the + basename of a file. + + Note that this pattern mechanism has nothing to do with implicit patterns + in make. It's a way to produce target which name is different for name of + source. + """ + if not name: + name = self.determine_output_name(sources) + + # Assign an action for each target + action = self.action_class() + a = action(project.manager(), sources, self.id_, prop_set) + + # Create generated target for each target type. + targets = [] + pre = self.name_prefix_ + post = self.name_postfix_ + for t in self.target_types_: + basename = os.path.basename(name) + idx = basename.find(".") + if idx != -1: + basename = basename[:idx] + generated_name = pre[0] + basename + post[0] + generated_name = os.path.join(os.path.dirname(name), generated_name) + pre = pre[1:] + post = post[1:] + + targets.append(virtual_target.FileTarget(generated_name, t, project, a)) + + return [ project.manager().virtual_targets().register(t) for t in targets ] + + def convert_to_consumable_types (self, project, name, prop_set, sources, only_one=False): + """ Attempts to convert 'source' to the types that this generator can + handle. The intention is to produce the set of targets can should be + used when generator is run. + only_one: convert 'source' to only one of source types + if there's more that one possibility, report an + error. + + Returns a pair: + consumed: all targets that can be consumed. + bypassed: all targets that cannot be consumed. + """ + consumed = [] + bypassed = [] + missing_types = [] + + if len (sources) > 1: + # Don't know how to handle several sources yet. Just try + # to pass the request to other generator + missing_types = self.source_types_ + + else: + (c, m) = self.consume_directly (sources [0]) + consumed += c + missing_types += m + + # No need to search for transformation if + # some source type has consumed source and + # no more source types are needed. + if only_one and consumed: + missing_types = [] + + #TODO: we should check that only one source type + #if create of 'only_one' is true. + # TODO: consider if consuned/bypassed separation should + # be done by 'construct_types'. + + if missing_types: + transformed = construct_types (project, name, missing_types, prop_set, sources) + + # Add targets of right type to 'consumed'. Add others to + # 'bypassed'. The 'generators.construct' rule has done + # its best to convert everything to the required type. + # There's no need to rerun it on targets of different types. + + # NOTE: ignoring usage requirements + for t in transformed[1]: + if t.type() in missing_types: + consumed.append(t) + + else: + bypassed.append(t) + + consumed = unique(consumed) + bypassed = unique(bypassed) + + # remove elements of 'bypassed' that are in 'consumed' + + # Suppose the target type of current generator, X is produced from + # X_1 and X_2, which are produced from Y by one generator. + # When creating X_1 from Y, X_2 will be added to 'bypassed' + # Likewise, when creating X_2 from Y, X_1 will be added to 'bypassed' + # But they are also in 'consumed'. We have to remove them from + # bypassed, so that generators up the call stack don't try to convert + # them. + + # In this particular case, X_1 instance in 'consumed' and X_1 instance + # in 'bypassed' will be the same: because they have the same source and + # action name, and 'virtual-target.register' won't allow two different + # instances. Therefore, it's OK to use 'set.difference'. + + bypassed = set.difference(bypassed, consumed) + + return (consumed, bypassed) + + + def convert_multiple_sources_to_consumable_types (self, project, prop_set, sources): + """ Converts several files to consumable types. + """ + consumed = [] + bypassed = [] + + # We process each source one-by-one, trying to convert it to + # a usable type. + for s in sources: + # TODO: need to check for failure on each source. + (c, b) = self.convert_to_consumable_types (project, None, prop_set, [s], True) + if not c: + project.manager ().logger ().log (__name__, " failed to convert ", s) + + consumed.extend (c) + bypassed.extend (b) + + return (consumed, bypassed) + + def consume_directly (self, source): + real_source_type = source.type () + + # If there are no source types, we can consume anything + source_types = self.source_types() + if not source_types: + source_types = [real_source_type] + + consumed = [] + missing_types = [] + for st in source_types: + # The 'source' if of right type already) + if real_source_type == st or type.is_derived (real_source_type, st): + consumed.append (source) + + else: + missing_types.append (st) + + return (consumed, missing_types) + + def action_class (self): + """ Returns the class to be used to actions. Default implementation + returns "action". + """ + return virtual_target.Action + + +def find (id): + """ Finds the generator with id. Returns None if not found. + """ + return __generators.get (id, None) + +def register (g): + """ Registers new generator instance 'g'. + """ + id = g.id() + + __generators [id] = g + + # A generator can produce several targets of the + # same type. We want unique occurence of that generator + # in .generators.$(t) in that case, otherwise, it will + # be tried twice and we'll get false ambiguity. + for t in sequence.unique(g.target_types()): + __type_to_generators.setdefault(t, []).append(g) + + # Update the set of generators for toolset + + # TODO: should we check that generator with this id + # is not already registered. For example, the fop.jam + # module intentionally declared two generators with the + # same id, so such check will break it. + + # Some generators have multiple periods in their name, so the + # normal $(id:S=) won't generate the right toolset name. + # e.g. if id = gcc.compile.c++, then + # .generators-for-toolset.$(id:S=) will append to + # .generators-for-toolset.gcc.compile, which is a separate + # value from .generators-for-toolset.gcc. Correcting this + # makes generator inheritance work properly. + # See also inherit-generators in module toolset + base = id.split ('.', 100) [0] + + __generators_for_toolset.setdefault(base, []).append(g) + + # After adding a new generator that can construct new target types, we need + # to clear the related cached viable source target type information for + # constructing a specific target type or using a specific generator. Cached + # viable source target type lists affected by this are those containing any + # of the target types constructed by the new generator or any of their base + # target types. + # + # A more advanced alternative to clearing that cached viable source target + # type information would be to expand it with additional source types or + # even better - mark it as needing to be expanded on next use. + # + # For now we just clear all the cached viable source target type information + # that does not simply state 'all types' and may implement a more detailed + # algorithm later on if it becomes needed. + + invalidate_extendable_viable_source_target_type_cache() + + +def register_standard (id, source_types, target_types, requirements = []): + """ Creates new instance of the 'generator' class and registers it. + Returns the creates instance. + Rationale: the instance is returned so that it's possible to first register + a generator and then call 'run' method on that generator, bypassing all + generator selection. + """ + g = Generator (id, False, source_types, target_types, requirements) + register (g) + return g + +def register_composing (id, source_types, target_types, requirements = []): + g = Generator (id, True, source_types, target_types, requirements) + register (g) + return g + +def generators_for_toolset (toolset): + """ Returns all generators which belong to 'toolset'. + """ + return __generators_for_toolset.get(toolset, []) + +def override (overrider_id, overridee_id): + """Make generator 'overrider-id' be preferred to + 'overridee-id'. If, when searching for generators + that could produce a target of certain type, + both those generators are amoung viable generators, + the overridden generator is immediately discarded. + + The overridden generators are discarded immediately + after computing the list of viable generators, before + running any of them.""" + + __overrides.get(overrider_id, []).append(overridee_id) + +def __viable_source_types_real (target_type): + """ Returns a list of source type which can possibly be converted + to 'target_type' by some chain of generator invocation. + + More formally, takes all generators for 'target_type' and + returns union of source types for those generators and result + of calling itself recusrively on source types. + """ + generators = [] + + # 't0' is the initial list of target types we need to process to get a list + # of their viable source target types. New target types will not be added to + # this list. + t0 = type.all_bases (target_type) + + + # 't' is the list of target types which have not yet been processed to get a + # list of their viable source target types. This list will get expanded as + # we locate more target types to process. + t = t0 + + result = [] + while t: + # Find all generators for current type. + # Unlike 'find_viable_generators' we don't care about prop_set. + generators = __type_to_generators.get (t [0], []) + t = t[1:] + + for g in generators: + if not g.source_types(): + # Empty source types -- everything can be accepted + result = "*" + # This will terminate outer loop. + t = None + break + + for source_type in g.source_types (): + if not source_type in result: + # If generator accepts 'source_type' it + # will happily accept any type derived from it + all = type.all_derived (source_type) + for n in all: + if not n in result: + + # Here there is no point in adding target types to + # the list of types to process in case they are or + # have already been on that list. We optimize this + # check by realizing that we only need to avoid the + # original target type's base types. Other target + # types that are or have been on the list of target + # types to process have been added to the 'result' + # list as well and have thus already been eliminated + # by the previous if. + if not n in t0: + t.append (n) + result.append (n) + + return result + + +def viable_source_types (target_type): + """ Helper rule, caches the result of '__viable_source_types_real'. + """ + if not __viable_source_types_cache.has_key(target_type): + __vst_cached_types.append(target_type) + __viable_source_types_cache [target_type] = __viable_source_types_real (target_type) + return __viable_source_types_cache [target_type] + +def viable_source_types_for_generator_real (generator): + """ Returns the list of source types, which, when passed to 'run' + method of 'generator', has some change of being eventually used + (probably after conversion by other generators) + """ + source_types = generator.source_types () + + if not source_types: + # If generator does not specify any source types, + # it might be special generator like builtin.lib-generator + # which just relays to other generators. Return '*' to + # indicate that any source type is possibly OK, since we don't + # know for sure. + return ['*'] + + else: + result = [] + for s in source_types: + viable_sources = viable_source_types(s) + if viable_sources == "*": + result = ["*"] + break + else: + result.extend(type.all_derived(s) + viable_sources) + return unique(result) + +def viable_source_types_for_generator (generator): + """ Caches the result of 'viable_source_types_for_generator'. + """ + if not __viable_source_types_cache.has_key(generator): + __vstg_cached_generators.append(generator) + __viable_source_types_cache[generator] = viable_source_types_for_generator_real (generator) + + return __viable_source_types_cache[generator] + +def try_one_generator_really (project, name, generator, target_type, properties, sources): + """ Returns usage requirements + list of created targets. + """ + targets = generator.run (project, name, properties, sources) + + usage_requirements = [] + success = False + + dout("returned " + str(targets)) + + if targets: + success = True; + + if isinstance (targets[0], property_set.PropertySet): + usage_requirements = targets [0] + targets = targets [1] + + else: + usage_requirements = property_set.empty () + + dout( " generator" + generator.id() + " spawned ") + # generators.dout [ indent ] " " $(targets) ; +# if $(usage-requirements) +# { +# generators.dout [ indent ] " with usage requirements:" $(x) ; +# } + + if success: + return (usage_requirements, targets) + else: + return None + +def try_one_generator (project, name, generator, target_type, properties, sources): + """ Checks if generator invocation can be pruned, because it's guaranteed + to fail. If so, quickly returns empty list. Otherwise, calls + try_one_generator_really. + """ + source_types = [] + + for s in sources: + source_types.append (s.type ()) + + viable_source_types = viable_source_types_for_generator (generator) + + if source_types and viable_source_types != ['*'] and\ + not set.intersection (source_types, viable_source_types): + if project.manager ().logger ().on (): + id = generator.id () + project.manager ().logger ().log (__name__, "generator '%s' pruned" % id) + project.manager ().logger ().log (__name__, "source_types" '%s' % source_types) + project.manager ().logger ().log (__name__, "viable_source_types '%s'" % viable_source_types) + + return [] + + else: + return try_one_generator_really (project, name, generator, target_type, properties, sources) + + +def construct_types (project, name, target_types, prop_set, sources): + + result = [] + usage_requirements = property_set.empty() + + for t in target_types: + r = construct (project, name, t, prop_set, sources) + + if r: + (ur, targets) = r + usage_requirements = usage_requirements.add(ur) + result.extend(targets) + + # TODO: have to introduce parameter controlling if + # several types can be matched and add appropriate + # checks + + # TODO: need to review the documentation for + # 'construct' to see if it should return $(source) even + # if nothing can be done with it. Currents docs seem to + # imply that, contrary to the behaviour. + if result: + return (usage_requirements, result) + + else: + return (usage_requirements, sources) + +def __ensure_type (targets): + """ Ensures all 'targets' have types. If this is not so, exists with + error. + """ + for t in targets: + if not t.type (): + get_manager().errors()("target '%s' has no type" % str (t)) + +def find_viable_generators_aux (target_type, prop_set): + """ Returns generators which can be used to construct target of specified type + with specified properties. Uses the following algorithm: + - iterates over requested target_type and all it's bases (in the order returned bt + type.all-bases. + - for each type find all generators that generate that type and which requirements + are satisfied by properties. + - if the set of generators is not empty, returns that set. + + Note: this algorithm explicitly ignores generators for base classes if there's + at least one generator for requested target_type. + """ + # Select generators that can create the required target type. + viable_generators = [] + initial_generators = [] + + import type + + # Try all-type generators first. Assume they have + # quite specific requirements. + all_bases = type.all_bases(target_type) + + for t in all_bases: + + initial_generators = __type_to_generators.get(t, []) + + if initial_generators: + dout("there are generators for this type") + if t != target_type: + # We're here, when no generators for target-type are found, + # but there are some generators for a base type. + # We'll try to use them, but they will produce targets of + # base type, not of 'target-type'. So, we clone the generators + # and modify the list of target types. + generators2 = [] + for g in initial_generators[:]: + # generators.register adds generator to the list of generators + # for toolsets, which is a bit strange, but should work. + # That list is only used when inheriting toolset, which + # should have being done before generators are run. + ng = g.clone_and_change_target_type(t, target_type) + generators2.append(ng) + register(ng) + + initial_generators = generators2 + break + + for g in initial_generators: + dout("trying generator " + g.id() + + "(" + str(g.source_types()) + "->" + str(g.target_types()) + ")") + + m = g.match_rank(prop_set) + if m: + dout(" is viable") + viable_generators.append(g) + + return viable_generators + +def find_viable_generators (target_type, prop_set): + key = target_type + '.' + str (prop_set) + + l = __viable_generators_cache.get (key, None) + if not l: + l = [] + + if not l: + l = find_viable_generators_aux (target_type, prop_set) + + __viable_generators_cache [key] = l + + viable_generators = [] + for g in l: + # Avoid trying the same generator twice on different levels. + # TODO: is this really used? + if not g in __active_generators: + viable_generators.append (g) + else: + dout(" generator %s is active, discarding" % g.id()) + + # Generators which override 'all'. + all_overrides = [] + + # Generators which are overriden + overriden_ids = [] + + for g in viable_generators: + id = g.id () + + this_overrides = __overrides.get (id, []) + + if this_overrides: + overriden_ids.extend (this_overrides) + if 'all' in this_overrides: + all_overrides.append (g) + + if all_overrides: + viable_generators = all_overrides + + result = [] + for g in viable_generators: + if not g.id () in overriden_ids: + result.append (g) + + + return result + +def __construct_really (project, name, target_type, prop_set, sources): + """ Attempts to construct target by finding viable generators, running them + and selecting the dependency graph. + """ + viable_generators = find_viable_generators (target_type, prop_set) + + result = [] + + project.manager ().logger ().log (__name__, "*** %d viable generators" % len (viable_generators)) + + generators_that_succeeded = [] + + for g in viable_generators: + __active_generators.append(g) + r = try_one_generator (project, name, g, target_type, prop_set, sources) + del __active_generators[-1] + + if r: + generators_that_succeeded.append(g) + if result: + output = cStringIO.StringIO() + print >>output, "ambiguity found when searching for best transformation" + print >>output, "Trying to produce type '%s' from: " % (target_type) + for s in sources: + print >>output, " - " + s.str() + print >>output, "Generators that succeeded:" + for g in generators_that_succeeded: + print >>output, " - " + g.id() + print >>output, "First generator produced: " + for t in result[1:]: + print >>output, " - " + str(t) + print >>output, "Second generator produced:" + for t in r[1:]: + print >>output, " - " + str(t) + get_manager().errors()(output.getvalue()) + else: + result = r; + + return result; + + +def construct (project, name, target_type, prop_set, sources, top_level=False): + """ Attempts to create target of 'target-type' with 'properties' + from 'sources'. The 'sources' are treated as a collection of + *possible* ingridients -- i.e. it is not required to consume + them all. If 'multiple' is true, the rule is allowed to return + several targets of 'target-type'. + + Returns a list of target. When this invocation is first instance of + 'construct' in stack, returns only targets of requested 'target-type', + otherwise, returns also unused sources and additionally generated + targets. + + If 'top-level' is set, does not suppress generators that are already + used in the stack. This may be useful in cases where a generator + has to build a metatarget -- for example a target corresponding to + built tool. + """ + + global __active_generators + if top_level: + saved_active = __active_generators + __active_generators = [] + + global __construct_stack + if not __construct_stack: + __ensure_type (sources) + + __construct_stack.append (1) + + if project.manager().logger().on(): + increase_indent () + + dout( "*** construct " + target_type) + + for s in sources: + dout(" from " + str(s)) + + project.manager().logger().log (__name__, " properties: ", prop_set.raw ()) + + result = __construct_really(project, name, target_type, prop_set, sources) + + project.manager().logger().decrease_indent() + + __construct_stack = __construct_stack [1:] + + if top_level: + __active_generators = saved_active + + return result + diff --git a/jam-files/boost-build/build/modifiers.jam b/jam-files/boost-build/build/modifiers.jam new file mode 100644 index 00000000..6b009343 --- /dev/null +++ b/jam-files/boost-build/build/modifiers.jam @@ -0,0 +1,232 @@ +# Copyright 2003 Rene Rivera +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Modifiers are generalized generators that mutate targets in specific ways. +# This structure allows for grouping a variety of functionality in an +# orthogonal way to the functionality in toolsets, and without specifying +# more target variations. In turn the modifiers can be used as building +# blocks to implement simple requests, like the <version> feature. + +import modules ; +import feature ; +import errors ; +import type ; +import "class" : new ; +import generators ; +import property ; +import virtual-target ; +import numbers ; +import sequence ; +import symlink ; +import property-set ; + +# Base generator for creating targets that are modifications of existing +# targets. +# +class modifier : generator +{ + rule __init__ ( + id + composing ? + : source-types * + : target-types-and-names + + : requirements * + ) + { + generator.__init__ $(id) $(composing) + : $(source-types) + : $(target-types-and-names) + : $(requirements) ; + + self.targets-in-progress = ; + } + + # Wraps the generation of the target to call before and after rules to + # affect the real target. + # + rule run ( project name ? : property-set : sources + ) + { + local result ; + local current-target = $(project)^$(name) ; + if ! $(current-target) in $(self.targets-in-progress) + { + # Before modifications... + local project_ = + [ modify-project-before + $(project) $(name) : $(property-set) : $(sources) ] ; + local name_ = + [ modify-name-before + $(project) $(name) : $(property-set) : $(sources) ] ; + local property-set_ = + [ modify-properties-before + $(project) $(name) : $(property-set) : $(sources) ] ; + local sources_ = + [ modify-sources-before + $(project) $(name) : $(property-set) : $(sources) ] ; + project = $(project_) ; + name = $(name_) ; + property-set = $(property-set_) ; + sources = $(sources_) ; + + # Generate the real target... + local target-type-p = + [ property.select <main-target-type> : [ $(property-set).raw ] ] ; + self.targets-in-progress += $(current-target) ; + result = + [ generators.construct $(project) $(name) + : $(target-type-p:G=) + : $(property-set) + : $(sources) ] ; + self.targets-in-progress = $(self.targets-in-progress[1--2]) ; + + # After modifications... + result = + [ modify-target-after $(result) + : $(project) $(name) + : $(property-set) + : $(sources) ] ; + } + return $(result) ; + } + + rule modify-project-before ( project name ? : property-set : sources + ) + { + return $(project) ; + } + + rule modify-name-before ( project name ? : property-set : sources + ) + { + return $(name) ; + } + + rule modify-properties-before ( project name ? : property-set : sources + ) + { + return $(property-set) ; + } + + rule modify-sources-before ( project name ? : property-set : sources + ) + { + return $(sources) ; + } + + rule modify-target-after ( target : project name ? : property-set : sources + ) + { + return $(target) ; + } + + # Utility, clones a file-target with optional changes to the name, type and + # project of the target. + # NOTE: This functionality should be moved, and generalized, to + # virtual-targets. + # + rule clone-file-target ( target : new-name ? : new-type ? : new-project ? ) + { + # Need a MUTCH better way to clone a target... + new-name ?= [ $(target).name ] ; + new-type ?= [ $(target).type ] ; + new-project ?= [ $(target).project ] ; + local result = [ new file-target $(new-name) : $(new-type) : $(new-project) ] ; + + if [ $(target).dependencies ] { $(result).depends [ $(target).dependencies ] ; } + $(result).root [ $(target).root ] ; + $(result).set-usage-requirements [ $(target).usage-requirements ] ; + + local action = [ $(target).action ] ; + local action-class = [ modules.peek $(action) : __class__ ] ; + + local ps = [ $(action).properties ] ; + local cloned-action = [ new $(action-class) $(result) : + [ $(action).sources ] : [ $(action).action-name ] : $(ps) ] ; + $(result).action $(cloned-action) ; + + return $(result) ; + } +} + + +# A modifier that changes the name of a target, after it's generated, given a +# regular expression to split the name, and a set of token to insert between the +# split tokens of the name. This also exposes the target for other uses with a +# symlink to the original name (optionally). +# +class name-modifier : modifier +{ + rule __init__ ( ) + { + # Apply ourselves to EXE targets, for now. + modifier.__init__ name.modifier : : EXE LIB : <name-modify>yes ; + } + + # Modifies the name, by cloning the target with the new name. + # + rule modify-target-after ( target : project name ? : property-set : sources + ) + { + local result = $(target) ; + + local name-mod-p = [ property.select <name-modifier> : [ $(property-set).raw ] ] ; + if $(name-mod-p) + { + local new-name = [ modify-name [ $(target).name ] : $(name-mod-p:G=) ] ; + if $(new-name) != [ $(target).name ] + { + result = [ clone-file-target $(target) : $(new-name) ] ; + } + local expose-original-as-symlink = [ MATCH "<symlink>(.*)" : $(name-mod-p) ] ; + if $(expose-original-as-symlink) + { + local symlink-t = [ new symlink-targets $(project) : $(name) : [ $(result).name ] ] ; + result = [ $(symlink-t).construct $(result) + : [ property-set.create [ $(property-set).raw ] <symlink-location>build-relative ] ] ; + } + } + + return $(result) ; + } + + # Do the transformation of the name. + # + rule modify-name ( name : modifier-spec + ) + { + local match = [ MATCH "<match>(.*)" : $(modifier-spec) ] ; + local name-parts = [ MATCH $(match) : $(name) ] ; + local insertions = [ sequence.insertion-sort [ MATCH "(<[0123456789]+>.*)" : $(modifier-spec) ] ] ; + local new-name-parts ; + local insert-position = 1 ; + while $(insertions) + { + local insertion = [ MATCH "<$(insert-position)>(.*)" : $(insertions[1]) ] ; + if $(insertion) + { + new-name-parts += $(insertion) ; + insertions = $(insertions[2-]) ; + } + new-name-parts += $(name-parts[1]) ; + name-parts = $(name-parts[2-]) ; + insert-position = [ numbers.increment $(insert-position) ] ; + } + new-name-parts += $(name-parts) ; + return [ sequence.join $(new-name-parts) ] ; + } + + rule optional-properties ( ) + { + return <name-modify>yes ; + } +} +feature.feature name-modifier : : free ; +feature.feature name-modify : no yes : incidental optional ; +generators.register [ new name-modifier ] ; + +# Translates <version> property to a set of modification properties +# that are applied by the name-modifier, and symlink-modifier. +# +rule version-to-modifier ( property : properties * ) +{ + return + <name-modify>yes + <name-modifier><match>"^([^.]*)(.*)" <name-modifier><2>.$(property:G=) + <name-modifier><symlink>yes + ; +} +feature.action <version> : version-to-modifier ; diff --git a/jam-files/boost-build/build/project.ann.py b/jam-files/boost-build/build/project.ann.py new file mode 100644 index 00000000..349f5495 --- /dev/null +++ b/jam-files/boost-build/build/project.ann.py @@ -0,0 +1,996 @@ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 1) # Status: being ported by Vladimir Prus +ddc17f01 (vladimir_prus 2007-10-26 14:57:56 +0000 2) # Base revision: 40480 +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 3) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 4) # Copyright 2002, 2003 Dave Abrahams +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 5) # Copyright 2002, 2005, 2006 Rene Rivera +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 6) # Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 7) # Distributed under the Boost Software License, Version 1.0. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 8) # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 9) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 10) # Implements project representation and loading. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 11) # Each project is represented by +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 12) # - a module where all the Jamfile content live. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 13) # - an instance of 'project-attributes' class. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 14) # (given module name, can be obtained by 'attributes' rule) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 15) # - an instance of 'project-target' class (from targets.jam) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 16) # (given a module name, can be obtained by 'target' rule) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 17) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 18) # Typically, projects are created as result of loading Jamfile, which is +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 19) # do by rules 'load' and 'initialize', below. First, module for Jamfile +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 20) # is loaded and new project-attributes instance is created. Some rules +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 21) # necessary for project are added to the module (see 'project-rules' module) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 22) # at the bottom of this file. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 23) # Default project attributes are set (inheriting attributes of parent project, if +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 24) # it exists). After that, Jamfile is read. It can declare its own attributes, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 25) # via 'project' rule, which will be combined with already set attributes. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 26) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 27) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 28) # The 'project' rule can also declare project id, which will be associated with +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 29) # the project module. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 30) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 31) # There can also be 'standalone' projects. They are created by calling 'initialize' +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 32) # on arbitrary module, and not specifying location. After the call, the module can +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 33) # call 'project' rule, declare main target and behave as regular projects. However, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 34) # since it's not associated with any location, it's better declare only prebuilt +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 35) # targets. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 36) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 37) # The list of all loaded Jamfile is stored in variable .project-locations. It's possible +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 38) # to obtain module name for a location using 'module-name' rule. The standalone projects +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 39) # are not recorded, the only way to use them is by project id. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 40) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 41) import b2.util.path +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 42) from b2.build import property_set, property +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 43) from b2.build.errors import ExceptionWithUserContext +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 44) import b2.build.targets +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 45) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 46) import bjam +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 47) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 48) import re +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 49) import sys +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 50) import os +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 51) import string +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 52) import imp +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 53) import traceback +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 54) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 55) class ProjectRegistry: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 56) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 57) def __init__(self, manager, global_build_dir): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 58) self.manager = manager +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 59) self.global_build_dir = None +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 60) self.project_rules_ = ProjectRules(self) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 61) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 62) # The target corresponding to the project being loaded now +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 63) self.current_project = None +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 64) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 65) # The set of names of loaded project modules +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 66) self.jamfile_modules = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 67) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 68) # Mapping from location to module name +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 69) self.location2module = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 70) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 71) # Mapping from project id to project module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 72) self.id2module = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 73) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 74) # Map from Jamfile directory to parent Jamfile/Jamroot +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 75) # location. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 76) self.dir2parent_jamfile = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 77) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 78) # Map from directory to the name of Jamfile in +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 79) # that directory (or None). +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 80) self.dir2jamfile = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 81) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 82) # Map from project module to attributes object. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 83) self.module2attributes = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 84) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 85) # Map from project module to target for the project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 86) self.module2target = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 87) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 88) # Map from names to Python modules, for modules loaded +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 89) # via 'using' and 'import' rules in Jamfiles. +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 90) self.loaded_tool_modules_ = {} +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 91) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 92) # Map from project target to the list of +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 93) # (id,location) pairs corresponding to all 'use-project' +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 94) # invocations. +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 95) # TODO: should not have a global map, keep this +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 96) # in ProjectTarget. +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 97) self.used_projects = {} +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 98) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 99) self.saved_current_project = [] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 100) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 101) self.JAMROOT = self.manager.getenv("JAMROOT"); +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 102) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 103) # Note the use of character groups, as opposed to listing +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 104) # 'Jamroot' and 'jamroot'. With the latter, we'd get duplicate +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 105) # matches on windows and would have to eliminate duplicates. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 106) if not self.JAMROOT: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 107) self.JAMROOT = ["project-root.jam", "[Jj]amroot", "[Jj]amroot.jam"] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 108) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 109) # Default patterns to search for the Jamfiles to use for build +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 110) # declarations. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 111) self.JAMFILE = self.manager.getenv("JAMFILE") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 112) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 113) if not self.JAMFILE: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 114) self.JAMFILE = ["[Bb]uild.jam", "[Jj]amfile.v2", "[Jj]amfile", +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 115) "[Jj]amfile.jam"] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 116) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 117) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 118) def load (self, jamfile_location): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 119) """Loads jamfile at the given location. After loading, project global +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 120) file and jamfile needed by the loaded one will be loaded recursively. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 121) If the jamfile at that location is loaded already, does nothing. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 122) Returns the project module for the Jamfile.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 123) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 124) absolute = os.path.join(os.getcwd(), jamfile_location) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 125) absolute = os.path.normpath(absolute) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 126) jamfile_location = b2.util.path.relpath(os.getcwd(), absolute) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 127) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 128) if "--debug-loading" in self.manager.argv(): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 129) print "Loading Jamfile at '%s'" % jamfile_location +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 130) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 131) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 132) mname = self.module_name(jamfile_location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 133) # If Jamfile is already loaded, don't try again. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 134) if not mname in self.jamfile_modules: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 135) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 136) self.load_jamfile(jamfile_location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 137) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 138) # We want to make sure that child project are loaded only +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 139) # after parent projects. In particular, because parent projects +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 140) # define attributes whch are inherited by children, and we don't +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 141) # want children to be loaded before parents has defined everything. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 142) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 143) # While "build-project" and "use-project" can potentially refer +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 144) # to child projects from parent projects, we don't immediately +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 145) # load child projects when seing those attributes. Instead, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 146) # we record the minimal information that will be used only later. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 147) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 148) self.load_used_projects(mname) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 149) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 150) return mname +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 151) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 152) def load_used_projects(self, module_name): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 153) # local used = [ modules.peek $(module-name) : .used-projects ] ; +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 154) used = self.used_projects[module_name] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 155) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 156) location = self.attribute(module_name, "location") +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 157) for u in used: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 158) id = u[0] +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 159) where = u[1] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 160) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 161) self.use(id, os.path.join(location, where)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 162) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 163) def load_parent(self, location): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 164) """Loads parent of Jamfile at 'location'. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 165) Issues an error if nothing is found.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 166) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 167) found = b2.util.path.glob_in_parents( +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 168) location, self.JAMROOT + self.JAMFILE) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 169) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 170) if not found: +1674e2d9 (jhunold 2008-08-08 19:52:05 +0000 171) print "error: Could not find parent for project at '%s'" % location +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 172) print "error: Did not find Jamfile or project-root.jam in any parent directory." +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 173) sys.exit(1) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 174) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 175) return self.load(os.path.dirname(found[0])) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 176) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 177) def act_as_jamfile(self, module, location): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 178) """Makes the specified 'module' act as if it were a regularly loaded Jamfile +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 179) at 'location'. If Jamfile is already located for that location, it's an +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 180) error.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 181) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 182) if self.module_name(location) in self.jamfile_modules: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 183) self.manager.errors()( +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 184) "Jamfile was already loaded for '%s'" % location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 185) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 186) # Set up non-default mapping from location to module. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 187) self.location2module[location] = module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 188) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 189) # Add the location to the list of project locations +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 190) # so that we don't try to load Jamfile in future +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 191) self.jamfile_modules.append(location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 192) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 193) self.initialize(module, location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 194) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 195) def find(self, name, current_location): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 196) """Given 'name' which can be project-id or plain directory name, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 197) return project module corresponding to that id or directory. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 198) Returns nothing of project is not found.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 199) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 200) project_module = None +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 201) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 202) # Try interpreting name as project id. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 203) if name[0] == '/': +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 204) project_module = self.id2module.get(name) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 205) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 206) if not project_module: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 207) location = os.path.join(current_location, name) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 208) # If no project is registered for the given location, try to +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 209) # load it. First see if we have Jamfile. If not we might have project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 210) # root, willing to act as Jamfile. In that case, project-root +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 211) # must be placed in the directory referred by id. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 212) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 213) project_module = self.module_name(location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 214) if not project_module in self.jamfile_modules and \ +49c03622 (jhunold 2008-07-23 09:57:41 +0000 215) b2.util.path.glob([location], self.JAMROOT + self.JAMFILE): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 216) project_module = self.load(location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 217) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 218) return project_module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 219) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 220) def module_name(self, jamfile_location): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 221) """Returns the name of module corresponding to 'jamfile-location'. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 222) If no module corresponds to location yet, associates default +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 223) module name with that location.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 224) module = self.location2module.get(jamfile_location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 225) if not module: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 226) # Root the path, so that locations are always umbiguious. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 227) # Without this, we can't decide if '../../exe/program1' and '.' +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 228) # are the same paths, or not. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 229) jamfile_location = os.path.realpath( +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 230) os.path.join(os.getcwd(), jamfile_location)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 231) module = "Jamfile<%s>" % jamfile_location +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 232) self.location2module[jamfile_location] = module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 233) return module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 234) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 235) def find_jamfile (self, dir, parent_root=0, no_errors=0): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 236) """Find the Jamfile at the given location. This returns the +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 237) exact names of all the Jamfiles in the given directory. The optional +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 238) parent-root argument causes this to search not the given directory +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 239) but the ones above it up to the directory given in it.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 240) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 241) # Glob for all the possible Jamfiles according to the match pattern. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 242) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 243) jamfile_glob = None +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 244) if parent_root: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 245) parent = self.dir2parent_jamfile.get(dir) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 246) if not parent: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 247) parent = b2.util.path.glob_in_parents(dir, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 248) self.JAMFILE) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 249) self.dir2parent_jamfile[dir] = parent +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 250) jamfile_glob = parent +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 251) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 252) jamfile = self.dir2jamfile.get(dir) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 253) if not jamfile: +49c03622 (jhunold 2008-07-23 09:57:41 +0000 254) jamfile = b2.util.path.glob([dir], self.JAMFILE) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 255) self.dir2jamfile[dir] = jamfile +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 256) jamfile_glob = jamfile +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 257) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 258) if len(jamfile_glob): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 259) # Multiple Jamfiles found in the same place. Warn about this. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 260) # And ensure we use only one of them. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 261) # As a temporary convenience measure, if there's Jamfile.v2 amount +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 262) # found files, suppress the warning and use it. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 263) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 264) pattern = "(.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam)" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 265) v2_jamfiles = [x for x in jamfile_glob if re.match(pattern, x)] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 266) if len(v2_jamfiles) == 1: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 267) jamfile_glob = v2_jamfiles +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 268) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 269) print """warning: Found multiple Jamfiles at '%s'! +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 270) Loading the first one: '%s'.""" % (dir, jamfile_glob[0]) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 271) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 272) # Could not find it, error. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 273) if not no_errors and not jamfile_glob: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 274) self.manager.errors()( +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 275) """Unable to load Jamfile. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 276) Could not find a Jamfile in directory '%s' +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 277) Attempted to find it with pattern '%s'. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 278) Please consult the documentation at 'http://boost.org/b2.'.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 279) % (dir, string.join(self.JAMFILE))) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 280) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 281) return jamfile_glob[0] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 282) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 283) def load_jamfile(self, dir): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 284) """Load a Jamfile at the given directory. Returns nothing. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 285) Will attempt to load the file as indicated by the JAMFILE patterns. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 286) Effect of calling this rule twice with the same 'dir' is underfined.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 287) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 288) # See if the Jamfile is where it should be. +49c03622 (jhunold 2008-07-23 09:57:41 +0000 289) jamfile_to_load = b2.util.path.glob([dir], self.JAMROOT) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 290) if not jamfile_to_load: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 291) jamfile_to_load = self.find_jamfile(dir) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 292) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 293) jamfile_to_load = jamfile_to_load[0] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 294) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 295) # The module of the jamfile. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 296) dir = os.path.realpath(os.path.dirname(jamfile_to_load)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 297) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 298) jamfile_module = self.module_name (dir) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 299) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 300) # Initialize the jamfile module before loading. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 301) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 302) self.initialize(jamfile_module, dir, os.path.basename(jamfile_to_load)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 303) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 304) saved_project = self.current_project +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 305) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 306) self.used_projects[jamfile_module] = [] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 307) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 308) # Now load the Jamfile in it's own context. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 309) # Initialization might have load parent Jamfiles, which might have +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 310) # loaded the current Jamfile with use-project. Do a final check to make +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 311) # sure it's not loaded already. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 312) if not jamfile_module in self.jamfile_modules: +49c03622 (jhunold 2008-07-23 09:57:41 +0000 313) self.jamfile_modules[jamfile_module] = True +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 314) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 315) # FIXME: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 316) # mark-as-user $(jamfile-module) ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 317) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 318) bjam.call("load", jamfile_module, jamfile_to_load) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 319) basename = os.path.basename(jamfile_to_load) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 320) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 321) # Now do some checks +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 322) if self.current_project != saved_project: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 323) self.manager.errors()( +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 324) """The value of the .current-project variable +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 325) has magically changed after loading a Jamfile. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 326) This means some of the targets might be defined a the wrong project. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 327) after loading %s +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 328) expected value %s +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 329) actual value %s""" % (jamfile_module, saved_project, self.current_project)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 330) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 331) if self.global_build_dir: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 332) id = self.attribute(jamfile_module, "id") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 333) project_root = self.attribute(jamfile_module, "project-root") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 334) location = self.attribute(jamfile_module, "location") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 335) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 336) if location and project_root == dir: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 337) # This is Jamroot +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 338) if not id: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 339) # FIXME: go via errors module, so that contexts are +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 340) # shown? +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 341) print "warning: the --build-dir option was specified" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 342) print "warning: but Jamroot at '%s'" % dir +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 343) print "warning: specified no project id" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 344) print "warning: the --build-dir option will be ignored" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 345) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 346) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 347) def load_standalone(self, jamfile_module, file): +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 348) """Loads 'file' as standalone project that has no location +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 349) associated with it. This is mostly useful for user-config.jam, +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 350) which should be able to define targets, but although it has +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 351) some location in filesystem, we don't want any build to +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 352) happen in user's HOME, for example. +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 353) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 354) The caller is required to never call this method twice on +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 355) the same file. +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 356) """ +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 357) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 358) self.initialize(jamfile_module) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 359) self.used_projects[jamfile_module] = [] +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 360) bjam.call("load", jamfile_module, file) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 361) self.load_used_projects(jamfile_module) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 362) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 363) def is_jamroot(self, basename): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 364) match = [ pat for pat in self.JAMROOT if re.match(pat, basename)] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 365) if match: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 366) return 1 +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 367) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 368) return 0 +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 369) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 370) def initialize(self, module_name, location=None, basename=None): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 371) """Initialize the module for a project. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 372) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 373) module-name is the name of the project module. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 374) location is the location (directory) of the project to initialize. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 375) If not specified, stanalone project will be initialized +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 376) """ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 377) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 378) if "--debug-loading" in self.manager.argv(): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 379) print "Initializing project '%s'" % module_name +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 380) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 381) # TODO: need to consider if standalone projects can do anything but defining +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 382) # prebuilt targets. If so, we need to give more sensible "location", so that +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 383) # source paths are correct. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 384) if not location: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 385) location = "" +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 386) else: +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 387) location = b2.util.path.relpath(os.getcwd(), location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 388) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 389) attributes = ProjectAttributes(self.manager, location, module_name) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 390) self.module2attributes[module_name] = attributes +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 391) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 392) if location: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 393) attributes.set("source-location", location, exact=1) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 394) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 395) attributes.set("source-location", "", exact=1) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 396) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 397) attributes.set("requirements", property_set.empty(), exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 398) attributes.set("usage-requirements", property_set.empty(), exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 399) attributes.set("default-build", [], exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 400) attributes.set("projects-to-build", [], exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 401) attributes.set("project-root", None, exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 402) attributes.set("build-dir", None, exact=True) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 403) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 404) self.project_rules_.init_project(module_name) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 405) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 406) jamroot = False +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 407) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 408) parent_module = None; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 409) if module_name == "site-config": +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 410) # No parent +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 411) pass +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 412) elif module_name == "user-config": +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 413) parent_module = "site-config" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 414) elif location and not self.is_jamroot(basename): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 415) # We search for parent/project-root only if jamfile was specified +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 416) # --- i.e +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 417) # if the project is not standalone. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 418) parent_module = self.load_parent(location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 419) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 420) # It's either jamroot, or standalone project. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 421) # If it's jamroot, inherit from user-config. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 422) if location: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 423) parent_module = "user-config" ; +49c03622 (jhunold 2008-07-23 09:57:41 +0000 424) jamroot = True ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 425) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 426) if parent_module: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 427) self.inherit_attributes(module_name, parent_module) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 428) attributes.set("parent-module", parent_module, exact=1) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 429) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 430) if jamroot: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 431) attributes.set("project-root", location, exact=1) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 432) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 433) parent = None +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 434) if parent_module: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 435) parent = self.target(parent_module) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 436) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 437) if not self.module2target.has_key(module_name): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 438) target = b2.build.targets.ProjectTarget(self.manager, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 439) module_name, module_name, parent, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 440) self.attribute(module_name,"requirements"), +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 441) # FIXME: why we need to pass this? It's not +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 442) # passed in jam code. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 443) self.attribute(module_name, "default-build")) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 444) self.module2target[module_name] = target +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 445) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 446) self.current_project = self.target(module_name) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 447) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 448) def inherit_attributes(self, project_module, parent_module): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 449) """Make 'project-module' inherit attributes of project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 450) root and parent module.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 451) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 452) attributes = self.module2attributes[project_module] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 453) pattributes = self.module2attributes[parent_module] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 454) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 455) # Parent module might be locationless user-config. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 456) # FIXME: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 457) #if [ modules.binding $(parent-module) ] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 458) #{ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 459) # $(attributes).set parent : [ path.parent +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 460) # [ path.make [ modules.binding $(parent-module) ] ] ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 461) # } +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 462) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 463) attributes.set("project-root", pattributes.get("project-root"), exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 464) attributes.set("default-build", pattributes.get("default-build"), exact=True) +49c03622 (jhunold 2008-07-23 09:57:41 +0000 465) attributes.set("requirements", pattributes.get("requirements"), exact=True) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 466) attributes.set("usage-requirements", +cde6f09a (vladimir_prus 2007-10-19 23:12:33 +0000 467) pattributes.get("usage-requirements"), exact=1) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 468) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 469) parent_build_dir = pattributes.get("build-dir") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 470) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 471) if parent_build_dir: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 472) # Have to compute relative path from parent dir to our dir +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 473) # Convert both paths to absolute, since we cannot +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 474) # find relative path from ".." to "." +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 475) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 476) location = attributes.get("location") +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 477) parent_location = pattributes.get("location") +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 478) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 479) our_dir = os.path.join(os.getcwd(), location) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 480) parent_dir = os.path.join(os.getcwd(), parent_location) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 481) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 482) build_dir = os.path.join(parent_build_dir, +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 483) b2.util.path.relpath(parent_dir, +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 484) our_dir)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 485) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 486) def register_id(self, id, module): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 487) """Associate the given id with the given project module.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 488) self.id2module[id] = module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 489) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 490) def current(self): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 491) """Returns the project which is currently being loaded.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 492) return self.current_project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 493) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 494) def push_current(self, project): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 495) """Temporary changes the current project to 'project'. Should +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 496) be followed by 'pop-current'.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 497) self.saved_current_project.append(self.current_project) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 498) self.current_project = project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 499) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 500) def pop_current(self): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 501) self.current_project = self.saved_current_project[-1] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 502) del self.saved_current_project[-1] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 503) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 504) def attributes(self, project): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 505) """Returns the project-attribute instance for the +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 506) specified jamfile module.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 507) return self.module2attributes[project] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 508) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 509) def attribute(self, project, attribute): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 510) """Returns the value of the specified attribute in the +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 511) specified jamfile module.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 512) return self.module2attributes[project].get(attribute) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 513) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 514) def target(self, project_module): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 515) """Returns the project target corresponding to the 'project-module'.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 516) if not self.module2target[project_module]: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 517) self.module2target[project_module] = \ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 518) ProjectTarget(project_module, project_module, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 519) self.attribute(project_module, "requirements")) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 520) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 521) return self.module2target[project_module] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 522) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 523) def use(self, id, location): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 524) # Use/load a project. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 525) saved_project = self.current_project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 526) project_module = self.load(location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 527) declared_id = self.attribute(project_module, "id") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 528) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 529) if not declared_id or declared_id != id: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 530) # The project at 'location' either have no id or +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 531) # that id is not equal to the 'id' parameter. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 532) if self.id2module[id] and self.id2module[id] != project_module: +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 533) self.manager.errors()( +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 534) """Attempt to redeclare already existing project id '%s'""" % id) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 535) self.id2module[id] = project_module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 536) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 537) self.current_module = saved_project +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 538) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 539) def add_rule(self, name, callable): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 540) """Makes rule 'name' available to all subsequently loaded Jamfiles. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 541) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 542) Calling that rule wil relay to 'callable'.""" +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 543) self.project_rules_.add_rule(name, callable) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 544) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 545) def project_rules(self): +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 546) return self.project_rules_ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 547) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 548) def glob_internal(self, project, wildcards, excludes, rule_name): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 549) location = project.get("source-location") +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 550) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 551) result = [] +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 552) callable = b2.util.path.__dict__[rule_name] +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 553) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 554) paths = callable(location, wildcards, excludes) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 555) has_dir = 0 +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 556) for w in wildcards: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 557) if os.path.dirname(w): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 558) has_dir = 1 +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 559) break +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 560) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 561) if has_dir or rule_name != "glob": +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 562) # The paths we've found are relative to current directory, +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 563) # but the names specified in sources list are assumed to +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 564) # be relative to source directory of the corresponding +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 565) # prject. So, just make the name absolute. +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 566) result = [os.path.join(os.getcwd(), p) for p in paths] +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 567) else: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 568) # There were not directory in wildcard, so the files are all +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 569) # in the source directory of the project. Just drop the +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 570) # directory, instead of making paths absolute. +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 571) result = [os.path.basename(p) for p in paths] +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 572) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 573) return result +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 574) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 575) def load_module(self, name, extra_path=None): +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 576) """Classic Boost.Build 'modules' are in fact global variables. +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 577) Therefore, try to find an already loaded Python module called 'name' in sys.modules. +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 578) If the module ist not loaded, find it Boost.Build search +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 579) path and load it. The new module is not entered in sys.modules. +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 580) The motivation here is to have disjoint namespace of modules +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 581) loaded via 'import/using' in Jamfile, and ordinary Python +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 582) modules. We don't want 'using foo' in Jamfile to load ordinary +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 583) Python module 'foo' which is going to not work. And we +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 584) also don't want 'import foo' in regular Python module to +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 585) accidentally grab module named foo that is internal to +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 586) Boost.Build and intended to provide interface to Jamfiles.""" +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 587) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 588) existing = self.loaded_tool_modules_.get(name) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 589) if existing: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 590) return existing +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 591) +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 592) modules = sys.modules +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 593) for class_name in modules: +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 594) if name in class_name: +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 595) module = modules[class_name] +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 596) self.loaded_tool_modules_[name] = module +53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 597) return module +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 598) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 599) path = extra_path +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 600) if not path: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 601) path = [] +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 602) path.extend(self.manager.b2.path()) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 603) location = None +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 604) for p in path: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 605) l = os.path.join(p, name + ".py") +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 606) if os.path.exists(l): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 607) location = l +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 608) break +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 609) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 610) if not location: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 611) self.manager.errors()("Cannot find module '%s'" % name) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 612) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 613) mname = "__build_build_temporary__" +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 614) file = open(location) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 615) try: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 616) # TODO: this means we'll never make use of .pyc module, +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 617) # which might be a problem, or not. +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 618) module = imp.load_module(mname, file, os.path.basename(location), +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 619) (".py", "r", imp.PY_SOURCE)) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 620) del sys.modules[mname] +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 621) self.loaded_tool_modules_[name] = module +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 622) return module +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 623) finally: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 624) file.close() +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 625) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 626) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 627) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 628) # FIXME: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 629) # Defines a Boost.Build extension project. Such extensions usually +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 630) # contain library targets and features that can be used by many people. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 631) # Even though extensions are really projects, they can be initialize as +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 632) # a module would be with the "using" (project.project-rules.using) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 633) # mechanism. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 634) #rule extension ( id : options * : * ) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 635) #{ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 636) # # The caller is a standalone module for the extension. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 637) # local mod = [ CALLER_MODULE ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 638) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 639) # # We need to do the rest within the extension module. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 640) # module $(mod) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 641) # { +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 642) # import path ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 643) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 644) # # Find the root project. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 645) # local root-project = [ project.current ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 646) # root-project = [ $(root-project).project-module ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 647) # while +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 648) # [ project.attribute $(root-project) parent-module ] && +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 649) # [ project.attribute $(root-project) parent-module ] != user-config +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 650) # { +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 651) # root-project = [ project.attribute $(root-project) parent-module ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 652) # } +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 653) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 654) # # Create the project data, and bring in the project rules +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 655) # # into the module. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 656) # project.initialize $(__name__) : +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 657) # [ path.join [ project.attribute $(root-project) location ] ext $(1:L) ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 658) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 659) # # Create the project itself, i.e. the attributes. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 660) # # All extensions are created in the "/ext" project space. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 661) # project /ext/$(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 662) # local attributes = [ project.attributes $(__name__) ] ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 663) # +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 664) # # Inherit from the root project of whomever is defining us. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 665) # project.inherit-attributes $(__name__) : $(root-project) ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 666) # $(attributes).set parent-module : $(root-project) : exact ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 667) # } +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 668) #} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 669) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 670) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 671) class ProjectAttributes: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 672) """Class keeping all the attributes of a project. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 673) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 674) The standard attributes are 'id', "location", "project-root", "parent" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 675) "requirements", "default-build", "source-location" and "projects-to-build". +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 676) """ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 677) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 678) def __init__(self, manager, location, project_module): +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 679) self.manager = manager +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 680) self.location = location +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 681) self.project_module = project_module +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 682) self.attributes = {} +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 683) self.usage_requirements = None +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 684) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 685) def set(self, attribute, specification, exact): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 686) """Set the named attribute from the specification given by the user. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 687) The value actually set may be different.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 688) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 689) if exact: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 690) self.__dict__[attribute] = specification +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 691) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 692) elif attribute == "requirements": +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 693) self.requirements = property_set.refine_from_user_input( +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 694) self.requirements, specification, +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 695) self.project_module, self.location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 696) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 697) elif attribute == "usage-requirements": +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 698) unconditional = [] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 699) for p in specification: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 700) split = property.split_conditional(p) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 701) if split: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 702) unconditional.append(split[1]) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 703) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 704) unconditional.append(p) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 705) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 706) non_free = property.remove("free", unconditional) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 707) if non_free: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 708) pass +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 709) # FIXME: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 710) #errors.error "usage-requirements" $(specification) "have non-free properties" $(non-free) ; +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 711) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 712) t = property.translate_paths(specification, self.location) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 713) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 714) existing = self.__dict__.get("usage-requirements") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 715) if existing: +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 716) new = property_set.create(existing.raw() + t) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 717) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 718) new = property_set.create(t) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 719) self.__dict__["usage-requirements"] = new +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 720) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 721) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 722) elif attribute == "default-build": +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 723) self.__dict__["default-build"] = property_set.create(specification) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 724) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 725) elif attribute == "source-location": +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 726) source_location = [] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 727) for path in specification: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 728) source_location += os.path.join(self.location, path) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 729) self.__dict__["source-location"] = source_location +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 730) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 731) elif attribute == "build-dir": +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 732) self.__dict__["build-dir"] = os.path.join(self.location, specification) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 733) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 734) elif not attribute in ["id", "default-build", "location", +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 735) "source-location", "parent", +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 736) "projects-to-build", "project-root"]: +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 737) self.manager.errors()( +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 738) """Invalid project attribute '%s' specified +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 739) for project at '%s'""" % (attribute, self.location)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 740) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 741) self.__dict__[attribute] = specification +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 742) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 743) def get(self, attribute): +cde6f09a (vladimir_prus 2007-10-19 23:12:33 +0000 744) return self.__dict__[attribute] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 745) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 746) def dump(self): +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 747) """Prints the project attributes.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 748) id = self.get("id") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 749) if not id: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 750) id = "(none)" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 751) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 752) id = id[0] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 753) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 754) parent = self.get("parent") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 755) if not parent: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 756) parent = "(none)" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 757) else: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 758) parent = parent[0] +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 759) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 760) print "'%s'" % id +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 761) print "Parent project:%s", parent +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 762) print "Requirements:%s", self.get("requirements") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 763) print "Default build:%s", string.join(self.get("debuild-build")) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 764) print "Source location:%s", string.join(self.get("source-location")) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 765) print "Projects to build:%s", string.join(self.get("projects-to-build").sort()); +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 766) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 767) class ProjectRules: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 768) """Class keeping all rules that are made available to Jamfile.""" +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 769) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 770) def __init__(self, registry): +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 771) self.registry = registry +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 772) self.manager_ = registry.manager +38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 773) self.rules = {} +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 774) self.local_names = [x for x in self.__class__.__dict__ +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 775) if x not in ["__init__", "init_project", "add_rule", +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 776) "error_reporting_wrapper", "add_rule_for_type"]] +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 777) self.all_names_ = [x for x in self.local_names] +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 778) +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 779) def add_rule_for_type(self, type): +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 780) rule_name = type.lower(); +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 781) +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 782) def xpto (name, sources, requirements = [], default_build = None, usage_requirements = []): +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 783) return self.manager_.targets().create_typed_target( +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 784) type, self.registry.current(), name[0], sources, +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 785) requirements, default_build, usage_requirements) +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 786) +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 787) self.add_rule(type.lower(), xpto) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 788) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 789) def add_rule(self, name, callable): +38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 790) self.rules[name] = callable +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 791) self.all_names_.append(name) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 792) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 793) def all_names(self): +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 794) return self.all_names_ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 795) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 796) def call_and_report_errors(self, callable, *args): +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 797) result = None +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 798) try: +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 799) self.manager_.errors().push_jamfile_context() +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 800) result = callable(*args) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 801) except ExceptionWithUserContext, e: +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 802) e.report() +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 803) except Exception, e: +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 804) try: +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 805) self.manager_.errors().handle_stray_exception (e) +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 806) except ExceptionWithUserContext, e: +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 807) e.report() +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 808) finally: +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 809) self.manager_.errors().pop_jamfile_context() +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 810) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 811) return result +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 812) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 813) def make_wrapper(self, callable): +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 814) """Given a free-standing function 'callable', return a new +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 815) callable that will call 'callable' and report all exceptins, +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 816) using 'call_and_report_errors'.""" +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 817) def wrapper(*args): +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 818) self.call_and_report_errors(callable, *args) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 819) return wrapper +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 820) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 821) def init_project(self, project_module): +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 822) +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 823) for n in self.local_names: +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 824) # Using 'getattr' here gives us a bound method, +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 825) # while using self.__dict__[r] would give unbound one. +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 826) v = getattr(self, n) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 827) if callable(v): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 828) if n == "import_": +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 829) n = "import" +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 830) else: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 831) n = string.replace(n, "_", "-") +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 832) +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 833) bjam.import_rule(project_module, n, +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 834) self.make_wrapper(v)) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 835) +38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 836) for n in self.rules: +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 837) bjam.import_rule(project_module, n, +0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 838) self.make_wrapper(self.rules[n])) +38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 839) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 840) def project(self, *args): +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 841) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 842) jamfile_module = self.registry.current().project_module() +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 843) attributes = self.registry.attributes(jamfile_module) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 844) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 845) id = None +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 846) if args and args[0]: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 847) id = args[0][0] +092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 848) args = args[1:] +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 849) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 850) if id: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 851) if id[0] != '/': +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 852) id = '/' + id +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 853) self.registry.register_id (id, jamfile_module) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 854) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 855) explicit_build_dir = None +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 856) for a in args: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 857) if a: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 858) attributes.set(a[0], a[1:], exact=0) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 859) if a[0] == "build-dir": +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 860) explicit_build_dir = a[1] +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 861) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 862) # If '--build-dir' is specified, change the build dir for the project. +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 863) if self.registry.global_build_dir: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 864) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 865) location = attributes.get("location") +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 866) # Project with empty location is 'standalone' project, like +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 867) # user-config, or qt. It has no build dir. +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 868) # If we try to set build dir for user-config, we'll then +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 869) # try to inherit it, with either weird, or wrong consequences. +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 870) if location and location == attributes.get("project-root"): +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 871) # This is Jamroot. +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 872) if id: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 873) if explicit_build_dir and os.path.isabs(explicit_build_dir): +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 874) self.register.manager.errors()( +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 875) """Absolute directory specified via 'build-dir' project attribute +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 876) Don't know how to combine that with the --build-dir option.""") +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 877) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 878) rid = id +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 879) if rid[0] == '/': +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 880) rid = rid[1:] +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 881) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 882) p = os.path.join(self.registry.global_build_dir, +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 883) rid, explicit_build_dir) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 884) attributes.set("build-dir", p, exact=1) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 885) elif explicit_build_dir: +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 886) self.registry.manager.errors()( +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 887) """When --build-dir is specified, the 'build-project' +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 888) attribute is allowed only for top-level 'project' invocations""") +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 889) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 890) def constant(self, name, value): +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 891) """Declare and set a project global constant. +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 892) Project global constants are normal variables but should +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 893) not be changed. They are applied to every child Jamfile.""" +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 894) m = "Jamfile</home/ghost/Work/Boost/boost-svn/tools/build/v2_python/python/tests/bjam/make>" +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 895) self.registry.current().add_constant(name[0], value) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 896) +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 897) def path_constant(self, name, value): +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 898) """Declare and set a project global constant, whose value is a path. The +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 899) path is adjusted to be relative to the invocation directory. The given +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 900) value path is taken to be either absolute, or relative to this project +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 901) root.""" +0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 902) self.registry.current().add_constant(name[0], value, path=1) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 903) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 904) def use_project(self, id, where): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 905) # See comment in 'load' for explanation why we record the +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 906) # parameters as opposed to loading the project now. +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 907) m = self.registry.current().project_module(); +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 908) self.registry.used_projects[m].append((id, where)) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 909) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 910) def build_project(self, dir): +1674e2d9 (jhunold 2008-08-08 19:52:05 +0000 911) assert(isinstance(dir, list)) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 912) jamfile_module = self.registry.current().project_module() +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 913) attributes = self.registry.attributes(jamfile_module) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 914) now = attributes.get("projects-to-build") +1674e2d9 (jhunold 2008-08-08 19:52:05 +0000 915) attributes.set("projects-to-build", now + dir, exact=True) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 916) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 917) def explicit(self, target_names): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 918) t = self.registry.current() +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 919) for n in target_names: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 920) t.mark_target_as_explicit(n) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 921) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 922) def glob(self, wildcards, excludes=None): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 923) return self.registry.glob_internal(self.registry.current(), +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 924) wildcards, excludes, "glob") +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 925) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 926) def glob_tree(self, wildcards, excludes=None): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 927) bad = 0 +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 928) for p in wildcards: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 929) if os.path.dirname(p): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 930) bad = 1 +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 931) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 932) if excludes: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 933) for p in excludes: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 934) if os.path.dirname(p): +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 935) bad = 1 +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 936) +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 937) if bad: +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 938) self.registry.manager().errors()( +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 939) "The patterns to 'glob-tree' may not include directory") +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 940) return self.registry.glob_internal(self.registry.current(), +2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 941) wildcards, excludes, "glob_tree") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 942) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 943) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 944) def using(self, toolset, *args): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 945) # The module referred by 'using' can be placed in +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 946) # the same directory as Jamfile, and the user +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 947) # will expect the module to be found even though +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 948) # the directory is not in BOOST_BUILD_PATH. +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 949) # So temporary change the search path. +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 950) jamfile_module = self.registry.current().project_module() +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 951) attributes = self.registry.attributes(jamfile_module) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 952) location = attributes.get("location") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 953) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 954) m = self.registry.load_module(toolset[0], [location]) +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 955) if not m.__dict__.has_key("init"): +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 956) self.registry.manager.errors()( +7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 957) "Tool module '%s' does not define the 'init' method" % toolset[0]) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 958) m.init(*args) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 959) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 960) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 961) def import_(self, name, names_to_import=None, local_names=None): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 962) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 963) name = name[0] +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 964) jamfile_module = self.registry.current().project_module() +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 965) attributes = self.registry.attributes(jamfile_module) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 966) location = attributes.get("location") +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 967) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 968) m = self.registry.load_module(name, [location]) +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 969) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 970) for f in m.__dict__: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 971) v = m.__dict__[f] +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 972) if callable(v): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 973) bjam.import_rule(jamfile_module, name + "." + f, v) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 974) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 975) if names_to_import: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 976) if not local_names: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 977) local_names = names_to_import +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 978) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 979) if len(names_to_import) != len(local_names): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 980) self.registry.manager.errors()( +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 981) """The number of names to import and local names do not match.""") +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 982) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 983) for n, l in zip(names_to_import, local_names): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 984) bjam.import_rule(jamfile_module, l, m.__dict__[n]) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 985) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 986) def conditional(self, condition, requirements): +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 987) """Calculates conditional requirements for multiple requirements +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 988) at once. This is a shorthand to be reduce duplication and to +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 989) keep an inline declarative syntax. For example: +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 990) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 991) lib x : x.cpp : [ conditional <toolset>gcc <variant>debug : +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 992) <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ; +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 993) """ +f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 994) +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 995) c = string.join(condition, ",") +f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 996) return [c + ":" + r for r in requirements] diff --git a/jam-files/boost-build/build/project.jam b/jam-files/boost-build/build/project.jam new file mode 100644 index 00000000..c9967613 --- /dev/null +++ b/jam-files/boost-build/build/project.jam @@ -0,0 +1,1110 @@ +# Copyright 2002, 2003 Dave Abrahams +# Copyright 2002, 2005, 2006 Rene Rivera +# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Implements project representation and loading. Each project is represented by: +# - a module where all the Jamfile content live. +# - an instance of 'project-attributes' class. +# (given a module name, can be obtained using the 'attributes' rule) +# - an instance of 'project-target' class (from targets.jam) +# (given a module name, can be obtained using the 'target' rule) +# +# Typically, projects are created as result of loading a Jamfile, which is done +# by rules 'load' and 'initialize', below. First, module for Jamfile is loaded +# and new project-attributes instance is created. Some rules necessary for +# project are added to the module (see 'project-rules' module) at the bottom of +# this file. Default project attributes are set (inheriting attributes of parent +# project, if it exists). After that the Jamfile is read. It can declare its own +# attributes using the 'project' rule which will be combined with any already +# set attributes. +# +# The 'project' rule can also declare a project id which will be associated with +# the project module. +# +# There can also be 'standalone' projects. They are created by calling +# 'initialize' on an arbitrary module and not specifying their location. After +# the call, the module can call the 'project' rule, declare main targets and +# behave as a regular project except that, since it is not associated with any +# location, it should not declare targets that are not prebuilt. +# +# The list of all loaded Jamfile is stored in the .project-locations variable. +# It is possible to obtain a module name for a location using the 'module-name' +# rule. Standalone projects are not recorded and can only be referenced using +# their project id. + +import "class" : new ; +import errors ; +import modules ; +import path ; +import print ; +import property-set ; +import sequence ; + + +# Loads the Jamfile at the given location. After loading, project global file +# and Jamfiles needed by the requested one will be loaded recursively. If the +# Jamfile at that location is loaded already, does nothing. Returns the project +# module for the Jamfile. +# +rule load ( jamfile-location ) +{ + if --debug-loading in [ modules.peek : ARGV ] + { + ECHO "Loading Jamfile at" '$(jamfile-location)' ; + } + + local module-name = [ module-name $(jamfile-location) ] ; + # If Jamfile is already loaded, don't try again. + if ! $(module-name) in $(.jamfile-modules) + { + load-jamfile $(jamfile-location) : $(module-name) ; + + # We want to make sure that child project are loaded only after parent + # projects. In particular, because parent projects define attributes + # which are inherited by children, and we don't want children to be + # loaded before parent has defined everything. + # + # While "build-project" and "use-project" can potentially refer to child + # projects from parent projects, we don't immediately load child + # projects when seeing those attributes. Instead, we record the minimal + # information to be used only later. + load-used-projects $(module-name) ; + } + return $(module-name) ; +} + + +rule load-used-projects ( module-name ) +{ + local used = [ modules.peek $(module-name) : .used-projects ] ; + local location = [ attribute $(module-name) location ] ; + import project ; + while $(used) + { + local id = $(used[1]) ; + local where = $(used[2]) ; + + project.use $(id) : [ path.root [ path.make $(where) ] $(location) ] ; + used = $(used[3-]) ; + } +} + + +# Note the use of character groups, as opposed to listing 'Jamroot' and +# 'jamroot'. With the latter, we would get duplicate matches on Windows and +# would have to eliminate duplicates. +JAMROOT ?= [ modules.peek : JAMROOT ] ; +JAMROOT ?= project-root.jam [Jj]amroot [Jj]amroot.jam ; + + +# Loads parent of Jamfile at 'location'. Issues an error if nothing is found. +# +rule load-parent ( location ) +{ + local found = [ path.glob-in-parents $(location) : $(JAMROOT) $(JAMFILE) ] ; + + if ! $(found) + { + ECHO error: Could not find parent for project at '$(location)' ; + EXIT error: Did not find Jamfile.jam or Jamroot.jam in any parent + directory. ; + } + + return [ load $(found[1]:D) ] ; +} + + +# Makes the specified 'module' act as if it were a regularly loaded Jamfile at +# 'location'. Reports an error if a Jamfile has already been loaded for that +# location. +# +rule act-as-jamfile ( module : location ) +{ + if [ module-name $(location) ] in $(.jamfile-modules) + { + errors.error "Jamfile was already loaded for '$(location)'" ; + } + # Set up non-default mapping from location to module. + .module.$(location) = $(module) ; + + # Add the location to the list of project locations so that we don't try to + # reload the same Jamfile in the future. + .jamfile-modules += [ module-name $(location) ] ; + + initialize $(module) : $(location) ; +} + + +# Returns the project module corresponding to the given project-id or plain +# directory name. Returns nothing if such a project can not be found. +# +rule find ( name : current-location ) +{ + local project-module ; + + # Try interpreting name as project id. + if [ path.is-rooted $(name) ] + { + project-module = $($(name).jamfile-module) ; + } + + if ! $(project-module) + { + local location = [ path.root [ path.make $(name) ] $(current-location) ] + ; + + # If no project is registered for the given location, try to load it. + # First see if we have a Jamfile. If not, then see if we might have a + # project root willing to act as a Jamfile. In that case, project root + # must be placed in the directory referred by id. + + project-module = [ module-name $(location) ] ; + if ! $(project-module) in $(.jamfile-modules) + { + if [ path.glob $(location) : $(JAMROOT) $(JAMFILE) ] + { + project-module = [ load $(location) ] ; + } + else + { + project-module = ; + } + } + } + + return $(project-module) ; +} + + +# Returns the name of the module corresponding to 'jamfile-location'. If no +# module corresponds to that location yet, associates the default module name +# with that location. +# +rule module-name ( jamfile-location ) +{ + if ! $(.module.$(jamfile-location)) + { + # Root the path, so that locations are always unambiguous. Without this, + # we can't decide if '../../exe/program1' and '.' are the same paths. + jamfile-location = [ path.root $(jamfile-location) [ path.pwd ] ] ; + .module.$(jamfile-location) = Jamfile<$(jamfile-location)> ; + } + return $(.module.$(jamfile-location)) ; +} + + +# Default patterns to search for the Jamfiles to use for build declarations. +# +JAMFILE = [ modules.peek : JAMFILE ] ; +JAMFILE ?= [Bb]uild.jam [Jj]amfile.v2 [Jj]amfile [Jj]amfile.jam ; + + +# Find the Jamfile at the given location. This returns the exact names of all +# the Jamfiles in the given directory. The optional parent-root argument causes +# this to search not the given directory but the ones above it up to the +# directory given in it. +# +rule find-jamfile ( + dir # The directory(s) to look for a Jamfile. + parent-root ? # Optional flag indicating to search for the parent Jamfile. + : no-errors ? + ) +{ + # Glob for all the possible Jamfiles according to the match pattern. + # + local jamfile-glob = ; + if $(parent-root) + { + if ! $(.parent-jamfile.$(dir)) + { + .parent-jamfile.$(dir) = [ path.glob-in-parents $(dir) : $(JAMFILE) + ] ; + } + jamfile-glob = $(.parent-jamfile.$(dir)) ; + } + else + { + if ! $(.jamfile.$(dir)) + { + .jamfile.$(dir) = [ path.glob $(dir) : $(JAMFILE) ] ; + } + jamfile-glob = $(.jamfile.$(dir)) ; + + } + + local jamfile-to-load = $(jamfile-glob) ; + # Multiple Jamfiles found in the same place. Warn about this and ensure we + # use only one of them. As a temporary convenience measure, if there is + # Jamfile.v2 among found files, suppress the warning and use it. + # + if $(jamfile-to-load[2-]) + { + local v2-jamfiles = [ MATCH (.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam) : $(jamfile-to-load) ] ; + + if $(v2-jamfiles) && ! $(v2-jamfiles[2]) + { + jamfile-to-load = $(v2-jamfiles) ; + } + else + { + local jamfile = [ path.basename $(jamfile-to-load[1]) ] ; + ECHO "warning: Found multiple Jamfiles at '"$(dir)"'!" + "Loading the first one: '$(jamfile)'." ; + } + + jamfile-to-load = $(jamfile-to-load[1]) ; + } + + # Could not find it, error. + # + if ! $(no-errors) && ! $(jamfile-to-load) + { + errors.error Unable to load Jamfile. + : Could not find a Jamfile in directory '$(dir)'. + : Attempted to find it with pattern '"$(JAMFILE:J=" ")"'. + : Please consult the documentation at 'http://www.boost.org'. ; + } + + return $(jamfile-to-load) ; +} + + +# Load a Jamfile at the given directory. Returns nothing. Will attempt to load +# the file as indicated by the JAMFILE patterns. Effect of calling this rule +# twice with the same 'dir' is undefined. +# +local rule load-jamfile ( + dir # The directory of the project Jamfile. + : jamfile-module + ) +{ + # See if the Jamfile is where it should be. + # + local jamfile-to-load = [ path.glob $(dir) : $(JAMROOT) ] ; + if ! $(jamfile-to-load) + { + jamfile-to-load = [ find-jamfile $(dir) ] ; + } + + if $(jamfile-to-load[2]) + { + errors.error "Multiple Jamfiles found at '$(dir)'" + : "Filenames are: " $(jamfile-to-load:D=) ; + } + + # Now load the Jamfile in it's own context. + # The call to 'initialize' may load parent Jamfile, which might have + # 'use-project' statement that causes a second attempt to load the + # same project we're loading now. Checking inside .jamfile-modules + # prevents that second attempt from messing up. + if ! $(jamfile-module) in $(.jamfile-modules) + { + .jamfile-modules += $(jamfile-module) ; + + # Initialize the Jamfile module before loading. + # + initialize $(jamfile-module) : [ path.parent $(jamfile-to-load) ] + : $(jamfile-to-load:BS) ; + + local saved-project = $(.current-project) ; + + mark-as-user $(jamfile-module) ; + modules.load $(jamfile-module) : [ path.native $(jamfile-to-load) ] : . ; + if [ MATCH ($(JAMROOT)) : $(jamfile-to-load:BS) ] + { + jamfile = [ find-jamfile $(dir) : no-errors ] ; + if $(jamfile) + { + load-aux $(jamfile-module) : [ path.native $(jamfile) ] ; + } + } + + # Now do some checks. + if $(.current-project) != $(saved-project) + { + errors.error "The value of the .current-project variable has magically" + : "changed after loading a Jamfile. This means some of the targets" + : "might be defined in the wrong project." + : "after loading" $(jamfile-module) + : "expected value" $(saved-project) + : "actual value" $(.current-project) ; + } + + if $(.global-build-dir) + { + local id = [ attribute $(jamfile-module) id ] ; + local project-root = [ attribute $(jamfile-module) project-root ] ; + local location = [ attribute $(jamfile-module) location ] ; + + if $(location) && $(project-root) = $(dir) + { + # This is Jamroot. + if ! $(id) + { + ECHO "warning: the --build-dir option was specified" ; + ECHO "warning: but Jamroot at '$(dir)'" ; + ECHO "warning: specified no project id" ; + ECHO "warning: the --build-dir option will be ignored" ; + } + } + } + } +} + + +rule mark-as-user ( module-name ) +{ + if USER_MODULE in [ RULENAMES ] + { + USER_MODULE $(module-name) ; + } +} + + +rule load-aux ( module-name : file ) +{ + mark-as-user $(module-name) ; + + module $(module-name) + { + include $(2) ; + local rules = [ RULENAMES $(1) ] ; + IMPORT $(1) : $(rules) : $(1) : $(1).$(rules) ; + } +} + + +.global-build-dir = [ MATCH --build-dir=(.*) : [ modules.peek : ARGV ] ] ; +if $(.global-build-dir) +{ + # If the option is specified several times, take the last value. + .global-build-dir = [ path.make $(.global-build-dir[-1]) ] ; +} + + +# Initialize the module for a project. +# +rule initialize ( + module-name # The name of the project module. + : location ? # The location (directory) of the project to initialize. If + # not specified, a standalone project will be initialized. + : basename ? + ) +{ + if --debug-loading in [ modules.peek : ARGV ] + { + ECHO "Initializing project '$(module-name)'" ; + } + + # TODO: need to consider if standalone projects can do anything but define + # prebuilt targets. If so, we need to give it a more sensible "location", so + # that source paths are correct. + location ?= "" ; + # Create the module for the Jamfile first. + module $(module-name) + { + } + $(module-name).attributes = [ new project-attributes $(location) + $(module-name) ] ; + local attributes = $($(module-name).attributes) ; + + if $(location) + { + $(attributes).set source-location : [ path.make $(location) ] : exact ; + } + else if ! $(module-name) in test-config site-config user-config project-config + { + # This is a standalone project with known location. Set source location + # so that it can declare targets. This is intended so that you can put + # a .jam file in your sources and use it via 'using'. Standard modules + # (in 'tools' subdir) may not assume source dir is set. + local s = [ modules.binding $(module-name) ] ; + if ! $(s) + { + errors.error "Could not determine project location $(module-name)" ; + } + $(attributes).set source-location : $(s:D) : exact ; + } + + $(attributes).set requirements : [ property-set.empty ] : exact ; + $(attributes).set usage-requirements : [ property-set.empty ] : exact ; + + # Import rules common to all project modules from project-rules module, + # defined at the end of this file. + local rules = [ RULENAMES project-rules ] ; + IMPORT project-rules : $(rules) : $(module-name) : $(rules) ; + + local jamroot ; + + local parent-module ; + if $(module-name) = test-config + { + # No parent. + } + else if $(module-name) = site-config + { + parent-module = test-config ; + } + else if $(module-name) = user-config + { + parent-module = site-config ; + } + else if $(module-name) = project-config + { + parent-module = user-config ; + } + else + { + # We search for parent/project-root only if Jamfile was specified, i.e. + # if the project is not standalone. + if $(location) && ! [ MATCH ($(JAMROOT)) : $(basename) ] + { + parent-module = [ load-parent $(location) ] ; + } + else + { + # It's either jamroot or standalone project. If it's jamroot, + # inherit from user-config. + if $(location) + { + # If project-config module exist, inherit from it. + if $(project-config.attributes) + { + parent-module = project-config ; + } + else + { + parent-module = user-config ; + } + jamroot = true ; + } + } + } + + if $(parent-module) + { + inherit-attributes $(module-name) : $(parent-module) ; + $(attributes).set parent-module : $(parent-module) : exact ; + } + + if $(jamroot) + { + $(attributes).set project-root : $(location) : exact ; + } + + local parent ; + if $(parent-module) + { + parent = [ target $(parent-module) ] ; + } + + if ! $(.target.$(module-name)) + { + .target.$(module-name) = [ new project-target $(module-name) + : $(module-name) $(parent) + : [ attribute $(module-name) requirements ] ] ; + + if --debug-loading in [ modules.peek : ARGV ] + { + ECHO "Assigned project target" $(.target.$(module-name)) + "to '$(module-name)'" ; + } + } + + .current-project = [ target $(module-name) ] ; +} + + +# Make 'project-module' inherit attributes of project root and parent module. +# +rule inherit-attributes ( project-module : parent-module ) +{ + local attributes = $($(project-module).attributes) ; + local pattributes = [ attributes $(parent-module) ] ; + # Parent module might be locationless configuration module. + if [ modules.binding $(parent-module) ] + { + $(attributes).set parent : [ path.parent + [ path.make [ modules.binding $(parent-module) ] ] ] ; + } + local v = [ $(pattributes).get project-root ] ; + $(attributes).set project-root : $(v) : exact ; + $(attributes).set default-build + : [ $(pattributes).get default-build ] ; + $(attributes).set requirements + : [ $(pattributes).get requirements ] : exact ; + $(attributes).set usage-requirements + : [ $(pattributes).get usage-requirements ] : exact ; + + local parent-build-dir = [ $(pattributes).get build-dir ] ; + if $(parent-build-dir) + { + # Have to compute relative path from parent dir to our dir. Convert both + # paths to absolute, since we cannot find relative path from ".." to + # ".". + + local location = [ attribute $(project-module) location ] ; + local parent-location = [ attribute $(parent-module) location ] ; + + local pwd = [ path.pwd ] ; + local parent-dir = [ path.root $(parent-location) $(pwd) ] ; + local our-dir = [ path.root $(location) $(pwd) ] ; + $(attributes).set build-dir : [ path.join $(parent-build-dir) + [ path.relative $(our-dir) $(parent-dir) ] ] : exact ; + } +} + + +# Associate the given id with the given project module. +# +rule register-id ( id : module ) +{ + $(id).jamfile-module = $(module) ; +} + + +# Class keeping all the attributes of a project. +# +# The standard attributes are "id", "location", "project-root", "parent" +# "requirements", "default-build", "source-location" and "projects-to-build". +# +class project-attributes +{ + import property ; + import property-set ; + import errors ; + import path ; + import print ; + import sequence ; + import project ; + + rule __init__ ( location project-module ) + { + self.location = $(location) ; + self.project-module = $(project-module) ; + } + + # Set the named attribute from the specification given by the user. The + # value actually set may be different. + # + rule set ( attribute : specification * + : exact ? # Sets value from 'specification' without any processing. + ) + { + if $(exact) + { + self.$(attribute) = $(specification) ; + } + else if $(attribute) = "requirements" + { + local result = [ property-set.refine-from-user-input + $(self.requirements) : $(specification) + : $(self.project-module) : $(self.location) ] ; + + if $(result[1]) = "@error" + { + errors.error Requirements for project at '$(self.location)' + conflict with parent's. : Explanation: $(result[2-]) ; + } + else + { + self.requirements = $(result) ; + } + } + else if $(attribute) = "usage-requirements" + { + local unconditional ; + for local p in $(specification) + { + local split = [ property.split-conditional $(p) ] ; + split ?= nothing $(p) ; + unconditional += $(split[2]) ; + } + + local non-free = [ property.remove free : $(unconditional) ] ; + if $(non-free) + { + errors.error usage-requirements $(specification) have non-free + properties $(non-free) ; + } + local t = [ property.translate-paths $(specification) + : $(self.location) ] ; + if $(self.usage-requirements) + { + self.usage-requirements = [ property-set.create + [ $(self.usage-requirements).raw ] $(t) ] ; + } + else + { + self.usage-requirements = [ property-set.create $(t) ] ; + } + } + else if $(attribute) = "default-build" + { + self.default-build = [ property.make $(specification) ] ; + } + else if $(attribute) = "source-location" + { + self.source-location = ; + for local src-path in $(specification) + { + self.source-location += [ path.root [ path.make $(src-path) ] + $(self.location) ] ; + } + } + else if $(attribute) = "build-dir" + { + self.build-dir = [ path.root + [ path.make $(specification) ] $(self.location) ] ; + } + else if $(attribute) = "id" + { + id = [ path.root $(specification) / ] ; + project.register-id $(id) : $(self.project-module) ; + self.id = $(id) ; + } + else if ! $(attribute) in "default-build" "location" "parent" + "projects-to-build" "project-root" "source-location" + { + errors.error Invalid project attribute '$(attribute)' specified for + project at '$(self.location)' ; + } + else + { + self.$(attribute) = $(specification) ; + } + } + + # Returns the value of the given attribute. + # + rule get ( attribute ) + { + return $(self.$(attribute)) ; + } + + # Prints the project attributes. + # + rule print ( ) + { + local id = $(self.id) ; id ?= (none) ; + local parent = $(self.parent) ; parent ?= (none) ; + print.section "'"$(id)"'" ; + print.list-start ; + print.list-item "Parent project:" $(parent) ; + print.list-item "Requirements:" [ $(self.requirements).raw ] ; + print.list-item "Default build:" $(self.default-build) ; + print.list-item "Source location:" $(self.source-location) ; + print.list-item "Projects to build:" + [ sequence.insertion-sort $(self.projects-to-build) ] ; + print.list-end ; + } +} + + +# Returns the project which is currently being loaded. +# +rule current ( ) +{ + return $(.current-project) ; +} + + +# Temporarily changes the current project to 'project'. Should be followed by +# 'pop-current'. +# +rule push-current ( project ) +{ + .saved-current-project += $(.current-project) ; + .current-project = $(project) ; +} + + +rule pop-current ( ) +{ + .current-project = $(.saved-current-project[-1]) ; + .saved-current-project = $(.saved-current-project[1--2]) ; +} + + +# Returns the project-attribute instance for the specified Jamfile module. +# +rule attributes ( project ) +{ + return $($(project).attributes) ; +} + + +# Returns the value of the specified attribute in the specified Jamfile module. +# +rule attribute ( project attribute ) +{ + return [ $($(project).attributes).get $(attribute) ] ; +} + + +# Returns the project target corresponding to the 'project-module'. +# +rule target ( project-module ) +{ + if ! $(.target.$(project-module)) + { + .target.$(project-module) = [ new project-target $(project-module) + : $(project-module) + : [ attribute $(project-module) requirements ] ] ; + } + return $(.target.$(project-module)) ; +} + + +# Use/load a project. +# +rule use ( id : location ) +{ + local saved-project = $(.current-project) ; + local project-module = [ project.load $(location) ] ; + local declared-id = [ project.attribute $(project-module) id ] ; + + if ! $(declared-id) || $(declared-id) != $(id) + { + # The project at 'location' either has no id or that id is not equal to + # the 'id' parameter. + if $($(id).jamfile-module) && ( $($(id).jamfile-module) != + $(project-module) ) + { + errors.user-error Attempt to redeclare already existing project id + '$(id)' + location '$(location)' ; + } + $(id).jamfile-module = $(project-module) ; + } + .current-project = $(saved-project) ; +} + + +# Defines a Boost.Build extension project. Such extensions usually contain +# library targets and features that can be used by many people. Even though +# extensions are really projects, they can be initialized as a module would be +# with the "using" (project.project-rules.using) mechanism. +# +rule extension ( id : options * : * ) +{ + # The caller is a standalone module for the extension. + local mod = [ CALLER_MODULE ] ; + + # We need to do the rest within the extension module. + module $(mod) + { + import path ; + + # Find the root project. + local root-project = [ project.current ] ; + root-project = [ $(root-project).project-module ] ; + while + [ project.attribute $(root-project) parent-module ] && + [ project.attribute $(root-project) parent-module ] != user-config + { + root-project = [ project.attribute $(root-project) parent-module ] ; + } + + # Create the project data, and bring in the project rules into the + # module. + project.initialize $(__name__) : [ path.join [ project.attribute + $(root-project) location ] ext $(1:L) ] ; + + # Create the project itself, i.e. the attributes. All extensions are + # created in the "/ext" project space. + project /ext/$(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : + $(9) ; + local attributes = [ project.attributes $(__name__) ] ; + + # Inherit from the root project of whomever is defining us. + project.inherit-attributes $(__name__) : $(root-project) ; + $(attributes).set parent-module : $(root-project) : exact ; + } +} + + +rule glob-internal ( project : wildcards + : excludes * : rule-name ) +{ + local location = [ $(project).get source-location ] ; + + local result ; + local paths = [ path.$(rule-name) $(location) : + [ sequence.transform path.make : $(wildcards) ] : + [ sequence.transform path.make : $(excludes) ] ] ; + if $(wildcards:D) || $(rule-name) != glob + { + # The paths we have found are relative to the current directory, but the + # names specified in the sources list are assumed to be relative to the + # source directory of the corresponding project. So, just make the names + # absolute. + for local p in $(paths) + { + # If the path is below source location, use relative path. + # Otherwise, use full path just to avoid any ambiguities. + local rel = [ path.relative $(p) $(location) : no-error ] ; + if $(rel) = not-a-child + { + result += [ path.root $(p) [ path.pwd ] ] ; + } + else + { + result += $(rel) ; + } + } + } + else + { + # There were no wildcards in the directory path, so the files are all in + # the source directory of the project. Just drop the directory, instead + # of making paths absolute. + result = $(paths:D="") ; + } + + return $(result) ; +} + + +# This module defines rules common to all projects. +# +module project-rules +{ + rule using ( toolset-module : * ) + { + import toolset ; + import modules ; + import project ; + + # Temporarily change the search path so the module referred to by + # 'using' can be placed in the same directory as Jamfile. User will + # expect the module to be found even though the directory is not in + # BOOST_BUILD_PATH. + local x = [ modules.peek : BOOST_BUILD_PATH ] ; + local caller = [ CALLER_MODULE ] ; + local caller-location = [ modules.binding $(caller) ] ; + modules.poke : BOOST_BUILD_PATH : $(caller-location:D) $(x) ; + toolset.using $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + modules.poke : BOOST_BUILD_PATH : $(x) ; + + # The above might have clobbered .current-project. Restore the correct + # value. + modules.poke project : .current-project + : [ project.target $(caller) ] ; + } + + import modules ; + + rule import ( * : * : * ) + { + modules.import project ; + + local caller = [ CALLER_MODULE ] ; + local saved = [ modules.peek project : .current-project ] ; + module $(caller) + { + modules.import $(1) : $(2) : $(3) ; + } + modules.poke project : .current-project : $(saved) ; + } + + rule project ( id ? : options * : * ) + { + import errors ; + import path ; + import project ; + + local caller = [ CALLER_MODULE ] ; + local attributes = [ project.attributes $(caller) ] ; + if $(id) + { + $(attributes).set id : $(id) ; + } + + local explicit-build-dir ; + + for n in 2 3 4 5 6 7 8 9 + { + local option = $($(n)) ; + if $(option) + { + $(attributes).set $(option[1]) : $(option[2-]) ; + } + if $(option[1]) = "build-dir" + { + explicit-build-dir = [ path.make $(option[2-]) ] ; + } + } + + # If '--build-dir' is specified, change the build dir for the project. + local global-build-dir = + [ modules.peek project : .global-build-dir ] ; + + if $(global-build-dir) + { + local location = [ $(attributes).get location ] ; + # Project with an empty location is a 'standalone' project such as + # user-config or qt. It has no build dir. If we try to set build dir + # for user-config, we shall then try to inherit it, with either + # weird or wrong consequences. + if $(location) && $(location) = [ $(attributes).get project-root ] + { + # Re-read the project id, since it might have been changed in + # the project's attributes. + id = [ $(attributes).get id ] ; + # This is Jamroot. + if $(id) + { + if $(explicit-build-dir) && + [ path.is-rooted $(explicit-build-dir) ] + { + errors.user-error Absolute directory specified via + 'build-dir' project attribute : Do not know how to + combine that with the --build-dir option. ; + } + # Strip the leading slash from id. + local rid = [ MATCH /(.*) : $(id) ] ; + local p = [ path.join + $(global-build-dir) $(rid) $(explicit-build-dir) ] ; + + $(attributes).set build-dir : $(p) : exact ; + } + } + else + { + # Not Jamroot. + if $(explicit-build-dir) + { + errors.user-error When --build-dir is specified, the + 'build-dir' project : attribute is allowed only for + top-level 'project' invocations ; + } + } + } + } + + # Declare and set a project global constant. Project global constants are + # normal variables but should not be changed. They are applied to every + # child Jamfile. + # + rule constant ( + name # Variable name of the constant. + : value + # Value of the constant. + ) + { + import project ; + local caller = [ CALLER_MODULE ] ; + local p = [ project.target $(caller) ] ; + $(p).add-constant $(name) : $(value) ; + } + + # Declare and set a project global constant, whose value is a path. The path + # is adjusted to be relative to the invocation directory. The given value + # path is taken to be either absolute, or relative to this project root. + # + rule path-constant ( + name # Variable name of the constant. + : value + # Value of the constant. + ) + { + import project ; + local caller = [ CALLER_MODULE ] ; + local p = [ project.target $(caller) ] ; + $(p).add-constant $(name) : $(value) : path ; + } + + rule use-project ( id : where ) + { + import modules ; + # See comment in 'load' for explanation. + local caller = [ CALLER_MODULE ] ; + modules.poke $(caller) : .used-projects : + [ modules.peek $(caller) : .used-projects ] + $(id) $(where) ; + } + + rule build-project ( dir ) + { + import project ; + local caller = [ CALLER_MODULE ] ; + local attributes = [ project.attributes $(caller) ] ; + + local now = [ $(attributes).get projects-to-build ] ; + $(attributes).set projects-to-build : $(now) $(dir) ; + } + + rule explicit ( target-names * ) + { + import project ; + # If 'explicit' is used in a helper rule defined in Jamroot and + # inherited by children, then most of the time we want 'explicit' to + # operate on the Jamfile where the helper rule is invoked. + local t = [ project.current ] ; + for local n in $(target-names) + { + $(t).mark-target-as-explicit $(n) ; + } + } + + rule always ( target-names * ) + { + import project ; + local t = [ project.current ] ; + for local n in $(target-names) + { + $(t).mark-target-as-always $(n) ; + } + } + + rule glob ( wildcards + : excludes * ) + { + import project ; + return [ project.glob-internal [ project.current ] : $(wildcards) : + $(excludes) : glob ] ; + } + + rule glob-tree ( wildcards + : excludes * ) + { + import project ; + + if $(wildcards:D) || $(excludes:D) + { + errors.user-error The patterns to 'glob-tree' may not include + directory ; + } + return [ project.glob-internal [ project.current ] : $(wildcards) : + $(excludes) : glob-tree ] ; + } + + # Calculates conditional requirements for multiple requirements at once. + # This is a shorthand to reduce duplication and to keep an inline + # declarative syntax. For example: + # + # lib x : x.cpp : [ conditional <toolset>gcc <variant>debug : + # <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ; + # + rule conditional ( condition + : requirements * ) + { + local condition = $(condition:J=,) ; + if [ MATCH (:) : $(condition) ] + { + return $(condition)$(requirements) ; + } + else + { + return $(condition):$(requirements) ; + } + } + + rule option ( name : value ) + { + if $(__name__) != site-config && $(__name__) != user-config && $(__name__) != project-config + { + import errors ; + errors.error "The 'option' rule may be used only in site-config or user-config" ; + } + import option ; + option.set $(name) : $(value) ; + } +} diff --git a/jam-files/boost-build/build/project.py b/jam-files/boost-build/build/project.py new file mode 100644 index 00000000..1e1e16fa --- /dev/null +++ b/jam-files/boost-build/build/project.py @@ -0,0 +1,1120 @@ +# Status: ported. +# Base revision: 64488 + +# Copyright 2002, 2003 Dave Abrahams +# Copyright 2002, 2005, 2006 Rene Rivera +# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Implements project representation and loading. +# Each project is represented by +# - a module where all the Jamfile content live. +# - an instance of 'project-attributes' class. +# (given module name, can be obtained by 'attributes' rule) +# - an instance of 'project-target' class (from targets.jam) +# (given a module name, can be obtained by 'target' rule) +# +# Typically, projects are created as result of loading Jamfile, which is +# do by rules 'load' and 'initialize', below. First, module for Jamfile +# is loaded and new project-attributes instance is created. Some rules +# necessary for project are added to the module (see 'project-rules' module) +# at the bottom of this file. +# Default project attributes are set (inheriting attributes of parent project, if +# it exists). After that, Jamfile is read. It can declare its own attributes, +# via 'project' rule, which will be combined with already set attributes. +# +# +# The 'project' rule can also declare project id, which will be associated with +# the project module. +# +# There can also be 'standalone' projects. They are created by calling 'initialize' +# on arbitrary module, and not specifying location. After the call, the module can +# call 'project' rule, declare main target and behave as regular projects. However, +# since it's not associated with any location, it's better declare only prebuilt +# targets. +# +# The list of all loaded Jamfile is stored in variable .project-locations. It's possible +# to obtain module name for a location using 'module-name' rule. The standalone projects +# are not recorded, the only way to use them is by project id. + +import b2.util.path +from b2.build import property_set, property +from b2.build.errors import ExceptionWithUserContext +import b2.build.targets + +import bjam + +import re +import sys +import os +import string +import imp +import traceback +import b2.util.option as option + +from b2.util import record_jam_to_value_mapping, qualify_jam_action + +class ProjectRegistry: + + def __init__(self, manager, global_build_dir): + self.manager = manager + self.global_build_dir = global_build_dir + self.project_rules_ = ProjectRules(self) + + # The target corresponding to the project being loaded now + self.current_project = None + + # The set of names of loaded project modules + self.jamfile_modules = {} + + # Mapping from location to module name + self.location2module = {} + + # Mapping from project id to project module + self.id2module = {} + + # Map from Jamfile directory to parent Jamfile/Jamroot + # location. + self.dir2parent_jamfile = {} + + # Map from directory to the name of Jamfile in + # that directory (or None). + self.dir2jamfile = {} + + # Map from project module to attributes object. + self.module2attributes = {} + + # Map from project module to target for the project + self.module2target = {} + + # Map from names to Python modules, for modules loaded + # via 'using' and 'import' rules in Jamfiles. + self.loaded_tool_modules_ = {} + + self.loaded_tool_module_path_ = {} + + # Map from project target to the list of + # (id,location) pairs corresponding to all 'use-project' + # invocations. + # TODO: should not have a global map, keep this + # in ProjectTarget. + self.used_projects = {} + + self.saved_current_project = [] + + self.JAMROOT = self.manager.getenv("JAMROOT"); + + # Note the use of character groups, as opposed to listing + # 'Jamroot' and 'jamroot'. With the latter, we'd get duplicate + # matches on windows and would have to eliminate duplicates. + if not self.JAMROOT: + self.JAMROOT = ["project-root.jam", "[Jj]amroot", "[Jj]amroot.jam"] + + # Default patterns to search for the Jamfiles to use for build + # declarations. + self.JAMFILE = self.manager.getenv("JAMFILE") + + if not self.JAMFILE: + self.JAMFILE = ["[Bb]uild.jam", "[Jj]amfile.v2", "[Jj]amfile", + "[Jj]amfile.jam"] + + + def load (self, jamfile_location): + """Loads jamfile at the given location. After loading, project global + file and jamfile needed by the loaded one will be loaded recursively. + If the jamfile at that location is loaded already, does nothing. + Returns the project module for the Jamfile.""" + + absolute = os.path.join(os.getcwd(), jamfile_location) + absolute = os.path.normpath(absolute) + jamfile_location = b2.util.path.relpath(os.getcwd(), absolute) + + if "--debug-loading" in self.manager.argv(): + print "Loading Jamfile at '%s'" % jamfile_location + + + mname = self.module_name(jamfile_location) + # If Jamfile is already loaded, don't try again. + if not mname in self.jamfile_modules: + + self.load_jamfile(jamfile_location, mname) + + # We want to make sure that child project are loaded only + # after parent projects. In particular, because parent projects + # define attributes whch are inherited by children, and we don't + # want children to be loaded before parents has defined everything. + # + # While "build-project" and "use-project" can potentially refer + # to child projects from parent projects, we don't immediately + # load child projects when seing those attributes. Instead, + # we record the minimal information that will be used only later. + + self.load_used_projects(mname) + + return mname + + def load_used_projects(self, module_name): + # local used = [ modules.peek $(module-name) : .used-projects ] ; + used = self.used_projects[module_name] + + location = self.attribute(module_name, "location") + for u in used: + id = u[0] + where = u[1] + + self.use(id, os.path.join(location, where)) + + def load_parent(self, location): + """Loads parent of Jamfile at 'location'. + Issues an error if nothing is found.""" + + found = b2.util.path.glob_in_parents( + location, self.JAMROOT + self.JAMFILE) + + if not found: + print "error: Could not find parent for project at '%s'" % location + print "error: Did not find Jamfile or project-root.jam in any parent directory." + sys.exit(1) + + return self.load(os.path.dirname(found[0])) + + def act_as_jamfile(self, module, location): + """Makes the specified 'module' act as if it were a regularly loaded Jamfile + at 'location'. If Jamfile is already located for that location, it's an + error.""" + + if self.module_name(location) in self.jamfile_modules: + self.manager.errors()( + "Jamfile was already loaded for '%s'" % location) + + # Set up non-default mapping from location to module. + self.location2module[location] = module + + # Add the location to the list of project locations + # so that we don't try to load Jamfile in future + self.jamfile_modules.append(location) + + self.initialize(module, location) + + def find(self, name, current_location): + """Given 'name' which can be project-id or plain directory name, + return project module corresponding to that id or directory. + Returns nothing of project is not found.""" + + project_module = None + + # Try interpreting name as project id. + if name[0] == '/': + project_module = self.id2module.get(name) + + if not project_module: + location = os.path.join(current_location, name) + # If no project is registered for the given location, try to + # load it. First see if we have Jamfile. If not we might have project + # root, willing to act as Jamfile. In that case, project-root + # must be placed in the directory referred by id. + + project_module = self.module_name(location) + if not project_module in self.jamfile_modules: + if b2.util.path.glob([location], self.JAMROOT + self.JAMFILE): + project_module = self.load(location) + else: + project_module = None + + return project_module + + def module_name(self, jamfile_location): + """Returns the name of module corresponding to 'jamfile-location'. + If no module corresponds to location yet, associates default + module name with that location.""" + module = self.location2module.get(jamfile_location) + if not module: + # Root the path, so that locations are always umbiguious. + # Without this, we can't decide if '../../exe/program1' and '.' + # are the same paths, or not. + jamfile_location = os.path.realpath( + os.path.join(os.getcwd(), jamfile_location)) + module = "Jamfile<%s>" % jamfile_location + self.location2module[jamfile_location] = module + return module + + def find_jamfile (self, dir, parent_root=0, no_errors=0): + """Find the Jamfile at the given location. This returns the + exact names of all the Jamfiles in the given directory. The optional + parent-root argument causes this to search not the given directory + but the ones above it up to the directory given in it.""" + + # Glob for all the possible Jamfiles according to the match pattern. + # + jamfile_glob = None + if parent_root: + parent = self.dir2parent_jamfile.get(dir) + if not parent: + parent = b2.util.path.glob_in_parents(dir, + self.JAMFILE) + self.dir2parent_jamfile[dir] = parent + jamfile_glob = parent + else: + jamfile = self.dir2jamfile.get(dir) + if not jamfile: + jamfile = b2.util.path.glob([dir], self.JAMFILE) + self.dir2jamfile[dir] = jamfile + jamfile_glob = jamfile + + if len(jamfile_glob) > 1: + # Multiple Jamfiles found in the same place. Warn about this. + # And ensure we use only one of them. + # As a temporary convenience measure, if there's Jamfile.v2 amount + # found files, suppress the warning and use it. + # + pattern = "(.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam)" + v2_jamfiles = [x for x in jamfile_glob if re.match(pattern, x)] + if len(v2_jamfiles) == 1: + jamfile_glob = v2_jamfiles + else: + print """warning: Found multiple Jamfiles at '%s'!""" % (dir) + for j in jamfile_glob: + print " -", j + print "Loading the first one" + + # Could not find it, error. + if not no_errors and not jamfile_glob: + self.manager.errors()( + """Unable to load Jamfile. +Could not find a Jamfile in directory '%s' +Attempted to find it with pattern '%s'. +Please consult the documentation at 'http://boost.org/boost-build2'.""" + % (dir, string.join(self.JAMFILE))) + + if jamfile_glob: + return jamfile_glob[0] + + def load_jamfile(self, dir, jamfile_module): + """Load a Jamfile at the given directory. Returns nothing. + Will attempt to load the file as indicated by the JAMFILE patterns. + Effect of calling this rule twice with the same 'dir' is underfined.""" + + # See if the Jamfile is where it should be. + is_jamroot = False + jamfile_to_load = b2.util.path.glob([dir], self.JAMROOT) + if not jamfile_to_load: + jamfile_to_load = self.find_jamfile(dir) + else: + if len(jamfile_to_load) > 1: + get_manager().errors()("Multiple Jamfiles found at '%s'\n" +\ + "Filenames are: %s" + % (dir, [os.path.basename(j) for j in jamfile_to_load])) + + is_jamroot = True + jamfile_to_load = jamfile_to_load[0] + + dir = os.path.dirname(jamfile_to_load) + if not dir: + dir = "." + + self.used_projects[jamfile_module] = [] + + # Now load the Jamfile in it's own context. + # The call to 'initialize' may load parent Jamfile, which might have + # 'use-project' statement that causes a second attempt to load the + # same project we're loading now. Checking inside .jamfile-modules + # prevents that second attempt from messing up. + if not jamfile_module in self.jamfile_modules: + self.jamfile_modules[jamfile_module] = True + + # Initialize the jamfile module before loading. + # + self.initialize(jamfile_module, dir, os.path.basename(jamfile_to_load)) + + saved_project = self.current_project + + bjam.call("load", jamfile_module, jamfile_to_load) + basename = os.path.basename(jamfile_to_load) + + if is_jamroot: + jamfile = self.find_jamfile(dir, no_errors=True) + if jamfile: + bjam.call("load", jamfile_module, jamfile) + + # Now do some checks + if self.current_project != saved_project: + self.manager.errors()( +"""The value of the .current-project variable +has magically changed after loading a Jamfile. +This means some of the targets might be defined a the wrong project. +after loading %s +expected value %s +actual value %s""" % (jamfile_module, saved_project, self.current_project)) + + if self.global_build_dir: + id = self.attributeDefault(jamfile_module, "id", None) + project_root = self.attribute(jamfile_module, "project-root") + location = self.attribute(jamfile_module, "location") + + if location and project_root == dir: + # This is Jamroot + if not id: + # FIXME: go via errors module, so that contexts are + # shown? + print "warning: the --build-dir option was specified" + print "warning: but Jamroot at '%s'" % dir + print "warning: specified no project id" + print "warning: the --build-dir option will be ignored" + + + def load_standalone(self, jamfile_module, file): + """Loads 'file' as standalone project that has no location + associated with it. This is mostly useful for user-config.jam, + which should be able to define targets, but although it has + some location in filesystem, we don't want any build to + happen in user's HOME, for example. + + The caller is required to never call this method twice on + the same file. + """ + + self.used_projects[jamfile_module] = [] + bjam.call("load", jamfile_module, file) + self.load_used_projects(jamfile_module) + + def is_jamroot(self, basename): + match = [ pat for pat in self.JAMROOT if re.match(pat, basename)] + if match: + return 1 + else: + return 0 + + def initialize(self, module_name, location=None, basename=None): + """Initialize the module for a project. + + module-name is the name of the project module. + location is the location (directory) of the project to initialize. + If not specified, stanalone project will be initialized + """ + + if "--debug-loading" in self.manager.argv(): + print "Initializing project '%s'" % module_name + + # TODO: need to consider if standalone projects can do anything but defining + # prebuilt targets. If so, we need to give more sensible "location", so that + # source paths are correct. + if not location: + location = "" + + attributes = ProjectAttributes(self.manager, location, module_name) + self.module2attributes[module_name] = attributes + + python_standalone = False + if location: + attributes.set("source-location", [location], exact=1) + elif not module_name in ["test-config", "site-config", "user-config", "project-config"]: + # This is a standalone project with known location. Set source location + # so that it can declare targets. This is intended so that you can put + # a .jam file in your sources and use it via 'using'. Standard modules + # (in 'tools' subdir) may not assume source dir is set. + module = sys.modules[module_name] + attributes.set("source-location", self.loaded_tool_module_path_[module_name], exact=1) + python_standalone = True + + attributes.set("requirements", property_set.empty(), exact=True) + attributes.set("usage-requirements", property_set.empty(), exact=True) + attributes.set("default-build", property_set.empty(), exact=True) + attributes.set("projects-to-build", [], exact=True) + attributes.set("project-root", None, exact=True) + attributes.set("build-dir", None, exact=True) + + self.project_rules_.init_project(module_name, python_standalone) + + jamroot = False + + parent_module = None; + if module_name == "test-config": + # No parent + pass + elif module_name == "site-config": + parent_module = "test-config" + elif module_name == "user-config": + parent_module = "site-config" + elif module_name == "project-config": + parent_module = "user-config" + elif location and not self.is_jamroot(basename): + # We search for parent/project-root only if jamfile was specified + # --- i.e + # if the project is not standalone. + parent_module = self.load_parent(location) + else: + # It's either jamroot, or standalone project. + # If it's jamroot, inherit from user-config. + if location: + # If project-config module exist, inherit from it. + if self.module2attributes.has_key("project-config"): + parent_module = "project-config" + else: + parent_module = "user-config" ; + + jamroot = True ; + + if parent_module: + self.inherit_attributes(module_name, parent_module) + attributes.set("parent-module", parent_module, exact=1) + + if jamroot: + attributes.set("project-root", location, exact=1) + + parent = None + if parent_module: + parent = self.target(parent_module) + + if not self.module2target.has_key(module_name): + target = b2.build.targets.ProjectTarget(self.manager, + module_name, module_name, parent, + self.attribute(module_name,"requirements"), + # FIXME: why we need to pass this? It's not + # passed in jam code. + self.attribute(module_name, "default-build")) + self.module2target[module_name] = target + + self.current_project = self.target(module_name) + + def inherit_attributes(self, project_module, parent_module): + """Make 'project-module' inherit attributes of project + root and parent module.""" + + attributes = self.module2attributes[project_module] + pattributes = self.module2attributes[parent_module] + + # Parent module might be locationless user-config. + # FIXME: + #if [ modules.binding $(parent-module) ] + #{ + # $(attributes).set parent : [ path.parent + # [ path.make [ modules.binding $(parent-module) ] ] ] ; + # } + + attributes.set("project-root", pattributes.get("project-root"), exact=True) + attributes.set("default-build", pattributes.get("default-build"), exact=True) + attributes.set("requirements", pattributes.get("requirements"), exact=True) + attributes.set("usage-requirements", + pattributes.get("usage-requirements"), exact=1) + + parent_build_dir = pattributes.get("build-dir") + + if parent_build_dir: + # Have to compute relative path from parent dir to our dir + # Convert both paths to absolute, since we cannot + # find relative path from ".." to "." + + location = attributes.get("location") + parent_location = pattributes.get("location") + + our_dir = os.path.join(os.getcwd(), location) + parent_dir = os.path.join(os.getcwd(), parent_location) + + build_dir = os.path.join(parent_build_dir, + os.path.relpath(our_dir, parent_dir)) + attributes.set("build-dir", build_dir, exact=True) + + def register_id(self, id, module): + """Associate the given id with the given project module.""" + self.id2module[id] = module + + def current(self): + """Returns the project which is currently being loaded.""" + return self.current_project + + def set_current(self, c): + self.current_project = c + + def push_current(self, project): + """Temporary changes the current project to 'project'. Should + be followed by 'pop-current'.""" + self.saved_current_project.append(self.current_project) + self.current_project = project + + def pop_current(self): + self.current_project = self.saved_current_project[-1] + del self.saved_current_project[-1] + + def attributes(self, project): + """Returns the project-attribute instance for the + specified jamfile module.""" + return self.module2attributes[project] + + def attribute(self, project, attribute): + """Returns the value of the specified attribute in the + specified jamfile module.""" + return self.module2attributes[project].get(attribute) + try: + return self.module2attributes[project].get(attribute) + except: + raise BaseException("No attribute '%s' for project" % (attribute, project)) + + def attributeDefault(self, project, attribute, default): + """Returns the value of the specified attribute in the + specified jamfile module.""" + return self.module2attributes[project].getDefault(attribute, default) + + def target(self, project_module): + """Returns the project target corresponding to the 'project-module'.""" + if not self.module2target.has_key(project_module): + self.module2target[project_module] = \ + b2.build.targets.ProjectTarget(project_module, project_module, + self.attribute(project_module, "requirements")) + + return self.module2target[project_module] + + def use(self, id, location): + # Use/load a project. + saved_project = self.current_project + project_module = self.load(location) + declared_id = self.attributeDefault(project_module, "id", "") + + if not declared_id or declared_id != id: + # The project at 'location' either have no id or + # that id is not equal to the 'id' parameter. + if self.id2module.has_key(id) and self.id2module[id] != project_module: + self.manager.errors()( +"""Attempt to redeclare already existing project id '%s' at location '%s'""" % (id, location)) + self.id2module[id] = project_module + + self.current_module = saved_project + + def add_rule(self, name, callable): + """Makes rule 'name' available to all subsequently loaded Jamfiles. + + Calling that rule wil relay to 'callable'.""" + self.project_rules_.add_rule(name, callable) + + def project_rules(self): + return self.project_rules_ + + def glob_internal(self, project, wildcards, excludes, rule_name): + location = project.get("source-location")[0] + + result = [] + callable = b2.util.path.__dict__[rule_name] + + paths = callable([location], wildcards, excludes) + has_dir = 0 + for w in wildcards: + if os.path.dirname(w): + has_dir = 1 + break + + if has_dir or rule_name != "glob": + result = [] + # The paths we've found are relative to current directory, + # but the names specified in sources list are assumed to + # be relative to source directory of the corresponding + # prject. Either translate them or make absolute. + + for p in paths: + rel = os.path.relpath(p, location) + # If the path is below source location, use relative path. + if not ".." in rel: + result.append(rel) + else: + # Otherwise, use full path just to avoid any ambiguities. + result.append(os.path.abspath(p)) + + else: + # There were not directory in wildcard, so the files are all + # in the source directory of the project. Just drop the + # directory, instead of making paths absolute. + result = [os.path.basename(p) for p in paths] + + return result + + def load_module(self, name, extra_path=None): + """Load a Python module that should be useable from Jamfiles. + + There are generally two types of modules Jamfiles might want to + use: + - Core Boost.Build. Those are imported using plain names, e.g. + 'toolset', so this function checks if we have module named + b2.package.module already. + - Python modules in the same directory as Jamfile. We don't + want to even temporary add Jamfile's directory to sys.path, + since then we might get naming conflicts between standard + Python modules and those. + """ + + # See if we loaded module of this name already + existing = self.loaded_tool_modules_.get(name) + if existing: + return existing + + # See if we have a module b2.whatever.<name>, where <name> + # is what is passed to this function + modules = sys.modules + for class_name in modules: + parts = class_name.split('.') + if name is class_name or parts[0] == "b2" \ + and parts[-1] == name.replace("-", "_"): + module = modules[class_name] + self.loaded_tool_modules_[name] = module + return module + + # Lookup a module in BOOST_BUILD_PATH + path = extra_path + if not path: + path = [] + path.extend(self.manager.boost_build_path()) + location = None + for p in path: + l = os.path.join(p, name + ".py") + if os.path.exists(l): + location = l + break + + if not location: + self.manager.errors()("Cannot find module '%s'" % name) + + mname = name + "__for_jamfile" + file = open(location) + try: + # TODO: this means we'll never make use of .pyc module, + # which might be a problem, or not. + self.loaded_tool_module_path_[mname] = location + module = imp.load_module(mname, file, os.path.basename(location), + (".py", "r", imp.PY_SOURCE)) + self.loaded_tool_modules_[name] = module + return module + finally: + file.close() + + + +# FIXME: +# Defines a Boost.Build extension project. Such extensions usually +# contain library targets and features that can be used by many people. +# Even though extensions are really projects, they can be initialize as +# a module would be with the "using" (project.project-rules.using) +# mechanism. +#rule extension ( id : options * : * ) +#{ +# # The caller is a standalone module for the extension. +# local mod = [ CALLER_MODULE ] ; +# +# # We need to do the rest within the extension module. +# module $(mod) +# { +# import path ; +# +# # Find the root project. +# local root-project = [ project.current ] ; +# root-project = [ $(root-project).project-module ] ; +# while +# [ project.attribute $(root-project) parent-module ] && +# [ project.attribute $(root-project) parent-module ] != user-config +# { +# root-project = [ project.attribute $(root-project) parent-module ] ; +# } +# +# # Create the project data, and bring in the project rules +# # into the module. +# project.initialize $(__name__) : +# [ path.join [ project.attribute $(root-project) location ] ext $(1:L) ] ; +# +# # Create the project itself, i.e. the attributes. +# # All extensions are created in the "/ext" project space. +# project /ext/$(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; +# local attributes = [ project.attributes $(__name__) ] ; +# +# # Inherit from the root project of whomever is defining us. +# project.inherit-attributes $(__name__) : $(root-project) ; +# $(attributes).set parent-module : $(root-project) : exact ; +# } +#} + + +class ProjectAttributes: + """Class keeping all the attributes of a project. + + The standard attributes are 'id', "location", "project-root", "parent" + "requirements", "default-build", "source-location" and "projects-to-build". + """ + + def __init__(self, manager, location, project_module): + self.manager = manager + self.location = location + self.project_module = project_module + self.attributes = {} + self.usage_requirements = None + + def set(self, attribute, specification, exact=False): + """Set the named attribute from the specification given by the user. + The value actually set may be different.""" + + if exact: + self.__dict__[attribute] = specification + + elif attribute == "requirements": + self.requirements = property_set.refine_from_user_input( + self.requirements, specification, + self.project_module, self.location) + + elif attribute == "usage-requirements": + unconditional = [] + for p in specification: + split = property.split_conditional(p) + if split: + unconditional.append(split[1]) + else: + unconditional.append(p) + + non_free = property.remove("free", unconditional) + if non_free: + get_manager().errors()("usage-requirements %s have non-free properties %s" \ + % (specification, non_free)) + + t = property.translate_paths( + property.create_from_strings(specification, allow_condition=True), + self.location) + + existing = self.__dict__.get("usage-requirements") + if existing: + new = property_set.create(existing.all() + t) + else: + new = property_set.create(t) + self.__dict__["usage-requirements"] = new + + + elif attribute == "default-build": + self.__dict__["default-build"] = property_set.create(specification) + + elif attribute == "source-location": + source_location = [] + for path in specification: + source_location.append(os.path.join(self.location, path)) + self.__dict__["source-location"] = source_location + + elif attribute == "build-dir": + self.__dict__["build-dir"] = os.path.join(self.location, specification[0]) + + elif attribute == "id": + id = specification[0] + if id[0] != '/': + id = "/" + id + self.manager.projects().register_id(id, self.project_module) + self.__dict__["id"] = id + + elif not attribute in ["default-build", "location", + "source-location", "parent", + "projects-to-build", "project-root"]: + self.manager.errors()( +"""Invalid project attribute '%s' specified +for project at '%s'""" % (attribute, self.location)) + else: + self.__dict__[attribute] = specification + + def get(self, attribute): + return self.__dict__[attribute] + + def getDefault(self, attribute, default): + return self.__dict__.get(attribute, default) + + def dump(self): + """Prints the project attributes.""" + id = self.get("id") + if not id: + id = "(none)" + else: + id = id[0] + + parent = self.get("parent") + if not parent: + parent = "(none)" + else: + parent = parent[0] + + print "'%s'" % id + print "Parent project:%s", parent + print "Requirements:%s", self.get("requirements") + print "Default build:%s", string.join(self.get("debuild-build")) + print "Source location:%s", string.join(self.get("source-location")) + print "Projects to build:%s", string.join(self.get("projects-to-build").sort()); + +class ProjectRules: + """Class keeping all rules that are made available to Jamfile.""" + + def __init__(self, registry): + self.registry = registry + self.manager_ = registry.manager + self.rules = {} + self.local_names = [x for x in self.__class__.__dict__ + if x not in ["__init__", "init_project", "add_rule", + "error_reporting_wrapper", "add_rule_for_type", "reverse"]] + self.all_names_ = [x for x in self.local_names] + + def _import_rule(self, bjam_module, name, callable): + if hasattr(callable, "bjam_signature"): + bjam.import_rule(bjam_module, name, self.make_wrapper(callable), callable.bjam_signature) + else: + bjam.import_rule(bjam_module, name, self.make_wrapper(callable)) + + + def add_rule_for_type(self, type): + rule_name = type.lower().replace("_", "-") + + def xpto (name, sources = [], requirements = [], default_build = [], usage_requirements = []): + return self.manager_.targets().create_typed_target( + type, self.registry.current(), name[0], sources, + requirements, default_build, usage_requirements) + + self.add_rule(rule_name, xpto) + + def add_rule(self, name, callable): + self.rules[name] = callable + self.all_names_.append(name) + + # Add new rule at global bjam scope. This might not be ideal, + # added because if a jamroot does 'import foo' where foo calls + # add_rule, we need to import new rule to jamroot scope, and + # I'm lazy to do this now. + self._import_rule("", name, callable) + + def all_names(self): + return self.all_names_ + + def call_and_report_errors(self, callable, *args, **kw): + result = None + try: + self.manager_.errors().push_jamfile_context() + result = callable(*args, **kw) + except ExceptionWithUserContext, e: + e.report() + except Exception, e: + try: + self.manager_.errors().handle_stray_exception (e) + except ExceptionWithUserContext, e: + e.report() + finally: + self.manager_.errors().pop_jamfile_context() + + return result + + def make_wrapper(self, callable): + """Given a free-standing function 'callable', return a new + callable that will call 'callable' and report all exceptins, + using 'call_and_report_errors'.""" + def wrapper(*args, **kw): + return self.call_and_report_errors(callable, *args, **kw) + return wrapper + + def init_project(self, project_module, python_standalone=False): + + if python_standalone: + m = sys.modules[project_module] + + for n in self.local_names: + if n != "import_": + setattr(m, n, getattr(self, n)) + + for n in self.rules: + setattr(m, n, self.rules[n]) + + return + + for n in self.local_names: + # Using 'getattr' here gives us a bound method, + # while using self.__dict__[r] would give unbound one. + v = getattr(self, n) + if callable(v): + if n == "import_": + n = "import" + else: + n = string.replace(n, "_", "-") + + self._import_rule(project_module, n, v) + + for n in self.rules: + self._import_rule(project_module, n, self.rules[n]) + + def project(self, *args): + + jamfile_module = self.registry.current().project_module() + attributes = self.registry.attributes(jamfile_module) + + id = None + if args and args[0]: + id = args[0][0] + args = args[1:] + + if id: + attributes.set('id', [id]) + + explicit_build_dir = None + for a in args: + if a: + attributes.set(a[0], a[1:], exact=0) + if a[0] == "build-dir": + explicit_build_dir = a[1] + + # If '--build-dir' is specified, change the build dir for the project. + if self.registry.global_build_dir: + + location = attributes.get("location") + # Project with empty location is 'standalone' project, like + # user-config, or qt. It has no build dir. + # If we try to set build dir for user-config, we'll then + # try to inherit it, with either weird, or wrong consequences. + if location and location == attributes.get("project-root"): + # Re-read the project id, since it might have been changed in + # the project's attributes. + id = attributes.get('id') + + # This is Jamroot. + if id: + if explicit_build_dir and os.path.isabs(explicit_build_dir): + self.registry.manager.errors()( +"""Absolute directory specified via 'build-dir' project attribute +Don't know how to combine that with the --build-dir option.""") + + rid = id + if rid[0] == '/': + rid = rid[1:] + + p = os.path.join(self.registry.global_build_dir, rid) + if explicit_build_dir: + p = os.path.join(p, explicit_build_dir) + attributes.set("build-dir", p, exact=1) + elif explicit_build_dir: + self.registry.manager.errors()( +"""When --build-dir is specified, the 'build-dir' +attribute is allowed only for top-level 'project' invocations""") + + def constant(self, name, value): + """Declare and set a project global constant. + Project global constants are normal variables but should + not be changed. They are applied to every child Jamfile.""" + m = "Jamfile</home/ghost/Work/Boost/boost-svn/tools/build/v2_python/python/tests/bjam/make>" + self.registry.current().add_constant(name[0], value) + + def path_constant(self, name, value): + """Declare and set a project global constant, whose value is a path. The + path is adjusted to be relative to the invocation directory. The given + value path is taken to be either absolute, or relative to this project + root.""" + if len(value) > 1: + self.registry.manager.error()("path constant should have one element") + self.registry.current().add_constant(name[0], value[0], path=1) + + def use_project(self, id, where): + # See comment in 'load' for explanation why we record the + # parameters as opposed to loading the project now. + m = self.registry.current().project_module(); + self.registry.used_projects[m].append((id[0], where[0])) + + def build_project(self, dir): + assert(isinstance(dir, list)) + jamfile_module = self.registry.current().project_module() + attributes = self.registry.attributes(jamfile_module) + now = attributes.get("projects-to-build") + attributes.set("projects-to-build", now + dir, exact=True) + + def explicit(self, target_names): + self.registry.current().mark_targets_as_explicit(target_names) + + def always(self, target_names): + self.registry.current().mark_targets_as_alays(target_names) + + def glob(self, wildcards, excludes=None): + return self.registry.glob_internal(self.registry.current(), + wildcards, excludes, "glob") + + def glob_tree(self, wildcards, excludes=None): + bad = 0 + for p in wildcards: + if os.path.dirname(p): + bad = 1 + + if excludes: + for p in excludes: + if os.path.dirname(p): + bad = 1 + + if bad: + self.registry.manager.errors()( +"The patterns to 'glob-tree' may not include directory") + return self.registry.glob_internal(self.registry.current(), + wildcards, excludes, "glob_tree") + + + def using(self, toolset, *args): + # The module referred by 'using' can be placed in + # the same directory as Jamfile, and the user + # will expect the module to be found even though + # the directory is not in BOOST_BUILD_PATH. + # So temporary change the search path. + current = self.registry.current() + location = current.get('location') + + m = self.registry.load_module(toolset[0], [location]) + if not m.__dict__.has_key("init"): + self.registry.manager.errors()( + "Tool module '%s' does not define the 'init' method" % toolset[0]) + m.init(*args) + + # The above might have clobbered .current-project. Restore the correct + # value. + self.registry.set_current(current) + + def import_(self, name, names_to_import=None, local_names=None): + + name = name[0] + py_name = name + if py_name == "os": + py_name = "os_j" + jamfile_module = self.registry.current().project_module() + attributes = self.registry.attributes(jamfile_module) + location = attributes.get("location") + + saved = self.registry.current() + + m = self.registry.load_module(py_name, [location]) + + for f in m.__dict__: + v = m.__dict__[f] + f = f.replace("_", "-") + if callable(v): + qn = name + "." + f + self._import_rule(jamfile_module, qn, v) + record_jam_to_value_mapping(qualify_jam_action(qn, jamfile_module), v) + + + if names_to_import: + if not local_names: + local_names = names_to_import + + if len(names_to_import) != len(local_names): + self.registry.manager.errors()( +"""The number of names to import and local names do not match.""") + + for n, l in zip(names_to_import, local_names): + self._import_rule(jamfile_module, l, m.__dict__[n]) + + self.registry.set_current(saved) + + def conditional(self, condition, requirements): + """Calculates conditional requirements for multiple requirements + at once. This is a shorthand to be reduce duplication and to + keep an inline declarative syntax. For example: + + lib x : x.cpp : [ conditional <toolset>gcc <variant>debug : + <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ; + """ + + c = string.join(condition, ",") + if c.find(":") != -1: + return [c + r for r in requirements] + else: + return [c + ":" + r for r in requirements] + + def option(self, name, value): + name = name[0] + if not name in ["site-config", "user-config", "project-config"]: + get_manager().errors()("The 'option' rule may be used only in site-config or user-config") + + option.set(name, value[0]) diff --git a/jam-files/boost-build/build/property-set.jam b/jam-files/boost-build/build/property-set.jam new file mode 100644 index 00000000..70fd90cd --- /dev/null +++ b/jam-files/boost-build/build/property-set.jam @@ -0,0 +1,481 @@ +# Copyright 2003 Dave Abrahams +# Copyright 2003, 2004, 2005, 2006 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +import "class" : new ; +import feature ; +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 Boost.Build'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 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)'" ; + } + + 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) ; + } + + if dependency in $(att) + { + self.dependency += $(p) ; + } + else + { + self.non-dependency += $(p) ; + } + + if [ MATCH (:) : $(p:G=) ] + { + self.conditional += $(p) ; + } + else + { + self.non-conditional += $(p) ; + } + + if propagated in $(att) + { + self.propagated += $(p) ; + } + if link-incompatible in $(att) + { + self.link-incompatible += $(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 ( ) + { + return $(self.base) ; + } + + # Returns free properties which are not incidental. + # + rule free ( ) + { + return $(self.free) ; + } + + # Returns dependency properties. + # + rule dependency ( ) + { + return $(self.dependency) ; + } + + rule non-dependency ( ) + { + return $(self.non-dependency) ; + } + + rule conditional ( ) + { + return $(self.conditional) ; + } + + rule non-conditional ( ) + { + return $(self.non-conditional) ; + } + + # Returns incidental properties. + # + rule incidental ( ) + { + 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) + { + self.propagated-ps = [ property-set.create $(self.propagated) ] ; + } + return $(self.propagated-ps) ; + } + + rule link-incompatible ( ) + { + if ! $(self.link-incompatible-ps) + { + self.link-incompatible-ps = + [ property-set.create $(self.link-incompatible) ] ; + } + return $(self.link-incompatible-ps) ; + } + + rule run-actions ( ) + { + if ! $(self.run) + { + self.run = [ property-set.create [ feature.run-actions $(self.raw) ] ] ; + } + return $(self.run) ; + } + + 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 $(self.base) ] ; + } + 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 = [ as-path ] ; + p = [ property-set.hash-maybe $(p) ] ; + + # 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) ] ] ; + } + + rule link-incompatible-with ( ps ) + { + if ! $(.li.$(ps)) + { + local li1 = [ $(__name__).link-incompatible ] ; + local li2 = [ $(ps).link-incompatible ] ; + if [ set.equal $(li1) : $(li2) ] + { + .li.$(ps) = false ; + } + else + { + .li.$(ps) = true ; + } + } + if $(.li.$(ps)) = true + { + return true ; + } + else + { + return ; + } + } + + # 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)) ; + } +} + + +# 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 ; + + +# Creates a new 'property-set' instance after checking that all properties are +# valid and converting incidental 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 specification = [ property.translate-paths $(raw-properties) + : $(location) ] ; + specification = [ property.translate-indirect $(specification) + : $(jamfile-module) ] ; + local project-id = [ project.attribute $(jamfile-module) id ] ; + project-id ?= [ path.root $(location) [ path.pwd ] ] ; + specification = [ property.translate-dependencies + $(specification) : $(project-id) : $(location) ] ; + specification = + [ property.expand-subfeatures-in-conditions $(specification) ] ; + specification = [ property.make $(specification) ] ; + return [ property-set.create $(specification) ] ; +} + + +# 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 [ $(parent-requirements).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) ; + } +} + diff --git a/jam-files/boost-build/build/property.jam b/jam-files/boost-build/build/property.jam new file mode 100644 index 00000000..a2ad5226 --- /dev/null +++ b/jam-files/boost-build/build/property.jam @@ -0,0 +1,788 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +import errors ; +import feature ; +import indirect ; +import path ; +import regex ; +import string ; +import sequence ; +import set ; +import utility ; + + +# Refines 'properties' by overriding any non-free and non-conditional properties +# for which a different value is specified in 'requirements'. Returns the +# resulting list of properties. +# +rule refine ( properties * : requirements * ) +{ + local result ; + local error ; + + # All the 'requirements' elements should be present in the result. Record + # them so that we can handle 'properties'. + for local r in $(requirements) + { + # Do not consider conditional requirements. + if ! [ MATCH (:) : $(r:G=) ] + { + # Note: cannot use a local variable here, so use an ugly name. + __require__$(r:G) = $(r:G=) ; + } + } + + for local p in $(properties) + { + if [ MATCH (:) : $(p:G=) ] + { + # Do not modify conditional properties. + result += $(p) ; + } + else if free in [ feature.attributes $(p:G) ] + { + # Do not modify free properties. + result += $(p) ; + } + else + { + local required-value = $(__require__$(p:G)) ; + if $(required-value) + { + if $(p:G=) != $(required-value) + { + result += $(p:G)$(required-value) ; + } + else + { + result += $(p) ; + } + } + else + { + result += $(p) ; + } + } + } + + # Unset our ugly map. + for local r in $(requirements) + { + __require__$(r:G) = ; + } + + if $(error) + { + return $(error) ; + } + else + { + return [ sequence.unique $(result) $(requirements) ] ; + } +} + + +# Removes all conditional properties whose conditions are not met. For those +# with met conditions, removes the condition. Properties in conditions are +# looked up in 'context'. +# +rule evaluate-conditionals-in-context ( properties * : context * ) +{ + local base ; + local conditionals ; + for local p in $(properties) + { + if [ MATCH (:<) : $(p) ] + { + conditionals += $(p) ; + } + else + { + base += $(p) ; + } + } + + local result = $(base) ; + for local p in $(conditionals) + { + # Separate condition and property. + local s = [ MATCH (.*):(<.*) : $(p) ] ; + # Split condition into individual properties. + local condition = [ regex.split $(s[1]) "," ] ; + # Evaluate condition. + if ! [ MATCH (!).* : $(condition:G=) ] + { + # Only positive checks + if $(condition) in $(context) + { + result += $(s[2]) ; + } + } + else + { + # Have negative checks + local fail ; + while $(condition) + { + local c = $(condition[1]) ; + local m = [ MATCH !(.*) : $(c) ] ; + if $(m) + { + local p = $(m:G=$(c:G)) ; + if $(p) in $(context) + { + fail = true ; + c = ; + } + } + else + { + if ! $(c) in $(context) + { + fail = true ; + c = ; + } + } + condition = $(condition[2-]) ; + } + if ! $(fail) + { + result += $(s[2]) ; + } + } + } + return $(result) ; +} + + +rule expand-subfeatures-in-conditions ( properties * ) +{ + local result ; + for local p in $(properties) + { + local s = [ MATCH (.*):(<.*) : $(p) ] ; + if ! $(s) + { + result += $(p) ; + } + else + { + local condition = $(s[1]) ; + local value = $(s[2]) ; + # Condition might include several elements. + condition = [ regex.split $(condition) "," ] ; + local e ; + for local c in $(condition) + { + # It is common for a condition to include a toolset or + # subfeatures that have not been defined. In that case we want + # the condition to simply 'never be satisfied' and validation + # would only produce a spurious error so we prevent it by + # passing 'true' as the second parameter. + e += [ feature.expand-subfeatures $(c) : true ] ; + } + if $(e) = $(condition) + { + # (todo) + # This is just an optimization and possibly a premature one at + # that. + # (todo) (12.07.2008.) (Jurko) + result += $(p) ; + } + else + { + result += $(e:J=,):$(value) ; + } + } + } + return $(result) ; +} + + +# Helper for as-path, below. Orders properties with the implicit ones first, and +# within the two sections in alphabetical order of feature name. +# +local rule path-order ( x y ) +{ + if $(y:G) && ! $(x:G) + { + return true ; + } + else if $(x:G) && ! $(y:G) + { + return ; + } + else + { + if ! $(x:G) + { + x = [ feature.expand-subfeatures $(x) ] ; + y = [ feature.expand-subfeatures $(y) ] ; + } + + if $(x[1]) < $(y[1]) + { + return true ; + } + } +} + + +local rule abbreviate-dashed ( string ) +{ + local r ; + for local part in [ regex.split $(string) - ] + { + r += [ string.abbreviate $(part) ] ; + } + return $(r:J=-) ; +} + + +local rule identity ( string ) +{ + return $(string) ; +} + + +if --abbreviate-paths in [ modules.peek : ARGV ] +{ + .abbrev = abbreviate-dashed ; +} +else +{ + .abbrev = identity ; +} + + +# Returns a path representing the given expanded property set. +# +rule as-path ( properties * ) +{ + local entry = .result.$(properties:J=-) ; + + if ! $($(entry)) + { + # Trim redundancy. + properties = [ feature.minimize $(properties) ] ; + + # Sort according to path-order. + properties = [ sequence.insertion-sort $(properties) : path-order ] ; + + local components ; + for local p in $(properties) + { + if $(p:G) + { + local f = [ utility.ungrist $(p:G) ] ; + p = $(f)-$(p:G=) ; + } + components += [ $(.abbrev) $(p) ] ; + } + + $(entry) = $(components:J=/) ; + } + + return $($(entry)) ; +} + + +# Exit with error if property is not valid. +# +local rule validate1 ( property ) +{ + local msg ; + if $(property:G) + { + local feature = $(property:G) ; + local value = $(property:G=) ; + + if ! [ feature.valid $(feature) ] + { + # Ungrist for better error messages. + feature = [ utility.ungrist $(property:G) ] ; + msg = "unknown feature '$(feature)'" ; + } + else if $(value) && ! free in [ feature.attributes $(feature) ] + { + feature.validate-value-string $(feature) $(value) ; + } + else if ! ( $(value) || ( optional in [ feature.attributes $(feature) ] ) ) + { + # Ungrist for better error messages. + feature = [ utility.ungrist $(property:G) ] ; + msg = "No value specified for feature '$(feature)'" ; + } + } + else + { + local feature = [ feature.implied-feature $(property) ] ; + feature.validate-value-string $(feature) $(property) ; + } + if $(msg) + { + errors.error "Invalid property "'$(property:J=" ")'": "$(msg:J=" "). ; + } +} + + +rule validate ( properties * ) +{ + for local p in $(properties) + { + validate1 $(p) ; + } +} + + +rule validate-property-sets ( property-sets * ) +{ + for local s in $(property-sets) + { + validate [ feature.split $(s) ] ; + } +} + + +# Expands any implicit property values in the given property 'specification' so +# they explicitly state their feature. +# +rule make ( specification * ) +{ + local result ; + for local e in $(specification) + { + if $(e:G) + { + result += $(e) ; + } + else if [ feature.is-implicit-value $(e) ] + { + local feature = [ feature.implied-feature $(e) ] ; + result += $(feature)$(e) ; + } + else + { + errors.error "'$(e)' is not a valid property specification" ; + } + } + return $(result) ; +} + + +# Returns a property set containing all the elements in 'properties' that do not +# have their attributes listed in 'attributes'. +# +rule remove ( attributes + : properties * ) +{ + local result ; + for local e in $(properties) + { + if ! [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ] + { + result += $(e) ; + } + } + return $(result) ; +} + + +# Returns a property set containing all the elements in 'properties' that have +# their attributes listed in 'attributes'. +# +rule take ( attributes + : properties * ) +{ + local result ; + for local e in $(properties) + { + if [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ] + { + result += $(e) ; + } + } + return $(result) ; +} + + +# Selects properties corresponding to any of the given features. +# +rule select ( features * : properties * ) +{ + local result ; + + # Add any missing angle brackets. + local empty = "" ; + features = $(empty:G=$(features)) ; + + for local p in $(properties) + { + if $(p:G) in $(features) + { + result += $(p) ; + } + } + return $(result) ; +} + + +# Returns a modified version of properties with all values of the given feature +# replaced by the given value. If 'value' is empty the feature will be removed. +# +rule change ( properties * : feature value ? ) +{ + local result ; + for local p in $(properties) + { + if $(p:G) = $(feature) + { + result += $(value:G=$(feature)) ; + } + else + { + result += $(p) ; + } + } + return $(result) ; +} + + +# If 'property' is a conditional property, returns the condition and the +# property. E.g. <variant>debug,<toolset>gcc:<inlining>full will become +# <variant>debug,<toolset>gcc <inlining>full. Otherwise, returns an empty +# string. +# +rule split-conditional ( property ) +{ + local m = [ MATCH "(.+):<(.+)" : $(property) ] ; + if $(m) + { + return $(m[1]) <$(m[2]) ; + } +} + + +# 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. +# +rule translate-paths ( properties * : path ) +{ + local result ; + for local p in $(properties) + { + local split = [ split-conditional $(p) ] ; + local condition = "" ; + if $(split) + { + condition = $(split[1]): ; + p = $(split[2]) ; + } + + if path in [ feature.attributes $(p:G) ] + { + local values = [ regex.split $(p:TG=) "&&" ] ; + local t ; + for local v in $(values) + { + t += [ path.root [ path.make $(v) ] $(path) ] ; + } + t = $(t:J="&&") ; + result += $(condition)$(t:TG=$(p:G)) ; + } + else + { + result += $(condition)$(p) ; + } + } + return $(result) ; +} + + +# 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. +# Converts such values into 'indirect-rule' format (see indirect.jam), so they +# can be called from other modules. Does nothing for such values that are +# already in the 'indirect-rule' format. +# +rule translate-indirect ( specification * : context-module ) +{ + local result ; + for local p in $(specification) + { + local m = [ MATCH ^@(.+) : $(p:G=) ] ; + if $(m) + { + local v ; + if [ MATCH "^([^%]*)%([^%]+)$" : $(m) ] + { + # Rule is already in the 'indirect-rule' format. + v = $(m) ; + } + else + { + if ! [ MATCH ".*([.]).*" : $(m) ] + { + # This is an unqualified rule name. The user might want to + # set flags on this rule name and toolset.flag + # auto-qualifies it. Need to do the same here so flag + # setting works. We can arrange for toolset.flag to *not* + # auto-qualify the argument but then two rules defined in + # two Jamfiles would conflict. + m = $(context-module).$(m) ; + } + v = [ indirect.make $(m) : $(context-module) ] ; + } + + v = @$(v) ; + result += $(v:G=$(p:G)) ; + } + else + { + result += $(p) ; + } + } + return $(result) ; +} + + +# Binds all dependency properties in a list relative to the given project. +# Targets with absolute paths will be left unchanged and targets which have a +# project specified will have the path to the project interpreted relative to +# the specified location. +# +rule translate-dependencies ( specification * : project-id : location ) +{ + local result ; + for local p in $(specification) + { + local split = [ split-conditional $(p) ] ; + local condition = "" ; + if $(split) + { + condition = $(split[1]): ; + p = $(split[2]) ; + } + if dependency in [ feature.attributes $(p:G) ] + { + local split-target = [ regex.match (.*)//(.*) : $(p:G=) ] ; + if $(split-target) + { + local rooted = [ path.root [ path.make $(split-target[1]) ] + [ path.root $(location) [ path.pwd ] ] ] ; + result += $(condition)$(p:G)$(rooted)//$(split-target[2]) ; + } + else if [ path.is-rooted $(p:G=) ] + { + result += $(condition)$(p) ; + } + else + { + result += $(condition)$(p:G)$(project-id)//$(p:G=) ; + } + } + else + { + result += $(condition)$(p) ; + } + } + return $(result) ; +} + + +# Class maintaining a property set -> string mapping. +# +class property-map +{ + import errors ; + import numbers ; + import sequence ; + + rule __init__ ( ) + { + self.next-flag = 1 ; + } + + # Associate 'value' with 'properties'. + # + rule insert ( properties + : value ) + { + self.all-flags += $(self.next-flag) ; + self.properties.$(self.next-flag) = $(properties) ; + self.value.$(self.next-flag) = $(value) ; + + self.next-flag = [ numbers.increment $(self.next-flag) ] ; + } + + # Returns the value associated with 'properties' or any subset of it. If + # more than one subset has a value assigned to it, returns the value for the + # longest subset, if it is unique. + # + rule find ( properties + ) + { + return [ find-replace $(properties) ] ; + } + + # Returns the value associated with 'properties'. If 'value' parameter is + # given, replaces the found value. + # + rule find-replace ( properties + : value ? ) + { + # First find all matches. + local matches ; + local match-ranks ; + for local i in $(self.all-flags) + { + if $(self.properties.$(i)) in $(properties) + { + matches += $(i) ; + match-ranks += [ sequence.length $(self.properties.$(i)) ] ; + } + } + local best = [ sequence.select-highest-ranked $(matches) + : $(match-ranks) ] ; + if $(best[2]) + { + errors.error "Ambiguous key $(properties:J= :E=)" ; + } + local original = $(self.value.$(best)) ; + if $(value) + { + self.value.$(best) = $(value) ; + } + return $(original) ; + } +} + + +rule __test__ ( ) +{ + import assert ; + import "class" : new ; + import errors : try catch ; + import feature ; + + # Local rules must be explicitly re-imported. + import property : path-order abbreviate-dashed ; + + feature.prepare-test property-test-temp ; + + feature.feature toolset : gcc : implicit symmetric ; + feature.subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4 3.0 3.0.1 + 3.0.2 : optional ; + feature.feature define : : free ; + feature.feature runtime-link : dynamic static : symmetric link-incompatible ; + feature.feature optimization : on off ; + feature.feature variant : debug release : implicit composite symmetric ; + feature.feature rtti : on off : link-incompatible ; + + feature.compose <variant>debug : <define>_DEBUG <optimization>off ; + feature.compose <variant>release : <define>NDEBUG <optimization>on ; + + validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ; + + assert.true path-order $(test-space) debug <define>foo ; + assert.false path-order $(test-space) <define>foo debug ; + assert.true path-order $(test-space) gcc debug ; + assert.false path-order $(test-space) debug gcc ; + assert.true path-order $(test-space) <optimization>on <rtti>on ; + assert.false path-order $(test-space) <rtti>on <optimization>on ; + + assert.result-set-equal <toolset>gcc <rtti>off <define>FOO + : refine <toolset>gcc <rtti>off + : <define>FOO + : $(test-space) ; + + assert.result-set-equal <toolset>gcc <optimization>on + : refine <toolset>gcc <optimization>off + : <optimization>on + : $(test-space) ; + + assert.result-set-equal <toolset>gcc <rtti>off + : refine <toolset>gcc : <rtti>off : $(test-space) ; + + assert.result-set-equal <toolset>gcc <rtti>off <rtti>off:<define>FOO + : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO + : $(test-space) ; + + assert.result-set-equal <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 ; + + assert.result debug + : as-path <optimization>off <variant>debug + : $(test-space) ; + + assert.result gcc/debug/rtti-off + : as-path <toolset>gcc <optimization>off <rtti>off <variant>debug + : $(test-space) ; + + assert.result optmz-off : abbreviate-dashed optimization-off ; + assert.result rntm-lnk-sttc : abbreviate-dashed runtime-link-static ; + + 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-set-equal <rtti>on + : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ; + + assert.result-set-equal <include>a + : select include : <include>a <toolset>gcc ; + + assert.result-set-equal <include>a + : select include bar : <include>a <toolset>gcc ; + + assert.result-set-equal <include>a <toolset>gcc + : select include <bar> <toolset> : <include>a <toolset>gcc ; + + assert.result-set-equal <toolset>kylix <include>a + : change <toolset>gcc <include>a : <toolset> kylix ; + + pm = [ new property-map ] ; + $(pm).insert <toolset>gcc : o ; + $(pm).insert <toolset>gcc <os>NT : obj ; + $(pm).insert <toolset>gcc <os>CYGWIN : obj ; + + assert.equal o : [ $(pm).find <toolset>gcc ] ; + + assert.equal obj : [ $(pm).find <toolset>gcc <os>NT ] ; + + try ; + $(pm).find <toolset>gcc <os>NT <os>CYGWIN ; + catch "Ambiguous key <toolset>gcc <os>NT <os>CYGWIN" ; + + # 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-set-equal <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 ; +} diff --git a/jam-files/boost-build/build/property.py b/jam-files/boost-build/build/property.py new file mode 100644 index 00000000..c4b13dbc --- /dev/null +++ b/jam-files/boost-build/build/property.py @@ -0,0 +1,593 @@ +# Status: ported, except for tests and --abbreviate-paths. +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +import re +from b2.util.utility import * +from b2.build import feature +from b2.util import sequence, qualify_jam_action +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'(.*):(<.*)') + +class Property(object): + + __slots__ = ('_feature', '_value', '_condition') + + def __init__(self, f, value, condition = []): + if type(f) == type(""): + f = feature.get(f) + # At present, single property has a single value. + assert type(value) != type([]) + assert(f.free() or value.find(':') == -1) + self._feature = f + self._value = value + self._condition = condition + + def feature(self): + return self._feature + + def value(self): + return self._value + + def condition(self): + return self._condition + + def to_raw(self): + result = "<" + self._feature.name() + ">" + str(self._value) + if self._condition: + result = ",".join(str(p) for p in self._condition) + ':' + result + return result + + def __str__(self): + return self.to_raw() + + def __hash__(self): + # FIXME: consider if this class should be value-is-identity one + return hash((self._feature, self._value, tuple(self._condition))) + + def __cmp__(self, other): + return cmp((self._feature, self._value, self._condition), + (other._feature, other._value, other._condition)) + + +def create_from_string(s, allow_condition=False): + + 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 + + feature_name = get_grist(s) + if not feature_name: + if feature.is_implicit_value(s): + f = feature.implied_feature(s) + value = s + else: + raise get_manager().errors()("Invalid property '%s' -- unknown feature" % s) + else: + f = feature.get(feature_name) + + value = get_value(s) + if not value: + get_manager().errors()("Invalid property '%s' -- no value specified" % s) + + + if condition: + condition = [create_from_string(x) for x in condition.split(',')] + + return Property(f, value, condition) + +def create_from_strings(string_list, allow_condition=False): + + 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 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. + """ + # 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 required.has_key(p.feature()): + 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. + """ + result = [] + + for p in properties: + + if p.feature().path(): + values = __re_two_ampersands.split(p.value()) + + new_value = "&&".join(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.""" + 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, str): + __validate1 (properties) + else: + for p in properties: + __validate1 (p) + +def expand_subfeatures_in_conditions (properties): + + result = [] + for p in properties: + + if not p.condition(): + result.append(p) + else: + expanded = [] + for c in p.condition(): + + if c.feature().name().startswith("toolset") or c.feature().name() == "os": + # 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)) + else: + expanded.extend(feature.expand_subfeatures([c])) + + 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. + """ + 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. + """ + 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): + 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. Properies + in conditions are looked up in 'context' + """ + 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. + """ + 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. + """ + 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'.""" + + 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'.""" + 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): + + 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. + """ + 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. + """ + return self.find_replace (properties) + + def find_replace(self, properties, value=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 ; +# } +# + diff --git a/jam-files/boost-build/build/property_set.py b/jam-files/boost-build/build/property_set.py new file mode 100644 index 00000000..f12eb90c --- /dev/null +++ b/jam-files/boost-build/build/property_set.py @@ -0,0 +1,449 @@ +# 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. + +from b2.util.utility import * +import property, feature, string +import b2.build.feature +from b2.exceptions import * +from b2.util.sequence import unique +from b2.util.set import difference +from b2.util import cached + +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. + """ + # 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] + x.sort() + x = unique (x) + + # FIXME: can we do better, e.g. by directly computing + # has value of the list? + key = tuple(x) + + if not __cache.has_key (key): + __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 incidental + properties into gristed form. + """ + 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'""" + + 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.""" + + + 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 = []): + + + raw_properties = [] + for p in properties: + raw_properties.append(p.to_raw()) + + self.all_ = properties + self.all_raw_ = raw_properties + self.all_set_ = set(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_ = {} + + for p in raw_properties: + if not get_grist (p): + raise BaseException ("Invalid property: '%s'" % p) + + att = feature.attributes (get_grist (p)) + + if 'propagated' in att: + self.propagated_.append (p) + + if 'link_incompatible' in att: + self.link_incompatible.append (p) + + for p in properties: + + # A feature can be both incidental and free, + # in which case we add it to incidental. + if p.feature().incidental(): + self.incidental_.append(p) + elif p.feature().free(): + self.free_.append(p) + else: + self.base_.append(p) + + if p.condition(): + self.conditional_.append(p) + else: + self.non_conditional_.append(p) + + if p.feature().dependency(): + self.dependency_.append (p) + else: + self.non_dependency_.append (p) + + + def all(self): + return self.all_ + + def raw (self): + """ Returns the list of stored properties. + """ + return self.all_raw_ + + def __str__(self): + return ' '.join(str(p) for p in self.all_) + + def base (self): + """ Returns properties that are neither incidental nor free. + """ + return self.base_ + + def free (self): + """ Returns free properties which are not dependency properties. + """ + return self.free_ + + def non_free(self): + return self.base_ + self.incidental_ + + def dependency (self): + """ Returns dependency properties. + """ + return self.dependency_ + + def non_dependency (self): + """ Returns properties that are not dependencies. + """ + return self.non_dependency_ + + 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. + """ + return self.incidental_ + + def refine (self, requirements): + """ Refines this set's properties using the requirements passed as an argument. + """ + assert isinstance(requirements, PropertySet) + if not self.refined_.has_key (requirements): + 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): + if not context: + context = self + + if not self.evaluated_.has_key(context): + # 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: + if p.feature().implicit(): + components.append(p.value()) + else: + components.append(p.feature().name() + "-" + p.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 () + + # 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. + """ + if not self.added_.has_key(ps): + 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) + + if not self.feature_map_: + self.feature_map_ = {} + + for v in self.all_: + if not self.feature_map_.has_key(v.feature()): + 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) + + result = [] + for p in self.all_: + if p.feature() == feature: + result.append(p) + return result + + def __contains__(self, item): + return item in self.all_set_ + diff --git a/jam-files/boost-build/build/readme.txt b/jam-files/boost-build/build/readme.txt new file mode 100644 index 00000000..c3dddd8d --- /dev/null +++ b/jam-files/boost-build/build/readme.txt @@ -0,0 +1,13 @@ +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 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 + + + diff --git a/jam-files/boost-build/build/scanner.jam b/jam-files/boost-build/build/scanner.jam new file mode 100644 index 00000000..d6042ea2 --- /dev/null +++ b/jam-files/boost-build/build/scanner.jam @@ -0,0 +1,153 @@ +# Copyright 2003 Dave Abrahams +# Copyright 2002, 2003, 2004, 2005 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Implements 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 "class" : new ; +import property virtual-target property-set ; +import errors : error ; + +# Base scanner class. +class scanner +{ + rule __init__ ( ) + { + } + + # Returns a pattern to use for scanning + rule pattern ( ) + { + error "method must be overriden" ; + } + + # Establish necessary relationship between targets, + # given actual target beeing scanned, and a list of + # pattern matches in that file. + rule process ( target : matches * ) + { + error "method must be overriden" ; + } +} + +# 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. +rule register ( scanner-class : relevant-properties * ) +{ + .registered += $(scanner-class) ; + .relevant-properties.$(scanner-class) = $(relevant-properties) ; +} + +# 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 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 previously registered scanner, +# with the specified properties. +rule get ( scanner-class : property-set ) +{ + if ! $(scanner-class) in $(.registered) + { + error "attempt to get unregisted 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=-)) + { + scanner.$(scanner-class).$(r:J=-) = [ new $(scanner-class) [ $(r).raw ] ] ; + } + return $(scanner.$(scanner-class).$(r:J=-)) ; +} + + +# Installs the specified scanner on actual target 'target'. +rule install ( scanner : target + vtarget # virtual target from which 'target' was actualized +) +{ + HDRSCAN on $(target) = [ $(scanner).pattern ] ; + SCANNER on $(target) = $(scanner) ; + HDRRULE on $(target) = scanner.hdrrule ; + + # 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. + HDRGRIST on $(target) = $(scanner) ; +} + +# Propagate scanner setting 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 that it can be invoked +# by header scanning +IMPORT scanner : hdrrule : : scanner.hdrrule ; + + + + diff --git a/jam-files/boost-build/build/scanner.py b/jam-files/boost-build/build/scanner.py new file mode 100644 index 00000000..19f1431d --- /dev/null +++ b/jam-files/boost-build/build/scanner.py @@ -0,0 +1,158 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.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.exceptions import * +from b2.manager import get_manager + +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. + """ + __scanners[str(scanner_class)] = relevant_properties + +def registered(scanner_class): + """ Returns true iff a scanner of that class is registered + """ + return __scanners.has_key(str(scanner_class)) + +def get(scanner_class, properties): + """ Returns an instance of previously registered scanner + with the specified properties. + """ + scanner_name = str(scanner_class) + + if not registered(scanner_name): + raise BaseException ("attempt to get unregisted scanner: %s" % scanner_name) + + relevant_properties = __scanners[scanner_name] + r = property.select(relevant_properties, properties) + + scanner_id = scanner_name + '.' + '-'.join(r) + + if not __scanner_cache.has_key(scanner_name): + __scanner_cache[scanner_name] = scanner_class(r) + + return __scanner_cache[scanner_name] + +class Scanner: + """ Base scanner class. + """ + def __init__ (self): + pass + + def pattern (self): + """ Returns a pattern to use for scanning. + """ + raise BaseException ("method must be overriden") + + def process (self, target, matches): + """ Establish necessary relationship between targets, + given actual target beeing scanned, and a list of + pattern matches in that file. + """ + raise BaseException ("method must be overriden") + + +# 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. + """ + engine = self.manager_.engine() + engine.set_target_variable(target, "HDRSCAN", scanner.pattern()) + if not self.exported_scanners_.has_key(scanner): + 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): + 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))) + diff --git a/jam-files/boost-build/build/targets.jam b/jam-files/boost-build/build/targets.jam new file mode 100644 index 00000000..a70532ce --- /dev/null +++ b/jam-files/boost-build/build/targets.jam @@ -0,0 +1,1659 @@ +# Copyright Vladimir Prus 2002. +# Copyright Rene Rivera 2006. +# +# 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) + +# Supports 'abstract' targets, which are targets explicitly defined in a +# Jamfile. +# +# Abstract targets are represented by classes derived from 'abstract-target' +# class. The first abstract target is 'project-target', which is created for +# each Jamfile, and can be obtained by the 'target' rule in the Jamfile's module +# (see project.jam). +# +# Project targets keep a list of 'main-target' instances. A main target is what +# the user explicitly defines in a Jamfile. It is possible to have several +# definitions for a main target, for example to have different lists of sources +# for different platforms. So, main targets keep a list of alternatives. +# +# Each alternative is an instance of 'abstract-target'. When a main target +# subvariant is defined by some rule, that rule will decide what class to use, +# create an instance of that class and add it to the list of alternatives for +# the main target. +# +# Rules supplied by the build system will use only targets derived from +# 'basic-target' class, which will provide some default behaviour. There will be +# different classes derived from it such as 'make-target', created by the 'make' +# rule, and 'typed-target', created by rules such as 'exe' and 'lib'. + +# +# +------------------------+ +# |abstract-target | +# +========================+ +# |name | +# |project | +# | | +# |generate(properties) = 0| +# +-----------+------------+ +# | +# ^ +# / \ +# +-+-+ +# | +# | +# +------------------------+------+------------------------------+ +# | | | +# | | | +# +----------+-----------+ +------+------+ +------+-------+ +# | project-target | | main-target | | basic-target | +# +======================+ 1 * +=============+ alternatives +==============+ +# | generate(properties) |o-----------+ generate |<>------------->| generate | +# | main-target | +-------------+ | construct = 0| +# +----------------------+ +--------------+ +# | +# ^ +# / \ +# +-+-+ +# | +# | +# ...--+----------------+------------------+----------------+---+ +# | | | | +# | | | | +# ... ---+-----+ +------+-------+ +------+------+ +--------+-----+ +# | | typed-target | | make-target | | stage-target | +# . +==============+ +=============+ +==============+ +# . | construct | | construct | | construct | +# +--------------+ +-------------+ +--------------+ + +import assert ; +import "class" : new ; +import errors ; +import feature ; +import indirect ; +import path ; +import property ; +import property-set ; +import sequence ; +import set ; +import toolset ; +import build-request ; + + +# Base class for all abstract targets. +# +class abstract-target +{ + import project ; + import assert ; + import "class" ; + import errors ; + + rule __init__ ( name # Name of the target in Jamfile. + : project-target # The project target to which this one belongs. + ) + { + # Note: it might seem that we don't need either name or project at all. + # However, there are places where we really need it. One example is + # error messages which should name problematic targets. Another is + # setting correct paths for sources and generated files. + + self.name = $(name) ; + self.project = $(project-target) ; + self.location = [ errors.nearest-user-location ] ; + } + + # Returns the name of this target. + rule name ( ) + { + return $(self.name) ; + } + + # Returns the project for this target. + rule project ( ) + { + return $(self.project) ; + } + + # Return the location where the target was declared. + rule location ( ) + { + return $(self.location) ; + } + + # Returns a user-readable name for this target. + rule full-name ( ) + { + local location = [ $(self.project).get location ] ; + return $(location)/$(self.name) ; + } + + # Generates virtual targets for this abstract target using the specified + # properties, unless a different value of some feature is required by the + # target. + # On success, returns: + # - a property-set with the usage requirements to be applied to dependants + # - a list of produced virtual targets, which may be empty. + # If 'property-set' is empty, performs the default build of this target, in + # a way specific to the derived class. + # + rule generate ( property-set ) + { + errors.error "method should be defined in derived classes" ; + } + + rule rename ( new-name ) + { + self.name = $(new-name) ; + } +} + + +if --debug-building in [ modules.peek : ARGV ] +{ + modules.poke : .debug-building : true ; +} + + +rule indent ( ) +{ + return $(.indent:J="") ; +} + + +rule increase-indent ( ) +{ + .indent += " " ; +} + + +rule decrease-indent ( ) +{ + .indent = $(.indent[2-]) ; +} + + +# Project target class (derived from 'abstract-target'). +# +# This class has the following responsibilities: +# - Maintaining a list of main targets in this project and building them. +# +# Main targets are constructed in two stages: +# - When Jamfile is read, a number of calls to 'add-alternative' is made. At +# that time, alternatives can also be renamed to account for inline targets. +# - The first time 'main-target' or 'has-main-target' rule is called, all +# alternatives are enumerated and main targets are created. +# +class project-target : abstract-target +{ + import project ; + import targets ; + import path ; + import print ; + import property-set ; + import set ; + import sequence ; + import "class" : new ; + import errors ; + + rule __init__ ( name : project-module parent-project ? + : requirements * : default-build * ) + { + abstract-target.__init__ $(name) : $(__name__) ; + + self.project-module = $(project-module) ; + self.location = [ project.attribute $(project-module) location ] ; + self.requirements = $(requirements) ; + self.default-build = $(default-build) ; + + if $(parent-project) + { + inherit $(parent-project) ; + } + } + + # This is needed only by the 'make' rule. Need to find the way to make + # 'make' work without this method. + # + rule project-module ( ) + { + return $(self.project-module) ; + } + + rule get ( attribute ) + { + return [ project.attribute $(self.project-module) $(attribute) ] ; + } + + rule build-dir ( ) + { + if ! $(self.build-dir) + { + self.build-dir = [ get build-dir ] ; + if ! $(self.build-dir) + { + self.build-dir = [ path.join [ $(self.project).get location ] + bin ] ; + } + } + return $(self.build-dir) ; + } + + # Generates all possible targets contained in this project. + # + rule generate ( property-set * ) + { + if [ modules.peek : .debug-building ] + { + ECHO [ targets.indent ] "building project" [ name ] " ('$(__name__)') with" [ $(property-set).raw ] ; + targets.increase-indent ; + } + + local usage-requirements = [ property-set.empty ] ; + local targets ; + + for local t in [ targets-to-build ] + { + local g = [ $(t).generate $(property-set) ] ; + usage-requirements = [ $(usage-requirements).add $(g[1]) ] ; + targets += $(g[2-]) ; + } + targets.decrease-indent ; + return $(usage-requirements) [ sequence.unique $(targets) ] ; + } + + # Computes and returns a list of abstract-target instances which must be + # built when this project is built. + # + rule targets-to-build ( ) + { + local result ; + + if ! $(self.built-main-targets) + { + build-main-targets ; + } + + # Collect all main targets here, except for "explicit" ones. + for local t in $(self.main-targets) + { + if ! [ $(t).name ] in $(self.explicit-targets) + { + result += $(t) ; + } + } + + # Collect all projects referenced via "projects-to-build" attribute. + local self-location = [ get location ] ; + for local pn in [ get projects-to-build ] + { + result += [ find $(pn)/ ] ; + } + + return $(result) ; + } + + # Add 'target' to the list of targets in this project that should be build + # only by explicit request + # + rule mark-target-as-explicit ( target-name * ) + { + # Record the name of the target, not instance, since this rule is called + # before main target instances are created. + self.explicit-targets += $(target-name) ; + } + + rule mark-target-as-always ( target-name * ) + { + # Record the name of the target, not instance, since this rule is called + # before main target instances are created. + self.always-targets += $(target-name) ; + } + + # Add new target alternative + # + rule add-alternative ( target-instance ) + { + if $(self.built-main-targets) + { + errors.error add-alternative called when main targets are already + created. : in project [ full-name ] ; + } + self.alternatives += $(target-instance) ; + } + + # Returns a 'main-target' class instance corresponding to 'name'. + # + rule main-target ( name ) + { + if ! $(self.built-main-targets) + { + build-main-targets ; + } + return $(self.main-target.$(name)) ; + } + + # Returns whether a main target with the specified name exists. + # + rule has-main-target ( name ) + { + if ! $(self.built-main-targets) + { + build-main-targets ; + } + + if $(self.main-target.$(name)) + { + return true ; + } + } + + # Worker function for the find rule not implementing any caching and simply + # returning nothing in case the target can not be found. + # + rule find-really ( id ) + { + local result ; + local current-location = [ get location ] ; + + local split = [ MATCH (.*)//(.*) : $(id) ] ; + local project-part = $(split[1]) ; + local target-part = $(split[2]) ; + + local extra-error-message ; + if $(project-part) + { + # There is an explicitly specified project part in id. Looks up the + # project and passes the request to it. + local pm = [ project.find $(project-part) : $(current-location) ] ; + if $(pm) + { + project-target = [ project.target $(pm) ] ; + result = [ $(project-target).find $(target-part) : no-error ] ; + } + else + { + # TODO: This extra error message will not get displayed most + # likely due to some buggy refactoring. Refactor the code so the + # message gets diplayed again. + extra-error-message = error: could not find project + '$(project-part)' ; + } + } + else + { + # Interpret target-name as name of main target. Need to do this + # before checking for file. Consider the following scenario with a + # toolset not modifying its executable's names, e.g. gcc on + # Unix-like platforms: + # + # exe test : test.cpp ; + # install s : test : <location>. ; + # + # After the first build we would have a target named 'test' in the + # Jamfile and a file named 'test' on the disk. We need the target to + # override the file. + result = [ main-target $(id) ] ; + + # Interpret id as an existing file reference. + if ! $(result) + { + result = [ new file-reference [ path.make $(id) ] : + $(self.project) ] ; + if ! [ $(result).exists ] + { + result = ; + } + } + + # Interpret id as project-id. + if ! $(result) + { + local project-module = [ project.find $(id) : + $(current-location) ] ; + if $(project-module) + { + result = [ project.target $(project-module) ] ; + } + } + } + + return $(result) ; + } + + # Find and return the target with the specified id, treated relative to + # self. Id may specify either a target or a file name with the target taking + # priority. May report an error or return nothing if the target is not found + # depending on the 'no-error' parameter. + # + rule find ( id : no-error ? ) + { + local v = $(.id.$(id)) ; + if ! $(v) + { + v = [ find-really $(id) ] ; + if ! $(v) + { + v = none ; + } + .id.$(id) = $(v) ; + } + + if $(v) != none + { + return $(v) ; + } + else + { + if ! $(no-error) + { + local current-location = [ get location ] ; + ECHO "error: Unable to find file or target named" ; + ECHO "error: '$(id)'" ; + ECHO "error: referred from project at" ; + ECHO "error: '$(current-location)'" ; + ECHO $(extra-error-message) ; + EXIT ; + } + } + } + + rule build-main-targets ( ) + { + self.built-main-targets = true ; + for local a in $(self.alternatives) + { + local name = [ $(a).name ] ; + local target = $(self.main-target.$(name)) ; + if ! $(target) + { + local t = [ new main-target $(name) : $(self.project) ] ; + self.main-target.$(name) = $(t) ; + self.main-targets += $(t) ; + target = $(self.main-target.$(name)) ; + } + + if $(name) in $(self.always-targets) + { + $(a).always ; + } + + $(target).add-alternative $(a) ; + } + } + + # Accessor, add a constant. + # + rule add-constant ( + name # Variable name of the constant. + : value + # Value of the constant. + : type ? # Optional type of value. + ) + { + switch $(type) + { + case path : + local r ; + for local v in $(value) + { + local l = $(self.location) ; + if ! $(l) + { + # Project corresponding to config files do not have + # 'location' attribute, but do have source location. + # It might be more reasonable to make every project have + # a location and use some other approach to prevent buildable + # targets in config files, but that's for later. + l = [ get source-location ] ; + } + v = [ path.root [ path.make $(v) ] $(l) ] ; + # Now make the value absolute path. + v = [ path.root $(v) [ path.pwd ] ] ; + # Constants should be in platform-native form. + v = [ path.native $(v) ] ; + r += $(v) ; + } + value = $(r) ; + } + if ! $(name) in $(self.constants) + { + self.constants += $(name) ; + } + self.constant.$(name) = $(value) ; + # Inject the constant in the scope of the Jamroot module. + modules.poke $(self.project-module) : $(name) : $(value) ; + } + + rule inherit ( parent ) + { + for local c in [ modules.peek $(parent) : self.constants ] + { + # No need to pass the type. Path constants were converted to + # absolute paths already by parent. + add-constant $(c) + : [ modules.peek $(parent) : self.constant.$(c) ] ; + } + + # Import rules from parent. + local this-module = [ project-module ] ; + local parent-module = [ $(parent).project-module ] ; + # Do not import rules coming from 'project-rules' as they must be + # imported localized. + local user-rules = [ set.difference + [ RULENAMES $(parent-module) ] : + [ RULENAMES project-rules ] ] ; + IMPORT $(parent-module) : $(user-rules) : $(this-module) : $(user-rules) ; + EXPORT $(this-module) : $(user-rules) ; + } +} + + +# Helper rules to detect cycles in main target references. +# +local rule start-building ( main-target-instance ) +{ + if $(main-target-instance) in $(.targets-being-built) + { + local names ; + for local t in $(.targets-being-built) $(main-target-instance) + { + names += [ $(t).full-name ] ; + } + + errors.error "Recursion in main target references" + : "the following target are being built currently:" + : $(names) ; + } + .targets-being-built += $(main-target-instance) ; +} + + +local rule end-building ( main-target-instance ) +{ + .targets-being-built = $(.targets-being-built[1--2]) ; +} + + +# A named top-level target in Jamfile. +# +class main-target : abstract-target +{ + import assert ; + import errors ; + import feature ; + import print ; + import property-set ; + import sequence ; + import targets : start-building end-building ; + + rule __init__ ( name : project ) + { + abstract-target.__init__ $(name) : $(project) ; + } + + # Add a new alternative for this target + rule add-alternative ( target ) + { + local d = [ $(target).default-build ] ; + if $(self.alternatives) && ( $(self.default-build) != $(d) ) + { + errors.error "default build must be identical in all alternatives" + : "main target is" [ full-name ] + : "with" [ $(d).raw ] + : "differing from previous default build" [ $(self.default-build).raw ] ; + } + else + { + self.default-build = $(d) ; + } + self.alternatives += $(target) ; + } + + # Returns the best viable alternative for this property-set. See the + # documentation for selection rules. + # + local rule select-alternatives ( property-set debug ? ) + { + # When selecting alternatives we have to consider defaults, for example: + # lib l : l.cpp : <variant>debug ; + # lib l : l_opt.cpp : <variant>release ; + # won't work unless we add default value <variant>debug. + property-set = [ $(p).add-defaults ] ; + + # The algorithm: we keep the current best viable alternative. When we've + # got a new best viable alternative, we compare it with the current one. + + local best ; + local best-properties ; + + if $(self.alternatives[2-]) + { + local bad ; + local worklist = $(self.alternatives) ; + while $(worklist) && ! $(bad) + { + local v = $(worklist[1]) ; + local properties = [ $(v).match $(property-set) $(debug) ] ; + + if $(properties) != no-match + { + if ! $(best) + { + best = $(v) ; + best-properties = $(properties) ; + } + else + { + if $(properties) = $(best-properties) + { + bad = true ; + } + else if $(properties) in $(best-properties) + { + # Do nothing, this alternative is worse + } + else if $(best-properties) in $(properties) + { + best = $(v) ; + best-properties = $(properties) ; + } + else + { + bad = true ; + } + } + } + worklist = $(worklist[2-]) ; + } + if ! $(bad) + { + return $(best) ; + } + } + else + { + return $(self.alternatives) ; + } + } + + rule apply-default-build ( property-set ) + { + return [ targets.apply-default-build $(property-set) + : $(self.default-build) ] ; + } + + # Select an alternative for this main target, by finding all alternatives + # which requirements are satisfied by 'properties' and picking the one with + # the longest requirements set. Returns the result of calling 'generate' on + # that alternative. + # + rule generate ( property-set ) + { + start-building $(__name__) ; + + # We want composite properties in build request act as if all the + # properties it expands too are explicitly specified. + property-set = [ $(property-set).expand ] ; + + local all-property-sets = [ apply-default-build $(property-set) ] ; + local usage-requirements = [ property-set.empty ] ; + local result ; + for local p in $(all-property-sets) + { + local r = [ generate-really $(p) ] ; + if $(r) + { + usage-requirements = [ $(usage-requirements).add $(r[1]) ] ; + result += $(r[2-]) ; + } + } + end-building $(__name__) ; + return $(usage-requirements) [ sequence.unique $(result) ] ; + } + + # Generates the main target with the given property set and returns a list + # which first element is property-set object containing usage-requirements + # of generated target and with generated virtual target in other elements. + # It is possible that no targets are generated. + # + local rule generate-really ( property-set ) + { + local best-alternatives = [ select-alternatives $(property-set) ] ; + if ! $(best-alternatives) + { + ECHO "error: No best alternative for" [ full-name ] ; + select-alternatives $(property-set) debug ; + return [ property-set.empty ] ; + } + else + { + # Now return virtual targets for the only alternative. + return [ $(best-alternatives).generate $(property-set) ] ; + } + } + + rule rename ( new-name ) + { + abstract-target.rename $(new-name) ; + for local a in $(self.alternatives) + { + $(a).rename $(new-name) ; + } + } +} + + +# Abstract target refering to a source file. This is an artificial entity +# allowing sources to a target to be represented using a list of abstract target +# instances. +# +class file-reference : abstract-target +{ + import virtual-target ; + import property-set ; + import path ; + + rule __init__ ( file : project ) + { + abstract-target.__init__ $(file) : $(project) ; + } + + rule generate ( properties ) + { + return [ property-set.empty ] [ virtual-target.from-file $(self.name) : + [ location ] : $(self.project) ] ; + } + + # Returns true if the referred file really exists. + rule exists ( ) + { + location ; + return $(self.file-path) ; + } + + # Returns the location of target. Needed by 'testing.jam'. + rule location ( ) + { + if ! $(self.file-location) + { + local source-location = [ $(self.project).get source-location ] ; + for local src-dir in $(source-location) + { + if ! $(self.file-location) + { + local location = [ path.root $(self.name) $(src-dir) ] ; + if [ CHECK_IF_FILE [ path.native $(location) ] ] + { + self.file-location = $(src-dir) ; + self.file-path = $(location) ; + } + } + } + } + return $(self.file-location) ; + } +} + + +# Given a target-reference, made in context of 'project', returns the +# abstract-target instance that is referred to, as well as properties explicitly +# specified for this reference. +# +rule resolve-reference ( target-reference : project ) +{ + # Separate target name from properties override. + local split = [ MATCH "^([^<]*)(/(<.*))?$" : $(target-reference) ] ; + local id = $(split[1]) ; + local sproperties = ; + if $(split[3]) + { + sproperties = [ property.make [ feature.split $(split[3]) ] ] ; + sproperties = [ feature.expand-composites $(sproperties) ] ; + } + + # Find the target. + local target = [ $(project).find $(id) ] ; + + return $(target) [ property-set.create $(sproperties) ] ; +} + + +# Attempts to generate the target given by target reference, which can refer +# both to a main target or to a file. Returns a list consisting of +# - usage requirements +# - generated virtual targets, if any +# +rule generate-from-reference ( + target-reference # Target reference. + : project # Project where the reference is made. + : property-set # Properties of the main target that makes the reference. +) +{ + local r = [ resolve-reference $(target-reference) : $(project) ] ; + local target = $(r[1]) ; + local sproperties = $(r[2]) ; + + # Take properties which should be propagated and refine them with + # source-specific requirements. + local propagated = [ $(property-set).propagated ] ; + local rproperties = [ $(propagated).refine $(sproperties) ] ; + if $(rproperties[1]) = "@error" + { + errors.error + "When building" [ full-name ] " with properties " $(properties) : + "Invalid properties specified for " $(source) ":" + $(rproperties[2-]) ; + } + return [ $(target).generate $(rproperties) ] ; +} + +rule apply-default-build ( property-set : default-build ) +{ + # 1. First, see what properties from default-build are already present + # in property-set. + + local raw = [ $(property-set).raw ] ; + local specified-features = $(raw:G) ; + + local defaults-to-apply ; + for local d in [ $(default-build).raw ] + { + if ! $(d:G) in $(specified-features) + { + defaults-to-apply += $(d) ; + } + } + + # 2. If there are any defaults to be applied, form a new build request. + # Pass it through to 'expand-no-defaults' since default-build might + # contain "release debug" resulting in two property-sets. + local result ; + if $(defaults-to-apply) + { + properties = [ + build-request.expand-no-defaults + + # We have to compress subproperties here to prevent property + # lists like: + # + # <toolset>msvc <toolset-msvc:version>7.1 <threading>multi + # + # from being expanded into: + # + # <toolset-msvc:version>7.1/<threading>multi + # <toolset>msvc/<toolset-msvc:version>7.1/<threading>multi + # + # due to a cross-product property combination. That may be an + # indication that build-request.expand-no-defaults is the wrong + # rule to use here. + [ feature.compress-subproperties $(raw) ] + $(defaults-to-apply) + ] ; + + if $(properties) + { + for local p in $(properties) + { + result += [ property-set.create + [ feature.expand [ feature.split $(p) ] ] ] ; + } + } + else + { + result = [ property-set.empty ] ; + } + } + else + { + result = $(property-set) ; + } + return $(result) ; +} + + +# Given a build request and requirements, return properties common to dependency +# build request and target requirements. +# +# TODO: Document exactly what 'common properties' are, whether they should +# include default property values, whether they should contain any conditional +# properties or should those be already processed, etc. See whether there are +# any differences between use cases with empty and non-empty build-request as +# well as with requirements containing and those not containing any non-free +# features. +# +rule common-properties ( build-request requirements ) +{ + # For optimization, we add free requirements directly, without using a + # complex algorithm. This gives the complex algorithm a better chance of + # caching results. + local free = [ $(requirements).free ] ; + local non-free = [ property-set.create [ $(requirements).base ] + [ $(requirements).incidental ] ] ; + + local key = .rp.$(build-request)-$(non-free) ; + if ! $($(key)) + { + $(key) = [ common-properties2 $(build-request) $(non-free) ] ; + } + result = [ $($(key)).add-raw $(free) ] ; +} + + +# Given a 'context' -- a set of already present properties, and 'requirements', +# decide which extra properties should be applied to 'context'. For conditional +# requirements, this means evaluating the condition. For indirect conditional +# requirements, this means calling a rule. Ordinary requirements are always +# applied. +# +# Handles the situation where evaluating one conditional requirement affects +# conditions of another conditional requirements, such as: +# <toolset>gcc:<variant>release <variant>release:<define>RELEASE +# +# If 'what' is 'refined' returns context refined with new requirements. If +# 'what' is 'added' returns just the requirements to be applied. +# +rule evaluate-requirements ( requirements : context : what ) +{ + # Apply non-conditional requirements. It is possible that further + # conditional requirement change a value set by non-conditional + # requirements. For example: + # + # exe a : a.cpp : <threading>single <toolset>foo:<threading>multi ; + # + # I am not sure if this should be an error, or not, especially given that + # + # <threading>single + # + # might come from project's requirements. + + local unconditional = [ feature.expand [ $(requirements).non-conditional ] ] ; + + local raw = [ $(context).raw ] ; + raw = [ property.refine $(raw) : $(unconditional) ] ; + + # We have collected properties that surely must be present in common + # properties. We now try to figure out what other properties should be added + # in order to satisfy rules (4)-(6) from the docs. + + local conditionals = [ $(requirements).conditional ] ; + # The 'count' variable has one element for each conditional feature and for + # each occurrence of '<indirect-conditional>' feature. It is used as a loop + # counter: for each iteration of the loop before we remove one element and + # the property set should stabilize before we are done. It is assumed that + # #conditionals iterations should be enough for properties to propagate + # along conditions in any direction. + local count = $(conditionals) + [ $(requirements).get <conditional> ] + and-once-more ; + + local added-requirements ; + + local current = $(raw) ; + + # It is assumed that ordinary conditional requirements can not add + # <conditional> properties (a.k.a. indirect conditional properties), and + # that rules referred to by <conditional> properties can not add new + # <conditional> properties. So the list of indirect conditionals does not + # change. + local indirect = [ $(requirements).get <conditional> ] ; + indirect = [ MATCH ^@(.*) : $(indirect) ] ; + + local ok ; + while $(count) + { + # Evaluate conditionals in context of current properties. + local e = [ property.evaluate-conditionals-in-context $(conditionals) + : $(current) ] ; + + # Evaluate indirect conditionals. + for local i in $(indirect) + { + e += [ indirect.call $(i) $(current) ] ; + } + + if $(e) = $(added-requirements) + { + # If we got the same result, we have found the final properties. + count = ; + ok = true ; + } + else + { + # Oops, conditional evaluation results have changed. Also 'current' + # contains leftovers from a previous evaluation. Recompute 'current' + # using initial properties and conditional requirements. + added-requirements = $(e) ; + current = [ property.refine $(raw) : [ feature.expand $(e) ] ] ; + } + count = $(count[2-]) ; + } + if ! $(ok) + { + errors.error "Can not evaluate conditional properties " $(conditionals) ; + } + + if $(what) = added + { + return [ property-set.create $(unconditional) $(added-requirements) ] ; + } + else if $(what) = refined + { + return [ property-set.create $(current) ] ; + } + else + { + errors.error "Invalid value of the 'what' parameter." ; + } +} + + +rule common-properties2 ( build-request requirements ) +{ + # This guarantees that default properties are present in the result, unless + # they are overriden by some requirement. FIXME: There is possibility that + # we have added <foo>bar, which is composite and expands to <foo2>bar2, but + # default value of <foo2> is not bar2, in which case it is not clear what to + # do. + # + build-request = [ $(build-request).add-defaults ] ; + # Features added by 'add-default' can be composite and expand to features + # without default values -- so they are not added yet. It could be clearer/ + # /faster to expand only newly added properties but that is not critical. + build-request = [ $(build-request).expand ] ; + + return [ evaluate-requirements $(requirements) : $(build-request) : + refined ] ; +} + +rule push-target ( target ) +{ + .targets = $(target) $(.targets) ; +} + +rule pop-target ( ) +{ + .targets = $(.targets[2-]) ; +} + +# Return the metatarget that is currently being generated. +rule current ( ) +{ + return $(.targets[1]) ; +} + + +# Implements the most standard way of constructing main target alternative from +# sources. Allows sources to be either file or other main target and handles +# generation of those dependency targets. +# +class basic-target : abstract-target +{ + import build-request ; + import build-system ; + import "class" : new ; + import errors ; + import feature ; + import property ; + import property-set ; + import sequence ; + import set ; + import targets ; + import virtual-target ; + + rule __init__ ( name : project : sources * : requirements * + : default-build * : usage-requirements * ) + { + abstract-target.__init__ $(name) : $(project) ; + + self.sources = $(sources) ; + if ! $(requirements) { + requirements = [ property-set.empty ] ; + } + self.requirements = $(requirements) ; + if ! $(default-build) + { + default-build = [ property-set.empty ] ; + } + self.default-build = $(default-build) ; + if ! $(usage-requirements) + { + usage-requirements = [ property-set.empty ] ; + } + self.usage-requirements = $(usage-requirements) ; + + if $(sources:G) + { + errors.user-error properties found in the 'sources' parameter for + [ full-name ] ; + } + } + + rule always ( ) + { + self.always = 1 ; + } + + # Returns the list of abstract-targets which are used as sources. The extra + # properties specified for sources are not represented. The only user for + # this rule at the moment is the "--dump-tests" feature of the test system. + # + rule sources ( ) + { + if ! $(self.source-targets) + { + for local s in $(self.sources) + { + self.source-targets += + [ targets.resolve-reference $(s) : $(self.project) ] ; + } + } + return $(self.source-targets) ; + } + + rule requirements ( ) + { + return $(self.requirements) ; + } + + rule default-build ( ) + { + return $(self.default-build) ; + } + + # Returns the alternative condition for this alternative, if the condition + # is satisfied by 'property-set'. + # + rule match ( property-set debug ? ) + { + # The condition is composed of all base non-conditional properties. It + # is not clear if we should expand 'self.requirements' or not. For one + # thing, it would be nice to be able to put + # <toolset>msvc-6.0 + # in requirements. On the other hand, if we have <variant>release as a + # condition it does not make sense to require <optimization>full to be + # in the build request just to select this variant. + local bcondition = [ $(self.requirements).base ] ; + local ccondition = [ $(self.requirements).conditional ] ; + local condition = [ set.difference $(bcondition) : $(ccondition) ] ; + if $(debug) + { + ECHO " next alternative: required properties:" $(condition:E=(empty)) ; + } + + if $(condition) in [ $(property-set).raw ] + { + if $(debug) + { + ECHO " matched" ; + } + return $(condition) ; + } + else + { + if $(debug) + { + ECHO " not matched" ; + } + return no-match ; + } + } + + # Takes a target reference, which might be either target id or a dependency + # property, and generates that target using 'property-set' as build request. + # + # The results are added to the variable called 'result-var'. Usage + # requirements are added to the variable called 'usage-requirements-var'. + # + rule generate-dependencies ( dependencies * : property-set + : result-var usage-requirements-var ) + { + for local dependency in $(dependencies) + { + local grist = $(dependency:G) ; + local id = $(dependency:G=) ; + + local result = [ targets.generate-from-reference $(id) : + $(self.project) : $(property-set) ] ; + + $(result-var) += $(result[2-]:G=$(grist)) ; + $(usage-requirements-var) += [ $(result[1]).raw ] ; + } + } + + # Determines final build properties, generates sources, and calls + # 'construct'. This method should not be overridden. + # + rule generate ( property-set ) + { + if [ modules.peek : .debug-building ] + { + ECHO ; + local fn = [ full-name ] ; + ECHO [ targets.indent ] "Building target '$(fn)'" ; + targets.increase-indent ; + ECHO [ targets.indent ] "Build request: " $(property-set) [ $(property-set).raw ] ; + local cf = [ build-system.command-line-free-features ] ; + ECHO [ targets.indent ] "Command line free features: " [ $(cf).raw ] ; + ECHO [ targets.indent ] "Target requirements: " [ $(self.requirements).raw ] ; + } + targets.push-target $(__name__) ; + + if ! $(self.generated.$(property-set)) + { + # Apply free features from the command line. If user said + # define=FOO + # he most likely wants this define to be set for all compiles. + property-set = [ $(property-set).refine + [ build-system.command-line-free-features ] ] ; + local rproperties = [ targets.common-properties $(property-set) + $(self.requirements) ] ; + + if [ modules.peek : .debug-building ] + { + ECHO ; + ECHO [ targets.indent ] "Common properties: " [ $(rproperties).raw ] ; + } + + if ( $(rproperties[1]) != "@error" ) && ( [ $(rproperties).get + <build> ] != no ) + { + local source-targets ; + local properties = [ $(rproperties).non-dependency ] ; + local usage-requirements ; + + generate-dependencies [ $(rproperties).dependency ] : + $(rproperties) : properties usage-requirements ; + + generate-dependencies $(self.sources) : $(rproperties) : + source-targets usage-requirements ; + + if [ modules.peek : .debug-building ] + { + ECHO ; + ECHO [ targets.indent ] "Usage requirements for" + $(self.name)": " $(usage-requirements) ; + } + + rproperties = [ property-set.create $(properties) + $(usage-requirements) ] ; + usage-requirements = [ property-set.create $(usage-requirements) ] ; + + if [ modules.peek : .debug-building ] + { + ECHO [ targets.indent ] "Build properties: " + [ $(rproperties).raw ] ; + } + + local extra = [ $(rproperties).get <source> ] ; + source-targets += $(extra:G=) ; + # We might get duplicate sources, for example if we link to two + # libraries having the same <library> usage requirement. + # Use stable sort, since for some targets the order is + # important. E.g. RUN_PY target need python source to come + # first. + source-targets = [ sequence.unique $(source-targets) : stable ] ; + + local result = [ construct $(self.name) : $(source-targets) : + $(rproperties) ] ; + + if $(result) + { + local gur = $(result[1]) ; + result = $(result[2-]) ; + + if $(self.always) + { + for local t in $(result) + { + $(t).always ; + } + } + + local s = [ create-subvariant $(result) + : [ virtual-target.recent-targets ] + : $(property-set) : $(source-targets) + : $(rproperties) : $(usage-requirements) ] ; + virtual-target.clear-recent-targets ; + + local ur = [ compute-usage-requirements $(s) ] ; + ur = [ $(ur).add $(gur) ] ; + $(s).set-usage-requirements $(ur) ; + if [ modules.peek : .debug-building ] + { + ECHO [ targets.indent ] "Usage requirements from" + $(self.name)": " [ $(ur).raw ] ; + } + + self.generated.$(property-set) = $(ur) $(result) ; + } + } + else + { + if $(rproperties[1]) = "@error" + { + ECHO [ targets.indent ] "Skipping build of:" [ full-name ] + "cannot compute common properties" ; + } + else if [ $(rproperties).get <build> ] = no + { + # If we just see <build>no, we cannot produce any reasonable + # diagnostics. The code that adds this property is expected + # to explain why a target is not built, for example using + # the configure.log-component-configuration function. + } + else + { + ECHO [ targets.indent ] "Skipping build of: " [ full-name ] + " unknown reason" ; + } + + # We are here either because there has been an error computing + # properties or there is <build>no in properties. In the latter + # case we do not want any diagnostic. In the former case, we + # need diagnostics. FIXME + + # If this target fails to build, add <build>no to properties to + # cause any parent target to fail to build. Except that it + # - does not work now, since we check for <build>no only in + # common properties, but not in properties that came from + # dependencies + # - it is not clear if that is a good idea anyway. The alias + # target, for example, should not fail to build if a + # dependency fails. + self.generated.$(property-set) = [ property-set.create <build>no ] ; + } + } + else + { + if [ modules.peek : .debug-building ] + { + ECHO [ targets.indent ] "Already built" ; + local ur = $(self.generated.$(property-set)) ; + ur = $(ur[0]) ; + targets.increase-indent ; + ECHO [ targets.indent ] "Usage requirements from" + $(self.name)": " [ $(ur).raw ] ; + targets.decrease-indent ; + } + } + + targets.pop-target ; + targets.decrease-indent ; + return $(self.generated.$(property-set)) ; + } + + # Given the set of generated targets, and refined build properties, + # determines and sets appropriate usage requirements on those targets. + # + rule compute-usage-requirements ( subvariant ) + { + local rproperties = [ $(subvariant).build-properties ] ; + xusage-requirements = [ targets.evaluate-requirements + $(self.usage-requirements) : $(rproperties) : added ] ; + + # We generate all dependency properties and add them, as well as their + # usage requirements, to the result. + local extra ; + generate-dependencies [ $(xusage-requirements).dependency ] : + $(rproperties) : extra extra ; + + local result = [ property-set.create + [ $(xusage-requirements).non-dependency ] $(extra) ] ; + + # Propagate usage requirements we got from sources, except for the + # <pch-header> and <pch-file> features. + # + # That feature specifies which pch file to use, and should apply only to + # direct dependents. Consider: + # + # pch pch1 : ... + # lib lib1 : ..... pch1 ; + # pch pch2 : + # lib lib2 : pch2 lib1 ; + # + # Here, lib2 should not get <pch-header> property from pch1. + # + # Essentially, when those two features are in usage requirements, they + # are propagated only to direct dependents. We might need a more general + # mechanism, but for now, only those two features are special. + # + # TODO - Actually there are more possible candidates like for instance + # when listing static library X as a source for another static library. + # Then static library X will be added as a <source> property to the + # second library's usage requirements but those requirements should last + # only up to the first executable or shared library that actually links + # to it. + local raw = [ $(subvariant).sources-usage-requirements ] ; + raw = [ $(raw).raw ] ; + raw = [ property.change $(raw) : <pch-header> ] ; + raw = [ property.change $(raw) : <pch-file> ] ; + return [ $(result).add [ property-set.create $(raw) ] ] ; + } + + # Creates new subvariant instances for 'targets'. + # 'root-targets' - virtual targets to be returned to dependants + # 'all-targets' - virtual targets created while building this main target + # 'build-request' - property-set instance with requested build properties + # + local rule create-subvariant ( root-targets * : all-targets * : + build-request : sources * : rproperties : usage-requirements ) + { + for local e in $(root-targets) + { + $(e).root true ; + } + + # Process all virtual targets that will be created if this main target + # is created. + local s = [ new subvariant $(__name__) : $(build-request) : $(sources) : + $(rproperties) : $(usage-requirements) : $(all-targets) ] ; + for local v in $(all-targets) + { + if ! [ $(v).creating-subvariant ] + { + $(v).creating-subvariant $(s) ; + } + } + return $(s) ; + } + + # Constructs virtual targets for this abstract target and the dependency + # graph. Returns a usage-requirements property-set and a list of virtual + # targets. Should be overriden in derived classes. + # + rule construct ( name : source-targets * : properties * ) + { + errors.error "method should be defined in derived classes" ; + } +} + + +class typed-target : basic-target +{ + import generators ; + + rule __init__ ( name : project : type : sources * : requirements * : + default-build * : usage-requirements * ) + { + basic-target.__init__ $(name) : $(project) : $(sources) : + $(requirements) : $(default-build) : $(usage-requirements) ; + + self.type = $(type) ; + } + + rule type ( ) + { + return $(self.type) ; + } + + rule construct ( name : source-targets * : property-set ) + { + local r = [ generators.construct $(self.project) $(name:S=) : $(self.type) + : [ property-set.create [ $(property-set).raw ] + <main-target-type>$(self.type) ] + : $(source-targets) : true ] ; + if ! $(r) + { + ECHO "warn: Unable to construct" [ full-name ] ; + + # Are there any top-level generators for this type/property set. + if ! [ generators.find-viable-generators $(self.type) + : $(property-set) ] + { + ECHO "error: no generators were found for type '$(self.type)'" ; + ECHO "error: and the requested properties" ; + ECHO "error: make sure you've configured the needed tools" ; + ECHO "See http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html" ; + ECHO "To debug this problem, try the --debug-generators option." ; + EXIT ; + } + } + return $(r) ; + } +} + + +# Return the list of sources to use, if main target rule is invoked with +# 'sources'. If there are any objects in 'sources', they are treated as main +# target instances, and the name of such targets are adjusted to be +# '<name_of_this_target>__<name_of_source_target>'. Such renaming is disabled if +# a non-empty value is passed as the 'no-renaming' parameter. +# +rule main-target-sources ( sources * : main-target-name : no-renaming ? ) +{ + local result ; + for local t in $(sources) + { + if [ class.is-instance $(t) ] + { + local name = [ $(t).name ] ; + if ! $(no-renaming) + { + name = $(main-target-name)__$(name) ; + $(t).rename $(name) ; + } + # Inline targets are not built by default. + local p = [ $(t).project ] ; + $(p).mark-target-as-explicit $(name) ; + result += $(name) ; + } + else + { + result += $(t) ; + } + } + return $(result) ; +} + + +# Returns the requirements to use when declaring a main target, obtained by +# translating all specified property paths and refining project requirements +# with the ones specified for the target. +# +rule main-target-requirements ( + specification * # Properties explicitly specified for the main target. + : project # Project where the main target is to be declared. +) +{ + specification += [ toolset.requirements ] ; + + local requirements = [ property-set.refine-from-user-input + [ $(project).get requirements ] : $(specification) : + [ $(project).project-module ] : [ $(project).get location ] ] ; + if $(requirements[1]) = "@error" + { + errors.error "Conflicting requirements for target:" $(requirements) ; + } + return $(requirements) ; +} + + +# Returns the usage requirements to use when declaring a main target, which are +# obtained by translating all specified property paths and adding project's +# usage requirements. +# +rule main-target-usage-requirements ( + specification * # Use-properties explicitly specified for a main target. + : project # Project where the main target is to be declared. +) +{ + local project-usage-requirements = [ $(project).get usage-requirements ] ; + + # We do not use 'refine-from-user-input' because: + # - I am not sure if removing parent's usage requirements makes sense + # - refining usage requirements is not needed, since usage requirements are + # always free. + local usage-requirements = [ property-set.create-from-user-input + $(specification) + : [ $(project).project-module ] [ $(project).get location ] ] ; + + return [ $(project-usage-requirements).add $(usage-requirements) ] ; +} + + +# Return the default build value to use when declaring a main target, which is +# obtained by using the specified value if not empty and parent's default build +# attribute otherwise. +# +rule main-target-default-build ( + specification * # Default build explicitly specified for a main target. + : project # Project where the main target is to be declared. +) +{ + local result ; + if $(specification) + { + result = $(specification) ; + } + else + { + result = [ $(project).get default-build ] ; + } + return [ property-set.create-with-validation $(result) ] ; +} + + +# Registers the specified target as a main target alternative and returns it. +# +rule main-target-alternative ( target ) +{ + local ptarget = [ $(target).project ] ; + $(ptarget).add-alternative $(target) ; + return $(target) ; +} + +# Creates a new metargets with the specified properties, using 'klass' as +# the class. The 'name', 'sources', +# 'requirements', 'default-build' and 'usage-requirements' are assumed to be in +# the form specified by the user in Jamfile corresponding to 'project'. +# +rule create-metatarget ( klass : project : name : sources * : requirements * : + default-build * : usage-requirements * ) +{ + return [ + targets.main-target-alternative + [ new $(klass) $(name) : $(project) + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + : [ targets.main-target-usage-requirements $(usage-requirements) : $(project) ] + ] ] ; +} + +# Creates a typed-target with the specified properties. The 'name', 'sources', +# 'requirements', 'default-build' and 'usage-requirements' are assumed to be in +# the form specified by the user in Jamfile corresponding to 'project'. +# +rule create-typed-target ( type : project : name : sources * : requirements * : + default-build * : usage-requirements * ) +{ + return [ + targets.main-target-alternative + [ new typed-target $(name) : $(project) : $(type) + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + : [ targets.main-target-usage-requirements $(usage-requirements) : $(project) ] + ] ] ; +} diff --git a/jam-files/boost-build/build/targets.py b/jam-files/boost-build/build/targets.py new file mode 100644 index 00000000..a35612ce --- /dev/null +++ b/jam-files/boost-build/build/targets.py @@ -0,0 +1,1401 @@ +# Status: ported. +# Base revision: 64488 + +# Copyright Vladimir Prus 2002-2007. +# Copyright Rene Rivera 2006. +# +# 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) + +# Supports 'abstract' targets, which are targets explicitly defined in Jamfile. +# +# Abstract targets are represented by classes derived from 'AbstractTarget' class. +# The first abstract target is 'project_target', which is created for each +# Jamfile, and can be obtained by the 'target' rule in the Jamfile's module. +# (see project.jam). +# +# Project targets keep a list of 'MainTarget' instances. +# A main target is what the user explicitly defines in a Jamfile. It is +# possible to have several definitions for a main target, for example to have +# different lists of sources for different platforms. So, main targets +# keep a list of alternatives. +# +# Each alternative is an instance of 'AbstractTarget'. When a main target +# subvariant is defined by some rule, that rule will decide what class to +# use, create an instance of that class and add it to the list of alternatives +# for the main target. +# +# Rules supplied by the build system will use only targets derived +# from 'BasicTarget' class, which will provide some default behaviour. +# There will be two classes derived from it, 'make-target', created by the +# 'make' rule, and 'TypedTarget', created by rules such as 'exe' and 'dll'. + +# +# +------------------------+ +# |AbstractTarget | +# +========================+ +# |name | +# |project | +# | | +# |generate(properties) = 0| +# +-----------+------------+ +# | +# ^ +# / \ +# +-+-+ +# | +# | +# +------------------------+------+------------------------------+ +# | | | +# | | | +# +----------+-----------+ +------+------+ +------+-------+ +# | project_target | | MainTarget | | BasicTarget | +# +======================+ 1 * +=============+ alternatives +==============+ +# | generate(properties) |o-----------+ generate |<>------------->| generate | +# | main-target | +-------------+ | construct = 0| +# +----------------------+ +--------------+ +# | +# ^ +# / \ +# +-+-+ +# | +# | +# ...--+----------------+------------------+----------------+---+ +# | | | | +# | | | | +# ... ---+-----+ +------+-------+ +------+------+ +--------+-----+ +# | | TypedTarget | | make-target | | stage-target | +# . +==============+ +=============+ +==============+ +# . | construct | | construct | | construct | +# +--------------+ +-------------+ +--------------+ + +import re +import os.path +import sys + +from b2.manager import get_manager + +from b2.util.utility import * +import property, project, virtual_target, property_set, feature, generators, toolset +from virtual_target import Subvariant +from b2.exceptions import * +from b2.util.sequence import unique +from b2.util import path, bjam_signature +from b2.build.errors import user_error_checkpoint + +import b2.build.build_request as build_request + +import b2.util.set +_re_separate_target_from_properties = re.compile (r'^([^<]*)(/(<.*))?$') + +class TargetRegistry: + + def __init__ (self): + # All targets that are currently being built. + # Only the key is id (target), the value is the actual object. + self.targets_being_built_ = {} + + # Current indent for debugging messages + self.indent_ = "" + + self.debug_building_ = "--debug-building" in bjam.variable("ARGV") + + self.targets_ = [] + + def main_target_alternative (self, target): + """ Registers the specified target as a main target alternatives. + Returns 'target'. + """ + target.project ().add_alternative (target) + return target + + def main_target_sources (self, sources, main_target_name, no_renaming=0): + """Return the list of sources to use, if main target rule is invoked + with 'sources'. If there are any objects in 'sources', they are treated + as main target instances, and the name of such targets are adjusted to + be '<name_of_this_target>__<name_of_source_target>'. Such renaming + is disabled is non-empty value is passed for 'no-renaming' parameter.""" + result = [] + + for t in sources: + + t = b2.util.jam_to_value_maybe(t) + + if isinstance (t, AbstractTarget): + name = t.name () + + if not no_renaming: + name = main_target_name + '__' + name + t.rename (name) + + # Inline targets are not built by default. + p = t.project() + p.mark_targets_as_explicit([name]) + result.append(name) + + else: + result.append (t) + + return result + + + def main_target_requirements(self, specification, project): + """Returns the requirement to use when declaring a main target, + which are obtained by + - translating all specified property paths, and + - refining project requirements with the one specified for the target + + 'specification' are the properties xplicitly specified for a + main target + 'project' is the project where the main taret is to be declared.""" + + specification.extend(toolset.requirements()) + + requirements = property_set.refine_from_user_input( + project.get("requirements"), specification, + project.project_module(), project.get("location")) + + return requirements + + def main_target_usage_requirements (self, specification, project): + """ Returns the use requirement to use when declaraing a main target, + which are obtained by + - translating all specified property paths, and + - adding project's usage requirements + specification: Use-properties explicitly specified for a main target + project: Project where the main target is to be declared + """ + project_usage_requirements = project.get ('usage-requirements') + + # We don't use 'refine-from-user-input' because I'm not sure if: + # - removing of parent's usage requirements makes sense + # - refining of usage requirements is not needed, since usage requirements + # are always free. + usage_requirements = property_set.create_from_user_input( + specification, project.project_module(), project.get("location")) + + return project_usage_requirements.add (usage_requirements) + + def main_target_default_build (self, specification, project): + """ Return the default build value to use when declaring a main target, + which is obtained by using specified value if not empty and parent's + default build attribute otherwise. + specification: Default build explicitly specified for a main target + project: Project where the main target is to be declared + """ + if specification: + return property_set.create_with_validation(specification) + else: + return project.get ('default-build') + + def start_building (self, main_target_instance): + """ Helper rules to detect cycles in main target references. + """ + if self.targets_being_built_.has_key(id(main_target_instance)): + names = [] + for t in self.targets_being_built_.values() + [main_target_instance]: + names.append (t.full_name()) + + get_manager().errors()("Recursion in main target references\n") + + self.targets_being_built_[id(main_target_instance)] = main_target_instance + + def end_building (self, main_target_instance): + assert (self.targets_being_built_.has_key (id (main_target_instance))) + del self.targets_being_built_ [id (main_target_instance)] + + def create_typed_target (self, type, project, name, sources, requirements, default_build, usage_requirements): + """ Creates a TypedTarget with the specified properties. + The 'name', 'sources', 'requirements', 'default_build' and + 'usage_requirements' are assumed to be in the form specified + by the user in Jamfile corresponding to 'project'. + """ + return self.main_target_alternative (TypedTarget (name, project, type, + self.main_target_sources (sources, name), + self.main_target_requirements (requirements, project), + self.main_target_default_build (default_build, project), + self.main_target_usage_requirements (usage_requirements, project))) + + def increase_indent(self): + self.indent_ += " " + + def decrease_indent(self): + self.indent_ = self.indent_[0:-4] + + def logging(self): + return self.debug_building_ + + def log(self, message): + if self.debug_building_: + print self.indent_ + message + + def push_target(self, target): + self.targets_.append(target) + + def pop_target(self): + self.targets_ = self.targets_[:-1] + + def current(self): + return self.targets_[0] + + +class GenerateResult: + + def __init__ (self, ur=None, targets=None): + if not targets: + targets = [] + + self.__usage_requirements = ur + self.__targets = targets + assert all(isinstance(t, virtual_target.VirtualTarget) for t in targets) + + if not self.__usage_requirements: + self.__usage_requirements = property_set.empty () + + def usage_requirements (self): + return self.__usage_requirements + + def targets (self): + return self.__targets + + def extend (self, other): + assert (isinstance (other, GenerateResult)) + + self.__usage_requirements = self.__usage_requirements.add (other.usage_requirements ()) + self.__targets.extend (other.targets ()) + +class AbstractTarget: + """ Base class for all abstract targets. + """ + def __init__ (self, name, project, manager = None): + """ manager: the Manager object + name: name of the target + project: the project target to which this one belongs + manager:the manager object. If none, uses project.manager () + """ + assert (isinstance (project, ProjectTarget)) + # Note: it might seem that we don't need either name or project at all. + # However, there are places where we really need it. One example is error + # messages which should name problematic targets. Another is setting correct + # paths for sources and generated files. + + # Why allow manager to be specified? Because otherwise project target could not derive + # from this class. + if manager: + self.manager_ = manager + else: + self.manager_ = project.manager () + + self.name_ = name + self.project_ = project + + def manager (self): + return self.manager_ + + def name (self): + """ Returns the name of this target. + """ + return self.name_ + + def project (self): + """ Returns the project for this target. + """ + return self.project_ + + def location (self): + """ Return the location where the target was declared. + """ + return self.location_ + + def full_name (self): + """ Returns a user-readable name for this target. + """ + location = self.project ().get ('location') + return location + '/' + self.name_ + + def generate (self, property_set): + """ Takes a property set. Generates virtual targets for this abstract + target, using the specified properties, unless a different value of some + feature is required by the target. + On success, returns a GenerateResult instance with: + - a property_set with the usage requirements to be + applied to dependents + - a list of produced virtual targets, which may be + empty. + If 'property_set' is empty, performs default build of this + target, in a way specific to derived class. + """ + raise BaseException ("method should be defined in derived classes") + + def rename (self, new_name): + self.name_ = new_name + +class ProjectTarget (AbstractTarget): + """ Project target class (derived from 'AbstractTarget') + + This class these responsibilities: + - maintaining a list of main target in this project and + building it + + Main targets are constructed in two stages: + - When Jamfile is read, a number of calls to 'add_alternative' is made. + At that time, alternatives can also be renamed to account for inline + targets. + - The first time 'main-target' or 'has-main-target' rule is called, + all alternatives are enumerated an main targets are created. + """ + def __init__ (self, manager, name, project_module, parent_project, requirements, default_build): + AbstractTarget.__init__ (self, name, self, manager) + + self.project_module_ = project_module + self.location_ = manager.projects().attribute (project_module, 'location') + self.requirements_ = requirements + self.default_build_ = default_build + + self.build_dir_ = None + + # A cache of IDs + self.ids_cache_ = {} + + # True is main targets have already been built. + self.built_main_targets_ = False + + # A list of the registered alternatives for this project. + self.alternatives_ = [] + + # A map from main target name to the target corresponding + # to it. + self.main_target_ = {} + + # Targets marked as explicit. + self.explicit_targets_ = set() + + # Targets marked as always + self.always_targets_ = set() + + # The constants defined for this project. + self.constants_ = {} + + # Whether targets for all main target are already created. + self.built_main_targets_ = 0 + + if parent_project: + self.inherit (parent_project) + + + # TODO: This is needed only by the 'make' rule. Need to find the + # way to make 'make' work without this method. + def project_module (self): + return self.project_module_ + + def get (self, attribute): + return self.manager().projects().attribute( + self.project_module_, attribute) + + def build_dir (self): + if not self.build_dir_: + self.build_dir_ = self.get ('build-dir') + if not self.build_dir_: + self.build_dir_ = os.path.join(self.project_.get ('location'), 'bin') + + return self.build_dir_ + + def generate (self, ps): + """ Generates all possible targets contained in this project. + """ + self.manager_.targets().log( + "Building project '%s' with '%s'" % (self.name (), str(ps))) + self.manager_.targets().increase_indent () + + result = GenerateResult () + + for t in self.targets_to_build (): + g = t.generate (ps) + result.extend (g) + + self.manager_.targets().decrease_indent () + return result + + def targets_to_build (self): + """ Computes and returns a list of AbstractTarget instances which + must be built when this project is built. + """ + result = [] + + if not self.built_main_targets_: + self.build_main_targets () + + # Collect all main targets here, except for "explicit" ones. + for n, t in self.main_target_.iteritems (): + if not t.name () in self.explicit_targets_: + result.append (t) + + # Collect all projects referenced via "projects-to-build" attribute. + self_location = self.get ('location') + for pn in self.get ('projects-to-build'): + result.append (self.find(pn + "/")) + + return result + + def mark_targets_as_explicit (self, target_names): + """Add 'target' to the list of targets in this project + that should be build only by explicit request.""" + + # Record the name of the target, not instance, since this + # rule is called before main target instaces are created. + self.explicit_targets_.update(target_names) + + def mark_targets_as_always(self, target_names): + self.always_targets_.update(target_names) + + def add_alternative (self, target_instance): + """ Add new target alternative. + """ + if self.built_main_targets_: + raise IllegalOperation ("add-alternative called when main targets are already created for project '%s'" % self.full_name ()) + + self.alternatives_.append (target_instance) + + def main_target (self, name): + if not self.built_main_targets_: + self.build_main_targets() + + return self.main_target_[name] + + def has_main_target (self, name): + """Tells if a main target with the specified name exists.""" + if not self.built_main_targets_: + self.build_main_targets() + + return self.main_target_.has_key(name) + + def create_main_target (self, name): + """ Returns a 'MainTarget' class instance corresponding to the 'name'. + """ + if not self.built_main_targets_: + self.build_main_targets () + + return self.main_targets_.get (name, None) + + + def find_really(self, id): + """ Find and return the target with the specified id, treated + relative to self. + """ + result = None + current_location = self.get ('location') + + __re_split_project_target = re.compile (r'(.*)//(.*)') + split = __re_split_project_target.match (id) + + project_part = None + target_part = None + + if split: + project_part = split.group (1) + target_part = split.group (2) + + project_registry = self.project_.manager ().projects () + + extra_error_message = '' + if project_part: + # There's explicit project part in id. Looks up the + # project and pass the request to it. + pm = project_registry.find (project_part, current_location) + + if pm: + project_target = project_registry.target (pm) + result = project_target.find (target_part, no_error=1) + + else: + extra_error_message = "error: could not find project '$(project_part)'" + + else: + # Interpret target-name as name of main target + # Need to do this before checking for file. Consider this: + # + # exe test : test.cpp ; + # install s : test : <location>. ; + # + # After first build we'll have target 'test' in Jamfile and file + # 'test' on the disk. We need target to override the file. + + result = None + if self.has_main_target(id): + result = self.main_target(id) + + if not result: + result = FileReference (self.manager_, id, self.project_) + if not result.exists (): + # File actually does not exist. + # Reset 'target' so that an error is issued. + result = None + + + if not result: + # Interpret id as project-id + project_module = project_registry.find (id, current_location) + if project_module: + result = project_registry.target (project_module) + + return result + + def find (self, id, no_error = False): + v = self.ids_cache_.get (id, None) + + if not v: + v = self.find_really (id) + self.ids_cache_ [id] = v + + if v or no_error: + return v + + raise BaseException ("Unable to find file or target named '%s'\nreferred from project at '%s'" % (id, self.get ('location'))) + + + def build_main_targets (self): + self.built_main_targets_ = True + + for a in self.alternatives_: + name = a.name () + if not self.main_target_.has_key (name): + t = MainTarget (name, self.project_) + self.main_target_ [name] = t + + if name in self.always_targets_: + a.always() + + self.main_target_ [name].add_alternative (a) + + def add_constant(self, name, value, path=0): + """Adds a new constant for this project. + + The constant will be available for use in Jamfile + module for this project. If 'path' is true, + the constant will be interpreted relatively + to the location of project. + """ + + if path: + l = self.location_ + if not l: + # Project corresponding to config files do not have + # 'location' attribute, but do have source location. + # It might be more reasonable to make every project have + # a location and use some other approach to prevent buildable + # targets in config files, but that's for later. + l = get('source-location') + + value = os.path.join(l, value) + # Now make the value absolute path + value = os.path.join(os.getcwd(), value) + + self.constants_[name] = value + bjam.call("set-variable", self.project_module(), name, value) + + def inherit(self, parent_project): + for c in parent_project.constants_: + # No need to pass the type. Path constants were converted to + # absolute paths already by parent. + self.add_constant(c, parent_project.constants_[c]) + + # Import rules from parent + this_module = self.project_module() + parent_module = parent_project.project_module() + + rules = bjam.call("RULENAMES", parent_module) + if not rules: + rules = [] + user_rules = [x for x in rules + if x not in self.manager().projects().project_rules().all_names()] + if user_rules: + bjam.call("import-rules-from-parent", parent_module, this_module, user_rules) + +class MainTarget (AbstractTarget): + """ A named top-level target in Jamfile. + """ + def __init__ (self, name, project): + AbstractTarget.__init__ (self, name, project) + self.alternatives_ = [] + self.default_build_ = property_set.empty () + + def add_alternative (self, target): + """ Add a new alternative for this target. + """ + d = target.default_build () + + if self.alternatives_ and self.default_build_ != d: + get_manager().errors()("default build must be identical in all alternatives\n" + "main target is '%s'\n" + "with '%s'\n" + "differing from previous default build: '%s'" % (self.full_name (), d.raw (), self.default_build_.raw ())) + + else: + self.default_build_ = d + + self.alternatives_.append (target) + + def __select_alternatives (self, property_set, debug): + """ Returns the best viable alternative for this property_set + See the documentation for selection rules. + # TODO: shouldn't this be 'alternative' (singular)? + """ + # When selecting alternatives we have to consider defaults, + # for example: + # lib l : l.cpp : <variant>debug ; + # lib l : l_opt.cpp : <variant>release ; + # won't work unless we add default value <variant>debug. + property_set = property_set.add_defaults () + + # The algorithm: we keep the current best viable alternative. + # When we've got new best viable alternative, we compare it + # with the current one. + best = None + best_properties = None + + if len (self.alternatives_) == 0: + return None + + if len (self.alternatives_) == 1: + return self.alternatives_ [0] + + if debug: + print "Property set for selection:", property_set + + for v in self.alternatives_: + properties = v.match (property_set, debug) + + if properties is not None: + if not best: + best = v + best_properties = properties + + else: + if b2.util.set.equal (properties, best_properties): + return None + + elif b2.util.set.contains (properties, best_properties): + # Do nothing, this alternative is worse + pass + + elif b2.util.set.contains (best_properties, properties): + best = v + best_properties = properties + + else: + return None + + return best + + def apply_default_build (self, property_set): + return apply_default_build(property_set, self.default_build_) + + def generate (self, ps): + """ Select an alternative for this main target, by finding all alternatives + which requirements are satisfied by 'properties' and picking the one with + longest requirements set. + Returns the result of calling 'generate' on that alternative. + """ + self.manager_.targets ().start_building (self) + + # We want composite properties in build request act as if + # all the properties it expands too are explicitly specified. + ps = ps.expand () + + all_property_sets = self.apply_default_build (ps) + + result = GenerateResult () + + for p in all_property_sets: + result.extend (self.__generate_really (p)) + + self.manager_.targets ().end_building (self) + + return result + + def __generate_really (self, prop_set): + """ Generates the main target with the given property set + and returns a list which first element is property_set object + containing usage_requirements of generated target and with + generated virtual target in other elements. It's possible + that no targets are generated. + """ + best_alternative = self.__select_alternatives (prop_set, debug=0) + + if not best_alternative: + # FIXME: revive. + # self.__select_alternatives(prop_set, debug=1) + self.manager_.errors()( + "No best alternative for '%s'.\n" + % (self.full_name(),)) + + result = best_alternative.generate (prop_set) + + # Now return virtual targets for the only alternative + return result + + def rename(self, new_name): + AbstractTarget.rename(self, new_name) + for a in self.alternatives_: + a.rename(new_name) + +class FileReference (AbstractTarget): + """ Abstract target which refers to a source file. + This is artificial creature; it's usefull so that sources to + a target can be represented as list of abstract target instances. + """ + def __init__ (self, manager, file, project): + AbstractTarget.__init__ (self, file, project) + self.file_location_ = None + + def generate (self, properties): + return GenerateResult (None, [ + self.manager_.virtual_targets ().from_file ( + self.name_, self.location(), self.project_) ]) + + def exists (self): + """ Returns true if the referred file really exists. + """ + if self.location (): + return True + else: + return False + + def location (self): + # Returns the location of target. Needed by 'testing.jam' + if not self.file_location_: + source_location = self.project_.get('source-location') + + for src_dir in source_location: + location = os.path.join(src_dir, self.name()) + if os.path.isfile(location): + self.file_location_ = src_dir + self.file_path = location + break + + return self.file_location_ + +def resolve_reference(target_reference, project): + """ Given a target_reference, made in context of 'project', + returns the AbstractTarget instance that is referred to, as well + as properties explicitly specified for this reference. + """ + # Separate target name from properties override + split = _re_separate_target_from_properties.match (target_reference) + if not split: + raise BaseException ("Invalid reference: '%s'" % target_reference) + + id = split.group (1) + + sproperties = [] + + if split.group (3): + sproperties = property.create_from_strings(feature.split(split.group(3))) + sproperties = feature.expand_composites(sproperties) + + # Find the target + target = project.find (id) + + return (target, property_set.create(sproperties)) + +def generate_from_reference(target_reference, project, property_set): + """ Attempts to generate the target given by target reference, which + can refer both to a main target or to a file. + Returns a list consisting of + - usage requirements + - generated virtual targets, if any + target_reference: Target reference + project: Project where the reference is made + property_set: Properties of the main target that makes the reference + """ + target, sproperties = resolve_reference(target_reference, project) + + # Take properties which should be propagated and refine them + # with source-specific requirements. + propagated = property_set.propagated() + rproperties = propagated.refine(sproperties) + + return target.generate(rproperties) + + + +class BasicTarget (AbstractTarget): + """ Implements the most standard way of constructing main target + alternative from sources. Allows sources to be either file or + other main target and handles generation of those dependency + targets. + """ + def __init__ (self, name, project, sources, requirements = None, default_build = None, usage_requirements = None): + AbstractTarget.__init__ (self, name, project) + + for s in sources: + if get_grist (s): + raise InvalidSource ("property '%s' found in the 'sources' parameter for '%s'" % (s, name)) + + self.sources_ = sources + + if not requirements: requirements = property_set.empty () + self.requirements_ = requirements + + if not default_build: default_build = property_set.empty () + self.default_build_ = default_build + + if not usage_requirements: usage_requirements = property_set.empty () + self.usage_requirements_ = usage_requirements + + # A cache for resolved references + self.source_targets_ = None + + # A cache for generated targets + self.generated_ = {} + + # A cache for build requests + self.request_cache = {} + + # Result of 'capture_user_context' has everything. For example, if this + # target is declare as result of loading Jamfile which was loaded when + # building target B which was requested from A, then we'll have A, B and + # Jamroot location in context. We only care about Jamroot location, most + # of the times. + self.user_context_ = self.manager_.errors().capture_user_context()[-1:] + + self.always_ = False + + def always(self): + self.always_ = True + + def sources (self): + """ Returns the list of AbstractTargets which are used as sources. + The extra properties specified for sources are not represented. + The only used of this rule at the moment is the '--dump-tests' + feature of the test system. + """ + if self.source_targets_ == None: + self.source_targets_ = [] + for s in self.sources_: + self.source_targets_.append(resolve_reference(s, self.project_)[0]) + + return self.source_targets_ + + def requirements (self): + return self.requirements_ + + def default_build (self): + return self.default_build_ + + def common_properties (self, build_request, requirements): + """ Given build request and requirements, return properties + common to dependency build request and target build + properties. + """ + # For optimization, we add free unconditional requirements directly, + # without using complex algorithsm. + # This gives the complex algorithm better chance of caching results. + # The exact effect of this "optimization" is no longer clear + free_unconditional = [] + other = [] + for p in requirements.all(): + if p.feature().free() and not p.condition() and p.feature().name() != 'conditional': + free_unconditional.append(p) + else: + other.append(p) + other = property_set.create(other) + + key = (build_request, other) + if not self.request_cache.has_key(key): + self.request_cache[key] = self.__common_properties2 (build_request, other) + + return self.request_cache[key].add_raw(free_unconditional) + + # Given 'context' -- a set of already present properties, and 'requirements', + # decide which extra properties should be applied to 'context'. + # For conditional requirements, this means evaluating condition. For + # indirect conditional requirements, this means calling a rule. Ordinary + # requirements are always applied. + # + # Handles situation where evaluating one conditional requirements affects + # condition of another conditional requirements, for example: + # + # <toolset>gcc:<variant>release <variant>release:<define>RELEASE + # + # If 'what' is 'refined' returns context refined with new requirements. + # If 'what' is 'added' returns just the requirements that must be applied. + def evaluate_requirements(self, requirements, context, what): + # Apply non-conditional requirements. + # It's possible that that further conditional requirement change + # a value set by non-conditional requirements. For example: + # + # exe a : a.cpp : <threading>single <toolset>foo:<threading>multi ; + # + # I'm not sure if this should be an error, or not, especially given that + # + # <threading>single + # + # might come from project's requirements. + unconditional = feature.expand(requirements.non_conditional()) + + context = context.refine(property_set.create(unconditional)) + + # We've collected properties that surely must be present in common + # properties. We now try to figure out what other properties + # should be added in order to satisfy rules (4)-(6) from the docs. + + conditionals = property_set.create(requirements.conditional()) + + # It's supposed that #conditionals iterations + # should be enough for properties to propagate along conditions in any + # direction. + max_iterations = len(conditionals.all()) +\ + len(requirements.get("<conditional>")) + 1 + + added_requirements = [] + current = context + + # It's assumed that ordinary conditional requirements can't add + # <indirect-conditional> properties, and that rules referred + # by <indirect-conditional> properties can't add new + # <indirect-conditional> properties. So the list of indirect conditionals + # does not change. + indirect = requirements.get("<conditional>") + + ok = 0 + for i in range(0, max_iterations): + + e = conditionals.evaluate_conditionals(current).all()[:] + + # Evaluate indirect conditionals. + for i in indirect: + i = b2.util.jam_to_value_maybe(i) + if callable(i): + # This is Python callable, yeah. + e.extend(i(current)) + else: + # Name of bjam function. Because bjam is unable to handle + # list of Property, pass list of strings. + br = b2.util.call_jam_function(i[1:], [str(p) for p in current.all()]) + if br: + e.extend(property.create_from_strings(br)) + + if e == added_requirements: + # If we got the same result, we've found final properties. + ok = 1 + break + else: + # Oops, results of evaluation of conditionals has changed. + # Also 'current' contains leftover from previous evaluation. + # Recompute 'current' using initial properties and conditional + # requirements. + added_requirements = e + current = context.refine(property_set.create(feature.expand(e))) + + if not ok: + self.manager().errors()("Can't evaluate conditional properties " + + str(conditionals)) + + + if what == "added": + return property_set.create(unconditional + added_requirements) + elif what == "refined": + return current + else: + self.manager().errors("Invalid value of the 'what' parameter") + + def __common_properties2(self, build_request, requirements): + # This guarantees that default properties are present + # in result, unless they are overrided by some requirement. + # TODO: There is possibility that we've added <foo>bar, which is composite + # and expands to <foo2>bar2, but default value of <foo2> is not bar2, + # in which case it's not clear what to do. + # + build_request = build_request.add_defaults() + # Featured added by 'add-default' can be composite and expand + # to features without default values -- so they are not added yet. + # It could be clearer/faster to expand only newly added properties + # but that's not critical. + build_request = build_request.expand() + + return self.evaluate_requirements(requirements, build_request, + "refined") + + def match (self, property_set, debug): + """ Returns the alternative condition for this alternative, if + the condition is satisfied by 'property_set'. + """ + # The condition is composed of all base non-conditional properties. + # It's not clear if we should expand 'self.requirements_' or not. + # For one thing, it would be nice to be able to put + # <toolset>msvc-6.0 + # in requirements. + # On the other hand, if we have <variant>release in condition it + # does not make sense to require <optimization>full to be in + # build request just to select this variant. + bcondition = self.requirements_.base () + ccondition = self.requirements_.conditional () + condition = b2.util.set.difference (bcondition, ccondition) + + if debug: + print " next alternative: required properties:", [str(p) for p in condition] + + if b2.util.set.contains (condition, property_set.all()): + + if debug: + print " matched" + + return condition + + else: + return None + + + def generate_dependency_targets (self, target_ids, property_set): + targets = [] + usage_requirements = [] + for id in target_ids: + + result = generate_from_reference(id, self.project_, property_set) + targets += result.targets() + usage_requirements += result.usage_requirements().all() + + return (targets, usage_requirements) + + def generate_dependency_properties(self, properties, ps): + """ Takes a target reference, which might be either target id + or a dependency property, and generates that target using + 'property_set' as build request. + + Returns a tuple (result, usage_requirements). + """ + result_properties = [] + usage_requirements = [] + for p in properties: + + result = generate_from_reference(p.value(), self.project_, ps) + + for t in result.targets(): + result_properties.append(property.Property(p.feature(), t)) + + usage_requirements += result.usage_requirements().all() + + return (result_properties, usage_requirements) + + + + + @user_error_checkpoint + def generate (self, ps): + """ Determines final build properties, generates sources, + and calls 'construct'. This method should not be + overridden. + """ + self.manager_.errors().push_user_context( + "Generating target " + self.full_name(), self.user_context_) + + if self.manager().targets().logging(): + self.manager().targets().log( + "Building target '%s'" % self.name_) + self.manager().targets().increase_indent () + self.manager().targets().log( + "Build request: '%s'" % str (ps.raw ())) + cf = self.manager().command_line_free_features() + self.manager().targets().log( + "Command line free features: '%s'" % str (cf.raw ())) + self.manager().targets().log( + "Target requirements: %s'" % str (self.requirements().raw ())) + + self.manager().targets().push_target(self) + + if not self.generated_.has_key(ps): + + # Apply free features form the command line. If user + # said + # define=FOO + # he most likely want this define to be set for all compiles. + ps = ps.refine(self.manager().command_line_free_features()) + rproperties = self.common_properties (ps, self.requirements_) + + self.manager().targets().log( + "Common properties are '%s'" % str (rproperties)) + + if rproperties.get("<build>") != ["no"]: + + result = GenerateResult () + + properties = rproperties.non_dependency () + + (p, u) = self.generate_dependency_properties (rproperties.dependency (), rproperties) + properties += p + assert all(isinstance(p, property.Property) for p in properties) + usage_requirements = u + + (source_targets, u) = self.generate_dependency_targets (self.sources_, rproperties) + usage_requirements += u + + self.manager_.targets().log( + "Usage requirements for '%s' are '%s'" % (self.name_, usage_requirements)) + + # FIXME: + + rproperties = property_set.create(properties + usage_requirements) + usage_requirements = property_set.create (usage_requirements) + + self.manager_.targets().log( + "Build properties: '%s'" % str(rproperties)) + + source_targets += rproperties.get('<source>') + + # We might get duplicate sources, for example if + # we link to two library which have the same <library> in + # usage requirements. + # Use stable sort, since for some targets the order is + # important. E.g. RUN_PY target need python source to come + # first. + source_targets = unique(source_targets, stable=True) + + # FIXME: figure why this call messes up source_targets in-place + result = self.construct (self.name_, source_targets[:], rproperties) + + if result: + assert len(result) == 2 + gur = result [0] + result = result [1] + + if self.always_: + for t in result: + t.always() + + s = self.create_subvariant ( + result, + self.manager().virtual_targets().recent_targets(), ps, + source_targets, rproperties, usage_requirements) + self.manager().virtual_targets().clear_recent_targets() + + ur = self.compute_usage_requirements (s) + ur = ur.add (gur) + s.set_usage_requirements (ur) + + self.manager_.targets().log ( + "Usage requirements from '%s' are '%s'" % + (self.name(), str(rproperties))) + + self.generated_[ps] = GenerateResult (ur, result) + else: + self.generated_[ps] = GenerateResult (property_set.empty(), []) + else: + # If we just see <build>no, we cannot produce any reasonable + # diagnostics. The code that adds this property is expected + # to explain why a target is not built, for example using + # the configure.log-component-configuration function. + + # If this target fails to build, add <build>no to properties + # to cause any parent target to fail to build. Except that it + # - does not work now, since we check for <build>no only in + # common properties, but not in properties that came from + # dependencies + # - it's not clear if that's a good idea anyway. The alias + # target, for example, should not fail to build if a dependency + # fails. + self.generated_[ps] = GenerateResult( + property_set.create(["<build>no"]), []) + else: + self.manager().targets().log ("Already built") + + self.manager().targets().pop_target() + self.manager().targets().decrease_indent() + + return self.generated_[ps] + + def compute_usage_requirements (self, subvariant): + """ Given the set of generated targets, and refined build + properties, determines and sets appripriate usage requirements + on those targets. + """ + rproperties = subvariant.build_properties () + xusage_requirements =self.evaluate_requirements( + self.usage_requirements_, rproperties, "added") + + # We generate all dependency properties and add them, + # as well as their usage requirements, to result. + (r1, r2) = self.generate_dependency_properties(xusage_requirements.dependency (), rproperties) + extra = r1 + r2 + + result = property_set.create (xusage_requirements.non_dependency () + extra) + + # Propagate usage requirements we've got from sources, except + # for the <pch-header> and <pch-file> features. + # + # That feature specifies which pch file to use, and should apply + # only to direct dependents. Consider: + # + # pch pch1 : ... + # lib lib1 : ..... pch1 ; + # pch pch2 : + # lib lib2 : pch2 lib1 ; + # + # Here, lib2 should not get <pch-header> property from pch1. + # + # Essentially, when those two features are in usage requirements, + # they are propagated only to direct dependents. We might need + # a more general mechanism, but for now, only those two + # features are special. + raw = subvariant.sources_usage_requirements().raw() + raw = property.change(raw, "<pch-header>", None); + raw = property.change(raw, "<pch-file>", None); + result = result.add(property_set.create(raw)) + + return result + + def create_subvariant (self, root_targets, all_targets, + build_request, sources, + rproperties, usage_requirements): + """Creates a new subvariant-dg instances for 'targets' + - 'root-targets' the virtual targets will be returned to dependents + - 'all-targets' all virtual + targets created while building this main target + - 'build-request' is property-set instance with + requested build properties""" + + for e in root_targets: + e.root (True) + + s = Subvariant (self, build_request, sources, + rproperties, usage_requirements, all_targets) + + for v in all_targets: + if not v.creating_subvariant(): + v.creating_subvariant(s) + + return s + + def construct (self, name, source_targets, properties): + """ Constructs the virtual targets for this abstract targets and + the dependecy graph. Returns a tuple consisting of the properties and the list of virtual targets. + Should be overrided in derived classes. + """ + raise BaseException ("method should be defined in derived classes") + + +class TypedTarget (BasicTarget): + import generators + + def __init__ (self, name, project, type, sources, requirements, default_build, usage_requirements): + BasicTarget.__init__ (self, name, project, sources, requirements, default_build, usage_requirements) + self.type_ = type + + def __jam_repr__(self): + return b2.util.value_to_jam(self) + + def type (self): + return self.type_ + + def construct (self, name, source_targets, prop_set): + + r = generators.construct (self.project_, name, self.type_, + prop_set.add_raw(['<main-target-type>' + self.type_]), + source_targets, True) + + if not r: + print "warning: Unable to construct '%s'" % self.full_name () + + # Are there any top-level generators for this type/property set. + if not generators.find_viable_generators (self.type_, prop_set): + print "error: no generators were found for type '" + self.type_ + "'" + print "error: and the requested properties" + print "error: make sure you've configured the needed tools" + print "See http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html" + + print "To debug this problem, try the --debug-generators option." + sys.exit(1) + + return r + +def apply_default_build(property_set, default_build): + # 1. First, see what properties from default_build + # are already present in property_set. + + specified_features = set(p.feature() for p in property_set.all()) + + defaults_to_apply = [] + for d in default_build.all(): + if not d.feature() in specified_features: + defaults_to_apply.append(d) + + # 2. If there's any defaults to be applied, form the new + # build request. Pass it throw 'expand-no-defaults', since + # default_build might contain "release debug", which will + # result in two property_sets. + result = [] + if defaults_to_apply: + + # We have to compress subproperties here to prevent + # property lists like: + # + # <toolset>msvc <toolset-msvc:version>7.1 <threading>multi + # + # from being expanded into: + # + # <toolset-msvc:version>7.1/<threading>multi + # <toolset>msvc/<toolset-msvc:version>7.1/<threading>multi + # + # due to cross-product property combination. That may + # be an indication that + # build_request.expand-no-defaults is the wrong rule + # to use here. + compressed = feature.compress_subproperties(property_set.all()) + + result = build_request.expand_no_defaults( + b2.build.property_set.create([p]) for p in (compressed + defaults_to_apply)) + + else: + result.append (property_set) + + return result + + +def create_typed_metatarget(name, type, sources, requirements, default_build, usage_requirements): + + from b2.manager import get_manager + t = get_manager().targets() + + project = get_manager().projects().current() + + return t.main_target_alternative( + TypedTarget(name, project, type, + t.main_target_sources(sources, name), + t.main_target_requirements(requirements, project), + t.main_target_default_build(default_build, project), + t.main_target_usage_requirements(usage_requirements, project))) + + +def create_metatarget(klass, name, sources, requirements=[], default_build=[], usage_requirements=[]): + from b2.manager import get_manager + t = get_manager().targets() + + project = get_manager().projects().current() + + return t.main_target_alternative( + klass(name, project, + t.main_target_sources(sources, name), + t.main_target_requirements(requirements, project), + t.main_target_default_build(default_build, project), + t.main_target_usage_requirements(usage_requirements, project))) + +def metatarget_function_for_class(class_): + + @bjam_signature((["name"], ["sources", "*"], ["requirements", "*"], + ["default_build", "*"], ["usage_requirements", "*"])) + def create_metatarget(name, sources, requirements = [], default_build = None, usage_requirements = []): + + from b2.manager import get_manager + t = get_manager().targets() + + project = get_manager().projects().current() + + return t.main_target_alternative( + class_(name, project, + t.main_target_sources(sources, name), + t.main_target_requirements(requirements, project), + t.main_target_default_build(default_build, project), + t.main_target_usage_requirements(usage_requirements, project))) + + return create_metatarget diff --git a/jam-files/boost-build/build/toolset.jam b/jam-files/boost-build/build/toolset.jam new file mode 100644 index 00000000..f2036d99 --- /dev/null +++ b/jam-files/boost-build/build/toolset.jam @@ -0,0 +1,502 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Support for toolset definition. + +import errors ; +import feature ; +import generators ; +import numbers ; +import path ; +import property ; +import regex ; +import sequence ; +import set ; + + +.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 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. + # + # Propery 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) ] ; + 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) + { + result += [ sequence.transform path.native : $(values) ] ; + } + else + { + result += $(values) ; + } + } + else + { + result += $(p:G=) ; + } + } + } + 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 set-target-variables ( rule-or-module targets + : property-set ) +{ + properties = [ $(property-set).raw ] ; + local key = $(rule-or-module).$(property-set) ; + local settings = $(.stv.$(key)) ; + if ! $(settings) + { + settings = [ set-target-variables-aux $(rule-or-module) : + $(property-set) ] ; + + if ! $(settings) + { + settings = none ; + } + .stv.$(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) ; + } + } + } +} + + +# 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) ; +} + + +# 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 += $(requirements) ; + } +} + + +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 ; +} diff --git a/jam-files/boost-build/build/toolset.py b/jam-files/boost-build/build/toolset.py new file mode 100644 index 00000000..b4267987 --- /dev/null +++ b/jam-files/boost-build/build/toolset.py @@ -0,0 +1,398 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +""" Support for toolset definition. +""" + +import feature, property, generators, property_set +import b2.util.set +from b2.util import cached, qualify_jam_action +from b2.util.utility import * +from b2.util import bjam_signature +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'[^.]*\.(.*)') + +# 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): + 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 varaible settings. The key is generated from the rule name and the properties. + __stv = {} + +reset () + +# FIXME: --ignore-toolset-requirements +# FIXME: using + +# 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. + + Propery 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. + """ + caller = bjam.caller()[:-1] + if not '.' in rule_or_module and caller.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) 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): + """ + """ + 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.""" + + features = set(p.feature() for p in ps.all()) + + for condition in conditions: + + found_all = True + for i in condition.all(): + + found = False + 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 i.feature() in features + + found_all = found_all and found + + if found_all: + return condition + + return None + + +def register (toolset): + """ Registers a new toolset. + """ + feature.extend('toolset', [toolset]) + +def inherit_generators (toolset, properties, base, generators_to_ignore = []): + 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.""" + 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): + pass + # FIXME: do something about this. +# base_generators = generators.generators_for_toolset (base) + +# import action + +# ids = [] +# for g in base_generators: +# (old_toolset, id) = split_action_id (g.id ()) +# ids.append (id) ; + +# new_actions = [] + +# engine = get_manager().engine() + # FIXME: do this! +# for action in engine.action.values(): +# pass +# (old_toolset, id) = split_action_id(action.action_name) +# +# if old_toolset == base: +# new_actions.append ((id, value [0], value [1])) +# +# for a in new_actions: +# action.register (toolset + '.' + a [0], a [1], a [2]) + + # TODO: how to deal with this? +# IMPORT $(base) : $(rules) : $(toolset) : $(rules) : localized ; +# # Import the rules to the global scope +# IMPORT $(toolset) : $(rules) : : $(toolset).$(rules) ; +# } +# + +###################################################################################### +# 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. + """ + 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): + 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 (ungristed) + else: + result.append (value) + + return result + +def __add_flag (rule_or_module, variable_name, condition, values): + """ Adds a new flag setting with the specified values. + Does no checking. + """ + 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(m, []).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.""" + + #if ! $(.ignore-requirements) + #{ + print "XXXX", 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): + get_manager().projects().load_module(base, []); + + inherit_generators(toolset, [], base) + inherit_flags(toolset, base) + inherit_rules(toolset, base) diff --git a/jam-files/boost-build/build/type.jam b/jam-files/boost-build/build/type.jam new file mode 100644 index 00000000..1a7a5782 --- /dev/null +++ b/jam-files/boost-build/build/type.jam @@ -0,0 +1,425 @@ +# 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_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Deals with target type declaration and defines target class which supports +# typed targets. + +import "class" : new ; +import errors ; +import feature ; +import generators : * ; +import project ; +import property ; +import scanner ; +import os ; + +# 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 *-* : errors.error "type name \"$(type)\" contains a hyphen" ; + } + + if $(type) in $(.types) + { + errors.error "Type $(type) is already registered." ; + } + else + { + .types += $(type) ; + .base.$(type) = $(base-type) ; + .derived.$(base-type) += $(type) ; + + if $(suffixes)-is-not-empty + { + # 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 overriden. + 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) + { + 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) ] + { + 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 ) +{ + local result = $(type) ; + while $(type) + { + type = [ base $(type) ] ; + result += $(type) ; + } + return $(result) ; +} + + +# 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 [ all-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) ] ; +} + + +# Store suffixes for generated targets. +.suffixes = [ new property-map ] ; + +# Store prefixes for generated targets (e.g. "lib" for library). +.prefixes = [ new property-map ] ; + + +# 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 ) +{ + properties = <target-type>$(type) $(properties) ; + $(.$(ps)es).insert $(properties) : $(psval) ; +} + + +local rule change-generated-target-ps ( ps : type : properties * : psval ) +{ + properties = <target-type>$(type) $(properties) ; + local prev = [ $(.$(ps)es).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-real ( ps : type : properties * ) +{ + local result ; + local found ; + while $(type) && ! $(found) + { + result = [ $(.$(ps)es).find <target-type>$(type) $(properties) ] ; + # 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-not-empty + { + found = true ; + } + type = $(.base.$(type)) ; + } + if $(result) = "" + { + result = ; + } + return $(result) ; +} + + +local rule generated-target-ps ( ps : type : property-set ) +{ + local key = .$(ps).$(type).$(property-set) ; + local v = $($(key)) ; + if ! $(v) + { + v = [ generated-target-ps-real $(ps) : $(type) : [ $(property-set).raw ] + ] ; + if ! $(v) + { + v = none ; + } + $(key) = $(v) ; + } + + if $(v) != none + { + return $(v) ; + } +} + + +# 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 * ) +{ + # 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. +} diff --git a/jam-files/boost-build/build/type.py b/jam-files/boost-build/build/type.py new file mode 100644 index 00000000..ddb7ba09 --- /dev/null +++ b/jam-files/boost-build/build/type.py @@ -0,0 +1,313 @@ +# 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 + + +__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) + + if __types.has_key (type): + raise BaseException ('Type "%s" is already registered.' % type) + + entry = {} + entry ['base'] = base_type + entry ['derived'] = [] + entry ['scanner'] = None + __types [type] = entry + + if base_type: + __types [base_type]['derived'].append (type) + + if len (suffixes) > 0: + # Generated targets of 'type' will use the first of 'suffixes' + # (this may be overriden) + 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): + 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. + """ + for s in suffixes: + if __suffixes_to_types.has_key (s): + 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. + """ + return __types.has_key (type) + +def validate (type): + """ Issues an error if 'type' is unknown. + """ + 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'. + """ + validate (type) + __types [type]['scanner'] = scanner + +def get_scanner (type, prop_set): + """ Returns a scanner instance appropriate to 'type' and 'property_set'. + """ + 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.""" + + return __types[type]['base'] + +def all_bases (type): + """ Returns type and all of its bases, in the order of their distance from type. + """ + 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. + """ + 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. + """ + # 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. + """ + # 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. + """ + 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. + """ + change_generated_target_ps(1, type, properties, suffix) + +def generated_target_suffix(type, properties): + return generated_target_ps(1, type, properties) + +# 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. +@bjam_signature((["type"], ["properties", "*"], ["suffix"])) +def set_generated_target_prefix(type, properties, prefix): + 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): + change_generated_target_ps(0, type, properties, prefix) + +def generated_target_prefix(type, properties): + return generated_target_ps(0, type, properties) + +def set_generated_target_ps(is_suffix, type, properties, val): + properties.append ('<target-type>' + type) + __prefixes_suffixes[is_suffix].insert (properties, val) + +def change_generated_target_ps(is_suffix, type, properties, val): + 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): + + 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. + """ + 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. + """ + while 1: + filename, suffix = os.path.splitext (filename) + if not suffix: return None + suffix = suffix[1:] + + if __suffixes_to_types.has_key(suffix): + 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. + """ + if registered (type): + return + + if not os or os_name () in os: + register (type, suffixes, base_type) diff --git a/jam-files/boost-build/build/version.jam b/jam-files/boost-build/build/version.jam new file mode 100644 index 00000000..7626ddda --- /dev/null +++ b/jam-files/boost-build/build/version.jam @@ -0,0 +1,161 @@ +# Copyright 2002, 2003, 2004, 2006 Vladimir Prus +# Copyright 2008 Jurko Gospodnetic +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +import errors ; +import numbers ; + +major = "2011" ; +minor = "04" ; + +rule boost-build ( ) +{ + return "$(major).$(minor)-svn" ; +} + +rule print ( ) +{ + if [ verify-engine-version ] + { + ECHO "Boost.Build" [ 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 Boost.Build engine and core" ; + ECHO "warning: Boost.Build engine ($(e)) is $(v:J=.)" ; + ECHO "warning: Boost.Build 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 current JAM version version is at least the given +# version. +# +rule check-jam-version ( version + ) +{ + local version-tag = $(version:J=.) ; + if ! $(version-tag) + { + errors.error Invalid version specifier: : $(version:E="(undefined)") ; + } + + if ! $(.jam-version-check.$(version-tag))-is-not-empty + { + local jam-version = [ modules.peek : JAM_VERSION ] ; + if ! $(jam-version) + { + 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. +} diff --git a/jam-files/boost-build/build/virtual-target.jam b/jam-files/boost-build/build/virtual-target.jam new file mode 100644 index 00000000..2e8446bc --- /dev/null +++ b/jam-files/boost-build/build/virtual-target.jam @@ -0,0 +1,1317 @@ +# Copyright 2003 Dave Abrahams +# Copyright 2005, 2006 Rene Rivera +# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + +# Implements virtual targets, which correspond to actual files created during a +# build, but are not yet targets in Jam sense. They are needed, for example, +# when searching for possible transformation sequences, when it is not yet known +# whether a particular target should be created at all. + +import "class" : new ; +import errors ; +import path ; +import sequence ; +import set ; +import type ; +import utility ; + + +# +--------------------------+ +# | virtual-target | +# +==========================+ +# | actualize | +# +--------------------------+ +# | actualize-action() = 0 | +# | actualize-location() = 0 | +# +----------------+---------+ +# | +# ^ +# / \ +# +-+-+ +# | +# +---------------------+ +-------+--------------+ +# | action | | abstract-file-target | +# +=====================| * +======================+ +# | action-name | +--+ action | +# | properties | | +----------------------+ +# +---------------------+--+ | actualize-action() | +# | actualize() |0..1 +-----------+----------+ +# | path() | | +# | adjust-properties() | sources | +# | actualize-sources() | targets | +# +------+--------------+ ^ +# | / \ +# ^ +-+-+ +# / \ | +# +-+-+ +-------------+-------------+ +# | | | +# | +------+---------------+ +--------+-------------+ +# | | file-target | | searched-lib-target | +# | +======================+ +======================+ +# | | actualize-location() | | actualize-location() | +# | +----------------------+ +----------------------+ +# | +# +-+------------------------------+ +# | | +# +----+----------------+ +---------+-----------+ +# | compile-action | | link-action | +# +=====================+ +=====================+ +# | adjust-properties() | | adjust-properties() | +# +---------------------+ | actualize-sources() | +# +---------------------+ +# +# The 'compile-action' and 'link-action' classes are not defined here but in +# builtin.jam modules. They are shown in the diagram to give the big picture. + + +# Models a potential target. It can be converted into a Jam target and used in +# building, if needed. However, it can be also dropped, which allows us to +# search for different transformations and select only one. +# +class virtual-target +{ + import scanner ; + import sequence ; + import utility ; + import virtual-target ; + + rule __init__ ( + name # Target/project name. + : project # Project to which this target belongs. + ) + { + self.name = $(name) ; + self.project = $(project) ; + self.dependencies = ; + } + + # Name of this target. + # + rule name ( ) + { + return $(self.name) ; + } + + # Project of this target. + # + rule project ( ) + { + return $(self.project) ; + } + + # Adds additional 'virtual-target' instances this one depends on. + # + rule depends ( d + ) + { + self.dependencies = [ sequence.merge $(self.dependencies) : + [ sequence.insertion-sort $(d) ] ] ; + } + + rule dependencies ( ) + { + return $(self.dependencies) ; + } + + rule always ( ) + { + .always = 1 ; + } + + # Generates all the actual targets and sets up build actions for this + # target. + # + # If 'scanner' is specified, creates an additional target with the same + # location as the actual target, which will depend on the actual target and + # be associated with a 'scanner'. That additional target is returned. See + # the docs (#dependency_scanning) for rationale. Target must correspond to a + # file if 'scanner' is specified. + # + # If scanner is not specified then the actual target is returned. + # + rule actualize ( scanner ? ) + { + local actual-name = [ actualize-no-scanner ] ; + + if $(.always) + { + ALWAYS $(actual-name) ; + } + + if ! $(scanner) + { + return $(actual-name) ; + } + else + { + # Add the scanner instance to the grist for name. + local g = [ sequence.join + [ utility.ungrist $(actual-name:G) ] $(scanner) : - ] ; + local name = $(actual-name:G=$(g)) ; + + if ! $(self.made.$(name)) + { + self.made.$(name) = true ; + + DEPENDS $(name) : $(actual-name) ; + + actualize-location $(name) ; + + scanner.install $(scanner) : $(name) $(__name__) ; + } + return $(name) ; + } + } + +# private: (overridables) + + # Sets up build actions for 'target'. Should call appropriate rules and set + # target variables. + # + rule actualize-action ( target ) + { + errors.error "method should be defined in derived classes" ; + } + + # Sets up variables on 'target' which specify its location. + # + rule actualize-location ( target ) + { + errors.error "method should be defined in derived classes" ; + } + + # If the target is a generated one, returns the path where it will be + # generated. Otherwise, returns an empty list. + # + rule path ( ) + { + errors.error "method should be defined in derived classes" ; + } + + # Returns the actual target name to be used in case when no scanner is + # involved. + # + rule actual-name ( ) + { + errors.error "method should be defined in derived classes" ; + } + +# implementation + rule actualize-no-scanner ( ) + { + # In fact, we just need to merge virtual-target with + # abstract-file-target as the latter is the only class derived from the + # former. But that has been left for later. + + errors.error "method should be defined in derived classes" ; + } +} + + +# Target corresponding to a file. The exact mapping for file is not yet +# specified in this class. (TODO: Actually, the class name could be better...) +# +# May be a source file (when no action is specified) or a derived file +# (otherwise). +# +# The target's grist is a concatenation of its project's location, action +# properties (for derived targets) and, optionally, value identifying the main +# target. +# +class abstract-file-target : virtual-target +{ + import project ; + import regex ; + import sequence ; + import path ; + import type ; + import property-set ; + import indirect ; + + rule __init__ ( + name # Target's name. + exact ? # If non-empty, the name is exactly the name created file + # should have. Otherwise, the '__init__' method will add a + # suffix obtained from 'type' by calling + # 'type.generated-target-suffix'. + : type ? # Target's type. + : project + : action ? + ) + { + virtual-target.__init__ $(name) : $(project) ; + + self.type = $(type) ; + self.action = $(action) ; + if $(action) + { + $(action).add-targets $(__name__) ; + + if $(self.type) && ! $(exact) + { + _adjust-name $(name) ; + } + } + } + + rule type ( ) + { + return $(self.type) ; + } + + # Sets the path. When generating target name, it will override any path + # computation from properties. + # + rule set-path ( path ) + { + self.path = [ path.native $(path) ] ; + } + + # Returns the currently set action. + # + rule action ( ) + { + return $(self.action) ; + } + + # Sets/gets the 'root' flag. Target is root if it directly corresponds to + # some variant of a main target. + # + rule root ( set ? ) + { + if $(set) + { + self.root = true ; + } + return $(self.root) ; + } + + # Gets or sets the subvariant which created this target. Subvariant is set + # when target is brought into existance and is never changed after that. In + # particular, if a target is shared by a subvariant, only the first is + # stored. + # + rule creating-subvariant ( s ? # If specified, specifies the value to set, + # which should be a 'subvariant' class + # instance. + ) + { + if $(s) && ! $(self.creating-subvariant) + { + self.creating-subvariant = $(s) ; + } + return $(self.creating-subvariant) ; + } + + rule actualize-action ( target ) + { + if $(self.action) + { + $(self.action).actualize ; + } + } + + # Return a human-readable representation of this target. If this target has + # an action, that is: + # + # { <action-name>-<self.name>.<self.type> <action-sources>... } + # + # otherwise, it is: + # + # { <self.name>.<self.type> } + # + rule str ( ) + { + local action = [ action ] ; + local name-dot-type = [ sequence.join $(self.name) "." $(self.type) ] ; + + if $(action) + { + local sources = [ $(action).sources ] ; + local action-name = [ $(action).action-name ] ; + + local ss ; + for local s in $(sources) + { + ss += [ $(s).str ] ; + } + + return "{" $(action-name)-$(name-dot-type) $(ss) "}" ; + } + else + { + return "{" $(name-dot-type) "}" ; + } + } + + rule less ( a ) + { + if [ str ] < [ $(a).str ] + { + return true ; + } + } + + rule equal ( a ) + { + if [ str ] = [ $(a).str ] + { + return true ; + } + } + +# private: + rule actual-name ( ) + { + if ! $(self.actual-name) + { + local grist = [ grist ] ; + local basename = [ path.native $(self.name) ] ; + self.actual-name = <$(grist)>$(basename) ; + } + return $(self.actual-name) ; + } + + # Helper to 'actual-name', above. Computes a unique prefix used to + # distinguish this target from other targets with the same name creating + # different files. + # + rule grist ( ) + { + # Depending on target, there may be different approaches to generating + # unique prefixes. We generate prefixes in the form: + # <one letter approach code> <the actual prefix> + local path = [ path ] ; + if $(path) + { + # The target will be generated to a known path. Just use the path + # for identification, since path is as unique as it can get. + return p$(path) ; + } + else + { + # File is either source, which will be searched for, or is not a + # file at all. Use the location of project for distinguishing. + local project-location = [ $(self.project).get location ] ; + local location-grist = [ sequence.join [ regex.split + $(project-location) "/" ] : "!" ] ; + + if $(self.action) + { + local ps = [ $(self.action).properties ] ; + local property-grist = [ $(ps).as-path ] ; + # 'property-grist' can be empty when 'ps' is an empty property + # set. + if $(property-grist) + { + location-grist = $(location-grist)/$(property-grist) ; + } + } + + return l$(location-grist) ; + } + } + + # Given the target name specified in constructor, returns the name which + # should be really used, by looking at the <tag> properties. Tag properties + # need to be specified as <tag>@rule-name. This makes Boost Build call the + # specified rule with the target name, type and properties to get the new + # name. If no <tag> property is specified or the rule specified by <tag> + # returns nothing, returns the result of calling + # virtual-target.add-prefix-and-suffix. + # + rule _adjust-name ( specified-name ) + { + local ps ; + if $(self.action) + { + ps = [ $(self.action).properties ] ; + } + else + { + ps = [ property-set.empty ] ; + } + + # We add ourselves to the properties so that any tag rule can get more + # direct information about the target than just that available through + # the properties. This is useful in implementing name changes based on + # the sources of the target. For example to make unique names of object + # files based on the source file. --grafik + ps = [ property-set.create [ $(ps).raw ] <target>$(__name__) ] ; + + local tag = [ $(ps).get <tag> ] ; + + if $(tag) + { + local rule-name = [ MATCH ^@(.*) : $(tag) ] ; + if $(rule-name) + { + if $(tag[2]) + { + errors.error "<tag>@rulename is present but is not the only" + "<tag> feature" ; + } + + self.name = [ indirect.call $(rule-name) $(specified-name) + : $(self.type) : $(ps) ] ; + } + else + { + errors.error + "The value of the <tag> feature must be '@rule-name'" ; + } + } + + # If there is no tag or the tag rule returned nothing. + if ! $(tag) || ! $(self.name) + { + self.name = [ virtual-target.add-prefix-and-suffix $(specified-name) + : $(self.type) : $(ps) ] ; + } + } + + rule actualize-no-scanner ( ) + { + local name = [ actual-name ] ; + + # Do anything only on the first invocation. + if ! $(self.made.$(name)) + { + self.made.$(name) = true ; + + if $(self.action) + { + # For non-derived target, we do not care if there are several + # virtual targets that refer to the same name. One case when + # this is unavoidable is when the file name is main.cpp and two + # targets have types CPP (for compiling) and MOCCABLE_CPP (for + # conversion to H via Qt tools). + virtual-target.register-actual-name $(name) : $(__name__) ; + } + + for local i in $(self.dependencies) + { + DEPENDS $(name) : [ $(i).actualize ] ; + } + + actualize-location $(name) ; + actualize-action $(name) ; + } + return $(name) ; + } +} + + +# Appends the suffix appropriate to 'type/property-set' combination to the +# specified name and returns the result. +# +rule add-prefix-and-suffix ( specified-name : type ? : property-set ) +{ + local suffix = [ type.generated-target-suffix $(type) : $(property-set) ] ; + + # Handle suffixes for which no leading dot is desired. Those are specified + # by enclosing them in <...>. Needed by python so it can create "_d.so" + # extensions, for example. + if $(suffix:G) + { + suffix = [ utility.ungrist $(suffix) ] ; + } + else + { + suffix = .$(suffix) ; + } + + local prefix = [ type.generated-target-prefix $(type) : $(property-set) ] ; + + if [ MATCH ^($(prefix)) : $(specified-name) ] + { + prefix = ; + } + return $(prefix:E="")$(specified-name)$(suffix:E="") ; +} + + +# File targets with explicitly known location. +# +# The file path is determined as +# * Value passed to the 'set-path' method, if any. +# * For derived files, project's build dir, joined with components that +# describe action properties. If free properties are not equal to the +# project's reference properties an element with the name of the main +# target is added. +# * For source files, project's source dir. +# +# The file suffix is determined as: +# * The value passed to the 'suffix' method, if any. +# * The suffix corresponding to the target's type. +# +class file-target : abstract-file-target +{ + import "class" : new ; + import common ; + import errors ; + + rule __init__ ( + name exact ? + : type ? # Optional type for this target. + : project + : action ? + : path ? + ) + { + abstract-file-target.__init__ $(name) $(exact) : $(type) : $(project) : + $(action) ; + + self.path = $(path) ; + } + + rule clone-with-different-type ( new-type ) + { + return [ new file-target $(self.name) exact : $(new-type) : + $(self.project) : $(self.action) : $(self.path) ] ; + } + + rule actualize-location ( target ) + { + if $(self.action) + { + # This is a derived file. + local path = [ path ] ; + LOCATE on $(target) = $(path) ; + + # Make sure the path exists. + DEPENDS $(target) : $(path) ; + common.MkDir $(path) ; + + # It is possible that the target name includes a directory too, for + # example when installing headers. Create that directory. + if $(target:D) + { + local d = $(target:D) ; + d = $(d:R=$(path)) ; + DEPENDS $(target) : $(d) ; + common.MkDir $(d) ; + } + + # For a real file target, we create a fake target depending on the + # real target. This allows us to run + # + # bjam hello.o + # + # without trying to guess the name of the real target. Note that the + # target has no directory name and uses a special <e> grist. + # + # First, that means that "bjam hello.o" will build all known hello.o + # targets. Second, the <e> grist makes sure this target will not be + # confused with other targets, for example, if we have subdir 'test' + # with target 'test' in it that includes a 'test.o' file, then the + # target for directory will be just 'test' the target for test.o + # will be <ptest/bin/gcc/debug>test.o and the target we create below + # will be <e>test.o + DEPENDS $(target:G=e) : $(target) ; + # Allow bjam <path-to-file>/<file> to work. This will not catch all + # possible ways to refer to the path (relative/absolute, extra ".", + # various "..", but should help in obvious cases. + DEPENDS $(target:G=e:R=$(path)) : $(target) ; + } + else + { + SEARCH on $(target) = [ path.native $(self.path) ] ; + } + } + + # Returns the directory for this target. + # + rule path ( ) + { + if ! $(self.path) + { + if $(self.action) + { + local p = [ $(self.action).properties ] ; + local path,relative-to-build-dir = [ $(p).target-path ] ; + local path = $(path,relative-to-build-dir[1]) ; + local relative-to-build-dir = $(path,relative-to-build-dir[2]) ; + + if $(relative-to-build-dir) + { + path = [ path.join [ $(self.project).build-dir ] $(path) ] ; + } + + self.path = [ path.native $(path) ] ; + } + } + return $(self.path) ; + } +} + + +class notfile-target : abstract-file-target +{ + rule __init__ ( name : project : action ? ) + { + abstract-file-target.__init__ $(name) : : $(project) : $(action) ; + } + + # Returns nothing to indicate that the target's path is not known. + # + rule path ( ) + { + return ; + } + + rule actualize-location ( target ) + { + NOTFILE $(target) ; + ALWAYS $(target) ; + # TEMPORARY $(target) ; + NOUPDATE $(target) ; + } +} + + +# Class representing an action. Both 'targets' and 'sources' should list +# instances of 'virtual-target'. Action name should name a rule with this +# prototype: +# rule action-name ( targets + : sources * : properties * ) +# Targets and sources are passed as actual Jam targets. The rule may not +# establish additional dependency relationships. +# +class action +{ + import "class" ; + import errors ; + import type ; + import toolset ; + import property-set ; + import indirect ; + import path ; + import set : difference ; + + rule __init__ ( sources * : action-name + : property-set ? ) + { + self.sources = $(sources) ; + + self.action-name = [ indirect.make-qualified $(action-name) ] ; + + if ! $(property-set) + { + property-set = [ property-set.empty ] ; + } + + if ! [ class.is-instance $(property-set) ] + { + errors.error "Property set instance required" ; + } + + self.properties = $(property-set) ; + } + + rule add-targets ( targets * ) + { + self.targets += $(targets) ; + } + + rule replace-targets ( old-targets * : new-targets * ) + { + self.targets = [ set.difference $(self.targets) : $(old-targets) ] ; + self.targets += $(new-targets) ; + } + + rule targets ( ) + { + return $(self.targets) ; + } + + rule sources ( ) + { + return $(self.sources) ; + } + + rule action-name ( ) + { + return $(self.action-name) ; + } + + rule properties ( ) + { + return $(self.properties) ; + } + + # Generates actual build instructions. + # + rule actualize ( ) + { + if ! $(self.actualized) + { + self.actualized = true ; + + local ps = [ properties ] ; + local properties = [ adjust-properties $(ps) ] ; + + local actual-targets ; + for local i in [ targets ] + { + actual-targets += [ $(i).actualize ] ; + } + + actualize-sources [ sources ] : $(properties) ; + + DEPENDS $(actual-targets) : $(self.actual-sources) + $(self.dependency-only-sources) ; + + # This works around a bug with -j and actions that + # produce multiple target, where: + # - dependency on the first output is found, and + # the action is started + # - dependency on the second output is found, and + # bjam noticed that command is already running + # - instead of waiting for the command, dependents + # of the second targets are immediately updated. + if $(actual-targets[2]) + { + INCLUDES $(actual-targets) : $(actual-targets) ; + } + + # Action name can include additional argument to rule, which should + # not be passed to 'set-target-variables' + toolset.set-target-variables + [ indirect.get-rule $(self.action-name[1]) ] $(actual-targets) + : $(properties) ; + + # Reflect ourselves in a variable for the target. This allows + # looking up additional info for the action given the raw target. + # For example to debug or output action information from action + # rules. + .action on $(actual-targets) = $(__name__) ; + + indirect.call $(self.action-name) $(actual-targets) + : $(self.actual-sources) : [ $(properties).raw ] ; + + # Since we set up the creating action here, we set up the action for + # cleaning up as well. + common.Clean clean-all : $(actual-targets) ; + } + } + + # Helper for 'actualize-sources'. For each passed source, actualizes it with + # the appropriate scanner. Returns the actualized virtual targets. + # + rule actualize-source-type ( sources * : property-set ) + { + local result = ; + for local i in $(sources) + { + local scanner ; + if [ $(i).type ] + { + scanner = [ type.get-scanner [ $(i).type ] : $(property-set) ] ; + } + result += [ $(i).actualize $(scanner) ] ; + } + return $(result) ; + } + + # Creates actual Jam targets for sources. Initializes the following member + # variables: + # 'self.actual-sources' -- sources passed to the updating action. + # 'self.dependency-only-sources' -- sources marked as dependencies, but + # are not used otherwise. + # + # New values will be *appended* to the variables. They may be non-empty if + # caller wants it. + # + rule actualize-sources ( sources * : property-set ) + { + local dependencies = [ $(self.properties).get <dependency> ] ; + + self.dependency-only-sources += + [ actualize-source-type $(dependencies) : $(property-set) ] ; + self.actual-sources += + [ actualize-source-type $(sources) : $(property-set) ] ; + + # This is used to help bjam find dependencies in generated headers and + # other main targets, e.g. in: + # + # make a.h : ....... ; + # exe hello : hello.cpp : <implicit-dependency>a.h ; + # + # For bjam to find the dependency the generated target must be + # actualized (i.e. have its Jam target constructed). In the above case, + # if we are building just hello ("bjam hello"), 'a.h' will not be + # actualized unless we do it here. + local implicit = [ $(self.properties).get <implicit-dependency> ] ; + for local i in $(implicit) + { + $(i:G=).actualize ; + } + } + + # Determines real properties when trying to build with 'properties'. This is + # the last chance to fix properties, for example to adjust includes to get + # generated headers correctly. Default implementation simply returns its + # argument. + # + rule adjust-properties ( property-set ) + { + return $(property-set) ; + } +} + + +# Action class which does nothing --- it produces the targets with specific +# properties out of nowhere. It is needed to distinguish virtual targets with +# different properties that are known to exist and have no actions which create +# them. +# +class null-action : action +{ + rule __init__ ( property-set ? ) + { + action.__init__ : .no-action : $(property-set) ; + } + + rule actualize ( ) + { + if ! $(self.actualized) + { + self.actualized = true ; + for local i in [ targets ] + { + $(i).actualize ; + } + } + } +} + + +# Class which acts exactly like 'action', except that its sources are not +# scanned for dependencies. +# +class non-scanning-action : action +{ + rule __init__ ( sources * : action-name + : property-set ? ) + { + action.__init__ $(sources) : $(action-name) : $(property-set) ; + } + + rule actualize-source-type ( sources * : property-set ) + { + local result ; + for local i in $(sources) + { + result += [ $(i).actualize ] ; + } + return $(result) ; + } +} + + +# Creates a virtual target with an appropriate name and type from 'file'. If a +# target with that name in that project already exists, returns that already +# created target. +# +# FIXME: a more correct way would be to compute the path to the file, based on +# name and source location for the project, and use that path to determine if +# the target has already been created. This logic should be shared with how we +# usually find targets identified by a specific target id. It should also be +# updated to work correctly when the file is specified using both relative and +# absolute paths. +# +# TODO: passing a project with all virtual targets is starting to be annoying. +# +rule from-file ( file : file-loc : project ) +{ + import type ; # Had to do this here to break a circular dependency. + + # Check whether we already created a target corresponding to this file. + local path = [ path.root [ path.root $(file) $(file-loc) ] [ path.pwd ] ] ; + + if $(.files.$(path)) + { + return $(.files.$(path)) ; + } + else + { + local name = [ path.make $(file) ] ; + local type = [ type.type $(file) ] ; + local result ; + + result = [ new file-target $(file) : $(type) : $(project) : : + $(file-loc) ] ; + + .files.$(path) = $(result) ; + return $(result) ; + } +} + + +# Registers a new virtual target. Checks if there is already a registered target +# with the same name, type, project and subvariant properties as well as the +# same sources and equal action. If such target is found it is returned and a +# new 'target' is not registered. Otherwise, 'target' is registered and +# returned. +# +rule register ( target ) +{ + local signature = [ sequence.join + [ $(target).path ] [ $(target).name ] : - ] ; + + local result ; + for local t in $(.cache.$(signature)) + { + local a1 = [ $(t).action ] ; + local a2 = [ $(target).action ] ; + + if ! $(result) + { + if ! $(a1) && ! $(a2) + { + result = $(t) ; + } + else + { + if $(a1) && $(a2) && + ( [ $(a1).action-name ] = [ $(a2).action-name ] ) && + ( [ $(a1).sources ] = [ $(a2).sources ] ) + { + local ps1 = [ $(a1).properties ] ; + local ps2 = [ $(a2).properties ] ; + local p1 = [ $(ps1).base ] [ $(ps1).free ] [ set.difference + [ $(ps1).dependency ] : [ $(ps1).incidental ] ] ; + local p2 = [ $(ps2).base ] [ $(ps2).free ] [ set.difference + [ $(ps2).dependency ] : [ $(ps2).incidental ] ] ; + if $(p1) = $(p2) + { + result = $(t) ; + } + } + } + } + } + + if ! $(result) + { + .cache.$(signature) += $(target) ; + result = $(target) ; + } + + .recent-targets += $(result) ; + .all-targets += $(result) ; + + return $(result) ; +} + + +# Each target returned by 'register' is added to the .recent-targets list, +# returned by this function. This allows us to find all virtual targets created +# when building a specific main target, even those constructed only as +# intermediate targets. +# +rule recent-targets ( ) +{ + return $(.recent-targets) ; +} + + +rule clear-recent-targets ( ) +{ + .recent-targets = ; +} + + +# Returns all virtual targets ever created. +# +rule all-targets ( ) +{ + return $(.all-targets) ; +} + + +# Returns all targets from 'targets' with types equal to 'type' or derived from +# it. +# +rule select-by-type ( type : targets * ) +{ + local result ; + for local t in $(targets) + { + if [ type.is-subtype [ $(t).type ] $(type) ] + { + result += $(t) ; + } + } + return $(result) ; +} + + +rule register-actual-name ( actual-name : virtual-target ) +{ + if $(.actual.$(actual-name)) + { + local cs1 = [ $(.actual.$(actual-name)).creating-subvariant ] ; + local cs2 = [ $(virtual-target).creating-subvariant ] ; + local cmt1 = [ $(cs1).main-target ] ; + local cmt2 = [ $(cs2).main-target ] ; + + local action1 = [ $(.actual.$(actual-name)).action ] ; + local action2 = [ $(virtual-target).action ] ; + local properties-added ; + local properties-removed ; + if $(action1) && $(action2) + { + local p1 = [ $(action1).properties ] ; + p1 = [ $(p1).raw ] ; + local p2 = [ $(action2).properties ] ; + p2 = [ $(p2).raw ] ; + properties-removed = [ set.difference $(p1) : $(p2) ] ; + properties-removed ?= "none" ; + properties-added = [ set.difference $(p2) : $(p1) ] ; + properties-added ?= "none" ; + } + errors.error "Duplicate name of actual target:" $(actual-name) + : "previous virtual target" [ $(.actual.$(actual-name)).str ] + : "created from" [ $(cmt1).full-name ] + : "another virtual target" [ $(virtual-target).str ] + : "created from" [ $(cmt2).full-name ] + : "added properties:" $(properties-added) + : "removed properties:" $(properties-removed) ; + } + else + { + .actual.$(actual-name) = $(virtual-target) ; + } +} + + +# Traverses the dependency graph of 'target' and return all targets that will be +# created before this one is created. If the root of some dependency graph is +# found during traversal, it is either included or not, depending on the +# 'include-roots' value. In either case traversal stops at root targets, i.e. +# root target sources are not traversed. +# +rule traverse ( target : include-roots ? : include-sources ? ) +{ + local result ; + if [ $(target).action ] + { + local action = [ $(target).action ] ; + # This includes the 'target' as well. + result += [ $(action).targets ] ; + + for local t in [ $(action).sources ] + { + if ! [ $(t).root ] + { + result += [ traverse $(t) : $(include-roots) : $(include-sources) ] ; + } + else if $(include-roots) + { + result += $(t) ; + } + } + } + else if $(include-sources) + { + result = $(target) ; + } + return $(result) ; +} + + +# Takes an 'action' instance and creates a new instance of it and all targets +# produced by the action. The rule-name and properties are set to +# 'new-rule-name' and 'new-properties', if those are specified. Returns the +# cloned action. +# +rule clone-action ( action : new-project : new-action-name ? : new-properties ? ) +{ + if ! $(new-action-name) + { + new-action-name = [ $(action).action-name ] ; + } + if ! $(new-properties) + { + new-properties = [ $(action).properties ] ; + } + + local action-class = [ modules.peek $(action) : __class__ ] ; + local cloned-action = [ class.new $(action-class) + [ $(action).sources ] : $(new-action-name) : $(new-properties) ] ; + + local cloned-targets ; + for local target in [ $(action).targets ] + { + local n = [ $(target).name ] ; + # Do not modify produced target names. + local cloned-target = [ class.new file-target $(n) exact : + [ $(target).type ] : $(new-project) : $(cloned-action) ] ; + local d = [ $(target).dependencies ] ; + if $(d) + { + $(cloned-target).depends $(d) ; + } + $(cloned-target).root [ $(target).root ] ; + $(cloned-target).creating-subvariant [ $(target).creating-subvariant ] ; + + cloned-targets += $(cloned-target) ; + } + + return $(cloned-action) ; +} + + +class subvariant +{ + import sequence ; + import type ; + + rule __init__ ( main-target # The instance of main-target class. + : property-set # Properties requested for this target. + : sources * + : build-properties # Actually used properties. + : sources-usage-requirements # Properties propagated from sources. + : created-targets * ) # Top-level created targets. + { + self.main-target = $(main-target) ; + self.properties = $(property-set) ; + self.sources = $(sources) ; + self.build-properties = $(build-properties) ; + self.sources-usage-requirements = $(sources-usage-requirements) ; + self.created-targets = $(created-targets) ; + + # Pre-compose a list of other dependency graphs this one depends on. + local deps = [ $(build-properties).get <implicit-dependency> ] ; + for local d in $(deps) + { + self.other-dg += [ $(d:G=).creating-subvariant ] ; + } + + self.other-dg = [ sequence.unique $(self.other-dg) ] ; + } + + rule main-target ( ) + { + return $(self.main-target) ; + } + + rule created-targets ( ) + { + return $(self.created-targets) ; + } + + rule requested-properties ( ) + { + return $(self.properties) ; + } + + rule build-properties ( ) + { + return $(self.build-properties) ; + } + + rule sources-usage-requirements ( ) + { + return $(self.sources-usage-requirements) ; + } + + rule set-usage-requirements ( usage-requirements ) + { + self.usage-requirements = $(usage-requirements) ; + } + + rule usage-requirements ( ) + { + return $(self.usage-requirements) ; + } + + # Returns all targets referenced by this subvariant, either directly or + # indirectly, and either as sources, or as dependency properties. Targets + # referred to using the dependency property are returned as properties, not + # targets. + # + rule all-referenced-targets ( theset ) + { + # Find directly referenced targets. + local deps = [ $(self.build-properties).dependency ] ; + local all-targets = $(self.sources) $(deps) ; + + # Find other subvariants. + local r ; + for local t in $(all-targets) + { + if ! [ $(theset).contains $(t) ] + { + $(theset).add $(t) ; + r += [ $(t:G=).creating-subvariant ] ; + } + } + r = [ sequence.unique $(r) ] ; + for local s in $(r) + { + if $(s) != $(__name__) + { + $(s).all-referenced-targets $(theset) ; + } + } + } + + # Returns the properties specifying implicit include paths to generated + # headers. This traverses all targets in this subvariant and subvariants + # referred by <implcit-dependecy> properties. For all targets of type + # 'target-type' (or for all targets, if 'target-type' is not specified), the + # result will contain <$(feature)>path-to-that-target. + # + rule implicit-includes ( feature : target-type ? ) + { + local key = ii$(feature)-$(target-type:E="") ; + if ! $($(key))-is-not-empty + { + local target-paths = [ all-target-directories $(target-type) ] ; + target-paths = [ sequence.unique $(target-paths) ] ; + local result = $(target-paths:G=$(feature)) ; + if ! $(result) + { + result = "" ; + } + $(key) = $(result) ; + } + if $($(key)) = "" + { + return ; + } + else + { + return $($(key)) ; + } + } + + rule all-target-directories ( target-type ? ) + { + if ! $(self.target-directories) + { + compute-target-directories $(target-type) ; + } + return $(self.target-directories) ; + } + + rule compute-target-directories ( target-type ? ) + { + local result ; + for local t in $(self.created-targets) + { + # Skip targets of the wrong type. + if ! $(target-type) || + [ type.is-derived [ $(t).type ] $(target-type) ] + { + result = [ sequence.merge $(result) : [ $(t).path ] ] ; + } + } + for local d in $(self.other-dg) + { + result += [ $(d).all-target-directories $(target-type) ] ; + } + self.target-directories = $(result) ; + } +} diff --git a/jam-files/boost-build/build/virtual_target.py b/jam-files/boost-build/build/virtual_target.py new file mode 100644 index 00000000..51dff037 --- /dev/null +++ b/jam-files/boost-build/build/virtual_target.py @@ -0,0 +1,1118 @@ +# Status: ported. +# Base revision: 64488. +# +# 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. + +# Implements virtual targets, which correspond to actual files created during +# build, but are not yet targets in Jam sense. They are needed, for example, +# when searching for possible transormation sequences, when it's not known +# if particular target should be created at all. +# +# +# +--------------------------+ +# | VirtualTarget | +# +==========================+ +# | actualize | +# +--------------------------+ +# | actualize_action() = 0 | +# | actualize_location() = 0 | +# +----------------+---------+ +# | +# ^ +# / \ +# +-+-+ +# | +# +---------------------+ +-------+--------------+ +# | Action | | AbstractFileTarget | +# +=====================| * +======================+ +# | action_name | +--+ action | +# | properties | | +----------------------+ +# +---------------------+--+ | actualize_action() | +# | actualize() |0..1 +-----------+----------+ +# | path() | | +# | adjust_properties() | sources | +# | actualize_sources() | targets | +# +------+--------------+ ^ +# | / \ +# ^ +-+-+ +# / \ | +# +-+-+ +-------------+-------------+ +# | | | +# | +------+---------------+ +--------+-------------+ +# | | FileTarget | | SearchedLibTarget | +# | +======================+ +======================+ +# | | actualize-location() | | actualize-location() | +# | +----------------------+ +----------------------+ +# | +# +-+------------------------------+ +# | | +# +----+----------------+ +---------+-----------+ +# | CompileAction | | LinkAction | +# +=====================+ +=====================+ +# | adjust_properties() | | adjust_properties() | +# +---------------------+ | actualize_sources() | +# +---------------------+ +# +# The 'CompileAction' and 'LinkAction' classes are defined not here, +# but in builtin.jam modules. They are shown in the diagram to give +# the big picture. + +import bjam + +import re +import os.path +import string +import types + +from b2.util import path, utility, set +from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, get_value +from b2.util.sequence import unique +from b2.tools import common +from b2.exceptions import * +import b2.build.type +import b2.build.property_set as property_set + +import b2.build.property as property + +from b2.manager import get_manager +from b2.util import bjam_signature + +__re_starts_with_at = re.compile ('^@(.*)') + +class VirtualTargetRegistry: + def __init__ (self, manager): + self.manager_ = manager + + # A cache for FileTargets + self.files_ = {} + + # A cache for targets. + self.cache_ = {} + + # A map of actual names to virtual targets. + # Used to make sure we don't associate same + # actual target to two virtual targets. + self.actual_ = {} + + self.recent_targets_ = [] + + # All targets ever registed + self.all_targets_ = [] + + self.next_id_ = 0 + + def register (self, target): + """ Registers a new virtual target. Checks if there's already registered target, with the same + name, type, project and subvariant properties, and also with the same sources + and equal action. If such target is found it is retured and 'target' is not registered. + Otherwise, 'target' is registered and returned. + """ + if target.path(): + signature = target.path() + "-" + target.name() + else: + signature = "-" + target.name() + + result = None + if not self.cache_.has_key (signature): + self.cache_ [signature] = [] + + for t in self.cache_ [signature]: + a1 = t.action () + a2 = target.action () + + # TODO: why are we checking for not result? + if not result: + if not a1 and not a2: + result = t + else: + if a1 and a2 and a1.action_name () == a2.action_name () and a1.sources () == a2.sources (): + ps1 = a1.properties () + ps2 = a2.properties () + p1 = ps1.base () + ps1.free () +\ + b2.util.set.difference(ps1.dependency(), ps1.incidental()) + p2 = ps2.base () + ps2.free () +\ + b2.util.set.difference(ps2.dependency(), ps2.incidental()) + if p1 == p2: + result = t + + if not result: + self.cache_ [signature].append (target) + result = target + + # TODO: Don't append if we found pre-existing target? + self.recent_targets_.append(result) + self.all_targets_.append(result) + + return result + + def from_file (self, file, file_location, project): + """ Creates a virtual target with appropriate name and type from 'file'. + If a target with that name in that project was already created, returns that already + created target. + TODO: more correct way would be to compute path to the file, based on name and source location + for the project, and use that path to determine if the target was already created. + TODO: passing project with all virtual targets starts to be annoying. + """ + # Check if we've created a target corresponding to this file. + path = os.path.join(os.getcwd(), file_location, file) + path = os.path.normpath(path) + + if self.files_.has_key (path): + return self.files_ [path] + + file_type = b2.build.type.type (file) + + result = FileTarget (file, file_type, project, + None, file_location) + self.files_ [path] = result + + return result + + def recent_targets(self): + """Each target returned by 'register' is added to a list of + 'recent-target', returned by this function. So, this allows + us to find all targets created when building a given main + target, even if the target.""" + + return self.recent_targets_ + + def clear_recent_targets(self): + self.recent_targets_ = [] + + def all_targets(self): + # Returns all virtual targets ever created + return self.all_targets_ + + # Returns all targets from 'targets' with types + # equal to 'type' or derived from it. + def select_by_type(self, type, targets): + return [t for t in targets if b2.build.type.is_sybtype(t.type(), type)] + + def register_actual_name (self, actual_name, virtual_target): + if self.actual_.has_key (actual_name): + cs1 = self.actual_ [actual_name].creating_subvariant () + cs2 = virtual_target.creating_subvariant () + cmt1 = cs1.main_target () + cmt2 = cs2.main_target () + + action1 = self.actual_ [actual_name].action () + action2 = virtual_target.action () + + properties_added = [] + properties_removed = [] + if action1 and action2: + p1 = action1.properties () + p1 = p1.raw () + p2 = action2.properties () + p2 = p2.raw () + + properties_removed = set.difference (p1, p2) + if not properties_removed: properties_removed = "none" + + properties_added = set.difference (p2, p1) + if not properties_added: properties_added = "none" + + # FIXME: Revive printing of real location. + get_manager().errors()( + "Duplicate name of actual target: '%s'\n" + "previous virtual target '%s'\n" + "created from '%s'\n" + "another virtual target '%s'\n" + "created from '%s'\n" + "added properties: '%s'\n" + "removed properties: '%s'\n" + % (actual_name, + self.actual_ [actual_name], "loc", #cmt1.location (), + virtual_target, + "loc", #cmt2.location (), + properties_added, properties_removed)) + + else: + self.actual_ [actual_name] = virtual_target + + + def add_suffix (self, specified_name, file_type, prop_set): + """ Appends the suffix appropriate to 'type/property_set' combination + to the specified name and returns the result. + """ + suffix = b2.build.type.generated_target_suffix (file_type, prop_set) + + if suffix: + return specified_name + '.' + suffix + + else: + return specified_name + +class VirtualTarget: + """ Potential target. It can be converted into jam target and used in + building, if needed. However, it can be also dropped, which allows + to search for different transformation and select only one. + name: name of this target. + project: project to which this target belongs. + """ + def __init__ (self, name, project): + self.name_ = name + self.project_ = project + self.dependencies_ = [] + self.always_ = False + + # Caches if dapendencies for scanners have already been set. + self.made_ = {} + + def manager(self): + return self.project_.manager() + + def virtual_targets(self): + return self.manager().virtual_targets() + + def name (self): + """ Name of this target. + """ + return self.name_ + + def project (self): + """ Project of this target. + """ + return self.project_ + + def depends (self, d): + """ Adds additional instances of 'VirtualTarget' that this + one depends on. + """ + self.dependencies_ = unique (self.dependencies_ + d).sort () + + def dependencies (self): + return self.dependencies_ + + def always(self): + self.always_ = True + + def actualize (self, scanner = None): + """ Generates all the actual targets and sets up build actions for + this target. + + If 'scanner' is specified, creates an additional target + with the same location as actual target, which will depend on the + actual target and be associated with 'scanner'. That additional + target is returned. See the docs (#dependency_scanning) for rationale. + Target must correspond to a file if 'scanner' is specified. + + If scanner is not specified, then actual target is returned. + """ + actual_name = self.actualize_no_scanner () + + if self.always_: + bjam.call("ALWAYS", actual_name) + + if not scanner: + return actual_name + + else: + # Add the scanner instance to the grist for name. + g = '-'.join ([ungrist(get_grist(actual_name)), str(id(scanner))]) + + name = replace_grist (actual_name, '<' + g + '>') + + if not self.made_.has_key (name): + self.made_ [name] = True + + self.project_.manager ().engine ().add_dependency (name, actual_name) + + self.actualize_location (name) + + self.project_.manager ().scanners ().install (scanner, name, str (self)) + + return name + +# private: (overridables) + + def actualize_action (self, target): + """ Sets up build actions for 'target'. Should call appropriate rules + and set target variables. + """ + raise BaseException ("method should be defined in derived classes") + + def actualize_location (self, target): + """ Sets up variables on 'target' which specify its location. + """ + raise BaseException ("method should be defined in derived classes") + + def path (self): + """ If the target is generated one, returns the path where it will be + generated. Otherwise, returns empty list. + """ + raise BaseException ("method should be defined in derived classes") + + def actual_name (self): + """ Return that actual target name that should be used + (for the case where no scanner is involved) + """ + raise BaseException ("method should be defined in derived classes") + + +class AbstractFileTarget (VirtualTarget): + """ Target which correspond to a file. The exact mapping for file + is not yet specified in this class. (TODO: Actually, the class name + could be better...) + + May be a source file (when no action is specified), or + derived file (otherwise). + + The target's grist is concatenation of project's location, + properties of action (for derived files), and, optionally, + value identifying the main target. + + exact: If non-empty, the name is exactly the name + created file should have. Otherwise, the '__init__' + method will add suffix obtained from 'type' by + calling 'type.generated-target-suffix'. + + type: optional type of this target. + """ + def __init__ (self, name, type, project, action = None, exact=False): + VirtualTarget.__init__ (self, name, project) + + self.type_ = type + + self.action_ = action + self.exact_ = exact + + if action: + action.add_targets ([self]) + + if self.type and not exact: + self.__adjust_name (name) + + + self.actual_name_ = None + self.path_ = None + self.intermediate_ = False + self.creating_subvariant_ = None + + # True if this is a root target. + self.root_ = False + + def type (self): + return self.type_ + + def set_path (self, path): + """ Sets the path. When generating target name, it will override any path + computation from properties. + """ + self.path_ = path + + def action (self): + """ Returns the action. + """ + return self.action_ + + def root (self, set = None): + """ Sets/gets the 'root' flag. Target is root is it directly correspods to some + variant of a main target. + """ + if set: + self.root_ = True + return self.root_ + + def creating_subvariant (self, s = None): + """ Gets or sets the subvariant which created this target. Subvariant + is set when target is brought into existance, and is never changed + after that. In particual, if target is shared by subvariant, only + the first is stored. + s: If specified, specified the value to set, + which should be instance of 'subvariant' class. + """ + if s and not self.creating_subvariant (): + if self.creating_subvariant (): + raise BaseException ("Attempt to change 'dg'") + + else: + self.creating_subvariant_ = s + + return self.creating_subvariant_ + + def actualize_action (self, target): + if self.action_: + self.action_.actualize () + + # Return a human-readable representation of this target + # + # If this target has an action, that's: + # + # { <action-name>-<self.name>.<self.type> <action-sources>... } + # + # otherwise, it's: + # + # { <self.name>.<self.type> } + # + def str(self): + a = self.action() + + name_dot_type = self.name_ + "." + self.type_ + + if a: + action_name = a.action_name() + ss = [ s.str() for s in a.sources()] + + return "{ %s-%s %s}" % (action_name, name_dot_type, str(ss)) + else: + return "{ " + name_dot_type + " }" + +# private: + + def actual_name (self): + if not self.actual_name_: + self.actual_name_ = '<' + self.grist() + '>' + self.name_ + + return self.actual_name_ + + def grist (self): + """Helper to 'actual_name', above. Compute unique prefix used to distinguish + this target from other targets with the same name which create different + file. + """ + # Depending on target, there may be different approaches to generating + # unique prefixes. We'll generate prefixes in the form + # <one letter approach code> <the actual prefix> + path = self.path () + + if path: + # The target will be generated to a known path. Just use the path + # for identification, since path is as unique as it can get. + return 'p' + path + + else: + # File is either source, which will be searched for, or is not a file at + # all. Use the location of project for distinguishing. + project_location = self.project_.get ('location') + path_components = b2.util.path.split(project_location) + location_grist = '!'.join (path_components) + + if self.action_: + ps = self.action_.properties () + property_grist = ps.as_path () + # 'property_grist' can be empty when 'ps' is an empty + # property set. + if property_grist: + location_grist = location_grist + '/' + property_grist + + return 'l' + location_grist + + def __adjust_name(self, specified_name): + """Given the target name specified in constructor, returns the + name which should be really used, by looking at the <tag> properties. + The tag properties come in two flavour: + - <tag>value, + - <tag>@rule-name + In the first case, value is just added to name + In the second case, the specified rule is called with specified name, + target type and properties and should return the new name. + If not <tag> property is specified, or the rule specified by + <tag> returns nothing, returns the result of calling + virtual-target.add-suffix""" + + if self.action_: + ps = self.action_.properties() + else: + ps = property_set.empty() + + # FIXME: I'm not sure how this is used, need to check with + # Rene to figure out how to implement + #~ We add ourselves to the properties so that any tag rule can get + #~ more direct information about the target than just that available + #~ through the properties. This is useful in implementing + #~ name changes based on the sources of the target. For example to + #~ make unique names of object files based on the source file. + #~ --grafik + #ps = property_set.create(ps.raw() + ["<target>%s" % "XXXX"]) + #ps = [ property-set.create [ $(ps).raw ] <target>$(__name__) ] ; + + tag = ps.get("<tag>") + + if tag: + + if len(tag) > 1: + get_manager().errors()( + """<tag>@rulename is present but is not the only <tag> feature""") + + tag = tag[0] + if callable(tag): + self.name_ = tag(specified_name, self.type_, ps) + else: + if not tag[0] == '@': + self.manager_.errors()("""The value of the <tag> feature must be '@rule-nane'""") + + exported_ps = b2.util.value_to_jam(ps, methods=True) + self.name_ = b2.util.call_jam_function( + tag[1:], specified_name, self.type_, exported_ps) + if self.name_: + self.name_ = self.name_[0] + + # If there's no tag or the tag rule returned nothing. + if not tag or not self.name_: + self.name_ = add_prefix_and_suffix(specified_name, self.type_, ps) + + def actualize_no_scanner(self): + name = self.actual_name() + + # Do anything only on the first invocation + if not self.made_: + self.made_[name] = True + + if self.action_: + # For non-derived target, we don't care if there + # are several virtual targets that refer to the same name. + # One case when this is unavoidable is when file name is + # main.cpp and two targets have types CPP (for compiling) + # and MOCCABLE_CPP (for convertion to H via Qt tools). + self.virtual_targets().register_actual_name(name, self) + + for i in self.dependencies_: + self.manager_.engine().add_dependency(name, i.actualize()) + + self.actualize_location(name) + self.actualize_action(name) + + return name + +@bjam_signature((["specified_name"], ["type"], ["property_set"])) +def add_prefix_and_suffix(specified_name, type, property_set): + """Appends the suffix appropriate to 'type/property-set' combination + to the specified name and returns the result.""" + + property_set = b2.util.jam_to_value_maybe(property_set) + + suffix = "" + if type: + suffix = b2.build.type.generated_target_suffix(type, property_set) + + # Handle suffixes for which no leading dot is desired. Those are + # specified by enclosing them in <...>. Needed by python so it + # can create "_d.so" extensions, for example. + if get_grist(suffix): + suffix = ungrist(suffix) + elif suffix: + suffix = "." + suffix + + prefix = "" + if type: + prefix = b2.build.type.generated_target_prefix(type, property_set) + + if specified_name.startswith(prefix): + prefix = "" + + if not prefix: + prefix = "" + if not suffix: + suffix = "" + return prefix + specified_name + suffix + + +class FileTarget (AbstractFileTarget): + """ File target with explicitly known location. + + The file path is determined as + - value passed to the 'set_path' method, if any + - for derived files, project's build dir, joined with components + that describe action's properties. If the free properties + are not equal to the project's reference properties + an element with name of main target is added. + - for source files, project's source dir + + The file suffix is + - the value passed to the 'suffix' method, if any, or + - the suffix which correspond to the target's type. + """ + def __init__ (self, name, type, project, action = None, path=None, exact=False): + AbstractFileTarget.__init__ (self, name, type, project, action, exact) + + self.path_ = path + + def __str__(self): + if self.type_: + return self.name_ + "." + self.type_ + else: + return self.name_ + + def clone_with_different_type(self, new_type): + return FileTarget(self.name_, new_type, self.project_, + self.action_, self.path_, exact=True) + + def actualize_location (self, target): + engine = self.project_.manager_.engine () + + if self.action_: + # This is a derived file. + path = self.path () + engine.set_target_variable (target, 'LOCATE', path) + + # Make sure the path exists. + engine.add_dependency (target, path) + common.mkdir(engine, path) + + # It's possible that the target name includes a directory + # too, for example when installing headers. Create that + # directory. + d = os.path.dirname(get_value(target)) + if d: + d = os.path.join(path, d) + engine.add_dependency(target, d) + common.mkdir(engine, d) + + # For real file target, we create a fake target that + # depends on the real target. This allows to run + # + # bjam hello.o + # + # without trying to guess the name of the real target. + # Note the that target has no directory name, and a special + # grist <e>. + # + # First, that means that "bjam hello.o" will build all + # known hello.o targets. + # Second, the <e> grist makes sure this target won't be confused + # with other targets, for example, if we have subdir 'test' + # with target 'test' in it that includes 'test.o' file, + # then the target for directory will be just 'test' the target + # for test.o will be <ptest/bin/gcc/debug>test.o and the target + # we create below will be <e>test.o + engine.add_dependency("<e>%s" % get_value(target), target) + + # Allow bjam <path-to-file>/<file> to work. This won't catch all + # possible ways to refer to the path (relative/absolute, extra ".", + # various "..", but should help in obvious cases. + engine.add_dependency("<e>%s" % (os.path.join(path, get_value(target))), target) + + else: + # This is a source file. + engine.set_target_variable (target, 'SEARCH', self.project_.get ('source-location')) + + + def path (self): + """ Returns the directory for this target. + """ + if not self.path_: + if self.action_: + p = self.action_.properties () + (target_path, relative_to_build_dir) = p.target_path () + + if relative_to_build_dir: + # Indicates that the path is relative to + # build dir. + target_path = os.path.join (self.project_.build_dir (), target_path) + + # Store the computed path, so that it's not recomputed + # any more + self.path_ = target_path + + return self.path_ + + +class NotFileTarget(AbstractFileTarget): + + def __init__(self, name, project, action): + AbstractFileTarget.__init__(self, name, None, project, action) + + def path(self): + """Returns nothing, to indicate that target path is not known.""" + return None + + def actualize_location(self, target): + bjam.call("NOTFILE", target) + bjam.call("ALWAYS", target) + bjam.call("NOUPDATE", target) + + +class Action: + """ Class which represents an action. + Both 'targets' and 'sources' should list instances of 'VirtualTarget'. + Action name should name a rule with this prototype + rule action_name ( targets + : sources * : properties * ) + Targets and sources are passed as actual jam targets. The rule may + not establish dependency relationship, but should do everything else. + """ + def __init__ (self, manager, sources, action_name, prop_set): + assert(isinstance(prop_set, property_set.PropertySet)) + assert type(sources) == types.ListType + self.sources_ = sources + self.action_name_ = action_name + if not prop_set: + prop_set = property_set.empty() + self.properties_ = prop_set + if not all(isinstance(v, VirtualTarget) for v in prop_set.get('implicit-dependency')): + import pdb + pdb.set_trace() + + self.manager_ = manager + self.engine_ = self.manager_.engine () + self.targets_ = [] + + # Indicates whether this has been actualized or not. + self.actualized_ = False + + self.dependency_only_sources_ = [] + self.actual_sources_ = [] + + + def add_targets (self, targets): + self.targets_ += targets + + + def replace_targets (old_targets, new_targets): + self.targets_ = [t for t in targets if not t in old_targets] + new_targets + + def targets (self): + return self.targets_ + + def sources (self): + return self.sources_ + + def action_name (self): + return self.action_name_ + + def properties (self): + return self.properties_ + + def actualize (self): + """ Generates actual build instructions. + """ + if self.actualized_: + return + + self.actualized_ = True + + ps = self.properties () + properties = self.adjust_properties (ps) + + + actual_targets = [] + + for i in self.targets (): + actual_targets.append (i.actualize ()) + + self.actualize_sources (self.sources (), properties) + + self.engine_.add_dependency (actual_targets, self.actual_sources_ + self.dependency_only_sources_) + + # This works around a bug with -j and actions that + # produce multiple target, where: + # - dependency on the first output is found, and + # the action is started + # - dependency on the second output is found, and + # bjam noticed that command is already running + # - instead of waiting for the command, dependents + # of the second targets are immediately updated. + if len(actual_targets) > 1: + bjam.call("INCLUDES", actual_targets, actual_targets) + + # FIXME: check the comment below. Was self.action_name_ [1] + # Action name can include additional argument to rule, which should not + # be passed to 'set-target-variables' + # FIXME: breaking circular dependency + import toolset + toolset.set_target_variables (self.manager_, self.action_name_, actual_targets, properties) + + engine = self.manager_.engine () + + # FIXME: this is supposed to help --out-xml option, but we don't + # implement that now, and anyway, we should handle it in Python, + # not but putting variables on bjam-level targets. + bjam.call("set-target-variable", actual_targets, ".action", repr(self)) + + self.manager_.engine ().set_update_action (self.action_name_, actual_targets, self.actual_sources_, + properties) + + # Since we set up creating action here, we also set up + # action for cleaning up + self.manager_.engine ().set_update_action ('common.Clean', 'clean-all', + actual_targets) + + return actual_targets + + def actualize_source_type (self, sources, prop_set): + """ Helper for 'actualize_sources'. + For each passed source, actualizes it with the appropriate scanner. + Returns the actualized virtual targets. + """ + result = [] + for i in sources: + scanner = None + +# FIXME: what's this? +# if isinstance (i, str): +# i = self.manager_.get_object (i) + + if i.type (): + scanner = b2.build.type.get_scanner (i.type (), prop_set) + + r = i.actualize (scanner) + result.append (r) + + return result + + def actualize_sources (self, sources, prop_set): + """ Creates actual jam targets for sources. Initializes two member + variables: + 'self.actual_sources_' -- sources which are passed to updating action + 'self.dependency_only_sources_' -- sources which are made dependencies, but + are not used otherwise. + + New values will be *appended* to the variables. They may be non-empty, + if caller wants it. + """ + dependencies = self.properties_.get ('<dependency>') + + self.dependency_only_sources_ += self.actualize_source_type (dependencies, prop_set) + self.actual_sources_ += self.actualize_source_type (sources, prop_set) + + # This is used to help bjam find dependencies in generated headers + # in other main targets. + # Say: + # + # make a.h : ....... ; + # exe hello : hello.cpp : <implicit-dependency>a.h ; + # + # However, for bjam to find the dependency the generated target must + # be actualized (i.e. have the jam target). In the above case, + # if we're building just hello ("bjam hello"), 'a.h' won't be + # actualized unless we do it here. + implicit = self.properties_.get("<implicit-dependency>") + + for i in implicit: + i.actualize() + + def adjust_properties (self, prop_set): + """ Determines real properties when trying building with 'properties'. + This is last chance to fix properties, for example to adjust includes + to get generated headers correctly. Default implementation returns + its argument. + """ + return prop_set + + +class NullAction (Action): + """ Action class which does nothing --- it produces the targets with + specific properties out of nowhere. It's needed to distinguish virtual + targets with different properties that are known to exist, and have no + actions which create them. + """ + def __init__ (self, manager, prop_set): + Action.__init__ (self, manager, [], None, prop_set) + + def actualize (self): + if not self.actualized_: + self.actualized_ = True + + for i in self.targets (): + i.actualize () + +class NonScanningAction(Action): + """Class which acts exactly like 'action', except that the sources + are not scanned for dependencies.""" + + def __init__(self, sources, action_name, property_set): + #FIXME: should the manager parameter of Action.__init__ + #be removed? -- Steven Watanabe + Action.__init__(self, b2.manager.get_manager(), sources, action_name, property_set) + + def actualize_source_type(self, sources, property_set): + + result = [] + for s in sources: + result.append(s.actualize()) + return result + +def traverse (target, include_roots = False, include_sources = False): + """ Traverses the dependency graph of 'target' and return all targets that will + be created before this one is created. If root of some dependency graph is + found during traversal, it's either included or not, dependencing of the + value of 'include_roots'. In either case, sources of root are not traversed. + """ + result = [] + + if target.action (): + action = target.action () + + # This includes 'target' as well + result += action.targets () + + for t in action.sources (): + + # FIXME: + # TODO: see comment in Manager.register_object () + #if not isinstance (t, VirtualTarget): + # t = target.project_.manager_.get_object (t) + + if not t.root (): + result += traverse (t, include_roots, include_sources) + + elif include_roots: + result.append (t) + + elif include_sources: + result.append (target) + + return result + +def clone_action (action, new_project, new_action_name, new_properties): + """Takes an 'action' instances and creates new instance of it + and all produced target. The rule-name and properties are set + to 'new-rule-name' and 'new-properties', if those are specified. + Returns the cloned action.""" + + if not new_action_name: + new_action_name = action.action_name() + + if not new_properties: + new_properties = action.properties() + + cloned_action = action.__class__(action.manager_, action.sources(), new_action_name, + new_properties) + + cloned_targets = [] + for target in action.targets(): + + n = target.name() + # Don't modify the name of the produced targets. Strip the directory f + cloned_target = FileTarget(n, target.type(), new_project, + cloned_action, exact=True) + + d = target.dependencies() + if d: + cloned_target.depends(d) + cloned_target.root(target.root()) + cloned_target.creating_subvariant(target.creating_subvariant()) + + cloned_targets.append(cloned_target) + + return cloned_action + +class Subvariant: + + def __init__ (self, main_target, prop_set, sources, build_properties, sources_usage_requirements, created_targets): + """ + main_target: The instance of MainTarget class + prop_set: Properties requested for this target + sources: + build_properties: Actually used properties + sources_usage_requirements: Properties propagated from sources + created_targets: Top-level created targets + """ + self.main_target_ = main_target + self.properties_ = prop_set + self.sources_ = sources + self.build_properties_ = build_properties + self.sources_usage_requirements_ = sources_usage_requirements + self.created_targets_ = created_targets + + self.usage_requirements_ = None + + # Pre-compose the list of other dependency graphs, on which this one + # depends + deps = build_properties.get('<implicit-dependency>') + + self.other_dg_ = [] + for d in deps: + self.other_dg_.append(d.creating_subvariant ()) + + self.other_dg_ = unique (self.other_dg_) + + self.implicit_includes_cache_ = {} + self.target_directories_ = None + + def main_target (self): + return self.main_target_ + + def created_targets (self): + return self.created_targets_ + + def requested_properties (self): + return self.properties_ + + def build_properties (self): + return self.build_properties_ + + def sources_usage_requirements (self): + return self.sources_usage_requirements_ + + def set_usage_requirements (self, usage_requirements): + self.usage_requirements_ = usage_requirements + + def usage_requirements (self): + return self.usage_requirements_ + + def all_referenced_targets(self, result): + """Returns all targets referenced by this subvariant, + either directly or indirectly, and either as sources, + or as dependency properties. Targets referred with + dependency property are returned a properties, not targets.""" + + # Find directly referenced targets. + deps = self.build_properties().dependency() + all_targets = self.sources_ + deps + + # Find other subvariants. + r = [] + for e in all_targets: + if not e in result: + result.add(e) + if isinstance(e, property.Property): + t = e.value() + else: + t = e + + # FIXME: how can this be? + cs = t.creating_subvariant() + if cs: + r.append(cs) + r = unique(r) + for s in r: + if s != self: + s.all_referenced_targets(result) + + + def implicit_includes (self, feature, target_type): + """ Returns the properties which specify implicit include paths to + generated headers. This traverses all targets in this subvariant, + and subvariants referred by <implcit-dependecy>properties. + For all targets which are of type 'target-type' (or for all targets, + if 'target_type' is not specified), the result will contain + <$(feature)>path-to-that-target. + """ + + if not target_type: + key = feature + else: + key = feature + "-" + target_type + + + result = self.implicit_includes_cache_.get(key) + if not result: + target_paths = self.all_target_directories(target_type) + target_paths = unique(target_paths) + result = ["<%s>%s" % (feature, p) for p in target_paths] + self.implicit_includes_cache_[key] = result + + return result + + def all_target_directories(self, target_type = None): + # TODO: does not appear to use target_type in deciding + # if we've computed this already. + if not self.target_directories_: + self.target_directories_ = self.compute_target_directories(target_type) + return self.target_directories_ + + def compute_target_directories(self, target_type=None): + result = [] + for t in self.created_targets(): + if not target_type or b2.build.type.is_derived(t.type(), target_type): + result.append(t.path()) + + for d in self.other_dg_: + result.extend(d.all_target_directories(target_type)) + + result = unique(result) + return result |