2
0
mirror of https://github.com/boostorg/build.git synced 2026-02-14 00:32:11 +00:00
Files
build/src/tools/common.jam
2006-11-05 07:13:39 +00:00

815 lines
23 KiB
Plaintext

# 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>$(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=<unspecified>)" ;
}
}
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 <cflags> in options
# - OPTIONS for compile.c++ to the value of <cxxflags> in options
# - OPTIOns for compile to the value of <compileflags> in options
# - OPTIONs for link to the value of <linkflags> 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 <compileflags> : $(options) ] : unchecked ;
toolset.flags $(toolset).compile.c OPTIONS $(condition) :
[ feature.get-values <cflags> : $(options) ] : unchecked ;
toolset.flags $(toolset).compile.c++ OPTIONS $(condition) :
[ feature.get-values <cxxflags> : $(options) ] : unchecked ;
toolset.flags $(toolset).compile.fortran OPTIONS $(condition) :
[ feature.get-values <fflags> : $(options) ] : unchecked ;
toolset.flags $(toolset).link OPTIONS $(condition) :
[ feature.get-values <linkflags> : $(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:
#
# <base>[joiner]
# :: The basename of the target name.
# <toolset>[joiner]
# :: The abbreviated toolset tag being used to build the target.
# <threading>[joiner]
# :: Indication of a multi-threaded build.
# <runtine>[joiner]
# :: Collective tag of the build runtime.
# <version:/version-feature | X.Y[.Z]/>[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.
# <property:/property-name/>[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_ <base> <toolset> <threading> <runtime> <version:boost-version>
#
# 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 <base> :
result += $(name:B) ;
case <toolset> :
result += [ join-tag $(f:G=) :
[ toolset-tag $(name) : $(type) : $(property-set) ] ] ;
case <threading> :
result += [ join-tag $(f:G=) :
[ threading-tag $(name) : $(type) : $(property-set) ] ] ;
case <runtime> :
result += [ join-tag $(f:G=) :
[ runtime-tag $(name) : $(type) : $(property-set) ] ] ;
case <version:*> :
local key = [ MATCH <version:(.*)> : $(f:G) ] ;
local version = [ $(property-set).get <$(key)> ] ;
version ?= $(key) ;
version = [ MATCH "^([^.]+)[.]([^.]+)[.]?([^.]*)"
: $(version) ] ;
result += [ join-tag $(f:G=) : $(version[1])_$(version[2]) ] ;
case <property:*> :
local key = [ MATCH <property:(.*)> : $(f:G) ] ;
local p = [ $(property-set).get [ MATCH <property:(.*)> : $(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 <toolset> ]
{
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 <toolset-gcc:flavor> ]
{
case *mingw* : tag += mgw ;
case * : tag += gcc ;
}
}
case intel :
if [ $(property-set).get <toolset-intel:platform> ] = 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 "<toolset.*version>([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 <threading>multi in $(properties) { tag = mt ; }
return $(tag:J=) ;
}
local rule runtime-tag ( name : type ? : property-set )
{
local tag = ;
local properties = [ $(property-set).raw ] ;
if <runtime-link>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
# <runtime-debugging> does not affect gcc toolset, the
# tag rules won't even see <runtime-debugging>.
# Similar functionality in V2 is not implemented yet, so we just
# check for toolsets which are know to care about runtime debug
if <toolset>msvc in $(properties)
|| <stdlib>stlport in $(properties)
{
if <runtime-debugging>on in $(properties) { tag += g ; }
}
if <variant>debug-python in $(properties) { tag += y ; }
if <variant>debug in $(properties) { tag += d ; }
if <stdlib>stlport in $(properties) { tag += p ; }
if <stdlib-stlport:iostream>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) ;
}