Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is the documentation for an old version of boost. Click here for the latest Boost documentation.

tools/build/v2/build/property.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.

import utility : ungrist ;
import sequence : unique ;
import errors : error ;
import feature ;
import regex ;
import sequence ;
import set ;
import path ;

# Refines 'properties' by overriding any non-free properties 
# for which a different value is specified in 'requirements'. 
# Conditional requirements are just added without modification.
# Returns the resulting list of properties.
rule refine ( properties * : requirements * )
{
    local result ;
    local error ;
    
    # All the elements of requirements should be present in the result
    # Record them so that we can handle 'properties'.
    for local r in $(requirements)
    {
        # Don't consider conditional requirements.
        if ! [ MATCH (:) : $(r:G=) ]
        {            
            # Note: cannot use local here, so take an ugly name
            __require__$(r:G) = $(r:G=) ;
        }        
    }

    for local p in $(properties)
    {       
        # No processing for free properties
        if [ MATCH (:) : $(p:G=) ]
        {
            # Skip conditional properties
            result += $(p) ;
        }        
        else if free in [ feature.attributes $(p:G) ]
        {
            result += $(p) ;
        }
        else
        {
            local required-value = $(__require__$(p:G)) ;
            if $(required-value)
            {
                local value = $(p:G=) ;
                if $(value) != $(required-value)
                {
                    result += $(p:G)$(required-value) ;
                }
                else
                {
                    result += $(p) ;
                }
            }
            else
            {
                result += $(p) ;
            }
        }
    }

    # Unset our ugly map.        
    for local r in $(requirements)
    {
         __require__$(r:G) = ;
    }
    
    if $(error)
    {
        return $(error) ;
    }
    else
    {
        return [ unique $(result) $(requirements) ] ;
    }
}

# Removes all conditional properties which conditions are not met
# For those with met conditions, removes the condition. Properies
# in conditions are looked up in 'context'
rule evaluate-conditionals-in-context ( properties * : context * )
{
    local base ;
    local conditionals ;
    for local p in $(properties)
    {
        if [ MATCH (:<) : $(p) ]
        {
            conditionals += $(p) ;
        }
        else
        {
            base += $(p) ;
        }        
    }

    local result = $(base) ;
    for local p in $(conditionals)
    {
        # Separate condition and property
        local s = [ MATCH ([^:]*):(.*) : $(p) ] ;
        # Split condition into individual properties
        local c = [ regex.split $(s[1]) "," ] ;
        # Evaluate condition
        if $(c) in $(context)
        {
            result += $(s[2]) ;
        }                
    }
    return $(result) ;    
}

rule evaluate-conditionals ( properties * )
{
    return [ evaluate-conditionals-in-context $(properties) : $(properties) ] ;
}



# Helper for as-path, below. Orders properties with the implicit ones
# first, and within the two sections in alphabetical order of feature
# name.
local rule path-order ( x y )
{
    if $(y:G) && ! $(x:G)
    {
        return true ;
    }
    else if $(x:G) && ! $(y:G)
    {
        return ;
    }
    else
    {
        if ! $(x:G)
        {
            x = [ feature.expand-subfeatures $(x) ] ;
            y = [ feature.expand-subfeatures $(y) ] ;
        }
        
        if $(x[1]) < $(y[1])
        {
            return true ;
        }
    }
}

# Returns a path which represents the given expanded property set.
rule as-path ( properties * )
{
    local entry = .result.$(properties:J=-) ;
    
    if ! $($(entry))
    {
        # trim redundancy
        properties = [ feature.minimize $(properties) ] ;
    
        # sort according to path-order
        properties = [ sequence.insertion-sort $(properties) : path-order ] ;
    
        local components ;
        for local p in $(properties)
        {
            if $(p:G)
            {
                local f = [ ungrist $(p:G) ] ;
                components += $(f)-$(p:G=) ;
            }
            else
            {
                components += $(p) ;
            }
        }
        
        $(entry) = $(components:J=/) ;
    }    
    
    return $($(entry)) ;
}

# Exit with error if property is not valid.
local rule validate1 ( property )
{
    local msg ;
    if $(property:G)
    {
        local feature = $(property:G) ;
        local value = $(property:G=) ;

        if ! [ feature.valid $(feature) ]
        {
            feature = [ ungrist $(property:G) ] ; # Ungrist for better error messages
            msg = "unknown feature '$(feature)'" ;
        }
        else if $(value) && ! free in [ feature.attributes $(feature) ] 
        {
            feature.validate-value-string $(feature) $(value) ;
        } 
        else if ! $(value)
        {
            feature = [ ungrist $(property:G) ] ; # Ungrist for better error messages
            msg = "No value specified for feature '$(feature)'" ; 
        }
    }
    else
    {
        local feature = [ feature.implied-feature $(property) ] ;
        feature.validate-value-string $(feature) $(property) ;
    }
    if $(msg) 
    {
        error "Invalid property "'$(property:J=" ")'": "$(msg:J=" "). ;
    }
}

rule validate ( properties * )
{
    for local p in $(properties)
    {
        validate1 $(p) ;
    }
}

rule validate-property-sets ( property-sets * )
{
    for local s in $(property-sets)
    {
        validate [ feature.split $(s) ] ;
    }
}

# Makes a property set from 'specification', converting implicit values into 
# full properties.
rule make ( specification * )
{
    local result ;
    for local e in $(specification) 
    {
        if $(e:G)
        {
            result += $(e) ;
        }
        else if [ feature.is-implicit-value $(e) ]
        {
            local feature = [ feature.implied-feature $(e) ] ;
            result += $(feature)$(e) ;      
        }
        else
        {
            error "'$(e)' is not a valid for property specification" ;
        }
    }
    return $(result) ;
}

# Returns a property sets which include all the elements in 'properties' that
# do not have attributes listed in 'attributes'. 
rule remove ( attributes + : properties * )
{
    local result ;
    for local e in $(properties)
    {
        if ! [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ]
        {
            result += $(e) ;
        }
    }
    return $(result) ;
}

# Returns a property set which include all properties in 'properties' that have
# any of 'attributes'.
rule take ( attributes + : properties * )
{
    local result ;
    for local e in $(properties)
    {
        if [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ]
        {
            result += $(e) ;
        }
    }
    return $(result) ;
}

# Selects properties which correspond to any of the given features.
rule select ( features * : properties * )
{
    local result ;
    
    # add any missing angle brackets
    local empty = "" ;
    features = $(empty:G=$(features)) ;
    
    for local p in $(properties)
    {
        if $(p:G) in $(features)
        {
            result += $(p) ;
        }
    }
    return $(result) ;
}

# Returns a modified version of properties with all values of the
# given feature replaced by the given value.
rule change ( properties * : feature value )
{
    local result ; 
    for local p in $(properties)
    {
        if $(p:G) = $(feature)
        {
            result += $(value:G=$(feature)) ;            
        }
        else
        {
            result += $(p) ;
        }
    }
    return $(result) ;
}

# If 'property' is conditional property, returns
# condition and the property, e.g
# <variant>debug,<toolset>gcc:<inlining>full will become
# <variant>debug,<toolset>gcc <inlining>full.
# Otherwise, returns empty string.
rule split-conditional ( property )
{
    local m = [ MATCH "(.+):<(.+)" : $(property) ] ;
    if $(m)
    {
        return $(m[1]) <$(m[2]) ;
    }    
}


# Interpret all path properties in 'properties' as relative to 'path'
# The property values are assumed to be in system-specific form, and
# will be translated into normalized form.
rule translate-paths ( properties * : path )
{
    local result ;
    for local p in $(properties)
    {
        local split = [ split-conditional $(p) ] ;
        local condition = "" ; 
        if $(split)
        {
            condition = $(split[1]): ;
            p = $(split[2]) ;
        }

        # need to do this here to get reasonable error messages for
        # unrecognized implicit features.
        validate $(p) ;   
        
        if path in [ feature.attributes $(p:G) ] 
        {
            local t = [ path.root [ path.make $(p:TG=) ] $(path) ] ;
            result += $(condition)$(t:TG=$(p:G)) ;
        }
        else
        {
            result += $(condition)$(p) ;
        }        
    }
    return $(result) ;
}

# Class which maintains a property set -> string
# mapping
class property-map
{
    import numbers ;
    import sequence ;
    import errors : error ;
    
    rule __init__ ( )
    {        
        self.next-flag = 1 ;
    }
    
    # Associate 'value' with 'properties'
    rule insert ( properties + : value )
    {
        self.all-flags += $(self.next-flag) ;
        self.properties.$(self.next-flag) = $(properties) ;
        self.value.$(self.next-flag) = $(value) ;

        self.next-flag = [ numbers.increment $(self.next-flag) ] ;
    }

    # Return the value associated with 'properties'
    # or any subset of it. If more than one
    # subset has value assigned to it, return the
    # value for the longest subset, if it's unique.
    rule find ( properties + )
    {
        return [ find-replace $(properties) ] ;
    }
    
    # Find the value associated with 'properties'.
    # If 'value' parameter is given, replaces the found value
    # Returns the value that were stored originally.
    rule find-replace ( properties + : value ? )
    {
        # First find all matches
        local matches ;
        local match-ranks ;
        for local i in $(self.all-flags)
        {
            if $(self.properties.$(i)) in $(properties)
            {
                matches += $(i) ;
                match-ranks += [ sequence.length 
                    $(self.properties.$(i)) ] ;
            }
        }
        local best = [ sequence.select-highest-ranked 
            $(matches) : $(match-ranks) ] ;
        if $(best[2])
        {
            error "Ambiguous key" ;
        }        
        local original = $(self.value.$(best)) ;
        if $(value)
        {
            self.value.$(best) = $(value) ;
        }        
        return $(original) ;
    }      
}

local rule __test__ ( )
{
    import errors : try catch ;
    import feature ;
    import feature : feature subfeature compose ;
    
    # local rules must be explicitly re-imported
    import property : path-order ;
    
    feature.prepare-test property-test-temp ;

    feature toolset : gcc : implicit symmetric ;
    subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
      3.0 3.0.1 3.0.2 : optional ;
    feature define : : free ;
    feature runtime-link : dynamic static : symmetric link-incompatible ;
    feature optimization : on off ;
    feature variant : debug release : implicit composite symmetric ;
    feature rtti : on off : link-incompatible ;

    compose <variant>debug : <define>_DEBUG <optimization>off ;
    compose <variant>release : <define>NDEBUG <optimization>on ;

    import assert ;
    import "class" : new ;
    
    validate <toolset>gcc  <toolset>gcc-3.0.1 : $(test-space) ;
    
    assert.true path-order $(test-space) debug <define>foo ;
    assert.false path-order $(test-space) <define>foo debug ;
    assert.true path-order $(test-space) gcc debug ;
    assert.false path-order $(test-space) debug gcc ;
    assert.true path-order $(test-space) <optimization>on <rtti>on ;
    assert.false path-order $(test-space) <rtti>on <optimization>on ;
      
    assert.result <toolset>gcc <rtti>off <define>FOO
        : refine <toolset>gcc <rtti>off
        : <define>FOO
        : $(test-space)
        ;

    assert.result <toolset>gcc <optimization>on
        : refine <toolset>gcc <optimization>off
        : <optimization>on
        : $(test-space)
        ;

    assert.result <toolset>gcc <rtti>off
        : refine <toolset>gcc : <rtti>off : $(test-space)
        ;

    assert.result <toolset>gcc <rtti>off <rtti>off:<define>FOO
        : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO 
        : $(test-space)
        ;
    
    assert.result <toolset>gcc:<define>foo <toolset>gcc:<define>bar 
        : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar 
        : $(test-space)
        ;

    assert.result <toolset>gcc <variant>release <rtti>off <define>MY_RELEASE
        : evaluate-conditionals <toolset>gcc <variant>release <rtti>off
          <variant>release,<rtti>off:<define>MY_RELEASE       
        ;

    assert.result debug
      : as-path <optimization>off <variant>debug
      : $(test-space)
      ;

    assert.result gcc/debug/rtti-off
      : as-path <toolset>gcc <optimization>off <rtti>off <variant>debug
      : $(test-space)
      ;
        
    try ;
        validate <feature>value : $(test-space) ;
    catch "Invalid property '<feature>value': unknown feature 'feature'." ;

    try ;
        validate <rtti>default : $(test-space) ;
    catch \"default\" is not a known value of feature <rtti> ;
    
    validate <define>WHATEVER : $(test-space) ;

    try ;
        validate <rtti> : $(test-space) ;
    catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;

    try ;
        validate value : $(test-space) ;
    catch "value" is not a value of an implicit feature ;
           

    assert.result <rtti>on 
        : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;

    assert.result <include>a 
        : select include : <include>a <toolset>gcc ;

    assert.result <include>a 
        : select include bar : <include>a <toolset>gcc ;

    assert.result <include>a <toolset>gcc
        : select include <bar> <toolset> : <include>a <toolset>gcc ;
    
    assert.result <toolset>kylix <include>a 
        : change <toolset>gcc <include>a : <toolset> kylix ;

    pm = [ new property-map ] ;
    $(pm).insert <toolset>gcc : o ;
    $(pm).insert <toolset>gcc <os>NT : obj ;
    $(pm).insert <toolset>gcc <os>CYGWIN : obj ;

    assert.equal o
      : [ $(pm).find <toolset>gcc ] ;

    assert.equal obj
      : [ $(pm).find <toolset>gcc <os>NT ] ;

    try ;
        $(pm).find <toolset>gcc <os>NT <os>CYGWIN ;
    catch "Ambiguous key" ;

    # Test ordinary properties 
    assert.result 
      : split-conditional <toolset>gcc 
      ;
    
    # Test properties with ":"
    assert.result
      : split-conditional <define>FOO=A::B
      ;
    
    # Test conditional feature
    assert.result <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO
      : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO
      ;
    
    feature.finish-test property-test-temp ;
}