# 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, for as making directoies 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 unique ID and one or more parameters. A typical example of unique ID is # a condition generated by 'common.check-init-parameters' rule. Other kinds of # ID can be used. Parameters may include any details about the configuration like # 'command', 'path', etc. # # A configuration may be in one of two states: # # - registered - a toolset configuration is registered (by autodetection code # for instance) but is not used. I.e. 'toolset.using' wasn't yet been called # for this configuration. # - used - once called 'toolset.using' marks the configuration as 'used'. # # The main difference between the states 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 a user. class configurations { import errors : error ; 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) { 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) { 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 checks toolset parameters. Each trailing parameter # should be a pair of parameter name and parameter value. # The rule will check that each parameter either has 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 subfeature. This rule will declare subfeature # the first time 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 : * ) { local sig = $(toolset) ; local condition = $(toolset) ; for local index in 2 3 4 5 6 7 8 9 { local name = $($(index)[1]) ; local value = $($(index)[2]) ; if $(value)-is-specified { 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 it that case it will # be impossible to register versionles intel-linux and # intel-win of 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) ; } 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 $(.show-configuration) { ECHO notice: $(condition) ; } return $(condition) ; } # A helper rule to get the command to invoke some tool. # In 'user-provided-command' is not given, tries to find binary # named 'tool' in PATH and in the passed 'additional-path'. Otherwise, # verified 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 to 'tool'. rule get-invocation-command ( 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) { if $(.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) { if $(.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 ] ; } # It's possible, in theory, that user-provided command is OK, but we're # not smart enough to understand that. command = $(user-provided-command) ; } } if ! $(command) { command = $(user-provided-command) ; } return $(command) ; } # Given an invocation command, # return the absolute path to the command. This works even if commnad # has not path element and is present in 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'. # If found in additional paths, returns absolute name. If the tool is found # in several directories, return all paths. # Otherwise, returns 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) ] { 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 absolute path, check that it exists. Returns 'command' # if ok and 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.c to the value of in options # - OPTIONS for compile.c++ to the value of in options # - OPTIOns for compile to the value of in options # - OPTIONs for link to the value of in options rule handle-options ( toolset : condition * : command * : options * ) { # The last parameter ('true') says it's 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" ; } else { RM = rm -f ; CP = cp ; } nl = " " ; rule rm-command ( ) { return $(RM) ; } rule copy-command ( ) { return $(CP) ; } # Returns the command needed to set the 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 ) { if [ os.name ] = NT { return "set $(variable)=$(value)$(nl)" ; } else { return "$(variable)=$(value)$(nl)export $(variable)$(nl)" ; } } # Returns a command that sets the named shell path variable to the # given NATIVE paths to 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 [ modules.peek : 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 ] in NT { return "echo. >> " ; } else { return "touch " ; } } rule MkDir { # If dir exists, don't update it # Do this even for $(DOT). NOUPDATE $(<) ; if $(<) != $(DOT) && ! $($(<)-mkdir) { local s ; # Cheesy gate to prevent multiple invocations on same dir # MkDir1 has the actions # Arrange for jam dirs $(<)-mkdir = true ; MkDir1 $(<) ; Depends dirs : $(<) ; # Recursively make parent directories. # $(<:P) = $(<)'s parent, & we recurse until root s = $(<:P) ; if $(NT) { switch $(s) { case *: : s = ; case *:\\ : s = ; } } if $(s) && $(s) != $(<) { Depends $(<) : $(s) ; MkDir $(s) ; } else if $(s) { NOTFILE $(s) ; } } } actions MkDir1 { mkdir "$(<)" } actions piecemeal together existing Clean { $(RM) "$(>)" } rule copy { } actions copy { $(CP) "$(>)" "$(<)" } rule RmTemps { } actions quietly updated piecemeal together RmTemps { $(RM) "$(>)" $(IGNORE) } # 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. # /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 p = [ $(property-set).get [ MATCH : $(f:G) ] ] ; 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 += ; 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 will 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 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 know to care about runtime debug if msvc in $(properties) || stlport in $(properties) { if on in $(properties) { tag += g ; } } if debug-python 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 save-os = [ modules.peek os : name ] ; modules.poke os : .name : LINUX ; local nl = " " ; 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) ; }