mirror of
https://github.com/boostorg/build.git
synced 2026-02-16 01:12:13 +00:00
prevents complining the same source twice with the same properties.
Also allow generators to change source names using patterns.
* new/generators.jam (generator): Accept name patterns together with
target types.
(generator.generated-targets): Use name patterns. Transform generated
targets with 'virtual-targets.register', to eliminate duplicate
virtual targets.
[SVN r15302]
653 lines
21 KiB
Plaintext
653 lines
21 KiB
Plaintext
|
|
#
|
|
# G1:
|
|
# - <target-type>EXE
|
|
# - <toolset>gcc
|
|
# Allowed source types: OBJ
|
|
#
|
|
# G2:
|
|
# - <target-type>OBJ
|
|
# - <toolset>gcc
|
|
# Allowed source types: CPP
|
|
#
|
|
# G3:
|
|
# - <target-type>CPP
|
|
# Allowed source types: WHL
|
|
#
|
|
#
|
|
#
|
|
#####################
|
|
#
|
|
# 1.We should make sure we don't find the same transformation twice. For
|
|
# example
|
|
# cpp <- whl,dlp <- wd
|
|
# when searching from cpp we'd find whl <- wd path and dlp <- wd path. We
|
|
# should discover that these paths result in exactly the same set of
|
|
# generators to be used and that this is not an ambiguity.
|
|
# Ideally, we'd want to avoid finding that duplicate transformation.
|
|
# (actually, if we implement efficient caching, we can search for duplicates too).
|
|
# Moreover, this case makes me thing we better have one set of requirements
|
|
# per generator, as otherwise we'd have a terrible mess.
|
|
|
|
# If is possible for the matching process to return type it was not asked for
|
|
# We can't avoid this.
|
|
# cpp <-\---- y <------ whl <---wd
|
|
# \--- l <------ dlp <---/
|
|
# The cpp<-y generator will call matching process to generate y from wd. However, it
|
|
# will be compelled to return dlp also, and we can use dlp until we go back to cpp.
|
|
|
|
# It is possible that generator will produce extra targets of type we
|
|
# don't want to handle. At some step we have to try transforming them
|
|
# into required type. It is possible to do either in generator's 'run'
|
|
# method or in macthing code. The latter alternative is chosen. The
|
|
# reason is that matching code often need to decide between two
|
|
# generators. In order to find if there's real ambiguity, it is
|
|
# required to run all possible transformations.
|
|
#
|
|
# While it's theoretically possible to have different required
|
|
# properties for different target types, it has no apparent benefit.
|
|
#
|
|
# Note that our target type concept is more powerfull than make's
|
|
# target name -- we can have .cpp files used in a different way by
|
|
# different rules.
|
|
|
|
|
|
import class : class new is-a ;
|
|
import container : vector ;
|
|
import numbers : range ;
|
|
import utility : str equal ;
|
|
import set ;
|
|
|
|
if "--debug-generators" in [ modules.peek : ARGV ]
|
|
{
|
|
.debug = true ;
|
|
}
|
|
|
|
# Outputs a debug message if generators debugging is on.
|
|
rule dout
|
|
{
|
|
if $(.debug)
|
|
{
|
|
ECHO $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
|
|
}
|
|
}
|
|
|
|
|
|
rule indent ( )
|
|
{
|
|
return $(.indent:J="") ;
|
|
}
|
|
|
|
rule increase-indent ( )
|
|
{
|
|
.indent += " " ;
|
|
}
|
|
|
|
rule decrase-indent ( )
|
|
{
|
|
.indent = $(.indent[2-]) ;
|
|
}
|
|
|
|
# Takes a vector of 'virtual-target' instances and makes a normalized
|
|
# representation, which is the same for given set of targets,
|
|
# regardless of their order.
|
|
rule normalize-target-list ( targets )
|
|
{
|
|
$(targets).sort ;
|
|
}
|
|
|
|
# Creates a generator with given properties.
|
|
# 'id' identifies the generator, it should be name of the rule which actually set ups build actions
|
|
# 'source-types' are types that this generator can handle
|
|
# 'target-types-and-names' defines which types 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.
|
|
rule generator ( id : source-types + : target-types-and-names + : requirements *
|
|
)
|
|
{
|
|
import generators ;
|
|
import assert ;
|
|
import generators : indent increase-indent decrase-indent ;
|
|
import set ;
|
|
import utility : equal ;
|
|
import feature ;
|
|
import errors : error ;
|
|
|
|
self.id = $(id) ;
|
|
self.source-types = $(source-types) ;
|
|
self.requirements = $(requirements) ;
|
|
|
|
for local e in $(target-types-and-names)
|
|
{
|
|
local m = [ MATCH ([^\\(]*)(\\((.*)%(.*)\\))? : $(e) ] ;
|
|
self.target-types += $(m[1]) ;
|
|
self.name-pre-post.$(m[1]) = $(m[3]) $(m[4]) ;
|
|
}
|
|
|
|
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 cannot change depending
|
|
# list of sources.
|
|
rule target-types ( )
|
|
{
|
|
return $(self.target-types) ;
|
|
}
|
|
|
|
# Returns the required properties for this generator.
|
|
rule requirements ( )
|
|
{
|
|
return $(self.requirements) ;
|
|
}
|
|
|
|
# Returns the list of properties which increase this generator's
|
|
# specificity for the given target-type.
|
|
# TODO: comment is out of date.
|
|
rule optional-properties ( )
|
|
{
|
|
}
|
|
|
|
# Tries to invoke this generator on the given sources. Returns a
|
|
# list of generated targets.
|
|
rule run ( project name ? : properties * : sources + : multiple ? )
|
|
{
|
|
generators.dout [ indent ] " generator" $(self.id) ;
|
|
generators.dout [ indent ] " multiple:" $(mutliple) ;
|
|
|
|
# Targets that this generator will consume directly.
|
|
local consumed ;
|
|
# Targets that can't be consumed and will be returned as-is.
|
|
local bypassed ;
|
|
local failed ;
|
|
|
|
if $(sources[2])
|
|
{
|
|
error "unsupported" ;
|
|
}
|
|
|
|
if $(self.source-types[2])
|
|
{
|
|
multiple = ;
|
|
}
|
|
|
|
local result ;
|
|
for local st in $(self.source-types)
|
|
{
|
|
local type = [ $(sources[1]).type ] ;
|
|
local all-types = [ feature.expand-composites <target-type>$(type) ] ;
|
|
generators.dout [ indent ] *** all types $(all-types) ;
|
|
if [ set.intersection $(all-types:G=) : $(st) ]
|
|
{
|
|
consumed += $(sources[1]) ;
|
|
}
|
|
else
|
|
{
|
|
local transformed = [ generators.construct-dbg $(project) $(name) :
|
|
$(st) $(multiple)
|
|
: $(properties) : $(sources[1]) ] ;
|
|
consumed += [ $(transformed).get-at 1 ] ;
|
|
bypassed += [ $(transformed).get-at 2 ] ;
|
|
}
|
|
}
|
|
|
|
|
|
# If this is 1->1 transformation, apply it to all consumed targets in order.
|
|
if ! $(self.source-types[2])
|
|
{
|
|
generators.dout [ indent ] "alt1" ;
|
|
for local r in $(consumed)
|
|
{
|
|
# CONSIDER: should use relevant properties only?
|
|
result += [ generated-targets $(r) : $(properties) : $(project) $(name) ] ; #(targets) ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
local v = [ new vector $(consumed) ] ;
|
|
generators.dout [ indent ] "alt2 : consumed is" [ $(v).str ] ;
|
|
if $(consumed)
|
|
{
|
|
result += [ generated-targets $(consumed) : $(properties) : $(project) $(name) ] ;
|
|
}
|
|
}
|
|
|
|
if $(result)
|
|
{
|
|
# If our type, X is produced from X_1 and X_2, which are produced
|
|
# from Y by one generator, then, when looking for Y->X_1
|
|
# tranformation X_2 will be added to bypassed. Therefore the
|
|
# set difference is needed.
|
|
# It is obviously reasonable: if a target is consumed here,
|
|
# no need to return it as bypassed.
|
|
# TODO: this is rather inefficient to compare targets.
|
|
for local v in $(bypassed)
|
|
{
|
|
local found = ;
|
|
for local v2 in $(consumed)
|
|
{
|
|
if [ equal $(v) $(v2) ]
|
|
{
|
|
found = true ;
|
|
}
|
|
}
|
|
if ! $(found)
|
|
{
|
|
result += $(v) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
if $(result)
|
|
{
|
|
local v = [ new vector $(result) ] ;
|
|
generators.dout [ indent ] " SUCCESS: " [ $(v).str ] ;
|
|
}
|
|
else
|
|
{
|
|
generators.dout [ indent ] " FAILURE" ;
|
|
}
|
|
generators.dout ;
|
|
return $(result) ;
|
|
}
|
|
|
|
# Constructs targets that are created after consuming 'sources'.
|
|
# If 'name' is specified, then all generated target will have that name.
|
|
# When 'sources' have more than one element, 'name' must be specified,
|
|
# or all source should have the same name.
|
|
rule generated-targets ( sources + : properties * : project name ? )
|
|
{
|
|
if ! $(name)
|
|
{
|
|
name = [ $(sources[1]).name ] ;
|
|
|
|
for local s in $(sources[2])
|
|
{
|
|
if [ $(s).name ] != $(name)
|
|
{
|
|
error "$(self.id): source targets have different names: cannot determine target name" ;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Create generated target for each target type.
|
|
local targets ;
|
|
for local t in $(self.target-types)
|
|
{
|
|
local generated-name ;
|
|
if $(self.name-pre-post.$(t))
|
|
{
|
|
generated-name = [ sequence.join $(self.name-pre-post.$(t)[1]) $(name) $(self.name-pre-post.$(t)[2]) ] ;
|
|
}
|
|
else
|
|
{
|
|
generated-name = $(name) ;
|
|
}
|
|
|
|
targets += [ new virtual-target $(generated-name) : $(t) : $(project) :
|
|
[ property.remove incidental : $(properties) ] ] ;
|
|
}
|
|
# Assign an action for each target
|
|
local a = [ new action $(targets) : $(sources) : $(self.id) : $(properties) ] ;
|
|
for local t in $(targets)
|
|
{
|
|
$(t).action $(a) ;
|
|
}
|
|
|
|
return [ sequence.transform virtual-target.register : $(targets) ] ;
|
|
}
|
|
}
|
|
|
|
class generator ;
|
|
|
|
rule composing-generator ( id : source-types + : target-types + :
|
|
requirements * )
|
|
{
|
|
generator.__init__ $(id) : $(source-types) : $(target-types) :
|
|
$(requirements) ;
|
|
|
|
rule run ( project name ? : properties * : sources + )
|
|
{
|
|
generators.dout [ indent ] " composing generator" $(self.id) ;
|
|
|
|
local consumed ;
|
|
local bypassed ;
|
|
local failed ;
|
|
|
|
# We process each source one-by-one, trying to convert it to
|
|
# a usable type.
|
|
while $(sources) && ! $(failed)
|
|
{
|
|
s = $(sources[1]) ;
|
|
generators.dout [ indent ] "type is " [ $(s).type ] ;
|
|
if [ $(s).type ] in $(self.source-types)
|
|
{
|
|
generators.dout [ indent ] "directly consuming " [ $(s).str ] ;
|
|
consumed += $(s) ;
|
|
}
|
|
else
|
|
{
|
|
local r = [ generators.construct-dbg-types $(project) : $(self.source-types)
|
|
: * : $(properties) : $(s) ] ;
|
|
if ! [ $(r).get-at 1 ]
|
|
{
|
|
failed = true ;
|
|
}
|
|
else
|
|
{
|
|
consumed += [ $(r).get-at 1 ] ;
|
|
bypassed += [ $(r).get-at 2 ] ;
|
|
}
|
|
}
|
|
sources = $(sources[2-]) ;
|
|
}
|
|
|
|
local result ;
|
|
if ! $(failed)
|
|
{
|
|
generators.dout [ indent ] " SUCCESS" ;
|
|
result += [ generated-targets $(consumed) : $(properties) : $(project) $(name) ] ;
|
|
result += $(bypassed) ;
|
|
}
|
|
else
|
|
{
|
|
generators.dout [ indent ] " FAILURE" ;
|
|
}
|
|
return $(result) ;
|
|
}
|
|
}
|
|
|
|
class composing-generator : generator ;
|
|
|
|
#rule generators-space ( )
|
|
#{
|
|
import errors : error ;
|
|
|
|
.generators = ;
|
|
|
|
rule register ( g )
|
|
{
|
|
.generators += $(g) ;
|
|
|
|
for local t in [ $(g).target-types ]
|
|
{
|
|
.generators.$(t) += $(g) ;
|
|
}
|
|
}
|
|
|
|
|
|
rule register-standard ( id : source-types + : target-types + :
|
|
requirements * )
|
|
{
|
|
local g = [ new generator $(id) : $(source-types) : $(target-types)
|
|
: $(requirements) ] ;
|
|
register $(g) ;
|
|
}
|
|
|
|
rule register-composing ( id : source-types + : target-types + :
|
|
requirements * )
|
|
{
|
|
local g = [ new composing-generator $(id) : $(source-types)
|
|
: $(target-types) : $(requirements) ] ;
|
|
.generators += $(g) ;
|
|
|
|
for local t in [ $(g).target-types ]
|
|
{
|
|
.generators.$(t) += $(g) ;
|
|
}
|
|
}
|
|
|
|
|
|
rule try-one-generator ( project name ? : generator multiple ? :
|
|
target-types + : properties * : sources + )
|
|
{
|
|
generators.dout [ indent ] " trying generator" [ $(generator).id ]
|
|
"for" $(target-types:J=" ") "(" $(multiple) ")" ;
|
|
|
|
local targets = [ $(generator).run $(project) $(name) : $(properties) : $(sources)
|
|
: $(multiple) ] ;
|
|
|
|
local v = [ new vector $(targets) ] ;
|
|
generators.dout "-- generator returned" [ $(v).str ] ;
|
|
|
|
|
|
# Generated targets that are of required types
|
|
local result ;
|
|
# Generated target of other types.
|
|
local extra ;
|
|
|
|
for local t in $(targets)
|
|
{
|
|
if [ $(t).type ] in $(target-types)
|
|
{
|
|
result += $(t) ;
|
|
}
|
|
else
|
|
{
|
|
extra += $(t) ;
|
|
}
|
|
}
|
|
|
|
v = [ new vector $(extra) ] ;
|
|
# Now try to convert extra targets
|
|
# 'construct-dbg' will to its best to return only requested
|
|
# target types, so if we receive any extra from that call,
|
|
# we don't try to do anything about them.
|
|
local extra2 ;
|
|
if $(multiple)
|
|
{
|
|
generators.dout "-- trying to convert extra targets" [ $(v).str ] ;
|
|
for local e in $(extra)
|
|
{
|
|
local try2 = [ construct-dbg-types $(project) $(name) : $(target-types) : : $(properties)
|
|
: $(e) ] ;
|
|
|
|
if [ $(try2).get-at 1 ]
|
|
{
|
|
result += [ $(try2).get-at 1 ] ;
|
|
extra2 += [ $(try2).get-at 2 ] ;
|
|
}
|
|
else
|
|
{
|
|
extra2 += $(e) ;
|
|
}
|
|
}
|
|
generators.dout "-- done trying to convert extra targets" [ $(v).str ] ;
|
|
}
|
|
else
|
|
{
|
|
extra2 = $(extra) ;
|
|
}
|
|
local rr = [ new vector [ new vector $(result) ]
|
|
[ new vector $(extra2) ] ] ;
|
|
generators.dout [ indent ] " generator" [ $(generator).id ] " spawned " ;
|
|
generators.dout [ indent ] " " [ $(rr).str ] ;
|
|
return $(rr) ;
|
|
}
|
|
|
|
rule construct-dbg-types ( project name ? : target-types + : multiple ? :
|
|
properties * : source )
|
|
{
|
|
local results ;
|
|
for local t in $(target-types)
|
|
{
|
|
local r = [ construct-dbg $(project) $(name) : $(t) $(multiple) : $(properties) :
|
|
$(source) ] ;
|
|
if [ $(r).get-at 1 ]
|
|
{
|
|
results += $(r) ;
|
|
}
|
|
}
|
|
if $(results[2])
|
|
{
|
|
error "Situation I can't handle:" $(target-types) --
|
|
[ $(source).str ] ;
|
|
}
|
|
if $(results[1])
|
|
{
|
|
return $(results[1]) ;
|
|
}
|
|
else
|
|
{
|
|
return [ new vector [ new vector ]
|
|
[ new vector $(source) ] ] ;
|
|
}
|
|
}
|
|
|
|
# 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 vector of two vectors. The first contains targets of
|
|
# requested types. The second contains all unused sources and
|
|
# additionally generated targets.
|
|
rule construct-dbg ( project name ? : target-type multiple ? : properties * : sources + )
|
|
{
|
|
increase-indent ;
|
|
generators.dout [ indent ] "*** construct" $(target-type) ;
|
|
for local s in $(sources)
|
|
{
|
|
generators.dout [ indent ] "*** from" [ $(s).str ] ;
|
|
}
|
|
if $(multiple)
|
|
{
|
|
generators.dout [ indent ] "*** multiple" ;
|
|
}
|
|
generators.dout [ indent ] "*** properties:" $(properties) ;
|
|
|
|
# Select generators that can create the required target type.
|
|
local viable-generators = ;
|
|
local generator-rank = ;
|
|
# TODO: rank generators by optional properties.
|
|
for local g in $(.generators.$(target-type))
|
|
{
|
|
# Avoid trying the same generator twice on different levels.
|
|
if ! $(g) in $(.active-generators)
|
|
&& ! ( [ is-a $(g) : composing-generator ] && $(.had-composing-generator) )
|
|
{
|
|
if [ $(g).requirements ] in $(properties)
|
|
{
|
|
viable-generators += $(g) ;
|
|
generator-rank += [ sequence.length [ set.intersection
|
|
[ $(g).optional-properties ] : $(properties) ] ] ;
|
|
}
|
|
}
|
|
}
|
|
|
|
viable-generators = [ sequence.select-highest-ranked $(viable-generators) : $(generator-rank) ] ;
|
|
|
|
#Don't do any error reporting there, just return empty string.
|
|
#Possibly, we can have a switch which would turn output of such
|
|
#messages.
|
|
#if ! $(viable-generators[1])
|
|
#{
|
|
# error "No viable generators found." ;
|
|
#}
|
|
|
|
local results = [ new vector ] ;
|
|
|
|
for local g in $(viable-generators)
|
|
{
|
|
.active-generators = $(g) $(.active-generators) ;
|
|
local had-composing-generator-save = $(.had-composing-generator) ;
|
|
if [ is-a $(g) : composing-generator ]
|
|
{
|
|
generators.dout "Had composing generator set to true" ;
|
|
.had-composing-generator = true ;
|
|
}
|
|
|
|
local r = [ try-one-generator $(project) $(name) : $(g) $(multiple) : $(target-type) :
|
|
$(properties) : $(sources) ] ;
|
|
|
|
.active-generators = $(.active-generators[2-]) ;
|
|
generators.dout "Restoring 'had...' to " $(had-composing-generator-save) ;
|
|
.had-composing-generator = $(had-composing-generator-save) ;
|
|
|
|
if [ $(r).get-at 1 ]
|
|
{
|
|
$(results).push-back $(r) ;
|
|
}
|
|
}
|
|
|
|
#The same comment as above applies.
|
|
#if [ $(results).empty ]
|
|
#{
|
|
# error "No generator could produce desired targets" ;
|
|
#}
|
|
|
|
local result ;
|
|
if ! [ $(results).size ] in 0 1
|
|
{
|
|
# We have several alternatives and need to check if they
|
|
# are the same.
|
|
|
|
for local r in [ $(results).get ]
|
|
{
|
|
normalize-target-list [ $(r).at 1 ] ;
|
|
normalize-target-list [ $(r).at 2 ] ;
|
|
generators.dout [ $(r).str ] ;
|
|
}
|
|
|
|
local f = [ $(results).at 0 ] ;
|
|
local mismatch ;
|
|
for local r in [ $(results).get ]
|
|
{
|
|
if ! [ utility.equal $(r) $(f) ]
|
|
{
|
|
mismatch = true ;
|
|
}
|
|
}
|
|
|
|
if ! $(mismatch)
|
|
{
|
|
$(results).clear ;
|
|
$(results).push-back $(f) ;
|
|
}
|
|
else
|
|
{
|
|
error [ $(results).size ] "possible generations for "
|
|
$(target-types) "Can't handle this now." ;
|
|
}
|
|
}
|
|
|
|
|
|
if ! [ $(results).empty ]
|
|
{
|
|
result = [ $(results).at 1 ] ;
|
|
for local c in [ $(result).get-at 1 ]
|
|
{
|
|
generators.dout [ indent ] "*** constructed" [ $(c).str ] ;
|
|
}
|
|
for local c in [ $(result).get-at 2 ]
|
|
{
|
|
generators.dout [ indent ] "*** also got" [ $(c).str ] ;
|
|
}
|
|
|
|
generators.dout ;
|
|
}
|
|
else
|
|
{
|
|
result = [ new vector [ new vector ]
|
|
[ new vector $(sources) ] ] ;
|
|
}
|
|
decrase-indent ;
|
|
|
|
return $(result) ;
|
|
}
|
|
|
|
#}
|
|
|
|
#class generators-space ;
|
|
|