📄 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 ;
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 )
{
$(targets).sort ;
}
# Creates a generator
class generator
{
import generators ;
import assert ;
import generators : indent increase-indent decrease-indent generators.dout ;
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
: 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) ;
if $(self.target-types) != *
{
sequence.transform type.validate : $(self.target-types) ;
}
# Add the bases of the target types to our optional properties.
# Since optional properties improve a generator's match-rank, a
# generator which requires only a base target type will not be as
# good a match as a generator which requires one of its derived
# target types (and thus has the base type as an optional
# property).
self.optional-properties
= [ feature.expand <base-target-type>$(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 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) ;
}
# Returns a number telling how well generator's properties match
# the passed properties, or an empty list if the generator can't
# be run at all.
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)
{
# We only count matched optional properties to achive better
# orthogonality:
# - required properties are what is needed to generators to run
# - optional properties are used to resolve ambiguities
#
# For example, we have two generators
# - CPP->OBJ, with required property <toolset>msvc
# - RC->OBJ, without required properties
#
# If we could required properties, then the first one will be
# considered more specific, and the second one will never be
# tried.
return
[ sequence.length
[ set.intersection
[ optional-properties ]
: $(properties-to-match)
]
] ;
}
}
# Returns another generator which differers from $(self) in
# - id
# - value to <toolset> feature in properties
rule clone ( new-id : new-toolset-name )
{
return [ new $(__class__) $(new-id)
: $(self.source-types)
: $(self.target-types-and-names)
: [ property.change $(self.requirements)
: <toolset> $(new-toolset-name)
]
] ;
}
# 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.
: 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.
)
{
# multiple = true ; # The tests seem to tolerate this; will
# remove the parameter altogether in the
# next revision to see what I learn -- DWA 2003/5/6
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" ;
}
if $(self.source-types[2])
{
multiple = ;
}
# 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) : $(multiple) ;
}
}
rule run-really ( project name ? : property-set : sources + : multiple ? )
{
# 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) : $(multiple)
:
: consumed bypassed ;
}
local result ;
if $(consumed)
{
result = [ construct-result $(consumed) : $(project) $(name)
: $(property-set) ] ;
result += $(bypassed) ;
}
if $(result)
{
generators.dout [ indent ] " SUCCESS: " $(result) ;
}
else
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -