123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- import os
- import sys
-
- try:
- basestring
- except NameError:
- # Python 3.x
- basestring = str
-
- def error(msg):
- from distutils.errors import DistutilsSetupError
- raise DistutilsSetupError(msg)
-
-
- def execfile(filename, glob):
- # We use execfile() (here rewritten for Python 3) instead of
- # __import__() to load the build script. The problem with
- # a normal import is that in some packages, the intermediate
- # __init__.py files may already try to import the file that
- # we are generating.
- with open(filename) as f:
- src = f.read()
- src += '\n' # Python 2.6 compatibility
- code = compile(src, filename, 'exec')
- exec(code, glob, glob)
-
-
- def add_cffi_module(dist, mod_spec):
- from cffi.api import FFI
-
- if not isinstance(mod_spec, basestring):
- error("argument to 'cffi_modules=...' must be a str or a list of str,"
- " not %r" % (type(mod_spec).__name__,))
- mod_spec = str(mod_spec)
- try:
- build_file_name, ffi_var_name = mod_spec.split(':')
- except ValueError:
- error("%r must be of the form 'path/build.py:ffi_variable'" %
- (mod_spec,))
- if not os.path.exists(build_file_name):
- ext = ''
- rewritten = build_file_name.replace('.', '/') + '.py'
- if os.path.exists(rewritten):
- ext = ' (rewrite cffi_modules to [%r])' % (
- rewritten + ':' + ffi_var_name,)
- error("%r does not name an existing file%s" % (build_file_name, ext))
-
- mod_vars = {'__name__': '__cffi__', '__file__': build_file_name}
- execfile(build_file_name, mod_vars)
-
- try:
- ffi = mod_vars[ffi_var_name]
- except KeyError:
- error("%r: object %r not found in module" % (mod_spec,
- ffi_var_name))
- if not isinstance(ffi, FFI):
- ffi = ffi() # maybe it's a function instead of directly an ffi
- if not isinstance(ffi, FFI):
- error("%r is not an FFI instance (got %r)" % (mod_spec,
- type(ffi).__name__))
- if not hasattr(ffi, '_assigned_source'):
- error("%r: the set_source() method was not called" % (mod_spec,))
- module_name, source, source_extension, kwds = ffi._assigned_source
- if ffi._windows_unicode:
- kwds = kwds.copy()
- ffi._apply_windows_unicode(kwds)
-
- if source is None:
- _add_py_module(dist, ffi, module_name)
- else:
- _add_c_module(dist, ffi, module_name, source, source_extension, kwds)
-
- def _set_py_limited_api(Extension, kwds):
- """
- Add py_limited_api to kwds if setuptools >= 26 is in use.
- Do not alter the setting if it already exists.
- Setuptools takes care of ignoring the flag on Python 2 and PyPy.
-
- CPython itself should ignore the flag in a debugging version
- (by not listing .abi3.so in the extensions it supports), but
- it doesn't so far, creating troubles. That's why we check
- for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent
- of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401)
-
- On Windows, with CPython <= 3.4, it's better not to use py_limited_api
- because virtualenv *still* doesn't copy PYTHON3.DLL on these versions.
- Recently (2020) we started shipping only >= 3.5 wheels, though. So
- we'll give it another try and set py_limited_api on Windows >= 3.5.
- """
- from cffi import recompiler
-
- if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount')
- and recompiler.USE_LIMITED_API):
- import setuptools
- try:
- setuptools_major_version = int(setuptools.__version__.partition('.')[0])
- if setuptools_major_version >= 26:
- kwds['py_limited_api'] = True
- except ValueError: # certain development versions of setuptools
- # If we don't know the version number of setuptools, we
- # try to set 'py_limited_api' anyway. At worst, we get a
- # warning.
- kwds['py_limited_api'] = True
- return kwds
-
- def _add_c_module(dist, ffi, module_name, source, source_extension, kwds):
- from distutils.core import Extension
- # We are a setuptools extension. Need this build_ext for py_limited_api.
- from setuptools.command.build_ext import build_ext
- from distutils.dir_util import mkpath
- from distutils import log
- from cffi import recompiler
-
- allsources = ['$PLACEHOLDER']
- allsources.extend(kwds.pop('sources', []))
- kwds = _set_py_limited_api(Extension, kwds)
- ext = Extension(name=module_name, sources=allsources, **kwds)
-
- def make_mod(tmpdir, pre_run=None):
- c_file = os.path.join(tmpdir, module_name + source_extension)
- log.info("generating cffi module %r" % c_file)
- mkpath(tmpdir)
- # a setuptools-only, API-only hook: called with the "ext" and "ffi"
- # arguments just before we turn the ffi into C code. To use it,
- # subclass the 'distutils.command.build_ext.build_ext' class and
- # add a method 'def pre_run(self, ext, ffi)'.
- if pre_run is not None:
- pre_run(ext, ffi)
- updated = recompiler.make_c_source(ffi, module_name, source, c_file)
- if not updated:
- log.info("already up-to-date")
- return c_file
-
- if dist.ext_modules is None:
- dist.ext_modules = []
- dist.ext_modules.append(ext)
-
- base_class = dist.cmdclass.get('build_ext', build_ext)
- class build_ext_make_mod(base_class):
- def run(self):
- if ext.sources[0] == '$PLACEHOLDER':
- pre_run = getattr(self, 'pre_run', None)
- ext.sources[0] = make_mod(self.build_temp, pre_run)
- base_class.run(self)
- dist.cmdclass['build_ext'] = build_ext_make_mod
- # NB. multiple runs here will create multiple 'build_ext_make_mod'
- # classes. Even in this case the 'build_ext' command should be
- # run once; but just in case, the logic above does nothing if
- # called again.
-
-
- def _add_py_module(dist, ffi, module_name):
- from distutils.dir_util import mkpath
- from setuptools.command.build_py import build_py
- from setuptools.command.build_ext import build_ext
- from distutils import log
- from cffi import recompiler
-
- def generate_mod(py_file):
- log.info("generating cffi module %r" % py_file)
- mkpath(os.path.dirname(py_file))
- updated = recompiler.make_py_source(ffi, module_name, py_file)
- if not updated:
- log.info("already up-to-date")
-
- base_class = dist.cmdclass.get('build_py', build_py)
- class build_py_make_mod(base_class):
- def run(self):
- base_class.run(self)
- module_path = module_name.split('.')
- module_path[-1] += '.py'
- generate_mod(os.path.join(self.build_lib, *module_path))
- def get_source_files(self):
- # This is called from 'setup.py sdist' only. Exclude
- # the generate .py module in this case.
- saved_py_modules = self.py_modules
- try:
- if saved_py_modules:
- self.py_modules = [m for m in saved_py_modules
- if m != module_name]
- return base_class.get_source_files(self)
- finally:
- self.py_modules = saved_py_modules
- dist.cmdclass['build_py'] = build_py_make_mod
-
- # distutils and setuptools have no notion I could find of a
- # generated python module. If we don't add module_name to
- # dist.py_modules, then things mostly work but there are some
- # combination of options (--root and --record) that will miss
- # the module. So we add it here, which gives a few apparently
- # harmless warnings about not finding the file outside the
- # build directory.
- # Then we need to hack more in get_source_files(); see above.
- if dist.py_modules is None:
- dist.py_modules = []
- dist.py_modules.append(module_name)
-
- # the following is only for "build_ext -i"
- base_class_2 = dist.cmdclass.get('build_ext', build_ext)
- class build_ext_make_mod(base_class_2):
- def run(self):
- base_class_2.run(self)
- if self.inplace:
- # from get_ext_fullpath() in distutils/command/build_ext.py
- module_path = module_name.split('.')
- package = '.'.join(module_path[:-1])
- build_py = self.get_finalized_command('build_py')
- package_dir = build_py.get_package_dir(package)
- file_name = module_path[-1] + '.py'
- generate_mod(os.path.join(package_dir, file_name))
- dist.cmdclass['build_ext'] = build_ext_make_mod
-
- def cffi_modules(dist, attr, value):
- assert attr == 'cffi_modules'
- if isinstance(value, basestring):
- value = [value]
-
- for cffi_module in value:
- add_cffi_module(dist, cffi_module)
|