codekingpro/portable-devtools
114k
1import os2import sys3import sysconfig4 5try:6 basestring7except NameError:8 # Python 3.x9 basestring = str10 11def error(msg):12 from cffi._shimmed_dist_utils import DistutilsSetupError13 raise DistutilsSetupError(msg)14 15 16def execfile(filename, glob):17 # We use execfile() (here rewritten for Python 3) instead of18 # __import__() to load the build script. The problem with19 # a normal import is that in some packages, the intermediate20 # __init__.py files may already try to import the file that21 # we are generating.22 with open(filename) as f:23 src = f.read()24 src += '\n' # Python 2.6 compatibility25 code = compile(src, filename, 'exec')26 exec(code, glob, glob)27 28 29def add_cffi_module(dist, mod_spec):30 from cffi.api import FFI31 32 if not isinstance(mod_spec, basestring):33 error("argument to 'cffi_modules=...' must be a str or a list of str,"34 " not %r" % (type(mod_spec).__name__,))35 mod_spec = str(mod_spec)36 try:37 build_file_name, ffi_var_name = mod_spec.split(':')38 except ValueError:39 error("%r must be of the form 'path/build.py:ffi_variable'" %40 (mod_spec,))41 if not os.path.exists(build_file_name):42 ext = ''43 rewritten = build_file_name.replace('.', '/') + '.py'44 if os.path.exists(rewritten):45 ext = ' (rewrite cffi_modules to [%r])' % (46 rewritten + ':' + ffi_var_name,)47 error("%r does not name an existing file%s" % (build_file_name, ext))48 49 mod_vars = {'__name__': '__cffi__', '__file__': build_file_name}50 execfile(build_file_name, mod_vars)51 52 try:53 ffi = mod_vars[ffi_var_name]54 except KeyError:55 error("%r: object %r not found in module" % (mod_spec,56 ffi_var_name))57 if not isinstance(ffi, FFI):58 ffi = ffi() # maybe it's a function instead of directly an ffi59 if not isinstance(ffi, FFI):60 error("%r is not an FFI instance (got %r)" % (mod_spec,61 type(ffi).__name__))62 if not hasattr(ffi, '_assigned_source'):63 error("%r: the set_source() method was not called" % (mod_spec,))64 module_name, source, source_extension, kwds = ffi._assigned_source65 if ffi._windows_unicode:66 kwds = kwds.copy()67 ffi._apply_windows_unicode(kwds)68 69 if source is None:70 _add_py_module(dist, ffi, module_name)71 else:72 _add_c_module(dist, ffi, module_name, source, source_extension, kwds)73 74def _set_py_limited_api(Extension, kwds):75 """76 Add py_limited_api to kwds if setuptools >= 26 is in use.77 Do not alter the setting if it already exists.78 Setuptools takes care of ignoring the flag on Python 2 and PyPy.79 80 CPython itself should ignore the flag in a debugging version81 (by not listing .abi3.so in the extensions it supports), but82 it doesn't so far, creating troubles. That's why we check83 for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent84 of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401)85 86 On Windows, with CPython <= 3.4, it's better not to use py_limited_api87 because virtualenv *still* doesn't copy PYTHON3.DLL on these versions.88 Recently (2020) we started shipping only >= 3.5 wheels, though. So89 we'll give it another try and set py_limited_api on Windows >= 3.5.90 """91 from cffi._shimmed_dist_utils import log92 from cffi import recompiler93 94 if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount')95 and recompiler.USE_LIMITED_API):96 import setuptools97 try:98 setuptools_major_version = int(setuptools.__version__.partition('.')[0])99 if setuptools_major_version >= 26:100 kwds['py_limited_api'] = True101 except ValueError: # certain development versions of setuptools102 # If we don't know the version number of setuptools, we103 # try to set 'py_limited_api' anyway. At worst, we get a104 # warning.105 kwds['py_limited_api'] = True106 107 if sysconfig.get_config_var("Py_GIL_DISABLED"):108 if kwds.get('py_limited_api'):109 log.info("Ignoring py_limited_api=True for free-threaded build.")110 111 kwds['py_limited_api'] = False112 113 if kwds.get('py_limited_api') is False:114 # avoid setting Py_LIMITED_API if py_limited_api=False115 # which _cffi_include.h does unless _CFFI_NO_LIMITED_API is defined116 kwds.setdefault("define_macros", []).append(("_CFFI_NO_LIMITED_API", None))117 return kwds118 119def _add_c_module(dist, ffi, module_name, source, source_extension, kwds):120 # We are a setuptools extension. Need this build_ext for py_limited_api.121 from setuptools.command.build_ext import build_ext122 from cffi._shimmed_dist_utils import Extension, log, mkpath123 from cffi import recompiler124 125 allsources = ['$PLACEHOLDER']126 allsources.extend(kwds.pop('sources', []))127 kwds = _set_py_limited_api(Extension, kwds)128 ext = Extension(name=module_name, sources=allsources, **kwds)129 130 def make_mod(tmpdir, pre_run=None):131 c_file = os.path.join(tmpdir, module_name + source_extension)132 log.info("generating cffi module %r" % c_file)133 mkpath(tmpdir)134 # a setuptools-only, API-only hook: called with the "ext" and "ffi"135 # arguments just before we turn the ffi into C code. To use it,136 # subclass the 'distutils.command.build_ext.build_ext' class and137 # add a method 'def pre_run(self, ext, ffi)'.138 if pre_run is not None:139 pre_run(ext, ffi)140 updated = recompiler.make_c_source(ffi, module_name, source, c_file)141 if not updated:142 log.info("already up-to-date")143 return c_file144 145 if dist.ext_modules is None:146 dist.ext_modules = []147 dist.ext_modules.append(ext)148 149 base_class = dist.cmdclass.get('build_ext', build_ext)150 class build_ext_make_mod(base_class):151 def run(self):152 if ext.sources[0] == '$PLACEHOLDER':153 pre_run = getattr(self, 'pre_run', None)154 ext.sources[0] = make_mod(self.build_temp, pre_run)155 base_class.run(self)156 dist.cmdclass['build_ext'] = build_ext_make_mod157 # NB. multiple runs here will create multiple 'build_ext_make_mod'158 # classes. Even in this case the 'build_ext' command should be159 # run once; but just in case, the logic above does nothing if160 # called again.161 162 163def _add_py_module(dist, ffi, module_name):164 from setuptools.command.build_py import build_py165 from setuptools.command.build_ext import build_ext166 from cffi._shimmed_dist_utils import log, mkpath167 from cffi import recompiler168 169 def generate_mod(py_file):170 log.info("generating cffi module %r" % py_file)171 mkpath(os.path.dirname(py_file))172 updated = recompiler.make_py_source(ffi, module_name, py_file)173 if not updated:174 log.info("already up-to-date")175 176 base_class = dist.cmdclass.get('build_py', build_py)177 class build_py_make_mod(base_class):178 def run(self):179 base_class.run(self)180 module_path = module_name.split('.')181 module_path[-1] += '.py'182 generate_mod(os.path.join(self.build_lib, *module_path))183 def get_source_files(self):184 # This is called from 'setup.py sdist' only. Exclude185 # the generate .py module in this case.186 saved_py_modules = self.py_modules187 try:188 if saved_py_modules:189 self.py_modules = [m for m in saved_py_modules190 if m != module_name]191 return base_class.get_source_files(self)192 finally:193 self.py_modules = saved_py_modules194 dist.cmdclass['build_py'] = build_py_make_mod195 196 # distutils and setuptools have no notion I could find of a197 # generated python module. If we don't add module_name to198 # dist.py_modules, then things mostly work but there are some199 # combination of options (--root and --record) that will miss200 # the module. So we add it here, which gives a few apparently201 # harmless warnings about not finding the file outside the202 # build directory.203 # Then we need to hack more in get_source_files(); see above.204 if dist.py_modules is None:205 dist.py_modules = []206 dist.py_modules.append(module_name)207 208 # the following is only for "build_ext -i"209 base_class_2 = dist.cmdclass.get('build_ext', build_ext)210 class build_ext_make_mod(base_class_2):211 def run(self):212 base_class_2.run(self)213 if self.inplace:214 # from get_ext_fullpath() in distutils/command/build_ext.py215 module_path = module_name.split('.')216 package = '.'.join(module_path[:-1])217 build_py = self.get_finalized_command('build_py')218 package_dir = build_py.get_package_dir(package)219 file_name = module_path[-1] + '.py'220 generate_mod(os.path.join(package_dir, file_name))221 dist.cmdclass['build_ext'] = build_ext_make_mod222 223def cffi_modules(dist, attr, value):224 assert attr == 'cffi_modules'225 if isinstance(value, basestring):226 value = [value]227 228 for cffi_module in value:229 add_cffi_module(dist, cffi_module)230 