# 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 ; import numbers ; 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. explicitly or by auto-detection # 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 has not been changed. Reports an # error if the configuration is not 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 not 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="")- ; } # We also need to consider requirements on the toolset as we can # configure the same toolset multiple times with different options that # are selected with the requirements. if $(requirement) { sig = $(sig)$(requirement:J=,) ; } 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 not # 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 = [ find-tool $(tool) : $(additional-paths) : $(path-last) ] ; if ! $(command) && $(.debug-configuration) { ECHO "warning:" toolset $(toolset) "initialization:" can not find tool $(tool) ; ECHO "warning:" initialized from [ errors.nearest-user-location ] ; } } else { command = [ check-tool $(user-provided-command) ] ; if ! $(command) && $(.debug-configuration) { ECHO "warning:" toolset $(toolset) "initialization:" ; ECHO "warning:" can not 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 the first # path found. Otherwise, returns an empty string. If 'path-last' is specified, # PATH is searched after 'additional-paths'. # rule find-tool ( name : additional-paths * : path-last ? ) { if $(name:D) { return [ check-tool-aux $(name) ] ; } 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. # local 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 & .cmd files by their unqualified names. || ( ( [ os.name ] = NT ) && ( [ path.exists $(command).bat ] || [ path.exists $(command).cmd ] ) ) { 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 command is an absolute path, # check that it exists. Returns 'command' if ok or empty string otherwise. # local 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.asm 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.asm 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 { NULL_DEVICE = "NUL" ; IGNORE = "2>$(NULL_DEVICE) >$(NULL_DEVICE) & setlocal" ; RM = del /f /q ; CP = copy /b ; LN ?= $(CP) ; # Ugly hack to convince copy to set the timestamp of the destination to the # current time by concatenating the source with a nonexistent file. Note # that this requires /b (binary) as the default when concatenating files is # /a (ascii). WINDOWS-CP-HACK = "+ this-file-does-not-exist-A698EE7806899E69" ; } else if [ os.name ] = VMS { NULL_DEVICE = "NL:" ; PIPE = PIPE ; IGNORE = "2>$(NULL_DEVICE) >$(NULL_DEVICE)" ; RM = DELETE /NOCONF ; CP = COPY /OVERWRITE ; LN = $(CP) ; } else { NULL_DEVICE = "/dev/null" ; IGNORE = "2>$(NULL_DEVICE) >$(NULL_DEVICE)" ; RM = rm -f ; CP = cp ; LN = ln ; } NULL_OUT = ">$(NULL_DEVICE)" ; rule null-device ( ) { return $(NULL_DEVICE) ; } rule rm-command ( ) { return $(RM) ; } rule copy-command ( ) { return $(CP) ; } if "\n" = "n" { # Escape characters not supported so use ugly hacks. Will not work on Cygwin # - see below. nl = " " ; q = "" ; } else { nl = "\n" ; q = "\"" ; } rule newline-char ( ) { return $(nl) ; } # 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 ) { if [ os.name ] = NT { return "set $(variable)=$(value)$(nl)" ; } else if [ os.name ] = VMS { return "$(variable) == $(q)$(value)$(q)$(nl)" ; } else { # If we do not have escape character support in bjam, the cod below # blows up on CYGWIN, since 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. # But we have a check for cygwin in kernel/bootstrap.jam already. return "$(variable)=$(q)$(value)$(q)$(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 { # A few alternative implementations on Windows: # # 'type NUL >> ' # That would construct an empty file instead of a file containing # a space and an end-of-line marker but it would also not change # the target's timestamp in case the file already exists. # # 'type NUL > ' # That would construct an empty file instead of a file containing # a space and an end-of-line marker but it would also destroy an # already existing file by overwriting it with an empty one. # # I guess the best solution would be to allow Boost Jam to define # built-in functions such as 'create a file', 'touch a file' or 'copy a # file' which could be used from inside action code. That would allow # completely portable operations without this kind of kludge. # (22.02.2009.) (Jurko) return "echo. > " ; } else if [ os.name ] = VMS { return "APPEND /NEW NL: " ; } 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 if [ os.name ] = VMS { return "APPEND /NEW NL: " ; } 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. common.mkdir $(<) ; # 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 "$(<)" #} # 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) if [ os.name ] = NT { actions quietly mkdir { if not exist "$(<)\\" mkdir "$(<)" } } else { actions quietly mkdir { mkdir -p "$(<)" } } actions piecemeal together existing Clean { $(RM) "$(>)" } rule copy { } actions copy { $(CP) "$(>)" $(WINDOWS-CP-HACK) "$(<)" } rule RmTemps { } actions quietly updated piecemeal together RmTemps { $(RM) "$(>)" $(IGNORE) } actions hard-link { $(RM) "$(<)" 2$(NULL_OUT) $(NULL_OUT) $(LN) "$(>)" "$(<)" $(NULL_OUT) } if [ os.name ] = VMS { actions mkdir { IF F$PARSE("$(<:W)") .EQS. "" THEN CREATE /DIR $(<:W) } actions piecemeal together existing Clean { $(RM) $(>:WJ=;*,);* } actions copy { $(CP) $(>:WJ=,) $(<:W) } actions quietly updated piecemeal together RmTemps { $(PIPE) $(RM) $(>:WJ=;*,);* $(IGNORE) } actions hard-link { $(PIPE) $(RM) $(>[1]:W);* $(IGNORE) $(PIPE) $(LN) $(>[1]:W) $(<:W) $(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 ) { 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 : result += [ join-tag $(f:G=) : [ qt-tag $(name) : $(type) : $(property-set) ] ] ; case : result += [ join-tag $(f:G=) : [ address-model-tag $(name) : $(type) : $(property-set) ] ] ; case : result += [ join-tag $(f:G=) : [ arch-and-model-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=) ; } } return [ virtual-target.add-prefix-and-suffix $(result:J=) : $(type) : $(property-set) ] ; } local rule join-tag ( joiner ? : tag ? ) { if ! $(joiner) { 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 clang* : { switch [ $(property-set).get ] { case darwin : tag += clang-darwin ; case linux : tag += clang ; case win : tag += clangw ; } } case como* : tag += como ; case cw : tag += cw ; case darwin* : tag += xgcc ; case edg* : tag += edg ; case gcc* : { switch [ $(property-set).get ] { case *windows* : 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 qcc* : tag += qcc ; 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 ; } } # From GCC 5, versioning changes and minor becomes patch if ( $(tag) = gcc || $(tag) = mgw ) && [ numbers.less 4 $(version[1]) ] { version = $(version[1]) ; } # Ditto, from Clang 4 if ( $(tag) = clang || $(tag) = clangw ) && [ numbers.less 3 $(version[1]) ] { version = $(version[1]) ; } # On intel, version is not added, because it does not matter and it is the # version of vc used as backend that matters. Ideally, we should 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 ) { if multi in [ $(property-set).raw ] { return mt ; } } 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 is code to automatically detect which # properties affect a target. So, if does not affect gcc # toolset, the tag rules will not even see . Similar # functionality in V2 is not implemented yet, so we just check for toolsets # known to care about runtime debugging. 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=) ; } # Create a tag for the Qt library version # "4.6.0" will result in tag "qt460" local rule qt-tag ( name : type ? : property-set ) { local v = [ MATCH "([0123456789]+)[.]?([0123456789]*)[.]?([0123456789]*)" : [ $(property-set).get ] ] ; return qt$(v:J=) ; } # Create a tag for the address-model # 64 will simply generate "64" local rule address-model-tag ( name : type ? : property-set ) { return [ $(property-set).get ] ; } # Create a tag for the architecture and model # x86 32 would generate "x32" # This relies on the fact that all architectures start with # unique letters. local rule arch-and-model-tag ( name : type ? : property-set ) { local architecture = [ $(property-set).get ] ; local address-model = [ $(property-set).get ] ; local arch = [ MATCH ^(.) : $(architecture) ] ; return $(arch)$(address-model) ; } rule __test__ ( ) { import assert ; local save-os = [ modules.peek os : .name ] ; modules.poke os : .name : LINUX ; assert.result "PATH=\"foo:bar:baz\"\nexport PATH\n" : path-variable-setting-command PATH : foo bar baz ; assert.result "PATH=\"foo:bar:$PATH\"\nexport PATH\n" : prepend-path-variable-command PATH : foo bar ; modules.poke os : .name : NT ; assert.result "set PATH=foo;bar;baz\n" : path-variable-setting-command PATH : foo bar baz ; assert.result "set PATH=foo;bar;%PATH%\n" : prepend-path-variable-command PATH : foo bar ; modules.poke os : .name : $(save-os) ; }