2
0
mirror of https://github.com/boostorg/build.git synced 2026-02-19 02:12:17 +00:00
Files
build/v2/tools/python.jam
Dave Abrahams 6c13901b8c Fix a bug wherein multiple python interpreters could get set on a
target.  My explanation from a mail to Martin Wille:


  IIUC, your
  configuration looks like:

     using python : 2.4 : ... ;
     using python : 2.4 : ... : : : <toolset>gcc <toolset-gcc:version>4.1.2_linux_x86_64 ;
     using python : 2.4 : ... : : : <toolset>gcc <toolset-gcc:version>4.1.0_linux_x86_64 ;

  The intention is of course that the latter pythons will be used in
  preference to the former one if their conditions are matched more
  explicitly.

  We are using the "flags" rule to directly associate the interpreter
  command with targets being built, provided the condition passed is
  matched.

      # Set up the PYTHON variable to point at the interpreter.
      flags python.capture-output PYTHON $(condition:J=/) : $(interpreter-cmd) ;

  Here's an excerpt of docs for the condition parameter on flags:

               condition * :    # A condition when this flag should be applied.
                                # Should be set of property sets. If one of
                                # those property sets is contained in build
                                # properties, the flag will be used.

  So what happens is that, because it's less specific, the flags
  invocation for the first python matches when either of the latter
  pythons was supposed to match, and the PYTHON variable that is used to
  hold the interpreter command on the testing target accumulates both
  interpreters.

  We have a mechanism for "choose the closest property match," but it
  doesn't apply to the flags rule: it's target alternatives.  Since we
  define target alternatives for the python library anyway, I think I
  can handle this by creating a property to hold the interpreter command
  and associating it with the appropriate target alternative, then
  keying off *that* command to set up flags.


[SVN r37272]
2007-03-23 14:21:49 +00:00

1103 lines
35 KiB
Plaintext

# 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 : flags ;
import regex ;
import numbers ;
import string ;
import property ;
import sequence ;
import path ;
import feature ;
# 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 ] ;
.alias-defined = ;
# 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 ;
# 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.
#
# Example usage:
#
# using python 2.3 ;
# using python 2.3 : /usr/local/bin/python ;
#
rule init ( version ? : cmd-or-prefix ? : includes ? : libraries ?
: condition * )
{
.configured = true ;
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) ;
project.pop-current ;
}
# A simpler version of SHELL that grabs stderr as well as stdout, but
# returns nothing if there's an error.
local rule shell-cmd ( cmd )
{
# debug-message running command '$(cmd)' ;
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 aren't 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 .bat ] ;
local paths = [ GLOB $(dirs) : $(base) ] ;
if $(paths)
{
# Make sure we didn't find a Cygwin symlink. Invoking such a
# file as an NT executable will be fatal to 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't enumerate registry sub-keys, we have no way of finding
# a version with a 2-digit minor version, e.g. 2.10 -- let's
# 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.
rule dump-sys ( 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. Don't 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 don't really want sys.version since that's 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
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 = $(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 ;
flags python.capture-output PYTHON : <python.interpreter> ;
# 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 don't 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 don't change their ABIs either.
#
# Returns a list consisting of ungristed library names to be used as
# direct dependencies and conditional library names to be added to
# target conditions.
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
# doesn't as long as we're not forcing <threading>multi.
return pthread dl <toolset>gcc:<library>rt ;
case osf : return pthread <toolset>gcc:<library>rt ;
case qnx* : return ;
case darwin : return ;
case windows : return ;
case hpux : return rt ;
case * : return pthread dl <toolset>gcc:<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 ! $(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 * )
{
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=) ;
# 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 didn't 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, don't 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) ;
}
}
# 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 dump-sys 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'll assume only native
# builds can possibly run, so we won't require a match and we
# leave sys.platform blank.
local platform ;
switch $(target-os)
{
case windows : platform = win32 ;
case cygwin : platform = cygwin ;
}
local fallback-cmd = $(cmds-to-try[1]) ;
local fallback-version ;
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 [ dump-sys $(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) && $(interpreter-cmd) )
{
version ?= $(fallback-version) ;
version ?= 2.5 ;
if ! $(interpreter-cmd)
{
fallback-cmd ?= python ;
ECHO warning: No working Python interpreter found. ;
if [ os.name ] != NT || ! [ invokes-cygwin-symlink $(fallback-cmd) ]
{
interpreter-cmd = $(fallback-cmd) ;
ECHO warning: falling back to \"$(interpreter-cmd)\" ;
}
}
exec-prefix ?= $(prefix) ;
compute-default-paths $(target-os) : $(version) : $(prefix:E=) ;
}
}
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
#
# Add the version, if any, to the condition
if $(version)
{
if ! $(version) in [ feature.values python ]
{
feature.extend python : $(version) ;
}
condition += <python>$(version:E=default) ;
}
condition += <target-os>$(target-os) ;
# Set up the PYTHON variable to point at the interpreter.
local target-requirements = $(condition) ;
local system-libs ;
for local x in [ system-library-dependencies $(target-os) ]
{
if $(x:G)
{
target-requirements += $(x) ;
}
else
{
system-libs += $(x) ;
}
}
# 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) = Python.framework
{
debug-message framework directory is \"$(fwk)\" ;
}
else
{
debug-message no framework directory found; using library path ;
framework-directory = ;
}
}
# Make sure that we can find the Python DLL on windows
local dll-path ;
if $(target-os) = windows && $(exec-prefix)
{
dll-path += $(exec-prefix) ;
}
local usage-requirements = <include>$(includes) <python.interpreter>$(interpreter-cmd) ;
#
# Declare the "python" target. This should really be called
# python_for_embedding
#
if $(framework-directory)
{
alias python
:
: $(target-requirements)
:
: $(usage-requirements) <framework>$(fwk)
;
}
else
{
declare-libpython-target $(version) : $(target-requirements) ;
alias python
: $(system-libs)
: $(target-requirements)
:
# why python.lib must be listed here instead of along with
# the system libs is a mystery, but if we don't do it, on
# cygwin, -lpythonX.Y never appears in the command line
# (although it does on linux).
: $(usage-requirements) <library-path>$(libraries) <dll-path>$(dll-path) <library>python.lib
;
}
# On *nix, we don't 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'd 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) ;
}
else
{
alias python_for_extensions
:
: $(target-requirements)
:
: $(usage-requirements)
;
}
}
rule configured ( )
{
return $(.configured) ;
}
type.register PYTHON_EXTENSION : : SHARED_LIB ;
# We can't simply assign the "dll" or "so" suffix to PYTHON_EXTENSION,
# because then we wouldn't know whether "x.dll" is a python extension
# or an ordinary library. Therefore, we specify only the suffixes used
# for target generation.
type.set-generated-target-suffix PYTHON_EXTENSION : : so ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>windows : pyd ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>cygwin : dll ;
# Prior to python 2.5, HPUX extension modules had a ".sl" extension
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>2.4 : sl ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>2.3 : sl ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>2.2 : sl ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>2.1 : sl ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>2.0 : sl ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>1.6 : sl ;
type.set-generated-target-suffix PYTHON_EXTENSION : <target-os>hpux <python>1.5 : sl ;
# Unset 'lib' prefix for PYTHON_EXTENSION
type.set-generated-target-prefix PYTHON_EXTENSION : : "" ;
rule python-extension ( name : sources * : requirements * : default-build * :
usage-requirements * )
{
requirements += <use>/python//python_for_extensions <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 ;
# Support for testing
type.register PY : py ;
type.register RUN_PYD_OUTPUT ;
#type.set-generated-target-suffix RUN_PYD : : run ;
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 python ;
local other-pythons ;
for local s in $(sources)
{
if [ $(s).type ] = PY
{
if ! $(python)
{
# First Python source ends up on command line.
python = $(s) ;
}
else
{
# Other Python sources become dependencies.
other-pythons += $(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*. Couldn't 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]) ;
}
# 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 proper DLL search path.
# Here, $(sources[1]) is python module and $(sources[2]) is
# 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 = [ on $(sources[2-]) return $(LOCATE) $(SEARCH) ] ;
# 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 pythons 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 isn't 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 * )
{
sources ?= $(name).py $(name).cpp ;
return [ testing.make-test
run-pyd : $(sources) /boost/python//boost_python
: $(requirements) : $(name) ] ;
}
IMPORT $(__name__) : bpl-test : : bpl-test ;