1import os 2import sys 3 4try: 5 basestring 6except NameError: 7 # Python 3.x 8 basestring = str 9 10def error(msg): 11 from distutils.errors import DistutilsSetupError 12 raise DistutilsSetupError(msg) 13 14 15def execfile(filename, glob): 16 # We use execfile() (here rewritten for Python 3) instead of 17 # __import__() to load the build script. The problem with 18 # a normal import is that in some packages, the intermediate 19 # __init__.py files may already try to import the file that 20 # we are generating. 21 with open(filename) as f: 22 src = f.read() 23 src += '\n' # Python 2.6 compatibility 24 code = compile(src, filename, 'exec') 25 exec(code, glob, glob) 26 27 28def add_cffi_module(dist, mod_spec): 29 from cffi.api import FFI 30 31 if not isinstance(mod_spec, basestring): 32 error("argument to 'cffi_modules=...' must be a str or a list of str," 33 " not %r" % (type(mod_spec).__name__,)) 34 mod_spec = str(mod_spec) 35 try: 36 build_file_name, ffi_var_name = mod_spec.split(':') 37 except ValueError: 38 error("%r must be of the form 'path/build.py:ffi_variable'" % 39 (mod_spec,)) 40 if not os.path.exists(build_file_name): 41 ext = '' 42 rewritten = build_file_name.replace('.', '/') + '.py' 43 if os.path.exists(rewritten): 44 ext = ' (rewrite cffi_modules to [%r])' % ( 45 rewritten + ':' + ffi_var_name,) 46 error("%r does not name an existing file%s" % (build_file_name, ext)) 47 48 mod_vars = {'__name__': '__cffi__', '__file__': build_file_name} 49 execfile(build_file_name, mod_vars) 50 51 try: 52 ffi = mod_vars[ffi_var_name] 53 except KeyError: 54 error("%r: object %r not found in module" % (mod_spec, 55 ffi_var_name)) 56 if not isinstance(ffi, FFI): 57 ffi = ffi() # maybe it's a function instead of directly an ffi 58 if not isinstance(ffi, FFI): 59 error("%r is not an FFI instance (got %r)" % (mod_spec, 60 type(ffi).__name__)) 61 if not hasattr(ffi, '_assigned_source'): 62 error("%r: the set_source() method was not called" % (mod_spec,)) 63 module_name, source, source_extension, kwds = ffi._assigned_source 64 if ffi._windows_unicode: 65 kwds = kwds.copy() 66 ffi._apply_windows_unicode(kwds) 67 68 if source is None: 69 _add_py_module(dist, ffi, module_name) 70 else: 71 _add_c_module(dist, ffi, module_name, source, source_extension, kwds) 72 73def _set_py_limited_api(Extension, kwds): 74 """ 75 Add py_limited_api to kwds if setuptools >= 26 is in use. 76 Do not alter the setting if it already exists. 77 Setuptools takes care of ignoring the flag on Python 2 and PyPy. 78 79 CPython itself should ignore the flag in a debugging version 80 (by not listing .abi3.so in the extensions it supports), but 81 it doesn't so far, creating troubles. That's why we check 82 for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent 83 of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401) 84 85 On Windows, with CPython <= 3.4, it's better not to use py_limited_api 86 because virtualenv *still* doesn't copy PYTHON3.DLL on these versions. 87 For now we'll skip py_limited_api on all Windows versions to avoid an 88 inconsistent mess. 89 """ 90 if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount') 91 and sys.platform != 'win32'): 92 import setuptools 93 try: 94 setuptools_major_version = int(setuptools.__version__.partition('.')[0]) 95 if setuptools_major_version >= 26: 96 kwds['py_limited_api'] = True 97 except ValueError: # certain development versions of setuptools 98 # If we don't know the version number of setuptools, we 99 # try to set 'py_limited_api' anyway. At worst, we get a 100 # warning. 101 kwds['py_limited_api'] = True 102 return kwds 103 104def _add_c_module(dist, ffi, module_name, source, source_extension, kwds): 105 from distutils.core import Extension 106 # We are a setuptools extension. Need this build_ext for py_limited_api. 107 from setuptools.command.build_ext import build_ext 108 from distutils.dir_util import mkpath 109 from distutils import log 110 from cffi import recompiler 111 112 allsources = ['$PLACEHOLDER'] 113 allsources.extend(kwds.pop('sources', [])) 114 kwds = _set_py_limited_api(Extension, kwds) 115 ext = Extension(name=module_name, sources=allsources, **kwds) 116 117 def make_mod(tmpdir, pre_run=None): 118 c_file = os.path.join(tmpdir, module_name + source_extension) 119 log.info("generating cffi module %r" % c_file) 120 mkpath(tmpdir) 121 # a setuptools-only, API-only hook: called with the "ext" and "ffi" 122 # arguments just before we turn the ffi into C code. To use it, 123 # subclass the 'distutils.command.build_ext.build_ext' class and 124 # add a method 'def pre_run(self, ext, ffi)'. 125 if pre_run is not None: 126 pre_run(ext, ffi) 127 updated = recompiler.make_c_source(ffi, module_name, source, c_file) 128 if not updated: 129 log.info("already up-to-date") 130 return c_file 131 132 if dist.ext_modules is None: 133 dist.ext_modules = [] 134 dist.ext_modules.append(ext) 135 136 base_class = dist.cmdclass.get('build_ext', build_ext) 137 class build_ext_make_mod(base_class): 138 def run(self): 139 if ext.sources[0] == '$PLACEHOLDER': 140 pre_run = getattr(self, 'pre_run', None) 141 ext.sources[0] = make_mod(self.build_temp, pre_run) 142 base_class.run(self) 143 dist.cmdclass['build_ext'] = build_ext_make_mod 144 # NB. multiple runs here will create multiple 'build_ext_make_mod' 145 # classes. Even in this case the 'build_ext' command should be 146 # run once; but just in case, the logic above does nothing if 147 # called again. 148 149 150def _add_py_module(dist, ffi, module_name): 151 from distutils.dir_util import mkpath 152 from setuptools.command.build_py import build_py 153 from setuptools.command.build_ext import build_ext 154 from distutils import log 155 from cffi import recompiler 156 157 def generate_mod(py_file): 158 log.info("generating cffi module %r" % py_file) 159 mkpath(os.path.dirname(py_file)) 160 updated = recompiler.make_py_source(ffi, module_name, py_file) 161 if not updated: 162 log.info("already up-to-date") 163 164 base_class = dist.cmdclass.get('build_py', build_py) 165 class build_py_make_mod(base_class): 166 def run(self): 167 base_class.run(self) 168 module_path = module_name.split('.') 169 module_path[-1] += '.py' 170 generate_mod(os.path.join(self.build_lib, *module_path)) 171 def get_source_files(self): 172 # This is called from 'setup.py sdist' only. Exclude 173 # the generate .py module in this case. 174 saved_py_modules = self.py_modules 175 try: 176 if saved_py_modules: 177 self.py_modules = [m for m in saved_py_modules 178 if m != module_name] 179 return base_class.get_source_files(self) 180 finally: 181 self.py_modules = saved_py_modules 182 dist.cmdclass['build_py'] = build_py_make_mod 183 184 # distutils and setuptools have no notion I could find of a 185 # generated python module. If we don't add module_name to 186 # dist.py_modules, then things mostly work but there are some 187 # combination of options (--root and --record) that will miss 188 # the module. So we add it here, which gives a few apparently 189 # harmless warnings about not finding the file outside the 190 # build directory. 191 # Then we need to hack more in get_source_files(); see above. 192 if dist.py_modules is None: 193 dist.py_modules = [] 194 dist.py_modules.append(module_name) 195 196 # the following is only for "build_ext -i" 197 base_class_2 = dist.cmdclass.get('build_ext', build_ext) 198 class build_ext_make_mod(base_class_2): 199 def run(self): 200 base_class_2.run(self) 201 if self.inplace: 202 # from get_ext_fullpath() in distutils/command/build_ext.py 203 module_path = module_name.split('.') 204 package = '.'.join(module_path[:-1]) 205 build_py = self.get_finalized_command('build_py') 206 package_dir = build_py.get_package_dir(package) 207 file_name = module_path[-1] + '.py' 208 generate_mod(os.path.join(package_dir, file_name)) 209 dist.cmdclass['build_ext'] = build_ext_make_mod 210 211def cffi_modules(dist, attr, value): 212 assert attr == 'cffi_modules' 213 if isinstance(value, basestring): 214 value = [value] 215 216 for cffi_module in value: 217 add_cffi_module(dist, cffi_module) 218