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/project.jam

#  Copyright (C) Vladimir Prus and Rene Rivera 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 project representation and loading.
#   Each project is represented by 
#   - a module where all the Jamfile content live. 
#   - an instance of 'project-attributes' class.
#     (given module name, can be obtained by 'attributes' rule)
#   - an instance of 'project-target' class (from targets.jam)
#     (given a module name, can be obtained by 'target' rule)
#
#  Typically, projects are created as result of loading Jamfile, which is
#  do by rules 'load' and 'initialize', below. First, module for Jamfile
#  is loaded and new project-attributes instance is created. Some rules
#  necessary for project are added to the module (see 'project-rules' module)
#  at the bottom of this file.
#  Default project attributes are set (inheriting attributes of parent project, if
#  it exists). After that, Jamfile is read. It can declare its own attributes,
#  via 'project' rule, which will be combined with already set attributes.
#
#
#  The 'project' rule can also declare project id, which will be associated with 
#  the project module.
#
#  There can also be 'standalone' projects. They are created by calling 'initialize'
#  on arbitrary module, and not specifying location. After the call, the module can
#  call 'project' rule, declare main target and behave as regular projects. However,
#  since it's not associated with any location, it's better declare only prebuilt 
#  targets.
#
#  The list of all loaded Jamfile is stored in variable .project-locations. It's possible
#  to obtain module name for a location using 'module-name' rule. The standalone projects
#  are not recorded, the only way to use them is by project id.


import modules : peek poke ;
import numbers ;
import path ;
import sequence ;
import errors : error ;
import project-roots ;

import print ;
import "class" : new ;
import errors ;
import assert ;
import property-set ;

#
#   Loads jamfile at the given location. After loading, project global
#   file and jamfile needed by the loaded one will be loaded recursively.
#   If the jamfile at that location is loaded already, does nothing.
#   Returns the project module for the Jamfile.
#
rule load ( jamfile-location )
{   
    # Load project root, first. It might decide to act as Jamfile.
    project-roots.load $(jamfile-location) ;
        
    local module-name = [ module-name $(jamfile-location) ] ;            
    # If Jamfile is already loaded, don't try again.
    if ! $(module-name) in $(.jamfile-modules)
    {      
        load-jamfile $(jamfile-location) ;
                
        for local p in [ attribute $(module-name) projects-to-build ]
        {
            load [ path.join $(jamfile-location) $(p) ] ;
        }
    }                
    return $(module-name) ;        
}

# Makes the specified 'module' act as if it were a regularly loaded Jamfile 
# at 'location'. If Jamfile is already located for that location, it's an 
# error. 
rule act-as-jamfile ( module : location )
{
    if [ module-name $(location) ] in $(.jamfile-modules)
    {
        errors.error "Jamfile was already loaded for '$(location)'" ;
    }
    # Set up non-default mapping from location to module.
    .module.$(location) = $(module) ;    
    
    # Add the location to the list of project locations
    # so that we don't try to load Jamfile in future
    .jamfile-modules += [ module-name $(location) ] ;
    
    initialize $(module) : $(location) ;
}


# Given 'name' which can be project-id or plain directory name,
# return project module corresponding to that id or directory.
# Returns nothing of project is not found.
rule find ( name : current-location )
{
    local project-module ;
    
    # Try interpreting name as project id.
    if [ path.is-rooted $(name) ]
    {            
        project-module =  $($(name).jamfile-module) ;
    }            
                
    if ! $(project-module)
    {            
        local location = [ path.root $(name) $(current-location) ] ;
        # If no project is registered for the given location, try to
        # load it. First see if we have Jamfile. If not we might have project
        # root, willing to act as Jamfile. In that case, project-root
        # must be placed in the directory referred by id.
        
        project-module = [ module-name $(location) ] ;
        if ! $(project-module) in $(.jamfile-modules) 
        {
            if [ find-jamfile $(location) ]
            {
                project-module = [ load $(location) ] ;            
            }        
            else
            {
                project-module = ;
            }
        }                    
    }
    
    return $(project-module) ;
}

#
# Returns the name of module corresponding to 'jamfile-location'.
# If no module corresponds to location yet, associates default
# module name with that location.
#
rule module-name ( jamfile-location )
{
    if ! $(.module.$(jamfile-location))
    {
        # Root the path, so that locations are always umbiguious.
        # Without this, we can't decide if '../../exe/program1' and '.'
        # are the same paths, or not.
        jamfile-location = [ path.root $(jamfile-location) [ path.pwd ] ] ;
        .module.$(jamfile-location) =  Jamfile<$(jamfile-location)> ;
    }
    return $(.module.$(jamfile-location)) ;
}

# Default patterns to search for the Jamfiles to use for build
# declarations.
#
JAMFILE = [ modules.peek : JAMFILE ] ;
JAMFILE ?= [Jj]amfile.v2 [Jj]amfile [Jj]amfile.jam ;

# Find the Jamfile at the given location. This returns the exact names of
# all the Jamfiles in the given directory. The optional parent-root argument
# causes this to search not the given directory but the ones above it up
# to the directory given in it.
#
local rule find-jamfile (
    dir # The directory(s) to look for a Jamfile.
    parent-root ? # Optional flag indicating to search for the parent Jamfile.
    )
{
    # Glob for all the possible Jamfiles according to the match pattern.
    #
    local jamfile-glob = ;
    if $(parent-root)
    {
        if ! $(.parent-jamfile.$(dir))
        {     
            .parent-jamfile.$(dir) = 
              [ path.glob-in-parents $(dir) : $(JAMFILE) : $(parent-root) ] ;            
        }        
        jamfile-glob = $(.parent-jamfile.$(dir)) ;                            
    }
    else
    {
        if ! $(.jamfile.$(dir))
        {            
            .jamfile.$(dir) = [ path.glob $(dir) : $(JAMFILE) ] ;         
        }   
        jamfile-glob = $(.jamfile.$(dir)) ;
        
    }

    return $(jamfile-glob) ;
}

# Load a Jamfile at the given directory. Returns nothing.
# Will attempt to load the file as indicated by the JAMFILE patterns. 
# Effect of calling this rule twice with the same 'dir' is underfined.

local rule load-jamfile (
    dir # The directory of the project Jamfile.
    )
{
    # See if the Jamfile is where it should be.
    #
    local jamfile-to-load = [ find-jamfile $(dir) ] ;

    # Could not find it, error.
    #
    if ! $(jamfile-to-load)
    {
        errors.error
            "Unable to load Jamfile." :
            "Could not find a Jamfile in directory '$(dir)'". : 
            "Attempted to find it with pattern '"$(JAMFILE:J=" ")"'." :
            "Please consult the documentation at 'http://www.boost.org'." ;
    }

    # Multiple Jamfiles found in the same place. Warn about this.
    # And ensure we use only one of them.
    # As a temporary convenience measure, if there's Jamfile.v2 amount
    # found files, suppress the warning and use it.
    #
    if $(jamfile-to-load[2-])
    {
        local v2-jamfiles = [ MATCH (.*[Jj]amfile\\.v2) : $(jamfile-to-load) ] ;

        if $(v2-jamfiles) && ! $(v2-jamfiles[2])
        {
            jamfile-to-load = $(v2-jamfiles) ;
        }        
        else
        {                    
            ECHO
              "WARNING: Found multiple Jamfiles at this '"$(dir)"' location!"
                "Loading the first one: '" [ path.basename $(jamfile-to-load[1]) ]  "'." ;
        }
                    
        jamfile-to-load = $(jamfile-to-load[1]) ;
    }
    
    # The module of the jamfile.
    #
    local jamfile-module = [ module-name  [ path.parent $(jamfile-to-load) ] ] ;

    # Initialize the jamfile module before loading.
    #
    initialize $(jamfile-module) : [ path.parent $(jamfile-to-load) ] ;

    # Now load the Jamfile in it's own context.
    # Initialization might have load parent Jamfiles, which might have
    # loaded the current Jamfile with use-project. Do a final check to make
    # sure it's not loaded already.
    if ! $(jamfile-module) in $(.jamfile-modules)
    {           
        .jamfile-modules += $(jamfile-module) ;
        modules.load $(jamfile-module) :  [ path.native $(jamfile-to-load) ] : . ;
    }
    
}

# Initialize the module for a project. 
#
rule initialize (
    module-name # The name of the project module.
    : location ? # The location (directory) of the project to initialize.
                 # If not specified, stanalone project will be initialized.               
    )
{
    # TODO: need to consider if standalone projects can do anything but defining
    # prebuilt targets. If so, we need to give more sensible "location", so that
    # source paths are correct.
    location ?= "" ;
    # Create the module for the Jamfile first.    
    module $(module-name)
    {          
    }    
    $(module-name).attributes = [ new project-attributes $(location) ] ;
    local attributes = $($(module-name).attributes) ;
    
    $(attributes).set source-location : $(location) : exact ;    
    $(attributes).set requirements : [ property-set.empty ] : exact ;
    $(attributes).set usage-requirements : [ property-set.empty ] : exact ;    

    # Import rules common to all project modules from project-rules module,
    # defined at the end of this file.
    modules.clone-rules project-rules $(module-name) ;

    # We search for parent/project-root only if jamfile was specified --- i.e
    # if the project is not standalone.
    if $(location)
    {       
        # Make sure we've loaded the project-root corresponding to this
        # Jamfile.
        #
        local project-root-module = [ project-roots.load $(location) ] ;
        local project-root = [ $(project-root-module).project-root get-location ] ;
        
        $(attributes).set project-root-module : $(project-root-module) : exact ;

        local parent = [ find-jamfile $(location) $(project-root) ] ;
        local parent-module = ;
        if $(parent)
        {
            parent-module = [ load [ path.parent $(parent[1]) ] ] ;
        }
        
        inherit-attributes $(module-name) : $(project-root-module) : $(parent-module) ;
    }    
}

# Make 'project-module' inherit attributes of project root and parent module.
rule inherit-attributes ( project-module : project-root-module : parent-module ? )
{
    # Register with the project root. This will inject project-root
    # constants and do some other initialization.
    $(project-root-module).project-root register-project $(project-module) ;

    if $(parent-module)
    {
        local attributes = $($(project-module).attributes) ;        
        local pattributes = [ attributes $(parent-module) ] ;
        $(attributes).set parent : [ path.parent 
            [ path.make [ modules.binding $(parent-module) ] ] ] ;
        $(attributes).set default-build 
            : [ $(pattributes).get default-build ] ;
        $(attributes).set requirements
            : [ $(pattributes).get requirements ] : exact ;
        $(attributes).set usage-requirements
            : [ $(pattributes).get usage-requirements ] : exact ;
        local parent-build-dir = [ $(pattributes).get build-dir ] ;
        if $(parent-build-dir)
        {            
            # Have to compute relative path from parent dir to our dir
            # Convert both paths to absolute, since we cannot
            # find relative path from ".." to "."
            
            local location = [ attribute $(project-module) location ] ;
            local parent-location = [ attribute $(parent-module) location ] ;
            
            local pwd = [ path.pwd ] ;
            local parent-dir = [ path.root $(parent-location) $(pwd) ] ;
            local our-dir = [ path.root $(location) $(pwd) ] ;
            $(attributes).set build-dir : [ path.join $(parent-build-dir) 
                  [ path.relative $(our-dir) $(parent-dir) ] ] : exact ;
        }        
    }            
}


# Associate the given id with the given project module
rule register-id ( id : module )
{
    $(id).jamfile-module = $(module) ;
}

# Class keeping all the attributes of a project.
#
# The standard attributes are "id", "location", "project-root", "parent"
# "requirements", "default-build", "source-location" and "projects-to-build".
class project-attributes 
{
    import property ;
    import property-set ;
    import errors ;
    import path ;
    import print ;
    import sequence ;
        
    rule __init__ ( location )
    {       
        self.location = $(location) ;
    }
            
    # Set the named attribute from the specification given by the user.
    # The value actually set may be different.
    rule set ( attribute : specification * 
        : exact ? # Sets value from 'specification' without any processing
        ) 
    {
        if $(exact)
        {
            self.$(attribute) = $(specification) ;
        }
        else if $(attribute) = "requirements" 
        {
            specification = [ property.translate-paths $(specification)
                              : $(self.location) ] ;
            specification = [ property.make $(specification) ] ;            
            result = [ property-set.create $(specification) ] ;            
            
            # If we have inherited properties, need to refine them with the
            # specified.
            local current = $(self.requirements) ;                                    
            if $(current)
            {
                result = [ $(current).refine $(result) ] ;
            }

            if $(result[1]) = "@error"
            {
                errors.error
                    "Requirements for project at '$(self.location)'"
                    "conflict with parent's." :
                    "Explanation: " $(result[2-]) ;
            }
            else
            {
                self.requirements = $(result) ;
            }
        }
        else if $(attribute) = "usage-requirements"
        {
            local unconditional ;
            for local p in $(specification)
            {
                local split = [ property.split-conditional $(p) ] ;
                split ?= nothing $(p) ;
                unconditional += $(split[2]) ;
            }
            
            local non-free = [ property.remove free : $(unconditional) ] ;
            if $(non-free)
            {
                errors.error "usage-requirements" $(specification) "have non-free properties" $(non-free) ;
            }            
            local t = [ property.translate-paths $(specification)
                                      : $(self.location) ] ;
            if $(self.usage-requirements)
            {
                self.usage-requirements = [ property-set.create 
                    [ $(self.usage-requirements).raw ] $(t) ] ;
            }
            else 
            {
                self.usage-requirements = [ property-set.create $(t) ] ;
            }                        
        }        
        else if $(attribute) = "default-build"
        {
            self.default-build = [ property.make $(specification) ] ;
        }        
        else if $(attribute) = "source-location"
        { 
            self.source-location = [ path.root $(specification) $(self.location) ] ;
        }            
        else if $(attribute) = "build-dir"
        {
            self.build-dir = [ path.root $(specification) $(self.location) ] ;
        }        
        else if ! $(attribute) in "id" "default-build" "location" "source-location"
          "parent" "projects-to-build"
        {
            errors.error "Invalid project attribute '$(attribute)' specified "
                               "for project at '$(self.location)'" ;
        }
        else
        {
            self.$(attribute) = $(specification) ;
        }
    }

    # Returns the value of the given attribute.
    rule get ( attribute )
    {
        return $(self.$(attribute)) ;
    }

    # Prints the project attributes.
    rule print ( )
    {
        local id = $(self.id) ; id ?= (none) ;
        local parent = $(self.parent) ; parent ?= (none) ;
        print.section "'"$(id)"'" ;
        print.list-start ;
        print.list-item "Parent project:" $(parent) ;
        print.list-item "Requirements:" [ $(self.requirements).raw ] ;
        print.list-item "Default build:" $(self.default-build) ;
        print.list-item "Source location:" $(self.source-location) ;
        print.list-item "Projects to build:" 
                            [ sequence.insertion-sort $(self.projects-to-build) ] ;
        print.list-end ;
    }
    
}

# Returns the project-attribute instance for the specified jamfile module.
rule attributes ( project )
{
    return $($(project).attributes) ;
}

# Returns the value of the specified attribute in the specified jamfile module.
rule attribute ( project attribute )
{
    return [ $($(project).attributes).get $(attribute) ] ;        
}

# Returns the project target corresponding to the 'project-module'.
rule target ( project-module )
{
    if ! $(.target.$(project-module))
    {
        .target.$(project-module) = [ new project-target $(project-module) 
          : $(project-module) 
           : [ attribute $(project-module) requirements ] ] ;
    }
    return $(.target.$(project-module)) ;    
}

# If 'path' is absolute, returns it.
# Oherwise, returns the location of 'project', joined
# with 'path'
rule path-relative-to-project-location ( path project )
{
    local project-location = [ attribute $(project) location ] ;
    return [ path.root $(path) $(project-location) ] ;
}


# Use/load a project.
rule use ( id : location )
{
    local project-module = [ project.load $(location) ] ;
    local declared-id = [ project.attribute $(project-module) id ] ;

    if ! $(declared-id)
    {
        error "project loaded by 'use-project' has no project-id." ;
    }
    if $(declared-id) != $(id)
    {
        error project \"$(declared-id)\" at \"$(location)\" redeclared with id \"$(id)\". ;
    }
}

# This module defines rules common to all projects
module project-rules
{
    rule project ( id ? : options * : * )
    {
        import project ;
        import path ;
                
        local attributes = [ project.attributes $(__name__) ] ;
        if $(id) 
        {
           id = [ path.root $(id) / ] ;
           project.register-id $(id) : $(__name__) ;
           $(attributes).set id : $(id) ;
        }

        for n in 2 3 4 5 6 7 8 9
        {
            local option = $($(n)) ;
            if $(option) 
            {
                $(attributes).set $(option[1]) : $(option[2-]) ;
            }
        }
    }

    rule use-project ( id : where )
    {
        import project ;
        import path ;
        local attributes = [ project.attributes $(__name__) ] ;
        project.use $(id) : [ path.root 
            [ path.make $(where) ] [ $(attributes).get location ] ] ;
    }

    rule build-project ( dir )
    {
        import project ;
        local attributes = [ project.attributes $(__name__) ] ;

        local now = [ $(attributes).get projects-to-build ] ;
        $(attributes).set projects-to-build : $(now) $(dir) ;
    }
    
    rule explicit ( target-names * )
    {
        import project ;
        local t = [ project.target $(__name__) ] ;
        for local n in $(target-names)
        {            
            $(t).mark-target-as-explicit $(n) ;
        }        
    }    
    
    rule glob ( wildcards + )
    {
        import path ;
        import project ;
        
        local location = [ project.attribute $(__name__) location ] ;
        local all-paths = [ path.glob $(location) : $(wildcards) ] ;
        return $(all-paths:D="") ;
    }        
}


local rule __test__ ( )
{
    import assert ;
    assert.result foo/bar : remove-trailing-slash foo/bar/ ;
    assert.result foo/bar : remove-trailing-slash foo/bar ;
    assert.result foo : remove-trailing-slash foo/ ;
    assert.result foo : remove-trailing-slash foo ;
}