diff options
Diffstat (limited to 'jam-files/boost-build/tools/python.jam')
-rw-r--r-- | jam-files/boost-build/tools/python.jam | 1267 |
1 files changed, 1267 insertions, 0 deletions
diff --git a/jam-files/boost-build/tools/python.jam b/jam-files/boost-build/tools/python.jam new file mode 100644 index 00000000..97a9f9a5 --- /dev/null +++ b/jam-files/boost-build/tools/python.jam @@ -0,0 +1,1267 @@ +# Copyright 2004 Vladimir Prus. +# Distributed under the Boost Software License, Version 1.0. (See +# accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Support for Python and the the Boost.Python library. +# +# This module defines +# +# - a project 'python' with a target 'python' in it, that corresponds to the +# python library +# +# - a main target rule 'python-extension' which can be used to build a python +# extension. +# +# Extensions that use Boost.Python must explicitly link to it. + +import type ; +import testing ; +import generators ; +import project ; +import errors ; +import targets ; +import "class" : new ; +import os ; +import common ; +import toolset ; +import regex ; +import numbers ; +import string ; +import property ; +import sequence ; +import path ; +import feature ; +import set ; +import builtin ; +import version ; + + +# Make this module a project. +project.initialize $(__name__) ; +project python ; + +# Save the project so that if 'init' is called several times we define new +# targets in the python project, not in whatever project we were called by. +.project = [ project.current ] ; + +# Dynamic linker lib. Necessary to specify it explicitly on some platforms. +lib dl ; +# This contains 'openpty' function need by python. Again, on some system need to +# pass this to linker explicitly. +lib util ; +# Python uses pthread symbols. +lib pthread ; +# Extra library needed by phtread on some platforms. +lib rt ; + +# The pythonpath feature specifies additional elements for the PYTHONPATH +# environment variable, set by run-pyd. For example, pythonpath can be used to +# access Python modules that are part of the product being built, but are not +# installed in the development system's default paths. +feature.feature pythonpath : : free optional path ; + +# Initializes the Python toolset. Note that all parameters are optional. +# +# - version -- the version of Python to use. Should be in Major.Minor format, +# for example 2.3. Do not include the subminor version. +# +# - cmd-or-prefix: Preferably, a command that invokes a Python interpreter. +# Alternatively, the installation prefix for Python libraries and includes. If +# empty, will be guessed from the version, the platform's installation +# patterns, and the python executables that can be found in PATH. +# +# - includes: the include path to Python headers. If empty, will be guessed. +# +# - libraries: the path to Python library binaries. If empty, will be guessed. +# On MacOS/Darwin, you can also pass the path of the Python framework. +# +# - condition: if specified, should be a set of properties that are matched +# against the build configuration when Boost.Build selects a Python +# configuration to use. +# +# - extension-suffix: A string to append to the name of extension modules before +# the true filename extension. Ordinarily we would just compute this based on +# the value of the <python-debugging> feature. However ubuntu's python-dbg +# package uses the windows convention of appending _d to debug-build extension +# modules. We have no way of detecting ubuntu, or of probing python for the +# "_d" requirement, and if you configure and build python using +# --with-pydebug, you'll be using the standard *nix convention. Defaults to "" +# (or "_d" when targeting windows and <python-debugging> is set). +# +# Example usage: +# +# using python : 2.3 ; +# using python : 2.3 : /usr/local/bin/python ; +# +rule init ( version ? : cmd-or-prefix ? : includes * : libraries ? + : condition * : extension-suffix ? ) +{ + project.push-current $(.project) ; + + debug-message Configuring python... ; + for local v in version cmd-or-prefix includes libraries condition + { + if $($(v)) + { + debug-message " user-specified "$(v): \"$($(v))\" ; + } + } + + configure $(version) : $(cmd-or-prefix) : $(includes) : $(libraries) : $(condition) : $(extension-suffix) ; + + project.pop-current ; +} + +# A simpler version of SHELL that grabs stderr as well as stdout, but returns +# nothing if there was an error. +# +local rule shell-cmd ( cmd ) +{ + debug-message running command '$(cmd)" 2>&1"' ; + x = [ SHELL $(cmd)" 2>&1" : exit-status ] ; + if $(x[2]) = 0 + { + return $(x[1]) ; + } + else + { + return ; + } +} + + +# Try to identify Cygwin symlinks. Invoking such a file directly as an NT +# executable from a native Windows build of bjam would be fatal to the bjam +# process. One /can/ invoke them through sh.exe or bash.exe, if you can prove +# that those are not also symlinks. ;-) +# +# If a symlink is found returns non-empty; we try to extract the target of the +# symlink from the file and return that. +# +# Note: 1. only works on NT 2. path is a native path. +local rule is-cygwin-symlink ( path ) +{ + local is-symlink = ; + + # Look for a file with the given path having the S attribute set, as cygwin + # symlinks do. /-C means "do not use thousands separators in file sizes." + local dir-listing = [ shell-cmd "DIR /-C /A:S \""$(path)"\"" ] ; + + if $(dir-listing) + { + # Escape any special regex characters in the base part of the path. + local base-pat = [ regex.escape $(path:D=) : ].[()*+?|\\$^ : \\ ] ; + + # Extract the file's size from the directory listing. + local size-of-system-file = [ MATCH "([0-9]+) "$(base-pat) : $(dir-listing) : 1 ] ; + + # If the file has a reasonably small size, look for the special symlink + # identification text. + if $(size-of-system-file) && [ numbers.less $(size-of-system-file) 1000 ] + { + local link = [ SHELL "FIND /OFF \"!<symlink>\" \""$(path)"\" 2>&1" ] ; + if $(link[2]) != 0 + { + local nl = " + +" ; + is-symlink = [ MATCH ".*!<symlink>([^"$(nl)"]*)" : $(link[1]) : 1 ] ; + if $(is-symlink) + { + is-symlink = [ *nix-path-to-native $(is-symlink) ] ; + is-symlink = $(is-symlink:R=$(path:D)) ; + } + + } + } + } + return $(is-symlink) ; +} + + +# Append ext to each member of names that does not contain '.'. +# +local rule default-extension ( names * : ext * ) +{ + local result ; + for local n in $(names) + { + switch $(n) + { + case *.* : result += $(n) ; + case * : result += $(n)$(ext) ; + } + } + return $(result) ; +} + + +# Tries to determine whether invoking "cmd" would actually attempt to launch a +# cygwin symlink. +# +# Note: only works on NT. +# +local rule invokes-cygwin-symlink ( cmd ) +{ + local dirs = $(cmd:D) ; + if ! $(dirs) + { + dirs = . [ os.executable-path ] ; + } + local base = [ default-extension $(cmd:D=) : .exe .cmd .bat ] ; + local paths = [ GLOB $(dirs) : $(base) ] ; + if $(paths) + { + # Make sure we have not run into a Cygwin symlink. Invoking such a file + # as an NT executable would be fatal for the bjam process. + return [ is-cygwin-symlink $(paths[1]) ] ; + } +} + + +local rule debug-message ( message * ) +{ + if --debug-configuration in [ modules.peek : ARGV ] + { + ECHO notice: [python-cfg] $(message) ; + } +} + + +# Like W32_GETREG, except prepend HKEY_CURRENT_USER\SOFTWARE and +# HKEY_LOCAL_MACHINE\SOFTWARE to the first argument, returning the first result +# found. Also accounts for the fact that on 64-bit machines, 32-bit software has +# its own area, under SOFTWARE\Wow6432node. +# +local rule software-registry-value ( path : data ? ) +{ + local result ; + for local root in HKEY_CURRENT_USER HKEY_LOCAL_MACHINE + { + for local x64elt in "" Wow6432node\\ # Account for 64-bit windows + { + if ! $(result) + { + result = [ W32_GETREG $(root)\\SOFTWARE\\$(x64elt)$(path) : $(data) ] ; + } + } + + } + return $(result) ; +} + + +.windows-drive-letter-re = ^([A-Za-z]):[\\/](.*) ; +.cygwin-drive-letter-re = ^/cygdrive/([a-z])/(.*) ; + +.working-directory = [ PWD ] ; +.working-drive-letter = [ SUBST $(.working-directory) $(.windows-drive-letter-re) $1 ] ; +.working-drive-letter ?= [ SUBST $(.working-directory) $(.cygwin-drive-letter-re) $1 ] ; + + +local rule windows-to-cygwin-path ( path ) +{ + # If path is rooted with a drive letter, rewrite it using the /cygdrive + # mountpoint. + local p = [ SUBST $(path:T) $(.windows-drive-letter-re) /cygdrive/$1/$2 ] ; + + # Else if path is rooted without a drive letter, use the working directory. + p ?= [ SUBST $(path:T) ^/(.*) /cygdrive/$(.working-drive-letter:L)/$2 ] ; + + # Else return the path unchanged. + return $(p:E=$(path:T)) ; +} + + +# :W only works in Cygwin builds of bjam. This one works on NT builds as well. +# +local rule cygwin-to-windows-path ( path ) +{ + path = $(path:R="") ; # strip any trailing slash + + local drive-letter = [ SUBST $(path) $(.cygwin-drive-letter-re) $1:/$2 ] ; + if $(drive-letter) + { + path = $(drive-letter) ; + } + else if $(path:R=/x) = $(path) # already rooted? + { + # Look for a cygwin mount that includes each head sequence in $(path). + local head = $(path) ; + local tail = "" ; + + while $(head) + { + local root = [ software-registry-value + "Cygnus Solutions\\Cygwin\\mounts v2\\"$(head) : native ] ; + + if $(root) + { + path = $(tail:R=$(root)) ; + head = ; + } + tail = $(tail:R=$(head:D=)) ; + + if $(head) = / + { + head = ; + } + else + { + head = $(head:D) ; + } + } + } + return [ regex.replace $(path:R="") / \\ ] ; +} + + +# Convert a *nix path to native. +# +local rule *nix-path-to-native ( path ) +{ + if [ os.name ] = NT + { + path = [ cygwin-to-windows-path $(path) ] ; + } + return $(path) ; +} + + +# Convert an NT path to native. +# +local rule windows-path-to-native ( path ) +{ + if [ os.name ] = NT + { + return $(path) ; + } + else + { + return [ windows-to-cygwin-path $(path) ] ; + } +} + + +# Return nonempty if path looks like a windows path, i.e. it starts with a drive +# letter or contains backslashes. +# +local rule guess-windows-path ( path ) +{ + return [ SUBST $(path) ($(.windows-drive-letter-re)|.*([\\]).*) $1 ] ; +} + + +local rule path-to-native ( paths * ) +{ + local result ; + + for local p in $(paths) + { + if [ guess-windows-path $(p) ] + { + result += [ windows-path-to-native $(p) ] ; + } + else + { + result += [ *nix-path-to-native $(p:T) ] ; + } + } + return $(result) ; +} + + +# Validate the version string and extract the major/minor part we care about. +# +local rule split-version ( version ) +{ + local major-minor = [ MATCH ^([0-9]+)\.([0-9]+)(.*)$ : $(version) : 1 2 3 ] ; + if ! $(major-minor[2]) || $(major-minor[3]) + { + ECHO "Warning: \"using python\" expects a two part (major, minor) version number; got" $(version) instead ; + + # Add a zero to account for the missing digit if necessary. + major-minor += 0 ; + } + + return $(major-minor[1]) $(major-minor[2]) ; +} + + +# Build a list of versions from 3.0 down to 1.5. Because bjam can not enumerate +# registry sub-keys, we have no way of finding a version with a 2-digit minor +# version, e.g. 2.10 -- let us hope that never happens. +# +.version-countdown = ; +for local v in [ numbers.range 15 30 ] +{ + .version-countdown = [ SUBST $(v) (.)(.*) $1.$2 ] $(.version-countdown) ; +} + + +local rule windows-installed-pythons ( version ? ) +{ + version ?= $(.version-countdown) ; + local interpreters ; + + for local v in $(version) + { + local install-path = [ + software-registry-value "Python\\PythonCore\\"$(v)"\\InstallPath" ] ; + + if $(install-path) + { + install-path = [ windows-path-to-native $(install-path) ] ; + debug-message Registry indicates Python $(v) installed at \"$(install-path)\" ; + } + + interpreters += $(:E=python:R=$(install-path)) ; + } + return $(interpreters) ; +} + + +local rule darwin-installed-pythons ( version ? ) +{ + version ?= $(.version-countdown) ; + + local prefix + = [ GLOB /System/Library/Frameworks /Library/Frameworks + : Python.framework ] ; + + return $(prefix)/Versions/$(version)/bin/python ; +} + + +# Assume "python-cmd" invokes a python interpreter and invoke it to extract all +# the information we care about from its "sys" module. Returns void if +# unsuccessful. +# +local rule probe ( python-cmd ) +{ + # Avoid invoking a Cygwin symlink on NT. + local skip-symlink ; + if [ os.name ] = NT + { + skip-symlink = [ invokes-cygwin-symlink $(python-cmd) ] ; + } + + if $(skip-symlink) + { + debug-message -------------------------------------------------------------------- ; + debug-message \"$(python-cmd)\" would attempt to invoke a Cygwin symlink, ; + debug-message causing a bjam built for Windows to hang. ; + debug-message ; + debug-message If you intend to target a Cygwin build of Python, please ; + debug-message replace the path to the link with the path to a real executable ; + debug-message (guessing: \"$(skip-symlink)\") "in" your 'using python' line ; + debug-message "in" user-config.jam or site-config.jam. Do not forget to escape ; + debug-message backslashes ; + debug-message -------------------------------------------------------------------- ; + } + else + { + # Prepare a List of Python format strings and expressions that can be + # used to print the constants we want from the sys module. + + # We do not really want sys.version since that is a complicated string, + # so get the information from sys.version_info instead. + local format = "version=%d.%d" ; + local exprs = "version_info[0]" "version_info[1]" ; + + for local s in $(sys-elements[2-]) + { + format += $(s)=%s ; + exprs += $(s) ; + } + + # Invoke Python and ask it for all those values. + if [ version.check-jam-version 3 1 17 ] || ( [ os.name ] != NT ) + { + # Prior to version 3.1.17 Boost Jam's SHELL command did not support + # quoted commands correctly on Windows. This means that on that + # platform we do not support using a Python command interpreter + # executable whose path contains a space character. + python-cmd = \"$(python-cmd)\" ; + } + local full-cmd = + $(python-cmd)" -c \"from sys import *; print('"$(format:J=\\n)"' % ("$(exprs:J=,)"))\"" ; + + local output = [ shell-cmd $(full-cmd) ] ; + if $(output) + { + # Parse the output to get all the results. + local nl = " + +" ; + for s in $(sys-elements) + { + # These variables are expected to be declared local in the + # caller, so Jam's dynamic scoping will set their values there. + sys.$(s) = [ SUBST $(output) \\<$(s)=([^$(nl)]+) $1 ] ; + } + } + return $(output) ; + } +} + + +# Make sure the "libraries" and "includes" variables (in an enclosing scope) +# have a value based on the information given. +# +local rule compute-default-paths ( target-os : version ? : prefix ? : + exec-prefix ? ) +{ + exec-prefix ?= $(prefix) ; + + if $(target-os) = windows + { + # The exec_prefix is where you're supposed to look for machine-specific + # libraries. + local default-library-path = $(exec-prefix)\\libs ; + local default-include-path = $(:E=Include:R=$(prefix)) ; + + # If the interpreter was found in a directory called "PCBuild" or + # "PCBuild8," assume we're looking at a Python built from the source + # distro, and go up one additional level to the default root. Otherwise, + # the default root is the directory where the interpreter was found. + + # We ask Python itself what the executable path is in case of + # intermediate symlinks or shell scripts. + local executable-dir = $(sys.executable:D) ; + + if [ MATCH ^(PCBuild) : $(executable-dir:D=) ] + { + debug-message "This Python appears to reside in a source distribution;" ; + debug-message "prepending \""$(executable-dir)"\" to default library search path" ; + + default-library-path = $(executable-dir) $(default-library-path) ; + + default-include-path = $(:E=PC:R=$(executable-dir:D)) $(default-include-path) ; + + debug-message "and \""$(default-include-path[1])"\" to default #include path" ; + } + + libraries ?= $(default-library-path) ; + includes ?= $(default-include-path) ; + } + else + { + includes ?= $(prefix)/include/python$(version) ; + + local lib = $(exec-prefix)/lib ; + libraries ?= $(lib)/python$(version)/config $(lib) ; + } +} + +# The version of the python interpreter to use. +feature.feature python : : propagated ; +feature.feature python.interpreter : : free ; + +toolset.flags python.capture-output PYTHON : <python.interpreter> ; + +# +# Support for Python configured --with-pydebug +# +feature.feature python-debugging : off on : propagated ; +builtin.variant debug-python : debug : <python-debugging>on ; + + +# Return a list of candidate commands to try when looking for a Python +# interpreter. prefix is expected to be a native path. +# +local rule candidate-interpreters ( version ? : prefix ? : target-os ) +{ + local bin-path = bin ; + if $(target-os) = windows + { + # On Windows, look in the root directory itself and, to work with the + # result of a build-from-source, the PCBuild directory. + bin-path = PCBuild8 PCBuild "" ; + } + + bin-path = $(bin-path:R=$(prefix)) ; + + if $(target-os) in windows darwin + { + return # Search: + $(:E=python:R=$(bin-path)) # Relative to the prefix, if any + python # In the PATH + [ $(target-os)-installed-pythons $(version) ] # Standard install locations + ; + } + else + { + # Search relative to the prefix, or if none supplied, in PATH. + local unversioned = $(:E=python:R=$(bin-path:E=)) ; + + # If a version was specified, look for a python with that specific + # version appended before looking for one called, simply, "python" + return $(unversioned)$(version) $(unversioned) ; + } +} + + +# Compute system library dependencies for targets linking with static Python +# libraries. +# +# On many systems, Python uses libraries such as pthreads or libdl. Since static +# libraries carry no library dependency information of their own that the linker +# can extract, these extra dependencies have to be given explicitly on the link +# line of the client. The information about these dependencies is packaged into +# the "python" target below. +# +# Even where Python itself uses pthreads, it never allows extension modules to +# be entered concurrently (unless they explicitly give up the interpreter lock). +# Therefore, extension modules do not need the efficiency overhead of threadsafe +# code as produced by <threading>multi, and we handle libpthread along with +# other libraries here. Note: this optimization is based on an assumption that +# the compiler generates link-compatible code in both the single- and +# multi-threaded cases, and that system libraries do not change their ABIs +# either. +# +# Returns a list of usage-requirements that link to the necessary system +# libraries. +# +local rule system-library-dependencies ( target-os ) +{ + switch $(target-os) + { + case s[uo][nl]* : # solaris, sun, sunos + # Add a librt dependency for the gcc toolset on SunOS (the sun + # toolset adds -lrt unconditionally). While this appears to + # duplicate the logic already in gcc.jam, it does not as long as + # we are not forcing <threading>multi. + + # On solaris 10, distutils.sysconfig.get_config_var('LIBS') yields + # '-lresolv -lsocket -lnsl -lrt -ldl'. However, that does not seem + # to be the right list for extension modules. For example, on my + # installation, adding -ldl causes at least one test to fail because + # the library can not be found and removing it causes no failures. + + # Apparently, though, we need to add -lrt for gcc. + return <toolset>gcc:<library>rt ; + + case osf : return <library>pthread <toolset>gcc:<library>rt ; + + case qnx* : return ; + case darwin : return ; + case windows : return ; + + case hpux : return <library>rt ; + case *bsd : return <library>pthread <toolset>gcc:<library>util ; + + case aix : return <library>pthread <library>dl ; + + case * : return <library>pthread <library>dl + <toolset>gcc:<library>util <toolset-intel:platform>linux:<library>util ; + } +} + + +# Declare a target to represent Python's library. +# +local rule declare-libpython-target ( version ? : requirements * ) +{ + # Compute the representation of Python version in the name of Python's + # library file. + local lib-version = $(version) ; + if <target-os>windows in $(requirements) + { + local major-minor = [ split-version $(version) ] ; + lib-version = $(major-minor:J="") ; + if <python-debugging>on in $(requirements) + { + lib-version = $(lib-version)_d ; + } + } + + if ! $(lib-version) + { + ECHO *** warning: could not determine Python version, which will ; + ECHO *** warning: probably prevent us from linking with the python ; + ECHO *** warning: library. Consider explicitly passing the version ; + ECHO *** warning: to 'using python'. ; + } + + # Declare it. + lib python.lib : : <name>python$(lib-version) $(requirements) ; +} + + +# Implementation of init. +local rule configure ( version ? : cmd-or-prefix ? : includes * : libraries ? : + condition * : extension-suffix ? ) +{ + local prefix ; + local exec-prefix ; + local cmds-to-try ; + local interpreter-cmd ; + + local target-os = [ feature.get-values target-os : $(condition) ] ; + target-os ?= [ feature.defaults target-os ] ; + target-os = $(target-os:G=) ; + + if $(target-os) = windows && <python-debugging>on in $(condition) + { + extension-suffix ?= _d ; + } + extension-suffix ?= "" ; + + # Normalize and dissect any version number. + local major-minor ; + if $(version) + { + major-minor = [ split-version $(version) ] ; + version = $(major-minor:J=.) ; + } + + local cmds-to-try ; + + if ! $(cmd-or-prefix) || [ GLOB $(cmd-or-prefix) : * ] + { + # If the user did not pass a command, whatever we got was a prefix. + prefix = $(cmd-or-prefix) ; + cmds-to-try = [ candidate-interpreters $(version) : $(prefix) : $(target-os) ] ; + } + else + { + # Work with the command the user gave us. + cmds-to-try = $(cmd-or-prefix) ; + + # On Windows, do not nail down the interpreter command just yet in case + # the user specified something that turns out to be a cygwin symlink, + # which could bring down bjam if we invoke it. + if $(target-os) != windows + { + interpreter-cmd = $(cmd-or-prefix) ; + } + } + + # Values to use in case we can not really find anything in the system. + local fallback-cmd = $(cmds-to-try[1]) ; + local fallback-version ; + + # Anything left to find or check? + if ! ( $(interpreter-cmd) && $(includes) && $(libraries) ) + { + # Values to be extracted from python's sys module. These will be set by + # the probe rule, above, using Jam's dynamic scoping. + local sys-elements = version platform prefix exec_prefix executable ; + local sys.$(sys-elements) ; + + # Compute the string Python's sys.platform needs to match. If not + # targeting Windows or cygwin we will assume only native builds can + # possibly run, so we will not require a match and we leave sys.platform + # blank. + local platform ; + switch $(target-os) + { + case windows : platform = win32 ; + case cygwin : platform = cygwin ; + } + + while $(cmds-to-try) + { + # Pop top command. + local cmd = $(cmds-to-try[1]) ; + cmds-to-try = $(cmds-to-try[2-]) ; + + debug-message Checking interpreter command \"$(cmd)\"... ; + if [ probe $(cmd) ] + { + fallback-version ?= $(sys.version) ; + + # Check for version/platform validity. + for local x in version platform + { + if $($(x)) && $($(x)) != $(sys.$(x)) + { + debug-message ...$(x) "mismatch (looking for" + $($(x)) but found $(sys.$(x))")" ; + cmd = ; + } + } + + if $(cmd) + { + debug-message ...requested configuration matched! ; + + exec-prefix = $(sys.exec_prefix) ; + + compute-default-paths $(target-os) : $(sys.version) : + $(sys.prefix) : $(sys.exec_prefix) ; + + version = $(sys.version) ; + interpreter-cmd ?= $(cmd) ; + cmds-to-try = ; # All done. + } + } + else + { + debug-message ...does not invoke a working interpreter ; + } + } + } + + # Anything left to compute? + if $(includes) && $(libraries) + { + .configured = true ; + } + else + { + version ?= $(fallback-version) ; + version ?= 2.5 ; + exec-prefix ?= $(prefix) ; + compute-default-paths $(target-os) : $(version) : $(prefix:E=) ; + } + + if ! $(interpreter-cmd) + { + fallback-cmd ?= python ; + debug-message No working Python interpreter found. ; + if [ os.name ] != NT || ! [ invokes-cygwin-symlink $(fallback-cmd) ] + { + interpreter-cmd = $(fallback-cmd) ; + debug-message falling back to \"$(interpreter-cmd)\" ; + } + } + + includes = [ path-to-native $(includes) ] ; + libraries = [ path-to-native $(libraries) ] ; + + debug-message "Details of this Python configuration:" ; + debug-message " interpreter command:" \"$(interpreter-cmd:E=<empty>)\" ; + debug-message " include path:" \"$(includes:E=<empty>)\" ; + debug-message " library path:" \"$(libraries:E=<empty>)\" ; + if $(target-os) = windows + { + debug-message " DLL search path:" \"$(exec-prefix:E=<empty>)\" ; + } + + # + # End autoconfiguration sequence. + # + local target-requirements = $(condition) ; + + # Add the version, if any, to the target requirements. + if $(version) + { + if ! $(version) in [ feature.values python ] + { + feature.extend python : $(version) ; + } + target-requirements += <python>$(version:E=default) ; + } + + target-requirements += <target-os>$(target-os) ; + + # See if we can find a framework directory on darwin. + local framework-directory ; + if $(target-os) = darwin + { + # Search upward for the framework directory. + local framework-directory = $(libraries[-1]) ; + while $(framework-directory:D=) && $(framework-directory:D=) != Python.framework + { + framework-directory = $(framework-directory:D) ; + } + + if $(framework-directory:D=) = Python.framework + { + debug-message framework directory is \"$(framework-directory)\" ; + } + else + { + debug-message "no framework directory found; using library path" ; + framework-directory = ; + } + } + + local dll-path = $(libraries) ; + + # Make sure that we can find the Python DLL on Windows. + if ( $(target-os) = windows ) && $(exec-prefix) + { + dll-path += $(exec-prefix) ; + } + + # + # Prepare usage requirements. + # + local usage-requirements = [ system-library-dependencies $(target-os) ] ; + usage-requirements += <include>$(includes) <python.interpreter>$(interpreter-cmd) ; + if <python-debugging>on in $(condition) + { + if $(target-os) = windows + { + # In pyconfig.h, Py_DEBUG is set if _DEBUG is set. If we define + # Py_DEBUG we will get multiple definition warnings. + usage-requirements += <define>_DEBUG ; + } + else + { + usage-requirements += <define>Py_DEBUG ; + } + } + + # Global, but conditional, requirements to give access to the interpreter + # for general utilities, like other toolsets, that run Python scripts. + toolset.add-requirements + $(target-requirements:J=,):<python.interpreter>$(interpreter-cmd) ; + + # Register the right suffix for extensions. + register-extension-suffix $(extension-suffix) : $(target-requirements) ; + + # + # Declare the "python" target. This should really be called + # python_for_embedding. + # + + if $(framework-directory) + { + alias python + : + : $(target-requirements) + : + : $(usage-requirements) <framework>$(framework-directory) + ; + } + else + { + declare-libpython-target $(version) : $(target-requirements) ; + + # This is an evil hack. On, Windows, when Python is embedded, nothing + # seems to set up sys.path to include Python's standard library + # (http://article.gmane.org/gmane.comp.python.general/544986). The evil + # here, aside from the workaround necessitated by Python's bug, is that: + # + # a. we're guessing the location of the python standard library from the + # location of pythonXX.lib + # + # b. we're hijacking the <testing.launcher> property to get the + # environment variable set up, and the user may want to use it for + # something else (e.g. launch the debugger). + local set-PYTHONPATH ; + if $(target-os) = windows + { + set-PYTHONPATH = [ common.prepend-path-variable-command PYTHONPATH : + $(libraries:D)/Lib ] ; + } + + alias python + : + : $(target-requirements) + : + # Why python.lib must be listed here instead of along with the + # system libs is a mystery, but if we do not do it, on cygwin, + # -lpythonX.Y never appears in the command line (although it does on + # linux). + : $(usage-requirements) + <testing.launcher>$(set-PYTHONPATH) + <library-path>$(libraries) <library>python.lib + ; + } + + # On *nix, we do not want to link either Boost.Python or Python extensions + # to libpython, because the Python interpreter itself provides all those + # symbols. If we linked to libpython, we would get duplicate symbols. So + # declare two targets -- one for building extensions and another for + # embedding. + # + # Unlike most *nix systems, Mac OS X's linker does not permit undefined + # symbols when linking a shared library. So, we still need to link against + # the Python framework, even when building extensions. Note that framework + # builds of Python always use shared libraries, so we do not need to worry + # about duplicate Python symbols. + if $(target-os) in windows cygwin darwin + { + alias python_for_extensions : python : $(target-requirements) ; + } + # On AIX we need Python extensions and Boost.Python to import symbols from + # the Python interpreter. Dynamic libraries opened with dlopen() do not + # inherit the symbols from the Python interpreter. + else if $(target-os) = aix + { + alias python_for_extensions + : + : $(target-requirements) + : + : $(usage-requirements) <linkflags>-Wl,-bI:$(libraries[1])/python.exp + ; + } + else + { + alias python_for_extensions + : + : $(target-requirements) + : + : $(usage-requirements) + ; + } +} + + +rule configured ( ) +{ + return $(.configured) ; +} + + +type.register PYTHON_EXTENSION : : SHARED_LIB ; + + +local rule register-extension-suffix ( root : condition * ) +{ + local suffix ; + + switch [ feature.get-values target-os : $(condition) ] + { + case windows : suffix = pyd ; + case cygwin : suffix = dll ; + case hpux : + { + if [ feature.get-values python : $(condition) ] in 1.5 1.6 2.0 2.1 2.2 2.3 2.4 + { + suffix = sl ; + } + else + { + suffix = so ; + } + } + case * : suffix = so ; + } + + type.set-generated-target-suffix PYTHON_EXTENSION : $(condition) : <$(root).$(suffix)> ; +} + + +# Unset 'lib' prefix for PYTHON_EXTENSION +type.set-generated-target-prefix PYTHON_EXTENSION : : "" ; + + +rule python-extension ( name : sources * : requirements * : default-build * : + usage-requirements * ) +{ + if [ configured ] + { + requirements += <use>/python//python_for_extensions ; + } + requirements += <suppress-import-lib>true ; + + local project = [ project.current ] ; + + targets.main-target-alternative + [ new typed-target $(name) : $(project) : PYTHON_EXTENSION + : [ targets.main-target-sources $(sources) : $(name) ] + : [ targets.main-target-requirements $(requirements) : $(project) ] + : [ targets.main-target-default-build $(default-build) : $(project) ] + ] ; +} + +IMPORT python : python-extension : : python-extension ; + +rule py2to3 +{ + common.copy $(>) $(<) ; + 2to3 $(<) ; +} + +actions 2to3 +{ + 2to3 -wn "$(<)" + 2to3 -dwn "$(<)" +} + + +# Support for testing. +type.register PY : py ; +type.register RUN_PYD_OUTPUT ; +type.register RUN_PYD : : TEST ; + + +class python-test-generator : generator +{ + import set ; + + rule __init__ ( * : * ) + { + generator.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + self.composing = true ; + } + + rule run ( project name ? : property-set : sources * : multiple ? ) + { + local pyversion = [ $(property-set).get <python> ] ; + local python ; + local other-pythons ; + + # Make new target that converting Python source by 2to3 when running with Python 3. + local rule make-2to3-source ( source ) + { + if $(pyversion) >= 3.0 + { + local a = [ new action $(source) : python.py2to3 : $(property-set) ] ; + local t = [ utility.basename [ $(s).name ] ] ; + local p = [ new file-target $(t) : PY : $(project) : $(a) ] ; + return $(p) ; + } + else + { + return $(source) ; + } + } + + for local s in $(sources) + { + if [ $(s).type ] = PY + { + if ! $(python) + { + # First Python source ends up on command line. + python = [ make-2to3-source $(s) ] ; + + } + else + { + # Other Python sources become dependencies. + other-pythons += [ make-2to3-source $(s) ] ; + } + } + } + + local extensions ; + for local s in $(sources) + { + if [ $(s).type ] = PYTHON_EXTENSION + { + extensions += $(s) ; + } + } + + local libs ; + for local s in $(sources) + { + if [ type.is-derived [ $(s).type ] LIB ] + && ! $(s) in $(extensions) + { + libs += $(s) ; + } + } + + local new-sources ; + for local s in $(sources) + { + if [ type.is-derived [ $(s).type ] CPP ] + { + local name = [ utility.basename [ $(s).name ] ] ; + if $(name) = [ utility.basename [ $(python).name ] ] + { + name = $(name)_ext ; + } + local extension = [ generators.construct $(project) $(name) : + PYTHON_EXTENSION : $(property-set) : $(s) $(libs) ] ; + + # The important part of usage requirements returned from + # PYTHON_EXTENSION generator are xdll-path properties that will + # allow us to find the python extension at runtime. + property-set = [ $(property-set).add $(extension[1]) ] ; + + # Ignore usage requirements. We're a top-level generator and + # nobody is going to use what we generate. + new-sources += $(extension[2-]) ; + } + } + + property-set = [ $(property-set).add-raw <dependency>$(other-pythons) ] ; + + result = [ construct-result $(python) $(extensions) $(new-sources) : + $(project) $(name) : $(property-set) ] ; + } +} + + +generators.register + [ new python-test-generator python.capture-output : : RUN_PYD_OUTPUT ] ; + +generators.register-standard testing.expect-success + : RUN_PYD_OUTPUT : RUN_PYD ; + + +# There are two different ways of spelling OS names. One is used for [ os.name ] +# and the other is used for the <host-os> and <target-os> properties. Until that +# is remedied, this sets up a crude mapping from the latter to the former, that +# will work *for the purposes of cygwin/NT cross-builds only*. Could not think +# of a better name than "translate". +# +.translate-os-windows = NT ; +.translate-os-cygwin = CYGWIN ; +local rule translate-os ( src-os ) +{ + local x = $(.translate-os-$(src-os)) [ os.name ] ; + return $(x[1]) ; +} + + +# Extract the path to a single ".pyd" source. This is used to build the +# PYTHONPATH for running bpl tests. +# +local rule pyd-pythonpath ( source ) +{ + return [ on $(source) return $(LOCATE) $(SEARCH) ] ; +} + + +# The flag settings on testing.capture-output do not apply to python.capture +# output at the moment. Redo this explicitly. +toolset.flags python.capture-output ARGS <testing.arg> ; + + +rule capture-output ( target : sources * : properties * ) +{ + # Setup up a proper DLL search path. Here, $(sources[1]) is a python module + # and $(sources[2]) is a DLL. Only $(sources[1]) is passed to + # testing.capture-output, so RUN_PATH variable on $(sources[2]) is not + # consulted. Move it over explicitly. + RUN_PATH on $(sources[1]) = [ on $(sources[2-]) return $(RUN_PATH) ] ; + + PYTHONPATH = [ sequence.transform pyd-pythonpath : $(sources[2-]) ] ; + PYTHONPATH += [ feature.get-values pythonpath : $(properties) ] ; + + # After test is run, we remove the Python module, but not the Python script. + testing.capture-output $(target) : $(sources[1]) : $(properties) : + $(sources[2-]) ; + + # PYTHONPATH is different; it will be interpreted by whichever Python is + # invoked and so must follow path rules for the target os. The only OSes + # where we can run python for other OSes currently are NT and CYGWIN so we + # only need to handle those cases. + local target-os = [ feature.get-values target-os : $(properties) ] ; + # Oddly, host-os is not in properties, so grab the default value. + local host-os = [ feature.defaults host-os ] ; + host-os = $(host-os:G=) ; + if $(target-os) != $(host-os) + { + PYTHONPATH = [ sequence.transform $(host-os)-to-$(target-os)-path : + $(PYTHONPATH) ] ; + } + local path-separator = [ os.path-separator [ translate-os $(target-os) ] ] ; + local set-PYTHONPATH = [ common.variable-setting-command PYTHONPATH : + $(PYTHONPATH:J=$(path-separator)) ] ; + LAUNCHER on $(target) = $(set-PYTHONPATH) [ on $(target) return \"$(PYTHON)\" ] ; +} + + +rule bpl-test ( name : sources * : requirements * ) +{ + local s ; + sources ?= $(name).py $(name).cpp ; + return [ testing.make-test run-pyd : $(sources) /boost/python//boost_python + : $(requirements) : $(name) ] ; +} + + +IMPORT $(__name__) : bpl-test : : bpl-test ; |