📄 generators.jam
字号:
# 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.
#
# 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" : is-a new ;
import container ;
import utility : str equal ;
import set sequence ;
import assert ;
import virtual-target ;
import property-set ;
if "--debug-generators" in [ modules.peek : ARGV ]
{
.debug = true ;
}
# Outputs a debug message if generators debugging is on.
# Each element of 'message' is checked to see if it's class instance.
# If so, instead of the value, the result of 'str' call is output.
local rule generators.dout ( message * )
{
if $(.debug)
{
ECHO [ sequence.transform utility.str : $(message) ] ;
}
}
local rule indent ( )
{
return $(.indent:J="") ;
}
local rule increase-indent ( )
{
.indent += " " ;
}
local rule decrease-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 )
{
local v = [ $(targets).get ] ;
$(targets).set $(v[1]) [ sequence.insertion-sort $(v[2-]) : utility.less ] ;
}
# Creates a generator
class generator
{
import generators ;
import assert ;
import generators : indent increase-indent decrease-indent generators.dout ;
import generators ;
import set ;
import utility : equal ;
import feature ;
import errors : error ;
import sequence ;
import type ;
import virtual-target ;
import "class" : new ;
import property ;
EXPORT class@generator : indent increase-indent decrease-indent generators.dout ;
rule __init__ (
id # identifies the generator - should be name of the rule which
# sets up build actions
composing ? # whether generator processes each source target in
# turn, converting it to required types.
# Ordinary generators pass all sources together to
# recusrive generators.construct-types call.
: source-types * # types that this generator can handle. If
# empty, the generator can consume anything.
: 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 *
)
{
self.id = $(id) ;
self.composing = $(composing) ;
self.source-types = $(source-types) ;
self.target-types-and-names = $(target-types-and-names) ;
self.requirements = $(requirements) ;
for local e in $(target-types-and-names)
{
# Create three parallel lists: one with the list of target types,
# and two other with prefixes and postfixes to be added to target
# name. We use parallel lists for prefix and postfix (as opposed
# to mapping), because given target type might occur several times,
# for example "H H(%_symbols)".
local m = [ MATCH ([^\\(]*)(\\((.*)%(.*)\\))? : $(e) ] ;
self.target-types += $(m[1]) ;
self.name-prefix += $(m[3]:E="") ;
self.name-postfix += $(m[4]:E="") ;
}
# Note that 'transform' here, is the same as 'for_each'.
sequence.transform type.validate : $(self.source-types) ;
sequence.transform type.validate : $(self.target-types) ;
}
############## End of constructor #################
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. Properties
# in returned set must be present in build properties if this
# generator is to be used. If result has grist-only element,
# that build properties must include some value of that feature.
# XXX: remove this method?
rule requirements ( )
{
return $(self.requirements) ;
}
# Returns a true value if the generator can be run with the specified
# properties.
rule match-rank ( property-set-to-match )
{
# See if generator's requirements are satisfied by
# 'properties'. Treat a feature name in requirements
# (i.e. grist-only element), as matching any value of the
# feature.
local all-requirements = [ requirements ] ;
local property-requirements feature-requirements ;
for local r in $(all-requirements)
{
if $(r:G=)
{
property-requirements += $(r) ;
}
else
{
feature-requirements += $(r) ;
}
}
local properties-to-match = [ $(property-set-to-match).raw ] ;
if $(property-requirements) in $(properties-to-match)
&& $(feature-requirements) in $(properties-to-match:G)
{
return true ;
}
else
{
return ;
}
}
# Returns another generator which differers from $(self) in
# - id
# - value to <toolset> feature in properties
rule clone ( new-id : new-toolset-properties + )
{
return [ new $(__class__) $(new-id) $(self.composing)
: $(self.source-types)
: $(self.target-types-and-names)
# Note: this does not remove any subfeatures of <toolset>
# which might cause problems
: [ property.change $(self.requirements) : <toolset> ]
$(new-toolset-properties)
] ;
}
# Creates another generator that is the same as $(self), except that
# if 'base' is in target types of $(self), 'type' will in target types
# of the new generator.
rule clone-and-change-target-type ( base : type )
{
local target-types ;
for local t in $(self.target-types-and-names)
{
local m = [ MATCH ([^\\(]*)(\\(.*\\))? : $(t) ] ;
if $(m) = $(base)
{
target-types += $(type)$(m[2]:E="") ;
}
else
{
target-types += $(t) ;
}
}
return [ new $(__class__) $(self.id) $(self.composing)
: $(self.source-types)
: $(target-types)
: $(self.requirements)
] ;
}
# 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.
: property-set # Desired properties for generated targets.
: sources + # Source targets.
)
{
generators.dout [ indent ] " generator" $(self.id) ;
generators.dout [ indent ] " multiple:" $(mutliple) ;
generators.dout [ indent ] " composing:" $(self.composing) ;
if ! $(self.composing) && $(sources[2]) && $(self.source-types[2])
{
errors.error "Unsupported source/source-type combination" ;
}
# We don't run composing generators if no name is specified. The reason
# is that composing generator combines several targets, which can have
# different names, and it cannot decide which name to give for produced
# target. Therefore, the name must be passed.
#
# This in effect, means that composing generators are runnable only
# at top-level of transofrmation graph, or if name is passed explicitly.
# Thus, we dissallow composing generators in the middle. For example, the
# transofrmation CPP -> OBJ -> STATIC_LIB -> RSP -> EXE won't be allowed
# (the OBJ -> STATIC_LIB generator is composing)
if ! $(self.composing) || $(name)
{
run-really $(project) $(name) : $(property-set) : $(sources) ;
}
}
rule run-really ( project name ? : property-set : sources + )
{
# Targets that this generator will consume directly.
local consumed = ;
# Targets that can't be consumed and will be returned as-is.
local bypassed = ;
if $(self.composing)
{
convert-multiple-sources-to-consumable-types $(project)
: $(property-set) : $(sources) : consumed bypassed ;
}
else
{
convert-to-consumable-types $(project) $(name) :
$(property-set) : $(sources)
:
: consumed bypassed ;
}
local result ;
if $(consumed)
{
result = [ construct-result $(consumed) : $(project) $(name)
: $(property-set) ] ;
}
if $(result)
{
generators.dout [ indent ] " SUCCESS: " $(result) ;
}
else
{
generators.dout [ indent ] " FAILURE" ;
}
generators.dout ;
return $(result) ;
}
# Constructs the dependency graph that will be returned by this
# generator
rule construct-result (
consumed + # Already prepared list of consumable targets
# If generator requires several source files will contain
# exactly len $(self.source-types) targets with matching types
# Otherwise, might contain several targets with the type of
# $(self.source-types[1])
: project name ?
: property-set # Properties to be used for all actions create here
)
{
local result ;
# If this is 1->1 transformation, apply it to all consumed targets in order.
if ! $(self.source-types[2]) && ! $(self.composing)
{
generators.dout [ indent ] "alt1" ;
for local r in $(consumed)
{
result += [ generated-targets $(r) : $(property-set) : $(project) $(name) ] ; #(targets) ;
}
}
else
{
generators.dout [ indent ] "alt2 : consumed is" $(consumed) ;
if $(consumed)
{
result += [ generated-targets $(consumed) : $(property-set)
: $(project) $(name) ] ;
}
}
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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -