161 lines
5.6 KiB
Python
161 lines
5.6 KiB
Python
|
"""Build a Pyrex file from .pyx source to .so loadable module using
|
||
|
the installed distutils infrastructure. Call:
|
||
|
|
||
|
out_fname = pyx_to_dll("foo.pyx")
|
||
|
"""
|
||
|
import os
|
||
|
import sys
|
||
|
|
||
|
from distutils.errors import DistutilsArgError, DistutilsError, CCompilerError
|
||
|
from distutils.extension import Extension
|
||
|
from distutils.util import grok_environment_error
|
||
|
try:
|
||
|
from Cython.Distutils.old_build_ext import old_build_ext as build_ext
|
||
|
HAS_CYTHON = True
|
||
|
except ImportError:
|
||
|
HAS_CYTHON = False
|
||
|
|
||
|
DEBUG = 0
|
||
|
|
||
|
_reloads={}
|
||
|
|
||
|
|
||
|
def pyx_to_dll(filename, ext=None, force_rebuild=0, build_in_temp=False, pyxbuild_dir=None,
|
||
|
setup_args=None, reload_support=False, inplace=False):
|
||
|
"""Compile a PYX file to a DLL and return the name of the generated .so
|
||
|
or .dll ."""
|
||
|
assert os.path.exists(filename), "Could not find %s" % os.path.abspath(filename)
|
||
|
|
||
|
path, name = os.path.split(os.path.abspath(filename))
|
||
|
|
||
|
if not ext:
|
||
|
modname, extension = os.path.splitext(name)
|
||
|
assert extension in (".pyx", ".py"), extension
|
||
|
if not HAS_CYTHON:
|
||
|
filename = filename[:-len(extension)] + '.c'
|
||
|
ext = Extension(name=modname, sources=[filename])
|
||
|
|
||
|
if setup_args is None:
|
||
|
setup_args = {}
|
||
|
if not pyxbuild_dir:
|
||
|
pyxbuild_dir = os.path.join(path, "_pyxbld")
|
||
|
|
||
|
package_base_dir = path
|
||
|
for package_name in ext.name.split('.')[-2::-1]:
|
||
|
package_base_dir, pname = os.path.split(package_base_dir)
|
||
|
if pname != package_name:
|
||
|
# something is wrong - package path doesn't match file path
|
||
|
package_base_dir = None
|
||
|
break
|
||
|
|
||
|
script_args=setup_args.get("script_args",[])
|
||
|
if DEBUG or "--verbose" in script_args:
|
||
|
quiet = "--verbose"
|
||
|
else:
|
||
|
quiet = "--quiet"
|
||
|
args = [quiet, "build_ext"]
|
||
|
if force_rebuild:
|
||
|
args.append("--force")
|
||
|
if inplace and package_base_dir:
|
||
|
args.extend(['--build-lib', package_base_dir])
|
||
|
if ext.name == '__init__' or ext.name.endswith('.__init__'):
|
||
|
# package => provide __path__ early
|
||
|
if not hasattr(ext, 'cython_directives'):
|
||
|
ext.cython_directives = {'set_initial_path' : 'SOURCEFILE'}
|
||
|
elif 'set_initial_path' not in ext.cython_directives:
|
||
|
ext.cython_directives['set_initial_path'] = 'SOURCEFILE'
|
||
|
|
||
|
if HAS_CYTHON and build_in_temp:
|
||
|
args.append("--pyrex-c-in-temp")
|
||
|
sargs = setup_args.copy()
|
||
|
sargs.update({
|
||
|
"script_name": None,
|
||
|
"script_args": args + script_args,
|
||
|
})
|
||
|
# late import, in case setuptools replaced it
|
||
|
from distutils.dist import Distribution
|
||
|
dist = Distribution(sargs)
|
||
|
if not dist.ext_modules:
|
||
|
dist.ext_modules = []
|
||
|
dist.ext_modules.append(ext)
|
||
|
if HAS_CYTHON:
|
||
|
dist.cmdclass = {'build_ext': build_ext}
|
||
|
build = dist.get_command_obj('build')
|
||
|
build.build_base = pyxbuild_dir
|
||
|
|
||
|
cfgfiles = dist.find_config_files()
|
||
|
dist.parse_config_files(cfgfiles)
|
||
|
|
||
|
try:
|
||
|
ok = dist.parse_command_line()
|
||
|
except DistutilsArgError:
|
||
|
raise
|
||
|
|
||
|
if DEBUG:
|
||
|
print("options (after parsing command line):")
|
||
|
dist.dump_option_dicts()
|
||
|
assert ok
|
||
|
|
||
|
|
||
|
try:
|
||
|
obj_build_ext = dist.get_command_obj("build_ext")
|
||
|
dist.run_commands()
|
||
|
so_path = obj_build_ext.get_outputs()[0]
|
||
|
if obj_build_ext.inplace:
|
||
|
# Python distutils get_outputs()[ returns a wrong so_path
|
||
|
# when --inplace ; see http://bugs.python.org/issue5977
|
||
|
# workaround:
|
||
|
so_path = os.path.join(os.path.dirname(filename),
|
||
|
os.path.basename(so_path))
|
||
|
if reload_support:
|
||
|
org_path = so_path
|
||
|
timestamp = os.path.getmtime(org_path)
|
||
|
global _reloads
|
||
|
last_timestamp, last_path, count = _reloads.get(org_path, (None,None,0) )
|
||
|
if last_timestamp == timestamp:
|
||
|
so_path = last_path
|
||
|
else:
|
||
|
basename = os.path.basename(org_path)
|
||
|
while count < 100:
|
||
|
count += 1
|
||
|
r_path = os.path.join(obj_build_ext.build_lib,
|
||
|
basename + '.reload%s'%count)
|
||
|
try:
|
||
|
import shutil # late import / reload_support is: debugging
|
||
|
try:
|
||
|
# Try to unlink first --- if the .so file
|
||
|
# is mmapped by another process,
|
||
|
# overwriting its contents corrupts the
|
||
|
# loaded image (on Linux) and crashes the
|
||
|
# other process. On Windows, unlinking an
|
||
|
# open file just fails.
|
||
|
if os.path.isfile(r_path):
|
||
|
os.unlink(r_path)
|
||
|
except OSError:
|
||
|
continue
|
||
|
shutil.copy2(org_path, r_path)
|
||
|
so_path = r_path
|
||
|
except IOError:
|
||
|
continue
|
||
|
break
|
||
|
else:
|
||
|
# used up all 100 slots
|
||
|
raise ImportError("reload count for %s reached maximum"%org_path)
|
||
|
_reloads[org_path]=(timestamp, so_path, count)
|
||
|
return so_path
|
||
|
except KeyboardInterrupt:
|
||
|
sys.exit(1)
|
||
|
except (IOError, os.error):
|
||
|
exc = sys.exc_info()[1]
|
||
|
error = grok_environment_error(exc)
|
||
|
|
||
|
if DEBUG:
|
||
|
sys.stderr.write(error + "\n")
|
||
|
raise
|
||
|
|
||
|
|
||
|
if __name__=="__main__":
|
||
|
pyx_to_dll("dummy.pyx")
|
||
|
from . import test
|
||
|
|