blob: 70fd90cde526e784277911f9019dc1522694aad6 [file] [log] [blame]
# Copyright 2003 Dave Abrahams
# Copyright 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
import "class" : new ;
import feature ;
import path ;
import project ;
import property ;
import sequence ;
import set ;
import option ;
# Class for storing a set of properties.
#
# There is 1<->1 correspondence between identity and value. No two instances
# of the class are equal. To maintain this property, the 'property-set.create'
# rule should be used to create new instances. Instances are immutable.
#
# Each property is classified with regard to its effect on build results.
# Incidental properties have no effect on build results, from Boost.Build's
# point of view. Others are either free, or non-free and we refer to non-free
# ones as 'base'. Each property belongs to exactly one of those categories.
#
# It is possible to get a list of properties belonging to each category as
# well as a list of properties with a specific attribute.
#
# Several operations, like and refine and as-path are provided. They all use
# caching whenever possible.
#
class property-set
{
import errors ;
import feature ;
import path ;
import property ;
import property-set ;
import set ;
rule __init__ ( raw-properties * )
{
self.raw = $(raw-properties) ;
for local p in $(raw-properties)
{
if ! $(p:G)
{
errors.error "Invalid property: '$(p)'" ;
}
local att = [ feature.attributes $(p:G) ] ;
# A feature can be both incidental and free, in which case we add it
# to incidental.
if incidental in $(att)
{
self.incidental += $(p) ;
}
else if free in $(att)
{
self.free += $(p) ;
}
else
{
self.base += $(p) ;
}
if dependency in $(att)
{
self.dependency += $(p) ;
}
else
{
self.non-dependency += $(p) ;
}
if [ MATCH (:) : $(p:G=) ]
{
self.conditional += $(p) ;
}
else
{
self.non-conditional += $(p) ;
}
if propagated in $(att)
{
self.propagated += $(p) ;
}
if link-incompatible in $(att)
{
self.link-incompatible += $(p) ;
}
}
}
# Returns Jam list of stored properties.
#
rule raw ( )
{
return $(self.raw) ;
}
rule str ( )
{
return "[" $(self.raw) "]" ;
}
# Returns properties that are neither incidental nor free.
#
rule base ( )
{
return $(self.base) ;
}
# Returns free properties which are not incidental.
#
rule free ( )
{
return $(self.free) ;
}
# Returns dependency properties.
#
rule dependency ( )
{
return $(self.dependency) ;
}
rule non-dependency ( )
{
return $(self.non-dependency) ;
}
rule conditional ( )
{
return $(self.conditional) ;
}
rule non-conditional ( )
{
return $(self.non-conditional) ;
}
# Returns incidental properties.
#
rule incidental ( )
{
return $(self.incidental) ;
}
rule refine ( ps )
{
if ! $(self.refined.$(ps))
{
local r = [ property.refine $(self.raw) : [ $(ps).raw ] ] ;
if $(r[1]) != "@error"
{
self.refined.$(ps) = [ property-set.create $(r) ] ;
}
else
{
self.refined.$(ps) = $(r) ;
}
}
return $(self.refined.$(ps)) ;
}
rule expand ( )
{
if ! $(self.expanded)
{
self.expanded = [ property-set.create [ feature.expand $(self.raw) ] ] ;
}
return $(self.expanded) ;
}
rule expand-composites ( )
{
if ! $(self.composites)
{
self.composites = [ property-set.create
[ feature.expand-composites $(self.raw) ] ] ;
}
return $(self.composites) ;
}
rule evaluate-conditionals ( context ? )
{
context ?= $(__name__) ;
if ! $(self.evaluated.$(context))
{
self.evaluated.$(context) = [ property-set.create
[ property.evaluate-conditionals-in-context $(self.raw) : [ $(context).raw ] ] ] ;
}
return $(self.evaluated.$(context)) ;
}
rule propagated ( )
{
if ! $(self.propagated-ps)
{
self.propagated-ps = [ property-set.create $(self.propagated) ] ;
}
return $(self.propagated-ps) ;
}
rule link-incompatible ( )
{
if ! $(self.link-incompatible-ps)
{
self.link-incompatible-ps =
[ property-set.create $(self.link-incompatible) ] ;
}
return $(self.link-incompatible-ps) ;
}
rule run-actions ( )
{
if ! $(self.run)
{
self.run = [ property-set.create [ feature.run-actions $(self.raw) ] ] ;
}
return $(self.run) ;
}
rule add-defaults ( )
{
if ! $(self.defaults)
{
self.defaults = [ property-set.create
[ feature.add-defaults $(self.raw) ] ] ;
}
return $(self.defaults) ;
}
rule as-path ( )
{
if ! $(self.as-path)
{
self.as-path = [ property.as-path $(self.base) ] ;
}
return $(self.as-path) ;
}
# Computes the path to be used for a target with the given properties.
# Returns a list of
# - the computed path
# - if the path is relative to the build directory, a value of 'true'.
#
rule target-path ( )
{
if ! $(self.target-path)
{
# The <location> feature can be used to explicitly change the
# location of generated targets.
local l = [ get <location> ] ;
if $(l)
{
self.target-path = $(l) ;
}
else
{
local p = [ as-path ] ;
p = [ property-set.hash-maybe $(p) ] ;
# A real ugly hack. Boost regression test system requires
# specific target paths, and it seems that changing it to handle
# other directory layout is really hard. For that reason, we
# teach V2 to do the things regression system requires. The
# value of '<location-prefix>' is prepended to the path.
local prefix = [ get <location-prefix> ] ;
if $(prefix)
{
self.target-path = [ path.join $(prefix) $(p) ] ;
}
else
{
self.target-path = $(p) ;
}
if ! $(self.target-path)
{
self.target-path = . ;
}
# The path is relative to build dir.
self.target-path += true ;
}
}
return $(self.target-path) ;
}
rule add ( ps )
{
if ! $(self.added.$(ps))
{
self.added.$(ps) = [ property-set.create $(self.raw) [ $(ps).raw ] ] ;
}
return $(self.added.$(ps)) ;
}
rule add-raw ( properties * )
{
return [ add [ property-set.create $(properties) ] ] ;
}
rule link-incompatible-with ( ps )
{
if ! $(.li.$(ps))
{
local li1 = [ $(__name__).link-incompatible ] ;
local li2 = [ $(ps).link-incompatible ] ;
if [ set.equal $(li1) : $(li2) ]
{
.li.$(ps) = false ;
}
else
{
.li.$(ps) = true ;
}
}
if $(.li.$(ps)) = true
{
return true ;
}
else
{
return ;
}
}
# Returns all values of 'feature'.
#
rule get ( feature )
{
if ! $(self.map-built)
{
# For each feature, create a member var and assign all values to it.
# Since all regular member vars start with 'self', there will be no
# conflicts between names.
self.map-built = true ;
for local v in $(self.raw)
{
$(v:G) += $(v:G=) ;
}
}
return $($(feature)) ;
}
}
# Creates a new 'property-set' instance for the given raw properties or returns
# an already existing ones.
#
rule create ( raw-properties * )
{
raw-properties = [ sequence.unique
[ sequence.insertion-sort $(raw-properties) ] ] ;
local key = $(raw-properties:J=-:E=) ;
if ! $(.ps.$(key))
{
.ps.$(key) = [ new property-set $(raw-properties) ] ;
}
return $(.ps.$(key)) ;
}
NATIVE_RULE property-set : create ;
# Creates a new 'property-set' instance after checking that all properties are
# valid and converting incidental properties into gristed form.
#
rule create-with-validation ( raw-properties * )
{
property.validate $(raw-properties) ;
return [ create [ property.make $(raw-properties) ] ] ;
}
# Creates a property-set from the input given by the user, in the context of
# 'jamfile-module' at 'location'.
#
rule create-from-user-input ( raw-properties * : jamfile-module location )
{
local specification = [ property.translate-paths $(raw-properties)
: $(location) ] ;
specification = [ property.translate-indirect $(specification)
: $(jamfile-module) ] ;
local project-id = [ project.attribute $(jamfile-module) id ] ;
project-id ?= [ path.root $(location) [ path.pwd ] ] ;
specification = [ property.translate-dependencies
$(specification) : $(project-id) : $(location) ] ;
specification =
[ property.expand-subfeatures-in-conditions $(specification) ] ;
specification = [ property.make $(specification) ] ;
return [ property-set.create $(specification) ] ;
}
# Refines requirements with requirements provided by the user. Specially handles
# "-<property>value" syntax in specification to remove given requirements.
# - parent-requirements -- property-set object with requirements to refine.
# - specification -- string list of requirements provided by the user.
# - project-module -- module to which context indirect features will be
# bound.
# - location -- path to which path features are relative.
#
rule refine-from-user-input ( parent-requirements : specification * :
project-module : location )
{
if ! $(specification)
{
return $(parent-requirements) ;
}
else
{
local add-requirements ;
local remove-requirements ;
for local r in $(specification)
{
local m = [ MATCH "^-(.*)" : $(r) ] ;
if $(m)
{
remove-requirements += $(m) ;
}
else
{
add-requirements += $(r) ;
}
}
if $(remove-requirements)
{
# Need to create a property set, so that path features and indirect
# features are translated just like they are in project
# requirements.
local ps = [ property-set.create-from-user-input
$(remove-requirements) : $(project-module) $(location) ] ;
parent-requirements = [ property-set.create
[ set.difference [ $(parent-requirements).raw ]
: [ $(ps).raw ] ] ] ;
specification = $(add-requirements) ;
}
local requirements = [ property-set.create-from-user-input
$(specification) : $(project-module) $(location) ] ;
return [ $(parent-requirements).refine $(requirements) ] ;
}
}
# Returns a property-set with an empty set of properties.
#
rule empty ( )
{
if ! $(.empty)
{
.empty = [ create ] ;
}
return $(.empty) ;
}
if [ option.get hash : : yes ] = yes
{
rule hash-maybe ( path ? )
{
path ?= "" ;
return [ MD5 $(path) ] ;
}
}
else
{
rule hash-maybe ( path ? )
{
return $(path) ;
}
}