Home | History | Annotate | Download | only in pyximport
      1 """Build a Pyrex file from .pyx source to .so loadable module using
      2 the installed distutils infrastructure. Call:
      3 
      4 out_fname = pyx_to_dll("foo.pyx")
      5 """
      6 import os
      7 import sys
      8 
      9 from distutils.dist import Distribution
     10 from distutils.errors import DistutilsArgError, DistutilsError, CCompilerError
     11 from distutils.extension import Extension
     12 from distutils.util import grok_environment_error
     13 try:
     14     from Cython.Distutils import build_ext
     15     HAS_CYTHON = True
     16 except ImportError:
     17     HAS_CYTHON = False
     18 
     19 DEBUG = 0
     20 
     21 _reloads={}
     22 
     23 def pyx_to_dll(filename, ext = None, force_rebuild = 0,
     24                build_in_temp=False, pyxbuild_dir=None, setup_args={},
     25                reload_support=False, inplace=False):
     26     """Compile a PYX file to a DLL and return the name of the generated .so 
     27        or .dll ."""
     28     assert os.path.exists(filename), "Could not find %s" % os.path.abspath(filename)
     29 
     30     path, name = os.path.split(os.path.abspath(filename))
     31 
     32     if not ext:
     33         modname, extension = os.path.splitext(name)
     34         assert extension in (".pyx", ".py"), extension
     35         if not HAS_CYTHON:
     36             filename = filename[:-len(extension)] + '.c'
     37         ext = Extension(name=modname, sources=[filename])
     38 
     39     if not pyxbuild_dir:
     40         pyxbuild_dir = os.path.join(path, "_pyxbld")
     41 
     42     package_base_dir = path
     43     for package_name in ext.name.split('.')[-2::-1]:
     44         package_base_dir, pname = os.path.split(package_base_dir)
     45         if pname != package_name:
     46             # something is wrong - package path doesn't match file path
     47             package_base_dir = None
     48             break
     49 
     50     script_args=setup_args.get("script_args",[])
     51     if DEBUG or "--verbose" in script_args:
     52         quiet = "--verbose"
     53     else:
     54         quiet = "--quiet"
     55     args = [quiet, "build_ext"]
     56     if force_rebuild:
     57         args.append("--force")
     58     if inplace and package_base_dir:
     59         args.extend(['--build-lib', package_base_dir])
     60         if ext.name == '__init__' or ext.name.endswith('.__init__'):
     61             # package => provide __path__ early
     62             if not hasattr(ext, 'cython_directives'):
     63                 ext.cython_directives = {'set_initial_path' : 'SOURCEFILE'}
     64             elif 'set_initial_path' not in ext.cython_directives:
     65                 ext.cython_directives['set_initial_path'] = 'SOURCEFILE'
     66 
     67     if HAS_CYTHON and build_in_temp:
     68         args.append("--pyrex-c-in-temp")
     69     sargs = setup_args.copy()
     70     sargs.update(
     71         {"script_name": None,
     72          "script_args": args + script_args} )
     73     dist = Distribution(sargs)
     74     if not dist.ext_modules:
     75         dist.ext_modules = []
     76     dist.ext_modules.append(ext)
     77     if HAS_CYTHON:
     78         dist.cmdclass = {'build_ext': build_ext}
     79     build = dist.get_command_obj('build')
     80     build.build_base = pyxbuild_dir
     81 
     82     config_files = dist.find_config_files()
     83     try: config_files.remove('setup.cfg')
     84     except ValueError: pass
     85     dist.parse_config_files(config_files)
     86 
     87     cfgfiles = dist.find_config_files()
     88     try: cfgfiles.remove('setup.cfg')
     89     except ValueError: pass
     90     dist.parse_config_files(cfgfiles)
     91     try:
     92         ok = dist.parse_command_line()
     93     except DistutilsArgError:
     94         raise
     95 
     96     if DEBUG:
     97         print("options (after parsing command line):")
     98         dist.dump_option_dicts()
     99     assert ok
    100 
    101 
    102     try:
    103         obj_build_ext = dist.get_command_obj("build_ext")
    104         dist.run_commands()
    105         so_path = obj_build_ext.get_outputs()[0]
    106         if obj_build_ext.inplace:
    107             # Python distutils get_outputs()[ returns a wrong so_path 
    108             # when --inplace ; see http://bugs.python.org/issue5977
    109             # workaround:
    110             so_path = os.path.join(os.path.dirname(filename),
    111                                    os.path.basename(so_path))
    112         if reload_support:
    113             org_path = so_path
    114             timestamp = os.path.getmtime(org_path)
    115             global _reloads
    116             last_timestamp, last_path, count = _reloads.get(org_path, (None,None,0) )
    117             if last_timestamp == timestamp:
    118                 so_path = last_path
    119             else:
    120                 basename = os.path.basename(org_path)
    121                 while count < 100:
    122                     count += 1
    123                     r_path = os.path.join(obj_build_ext.build_lib,
    124                                           basename + '.reload%s'%count)
    125                     try:
    126                         import shutil # late import / reload_support is: debugging
    127                         try:
    128                             # Try to unlink first --- if the .so file
    129                             # is mmapped by another process,
    130                             # overwriting its contents corrupts the
    131                             # loaded image (on Linux) and crashes the
    132                             # other process. On Windows, unlinking an
    133                             # open file just fails.
    134                             if os.path.isfile(r_path):
    135                                 os.unlink(r_path)
    136                         except OSError:
    137                             continue
    138                         shutil.copy2(org_path, r_path)
    139                         so_path = r_path
    140                     except IOError:
    141                         continue
    142                     break
    143                 else:
    144                     # used up all 100 slots 
    145                     raise ImportError("reload count for %s reached maximum"%org_path)
    146                 _reloads[org_path]=(timestamp, so_path, count)
    147         return so_path
    148     except KeyboardInterrupt:
    149         sys.exit(1)
    150     except (IOError, os.error):
    151         exc = sys.exc_info()[1]
    152         error = grok_environment_error(exc)
    153 
    154         if DEBUG:
    155             sys.stderr.write(error + "\n")
    156         raise
    157 
    158 if __name__=="__main__":
    159     pyx_to_dll("dummy.pyx")
    160     import test
    161 
    162