2
0
mirror of https://github.com/boostorg/build.git synced 2026-02-16 01:12:13 +00:00
Files
build/new/virtual-target.jam
Vladimir Prus 4c14009b18 Fix/improve the symlink rule.
* new/path.jam
    (make-NT, make-UNIX): Convert empty path into "."

* new/project.jam
    (path-relative-to-project-location): New rule.

* new/stage.jam
    (stage-target-class.construct): Use the above rule.

* new/symlink.jam
    (symlink-targets.construct): Call 'set-path' on created
    virtual targets.
    (ln): Attempts at better handling creating symlinks in
    directories.

* new/virtual-target.jam
    (abstract-file-target.actual-name): If explicit path was
    given, include it in grist.

* test/symlink.py: New test.

* test/test_all.py
    Run new test.


[SVN r16889]
2003-01-13 16:09:43 +00:00

867 lines
25 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.
# Implements virtual targets, which correspond to actual files created during
# build, but are not yet targets in Jam sense. They are needed, for example,
# when searching for possible transormation sequences, when it's not known
# if particular target should be created at all.
import class : class new ;
import type ;
# +--------------------------+
# | virtual-target |
# +==========================+
# | actualize |
# +--------------------------+
# | actualize-action() = 0 |
# | actualize-location() = 0 |
# +----------------+---------+
# |
# ^
# / \
# +-+-+
# |
# +---------------------+ +---------+------------+
# | action | | abstract-file-target |
# +=====================| 1 +======================+
# | action-name | +--| action |
# | properties | | +----------------------+
# +---------------------+--+ | actualize-action() |
# | actualize() |0..1 +-----------+----------+
# | path() | |
# | adjust-properties() | |
# | actualize-sources() | |
# +------+--------------+ ^
# | / \
# ^ +-+-+
# / \ |
# +-+-+ +-------------|-------------+
# | | |
# | +------+---------------+ +--------+-------------+
# | | file-target | | searched-lib-target |
# | +======================+ +======================+
# | | actualize-location() | | actualize-location() |
# | +----------------------+ +----------------------+
# |
# +--------------------------------+
# | |
# +---------------------+ +---------------------+
# | compile-action | | link-action |
# +=====================+ +=====================+
# | adjust-properties() | | adjust-properties() |
# +---------------------+ | actualize-sources() |
# +---------------------+
#
# The 'compile-action' and 'link-action' classes are defined not here,
# but in builtin.jam modules. They are shown in the diagram to give
# the big picture.
# Potential target. It can be converted into jam target and used in
# building, if needed. However, it can be also dropped, which allows
# to search for different transformation and select only one.
#
rule virtual-target ( name # Name of this target -- specifies the name of
: project # Project to which this target belongs
)
{
self.name = $(name) ;
self.project = $(project) ;
self.dependencies = ;
# Name of this target.
rule name ( ) { return $(self.name) ; }
# Project of this target.
rule project ( ) { return $(self.project) ; }
# Adds additional instances of 'virtual-target' that this
# one depends on.
rule depends ( d + )
{
self.dependencies = [ sequence.merge $(self.dependencies)
: [ sequence.insertion-sort $(d) ] ] ;
}
rule dependencies ( )
{
return $(self.dependencies) ;
}
# Generates all the actual targets and set ups build actions for
# this target.
#
# If 'scanner' is specified, creates an additional target
# with the same location as actual target, which will depend on the
# actual target and be associated with 'scanner'. That additional
# target is returned. See the docs (#dependency_scanning) for rationale.
# Target must correspond to a file if 'scanner' is specified.
#
# If scanner is not specified, then actual target is returned.
rule actualize ( scanner ? )
{
# ### This comment is probably out-of-date. Ignore for now.
# When we have explicitly specified target, we need to create a
# target and associate scanner with it.
# There may be many different scanners, but there's only one
# generating action. Therefore, generating action actualizes this
# target without any scanner. Actual target with scanners will
# depend on actual target without scanner.
local actual-name = [ actualize-no-scanner ] ;
if ! $(scanner)
{
return $(actual-name) ;
}
else
{
# Add the scanner instance to the grist for name.
local g = [ sequence.join
[ utility.ungrist $(actual-name:G) ] $(scanner) : - ] ;
local name = $(actual-name:G=$(g)) ;
if ! $(self.made.$(name)) {
self.made.$(name) = true ;
DEPENDS $(name) : $(actual-name) ;
BINDRULE on $(name) = virtual-target.remember-binding ;
actualize-location $(name) ;
scanner.install $(scanner) : $(name) $(__name__) ;
}
return $(name) ;
}
}
# private: (overridables)
# Sets up build actions for 'target'. Should call appriate rules
# and set target variables.
rule actualize-action ( target )
{
errors.error "method should be defined in derived classes" ;
}
# Sets up variables on 'target' which specify its location.
rule actualize-location ( target )
{
errors.error "method should be defined in derived classes" ;
}
# Return that actual target name that should be used
# (for the case where no scanner is involved)
rule actual-name ( )
{
errors.error "method should be defined in derived classes" ;
}
# implementation
rule actualize-no-scanner ( )
{
local name = [ actual-name ] ;
# Do anything only on the first invocation
if ! $(self.made.$(name)) {
self.made.$(name) = true ;
for local i in $(self.dependencies) {
DEPENDS $(name) : [ $(i).actualize ] ;
}
BINDRULE on $(name) = virtual-target.remember-binding ;
actualize-location $(name) ;
actualize-action $(name) ;
}
return $(name) ;
}
}
class virtual-target ;
# Target which correspond to a file. The exact mapping for file
# is not yet specified in this class. (TODO: Actually, the class name
# could be better...)
#
# May be a source file (when no action is specified), or
# derived file (otherwise).
#
# The target's grist is concatenation of project's location,
# properties of action (for derived files), and the value
# passed to 'extra-grist'.
#
rule abstract-file-target ( name
: type ? # Optional type for this target
: project
)
{
virtual-target.__init__ $(name) : $(project) ;
self.type = $(type) ;
self.action = ;
rule type ( ) { return $(self.type) ; }
rule set-type ( type )
{
self.type = $(type) ;
}
# Sets the suffix. When generating target name, it will be used in preference to
# the suffix that is associated with 'type'
rule suffix ( suffix ? )
{
if $(suffix)
{
self.suffix = $(suffix) ;
}
return $(self.suffix) ;
}
# Sets the path. When generating target name, it will override any path
# computation from properties.
rule set-path ( path )
{
self.path = $(path) ;
}
# If 'a' is supplied, sets action to 'a'.
# Returns the action currently set.
rule action ( a ? )
{
if $(a)
{
self.action = $(a) ;
}
return $(self.action) ;
}
# Sets/gets the 'root' flag. Target is root is it directly correspods to some
# variant of a main target.
rule root ( set ? )
{
if $(set)
{
self.root = true ;
}
return $(self.root) ;
}
# Gets 'use' requirements.
rule usage-requirements ( )
{
return $(self.usage-requirements) ;
}
# Sets 'use' requirements
rule set-usage-requirements ( requirements * )
{
self.usage-requirements = $(requirements) ;
}
# Sets the dependency graph this target is part of.
# 'dg' is an instance of 'subvariant-dg' class.
rule dg ( dg ? )
{
if $(dg)
{
self.dg = $(dg) ;
}
return $(self.dg) ;
}
# Specifies an extra grist to be added to the target name.
rule extra-grist ( g )
{
self.extra-grist = $(g) ;
}
rule actualize-action ( target )
{
if $(self.action)
{
# ### Not sure what this was used for
# if $(name:D)
# {
# errors.error
# "name for constructed target includes directory" ;
# }
$(self.action).actualize ;
common.Clean clean : $(name) ;
}
}
rule str ( )
{
local action = [ action ] ;
local filename = [ sequence.join $(self.name) "." $(self.type) ] ;
if $(action)
{
local sources = [ $(action).sources ] ;
local ss ;
for local s in $(sources)
{
ss += [ $(s).str ] ;
}
local name = [ $(action).action-name ] ;
return "{" $(name)-$(filename) $(ss) "}" ;
}
else
{
return "{" $(filename) "}" ;
}
}
rule less ( a )
{
if [ str ] < [ $(a).str ]
{
return true ;
}
}
rule equal ( a )
{
if [ str ] = [ $(a).str ]
{
return true ;
}
}
# private:
rule actual-name ( )
{
if ! $(self.actual-name)
{
local project-location = [ project.attribute $(self.project) location ] ;
local location-grist =
[ sequence.join [ regex.split $(project-location) "/" ] : "!" ] ;
local grist ;
local properties ;
if $(self.action)
{
properties = [ property.remove free incidental : [ $(self.action).properties ] ] ;
local property-grist = [ property.as-path $(properties) ] ;
grist = $(location-grist)/$(property-grist) ;
}
if ! $(grist)
{
grist = $(location-grist) ;
}
if $(self.path)
{
grist = $(grist)@$(self.path)@ ;
}
if $(self.extra-grist)
{
grist = $(grist)/$(self.extra-grist) ;
}
if $(self.suffix)
{
self.actual-name = [ sequence.join <$(grist)>$(self.name)
$(self.suffix) : "." ] ;
}
else if $(self.type)
{
self.actual-name = [ sequence.join <$(grist)>$(self.name)
[ type.generated-target-suffix $(self.type) :
$(properties)
] : "." ] ;
}
else
{
self.actual-name = <$(grist)>$(self.name) ;
}
}
return $(self.actual-name) ;
}
}
class abstract-file-target : virtual-target ;
# File target with explicitly known location.
#
# The file path is determined as
# - value passed to the 'set-path' method, if any
# - for derived files, project's build dir, joined with components
# that describe action's properties. If the free properties
# are not equal to the project's reference properties
# an element with name of main target is added.
# - for source files, project's source dir
#
# The file suffix is
# - the value passed to the 'suffix' method, if any, or
# - the suffix which correspond to the target's type.
#
rule file-target (
name
: type ? # Optional type for this target
: project
)
{
abstract-file-target.__init__ $(name) : $(type) : $(project) ;
rule actualize-location ( target )
{
compute-extra-path ;
if $(self.path)
{
LOCATE on $(target) = $(self.path) ;
# Make sure the path exists. Do this only
# for derived files.
if $(self.action)
{
DEPENDS $(target) : $(self.path) ;
common.MkDir $(self.path) ;
}
}
else if $(self.action)
{
# This is a derived file.
local path = [ path ] ;
LOCATE on $(target) = $(path) ;
# Make sure the path exists.
DEPENDS $(target) : $(path) ;
common.MkDir $(path) ;
}
else
{
# This is a source file.
SEARCH on $(target) =
[ path.native [ project.attribute $(self.project) source-location ] ] ;
}
}
rule compute-extra-path ( )
{
if $(self.action)
{
local properties = [ property.take free : [ property.remove incidental :
[ $(self.action).properties ] ] ] ;
local main-target = [ $(self.dg).main-target ] ;
local project = [ $(main-target).project ] ;
local plocation = [ project.attribute $(project) location ] ;
local ptarget = [ project.target $(plocation) ] ;
local ref = [ $(ptarget).reference-properties [ $(self.dg).properties ] ] ;
properties = [ sequence.insertion-sort
[ sequence.unique $(properties) ] ] ;
ref = [ sequence.insertion-sort
[ sequence.unique $(ref) ] ] ;
if $(properties) != $(ref)
{
self.extra-path = [ sequence.join main-target- [ $(main-target).name ] ] ;
}
}
}
# Returns the directory for this target
rule path ( )
{
if $(self.path)
{
return $(self.path) ;
}
else
{
compute-extra-path ;
local build-dir = [ project.attribute $(self.project) build-dir ] ;
if ! $(build-dir)
{
build-dir = [ project.attribute $(self.project) location ] ;
}
local path = [ path.join
$(build-dir)
bin
[ $(self.action).path ]
$(self.extra-path)
] ;
return [ path.native $(path) ] ;
}
}
}
class file-target : abstract-file-target ;
# Returns the binding for the given actual target.
# CONSIDER: not sure this rule belongs here.
rule binding ( target )
{
return $(.binding.$(target)) ;
}
rule remember-binding ( target : bound-path )
{
.binding.$(target) = $(bound-path) ;
}
# Class which represents an action.
# Both 'targets' and 'sources' should list instances of 'virtual-target'.
# Action name should name a rule with this prototype
# rule action-name ( targets + : sources * : properties * )
# Targets and sources are passed as actual jam targets. The rule may
# not establish dependency relationship, but should do everything else.
rule action ( targets + : sources * : action-name : properties * )
{
self.targets = $(targets) ;
self.sources = $(sources) ;
self.action-name = $(action-name) ;
self.properties = $(properties) ;
rule targets ( )
{
return $(self.targets) ;
}
rule sources ( )
{
return $(self.sources) ;
}
rule action-name ( )
{
return $(self.action-name) ;
}
rule properties ( )
{
return $(self.properties) ;
}
# Generates actual build instructions.
rule actualize ( )
{
if ! $(self.actualized)
{
self.actualized = true ;
local properties = [ adjust-properties [ properties ] ] ;
local actual-targets ;
for local i in [ targets ]
{
actual-targets += [ $(i).actualize ] ;
}
actualize-sources [ sources ] ;
DEPENDS $(actual-targets) : $(self.actual-sources) $(self.dependency-only-sources) ;
toolset.set-target-variables $(self.action-name) $(actual-targets[0]) :
$(properties) ;
$(self.action-name)
$(actual-targets) : $(self.actual-sources) : $(properties) ;
}
}
# Creates actual jam targets for sources. Initialized two member
# variables:.
# 'self.actual-sources' -- sources which are passed to updating action
# 'self.dependency-only-sources' -- sources which are made dependencies, but
# are not used otherwise.
#
# New values will be *appended* to the variables. They may be non-empty,
# if caller wants it.
rule actualize-sources ( sources * )
{
for local i in $(sources)
{
local scanner ;
if [ $(i).type ]
{
scanner =
[ type.get-scanner [ $(i).type ] : $(properties) ] ;
}
self.actual-sources += [ $(i).actualize $(scanner) ] ;
}
}
rule path ( )
{
local subvariant = [ property.remove free incidental : $(self.properties) ] ;
local pp = [ property.as-path $(subvariant) ] ;
return $(pp) ;
}
# Determined real properties when trying building with 'properties'.
# This is last chance to fix properties, for example to adjust includes
# to get generated headers correctly. Default implementation returns
# its argument.
rule adjust-properties ( properties * )
{
return $(properties) ;
}
rule set-targets ( targets * )
{
self.targets = $(targets) ;
}
}
class action ;
# Creates a virtual target with approariate name and type from 'file'.
# If a target with that name in that project was already created, returns that already
# created target.
# FIXME: more correct way would be to compute path to the file, based on name and source location
# for the project, and use that path to determine if the target was already created.
# TODO: passing project with all virtual targets starts to be annoying.
rule from-file ( file : project )
{
if $(.files.$(file).$(project))
{
return $(.files.$(file).$(project)) ;
}
else
{
local name = $(file:S=) ;
local type = [ type.type $(file:S) ] ;
local result ;
if ! $(type)
{
# warning "cannot determine type for file $(file)" ;
result = [ new file-target $(file) : : $(project) ] ;
}
else
{
local v = [ new file-target $(name) : $(type) : $(project) ] ;
$(v).suffix [ MATCH ^.(.*)$ : $(file:S) ] ;
result = $(v) ;
}
.files.$(file).$(project) = $(result) ;
return $(result) ;
}
}
# Registers a new virtual target. Checks if there's already registered target, with the same
# name, type, project and subvariant properties, and also with the same sources
# and equal action. If such target is found it is retured and 'target' is not registers.
# Otherwise, 'target' is registered and returned.
rule register ( target )
{
local signature = [ sequence.join
[ $(target).project ] [ $(target).name ] [ $(target).type ] : - ] ;
local result ;
for local t in $(.cache.$(signature))
{
local a1 = [ $(t).action ] ;
local a2 = [ $(target).action ] ;
if ! $(result)
{
if ! $(a1) && ! $(a2)
{
result = $(t) ;
}
else if $(a1) && $(a2) && [ $(a1).action-name ] = [ $(a2).action-name ]
&& [ $(a1).properties ] = [ $(a2).properties ] && [ $(a1).sources ] = [ $(a2).sources ]
{
result = $(t) ;
}
}
}
if ! $(result)
{
.cache.$(signature) += $(target) ;
result = $(target) ;
}
return $(result) ;
}
# Traverses the dependency graph of 'target' and return all targets that will
# be created before this one is created.
rule traverse ( target )
{
local result ;
if [ $(target).action ]
{
local action = [ $(target).action ] ;
# This includes 'target' as well
result += [ $(action).targets ] ;
for local t in [ $(action).sources ]
{
if ! [ $(t).root ]
{
result += [ traverse $(t) ] ;
}
}
}
return $(result) ;
}
# Clones a dependency graph template, given one of its root,
# and a new source target to instantinate the template with.
#
# If 'target's name is "%" and type is equal to 'new-source's
# return 'new-source', otherwise created a new target:
# - if there "%" in target's name, its replaced with 'new-target's
# - project for new target is the same as for 'new-target'
# - other attributes are copied
#
# If 'dont-recurse' is not set, clones action, which results in
# cloning of other targets, and, ultimately, cloning of the
# entire dependency graph.
rule clone-template ( target dont-recurse ? : new-source )
{
local name = [ $(new-source).name ] ;
local old-name = [ $(target).name ] ;
local new-name = $(old-name) ;
local m = [ MATCH (.*)(%)(.*) : $(old-name) ] ;
if $(m)
{
if [ $(target).action ]
{
new-name = [ sequence.join $(m[1]) $(name:D=) $(m[3]) ] ;
}
else
{
new-name = [ sequence.join $(m[1]) $(name) $(m[3]) ] ;
}
}
if $(old-name) = % && [ $(target).type ] = [ $(new-source).type ]
{
return $(new-source) ;
}
else
{
local cloned = [ new file-target $(new-name) : [ $(target).type ] :
[ $(new-source).project ] ] ;
if ! $(dont-recurse) && [ $(target).action ]
{
local cloned-action = [ clone-action-template
[ $(target).action ] $(target) $(cloned) : $(new-source) ] ;
cloned-targets = $(cloned) ;
for t in [ $(cloned-action).targets ]
{
if $(t) != $(target)
{
cloned-targets +=
[ clone-template $(t) dont-recurse : $(new-source) ] ;
}
}
local cloned-targets2 ;
for local t in $(cloned-targets)
{
$(t).action $(cloned-action) ;
cloned-targets2 += [ register $(t) ] ;
}
$(cloned-action).set-targets $(cloned-targets2) ;
cloned = $(cloned-targets2[1]) ;
}
else
{
cloned = [ register $(cloned) ] ;
}
return $(cloned) ;
}
}
# Clones an action template: helper for clone-template above.
local rule clone-action-template ( action from cloned-from : new-source )
{
local targets ;
local sources ;
for local t in [ $(action).sources ]
{
sources += [ clone-template $(t) : $(new-source) ] ;
}
local action-class = [ modules.peek $(action) : __class__ ] ;
local cloned = [ new $(action-class) [ $(action).targets ] : $(sources)
: [ $(action).action-name ] : [ $(action).properties ] ] ;
return $(cloned) ;
}
local rule subvariant-dg ( main-target # The instance of main-target class
: properties * # Properties requested for this target
: virtual-targets * )
{
self.main-target = $(main-target) ;
self.properties = $(properties) ;
self.virtual-targets = $(virtual-targets) ;
# Pre-compose the list of other dependency graphs, on which this one
# depends
for local t in $(virtual-targets)
{
for local d in [ $(t).dependencies ]
{
self.other-dg += [ $(d).dg ] ;
}
local a = [ $(t).action ] ;
if $(a)
{
for local s in [ $(a).sources ]
{
if [ $(s).root ]
{
self.other-dg += [ $(s).dg ] ;
}
}
}
}
self.other-dg = [ sequence.unique $(self.other-dg) ] ;
rule main-target ( )
{
return $(self.main-target) ;
}
rule properties ( )
{
return $(self.properties) ;
}
rule all-target-directories ( )
{
local result ;
for local t in $(self.virtual-targets)
{
result = [ sequence.merge $(result) : [ $(t).path ] ] ;
}
for local d in $(self.other-dg)
{
result += [ $(d).all-target-directories ] ;
}
return $(result) ;
}
}
class subvariant-dg ;