# 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. # Known problems: # - the directory where extension is generated is different from V2 # - the ext + py -> pyd_run_output generator is declared to take # SHARED_LIB, not PYTHON_EXTENSION. That's because we reuse # 'lib-target-class', which creates SHARED_LIB explicitly. import type ; import testing ; import generators ; import project ; import errors ; import targets ; import "class" : new ; import os ; import common ; import toolset : flags ; import regex ; # 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. # - version -- the version of Python to use. Should be in Major.Minor format, # for example 2.3 # - 'root' -- the install root for Python # - 'includes' -- the include path to Python headers. If empty, will be # guessed from 'root' # - 'libraries' -- the path to Python libraries. If empty, will be guessed # from 'root' # - 'cygwin-condition' -- if specified, should be a set of properties which # are present when we're building with cygwin gcc. # This argument is not used yet. # # Example usage: # # using python 2.3 ; # Use default root # using python 2.3 : /usr/local ; # Root specified, include and lib paths # # will be guessed # rule init ( version ? : root ? : includes ? : libraries ? : cygwin-condition * ) { .configured = true ; project.push-current $(.project) ; if [ os.name ] = NT { init-nt $(version) : $(root) : $(includes) : $(libraries) : $(cygwin-condition) ; } else if [ os.name ] = MACOSX { init-mac $(version) : $(root) : $(includes) : $(libraries) ; } else if [ modules.peek : UNIX ] { init-unix $(version) : $(root) : $(includes) : $(libraries) : $(cygwin-condition) ; } if [ os.on-windows ] && ! $(.alias-defined) { .alias-defined = true ; alias python_for_extensions : python ; } project.pop-current ; } local rule python-version ( cmd ) { cmd ?= python ; local version = [ SHELL $(cmd)" -c 'import sys; print sys.version'" : exit-status ] ; if $(version[2]) = 0 { return [ MATCH ^([0-9]+.[0-9]+) : $(version[1]) : 1 ] ; } else { return ; } } local rule python-interpreter ( cmd ) { local which = [ SHELL "which "$(cmd) : exit-status ] ; if $(which[2]) = 0 { return $(which[1]) ; } else { return ; } } local rule python-root ( cmd ) { return [ MATCH (.*)/bin/[^/]* : [ SHELL "which "$(cmd) ] : 1 ] ; } local rule debug-message ( message * ) { if --debug-configuration in [ modules.peek : ARGV ] { ECHO notice: $(message) ; } } rule init-unix ( version ? : root ? : includes ? : libraries ? : condition * ) { # # Autoconfiguration sequence # if $(version) { local v = [ MATCH ^([0-9]+\.[0-9]+)(.*)$ : $(version) : 1 2 ] ; if ! $(v) || $(v[2]) { ECHO "Warning: \"using python\" expects a two part (major, minor) version number; got" $(version) instead ; if $(v) { version = $(v[1]) ; } } debug-message looking for python $(version) ; } # if root is explicitly specified, look in its bin subdirectory local bin = $(bin/:R=(root)) ; if $(bin) { debug-message searching for python binaries in $(bin) ; } # Form the python commands to try in order. First look for python # with the explicit version number, then without it local cmds = $(bin:E="")python$(version:E="") $(bin:E="")python ; local interpreter ; while $(cmds) { # pop a command interpreter = $(cmds[0]) ; cmds = $(cmds[2-]) ; debug-message trying Python interpreter command $(interpreter) ; # Check to see what version that command actually runs, if any local true-version = [ python-version $(interpreter) ] ; if ! $(true-version) { debug-message $(interpreter) does not invoke a working Python interpreter ; } else { debug-message $(interpreter) invokes actual Python (major,minor) version $(true-version) ; # if no version was specified, assume that's OK version ?= $(true-version) ; # if the version is a match, stop searching if $(version) = $(true-version) { debug-message qualifying Python interpreter found ; root ?= [ python-root $(interpreter) ] ; cmds = ; # break } } } debug-message "Python interpreter command is" $(interpreter) ; includes ?= $(root)/include/python$(version) ; debug-message "Python include path is" $(includes) ; libraries ?= $(root)/lib/python$(version)/config ; debug-message "Python library path is" $(libraries) ; # # End autoconfiguration sequence # # If not specific condition is specified, set global value # If condition is specified, set PYTHON on target. It will # override the global value. if ! $(condition) { PYTHON = $(interpreter) ; } else { flags python.capture-output PYTHON $(condition:J=/) : $(interpreter) ; } # Depending on system, or on toolset used, we need extra libraries. # Libraries which are needed depending on system are added to # 'extra-libs' and for libraries which depend on toolset we need # conditional requirements to 'extra-libs-conditional'. local extra-libs extra-libs-conditional ; # Depending on system, Python library is either static # or shared. When it's static, we need to add 'pthread' # to link line of all clients, otherwise we'll get # unresolved symbols. Same effect can be accomplished # by using multi on the client, but # that can have performance overhead, and is not really # necessary, as Python interface has nothing that's # affected by threading mode. switch [ os.name ] { case SOLARIS : { extra-libs = pthread dl ; # Add 'rt' option on Sun. While we duplicate the # logic already in sun.jam and gcc.jam, I see no easy # way to refactor it -- Volodya. # Note that for 'sun' toolset, rt is already unconditionally # added. extra-libs-conditional = gcc:rt ; } case OSF : { extra-libs = pthread z ; extra-libs-conditional = gcc:rt ; } case QNX* : { extra-libs = ; } case * : extra-libs = pthread dl util ; } if ! [ os.on-windows ] { # 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 embedding alias python_for_extensions : : $(condition) : : $(includes) ; } # This should really be called python_for_embedding alias python : $(extra-libs) : $(condition) $(extra-libs-conditional) : : $(includes) $(libraries) python$(version) ; } rule init-mac ( version : root ? : includes ? : libraries ? ) { if ! $(root) { if [ GLOB /System/Library/Frameworks : Python.framework ] { root = /System/Library/Frameworks/Python.framework/Versions/$(version) ; } else { root = /Library/Frameworks/Python.framework/Versions/$(version) ; } } # includes ?= $(PYTHON_ROOT)/include/python$(PYTHON_VERSION) ; includes ?= $(root)/include/python$(version) ; libraries ?= $(root)/lib/python$(version)/config ; # Find the 'python' binary, which is used for testing. # Look first in $(root)/bin, then in PATH. local interpreter = [ common.get-invocation-command python : python : : $(root)/bin : path-last ] ; # debug support if --debug-configuration in [ modules.peek : ARGV ] { ECHO "notice: Python include path is" $(includes) ; ECHO "notice: Python library path is" $(libraries) ; ECHO "notice: Python interpreter is" $(interpreter) ; } flags python.capture-output PYTHON : $(interpreter) ; PYTHON_FRAMEWORK ?= $(root) ; while $(PYTHON_FRAMEWORK:D=) && $(PYTHON_FRAMEWORK:D=) != Python.framework { PYTHON_FRAMEWORK = $(PYTHON_FRAMEWORK:D) ; } PYTHON_FRAMEWORK = $(PYTHON_FRAMEWORK:D)/Python ; alias python : : MACOSX darwin : : $(includes) $(PYTHON_FRAMEWORK) ; # 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. .alias-defined = true ; alias python_for_extensions : python ; } rule init-nt ( version : root ? : includes ? : libraries ? : cygwin-condition ? ) { if ! $(cygwin-condition) { root ?= c:/tools/python ; local PATH = [ modules.peek : PATH ] ; local PATH = [ modules.peek : Path ] ; PYTHON_LIB_PATH ?= $(root)/libs [ GLOB $(root) : PCbuild ] ; PYTHON_INCLUDES ?= $(root)/include [ GLOB $(root) : PC ] ; # The name of Python library file does not have a dot between # major and minor version. local PYTHON_VERSION_NODOT = [ regex.match ([0-9]+)[.]([0-9]+).* : $(version) : 1 2 ] ; PYTHON_VERSION_NODOT = $(PYTHON_VERSION_NODOT:J="") ; PYTHON_DLL ?= [ GLOB $(PATH) $(Path) : python$(PYTHON_VERSION_NODOT).dll ] ; PYTHON_DEBUG_DLL ?= [ GLOB $(PATH) $(Path) : python$(PYTHON_VERSION_NODOT)_d.dll ] ; PYTHON_IMPORT_LIB ?= [ GLOB $(PYTHON_LIB_PATH) : libpython$(PYTHON_VERSION_NODOT).* ] ; PYTHON_DEBUG_IMPORT_LIB ?= [ GLOB $(PYTHON_LIB_PATH) : libpython$(PYTHON_VERSION_NODOT).* ] ; local interpreter = [ common.get-invocation-command python : python : : $(root)/bin $(root) $(root)/PCBuild : path-last ] ; if --debug-configuration in [ modules.peek : ARGV ] { ECHO "notice: Python include path is" $(includes) ; ECHO "notice: Python library path is" $(libraries) ; ECHO "notice: Python interpreter is" $(interpreter) ; ECHO "notice: Python library name is" python$(PYTHON_VERSION_NODOT) ; } flags python.capture-output PYTHON : $(interpreter) ; properties += $(PYTHON_LIB_PATH) ; # msvc compilers auto-find the python library # declare two alternatives -- one for msvc and another # for the rest of the world alias python : : msvc : : $(PYTHON_LIB_PATH) $(PYTHON_INCLUDES) ; local lib = python$(PYTHON_VERSION_NODOT) ; alias python : : : : $(PYTHON_LIB_PATH) $(PYTHON_INCLUDES) $(lib) ; } else { root ?= /usr ; if $(root) = /usr { CYGWIN_PYTHON_DLL_PATH ?= /bin ; } else { CYGWIN_PYTHON_DLL_PATH ?= $(root)/bin ; } CYGWIN_PYTHON_LIB_PATH ?= $(CYGWIN_PYTHON_ROOT)/lib/python$(version)/config ; CYGWIN_PYTHON_DEBUG_VERSION ?= $(version) ; CYGWIN_PYTHON_DEBUG_ROOT ?= /usr/local/pydebug ; CYGWIN_PYTHON_DEBUG_DLL_PATH ?= $(CYGWIN_PYTHON_DEBUG_ROOT)/bin ; CYGWIN_PYTHON_DEBUG_LIB_PATH ?= $(CYGWIN_PYTHON_DEBUG_ROOT)/lib/python$(CYGWIN_PYTHON_DEBUG_VERSION)/config ; local properties ; properties += $(CYGWIN_PYTHON_LIB_PATH) python$(CYGWIN_PYTHON_VERSION).dll ; properties += $(root)/include/python$(version) ; alias python : : $(cygwin-condition) : : $(properties) ; } } rule configured ( ) { return $(.configured) ; } type.register PYTHON_EXTENSION : : SHARED_LIB ; # We can't give "dll" suffix to PYTHON_EXTENSION, because # we would not know what "a.dll" is: python extenstion or # ordinary library. Therefore, we specify only suffixes # used for generation of targets. type.set-generated-target-suffix PYTHON_EXTENSION : : so ; type.set-generated-target-suffix PYTHON_EXTENSION : NT : pyd ; type.set-generated-target-suffix PYTHON_EXTENSION : CYGWIN : dll ; # Unset 'lib' prefix for PYTHON_EXTENSION type.set-generated-target-prefix PYTHON_EXTENSION : : "" ; rule python-extension ( name : sources * : requirements * : default-build * : usage-requirements * ) { requirements += /python//python_for_extensions ; 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 ; for local s in $(sources) { if [ $(s).type ] = PY { python = $(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 genrator are xdll-path properties that # will allow to find python extension at runtime. property-set = [ $(property-set).add $(extension[1]) ] ; # Ignore usage requirements. We're top-level generator and # nobody is going to use us. new-sources += $(extension[2-]) ; } } 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 ; 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) ] ; testing.capture-output $(target) : $(sources[1]) : $(properties) ; local c = [ common.prepend-path-variable-command PYTHONPATH : $(PYTHONPATH) ] ; LAUNCHER on $(target) = $(c) [ 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 ;