# (C) Copyright David Abrahams and Carlos Pinto Coelho 2001. 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. # # Jamrules file by David Abrahams (abrahams@mediaone.net) and Carlos Pinto # Coelho (cfspc@altrabroadband.com). # Notes on the design of the system: # # This system is designed to support building the "same" targets with multiple # build tool suites (e.g. msvc, gcc,...) and build variants (e.g. debug, # release...). Although it currently varies across two dimensions, it should # trivially support extension to three or more, e.g. in case of cross-platform # builds. The word "same" is written in quotes above because internally, # separate targets are generated for each unique toolset/build-variant # combination. # # Specifics of build tool suites are specified in files with names of the form # "-tools.jam", where is the name used to identify the tool suite. # Workarounds for Jam limitations: # # 1. Jam supports something like the setting of attributes on targets using the # syntax: # on = # # This facility is used to control build actions for individual targets # (target-specific variables take precedence over global ones when build # actions # are executed). An obvious approach would be to use target # attributes to hold # properties of build tools (e.g. where to find the # standard includes). # Unfortunately, although you can write target # attributes, there is no way to # read them. Instead, we take advantage of # two properties of Jam: # # a. A variable name can be formed by evaluating an expression. For example, # the following rule, appends FOOBAR to its first argument to form the # name of a new variable, which is given the value "baz" # # rule X { $(1)FOOBAR = baz ; } # # b. Any character is allowed in a variable name. So, although they are # actually global variables, we can form names like .c++-flags thus: # # C++FLAGS = $($(1).c++-flags) # Get the c++-flags "attribute" from $(1) # # 2. There is no way to call a rule based on the value of a variable # other than by using a switch statement. Because that approach requires # intrusive changes to rules when the system is extended, we have avoided # it. Instead, we have taken advantage of two "features" of Jam: # # a. The name of a file to be included can be computed from an # expression. For example, the following includes a file whose name is # formed by concatenating $(1) and "-tools.jam": # # include $(1)-tools.jam # # b. A rule can be redefined any number of times. Its latest definition is # the one invoked. For example, the following prints "#2". # # rule X { ECHO #1 ; } # rule X { ECHO #2 ; } # X ; # # Together, these facts allow us to select tool-suite-specific actions for # building specific target types by repeatedly redefining the generalized # build actions in the various -tools.jam files if $(NT) { TOOLS ?= borland gcc metrowerks msvc ; } else { TOOLS ?= gcc ; } # detect-build-tools : # # Declares a pseudotarget for the specified build tools which is built by # the given . # # Although not currently implemented, the plan is to make compilation of # tool-specific targets dependent on this pseudotarget. That way, we will never # even attempt to build targets for tools that don't exist. rule detect-build-tools { detection-command on $(<) = $($(<).bin-directory)$(>) ; } rule library-file { LibraryFromObjects $(<) : [ Objects $(>) ] ; } rule executable-file { type-DEPENDS exe : $(<) ; main-from-objects $(<) : [ Objects $(>) ] : EXE ; } rule dll-files { type-DEPENDS dll : $(<) ; # Set up the import library dependency on Windows if $(<[2]) { INCLUDES $(<[1]) : $(<[2-]) ; INCLUDES $(<[2-]) : $(<[1]) ; } main-from-objects $(<) : [ Objects $(>) ] : DLL ; } # main-from-objects exe-target : obj-target... : ("EXE"|"DLL") # # generate instructions to build the given "main" target from the given object # files given in the 2nd parameter. The 3rd parameter should be EXE for an # executable, or DLL for a shared library. rule main-from-objects { # make compiled sources a dependency of target MakeLocate $(<) : $(LOCATE_TARGET) ; Clean clean : $(<) ; MODE on $(<) = $($(3)MODE) ; local link-function = Link-$(3) ; local ignored = [ $(link-function) $(<) : $(>) : $(3) ] ; Chmod $(<[1]) ; DEPENDS $(<) : $(>) ; } rule Link-EXE { # N.B. By the time this rule is invoked, we had better have gRUN_PATH completely set. Link-action $(<) : $(>) : EXE ; RUN_PATH on $(<) = [ join $(gRUN_PATH($(<))) $(RUN_PATH) : $(SPLITPATH) ] ; } rule Link-DLL { if $(<[2]) { MODE on $(<[2]) = $(IMPMODE) ; Chmod $(<[2]) ; } gRUN_PATH($(<)) += $(gLOCATE($(<[1]))) ; Link-action $(<) : $(>) : DLL ; } # store the shell's PATH again, just in case someone uses PATH. # This also allows the user to customize the base path for running built # products from the command-line RUN_PATH ?= $(PATH) ; # A simple action to run an executable target actions Run { $(SHELL_SET)PATH=$(RUN_PATH) $(SHELL_EXPORT)PATH $(<) $(COMMAND-LINE) } # bubble variable-name # # Helper function for sort, below # Removes the greatest element from $(variable-name) and returns it. rule bubble # { local result = ; local last = $($(<)[1]) ; for x in $($(<)[2-]) { if $(last) <= $(x) { result += $(last) ; last = $(x) ; } else { result += $(x) ; } } $(<) = $(result) ; return $(last) ; } # sort args # # return args sorted in lexicographic order. rule sort { local _all = $(<) ; local _result = ; local _count ; for _count in $(<) { _result = [ bubble _all ] $(_result) ; } return $(_result) ; } # min args # # return the lexicographic minimum element of args rule min { local result = ; local x ; for x in $(<) { if ! $(result) || ( $(x) < $(result) ) { result = $(x) ; } } return $(result) ; } # difference listB : listA # returns the elements of B that are not in A rule difference { local result = ; local element ; for element in $(<) { if ! ( $(element) in $(>) ) { result += $(element) ; } } return $(result) ; } # replace list : old-value new-value # # returns list with occurrences of old-value replaced by new-value rule replace { local result = ; local x ; for x in $(<) { if $(x) = $(>[1]) { result += $(>[2]) ; } else { result += $(x) ; } } return $(result) ; } # select-ungristed list... # # Returns the elements of list that have no grist rule select-ungristed { local result x ; for x in $(<) { if ! $(x:G) { result += $(x) ; } } return $(result) ; } # Split a qualified property into 3 elements. # # Grammar description of qualified-property : # [[]]property # # returns property # missing or are treated as <*> rule split-qualified-property { local grist1 = $(<:G) ; local ungrist1 = $(<:G=) ; local grist2 = $(ungrist1:G) ; local ungrist2 = $(ungrist1:G=) ; local grist3 = $(ungrist2:G) ; local ungrist3 = $(ungrist2:G=) ; if $(grist3) { return $(grist1) $(grist2) $(grist3)$(ungrist3) ; } else if $(grist2) { return <*> $(grist1) $(grist2)$(ungrist2) ; } else { return <*> <*> $(<) ; } } rule unique # list { local result = ; local f ; for f in $(<) { if ! $(f) in $(result) { result += $(f) ; } } return $(result) ; } # get-properties features : properties # # Given a list of gristed features and a list of properties, returns the # properties matching the given features. rule get-properties { local result = ; local property ; for property in $(>) { if $(property:G) in $(<) { result += $(property) ; } } return $(result) ; } # get-values features : properties # # Given a list of gristed feature names and a list of properties, returns the # value(s) of the given features. rule get-values { local _properties = [ get-properties $(<) : $(>) ] ; return $(_properties:G=) ; } # normalize-properties properties # # Normalizes a set of (possibly qualified) properties by prepending <*> as many # times as neccessary to ensure that each property has at least 3 gristed elements. rule normalize-properties { local property ; local result ; for property in $(<) { switch $(property) { case <*><*><*>* : result += $(property) ; case <*><*>* : result += <*>$(property) ; case <*>* : result += <*><*>$(property) ; case * : result += <*><*><*>$(property) ; } } return $(result) ; } # intersection set1 : set2 # # Removes from set1 any items which don't appear in set2 and returns the result. rule intersection { local result v ; for v in $(<) { if $(v) in $(>) { result += $(v) ; } } return $(result) ; } # subset sub : super # # Returns true iff sub is a subset of super, empty otherwise rule is-subset { if [ intersection $(<) : $(>) ] = $(<) { return true ; } } # distribute-feature value1[/value2...] # # Distribute the given feature across the slash-separated set of values, i.e. # returns value1[ /value2...] rule distribute-feature { local g = $(<:G) ; local result = [ split-path $(<:G=) ] ; return $(g)$(result) ; } # set-insert variable-name : value... ; # # Appends the given values to the list designated by variable-name if they are # not already present. rule set-insert { local v ; for v in $(>) { if ! ( $(v) in $($(<)) ) { $(<) += $(v) ; } } } # equal-sets set1 : set2 # # Returns true iff set1 contains the same elements as set2. # Not sensitive to the same element appearing multiple times rule equal-sets { if ( ! [ difference $(<) : $(>) ] ) && ( ! [ difference $(>) : $(<) ] ) { return true ; } } # feature name : [values...] # # Declares a feature with the given name, and the given allowed values. rule feature { if $(<) in $(gFEATURES) { EXIT feature $(<) : $(gFEATURE_VALUES(<$(<)>) redeclared as $(<) : $(>) ; } gFEATURES += <$(<)> ; gUNGRISTED(<$(<)>) = $(<) ; gFEATURE_VALUES(<$(<)>) = $(>) ; } rule free-feature { feature $(<) : $(>) ; gFREE_FEATURES += <$(<)> ; if $(>) { gSINGLE_VALUED_FREE_FEATURES += <$(<)> ; } } rule path-feature { free-feature $(<) : $(>) ; gPATH_FEATURES += <$(<)> ; } rule dependency-feature { path-feature $(<) : $(>) ; gDEPENDENCY_FEATURES += <$(<)> ; } # feature-default ... # # return the default properties corresponding to the given feature(s) rule feature-default { local result f ; for f in $(<) { result += $(f)$(gFEATURE_VALUES($(f))[1]) ; } return $(result) ; } # flags tools-name variable-name condition [: value(s)] # # Declare command-line settings for a given toolset. # toolset: the name of the toolset # variable-name: the name of a global variable which can be used to carry # information to a command-line # condition: One of the following: # 1. zero or more property-sets of the form: # value[/value...] # 2. one or more [/...] # # This rule appends to the specified variable, depending on a target's build # configuration and the form of condition. # # 1. if any specified property-set is a subset of the target's build properties or if # condition is empty, the values specified in $(3) will be appended once to # /variable-name/. # # 2. The value of each specified feature that participates in the target's # build properaties is appended to /variable-name/. # # The variable will be set "on" the target so it may be used in its build actions. rule flags { local toolset = $(<[1]) ; local variable = $(<[2]) ; local condition = $(<[3-]) ; # record the names of all variables used so they can be set on targets if ! ( $(variable) in $(gTARGET_VARIABLES) ) { gTARGET_VARIABLES += $(variable) ; $(variable) = ; } local found = ; local x ; for x in $(condition) { x = [ split-path $(x) ] ; # Add each feature to the set of features relevant to the toolset gRELEVANT_FEATURES($(toolset)) += $(x:G) ; # is it a property set? if $(x:G=) { # if this property_set is a subset of the current build-properties if ( ! $(found) ) && [ is-subset $(x) : $(gBUILD_PROPERTIES) ] { found = true ; $(variable) += $(>) ; } } else { $(variable) += [ get-values $(x) : $(gBUILD_PROPERTIES) ] ; if $(x:G) in $(gDEPENDENCY_FEATURES) { gDEPENDENCY_VARIABLES($(toolset)) += $(variable) ; } } } if ! $(condition) { $(variable) += $(>) ; } } # include-tools toolset # # Unconditionally process the specification file for the given toolset. It is # neccessary to do this for each target built with that toolset, since the # toolset will invoke the flags rule to set global variables based on the build # properties of the target. rule include-tools { gCURRENT_TOOLSET = $(<) ; # clear any lingering target variables that may have been declared $(gTARGET_VARIABLES) = ; gTARGET_VARIABLES = NEEDLIBS ; # start over from the beginning gRELEVANT_FEATURES($(<)) = ; # clear relevant feature set gDEPENDENCY_VARIABLES($(<)) = ; include $(BOOST_BUILD_INSTALLATION)$(SLASH)$(<)-tools.jam ; # Always maintain the list of relevant features as unique gRELEVANT_FEATURES($(<)) = [ unique $(gRELEVANT_FEATURES($(<))) ] ; gINCLUDED($(BOOST_BUILD_INSTALLATION)$(SLASH)$(<)-tools.jam) = true ; } # extends-toolset toolset # # Used in a toolset definition file; Declares that the toolset currently being # defined is an extension of the given toolset. rule extends-toolset { { local gCURRENT_TOOLSET ; # protect this from being clobbered include-tools $(<) ; } # Add the relevant features from the base toolset gRELEVANT_FEATURES($(gCURRENT_TOOLSET)) += $(gRELEVANT_FEATURES($(<))) ; gDEPENDENCY_VARIABLES($(gCURRENT_TOOLSET)) += $(gDEPENDENCY_VARIABLES($(<))) ; } # relevant-features toolset # # Returns the set of unique features relevant to the given toolset; includes the # toolset description file as a side-effect if neccessary. rule relevant-features # name { if ! $(gRELEVANT_FEATURES($(<))) { include-tools $(<) ; } return $(gRELEVANT_FEATURES($(<))) ; } # variant name : []value... # # Delcare a build variant, whose configuration is given by the given (optionally # toolset-qualified) properties. rule variant { local toolset ; for toolset in $(TOOLS) { # We hijack select-properties to do our dirty work here. # Because properties in a variant declaration are only qualified with # toolset and not variant, we specify the toolset where # select-properties expects a variant name. The first toolset parameter # is neccessary to get the relevant-features correctly set. We supply # the variant name as the target name, so that error messages will look # coherent. gBASE_PROPERTIES($(toolset),$(<)) = [ sort [ select-properties $(toolset) $(toolset) $(<) : $(>) ] ] ; } } # select-properties toolset variant target-name : qualified-properties... # # Returns rule select-properties { local relevant_features = [ relevant-features $(<[1]) ] ; local normalized = [ normalize-properties $(>) ] ; # Classify properties by the specificity of their qualification. # First, grab those that apply to this toolset, or all toolsets local this_toolset = [ get-values <$(<[1])> : $(normalized) ] ; local all_toolsets = [ get-values <*> : $(normalized) ] ; local 0-stars = [ get-values <$(<[2])> : $(this_toolset) ] ; local 1-stars = [ get-values <*> : $(this_toolset) ] [ get-values <$(<[2])> : $(all_toolsets) ] ; local 2-stars = [ get-values <*> : $(all_toolsets) ] ; # Select feature names from the features relevant to the toolset. local features = [ intersection $(relevant_features) : $(0-stars:G) $(1-stars:G) $(2-stars:G) ] ; local result f ; for f in $(features) { local is_free = [ intersection $(f) : $(gFREE_FEATURES) ] ; # Go through the $(n-stars) lists from most- to least- specific; # collect the best set of values of a simple feature, and /all/ # values of a free feature. local r n ; for n in 0 1 2 { if ! $(r) || $(is_free) { r += [ get-values $(f) : $($(n)-stars) ] ; } } r = [ unique $(r) ] ; if $(r[2]) && ! $(is_free) # Check for conflicting simple-feature requests { EXIT "Error: Ambiguous properties requested for" $(<[3]) <$(<[1])><$(<[2])> ":" $(f)$(r) ; } result += $(f)$(r) ; } return $(result) ; } # get toolset features include $(BOOST_BUILD_INSTALLATION)$(SLASH)features.jam ; # ungrist-properties properties... # # Transforms a list of properties of the form: # value1 [value2... ] # into a list of the form: # feature1-value1 feature2-value2 # suitable for use as directory path elements # rule ungrist-properties { local property ; local result = ; for property in $(<) { result += $(gUNGRISTED($(property:G)))-$(property:G=) ; } return $(result) ; } # set-target-variables target # # attach global build tool settings to the given file-target, so that they can be # used in build actions. rule set-target-variables # file-target { local s ; for s in $(gTARGET_VARIABLES) { $(s) on $(<) = $($(s)) ; # set up dependencies if the target is a "top-level" target if ( $(s) in $(gDEPENDENCY_VARIABLES($(gCURRENT_TOOLSET))) ) && $(gTARGET_TYPE($(<))) { DEPENDS $(<) : $($(s)) ; } } } # fixup-path-properties properties... # # For path properties, add a relative path prefix to the value as neccessary to # locate the path relative to the current subproject. rule fixup-path-properties { local path-properties = [ get-properties $(gPATH_FEATURES) : $(<) ] ; local non-path = [ difference $(<) : $(path-properties) ] ; if $(RELATIVE_SUBDIR) { path-properties = [ root-paths $(path-properties) : $(RELATIVE_SUBDIR) ] ; } return $(path-properties) $(non-path) ; } # report-any-incompatible-properties requirements... : build-request... : target-name # # If any element of requirements has the same grist but a different ungristed # part as any element of build-request, exits with an error report about target-name rule report-any-incompatible-properties { local all-properties = [ unique $(<) $(>) ] ; local all-features = $(all-properties:G) ; local unique-features = [ unique $(all-features) ] ; if $(all-features) != $(unique-features) { EXIT "Error:" "$(3):" "target requirements conflict for requested build {" $(<) $(>) "}" ; } } if report-any-incompatible-properties in $(TEST) { report-any-incompatible-properties bar mumble : c bar : my-target ; report-any-incompatible-properties bat mumble buz f : c bar : my-target ; } # multiply-property-sets [value1[/value2...] ]... # # Expands a set of (possibly multi-valued) properties into all the combinations # that include every feature in the set. Each combination is given as a path, # with the slash separating the properties, sorted in feature order. rule multiply-property-sets { local result p ; for p in [ sort $(<) ] { # expand any multi-valued simple features from the default build local multiple = [ distribute-feature $(p) ] ; # concatenation produces a product, so the tree branches for each # multi-valued simple feature. result = $(result)/$(multiple) ; result ?= $(multiple) ; # this trick allows us to get started } return $(result) ; } # make-path-property-sets base-path : common-properties : property-sets # # Returns a list of paths where the initial ungristed part of each element is a # relative path to a subvariant directory from a target's build root and the # rest of the element is a slash-separated property set describing the # properties of the target to be built. # # Each path returned is base-path extended by one of the ungristed property-sets # (or just the base-path if no property-sets are supplied). Each property set # returned is formed by extending common-properties with one of the property-sets. # # For example, # # make-path-property-sets gcc/release : v1 : v2/v3 # # returns this single-element list: # # gcc/release/p2-v2/p3-v3/v1/v2/v3 # |<-- subvariant path -->|<-- property-set -->| rule make-path-property-sets { local result ; local s ; for s in $(3) { result += [ join $(<) [ ungrist-properties [ split-path $(s) ] ] # directory components $(>) $(s) : $(SLASH) ] ; # common properties + property set } # if there were no overrides, just add the base variant and properties if ! $(result) { result = [ join $(<) $(>) : $(SLASH) ] ; } return $(result) ; } # segregate-overrides override-var base-var # # removes elements of $(base-var) from $(override-var), and removes elements # whose grist is in $(override-var:G) from $(base-var). rule segregate-overrides { $(<) = [ difference $($(<)) : $($(>)) ] ; # Which features, and thus properties, of the base variant are we keeping? local kept-features = [ difference $($(>):G) : $($(<):G) ] ; $(>) = [ get-properties $(kept-features) : $($(>)) ] ; } # report-free-property-conflicts free-property... : target # # If any single-valued free-feature appears more than once in free-property..., # exit with an appropriate error message. rule report-free-property-conflicts { local p = [ get-properties $(gSINGLE_VALUED_FREE_FEATURES) $(<) ] ; local f = [ unique $(p:G) ] ; if $(p:G) != $(f) { EXIT $(>): multiple values for single-valued free feature(s) [ difference $(p:G) $(f) ] requested ; } } # expand-build-request # toolset variant target-name : requirements : build-request # # Returns a list of path-property-sets (see make-path-property-sets above) for # all build configurations based on the given toolset, requirements, and # build-request. Target-name is just used for error reporting. rule expand-build-request { local toolset = $(<[1]) ; local variant = $(<[2]) ; # grab the requirements and BUILD-request relevant to this toolset and variant local requirements = [ select-properties $(toolset) $(variant) : $(>) ] ; local build-request = [ select-properties $(toolset) $(variant) : $(3) ] ; # Separate the free features (e.g. , , ) from the others local free-properties = [ segregate-free-properties requirements build-request ] ; # Check for conflicts report-free-property-conflicts $(free-properties) : $(<[3]) ; report-any-incompatible-properties $(requirements) : $(build-request) : $(<[3]) ; # Get the base variant for the toolset. Includes free features local base-properties = $(gBASE_PROPERTIES($(toolset),$(variant))) ; # Which properties will override settings in the base variant? local override-properties = [ unique $(requirements) $(build-request) ] ; segregate-overrides override-properties : base-properties ; # Which features will pick up a default value because they are not in # the base variant or in the overrides? local relevant_features = [ relevant-features $(toolset) ] ; local defaulted-features = [ difference $(relevant_features) : $(override-properties:G) $(base-properties:G) ] ; local defaulted-properties = [ feature-default $(defaulted-features) ] ; # VP: defaulted-properties have the form value and there's 1 value. # Hence, each element of defaulted-properties will be part of each # component of override-sets and will be a part of each property-set # returned. By segregating them, the result is changed in only one # way: free properties does not show up in target path. local defaulted-free-properties = [ segregate-free-properties defaulted-properties ] ; # form override property sets of the form (property1[/property2...] )+, # sorted in feature order. These represent the properties of subvariants # that differ from the base variant local override-sets = [ multiply-property-sets $(override-properties) $(defaulted-properties) ] ; # return path-property-sets corresponding to each (sub)variant build # described. return [ make-path-property-sets $(toolset)$(SLASH)$(variant) : [ fixup-path-properties $(base-properties) $(free-properties) $(defaulted-free-properties) ] : $(override-sets) ] ; } # split-path-at-grist path # # Breaks path at each $(SLASH) that is followed by grist. This can be used to # break apart property sets, particularly where the feature is used, # since its value is typically a path. rule split-path-at-grist { local full-split = [ split-path $(<) ] ; local last ; local result x ; for x in $(full-split) { if $(x:G) { result += $(last) ; last = $(x) ; } else { last = $(last)$(SLASH)$(x) ; last ?= $(x) ; } } return $(result) $(last) ; } # declare-local-target name : sources : requirements : local-BUILD : target-type # # declares a subproject-local target of the given name and target-type. This is # all top-level rules which declare targets should eventually go through here. rule declare-local-target { # We add SOURCE_GRIST the base target name here because we're referring the # abstract target which generates all of the actual builds. We need a way to # distinguish targets of the same name from different subprojects. local target-id = [ FGristFiles $(<) ] ; if ! $(5) { EXIT No target type given for "$(<)" ; } if ! $(gTARGET_TYPE($(target-id))) { gTARGET_TYPE($(target-id)) = $(5) ; # Add the specified requirements to any requirements given by the target # type, and the corresponding property. gTARGET_REQUIREMENTS($(target-id)) = $(3) $(gTARGET_TYPE_REQUIREMENTS($(5))) $(5) ; gTARGET_LIBS($(target-id)) = [ get-values : $(>) ] ; gTARGET_SOURCES($(target-id)) = [ FGristFiles [ difference $(>:G=) : $(gTARGET_LIBS($(target-id))) ] ] ; } else if $(gTARGET_TYPE($(target-id))) != $(5) { EXIT conflicting target types for "$(<)": "$(gTARGET_TYPE($(target-id)))" "$(5)" ; } # Just gather information if we are including a library's Jamfile for a # dependent target. Don't generate build instructions here. if ! $(gIN_LIB_INCLUDE) { main-target $(target-id) : $(4) ; } return $(gTARGET_FILES($(target-id))) ; } # directory-of files... # # Returns a list of the directories containing each element of files rule directory-of { local result d ; for d in $(<:D) { if $(d) = "" { result += $(DOT) ; } else { result += $(d) ; } } return $(result) ; } # top-relative-tokens path # # Returns a list of path elements which form the relative path from TOP to path, # which is expected to be given relative to the current subproject. rule top-relative-tokens { return [ simplify-path-tokens $(SUBDIR_TOKENS) [ split-path $(<) ] ] ; } # dependent-include target-path... # # For each target-path, ensure that the appropriate Jamfile has been # included. Used when a target declares its dependency on another target. rule dependent-include { local target ; for target in $(<) { # compute the path to the Jamfile containing target. This path must be # relative to the directory of Jam's invocation in order for the include # rule to find it. local jamfile-path = [ tokens-to-simple-path $(RELATIVE_SUBDIR_TOKENS) [ directory-of $(target) ] $(JAMFILE) ] ; if ! $(gINCLUDED($(jamfile-path))) { # protect variables from being permanently set by SubDir invocations # in included files. local [ protect-subdir ] ; # this stack allows us to avoid making dependee libraries part of # the "type" targets, e.g. all, exe, obj. See rule type-DEPENDS. local gIN_LIB_INCLUDE = 1 ; include $(jamfile-path) ; gINCLUDED($(jamfile-path)) = true ; } } } # segregate-free-properties variable1 variable2... # # returns the and removes the unique list of free properties from # $(variable1) $(variable2)... rule segregate-free-properties { local free-properties = [ unique [ get-properties $(gFREE_FEATURES) : $($(<)) ] ] ; local v ; for v in $(<) { $(v) = [ difference $($(v)) : $(free-properties) ] ; } return $(free-properties) ; } # is-link-compatible feature : value1 : value2 # # return non-empty iff a library built with value1 can be linked into a # target with value2, empty otherwise rule is-link-compatible { return [ intersection $(<) $(>:G=$(<)) $(>:G=$(<))$(SLASH)$(3:G=$(<)) : $(gLINK_COMPATIBLE) ] ; } # find-compatible-subvariant main-target : toolset variant : dependent-simple-properties rule find-compatible-subvariant { local requirements = [ select-properties $(>) : $(gTARGET_REQUIREMENTS($(<))) ] ; local free-properties = [ segregate-free-properties requirements ] ; # begin with the properties overridden by the target requirements local override-properties = $(requirements) ; # add properties from build-request, checking for compatibility local p ; for p in [ difference $(3) : $(requirements) ] { local f = $(p:G) ; if $(f) in $(requirements:G) { local required-value = [ get-values $(f) : $(override-properties) ] ; if ! [ is-link-compatible $(f) : $(required-value) : $(p:G=) ] { EXIT $(<): required property $(f)$(required-value) incompatible with requested $(p) ; } } else { override-properties += $(p) ; } } local base-properties = $(gBASE_PROPERTIES($(>[1]),$(>[2]))) ; segregate-overrides override-properties : base-properties ; # build the path identifying the subvariant local subvariant-id = [ join $(>) # toolset variant [ ungrist-properties [ sort $(override-properties) ] ] : $(SLASH) ] ; return $(subvariant-id) [ fixup-path-properties $(base-properties) $(free-properties) ] $(override-properties) ; } # link-libraries libs... : toolset variant : dependent-simple-properties # # For each target specified in libs, generate build instructions # for a subvariant that can be linked with a dependent target with # dependent-properties, returning a list of the linkable targets. rule link-libraries { local lib-path result ; for lib-path in $(<) { local new-subdir = TOP [ top-relative-tokens [ directory-of $(lib-path) ] ] ; # protect global variables from being permanently set by SubDir local [ protect-subdir ] ; # Enter the dependee subproject SubDir $(new-subdir) ; local lib-target = [ FGristFiles $(lib-path:D=) ] ; local lib-subvariant = [ find-compatible-subvariant $(lib-target) : $(>) : $(3) ] ; # Generate build instructions for the library target local lib-files = [ subvariant-target $(lib-target) : $(lib-subvariant) : $(>) ] ; # Add the name of the linkable product to the result. local type = $(gTARGET_TYPE($(lib-target))) ; local index = $(gLINKABLE_PRODUCT_INDEX($(type))) ; index ?= 1 ; result += $(lib-files[$(index)]) ; } return $(result) ; } # Which configuration(s) to build if nothing is explicitly specified DEFAULT_BUILD ?= debug ; # get-BUILD [target-default-build] # # pick the first of ($(BUILD), $(>), $(DEFAULT_BUILD)) which is set. If it # contains no variants, add variants from $(DEFAULT_BUILD). rule get-BUILD { local build = $(BUILD) ; build ?= $(<) ; build ?= $(DEFAULT_BUILD) ; local variants = [ select-ungristed $(build) ] ; if ! $(variants) { build += [ select-ungristed $(DEFAULT_BUILD) ] ; } return $(build) ; } BIN_DIRECTORY ?= bin ; # declare-fake-targets abstract-target : target-file # # rule declare-fake-targets { # make a fake target so that it can be built without knowing the suffix # Since executables under *NIX have no suffix, we'd better check if $(>) != $(<) { DEPENDS $(<) : $(>) ; NOTFILE $(<) ; } # The following checks that we're in the subdirectory of Jam's invocation # so that we can arrange for ungristed target names to be built from the # command-line. if $(<:G) && [ in-invocation-subdir ] { DEPENDS $(<:G=) : $(<) ; # allows $(<:G=) to be used to build all variants NOTFILE $(<:G=) ; } } # declare-target-type TYPE : [[]]value... rule declare-target-type { gTARGET_TYPE_REQUIREMENTS($(<)) = $(>) ; } declare-target-type DLL : true ; if $(NT) { gIMPORT_SUFFIX(DLL) = .lib ; gIMPORT_SUFFIX(LIB) = .lib ; gEXPORT_SUFFIX(DLL) = .lib ; } else { gIMPORT_SUFFIX(DLL) = .so ; gIMPORT_SUFFIX(LIB) = .a ; } # depend-on-libraries target-files : library-targets rule depend-on-libraries { NEEDLIBS += $(>) ; NEEDLIBS on $(<) += $(>) ; DEPENDS $(<) : $(>) ; # To run these targets, we need everything needed to run the libraries gRUN_PATH($(<)) += $(gRUN_PATH($(>))) ; } # subvariant-target target : subvariant-id build-properties : toolset variant # # Given target, a main target name gristed with $(SOURCE_GRIST), generate build # instructions for a subvariant target using the given toolset, variant, etc. # # RETURNS: the a list of target names for the files built by the subvariant. If # the target is a library, the first filename is the one that should be linked # into a dependent target. rule subvariant-target { # SOURCE_GRIST identifies the subproject directory; TARGET_GRIST will identify # the target and subvariant, since unique versions of files will be built for # that combination. local TARGET_GRIST = [ join-path $(SOURCE_GRIST) $(<:G=) $(>[1]) ] ; local subvariant = $(<:G=$(TARGET_GRIST)) ; # Do nothing if build instructions and dependencies for this target have # already been generated. if ! $(TARGET_GRIST) in $(gDECLARED_TARGETS) { gDECLARED_TARGETS += $(TARGET_GRIST) ; local target-type = $(gTARGET_TYPE($(<))) ; # LOCATE_TARGET affects where built targets are generated. We move it # relative to the default location based on the subvariant local LOCATE_TARGET = [ join-path $(LOCATE_TARGET) $(BIN_DIRECTORY) $(<:G=) $(>[1]) ] ; local target-files = [ FAppendSuffix $(subvariant) : $(SUF$(target-type)) ] ; gTARGET_FILES($(subvariant)) = $(target-files) ; gTARGET_FILES($(<)) += $(target-files) ; # Remember the path from the build root to the subvariant directory gSUBVARIANT_PATH($(subvariant)) = $(>[1]) ; declare-fake-targets $(<) : $(target-files) ; # set up gBUILD_PROPERTIES for include-tools (below) local gBUILD_PROPERTIES = $(>[2-]) ; # Include the toolset specification. This will set up the global flags # variables in a way appropriate to this build. include-tools $(3[1]) ; # headers should be identified specific to the target, since search paths # may differ for different subvariants. The same header name or relative # path may refer to different files. local HDRGRIST = [ join $(SOURCE_GRIST) $(HDRS) $(STDHDRS) : "#" ] ; # transfer target variables to the target file. set-target-variables $(target-files) ; if $(gTARGET_LIBS($(<))) { local libs ; { # Protect target variables against modification while lib dependencies # are built. They will be made empty here, and restored when this scope exits local $(gTARGET_VARIABLES) ; # extract the simple properties from dependent-properties local simple-properties = $(gBUILD_PROPERTIES) ; segregate-free-properties simple-properties ; # generate library build instructionsn libs = [ link-libraries $(gTARGET_LIBS($(<))) : $(3) : $(simple-properties) ] ; } depend-on-libraries $(target-files) : $(libs) ; } # dispatch to the appropriate declaration function. Here we are using an # FTJam-only feature (thanks, David Turner!) local ignored = [ $(gGENERATOR_FUNCTION($(target-type))) $(target-files) : $(gTARGET_SOURCES($(<))) ] ; $(gTARGET_VARIABLES) = ; # Be sure that we don't mask bugs with lingering target variables } return $(gTARGET_FILES($(subvariant))) ; } # main-target target : local-build # # Generates requested subvariant build instructions for the given main target rule main-target { local BUILD = [ get-BUILD $(>) ] ; local variants = [ select-ungristed $(BUILD) ] ; local build-request = [ difference $(BUILD) : $(variants) ] ; # include each jamfile describing a dependee target. dependent-include $(gTARGET_LIBS($(<))) ; local toolset ; for toolset in $(TOOLS) { local v ; for v in $(variants) { local expanded = [ expand-build-request $(toolset) $(v) $(<) : $(gTARGET_REQUIREMENTS($(<))) : $(build-request) ] ; local x ; for x in $(expanded) { subvariant-target $(<) : [ split-path-at-grist $(x) ] : $(toolset) $(v) ; } } } } gGENERATOR_FUNCTION(EXE) = executable-file ; # exe target : sources : requirements : local-build # # Declare an executable target. rule exe { declare-local-target $(<) : $(2) : $(3) : $(4) : EXE ; } gGENERATOR_FUNCTION(DLL) = dll-files ; # dll target : sources : requirements : local-build # # Declare a shared library target. rule dll { declare-local-target $(<) : $(2) : $(3) : $(4) : DLL ; } gGENERATOR_FUNCTION(LIB) = library-file ; # lib target : sources : requirements : local-build # # Declare a statically-linked library target. rule lib { declare-local-target $(<) : $(2) : $(3) : $(4) : LIB ; } # unit-test target : sources : requirements : local-build # # Declare an executable target, to be run by tests. rule unit-test { local files = [ exe $(<) : $(2) : $(3) : $(4) ] ; type-DEPENDS test : $(files) ; COMMAND-LINE on $(files) = $(5) ; for local file in $(files) { Run $(file) ; } } # Used to build command files from a list of sources. rule build-command-file ( command : sources * ) { DEPENDS $(command) : $(sources) ; # Check whether there's anything to dump, so that we don't end up # executing a line of the form: # # echo > file.CMD # # on Windows this writes "echo is on." into the command-file, # which then breaks the link. if $(sources[1]) { # Handle the first target specially, so that the first source file # will clear the command file command-file-dump-1st $(command) : $(sources[1]) ; } if $(sources[2]) { # Then fill the rest up piecemeal command-file-dump-rest $(command) : $(sources[2-]) ; } } # command-file-dump-1st: dump the first source path into the target actions quietly command-file-dump-1st { echo "$(>)" > "$(<)" } # command-file-dump: dump the remaining source paths into the target actions quietly piecemeal command-file-dump-rest { echo "$(>)" >> "$(<)" } # Clean up the temporary COMMAND-FILE used to build TARGETS. rule remove-command-file ( targets + : command-file ) { TEMPORARY $(command-file) ; Clean clean : $(command-file) ; # Mark the file for removal via clean } actions ignore quietly piecemeal together remove-command-file { $(RM) $(>) } # build TARGETS from SOURCES using a command-file, where RULE-NAME is # used to generate the build instructions from the command-file to # TARGETS rule with-command-file ( rule-name targets * : sources * ) { # create a command-file target and place it where the first target # will be built local command-file = $(<[2]:S=.CMD) ; LOCATE on $(command-file) = $(gLOCATE($(targets[1]))) ; build-command-file $(command-file) : $(sources) ; # Build the targets from the command-file instead of the sources DEPENDS $(targets) : $(command-file) ; local result = [ $(rule-name) $(targets) : $(command-file) ] ; # clean up afterwards remove-command-file $(targets) : $(command-file) ; return result ; }