# Copyright 2003, 2005 Dave Abrahams # Copyright 2005, 2006 Rene Rivera # Copyright 2005 Toon Knapen # Copyright 2002, 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) # Provides actions common to all toolsets, such as creating directories and # removing files. import os ; import modules ; import utility ; import print ; import type ; import feature ; import errors ; import path ; import sequence ; import toolset ; import virtual-target ; if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ] { .debug-configuration = true ; } if [ MATCH (--show-configuration) : [ modules.peek : ARGV ] ] { .show-configuration = true ; } # Configurations # # The following class helps to manage toolset configurations. Each configuration # has a unique ID and one or more parameters. A typical example of a unique ID # is a condition generated by 'common.check-init-parameters' rule. Other kinds # of IDs can be used. Parameters may include any details about the configuration # like 'command', 'path', etc. # # A toolset configuration may be in one of the following states: # # - registered # Configuration has been registered (e.g. by autodetection code) but has # not yet been marked as used, i.e. 'toolset.using' rule has not yet been # called for it. # - used # Once called 'toolset.using' rule marks the configuration as 'used'. # # The main difference between the states above is that while a configuration is # 'registered' its options can be freely changed. This is useful in particular # for autodetection code - all detected configurations may be safely overwritten # by user code. class configurations { import errors ; rule __init__ ( ) { } # Registers a configuration. # # Returns 'true' if the configuration has been added and an empty value if # it already exists. Reports an error if the configuration is 'used'. # rule register ( id ) { if $(id) in $(self.used) { errors.error "common: the configuration '$(id)' is in use" ; } local retval ; if ! $(id) in $(self.all) { self.all += $(id) ; # Indicate that a new configuration has been added. retval = true ; } return $(retval) ; } # Mark a configuration as 'used'. # # Returns 'true' if the state of the configuration has been changed to # 'used' and an empty value if it the state wasn't changed. Reports an error # if the configuration isn't known. # rule use ( id ) { if ! $(id) in $(self.all) { errors.error "common: the configuration '$(id)' is not known" ; } local retval ; if ! $(id) in $(self.used) { self.used += $(id) ; # indicate that the configuration has been marked as 'used' retval = true ; } return $(retval) ; } # Return all registered configurations. # rule all ( ) { return $(self.all) ; } # Return all used configurations. # rule used ( ) { return $(self.used) ; } # Returns the value of a configuration parameter. # rule get ( id : param ) { return $(self.$(param).$(id)) ; } # Sets the value of a configuration parameter. # rule set ( id : param : value * ) { self.$(param).$(id) = $(value) ; } } # The rule for checking toolset parameters. Trailing parameters should all be # parameter name/value pairs. The rule will check that each parameter either has # a value in each invocation or has no value in each invocation. Also, the rule # will check that the combination of all parameter values is unique in all # invocations. # # Each parameter name corresponds to a subfeature. This rule will declare a # subfeature the first time a non-empty parameter value is passed and will # extend it with all the values. # # The return value from this rule is a condition to be used for flags settings. # rule check-init-parameters ( toolset requirement * : * ) { local sig = $(toolset) ; local condition = $(toolset) ; local subcondition ; for local index in 2 3 4 5 6 7 8 9 { local name = $($(index)[1]) ; local value = $($(index)[2]) ; if $(value)-is-not-empty { condition = $(condition)-$(value) ; if $(.had-unspecified-value.$(toolset).$(name)) { errors.user-error "$(toolset) initialization: parameter '$(name)' inconsistent" : "no value was specified in earlier initialization" : "an explicit value is specified now" ; } # The below logic is for intel compiler. It calls this rule with # 'intel-linux' and 'intel-win' as toolset, so we need to get the # base part of toolset name. We can't pass 'intel' as toolset # because in that case it will be impossible to register versionless # intel-linux and intel-win toolsets of a specific version. local t = $(toolset) ; local m = [ MATCH ([^-]*)- : $(toolset) ] ; if $(m) { t = $(m[1]) ; } if ! $(.had-value.$(toolset).$(name)) { if ! $(.declared-subfeature.$(t).$(name)) { feature.subfeature toolset $(t) : $(name) : : propagated ; .declared-subfeature.$(t).$(name) = true ; } .had-value.$(toolset).$(name) = true ; } feature.extend-subfeature toolset $(t) : $(name) : $(value) ; subcondition += $(value) ; } else { if $(.had-value.$(toolset).$(name)) { errors.user-error "$(toolset) initialization: parameter '$(name)' inconsistent" : "an explicit value was specified in an earlier initialization" : "no value is specified now" ; } .had-unspecified-value.$(toolset).$(name) = true ; } sig = $(sig)$(value:E="")- ; } if $(sig) in $(.all-signatures) { local message = "duplicate initialization of $(toolset) with the following parameters: " ; for local index in 2 3 4 5 6 7 8 9 { local p = $($(index)) ; if $(p) { message += "$(p[1]) = $(p[2]:E=)" ; } } message += "previous initialization at $(.init-loc.$(sig))" ; errors.user-error $(message[1]) : $(message[2]) : $(message[3]) : $(message[4]) : $(message[5]) : $(message[6]) : $(message[7]) : $(message[8]) ; } .all-signatures += $(sig) ; .init-loc.$(sig) = [ errors.nearest-user-location ] ; # If we have a requirement, this version should only be applied under that # condition. To accomplish this we add a toolset requirement that imposes # the toolset subcondition, which encodes the version. if $(requirement) { local r = $(toolset) $(requirement) ; r = $(r:J=,) ; toolset.add-requirements $(r):$(subcondition) ; } # We add the requirements, if any, to the condition to scope the toolset # variables and options to this specific version. condition += $(requirement) ; if $(.show-configuration) { ECHO notice: $(condition) ; } return $(condition:J=/) ; } # A helper rule to get the command to invoke some tool. If # 'user-provided-command' is not given, tries to find binary named 'tool' in # PATH and in the passed 'additional-path'. Otherwise, verifies that the first # element of 'user-provided-command' is an existing program. # # This rule returns the command to be used when invoking the tool. If we can't # find the tool, a warning is issued. If 'path-last' is specified, PATH is # checked after 'additional-paths' when searching for 'tool'. # rule get-invocation-command-nodefault ( toolset : tool : user-provided-command * : additional-paths * : path-last ? ) { local command ; if ! $(user-provided-command) { command = [ common.find-tool $(tool) : $(additional-paths) : $(path-last) ] ; if ! $(command) && $(.debug-configuration) { ECHO "warning: toolset $(toolset) initialization: can't find tool $(tool)" ; ECHO "warning: initialized from" [ errors.nearest-user-location ] ; } } else { command = [ common.check-tool $(user-provided-command) ] ; if ! $(command) && $(.debug-configuration) { ECHO "warning: toolset $(toolset) initialization: " ; ECHO "warning: can't find user-provided command " '$(user-provided-command)' ; ECHO "warning: initialized from" [ errors.nearest-user-location ] ; } } return $(command) ; } # Same as get-invocation-command-nodefault, except that if no tool is found, # returns either the user-provided-command, if present, or the 'tool' parameter. # rule get-invocation-command ( toolset : tool : user-provided-command * : additional-paths * : path-last ? ) { local result = [ get-invocation-command-nodefault $(toolset) : $(tool) : $(user-provided-command) : $(additional-paths) : $(path-last) ] ; if ! $(result) { if $(user-provided-command) { result = $(user-provided-command) ; } else { result = $(tool) ; } } return $(result) ; } # Given an invocation command return the absolute path to the command. This # works even if command has no path element and was found on the PATH. # rule get-absolute-tool-path ( command ) { if $(command:D) { return $(command:D) ; } else { local m = [ GLOB [ modules.peek : PATH Path path ] : $(command) $(command).exe ] ; return $(m[1]:D) ; } } # Attempts to find tool (binary) named 'name' in PATH and in 'additional-paths'. # If found in PATH, returns 'name' and if found in additional paths, returns # absolute name. If the tool is found in several directories, returns all paths. # Otherwise, returns an empty string. If 'path-last' is specified, PATH is # searched after 'additional-paths'. # rule find-tool ( name : additional-paths * : path-last ? ) { local path = [ path.programs-path ] ; local match = [ path.glob $(path) : $(name) $(name).exe ] ; local additional-match = [ path.glob $(additional-paths) : $(name) $(name).exe ] ; local result ; if $(path-last) { result = $(additional-match) ; if ! $(result) && $(match) { result = $(name) ; } } else { if $(match) { result = $(name) ; } else { result = $(additional-match) ; } } if $(result) { return [ path.native $(result[1]) ] ; } } # Checks if 'command' can be found either in path or is a full name to an # existing file. # rule check-tool-aux ( command ) { if $(command:D) { if [ path.exists $(command) ] # Both NT and Cygwin will run .exe files by their unqualified names. || ( [ os.on-windows ] && [ path.exists $(command).exe ] ) # Only NT will run .bat files by their unqualified names. || ( ( [ os.name ] = NT ) && [ path.exists $(command).bat ] ) { return $(command) ; } } else { if [ GLOB [ modules.peek : PATH Path path ] : $(command) ] { return $(command) ; } } } # Checks that a tool can be invoked by 'command'. If command is not an absolute # path, checks if it can be found in 'path'. If comand is an absolute path, # check that it exists. Returns 'command' if ok or empty string otherwise. # rule check-tool ( xcommand + ) { if [ check-tool-aux $(xcommand[1]) ] || [ check-tool-aux $(xcommand[-1]) ] { return $(xcommand) ; } } # Handle common options for toolset, specifically sets the following flag # variables: # - CONFIG_COMMAND to $(command) # - OPTIONS for compile to the value of in $(options) # - OPTIONS for compile.c to the value of in $(options) # - OPTIONS for compile.c++ to the value of in $(options) # - OPTIONS for compile.fortran to the value of in $(options) # - OPTIONS for link to the value of in $(options) # rule handle-options ( toolset : condition * : command * : options * ) { if $(.debug-configuration) { ECHO "notice: will use '$(command)' for $(toolset), condition $(condition:E=(empty))" ; } # The last parameter ('unchecked') says it is OK to set flags for another # module. toolset.flags $(toolset) CONFIG_COMMAND $(condition) : $(command) : unchecked ; toolset.flags $(toolset).compile OPTIONS $(condition) : [ feature.get-values : $(options) ] : unchecked ; toolset.flags $(toolset).compile.c OPTIONS $(condition) : [ feature.get-values : $(options) ] : unchecked ; toolset.flags $(toolset).compile.c++ OPTIONS $(condition) : [ feature.get-values : $(options) ] : unchecked ; toolset.flags $(toolset).compile.fortran OPTIONS $(condition) : [ feature.get-values : $(options) ] : unchecked ; toolset.flags $(toolset).link OPTIONS $(condition) : [ feature.get-values : $(options) ] : unchecked ; } # Returns the location of the "program files" directory on a Windows platform. # rule get-program-files-dir ( ) { local ProgramFiles = [ modules.peek : ProgramFiles ] ; if $(ProgramFiles) { ProgramFiles = "$(ProgramFiles:J= )" ; } else { ProgramFiles = "c:\\Program Files" ; } return $(ProgramFiles) ; } if [ os.name ] = NT { RM = del /f /q ; CP = copy ; IGNORE = "2>nul >nul & setlocal" ; LN ?= $(CP) ; } else { RM = rm -f ; CP = cp ; LN = ln ; } rule rm-command ( ) { return $(RM) ; } rule copy-command ( ) { return $(CP) ; } # Returns the command needed to set an environment variable on the current # platform. The variable setting persists through all following commands and is # visible in the environment seen by subsequently executed commands. In other # words, on Unix systems, the variable is exported, which is consistent with the # only possible behavior on Windows systems. # rule variable-setting-command ( variable : value ) { local nl = " " ; if [ os.name ] = NT { return "set $(variable)=$(value)$(nl)" ; } else { # (todo) # The following does not work on CYGWIN and needs to be fixed. On # CYGWIN the $(nl) variable holds a Windows new-line \r\n sequence that # messes up the executed export command which then reports that the # passed variable name is incorrect. This is most likely due to the # extra \r character getting interpreted as a part of the variable name. # # Several ideas pop to mind on how to fix this: # * One way would be to separate the commands using the ; shell # command separator. This seems like the quickest possible # solution but I do not know whether this would break code on any # platforms I I have no access to. # * Another would be to not use the terminating $(nl) but that would # require updating all the using code so it does not simply # prepend this variable to its own commands. # * I guess the cleanest solution would be to update Boost Jam to # allow explicitly specifying \n & \r characters in its scripts # instead of always relying only on the 'current OS native newline # sequence'. # # Some code found to depend on this behaviour: # * This Boost Build module. # * __test__ rule. # * path-variable-setting-command rule. # * python.jam toolset. # * xsltproc.jam toolset. # * fop.jam toolset. # (todo) (07.07.2008.) (Jurko) return "$(variable)=$(value)$(nl)export $(variable)$(nl)" ; } } # Returns a command to sets a named shell path variable to the given NATIVE # paths on the current platform. # rule path-variable-setting-command ( variable : paths * ) { local sep = [ os.path-separator ] ; return [ variable-setting-command $(variable) : $(paths:J=$(sep)) ] ; } # Returns a command that prepends the given paths to the named path variable on # the current platform. # rule prepend-path-variable-command ( variable : paths * ) { return [ path-variable-setting-command $(variable) : $(paths) [ os.expand-variable $(variable) ] ] ; } # Return a command which can create a file. If 'r' is result of invocation, then # 'r foobar' will create foobar with unspecified content. What happens if file # already exists is unspecified. # rule file-creation-command ( ) { if [ os.name ] = NT { return "echo. > " ; } else { return "touch " ; } } # Returns a command that may be used for 'touching' files. It is not a real # 'touch' command on NT because it adds an empty line at the end of file but it # works with source files. # rule file-touch-command ( ) { if [ os.name ] = NT { return "echo. >> " ; } else { return "touch " ; } } rule MkDir { # If dir exists, do not update it. Do this even for $(DOT). NOUPDATE $(<) ; if $(<) != $(DOT) && ! $($(<)-mkdir) { # Cheesy gate to prevent multiple invocations on same dir. $(<)-mkdir = true ; # Schedule the mkdir build action. if [ os.name ] = NT { MkDir1-quick-fix-for-windows $(<) ; } else { MkDir1-quick-fix-for-unix $(<) ; } # Prepare a Jam 'dirs' target that can be used to make the build only # construct all the target directories. DEPENDS dirs : $(<) ; # Recursively create parent directories. $(<:P) = $(<)'s parent & we # recurse until root. local s = $(<:P) ; if [ os.name ] = NT { switch $(s) { case *: : s = ; case *:\\ : s = ; } } if $(s) { if $(s) != $(<) { DEPENDS $(<) : $(s) ; MkDir $(s) ; } else { NOTFILE $(s) ; } } } } actions MkDir1 { mkdir "$(<)" } # (todo) # The following quick-fix actions should be replaced using the original MkDir1 # action once Boost Jam gets updated to correctly detect different paths leading # up to the same filesystem target and triggers their build action only once. # (todo) (04.07.2008.) (Jurko) actions MkDir1-quick-fix-for-unix { mkdir -p "$(<)" } actions MkDir1-quick-fix-for-windows { if not exist "$(<)\\" mkdir "$(<)" } actions piecemeal together existing Clean { $(RM) "$(>)" } rule copy { } actions copy { $(CP) "$(>)" "$(<)" } rule RmTemps { } actions quietly updated piecemeal together RmTemps { $(RM) "$(>)" $(IGNORE) } actions hard-link { $(RM) "$(<)" 2$(NULL_OUT) $(NULL_OUT) $(LN) "$(>)" "$(<)" $(NULL_OUT) } # Given a target, as given to a custom tag rule, returns a string formatted # according to the passed format. Format is a list of properties that is # represented in the result. For each element of format the corresponding target # information is obtained and added to the result string. For all, but the # literal, the format value is taken as the as string to prepend to the output # to join the item to the rest of the result. If not given "-" is used as a # joiner. # # The format options can be: # # [joiner] # :: The basename of the target name. # [joiner] # :: The abbreviated toolset tag being used to build the target. # [joiner] # :: Indication of a multi-threaded build. # [joiner] # :: Collective tag of the build runtime. # [joiner] # :: Short version tag taken from the given "version-feature" # in the build properties. Or if not present, the literal # value as the version number. # [joiner] # :: Direct lookup of the given property-name value in the # build properties. /property-name/ is a regular expression. # e.g. will match every toolset. # /otherwise/ # :: The literal value of the format argument. # # For example this format: # # boost_ # # Might return: # # boost_thread-vc80-mt-gd-1_33.dll, or # boost_regex-vc80-gd-1_33.dll # # The returned name also has the target type specific prefix and suffix which # puts it in a ready form to use as the value from a custom tag rule. # rule format-name ( format * : name : type ? : property-set ) { if [ type.is-derived $(type) LIB ] { local result = "" ; for local f in $(format) { switch $(f:G) { case : result += $(name:B) ; case : result += [ join-tag $(f:G=) : [ toolset-tag $(name) : $(type) : $(property-set) ] ] ; case : result += [ join-tag $(f:G=) : [ threading-tag $(name) : $(type) : $(property-set) ] ] ; case : result += [ join-tag $(f:G=) : [ runtime-tag $(name) : $(type) : $(property-set) ] ] ; case : local key = [ MATCH : $(f:G) ] ; local version = [ $(property-set).get <$(key)> ] ; version ?= $(key) ; version = [ MATCH "^([^.]+)[.]([^.]+)[.]?([^.]*)" : $(version) ] ; result += [ join-tag $(f:G=) : $(version[1])_$(version[2]) ] ; case : local key = [ MATCH : $(f:G) ] ; local p0 = [ MATCH <($(key))> : [ $(property-set).raw ] ] ; if $(p0) { local p = [ $(property-set).get <$(p0)> ] ; if $(p) { result += [ join-tag $(f:G=) : $(p) ] ; } } case * : result += $(f:G=) ; } } result = [ virtual-target.add-prefix-and-suffix $(result:J=) : $(type) : $(property-set) ] ; return $(result) ; } } local rule join-tag ( joiner ? : tag ? ) { if ! $(joinder) { joiner = - ; } return $(joiner)$(tag) ; } local rule toolset-tag ( name : type ? : property-set ) { local tag = ; local properties = [ $(property-set).raw ] ; switch [ $(property-set).get ] { case borland* : tag += bcb ; case como* : tag += como ; case cw : tag += cw ; case darwin* : tag += xgcc ; case edg* : tag += edg ; case gcc* : { switch [ $(property-set).get ] { case *mingw* : tag += mgw ; case * : tag += gcc ; } } case intel : if [ $(property-set).get ] = win { tag += iw ; } else { tag += il ; } case kcc* : tag += kcc ; case kylix* : tag += bck ; #case metrowerks* : tag += cw ; #case mingw* : tag += mgw ; case mipspro* : tag += mp ; case msvc* : tag += vc ; case sun* : tag += sw ; case tru64cxx* : tag += tru ; case vacpp* : tag += xlc ; } local version = [ MATCH "([0123456789]+)[.]([0123456789]*)" : $(properties) ] ; # For historical reasons, vc6.0 and vc7.0 use different naming. if $(tag) = vc { if $(version[1]) = 6 { # Cancel minor version. version = 6 ; } else if $(version[1]) = 7 && $(version[2]) = 0 { version = 7 ; } } # On intel, version is not added, because it does not matter and it's the # version of vc used as backend that matters. Ideally, we'd encode the # backend version but that would break compatibility with V1. if $(tag) = iw { version = ; } # On borland, version is not added for compatibility with V1. if $(tag) = bcb { version = ; } tag += $(version) ; return $(tag:J=) ; } local rule threading-tag ( name : type ? : property-set ) { local tag = ; local properties = [ $(property-set).raw ] ; if multi in $(properties) { tag = mt ; } return $(tag:J=) ; } local rule runtime-tag ( name : type ? : property-set ) { local tag = ; local properties = [ $(property-set).raw ] ; if static in $(properties) { tag += s ; } # This is an ugly thing. In V1, there's a code to automatically detect which # properties affect a target. So, if does not affect gcc # toolset, the tag rules won't even see . Similar # functionality in V2 is not implemented yet, so we just check for toolsets # which are known to care about runtime debug. if msvc in $(properties) || stlport in $(properties) || win in $(properties) { if on in $(properties) { tag += g ; } } if on in $(properties) { tag += y ; } if debug in $(properties) { tag += d ; } if stlport in $(properties) { tag += p ; } if hostios in $(properties) { tag += n ; } return $(tag:J=) ; } rule __test__ ( ) { import assert ; local nl = " " ; local save-os = [ modules.peek os : .name ] ; modules.poke os : .name : LINUX ; assert.result "PATH=foo:bar:baz$(nl)export PATH$(nl)" : path-variable-setting-command PATH : foo bar baz ; assert.result "PATH=foo:bar:$PATH$(nl)export PATH$(nl)" : prepend-path-variable-command PATH : foo bar ; modules.poke os : .name : NT ; assert.result "set PATH=foo;bar;baz$(nl)" : path-variable-setting-command PATH : foo bar baz ; assert.result "set PATH=foo;bar;%PATH%$(nl)" : prepend-path-variable-command PATH : foo bar ; modules.poke os : .name : $(save-os) ; }