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 Recently (2020) we started shipping only >= 3.5 wheels, though. So 88 we'll give it another try and set py_limited_api on Windows >= 3.5. 89 """ 90 from cffi import recompiler 91 92 if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount') 93 and recompiler.USE_LIMITED_API): 94 import setuptools 95 try: 96 setuptools_major_version = int(setuptools.__version__.partition('.')[0]) 97 if setuptools_major_version >= 26: 98 kwds['py_limited_api'] = True 99 except ValueError: # certain development versions of setuptools 100 # If we don't know the version number of setuptools, we 101 # try to set 'py_limited_api' anyway. At worst, we get a 102 # warning. 103 kwds['py_limited_api'] = True 104 return kwds 105 106def _add_c_module(dist, ffi, module_name, source, source_extension, kwds): 107 from distutils.core import Extension 108 # We are a setuptools extension. Need this build_ext for py_limited_api. 109 from setuptools.command.build_ext import build_ext 110 from distutils.dir_util import mkpath 111 from distutils import log 112 from cffi import recompiler 113 114 allsources = ['$PLACEHOLDER'] 115 allsources.extend(kwds.pop('sources', [])) 116 kwds = _set_py_limited_api(Extension, kwds) 117 ext = Extension(name=module_name, sources=allsources, **kwds) 118 119 def make_mod(tmpdir, pre_run=None): 120 c_file = os.path.join(tmpdir, module_name + source_extension) 121 log.info("generating cffi module %r" % c_file) 122 mkpath(tmpdir) 123 # a setuptools-only, API-only hook: called with the "ext" and "ffi" 124 # arguments just before we turn the ffi into C code. To use it, 125 # subclass the 'distutils.command.build_ext.build_ext' class and 126 # add a method 'def pre_run(self, ext, ffi)'. 127 if pre_run is not None: 128 pre_run(ext, ffi) 129 updated = recompiler.make_c_source(ffi, module_name, source, c_file) 130 if not updated: 131 log.info("already up-to-date") 132 return c_file 133 134 if dist.ext_modules is None: 135 dist.ext_modules = [] 136 dist.ext_modules.append(ext) 137 138 base_class = dist.cmdclass.get('build_ext', build_ext) 139 class build_ext_make_mod(base_class): 140 def run(self): 141 if ext.sources[0] == '$PLACEHOLDER': 142 pre_run = getattr(self, 'pre_run', None) 143 ext.sources[0] = make_mod(self.build_temp, pre_run) 144 base_class.run(self) 145 dist.cmdclass['build_ext'] = build_ext_make_mod 146 # NB. multiple runs here will create multiple 'build_ext_make_mod' 147 # classes. Even in this case the 'build_ext' command should be 148 # run once; but just in case, the logic above does nothing if 149 # called again. 150 151 152def _add_py_module(dist, ffi, module_name): 153 from distutils.dir_util import mkpath 154 from setuptools.command.build_py import build_py 155 from setuptools.command.build_ext import build_ext 156 from distutils import log 157 from cffi import recompiler 158 159 def generate_mod(py_file): 160 log.info("generating cffi module %r" % py_file) 161 mkpath(os.path.dirname(py_file)) 162 updated = recompiler.make_py_source(ffi, module_name, py_file) 163 if not updated: 164 log.info("already up-to-date") 165 166 base_class = dist.cmdclass.get('build_py', build_py) 167 class build_py_make_mod(base_class): 168 def run(self): 169 base_class.run(self) 170 module_path = module_name.split('.') 171 module_path[-1] += '.py' 172 generate_mod(os.path.join(self.build_lib, *module_path)) 173 def get_source_files(self): 174 # This is called from 'setup.py sdist' only. Exclude 175 # the generate .py module in this case. 176 saved_py_modules = self.py_modules 177 try: 178 if saved_py_modules: 179 self.py_modules = [m for m in saved_py_modules 180 if m != module_name] 181 return base_class.get_source_files(self) 182 finally: 183 self.py_modules = saved_py_modules 184 dist.cmdclass['build_py'] = build_py_make_mod 185 186 # distutils and setuptools have no notion I could find of a 187 # generated python module. If we don't add module_name to 188 # dist.py_modules, then things mostly work but there are some 189 # combination of options (--root and --record) that will miss 190 # the module. So we add it here, which gives a few apparently 191 # harmless warnings about not finding the file outside the 192 # build directory. 193 # Then we need to hack more in get_source_files(); see above. 194 if dist.py_modules is None: 195 dist.py_modules = [] 196 dist.py_modules.append(module_name) 197 198 # the following is only for "build_ext -i" 199 base_class_2 = dist.cmdclass.get('build_ext', build_ext) 200 class build_ext_make_mod(base_class_2): 201 def run(self): 202 base_class_2.run(self) 203 if self.inplace: 204 # from get_ext_fullpath() in distutils/command/build_ext.py 205 module_path = module_name.split('.') 206 package = '.'.join(module_path[:-1]) 207 build_py = self.get_finalized_command('build_py') 208 package_dir = build_py.get_package_dir(package) 209 file_name = module_path[-1] + '.py' 210 generate_mod(os.path.join(package_dir, file_name)) 211 dist.cmdclass['build_ext'] = build_ext_make_mod 212 213def cffi_modules(dist, attr, value): 214 assert attr == 'cffi_modules' 215 if isinstance(value, basestring): 216 value = [value] 217 218 for cffi_module in value: 219 add_cffi_module(dist, cffi_module) 220