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