codekingpro/portable-devtools
114k
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 