# 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 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 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 \"!\" \""$(path)"\" 2>&1" ] ; if $(link[2]) != 0 { local nl = " " ; is-symlink = [ MATCH ".*!([^"$(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 : ; # # Support for Python configured --with-pydebug # feature.feature python-debugging : off on : propagated ; builtin.variant debug-python : debug : 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 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 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 gcc:rt ; case osf : return pthread gcc:rt ; case qnx* : return ; case darwin : return ; case windows : return ; case hpux : return rt ; case *bsd : return pthread gcc:util ; case aix : return pthread dl ; case * : return pthread dl gcc:util linux: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 windows in $(requirements) { local major-minor = [ split-version $(version) ] ; lib-version = $(major-minor:J="") ; if 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 : : 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 && 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=)\" ; debug-message " include path:" \"$(includes:E=)\" ; debug-message " library path:" \"$(libraries:E=)\" ; if $(target-os) = windows { debug-message " DLL search path:" \"$(exec-prefix:E=)\" ; } # # 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 += $(version:E=default) ; } target-requirements += $(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 += $(includes) $(interpreter-cmd) ; if 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 += _DEBUG ; } else { usage-requirements += 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=,):$(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-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 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) $(set-PYTHONPATH) $(libraries) $(dll-path) 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) -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 += /python//python_for_extensions ; } requirements += 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 ] ; 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 $(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 and 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 ; 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 ;