358 lines
14 KiB
Python
358 lines
14 KiB
Python
"""Cython.Distutils.old_build_ext
|
|
|
|
Implements a version of the Distutils 'build_ext' command, for
|
|
building Cython extension modules.
|
|
|
|
Note that this module is deprecated. Use cythonize() instead.
|
|
"""
|
|
|
|
__revision__ = "$Id:$"
|
|
|
|
import sys
|
|
import os
|
|
from distutils.errors import DistutilsPlatformError
|
|
from distutils.dep_util import newer, newer_group
|
|
from distutils import log
|
|
from distutils.command import build_ext as _build_ext
|
|
from distutils import sysconfig
|
|
|
|
|
|
try:
|
|
from __builtin__ import basestring
|
|
except ImportError:
|
|
basestring = str
|
|
|
|
|
|
# FIXME: the below does not work as intended since importing 'Cython.Distutils' already
|
|
# imports this module through 'Cython/Distutils/build_ext.py', so the condition is
|
|
# always false and never prints the warning.
|
|
"""
|
|
import inspect
|
|
import warnings
|
|
|
|
def _check_stack(path):
|
|
try:
|
|
for frame in inspect.getouterframes(inspect.currentframe(), 0):
|
|
if path in frame[1].replace(os.sep, '/'):
|
|
return True
|
|
except Exception:
|
|
pass
|
|
return False
|
|
|
|
|
|
if (not _check_stack('setuptools/extensions.py')
|
|
and not _check_stack('pyximport/pyxbuild.py')
|
|
and not _check_stack('Cython/Distutils/build_ext.py')):
|
|
warnings.warn(
|
|
"Cython.Distutils.old_build_ext does not properly handle dependencies "
|
|
"and is deprecated.")
|
|
"""
|
|
|
|
extension_name_re = _build_ext.extension_name_re
|
|
|
|
show_compilers = _build_ext.show_compilers
|
|
|
|
class Optimization(object):
|
|
def __init__(self):
|
|
self.flags = (
|
|
'OPT',
|
|
'CFLAGS',
|
|
'CPPFLAGS',
|
|
'EXTRA_CFLAGS',
|
|
'BASECFLAGS',
|
|
'PY_CFLAGS',
|
|
)
|
|
self.state = sysconfig.get_config_vars(*self.flags)
|
|
self.config_vars = sysconfig.get_config_vars()
|
|
|
|
|
|
def disable_optimization(self):
|
|
"disable optimization for the C or C++ compiler"
|
|
badoptions = ('-O1', '-O2', '-O3')
|
|
|
|
for flag, option in zip(self.flags, self.state):
|
|
if option is not None:
|
|
L = [opt for opt in option.split() if opt not in badoptions]
|
|
self.config_vars[flag] = ' '.join(L)
|
|
|
|
def restore_state(self):
|
|
"restore the original state"
|
|
for flag, option in zip(self.flags, self.state):
|
|
if option is not None:
|
|
self.config_vars[flag] = option
|
|
|
|
|
|
optimization = Optimization()
|
|
|
|
|
|
class old_build_ext(_build_ext.build_ext):
|
|
|
|
description = "build C/C++ and Cython extensions (compile/link to build directory)"
|
|
|
|
sep_by = _build_ext.build_ext.sep_by
|
|
user_options = _build_ext.build_ext.user_options[:]
|
|
boolean_options = _build_ext.build_ext.boolean_options[:]
|
|
help_options = _build_ext.build_ext.help_options[:]
|
|
|
|
# Add the pyrex specific data.
|
|
user_options.extend([
|
|
('cython-cplus', None,
|
|
"generate C++ source files"),
|
|
('cython-create-listing', None,
|
|
"write errors to a listing file"),
|
|
('cython-line-directives', None,
|
|
"emit source line directives"),
|
|
('cython-include-dirs=', None,
|
|
"path to the Cython include files" + sep_by),
|
|
('cython-c-in-temp', None,
|
|
"put generated C files in temp directory"),
|
|
('cython-gen-pxi', None,
|
|
"generate .pxi file for public declarations"),
|
|
('cython-directives=', None,
|
|
"compiler directive overrides"),
|
|
('cython-gdb', None,
|
|
"generate debug information for cygdb"),
|
|
('cython-compile-time-env', None,
|
|
"cython compile time environment"),
|
|
|
|
# For backwards compatibility.
|
|
('pyrex-cplus', None,
|
|
"generate C++ source files"),
|
|
('pyrex-create-listing', None,
|
|
"write errors to a listing file"),
|
|
('pyrex-line-directives', None,
|
|
"emit source line directives"),
|
|
('pyrex-include-dirs=', None,
|
|
"path to the Cython include files" + sep_by),
|
|
('pyrex-c-in-temp', None,
|
|
"put generated C files in temp directory"),
|
|
('pyrex-gen-pxi', None,
|
|
"generate .pxi file for public declarations"),
|
|
('pyrex-directives=', None,
|
|
"compiler directive overrides"),
|
|
('pyrex-gdb', None,
|
|
"generate debug information for cygdb"),
|
|
])
|
|
|
|
boolean_options.extend([
|
|
'cython-cplus', 'cython-create-listing', 'cython-line-directives',
|
|
'cython-c-in-temp', 'cython-gdb',
|
|
|
|
# For backwards compatibility.
|
|
'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives',
|
|
'pyrex-c-in-temp', 'pyrex-gdb',
|
|
])
|
|
|
|
def initialize_options(self):
|
|
_build_ext.build_ext.initialize_options(self)
|
|
self.cython_cplus = 0
|
|
self.cython_create_listing = 0
|
|
self.cython_line_directives = 0
|
|
self.cython_include_dirs = None
|
|
self.cython_directives = None
|
|
self.cython_c_in_temp = 0
|
|
self.cython_gen_pxi = 0
|
|
self.cython_gdb = False
|
|
self.no_c_in_traceback = 0
|
|
self.cython_compile_time_env = None
|
|
|
|
def __getattr__(self, name):
|
|
if name[:6] == 'pyrex_':
|
|
return getattr(self, 'cython_' + name[6:])
|
|
else:
|
|
return _build_ext.build_ext.__getattr__(self, name)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name[:6] == 'pyrex_':
|
|
return setattr(self, 'cython_' + name[6:], value)
|
|
else:
|
|
# _build_ext.build_ext.__setattr__(self, name, value)
|
|
self.__dict__[name] = value
|
|
|
|
def finalize_options(self):
|
|
_build_ext.build_ext.finalize_options(self)
|
|
if self.cython_include_dirs is None:
|
|
self.cython_include_dirs = []
|
|
elif isinstance(self.cython_include_dirs, basestring):
|
|
self.cython_include_dirs = \
|
|
self.cython_include_dirs.split(os.pathsep)
|
|
if self.cython_directives is None:
|
|
self.cython_directives = {}
|
|
# finalize_options ()
|
|
|
|
def run(self):
|
|
# We have one shot at this before build_ext initializes the compiler.
|
|
# If --pyrex-gdb is in effect as a command line option or as option
|
|
# of any Extension module, disable optimization for the C or C++
|
|
# compiler.
|
|
if self.cython_gdb or [1 for ext in self.extensions
|
|
if getattr(ext, 'cython_gdb', False)]:
|
|
optimization.disable_optimization()
|
|
|
|
_build_ext.build_ext.run(self)
|
|
|
|
def check_extensions_list(self, extensions):
|
|
# Note: might get called multiple times.
|
|
_build_ext.build_ext.check_extensions_list(self, extensions)
|
|
for ext in self.extensions:
|
|
ext.sources = self.cython_sources(ext.sources, ext)
|
|
|
|
def cython_sources(self, sources, extension):
|
|
"""
|
|
Walk the list of source files in 'sources', looking for Cython
|
|
source files (.pyx and .py). Run Cython on all that are
|
|
found, and return a modified 'sources' list with Cython source
|
|
files replaced by the generated C (or C++) files.
|
|
"""
|
|
new_sources = []
|
|
cython_sources = []
|
|
cython_targets = {}
|
|
|
|
# Setup create_list and cplus from the extension options if
|
|
# Cython.Distutils.extension.Extension is used, otherwise just
|
|
# use what was parsed from the command-line or the configuration file.
|
|
# cplus will also be set to true is extension.language is equal to
|
|
# 'C++' or 'c++'.
|
|
#try:
|
|
# create_listing = self.cython_create_listing or \
|
|
# extension.cython_create_listing
|
|
# cplus = self.cython_cplus or \
|
|
# extension.cython_cplus or \
|
|
# (extension.language != None and \
|
|
# extension.language.lower() == 'c++')
|
|
#except AttributeError:
|
|
# create_listing = self.cython_create_listing
|
|
# cplus = self.cython_cplus or \
|
|
# (extension.language != None and \
|
|
# extension.language.lower() == 'c++')
|
|
|
|
create_listing = self.cython_create_listing or \
|
|
getattr(extension, 'cython_create_listing', 0)
|
|
line_directives = self.cython_line_directives or \
|
|
getattr(extension, 'cython_line_directives', 0)
|
|
no_c_in_traceback = self.no_c_in_traceback or \
|
|
getattr(extension, 'no_c_in_traceback', 0)
|
|
cplus = self.cython_cplus or getattr(extension, 'cython_cplus', 0) or \
|
|
(extension.language and extension.language.lower() == 'c++')
|
|
cython_gen_pxi = self.cython_gen_pxi or getattr(extension, 'cython_gen_pxi', 0)
|
|
cython_gdb = self.cython_gdb or getattr(extension, 'cython_gdb', False)
|
|
cython_compile_time_env = self.cython_compile_time_env or \
|
|
getattr(extension, 'cython_compile_time_env', None)
|
|
|
|
# Set up the include_path for the Cython compiler:
|
|
# 1. Start with the command line option.
|
|
# 2. Add in any (unique) paths from the extension
|
|
# cython_include_dirs (if Cython.Distutils.extension is used).
|
|
# 3. Add in any (unique) paths from the extension include_dirs
|
|
includes = list(self.cython_include_dirs)
|
|
try:
|
|
for i in extension.cython_include_dirs:
|
|
if i not in includes:
|
|
includes.append(i)
|
|
except AttributeError:
|
|
pass
|
|
|
|
# In case extension.include_dirs is a generator, evaluate it and keep
|
|
# result
|
|
extension.include_dirs = list(extension.include_dirs)
|
|
for i in extension.include_dirs:
|
|
if i not in includes:
|
|
includes.append(i)
|
|
|
|
# Set up Cython compiler directives:
|
|
# 1. Start with the command line option.
|
|
# 2. Add in any (unique) entries from the extension
|
|
# cython_directives (if Cython.Distutils.extension is used).
|
|
directives = dict(self.cython_directives)
|
|
if hasattr(extension, "cython_directives"):
|
|
directives.update(extension.cython_directives)
|
|
|
|
# Set the target file extension for C/C++ mode.
|
|
if cplus:
|
|
target_ext = '.cpp'
|
|
else:
|
|
target_ext = '.c'
|
|
|
|
# Decide whether to drop the generated C files into the temp dir
|
|
# or the source tree.
|
|
|
|
if not self.inplace and (self.cython_c_in_temp
|
|
or getattr(extension, 'cython_c_in_temp', 0)):
|
|
target_dir = os.path.join(self.build_temp, "pyrex")
|
|
for package_name in extension.name.split('.')[:-1]:
|
|
target_dir = os.path.join(target_dir, package_name)
|
|
else:
|
|
target_dir = None
|
|
|
|
newest_dependency = None
|
|
for source in sources:
|
|
(base, ext) = os.path.splitext(os.path.basename(source))
|
|
if ext == ".py":
|
|
# FIXME: we might want to special case this some more
|
|
ext = '.pyx'
|
|
if ext == ".pyx": # Cython source file
|
|
output_dir = target_dir or os.path.dirname(source)
|
|
new_sources.append(os.path.join(output_dir, base + target_ext))
|
|
cython_sources.append(source)
|
|
cython_targets[source] = new_sources[-1]
|
|
elif ext == '.pxi' or ext == '.pxd':
|
|
if newest_dependency is None \
|
|
or newer(source, newest_dependency):
|
|
newest_dependency = source
|
|
else:
|
|
new_sources.append(source)
|
|
|
|
if not cython_sources:
|
|
return new_sources
|
|
|
|
try:
|
|
from Cython.Compiler.Main \
|
|
import CompilationOptions, \
|
|
default_options as cython_default_options, \
|
|
compile as cython_compile
|
|
from Cython.Compiler.Errors import PyrexError
|
|
except ImportError:
|
|
e = sys.exc_info()[1]
|
|
print("failed to import Cython: %s" % e)
|
|
raise DistutilsPlatformError("Cython does not appear to be installed")
|
|
|
|
module_name = extension.name
|
|
|
|
for source in cython_sources:
|
|
target = cython_targets[source]
|
|
depends = [source] + list(extension.depends or ())
|
|
if source[-4:].lower() == ".pyx" and os.path.isfile(source[:-3] + "pxd"):
|
|
depends += [source[:-3] + "pxd"]
|
|
rebuild = self.force or newer_group(depends, target, 'newer')
|
|
if not rebuild and newest_dependency is not None:
|
|
rebuild = newer(newest_dependency, target)
|
|
if rebuild:
|
|
log.info("cythoning %s to %s", source, target)
|
|
self.mkpath(os.path.dirname(target))
|
|
if self.inplace:
|
|
output_dir = os.curdir
|
|
else:
|
|
output_dir = self.build_lib
|
|
options = CompilationOptions(cython_default_options,
|
|
use_listing_file = create_listing,
|
|
include_path = includes,
|
|
compiler_directives = directives,
|
|
output_file = target,
|
|
cplus = cplus,
|
|
emit_linenums = line_directives,
|
|
c_line_in_traceback = not no_c_in_traceback,
|
|
generate_pxi = cython_gen_pxi,
|
|
output_dir = output_dir,
|
|
gdb_debug = cython_gdb,
|
|
compile_time_env = cython_compile_time_env)
|
|
result = cython_compile(source, options=options,
|
|
full_module_name=module_name)
|
|
else:
|
|
log.info("skipping '%s' Cython extension (up-to-date)", target)
|
|
|
|
return new_sources
|
|
|
|
# cython_sources ()
|
|
|
|
# class build_ext
|