import feature ;
import set ;
import builtin ;
+import property-set ;
# Make this module a project.
# pass this to linker explicitly.
lib util ;
# Python uses pthread symbols.
-lib pthread ;
+lib pthread :
+ : <target-os>linux:<link>shared
+ ;
+
# Extra library needed by phtread on some platforms.
lib rt ;
# installed in the development system's default paths.
feature.feature pythonpath : : free optional path ;
+# The best configured version of Python 2 and 3.
+py2-version = ;
+py3-version = ;
+
# Initializes the Python toolset. Note that all parameters are optional.
#
# - version -- the version of Python to use. Should be in Major.Minor format,
# 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
+# against the build configuration when B2 selects a Python
# configuration to use.
#
# - extension-suffix: A string to append to the name of extension modules before
{
if $($(v))
{
- debug-message " user-specified "$(v): \"$($(v))\" ;
+ debug-message " user-specified $(v):" \"$($(v))\" ;
}
}
if $(dir-listing)
{
# Escape any special regex characters in the base part of the path.
- local base-pat = [ regex.escape $(path:D=) : ].[()*+?|\\$^ : \\ ] ;
+ 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 --debug-configuration in [ modules.peek : ARGV ]
{
- ECHO notice: [python-cfg] $(message) ;
+ ECHO "notice:" "[python-cfg]" $(message) ;
}
}
}
-.windows-drive-letter-re = ^([A-Za-z]):[\\/](.*) ;
-.cygwin-drive-letter-re = ^/cygdrive/([a-z])/(.*) ;
+.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 ] ;
{
path = $(path:R="") ; # strip any trailing slash
- local drive-letter = [ SUBST $(path) $(.cygwin-drive-letter-re) $1:/$2 ] ;
+ local drive-letter = [ SUBST $(path) $(.cygwin-drive-letter-re) "$1:/$2" ] ;
if $(drive-letter)
{
path = $(drive-letter) ;
#
local rule guess-windows-path ( path )
{
- return [ SUBST $(path) ($(.windows-drive-letter-re)|.*([\\]).*) $1 ] ;
+ return [ SUBST $(path) "($(.windows-drive-letter-re)|.*([\\]).*)" $1 ] ;
}
#
local rule split-version ( version )
{
- local major-minor = [ MATCH ^([0-9]+)\.([0-9]+)(.*)$ : $(version) : 1 2 3 ] ;
+ 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 ;
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 "(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 -------------------------------------------------------------------- ;
{
# 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 ] ;
+ sys.$(s) = [ SUBST $(output) "\\<$(s)=([^$(nl)]+)" $1 ] ;
}
}
return $(output) ;
}
# The version of the python interpreter to use.
-feature.feature python : : propagated ;
+feature.feature python : : propagated symmetric ;
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 ;
+variant debug-python : debug : <python-debugging>on ;
# Return a list of candidate commands to try when looking for a Python
}
+# Define a version suffix for libraries depending on Python.
+# For example, Boost.Python built for Python 2.7 uses the suffix "27"
+rule version-suffix ( version )
+{
+ local major-minor = [ split-version $(version) ] ;
+ local suffix = $(major-minor:J="") ;
+ return $(suffix) ;
+}
+
# Declare a target to represent Python's library.
#
local rule declare-libpython-target ( version ? : requirements * )
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'. ;
+ 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.
}
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) : * ]
debug-message Python headers and libraries not found. ;
return ;
}
-
+
.configured = true ;
if ! $(interpreter-cmd)
{
.numpy = true ;
.numpy-include = $(result[1]) ;
- debug-message "NumPy enabled" ;
+ debug-message "NumPy enabled" ;
}
else
{
- debug-message "NumPy disabled. Reason:" ;
- debug-message " $(full-cmd) aborted with " ;
- debug-message " $(result[1])" ;
+ debug-message "NumPy disabled. Reason:" ;
+ debug-message " $(full-cmd) aborted with " ;
+ debug-message " $(result[1])" ;
}
#
# End autoconfiguration sequence.
#
+
+ # Normalize and dissect any version number.
+ local major-minor ;
+ if $(version)
+ {
+ major-minor = [ split-version $(version) ] ;
+ version = $(major-minor:J=.) ;
+ }
+
+
local target-requirements = $(condition) ;
# Add the version, if any, to the target requirements.
if ! $(version) in [ feature.values python ]
{
feature.extend python : $(version) ;
+ py$(major-minor[1])-version ?= $(version) ;
+ if $(py$(major-minor[1])-version) < $(version)
+ {
+ py$(major-minor[1])-version = $(version) ;
+ }
}
target-requirements += <python>$(version:E=default) ;
}
}
}
+ # In case we added duplicate requirements from what the user specified.
+ target-requirements = [ sequence.unique $(target-requirements) ] ;
+
# 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) ;
-
- # We also set a default requirement that assigns the first python configured
- # for a particular target OS as the default. This makes it so that we can
- # select a python interpreter with only knowledge of the target OS. And hence
- # can configure different Pythons based on the target OS only.
- local toolset-requirements = [ toolset.requirements ] ;
- local toolset-target-os-requirements
- = [ property.evaluate-conditionals-in-context
- [ $(toolset-requirements).raw ] : <target-os>$(target-os) ] ;
- if ! <python> in $(toolset-target-os-requirements:G)
- {
- toolset.add-requirements <target-os>$(target-os):<python>$(version:E=default) ;
- }
-
- # We also set a default requirement that assigns the first python configured
- # for a particular target OS as the default. This makes it so that we can
- # select a python interpreter with only knowledge of the target OS. And hence
- # can configure different Pythons based on the target OS only.
- local toolset-requirements = [ toolset.requirements ] ;
- local toolset-target-os-requirements
- = [ property.evaluate-conditionals-in-context
- [ $(toolset-requirements).raw ] : <target-os>$(target-os) ] ;
- if ! <python> in $(toolset-target-os-requirements:G)
- {
- toolset.add-requirements <target-os>$(target-os):<python>$(version:E=default) ;
- }
+ "$(target-requirements:J=,):<python.interpreter>$(interpreter-cmd)" ;
# Register the right suffix for extensions.
register-extension-suffix $(extension-suffix) : $(target-requirements) ;
+ # Make sure that the python feature is always considered
+ # relevant for any targets that depend on python. Without
+ # this, it would only be considered relevant when there are
+ # multiple configurations defined within the same build.
+ target-requirements += <relevant>python ;
+
#
# Declare the "python" target. This should really be called
# python_for_embedding.
:
: $(target-requirements)
:
- : $(usage-requirements) <linkflags>-Wl,-bI:$(libraries[1])/python.exp
+ : $(usage-requirements) <linkflags>"-Wl,-bI:$(libraries[1])/python.exp"
;
}
else
: $(usage-requirements)
;
}
+
+}
+
+# Conditional rule specification that will prevent building of a target
+# if there is no matching python configuration available with the given
+# required properties.
+rule require-py ( properties * )
+{
+ local py-ext-target = [ $(.project).find python_for_extensions : no-error ] ;
+ if ! $(py-ext-target)
+ {
+ return <build>no ;
+ }
+ local property-set = [ property-set.create $(properties) ] ;
+ property-set = [ $(property-set).expand ] ;
+ local py-ext-alternative = [ $(py-ext-target).select-alternatives $(property-set) ] ;
+ if ! $(py-ext-alternative)
+ {
+ return <build>no ;
+ }
}
toolset.flags python.capture-output ARGS <testing.arg> ;
toolset.flags python.capture-output INPUT_FILES <testing.input-file> ;
+toolset.uses-features python.capture-output :
+ <testing.launcher> <testing.execute> <dll-path> <xdll-path> <target-os>
+ <pythonpath> ;
+
rule capture-output ( target : sources * : properties * )
{
# Setup up a proper DLL search path. Here, $(sources[1]) is a python module
PYTHONPATH += [ feature.get-values pythonpath : $(properties) ] ;
# After test is run, we remove the Python module, but not the Python script.
- local targets-to-remove = $(sources[2-]) ;
- targets-to-remove ?= none ;
- testing.capture-output $(target) : $(sources[1]) : $(properties) :
- $(targets-to-remove) ;
+ testing.capture-output $(target) : $(sources[1]) : $(properties) ;
# 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
}
local path-separator = [ os.path-separator [ translate-os $(target-os) ] ] ;
local set-PYTHONPATH = [ common.variable-setting-command PYTHONPATH :
- $(PYTHONPATH:J=$(path-separator)) ] ;
+ $(PYTHONPATH:E=:J=$(path-separator)) ] ;
LAUNCHER on $(target) = $(set-PYTHONPATH) [ on $(target) return \"$(PYTHON)\" ] ;
}
: $(name) ] ;
}
+rule py-version ( n )
+{
+ return $(py$(n)-version) ;
+}
+
IMPORT $(__name__) : bpl-test : : bpl-test ;
IMPORT $(__name__) : numpy-test : : numpy-test ;
+IMPORT $(__name__) : py-version : : py-version ;