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