diff --git a/src/build/property-set.jam b/src/build/property-set.jam index baf022111..1f623967a 100644 --- a/src/build/property-set.jam +++ b/src/build/property-set.jam @@ -63,6 +63,16 @@ class property-set { self.non-dependency += $(p) ; } + + if [ MATCH (:) : $(p:G=) ] + { + self.conditional += $(p) ; + } + else + { + self.non-conditional += $(p) ; + } + if propagated in $(att) { @@ -106,8 +116,17 @@ class property-set { return $(self.non-dependency) ; } - + rule conditional ( ) + { + return $(self.conditional) ; + } + + rule non-conditional ( ) + { + return $(self.non-conditional) ; + } + # Returns incidental properties rule incidental ( ) { @@ -131,6 +150,16 @@ class property-set 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) diff --git a/src/build/targets.jam b/src/build/targets.jam index c3a70e8cd..888beadd8 100644 --- a/src/build/targets.jam +++ b/src/build/targets.jam @@ -343,6 +343,13 @@ class main-target : abstract-target # See the documentation for selection rules. local rule select-alternatives ( property-set ) { + # When selecting alternatives we have to consider defaults, + # for example: + # lib l : l.cpp : debug ; + # lib l : l_opt.cpp : release ; + # won't work unless we add default value debug. + property-set = [ $(p).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. @@ -445,12 +452,13 @@ class main-target : abstract-target [ feature.compress-subproperties $(raw) ] $(defaults-to-apply) ] ; - + if $(properties) { for local p in $(properties) { - result += [ property-set.create [ feature.split $(p) ] ] ; + result += [ property-set.create + [ feature.expand [ feature.split $(p) ] ] ] ; } } else @@ -473,13 +481,16 @@ class main-target : abstract-target 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) { - p = [ $(p).expand-composites ] ; - p = [ $(p).add-defaults ] ; local r = [ generate-really $(p) ] ; usage-requirements = [ $(usage-requirements).add $(r[1]) ] ; result += $(r[2-]) ; @@ -494,7 +505,7 @@ class main-target : abstract-target # generated virtual target in other elements. It's possible # that no targets are generated. local rule generate-really ( property-set ) - { + { local best-alternatives = [ select-alternatives $(property-set) ] ; if ! $(best-alternatives) { @@ -556,15 +567,6 @@ class file-reference : abstract-target } - -# Helper for 'find', below. -local rule remove-trailing-slash ( string ) -{ - local stripped = [ MATCH (.*)/$ : $(string) ] ; - stripped ?= $(string) ; - return $(stripped) ; -} - if "--quiet" in [ modules.peek : ARGV ] { .quiet = true ; @@ -703,6 +705,92 @@ rule generate-from-reference return [ $(target).generate $(rproperties) ] ; } +# Given build request and requirements, return properties +# common to dependency build request and target build +# properties +rule common-properties ( build-request requirements ) +{ + # For optimization, we add free requirements directly, + # without using complex algorithsm. + # This gives the complex algorithm 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) ] ; +} + +rule common-properties2 ( build-request requirements ) +{ + # This guaranteed that default properties are present + # in result, unless they are overrided by some requirement. + # FIXME: There is possibility that we've added bar, which is composite + # and expands to bar2, but default value of 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 ] ; + + # Apply non-conditional requirements. + # There's a slight bug here: it's possible that conditional + # requirement change a value set by non-conditional requirements. This + # should be error, but we don't detect it yet. + local raw = [ $(build-request).raw ] ; + raw = [ property.refine $(raw) : + [ feature.expand [ $(requirements).non-conditional ] ] ] ; + + # 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. + + local conditionals = [ $(requirements).conditional ] ; + local count = $(conditionals) and-once-more ; + local prev ; + + local current = $(raw) ; + + local ok ; + while $(count) + { + # Evaluate conditionals in context of current properties + local e = [ property.evaluate-conditionals-in-context $(conditionals) + : $(current) ] ; + # ECHO "Got now: " $(e) ; + if $(e) = $(prev) + { + # If we got the same result, we've found final properties. + count = ; + ok = true ; + } + else + { + # Oops, results of evaluation of conditionals has changes + # Also 'current' contains leftover from previous evaluation. + # Recompute 'current' using initial properties and conditional + # requirements. + prev = $(e) ; + current = [ property.refine $(raw) : [ feature.expand $(e) ] ] ; + } + count = $(count[2-]) ; + } + if ! $(ok) + { + errors.error "Can't evaluate conditional properties " $(conditionals) ; + } + + + return [ property-set.create $(current) ] ; +} + # 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 @@ -714,6 +802,7 @@ class basic-target : abstract-target import property-set ; import set sequence errors ; import "class" : new ; + import property feature ; rule __init__ ( name : project : sources * : requirements * : @@ -772,17 +861,8 @@ class basic-target : abstract-target rule match ( property-set ) { local condition = [ $(self.requirements).base ] ; - local condition2 ; # Weed out conditional properties. - for local c in $(condition) - { - if [ MATCH ^([^:]*)\$ : $(c:G=) ] - { - condition2 += $(c) ; - } - } - condition = $(condition2) ; - + condition = [ MATCH ^([^:]*)\$ : $(condition) ] ; if $(condition) in [ $(property-set).raw ] { return $(condition) ; @@ -793,21 +873,6 @@ class basic-target : abstract-target } } - # Determine and return properties which should be used for - # building when given 'build-request'. This includes refining - # build request with requirements, evaluating conditionals, - # generating depenendecies and running actions for features. - local rule refined-properties ( build-request ) - { - local erequirements = [ $(self.requirements).evaluate-conditionals - $(build-request) ] ; - erequirements = [ $(erequirements).expand-composites ] ; - - local rproperties = [ $(build-request).refine $(erequirements) ] ; - - return $(rproperties) ; - } - # # Allows the user to tag the name of the target, according to properties. # @@ -861,7 +926,9 @@ class basic-target : abstract-target { if ! $(self.generated.$(property-set)) { - local rproperties = [ refined-properties $(property-set) ] ; + local rproperties = [ targets.common-properties $(property-set) + $(self.requirements) ] ; + if $(rproperties[1]) != "@error" { local source-targets ; diff --git a/src/tools/stlport.jam b/src/tools/stlport.jam index 084158ed5..2c90f2672 100644 --- a/src/tools/stlport.jam +++ b/src/tools/stlport.jam @@ -96,7 +96,7 @@ class stlport-target-class : basic-target # manually. property-set = [ property-set.create [ difference - [ $(property-set).raw ] : /stlport//stlport ] ] ; + [ $(property-set).raw ] : /stlport//stlport stlport ] ] ; return [ basic-target.generate $(property-set) ] ; } diff --git a/test/expansion.py b/test/expansion.py new file mode 100644 index 000000000..4c69ba158 --- /dev/null +++ b/test/expansion.py @@ -0,0 +1,72 @@ +#!/usr/bin/python + +# Copyright (C) Vladimir Prus 2003. 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. + +# This file is template for Boost.Build tests. It creates a simple +# project that builds one exe from one source, and checks that the exe +# is really created. +from BoostBuild import Tester, List + + +t = Tester() + +t.write("a.cpp", """ +#ifdef CF_IS_OFF +int main() { return 0; } +#endif + +""") + +t.write("b.cpp", """ +#ifdef CF_1 +int main() { return 0; } +#endif + +""") + +t.write("c.cpp", """ +#ifdef FOO +int main() { return 0; } +#endif + +""") + + +t.write("Jamfile", """ +# See if default value of composite feature 'cf' +# will be expanded to CF_IS_OFF +exe a : a.cpp ; + +# See if subfeature in requirements in expanded. +exe b : b.cpp : on-1 ; + +# See if conditional requirements are recursively expanded. +exe c : c.cpp : $toolset:release release:FOO ; +""") + +t.write("project-root.jam", """ +import feature ; + +feature.feature cf : off on : composite incidental ; + +feature.compose off : CF_IS_OFF ; + +feature.subfeature cf on : version : 1 2 : composite optional incidental ; + +feature.compose 1 : CF_1 ; + +""") + +t.expand_toolset("Jamfile") + +t.run_build_system() +t.expect_addition(["bin/$toolset/debug/a.exe", + "bin/$toolset/debug/b.exe", + "bin/$toolset/release/c.exe", + ]) + +t.cleanup() + diff --git a/test/test_all.py b/test/test_all.py index 429d14267..d8c8d08aa 100644 --- a/test/test_all.py +++ b/test/test_all.py @@ -122,6 +122,7 @@ tests = [ "project_test1", "unit_test", "standalone", "library_order", + "expansion", #"ordered_properties", ]