]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/setuptools/command/build_ext.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / setuptools / command / build_ext.py
1 import os
2 import sys
3 import itertools
4 from importlib.machinery import EXTENSION_SUFFIXES
5 from importlib.util import cache_from_source as _compiled_file_name
6 from typing import Dict, Iterator, List, Tuple
7 from pathlib import Path
8
9 from distutils.command.build_ext import build_ext as _du_build_ext
10 from distutils.ccompiler import new_compiler
11 from distutils.sysconfig import customize_compiler, get_config_var
12 from distutils import log
13
14 from setuptools.errors import BaseError
15 from setuptools.extension import Extension, Library
16
17 try:
18 # Attempt to use Cython for building extensions, if available
19 from Cython.Distutils.build_ext import build_ext as _build_ext
20
21 # Additionally, assert that the compiler module will load
22 # also. Ref #1229.
23 __import__('Cython.Compiler.Main')
24 except ImportError:
25 _build_ext = _du_build_ext
26
27 # make sure _config_vars is initialized
28 get_config_var("LDSHARED")
29 from distutils.sysconfig import _config_vars as _CONFIG_VARS # noqa
30
31
32 def _customize_compiler_for_shlib(compiler):
33 if sys.platform == "darwin":
34 # building .dylib requires additional compiler flags on OSX; here we
35 # temporarily substitute the pyconfig.h variables so that distutils'
36 # 'customize_compiler' uses them before we build the shared libraries.
37 tmp = _CONFIG_VARS.copy()
38 try:
39 # XXX Help! I don't have any idea whether these are right...
40 _CONFIG_VARS[
41 'LDSHARED'
42 ] = "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup"
43 _CONFIG_VARS['CCSHARED'] = " -dynamiclib"
44 _CONFIG_VARS['SO'] = ".dylib"
45 customize_compiler(compiler)
46 finally:
47 _CONFIG_VARS.clear()
48 _CONFIG_VARS.update(tmp)
49 else:
50 customize_compiler(compiler)
51
52
53 have_rtld = False
54 use_stubs = False
55 libtype = 'shared'
56
57 if sys.platform == "darwin":
58 use_stubs = True
59 elif os.name != 'nt':
60 try:
61 import dl
62
63 use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW')
64 except ImportError:
65 pass
66
67
68 def if_dl(s):
69 return s if have_rtld else ''
70
71
72 def get_abi3_suffix():
73 """Return the file extension for an abi3-compliant Extension()"""
74 for suffix in EXTENSION_SUFFIXES:
75 if '.abi3' in suffix: # Unix
76 return suffix
77 elif suffix == '.pyd': # Windows
78 return suffix
79
80
81 class build_ext(_build_ext):
82 editable_mode: bool = False
83 inplace: bool = False
84
85 def run(self):
86 """Build extensions in build directory, then copy if --inplace"""
87 old_inplace, self.inplace = self.inplace, 0
88 _build_ext.run(self)
89 self.inplace = old_inplace
90 if old_inplace:
91 self.copy_extensions_to_source()
92
93 def _get_inplace_equivalent(self, build_py, ext: Extension) -> Tuple[str, str]:
94 fullname = self.get_ext_fullname(ext.name)
95 filename = self.get_ext_filename(fullname)
96 modpath = fullname.split('.')
97 package = '.'.join(modpath[:-1])
98 package_dir = build_py.get_package_dir(package)
99 inplace_file = os.path.join(package_dir, os.path.basename(filename))
100 regular_file = os.path.join(self.build_lib, filename)
101 return (inplace_file, regular_file)
102
103 def copy_extensions_to_source(self):
104 build_py = self.get_finalized_command('build_py')
105 for ext in self.extensions:
106 inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)
107
108 # Always copy, even if source is older than destination, to ensure
109 # that the right extensions for the current Python/platform are
110 # used.
111 if os.path.exists(regular_file) or not ext.optional:
112 self.copy_file(regular_file, inplace_file, level=self.verbose)
113
114 if ext._needs_stub:
115 inplace_stub = self._get_equivalent_stub(ext, inplace_file)
116 self._write_stub_file(inplace_stub, ext, compile=True)
117 # Always compile stub and remove the original (leave the cache behind)
118 # (this behaviour was observed in previous iterations of the code)
119
120 def _get_equivalent_stub(self, ext: Extension, output_file: str) -> str:
121 dir_ = os.path.dirname(output_file)
122 _, _, name = ext.name.rpartition(".")
123 return f"{os.path.join(dir_, name)}.py"
124
125 def _get_output_mapping(self) -> Iterator[Tuple[str, str]]:
126 if not self.inplace:
127 return
128
129 build_py = self.get_finalized_command('build_py')
130 opt = self.get_finalized_command('install_lib').optimize or ""
131
132 for ext in self.extensions:
133 inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)
134 yield (regular_file, inplace_file)
135
136 if ext._needs_stub:
137 # This version of `build_ext` always builds artifacts in another dir,
138 # when "inplace=True" is given it just copies them back.
139 # This is done in the `copy_extensions_to_source` function, which
140 # always compile stub files via `_compile_and_remove_stub`.
141 # At the end of the process, a `.pyc` stub file is created without the
142 # corresponding `.py`.
143
144 inplace_stub = self._get_equivalent_stub(ext, inplace_file)
145 regular_stub = self._get_equivalent_stub(ext, regular_file)
146 inplace_cache = _compiled_file_name(inplace_stub, optimization=opt)
147 output_cache = _compiled_file_name(regular_stub, optimization=opt)
148 yield (output_cache, inplace_cache)
149
150 def get_ext_filename(self, fullname):
151 so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX')
152 if so_ext:
153 filename = os.path.join(*fullname.split('.')) + so_ext
154 else:
155 filename = _build_ext.get_ext_filename(self, fullname)
156 so_ext = get_config_var('EXT_SUFFIX')
157
158 if fullname in self.ext_map:
159 ext = self.ext_map[fullname]
160 use_abi3 = getattr(ext, 'py_limited_api') and get_abi3_suffix()
161 if use_abi3:
162 filename = filename[: -len(so_ext)]
163 so_ext = get_abi3_suffix()
164 filename = filename + so_ext
165 if isinstance(ext, Library):
166 fn, ext = os.path.splitext(filename)
167 return self.shlib_compiler.library_filename(fn, libtype)
168 elif use_stubs and ext._links_to_dynamic:
169 d, fn = os.path.split(filename)
170 return os.path.join(d, 'dl-' + fn)
171 return filename
172
173 def initialize_options(self):
174 _build_ext.initialize_options(self)
175 self.shlib_compiler = None
176 self.shlibs = []
177 self.ext_map = {}
178 self.editable_mode = False
179
180 def finalize_options(self):
181 _build_ext.finalize_options(self)
182 self.extensions = self.extensions or []
183 self.check_extensions_list(self.extensions)
184 self.shlibs = [ext for ext in self.extensions if isinstance(ext, Library)]
185 if self.shlibs:
186 self.setup_shlib_compiler()
187 for ext in self.extensions:
188 ext._full_name = self.get_ext_fullname(ext.name)
189 for ext in self.extensions:
190 fullname = ext._full_name
191 self.ext_map[fullname] = ext
192
193 # distutils 3.1 will also ask for module names
194 # XXX what to do with conflicts?
195 self.ext_map[fullname.split('.')[-1]] = ext
196
197 ltd = self.shlibs and self.links_to_dynamic(ext) or False
198 ns = ltd and use_stubs and not isinstance(ext, Library)
199 ext._links_to_dynamic = ltd
200 ext._needs_stub = ns
201 filename = ext._file_name = self.get_ext_filename(fullname)
202 libdir = os.path.dirname(os.path.join(self.build_lib, filename))
203 if ltd and libdir not in ext.library_dirs:
204 ext.library_dirs.append(libdir)
205 if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs:
206 ext.runtime_library_dirs.append(os.curdir)
207
208 if self.editable_mode:
209 self.inplace = True
210
211 def setup_shlib_compiler(self):
212 compiler = self.shlib_compiler = new_compiler(
213 compiler=self.compiler, dry_run=self.dry_run, force=self.force
214 )
215 _customize_compiler_for_shlib(compiler)
216
217 if self.include_dirs is not None:
218 compiler.set_include_dirs(self.include_dirs)
219 if self.define is not None:
220 # 'define' option is a list of (name,value) tuples
221 for name, value in self.define:
222 compiler.define_macro(name, value)
223 if self.undef is not None:
224 for macro in self.undef:
225 compiler.undefine_macro(macro)
226 if self.libraries is not None:
227 compiler.set_libraries(self.libraries)
228 if self.library_dirs is not None:
229 compiler.set_library_dirs(self.library_dirs)
230 if self.rpath is not None:
231 compiler.set_runtime_library_dirs(self.rpath)
232 if self.link_objects is not None:
233 compiler.set_link_objects(self.link_objects)
234
235 # hack so distutils' build_extension() builds a library instead
236 compiler.link_shared_object = link_shared_object.__get__(compiler)
237
238 def get_export_symbols(self, ext):
239 if isinstance(ext, Library):
240 return ext.export_symbols
241 return _build_ext.get_export_symbols(self, ext)
242
243 def build_extension(self, ext):
244 ext._convert_pyx_sources_to_lang()
245 _compiler = self.compiler
246 try:
247 if isinstance(ext, Library):
248 self.compiler = self.shlib_compiler
249 _build_ext.build_extension(self, ext)
250 if ext._needs_stub:
251 build_lib = self.get_finalized_command('build_py').build_lib
252 self.write_stub(build_lib, ext)
253 finally:
254 self.compiler = _compiler
255
256 def links_to_dynamic(self, ext):
257 """Return true if 'ext' links to a dynamic lib in the same package"""
258 # XXX this should check to ensure the lib is actually being built
259 # XXX as dynamic, and not just using a locally-found version or a
260 # XXX static-compiled version
261 libnames = dict.fromkeys([lib._full_name for lib in self.shlibs])
262 pkg = '.'.join(ext._full_name.split('.')[:-1] + [''])
263 return any(pkg + libname in libnames for libname in ext.libraries)
264
265 def get_source_files(self) -> List[str]:
266 return [*_build_ext.get_source_files(self), *self._get_internal_depends()]
267
268 def _get_internal_depends(self) -> Iterator[str]:
269 """Yield ``ext.depends`` that are contained by the project directory"""
270 project_root = Path(self.distribution.src_root or os.curdir).resolve()
271 depends = (dep for ext in self.extensions for dep in ext.depends)
272
273 def skip(orig_path: str, reason: str) -> None:
274 log.info(
275 "dependency %s won't be automatically "
276 "included in the manifest: the path %s",
277 orig_path,
278 reason,
279 )
280
281 for dep in depends:
282 path = Path(dep)
283
284 if path.is_absolute():
285 skip(dep, "must be relative")
286 continue
287
288 if ".." in path.parts:
289 skip(dep, "can't have `..` segments")
290 continue
291
292 try:
293 resolved = (project_root / path).resolve(strict=True)
294 except OSError:
295 skip(dep, "doesn't exist")
296 continue
297
298 try:
299 resolved.relative_to(project_root)
300 except ValueError:
301 skip(dep, "must be inside the project root")
302 continue
303
304 yield path.as_posix()
305
306 def get_outputs(self) -> List[str]:
307 if self.inplace:
308 return list(self.get_output_mapping().keys())
309 return sorted(_build_ext.get_outputs(self) + self.__get_stubs_outputs())
310
311 def get_output_mapping(self) -> Dict[str, str]:
312 """See :class:`setuptools.commands.build.SubCommand`"""
313 mapping = self._get_output_mapping()
314 return dict(sorted(mapping, key=lambda x: x[0]))
315
316 def __get_stubs_outputs(self):
317 # assemble the base name for each extension that needs a stub
318 ns_ext_bases = (
319 os.path.join(self.build_lib, *ext._full_name.split('.'))
320 for ext in self.extensions
321 if ext._needs_stub
322 )
323 # pair each base with the extension
324 pairs = itertools.product(ns_ext_bases, self.__get_output_extensions())
325 return list(base + fnext for base, fnext in pairs)
326
327 def __get_output_extensions(self):
328 yield '.py'
329 yield '.pyc'
330 if self.get_finalized_command('build_py').optimize:
331 yield '.pyo'
332
333 def write_stub(self, output_dir, ext, compile=False):
334 stub_file = os.path.join(output_dir, *ext._full_name.split('.')) + '.py'
335 self._write_stub_file(stub_file, ext, compile)
336
337 def _write_stub_file(self, stub_file: str, ext: Extension, compile=False):
338 log.info("writing stub loader for %s to %s", ext._full_name, stub_file)
339 if compile and os.path.exists(stub_file):
340 raise BaseError(stub_file + " already exists! Please delete.")
341 if not self.dry_run:
342 f = open(stub_file, 'w')
343 f.write(
344 '\n'.join(
345 [
346 "def __bootstrap__():",
347 " global __bootstrap__, __file__, __loader__",
348 " import sys, os, pkg_resources, importlib.util"
349 + if_dl(", dl"),
350 " __file__ = pkg_resources.resource_filename"
351 "(__name__,%r)" % os.path.basename(ext._file_name),
352 " del __bootstrap__",
353 " if '__loader__' in globals():",
354 " del __loader__",
355 if_dl(" old_flags = sys.getdlopenflags()"),
356 " old_dir = os.getcwd()",
357 " try:",
358 " os.chdir(os.path.dirname(__file__))",
359 if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"),
360 " spec = importlib.util.spec_from_file_location(",
361 " __name__, __file__)",
362 " mod = importlib.util.module_from_spec(spec)",
363 " spec.loader.exec_module(mod)",
364 " finally:",
365 if_dl(" sys.setdlopenflags(old_flags)"),
366 " os.chdir(old_dir)",
367 "__bootstrap__()",
368 "", # terminal \n
369 ]
370 )
371 )
372 f.close()
373 if compile:
374 self._compile_and_remove_stub(stub_file)
375
376 def _compile_and_remove_stub(self, stub_file: str):
377 from distutils.util import byte_compile
378
379 byte_compile([stub_file], optimize=0, force=True, dry_run=self.dry_run)
380 optimize = self.get_finalized_command('install_lib').optimize
381 if optimize > 0:
382 byte_compile(
383 [stub_file], optimize=optimize, force=True, dry_run=self.dry_run
384 )
385 if os.path.exists(stub_file) and not self.dry_run:
386 os.unlink(stub_file)
387
388
389 if use_stubs or os.name == 'nt':
390 # Build shared libraries
391 #
392 def link_shared_object(
393 self,
394 objects,
395 output_libname,
396 output_dir=None,
397 libraries=None,
398 library_dirs=None,
399 runtime_library_dirs=None,
400 export_symbols=None,
401 debug=0,
402 extra_preargs=None,
403 extra_postargs=None,
404 build_temp=None,
405 target_lang=None,
406 ):
407 self.link(
408 self.SHARED_LIBRARY,
409 objects,
410 output_libname,
411 output_dir,
412 libraries,
413 library_dirs,
414 runtime_library_dirs,
415 export_symbols,
416 debug,
417 extra_preargs,
418 extra_postargs,
419 build_temp,
420 target_lang,
421 )
422
423 else:
424 # Build static libraries everywhere else
425 libtype = 'static'
426
427 def link_shared_object(
428 self,
429 objects,
430 output_libname,
431 output_dir=None,
432 libraries=None,
433 library_dirs=None,
434 runtime_library_dirs=None,
435 export_symbols=None,
436 debug=0,
437 extra_preargs=None,
438 extra_postargs=None,
439 build_temp=None,
440 target_lang=None,
441 ):
442 # XXX we need to either disallow these attrs on Library instances,
443 # or warn/abort here if set, or something...
444 # libraries=None, library_dirs=None, runtime_library_dirs=None,
445 # export_symbols=None, extra_preargs=None, extra_postargs=None,
446 # build_temp=None
447
448 assert output_dir is None # distutils build_ext doesn't pass this
449 output_dir, filename = os.path.split(output_libname)
450 basename, ext = os.path.splitext(filename)
451 if self.library_filename("x").startswith('lib'):
452 # strip 'lib' prefix; this is kludgy if some platform uses
453 # a different prefix
454 basename = basename[3:]
455
456 self.create_static_lib(objects, basename, output_dir, debug, target_lang)