# 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. # Each subproject is represented by a module with name Jamfile. # The module interface is: # # rule location ( ) # rule id ( ) # rule project-root ( ) # rule parent ( ) # rule requirements ( ) # rule default-build ( ) # rule source-location ( ) # rule target ( ) -- returns the 'project-target' for this project. # rule subprojects ( ) # # Targets defined in Jamfile, as well as target representing the entire # Jamfile will be available using facilities in the 'targets' module. # import modules : peek poke ; import numbers ; import os.path ; import sequence ; import targets ; import errors : error ; import project-root ; import print ; # # Loads jamfile at the given location. After loading, project global # file and jamfile needed by the loaded one will be loaded recursively. # rule load ( jamfile-location ) { local loaded = ; local module-name = [ load-jamfile $(jamfile-location) loaded ] ; if $(loaded) { .projects += $(jamfile-location) ; for local subproject in [ $(module-name).subprojects ] { load [ os.path.join $(jamfile-location) $(subproject) ] ; } } return $(module-name) ; } # # Returns the project location given its id. # Projects can be referred using path@project-id notation. In it, 'path' # selects jamfile location relatively to 'current-location' and 'project-id' # names project relatively to the selected jamfile. # Rooted 'project-id' is possible: # "@/boost" will refer to the top-level project called "boost". # rule lookup ( id : current-location ) { local split = [ MATCH (.*)@(.*) : $(id) ] ; local location = $(split[1]) ; # A jam quirk: if there's no part before "@", 'location' will be empty # string, and ?= won't change it. if $(location) { location = [ os.path.root $(location) $(current-location) ] ; } else { location = $(current-location) ; } local project-id = $(split[2]) ; if [ os.path.is-rooted $(project-id) ] { return $($(project-id).jamfile-location) ; } else { if ! $(location) { error Jamfile location must be specified for relative project-id $(id) ; } if $(location) in $(.projects) { local module-name = [ module-name $(location) ] ; if ! $(project-id) { return [ $(module-name).location ] ; } else { local base-id = [ $(module-name).id ] ; if ! $(base-id) { error "Project in $(location) has no project id" ; } else { local rooted-id = $(base-id)/$(project-id) ; return $($(rooted-id).jamfile-location) ; } } } } } # Helper for 'find-target' local rule remove-trailing-slash ( string ) { if [ MATCH (.*/) : $(string) ] { return [ MATCH (.*)/ : $(string) ] ; } else { return $(string) ; } } # Given an 'id' for a target, return an instance of 'main-target' that # corresponds to it. If there's no such main-target, returns empty string. # The project referred to by id is loaded if it is not already loaded. rule find-target ( id : current-location ) { # Find the project first local project-id ; local target-id ; local explicit ; if [ MATCH (.*)@(.*) : $(id) ] { explicit = 1 ; # Take the last "/" separated component after "@" as target id. local split = [ MATCH (.*@(.*/)*)([^/]*) : $(id) ] ; project-id = [ remove-trailing-slash $(split[1]) ] ; target-id = $(split[3]) ; } else { # This is not @-id. Treat it as path -- the last "/" separated component # is target id, everything else denote project location. local split = [ MATCH ((.*/)*)([^/]*) : $(id) ] ; if $(split[1]) { project-id = [ remove-trailing-slash $(split[1]) ] ; project-id = $(project-id)@ ; } else { project-id = @ ; } target-id = $(split[3]) ; } local location = [ lookup $(project-id) : $(current-location) ] ; if ! $(location) { # Try to load the project at the specified location location = [ MATCH (.*)@(.*) : $(project-id) ] ; ECHO "XXX: looking if there's a jamfile at" $(location[1]) ; if [ find-jamfile $(location[1]) ] { ECHO "XXXX: found" ; load $(location[1]) ; # If there's proeject-id relative to the 'location' the # jamfile at 'location' should made those available somehow. location = [ lookup $(project-id) : $(current-location) ] ; ECHO "XXXXX: $(location)" ; } else { location = ; } } if $(location) { local project-module = [ module-name $(location) ] ; local project-target = [ $(project-module).target ] ; if [ $(project-target).has-main-target $(target-id) ] { return [ $(project-target).main-target $(target-id) ] ; } } else if $(explicit) { print.wrapped-text "The target id" $(id) " specified by project at" $(current-location) "is invalid" ; EXIT ; } } rule project ( id ? : option1 * : option2 * : option3 * ) { local caller = [ CALLER_MODULE ] ; if $(id) { id = [ os.path.root $(id) / ] ; $(id).jamfile-location = [ $(caller).location ] ; poke $(caller) : __id__ : $(id) ; } if $(option1) { assign-option [ CALLER_MODULE ] : $(option1) ; } if $(option2) { assign-option [ CALLER_MODULE ] : $(option2) ; } if $(option3) { assign-option [ CALLER_MODULE ] : $(option3) ; } } rule assign-option ( module : option + ) { local first = $(option[1]) ; local tail = $(option[2-]) ; switch $(first) { case "requirements" : local inherited = [ peek $(module) : __requirements__ ] ; local specified = $(tail) ; local result = [ property.refine $(inherited) : $(specified) ] ; if $(result[1]) = "@error" { local location = [ $(module).location ] ; print.wrapped-text "Requirements for project at '$(location)'" "conflict with parent's." ; print.wrapped-text "Explanation: " $(result[2-]) ; EXIT ; } else { poke $(module) : __requirements__ : $(result) ; } case "default-build" : poke $(module) : __default-build__ : $(tail) ; case "source-location" : poke $(module) : __source-location__ : [ os.path.join [ $(module).location ] $(tail) ] ; case * : print.wrapped-text "Invalid project option '$(first)' specified " "for project '$(module)'" ; EXIT ; } } # # Returns the name of module corresponding to 'jamfile-location'. # rule module-name ( jamfile-location ) { return Jamfile<$(jamfile-location)> ; } # Default patterns to search for the Jamfiles to use for build # declarations. # JAMFILE = [ modules.peek : JAMFILE ] ; JAMFILE ?= [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) { jamfile-glob = [ os.path.glob-in-parents $(dir) : $(JAMFILE) : $(parent-root) ] ; } else { jamfile-glob = [ os.path.glob $(dir) : $(JAMFILE) ] ; } return $(jamfile-glob) ; } # Load a Jamfile at the given directory. Will attempt to load # the file as indicated by the JAMFILE patterns. We return the # module for the Jamfile. # local rule load-jamfile ( dir # The directory of the project Jamfile. loaded-var ? # Name of variable to indicated we loaded the 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) { EXIT "Unable to load Jamfile. Could not find a Jamfile in" "this directory:" $(dir)"." "Attempted to find it with" "this pattern:" $(JAMFILE)"." "Please consult the documentation at 'http://www.boost.org'." ; } # The module of the jamfile. # local jamfile-module = [ module-name [ os.path.parent $(jamfile-to-load[1]) ] ] ; # Don't even bother with the rest if we know the file is already loaded. # if ! [ modules.binding $(jamfile-module) ] { # Multiple Jamfiles found in the same place. Warn about this. # And ensure we use only one of them. # if $(jamfile-to-load[2-]) { ECHO "WARNING: Found multiple Jamfiles at this '"$(dir)"' location!" "Loading the first one: '" [ os.path.basename $(jamfile-to-load[1]) ] "'." ; } jamfile-to-load = $(jamfile-to-load[1]) ; # Initialize the jamfile module before loading. # initialize $(jamfile-module) : $(jamfile-to-load) ; # Setup, by coordinating with project-root. # local project-root-module = [ $(jamfile-module).project-root-module ] ; $(project-root-module).register-project $(jamfile-module) ; # Now load the Jamfile in it's own context. # modules.load $(jamfile-module) : [ os.path.native $(jamfile-to-load) ] : . ; # Indicate we loaded the Jamfile. # if $(loaded-var) { $(loaded-var) = true ; } } # Return the Jamfile's filename/module. # return $(jamfile-module) ; } # Initialize the module for a Jamfile. # local rule initialize ( module-name # The name of the Jamfile module. : jamfile # The location (binding) of the jamfile for the project to initialize. ) { # Make sure we've loaded the project-root corresponding to this # Jamfile. # local project-root-module = [ project-root.load [ os.path.parent $(jamfile) ] ] ; local project-root = [ $(project-root-module).location ] ; local parent = [ find-jamfile [ os.path.parent $(jamfile) ] $(project-root) ] ; local parent-module = ; if $(parent) { parent-module = [ load [ os.path.parent $(parent[1]) ] ] ; } module $(module-name) { import project : project ; } # Import rules common to all project modules from project-rules module, # define at the end of this file. modules.clone-rules project-rules $(module-name) ; modules.poke $(module-name) : __jamfile-location__ : $(jamfile-location) ; modules.poke $(module-name) : __source-location__ : $(jamfile-location) ; modules.poke $(module-name) : __project-root__ : $(project-root) ; modules.poke $(module-name) : __project-root-module__ : $(project-root-module) ; if $(parent-module) { modules.poke $(module-name) : __parent__ : [ os.path.parent $(parent) ] ; modules.poke $(module-name) : __default-build__ : [ $(parent-module).default-build ] ; modules.poke $(module-name) : __requirements__ : [ $(parent-module).requirements ] ; } else { modules.poke $(module-name) : __default-build__ : debug ; } } # This module defines rules common to all projects module project-rules { rule location ( ) { return $(__jamfile-location__) ; } rule id ( ) { return $(__id__) ; } rule project-root ( ) { return $(__project-root__) ; } rule project-root-module ( ) { return $(__project-root-module__) ; } rule parent ( ) { return $(__parent__) ; } rule requirements ( ) { return $(__requirements__) ; } rule default-build ( ) { return $(__default-build__) ; } rule source-location ( ) { return $(__source-location__) ; } rule subproject ( jamfile-location ) { __subprojects__ += $(jamfile-location) ; } rule subprojects ( ) { return $(__subprojects__) ; } rule target ( ) { # FIXME: this should be done better! import class : new ; if ! $(__target__) { __target__ = [ new project-target $(__name__) : $(__name__) ] ; } return $(__target__) ; } rule print ( ) { import sequence ; import print ; local id = [ id ] ; id ?= (none) ; local parent = [ parent ] ; parent ?= (none) ; print.section "'"$(id)"'" ; print.list-start ; print.list-item "Project root:" [ project-root ] ; print.list-item "Parent project:" $(parent) ; print.list-item "Requirements:" [ requirements ] ; print.list-item "Default build:" [ default-build ] ; print.list-item "Source location:" [ source-location ] ; print.list-item "Subprojects:" [ sequence.insertion-sort [ subprojects ] ] ; print.list-end ; } rule use-project ( id : where ) { local used-location = [ os.path.root $(where) [ location ] ] ; local project-module = [ project.load $(used-location) ] ; local declared-id = [ $(project-module).id ] ; # CONSIDER: Should move this import somewhere? import errors : error ; #errors.push-context "error: in 'use-project' rule at" [ errors.call-site ] ; if ! $(declared-id) { error "project loaded by 'use-project' has no project-id." ; } if $(declared-id) != $(id) { error "project-id of a project differs from passed to 'use-project'" ; } #errors.pop-context ; } }