Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
setuptools_ext.py217 linesDownload Raw Back to cffi
1import os
2import sys
3
4try:
5    basestring
6except NameError:
7    # Python 3.x
8    basestring = str
9
10def error(msg):
11    from cffi._shimmed_dist_utils 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    # We are a setuptools extension. Need this build_ext for py_limited_api.
108    from setuptools.command.build_ext import build_ext
109    from cffi._shimmed_dist_utils import Extension, log, mkpath
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 setuptools.command.build_py import build_py
152    from setuptools.command.build_ext import build_ext
153    from cffi._shimmed_dist_utils import log, mkpath
154    from cffi import recompiler
155
156    def generate_mod(py_file):
157        log.info("generating cffi module %r" % py_file)
158        mkpath(os.path.dirname(py_file))
159        updated = recompiler.make_py_source(ffi, module_name, py_file)
160        if not updated:
161            log.info("already up-to-date")
162
163    base_class = dist.cmdclass.get('build_py', build_py)
164    class build_py_make_mod(base_class):
165        def run(self):
166            base_class.run(self)
167            module_path = module_name.split('.')
168            module_path[-1] += '.py'
169            generate_mod(os.path.join(self.build_lib, *module_path))
170        def get_source_files(self):
171            # This is called from 'setup.py sdist' only.  Exclude
172            # the generate .py module in this case.
173            saved_py_modules = self.py_modules
174            try:
175                if saved_py_modules:
176                    self.py_modules = [m for m in saved_py_modules
177                                         if m != module_name]
178                return base_class.get_source_files(self)
179            finally:
180                self.py_modules = saved_py_modules
181    dist.cmdclass['build_py'] = build_py_make_mod
182
183    # distutils and setuptools have no notion I could find of a
184    # generated python module.  If we don't add module_name to
185    # dist.py_modules, then things mostly work but there are some
186    # combination of options (--root and --record) that will miss
187    # the module.  So we add it here, which gives a few apparently
188    # harmless warnings about not finding the file outside the
189    # build directory.
190    # Then we need to hack more in get_source_files(); see above.
191    if dist.py_modules is None:
192        dist.py_modules = []
193    dist.py_modules.append(module_name)
194
195    # the following is only for "build_ext -i"
196    base_class_2 = dist.cmdclass.get('build_ext', build_ext)
197    class build_ext_make_mod(base_class_2):
198        def run(self):
199            base_class_2.run(self)
200            if self.inplace:
201                # from get_ext_fullpath() in distutils/command/build_ext.py
202                module_path = module_name.split('.')
203                package = '.'.join(module_path[:-1])
204                build_py = self.get_finalized_command('build_py')
205                package_dir = build_py.get_package_dir(package)
206                file_name = module_path[-1] + '.py'
207                generate_mod(os.path.join(package_dir, file_name))
208    dist.cmdclass['build_ext'] = build_ext_make_mod
209
210def cffi_modules(dist, attr, value):
211    assert attr == 'cffi_modules'
212    if isinstance(value, basestring):
213        value = [value]
214
215    for cffi_module in value:
216        add_cffi_module(dist, cffi_module)
217 
codekingpro/portable-devtools · Team Ai