diff --git a/new/errors.jam b/new/errors.jam index 72b116501..3acf8ce39 100644 --- a/new/errors.jam +++ b/new/errors.jam @@ -138,6 +138,7 @@ rule error ( messages * : * ) } } } + EXIT ; } else { diff --git a/new/targets.jam b/new/targets.jam index 446d3d4bf..bbbb5b730 100644 --- a/new/targets.jam +++ b/new/targets.jam @@ -262,6 +262,28 @@ rule main-target ( name : project ) self.direct-request.$(base:J=-) = $(property-set) ; } } + + 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) + { + # 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) + { + viable += $(v) ; + ranks += $(m) ; + } + } + + local best = [ sequence.select-highest-ranked $(viable) : $(ranks) ] ; + return $(best) ; + } # Select an alternative for this main target, by finding all alternatives @@ -277,83 +299,42 @@ rule main-target ( name : project ) property-set = $(ep) ; } - # Try to generate all the alternatives. - local alternatives = [ new vector ] ; - - for local v in $(self.alternatives) - { - local vtargets = [ $(v).generate $(property-set) ] ; - if $(vtargets) && $(vtargets[1]) != "@error" - { - $(alternatives).push-back [ new vector $(v) $(vtargets) ] ; - } - } - if [ $(alternatives).empty ] + 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 ] - "with properties" [ $(property-set).raw ] ; - } else { - local result ; - if [ $(alternatives).size ] = 1 - { - result = [ $(alternatives).get-at 1 ] ; - result = $(result[2-]) ; + "with properties" [ $(property-set).raw ] ; } + else if $(best-alternatives[2]) + { + # TODO: again, a better error message in in order. + errors.error "Ambiguous alternatives for main target" [ full-name ] ; + } else { - # Find the alternative with the longest set of non-free and - # non-indicental requirements and are in 'properties' - local r = [ $(alternatives).indices ] ; - - # First compute the length of requirements sets - local req-length ; - for local p in $(r) + local result = [ $(best-alternatives).generate $(property-set) ] ; + + # Mark all targets in result as roots + for local t in $(result) { - local target = [ $(alternatives).at $(p) : 1 ] ; - # FIXME: in general, 'abstract-target' derivatives might not - # have 'requirements' method. - # assert.equal [ is-a $(target) : basic-target ] : true ; - local req = [ $(target).requirements ] ; - req = [ $(req).base ] ; - req-length += [ sequence.length - [ set.intersection $(req) : [ $(property-set).raw ] ] ] ; + $(t).root true ; } - - local best = [ sequence.select-highest-ranked $(r) : $(req-length) ] ; - - if ! $(best[2]) - { - local index = $(best[1]) ; - result = [ $(alternatives).get-at $(index) ] ; - result = $(result[2-]) ; - } - else - { - error "Ambiguous alternatives for main target" [ full-name ] ; - } - } - - # Mark all targets in result as roots - for local t in $(result) - { - $(t).root true ; - } - # Process all vtargets that will be created if this main target - # is created. - local all-targets = - [ sequence.transform virtual-target.traverse : $(result) ] ; - local dg = [ new subvariant-dg $(__name__) : $(property-set) : $(all-targets) ] ; - for local v in $(all-targets) - { - $(v).dg $(dg) ; - } + # Process all vtargets that will be created if this main target + # is created. + local all-targets = + [ sequence.transform virtual-target.traverse : $(result) ] ; + local dg = [ new subvariant-dg $(__name__) : $(property-set) : $(all-targets) ] ; + for local v in $(all-targets) + { + $(v).dg $(dg) ; + } - # Now return virtual targets for the only alternative - return $(result) ; + # Now return virtual targets for the only alternative + return $(result) ; } } } @@ -461,8 +442,25 @@ rule basic-target ( name : project { errors.error "gristed element in sources for" [ full-name ] ; } - + # 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 + rule match-rank ( property-set ) + { + # First check if our requirements can be satisfied. + local rproperties = [ $(property-set).refine $(self.requirements) ] ; + if $(rproperties[1]) != "@error" + { + # Returns the number of properties common to requirements + # and build request. + return [ sequence.length [ set.intersection + [ $(self.requirements).base ] : + [ $(property-set).raw ] ] ] ; + } + } + # Applies default-build if 'properties' are empty or # have only single element. # Generates sources. Calls 'construct' diff --git a/test/alternatives.py b/test/alternatives.py new file mode 100644 index 000000000..df2ac5fda --- /dev/null +++ b/test/alternatives.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + +# Test main target alternatives. + +from BoostBuild import Tester +t = Tester() + + +# Test that basic alternatives selection works. +t.write("project-root.jam", " ") +t.write("Jamfile", """ + +exe a : a_empty.cpp ; +exe a : a.cpp : release ; +""") +t.write("a_empty.cpp", "") +t.write("a.cpp", "int main() { return 0; }\n") + +t.run_build_system("release") +t.expect_addition("bin/$toolset/release/a.exe") + +# Test that only properties which are in build request +# matters when selection alternative. IOW, alternative +# with release is better than one with +# debug when building release version. +t.write("Jamfile", """ + +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") + +# Test that free properties do not matter. We really don't +# want property in build request to affect +# alternative selection. +t.write("Jamfile", """ +exe a : a_empty.cpp : debug FOO BAR ; +exe a : a.cpp : release ; +""") + +t.rm("bin/$toolset/release/a.exe") +t.run_build_system("release define=FOO") +t.expect_addition("bin/$toolset/release/a.exe") + +# Test that abibuity is reported correctly +t.write("Jamfile", """ +exe a : a_empty.cpp ; +exe a : a.cpp ; +""") +expected="""error: Ambiguous alternatives for main target ./a + +""" +t.run_build_system("--no-error-backtrace", status=1, stdout=expected) + + +t.cleanup() diff --git a/test/project_test4.py b/test/project_test4.py index 143ee457e..6ffc0ae51 100644 --- a/test/project_test4.py +++ b/test/project_test4.py @@ -40,6 +40,7 @@ t.copy("lib/Jamfile2", "lib/Jamfile") expected="""error: Requirements for project at 'lib' conflict with parent's. Explanation: link-incompatible properties single and multi + """ t.run_build_system("--no-error-backtrace", stdout=expected, status=None) diff --git a/test/test_all.py b/test/test_all.py index bb3a1132b..d8a67a605 100644 --- a/test/test_all.py +++ b/test/test_all.py @@ -81,7 +81,9 @@ tests = [ "project_test1", "build_dir", "searched_lib", "make_rule", - "alias"] + "alias", + "alternatives", + ] if os.name == 'posix': tests.append("symlink") diff --git a/v2/build/targets.jam b/v2/build/targets.jam index 446d3d4bf..bbbb5b730 100644 --- a/v2/build/targets.jam +++ b/v2/build/targets.jam @@ -262,6 +262,28 @@ rule main-target ( name : project ) self.direct-request.$(base:J=-) = $(property-set) ; } } + + 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) + { + # 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) + { + viable += $(v) ; + ranks += $(m) ; + } + } + + local best = [ sequence.select-highest-ranked $(viable) : $(ranks) ] ; + return $(best) ; + } # Select an alternative for this main target, by finding all alternatives @@ -277,83 +299,42 @@ rule main-target ( name : project ) property-set = $(ep) ; } - # Try to generate all the alternatives. - local alternatives = [ new vector ] ; - - for local v in $(self.alternatives) - { - local vtargets = [ $(v).generate $(property-set) ] ; - if $(vtargets) && $(vtargets[1]) != "@error" - { - $(alternatives).push-back [ new vector $(v) $(vtargets) ] ; - } - } - if [ $(alternatives).empty ] + 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 ] - "with properties" [ $(property-set).raw ] ; - } else { - local result ; - if [ $(alternatives).size ] = 1 - { - result = [ $(alternatives).get-at 1 ] ; - result = $(result[2-]) ; + "with properties" [ $(property-set).raw ] ; } + else if $(best-alternatives[2]) + { + # TODO: again, a better error message in in order. + errors.error "Ambiguous alternatives for main target" [ full-name ] ; + } else { - # Find the alternative with the longest set of non-free and - # non-indicental requirements and are in 'properties' - local r = [ $(alternatives).indices ] ; - - # First compute the length of requirements sets - local req-length ; - for local p in $(r) + local result = [ $(best-alternatives).generate $(property-set) ] ; + + # Mark all targets in result as roots + for local t in $(result) { - local target = [ $(alternatives).at $(p) : 1 ] ; - # FIXME: in general, 'abstract-target' derivatives might not - # have 'requirements' method. - # assert.equal [ is-a $(target) : basic-target ] : true ; - local req = [ $(target).requirements ] ; - req = [ $(req).base ] ; - req-length += [ sequence.length - [ set.intersection $(req) : [ $(property-set).raw ] ] ] ; + $(t).root true ; } - - local best = [ sequence.select-highest-ranked $(r) : $(req-length) ] ; - - if ! $(best[2]) - { - local index = $(best[1]) ; - result = [ $(alternatives).get-at $(index) ] ; - result = $(result[2-]) ; - } - else - { - error "Ambiguous alternatives for main target" [ full-name ] ; - } - } - - # Mark all targets in result as roots - for local t in $(result) - { - $(t).root true ; - } - # Process all vtargets that will be created if this main target - # is created. - local all-targets = - [ sequence.transform virtual-target.traverse : $(result) ] ; - local dg = [ new subvariant-dg $(__name__) : $(property-set) : $(all-targets) ] ; - for local v in $(all-targets) - { - $(v).dg $(dg) ; - } + # Process all vtargets that will be created if this main target + # is created. + local all-targets = + [ sequence.transform virtual-target.traverse : $(result) ] ; + local dg = [ new subvariant-dg $(__name__) : $(property-set) : $(all-targets) ] ; + for local v in $(all-targets) + { + $(v).dg $(dg) ; + } - # Now return virtual targets for the only alternative - return $(result) ; + # Now return virtual targets for the only alternative + return $(result) ; } } } @@ -461,8 +442,25 @@ rule basic-target ( name : project { errors.error "gristed element in sources for" [ full-name ] ; } - + # 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 + rule match-rank ( property-set ) + { + # First check if our requirements can be satisfied. + local rproperties = [ $(property-set).refine $(self.requirements) ] ; + if $(rproperties[1]) != "@error" + { + # Returns the number of properties common to requirements + # and build request. + return [ sequence.length [ set.intersection + [ $(self.requirements).base ] : + [ $(property-set).raw ] ] ] ; + } + } + # Applies default-build if 'properties' are empty or # have only single element. # Generates sources. Calls 'construct' diff --git a/v2/errors.jam b/v2/errors.jam index 72b116501..3acf8ce39 100644 --- a/v2/errors.jam +++ b/v2/errors.jam @@ -138,6 +138,7 @@ rule error ( messages * : * ) } } } + EXIT ; } else { diff --git a/v2/test/alternatives.py b/v2/test/alternatives.py new file mode 100644 index 000000000..df2ac5fda --- /dev/null +++ b/v2/test/alternatives.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + +# Test main target alternatives. + +from BoostBuild import Tester +t = Tester() + + +# Test that basic alternatives selection works. +t.write("project-root.jam", " ") +t.write("Jamfile", """ + +exe a : a_empty.cpp ; +exe a : a.cpp : release ; +""") +t.write("a_empty.cpp", "") +t.write("a.cpp", "int main() { return 0; }\n") + +t.run_build_system("release") +t.expect_addition("bin/$toolset/release/a.exe") + +# Test that only properties which are in build request +# matters when selection alternative. IOW, alternative +# with release is better than one with +# debug when building release version. +t.write("Jamfile", """ + +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") + +# Test that free properties do not matter. We really don't +# want property in build request to affect +# alternative selection. +t.write("Jamfile", """ +exe a : a_empty.cpp : debug FOO BAR ; +exe a : a.cpp : release ; +""") + +t.rm("bin/$toolset/release/a.exe") +t.run_build_system("release define=FOO") +t.expect_addition("bin/$toolset/release/a.exe") + +# Test that abibuity is reported correctly +t.write("Jamfile", """ +exe a : a_empty.cpp ; +exe a : a.cpp ; +""") +expected="""error: Ambiguous alternatives for main target ./a + +""" +t.run_build_system("--no-error-backtrace", status=1, stdout=expected) + + +t.cleanup() diff --git a/v2/test/project_test4.py b/v2/test/project_test4.py index 143ee457e..6ffc0ae51 100644 --- a/v2/test/project_test4.py +++ b/v2/test/project_test4.py @@ -40,6 +40,7 @@ t.copy("lib/Jamfile2", "lib/Jamfile") expected="""error: Requirements for project at 'lib' conflict with parent's. Explanation: link-incompatible properties single and multi + """ t.run_build_system("--no-error-backtrace", stdout=expected, status=None) diff --git a/v2/test/test_all.py b/v2/test/test_all.py index bb3a1132b..d8a67a605 100644 --- a/v2/test/test_all.py +++ b/v2/test/test_all.py @@ -81,7 +81,9 @@ tests = [ "project_test1", "build_dir", "searched_lib", "make_rule", - "alias"] + "alias", + "alternatives", + ] if os.name == 'posix': tests.append("symlink")