2
0
mirror of https://github.com/boostorg/build.git synced 2026-02-16 01:12:13 +00:00
Files
build/new/generators.jam
Vladimir Prus c46c7fdae0 Prevent creating two equivivalent virtual targets. This, in particular,
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]
2002-09-13 12:19:42 +00:00

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 ;