diff --git a/src/build/generators.jam b/src/build/generators.jam index 4b764a683..f914fcbb2 100644 --- a/src/build/generators.jam +++ b/src/build/generators.jam @@ -6,6 +6,39 @@ # 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 class : class new is-a ; import container : vector ; @@ -129,6 +162,36 @@ rule generator ( return $(self.optional-properties) ; } + # Returns a number telling how good generator's properties match + # the passed properties, or empty list if generator can't be run + # at all. + rule match-rank ( properties * ) + { + # See if generator's requirements are satisfied by 'properties'. + # Treat feature name in requirements (i.e. grist-only element), + # as matching any value of feature. + local raw = [ requirements ] ; + local requirements ; + local features ; + for local r in $(raw) + { + if $(r:G=) + { + requirements += $(r) ; + } + else + { + features += $(r) ; + } + } + + if $(requirements) in $(properties) && $(features) in $(properties:G) + { + return [ sequence.length [ set.intersection + [ optional-properties ] : $(properties) ] ] ; + } + } + # Returns another generator which differers from $(self) in # - id # - value to feature in properties @@ -667,31 +730,13 @@ local rule find-viable-generators ( target-type : properties * : allow-composing if ! $(g) in $(.active-generators) && ! ( [ is-a $(g) : composing-generator ] && ! $(allow-composing) ) { - # See if generator's requirements are satisfied by 'properties'. - # Treat feature name in requirements (i.e. grist-only element), - # as matching any value of feature. - local raw = [ $(g).requirements ] ; - local requirements ; - local features ; - for local r in $(raw) - { - if $(r:G=) - { - requirements += $(r) ; - } - else - { - features += $(r) ; - } - } - - if $(requirements) in $(properties) && $(features) in $(properties:G) + local m = [ $(g).match-rank $(properties) ] ; + if $(m) { viable-generators += $(g) ; - generator-rank += [ sequence.length [ set.intersection - [ $(g).optional-properties ] : $(properties) ] ] ; + generator-rank += $(m) ; t = ; - } + } } } t = $(t[2-]) ; @@ -699,53 +744,6 @@ local rule find-viable-generators ( target-type : properties * : allow-composing return [ sequence.select-highest-ranked $(viable-generators) : $(generator-rank) ] ; } - -# currently unused. Sorry for the cruft. Delete at will -local rule find-viable-generators.new ( target-type : properties * : disallow-composing ? ) -{ - # Select generators that can create the required target type. - local viable-generators = ; - local generator-rank = ; - - import type ; - local t = [ type.all-bases $(target-type) ] ; - - while $(t[1]) - { - generators.dout [ indent ] " ...checking type" [ $(t[1]) ] ; - - for local g in $(.generators.$(t[1])) - { - generators.dout [ indent ] " ...checking" [ $(g).id ] ; - # Avoid trying the same generator twice on different levels. - if ! $(g) in $(.active-generators) - && ! ( [ is-a $(g) : composing-generator ] && $(.disallow-composing) ) - { - local requirements = [ $(g).requirements ] ; - generators.dout [ indent ] " ...requirements:" $(requirements) ; - - if $(requirements) in $(properties) - { - local optional = [ $(g).optional-properties ] ; - local match = $(requirements) [ set.intersection $(optional) : $(properties) ] ; - - if $(match) - { - viable-generators += $(g) ; - generator-rank += [ sequence.length $(match) ] ; - generators.dout [ indent ] " ...matched" [ $(g).id ] - "with rank $(generator-rank[-1]) :" $(match) ; - t = ; - } - } - } - } - t = $(t[2-]) ; - } - - - return [ sequence.select-highest-ranked $(viable-generators) : $(generator-rank) ] ; -} # Given a vector of vectors, of of them represents results of running some # generator, returns the 'best' result, it it exists. Otherwise, exit with