diff --git a/boost_build_v2.html b/boost_build_v2.html index 0bae4791f..8bce989c5 100644 --- a/boost_build_v2.html +++ b/boost_build_v2.html @@ -22,12 +22,12 @@ div.alert { color: red } table { align: center; border: thin; } - + + - build request, build request expansion and directly requested targets + - conditional properties + -->

Build process +

+
+
Alternative + selection
+
+
+
Generated headers
@@ -179,12 +186,12 @@ bjam there. A simple application will be built. You can also play with other projects in examples-v2. + it somewhere. + +

It is slighly better way is to copy new/user-config.jam + into one of the locations where it can be found (given in this table). This prevent you from + accidentally overwriting your config when updating.

--> @@ -740,8 +747,8 @@ boost-build /path/to/boost.build ; Each call to the 'exe' rule defines a new main target alternative for the main target a. In this case, the first alternative will be used for the gcc toolset, while the second - alternative will be used in other cases. TODO: document the exact - selection method under "Build process" below. + alternative will be used in other cases. See below for details.

Sometime a main target is really needed only by some other main target. E.g. a rule that declared test-suite uses a main target that @@ -1029,17 +1036,17 @@ boost-build /path/to/boost.build ;

  • It allows to have main target names with slashes. + That makes good rationale for why main target must contain names. + -->
  • @@ -1652,7 +1659,9 @@ borland/runtime-link=static,dynamic
  • - For main target, steps are: + For main target, with several alternatives, one of them is selected + as described below. If there's + only one alternative, it's used unconditionally.
    1. All main target alternatives which requirements are satisfied @@ -1686,6 +1695,27 @@ borland/runtime-link=static,dynamic issued.
    2. +

      Alternative selection

      + +

      When there are several alternatives, one of them must be selected. The + process is as follows:

      + +
        +
      1. For each alternative condition is defined as the set of + base properies in requirements. [Note: it might be better to explicitly + specify the condition explicitly, as in conditional requirements].
      2. + +
      3. An alternative is viable only if all properties in condition are + present in build request.
      4. + +
      5. If there's one viable alternative, it's choosen. Otherwise, an + attempt is made to find one best alternative. An alternative a is + better than another alternative b, iff set of properties in b's + condition is stict subset of the set of properities of 'a's condition. + If there's one viable alternative, which is better than all other, it's + selected. Otherwise, an error is reported.
      6. +
      +

      Generated headers

      Usually, Boost.Build handles implicit dependendies completely diff --git a/src/build/targets.jam b/src/build/targets.jam index 4641991ce..adf069c10 100644 --- a/src/build/targets.jam +++ b/src/build/targets.jam @@ -366,26 +366,65 @@ class main-target : abstract-target self.alternatives += $(target) ; } + # Returns the best viable alternative for this property-set + # See the documentation for selection rules. local rule select-alternatives ( property-set ) { - local viable ; # alternatives that may be used - local ranks ; # ranks for viable alternatives - for local v in $(self.alternatives) + # The algorithm: we keep the current best viable alternative. + # When we've got new best viable alternative, we compare it + # with the current one. + + local best ; + local best-properties ; + + if $(self.alternatives[2-]) { - # For now, alternative should be derived from 'basic-target'. - # We'll see if this restriction if reasonable. - assert.equal [ is-a $(v) : basic-target ] : true ; - local m = [ $(v).match-rank $(property-set) ] ; - - if $(m) + local bad ; + local worklist = $(self.alternatives) ; + while $(worklist) && ! $(bad) { - viable += $(v) ; - ranks += $(m) ; + local v = $(worklist[1]) ; + local properties = [ $(v).match $(property-set) ] ; + + 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) ; + } } - - local best = [ sequence.select-highest-ranked $(viable) : $(ranks) ] ; - return $(best) ; + else + { + return $(self.alternatives) ; + } } @@ -486,20 +525,13 @@ class main-target : abstract-target local best-alternatives = [ select-alternatives $(property-set) ] ; if ! $(best-alternatives) { - # TODO: probably, should explain, for each alternative, - # why it can't be build. - print.wrapped-text - "warning: skipped build of" [ full-name ] + errors.error + "skipped build of" [ full-name ] "with properties" [ $(property-set).raw ] "because no best-matching alternative could be found" ; return [ property-set.empty ] ; } - else if $(best-alternatives[2]) - { - # TODO: again, a better error message in in order. - errors.error "Ambiguous alternatives for main target" [ full-name ] ; - } else { local result = [ $(best-alternatives).generate $(property-set) ] ; @@ -781,32 +813,20 @@ class basic-target : abstract-target return $(self.default-build) ; } - # Returns a number which estimates this targets's suitability for - # building with the given 'property-set'. Among several alternatives - # for a main target, the one with greatest match-rank will be used - # to do actual generation. Return of empty value mean this target - # can't be built with the given 'property-set'. - rule match-rank ( property-set ) + # Returns the alternative condition for this alternative, if + # the condition is satisfied by 'property-set'. + rule match ( property-set ) { - # First check if our requirements can be satisfied. - local rproperties = [ $(property-set).refine $(self.requirements) ] ; - - # Kluge: previously, link-incompatible alternatives were skipped - # completely. Until a better alternative selection algorithm is - # in place, treat such alternatives as having rank of zero. - # As another kluge, add 1 to all other ranks so that zero is worse that - # anything. - if [ $(rproperties).link-incompatible-with $(property-set) ] + local condition = [ $(self.requirements).base ] ; + # Weed out conditional properties. + condition = [ MATCH ^([^:]*)\$ : $(condition) ] ; + if $(condition) in [ $(property-set).raw ] { - return 0 ; + return $(condition) ; } else - { - # Returns the number of properties common to requirements - # and build request. - return [ sequence.length "fake" [ set.intersection - [ $(self.requirements).base ] : - [ $(property-set).raw ] ] ] ; + { + return no-match ; } } diff --git a/test/alternatives.py b/test/alternatives.py index 7e8473be5..e4ced2c24 100644 --- a/test/alternatives.py +++ b/test/alternatives.py @@ -19,6 +19,29 @@ t.write("a.cpp", "int main() { return 0; }\n") t.run_build_system("release") t.expect_addition("bin/$toolset/release/a.exe") +# Test that alternative selection works for ordinary +# properties, in particular user-defined. +t.write("project-root.jam", " ") +t.write("Jamfile", """ + +import feature ; +feature.feature X : off on : propagated ; + +exe a : b.cpp ; +exe a : a.cpp : on ; +""") +t.write("b.cpp", "int main() { return 0; }\n") + +t.rm("bin") + +t.run_build_system() +t.expect_addition("bin/$toolset/debug/b.obj") + +t.run_build_system("X=on") +t.expect_addition("bin/$toolset/debug/X-on/a.obj") + +t.rm("bin") + # Test that everything works ok even with default # build. @@ -31,7 +54,6 @@ exe a : a.cpp : debug ; t.run_build_system() t.expect_addition("bin/$toolset/debug/a.exe") - # Test that only properties which are in build request # matters when selection alternative. IOW, alternative # with release is better than one with @@ -42,7 +64,6 @@ exe a : a_empty.cpp : debug ; exe a : a.cpp : release ; """) -t.rm("bin/$toolset/release/a.exe") t.run_build_system("release") t.expect_addition("bin/$toolset/release/a.exe") @@ -63,11 +84,25 @@ t.write("Jamfile", """ exe a : a_empty.cpp ; exe a : a.cpp ; """) -expected="""error: Ambiguous alternatives for main target ./a +expected="""error: skipped build of ./a with properties object on on false off shared shared off LINUX off on on native project-relative single gcc console debug because no best-matching alternative could be found """ t.run_build_system("--no-error-backtrace", status=1, stdout=expected) +# Another ambiguity test: two matches properties in one alternative are +# neither better nor worse than a single one in another alternative. +t.write("Jamfile", """ +exe a : a_empty.cpp : off off ; +exe a : a.cpp : on ; +""") +expected="""error: skipped build of ./a with properties object on on false off shared shared off LINUX off on on native project-relative single gcc console debug because no best-matching alternative could be found + +""" +t.run_build_system("--no-error-backtrace", status=1, stdout=expected) + + + + # Test that we can have alternative without sources t.write("Jamfile", """ alias specific-sources ;