2
0
mirror of https://github.com/boostorg/build.git synced 2026-02-16 13:22:11 +00:00
Files
build/new/generators.jam
Vladimir Prus b12a902687 Clean ups.
* new/builtin.jam: Made 'toolset' and 'variant'
    features symmentic. Document and fix the 'variant' rule.

* new/virtual-target.jam: Refactoring attempts. Split
   virtual-target into virtual-target and derived from it
   file-target. Eliminate some duplication in setting target
   locations.


[SVN r16437]
2002-11-27 08:37:07 +00:00

941 lines
31 KiB
Plaintext

# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
# distribute this software is granted provided this copyright notice appears in
# all copies. This software is provided "as is" without express or implied
# warranty, and with no claim as to its suitability for any purpose.
# Manages 'generators' --- objects which can do transformation between different
# target types and contain algorithm for finding transformation from sources
# to targets.
#####################
#
# 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
rule generator (
id # identifies the generator - should be name of the rule which
# sets up build actions
: source-types + # types that this generator can handle
: target-types-and-names +
# types the 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.
: 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]) ;
}
self.optional-properties
= [ feature.expand <base-target-type>$(self.target-types) ]
;
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 ( )
{
return $(self.optional-properties) ;
}
# Tries to invoke this generator on the given sources. Returns a
# list of generated targets (instances of 'virtual-target').
rule run ( project # Project for which the targets are generated
name ? # Determines the name of 'name' attribute for
# all generated targets. See 'generated-targets' method.
: properties * # Desired properties for generated targets.
: sources + : # Source targets.
multiple ? # Allows the rule to run generator several times and return
# multiple targets of the same type. When this argument is not
# given, 'run' will return the list of targets, which is equal
# in size to the list of target types, and where type of
# each target is the same as the corresponding element of
# target type list. Non-empty value allows to return several
# such target lists, joined together.
)
{
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 = ;
}
# Try to collect in 'consumed' full target set required for this generator.
# Directly place there target of acceptable types, and use 'construct'
# for all others. If 'construct' invocation return additional targets
# (of types we can't handle), place them to 'bypassed'.
local result ;
for local st in $(self.source-types)
{
local actual-st = [ $(sources[1]).type ] ;
if $(actual-st) = $(st) || [ type.is-derived $(actual-st) $(st) ]
{
consumed += $(sources[1]) ;
}
else
{
local transformed = [ generators.construct $(project) $(name) :
$(st) $(multiple)
: $(properties) : $(sources[1]) ] ;
for local t in $(transformed)
{
if [ $(t).type ] = $(st)
{
consumed += $(t) ;
}
else
{
bypassed += $(t) ;
}
}
}
}
# Construct 'result' by creating dependency graph with 'consumed' as targets.
# 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) ] ;
}
}
# Remove from 'bypassed' elements present in 'consumed'
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. We should consider
# if using of 'virtual-target.register' allows us to use a simple string
# comparision of targets, instead of 'equal'.
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'.
# The result will be the list of virtual-target, which the same length
# as 'target-types' attribute and with corresponding types.
#
# When 'name' is empty, all source targets must have the same value of
# the 'name' attribute, which will be used instead of the 'name' argument.
#
# The value of 'name' attribute for each generated target will be equal to
# the 'name' parameter if there's no name pattern for this type. Otherwise,
# the '%' symbol in the name pattern will be replaced with the 'name' parameter
# to obtain the 'name' attribute.
#
# For example, if targets types are T1 and T2(with name pattern "%_x"), suffixes
# for T1 and T2 are .t1 and t2, and source if foo.z, then created files would
# be "foo.t1" and "foo_x.t2". The 'name' attribute actually determined the
# basename of a file.
#
# Note that this pattern mechanism has nothing to do with implicit patterns
# in make. It's a way to produce target which name is different for name of
# source.
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" ;
}
}
# Names of sources might include directory. We should strip it.
name = $(name:D=) ;
}
# 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 file-target $(generated-name) : $(t) : $(project) ] ;
}
# Assign an action for each target
local action = [ action-class ] ;
local a = [ new $(action) $(targets) : $(sources) : $(self.id) : $(properties) ] ;
for local t in $(targets)
{
$(t).action $(a) ;
}
return [ sequence.transform virtual-target.register : $(targets) ] ;
}
# Returns the class to be used to actions. Default implementation
# returns "action".
rule action-class ( )
{
return "action" ;
}
}
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 ] ;
local consumable ;
local ast = [ $(s).type ] ;
for local st in $(self.source-types)
{
if $(ast) = $(st) || [ type.is-derived $(ast) $(st) ]
{
consumable = true ;
}
}
if $(consumable)
{
generators.dout [ indent ] "directly consuming " [ $(s).str ] ;
consumed += $(s) ;
}
else
{
local r = [ generators.construct-types $(project) : $(self.source-types)
: * : $(properties) : $(s) ] ;
if ! $(r[1]) || ! [ $(r[1]).type ] in $(self.source-types)
{
failed = true ;
}
else
{
for local t in $(r)
{
if [ $(t).type ] in $(self.source-types)
{
consumed += $(t) ;
}
else
{
bypassed += $(t) ;
}
}
}
}
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 ;
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) ;
}
}
# Set if results of the current generators search are going to be cached
# This means no futher attempts to cache generators search should be
# made.
.caching = ;
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
{
# We might have asked for a type 'D', but found only generator for
# a type 'B', where 'D' is derived from 'B'. In this case, the
# generation succeeds, but we should change type of the generated target.
local at = [ $(t).type ] ;
local found ;
for local tt in $(target-types)
{
if ! $(found) && [ type.is-derived $(tt) $(at) ]
{
$(t).set-type $(tt) ;
result += $(t) ;
}
}
if ! $(found)
{
extra += $(t) ;
}
}
}
v = [ new vector $(extra) ] ;
# Now try to convert extra targets
# 'construct' 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-types $(project) $(name) : $(target-types) : : $(properties)
: $(e) ] ;
result += $(try2) ;
}
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 $(result) $(extra2) ;
}
rule construct-types ( project name ? : target-types + : multiple ? :
properties * : source )
{
local results ;
for local t in $(target-types)
{
local r = [ construct $(project) $(name) : $(t) $(multiple) : $(properties) :
$(source) ] ;
if $(r)
{
results += [ new vector $(r) ] ;
}
}
if $(results[2])
{
error "Situation I can't handle:" $(target-types) --
[ $(source).str ] ;
}
if $(results[1])
{
return [ $(results[1]).get ] ;
}
else
{
return $(source) ;
}
}
# Ensures all 'targets' have types. If this is not so, exists with
# error.
local rule ensure-type ( targets * )
{
for local t in $(targets)
{
if ! [ $(t).type ]
{
errors.error "target" [ $(t).str ] "has no type" ;
}
}
}
# Returns generators which can be used to construct target of specified type
# with specified properties. Uses the following algorithm:
# - iterates over requested target-type and all it's bases (in the order returned bt
# type.all-bases.
# - for each type find all generators that generate that type and which requirements
# are satisfied by properties. If 'sallow-composing' is not given, ignores all
# composing generators.
# - if the set of generators is not empty, returns that set.
#
# Note: this algorithm explicitly ignores generators for base classes if there's
# at least one generator for requested target-type.
local rule find-viable-generators ( target-type : properties * : allow-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])
{
for local g in $(.generators.$(t))
{
# Avoid trying the same generator twice on different levels.
if ! $(g) in $(.active-generators)
&& ! ( [ is-a $(g) : composing-generator ] && ! $(allow-composing) )
{
if [ $(g).requirements ] in $(properties)
{
viable-generators += $(g) ;
generator-rank += [ sequence.length [ set.intersection
[ $(g).optional-properties ] : $(properties) ] ] ;
t = ;
}
}
}
t = $(t[2-]) ;
}
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
# and error. Result is returned as plain jam list.
local rule select-dependency-graph ( options )
{
if [ $(options).size ] = 0
{
return ;
}
else if [ $(options).size ] = 1
{
return [ $(options).get-at 1 ] ;
}
else
{
# We have several alternatives and need to check if they
# are the same.
for local r in [ $(options).get ]
{
normalize-target-list $(r) ;
generators.dout [ $(r).str ] ;
}
local f = [ $(options).at 1 ] ;
local mismatch ;
for local r in [ $(results).get ]
{
if ! [ utility.equal $(r) $(f) ]
{
mismatch = true ;
}
}
if ! $(mismatch)
{
return [ $(f).get ] ;
}
else
{
error [ $(options).size ] "possible generations for "
$(target-types) "Can't handle this now." ;
}
}
}
.construct-stack = ;
# 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 list of target. When this invocation is first instance of
# 'construct' in stack, returns only targets of requested 'target-type',
# otherwise, returns also unused sources and additionally generated
# targets.
rule construct ( project name ? : target-type multiple ? : properties * : sources +
: allow-composing ? # Allows to use composing generators for constructing this
# target. This will be typically set when creating main targets,
# and unset when called recursively from 'run' method of
# standard generators. Therefore, composing generators will
# be tried for main targets, but not for any intermediate.
# It prevents things like infinite recursion (LIB <- LIB <- ..).
# Generator may pass non-empty value if finding composing
# generator is reuiqred, see builtin.lib-generator for example.
)
{
local use-requirements ;
if (.construct-stack)
{
ensure-type $(sources) ;
for local e in $(sources)
{
use-requirements += [ $(e).use-requirements ] ;
}
}
properties += $(use-requirements) ;
properties = [ sequence.unique $(properties) ] ;
.construct-stack += 1 ;
increase-indent ;
local m ;
if $(multiple)
{
m = "(may return multiple targets)" ;
}
generators.dout [ indent ] "*** construct" $(target-type) $(m) ;
for local s in $(sources)
{
generators.dout [ indent ] " from" [ $(s).str ] ;
}
generators.dout [ indent ] " properties:" $(properties) ;
local result ;
# TODO: should probably use a better logic to decide when to activate
# caching
if ! $(.caching) && ! $(sources[2]) && $(sources[1]) && ! $(name)
{
local .caching = true ;
local t = $(sources[1]) ;
local signature = [ sequence.join [ $(t).type ] $(target-type) $(properties) : - ] ;
# Get a transformation template from cache or create it.
local cresult ;
if $(.transformation.cache.$(signature))
{
cresult = $(.transformation.cache.$(signature)) ;
}
else
{
local ut = [ new file-target % : [ $(t).type ] : "no project" ] ;
cresult = [ construct $(project) : $(target-type) $(multiple) : $(properties) : $(ut) ] ;
.transformation.cache.$(signature) = $(cresult) ;
}
# Substitute the real source name in the transformation template.
if $(cresult)
{
generators.dout [ indent ] "*** putting to cache?" ;
for local c in $(cresult)
{
local cc = [ virtual-target.clone-template $(c) : $(t) ] ;
generators.dout [ indent ] "*** cloning " [ $(c).str ] ;
generators.dout [ indent ] "*** cloned" $(cc) --- [ $(cc).str ] ;
result += $(cc) ;
}
}
} else {
viable-generators = [ find-viable-generators $(target-type) : $(properties)
: $(allow-composing) ] ;
#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 ] ;
generators.dout [ indent ] "*** " [ sequence.length $(viable-generators) ]
" viable generators" ;
for local g in $(viable-generators)
{
# This variable will be restored on exit from this scope.
local .active-generators = $(g) $(.active-generators) ;
local r = [ try-one-generator $(project) $(name) : $(g) $(multiple) : $(target-type) :
$(properties) : $(sources) ] ;
if $(r)
{
$(results).push-back [ new vector $(r) ] ;
}
}
#The same comment as above applies.
#if [ $(results).empty ]
#{
# error "No generator could produce desired targets" ;
#}
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) ;
generators.dout [ $(r).str ] ;
}
local f = [ $(results).at 1 ] ;
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)
: "generations:" [ $(results).str ] ;
}
}
result = [ select-dependency-graph $(results) ] ;
}
decrase-indent ;
.construct-stack = $(.construct-stack[2-]) ;
if ! $(.construct-stack) # This is first invocation in stack
{
# Make roots of dependency graph depent on all 'dependency' features.
local dp = [ property.take dependency : $(properties) ] ;
if $(dp)
{
for local t in $(result)
{
$(t).depends $(dp:G=) ;
}
}
local result2 ;
for local t in $(result)
{
local type = [ $(t).type ] ;
if $(type) = $(target-type) || [ type.is-derived $(type) $(target-type) ]
{
result2 += $(t) ;
}
}
return $(result2) ;
}
else
{
return $(result) ;
}
}