]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | """Provide access to Python's configuration information. The specific |
2 | configuration variables available depend heavily on the platform and | |
3 | configuration. The values may be retrieved using | |
4 | get_config_var(name), and the list of variables is available via | |
5 | get_config_vars().keys(). Additional convenience functions are also | |
6 | available. | |
7 | ||
8 | Written by: Fred L. Drake, Jr. | |
9 | Email: <fdrake@acm.org> | |
10 | """ | |
11 | ||
12 | import os | |
13 | import re | |
14 | import sys | |
15 | import sysconfig | |
16 | import pathlib | |
17 | ||
18 | from .errors import DistutilsPlatformError | |
19 | from . import py39compat | |
20 | from ._functools import pass_none | |
21 | ||
22 | IS_PYPY = '__pypy__' in sys.builtin_module_names | |
23 | ||
24 | # These are needed in a couple of spots, so just compute them once. | |
25 | PREFIX = os.path.normpath(sys.prefix) | |
26 | EXEC_PREFIX = os.path.normpath(sys.exec_prefix) | |
27 | BASE_PREFIX = os.path.normpath(sys.base_prefix) | |
28 | BASE_EXEC_PREFIX = os.path.normpath(sys.base_exec_prefix) | |
29 | ||
30 | # Path to the base directory of the project. On Windows the binary may | |
31 | # live in project/PCbuild/win32 or project/PCbuild/amd64. | |
32 | # set for cross builds | |
33 | if "_PYTHON_PROJECT_BASE" in os.environ: | |
34 | project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"]) | |
35 | else: | |
36 | if sys.executable: | |
37 | project_base = os.path.dirname(os.path.abspath(sys.executable)) | |
38 | else: | |
39 | # sys.executable can be empty if argv[0] has been changed and Python is | |
40 | # unable to retrieve the real program name | |
41 | project_base = os.getcwd() | |
42 | ||
43 | ||
44 | def _is_python_source_dir(d): | |
45 | """ | |
46 | Return True if the target directory appears to point to an | |
47 | un-installed Python. | |
48 | """ | |
49 | modules = pathlib.Path(d).joinpath('Modules') | |
50 | return any(modules.joinpath(fn).is_file() for fn in ('Setup', 'Setup.local')) | |
51 | ||
52 | ||
53 | _sys_home = getattr(sys, '_home', None) | |
54 | ||
55 | ||
56 | def _is_parent(dir_a, dir_b): | |
57 | """ | |
58 | Return True if a is a parent of b. | |
59 | """ | |
60 | return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b)) | |
61 | ||
62 | ||
63 | if os.name == 'nt': | |
64 | ||
65 | @pass_none | |
66 | def _fix_pcbuild(d): | |
67 | # In a venv, sys._home will be inside BASE_PREFIX rather than PREFIX. | |
68 | prefixes = PREFIX, BASE_PREFIX | |
69 | matched = ( | |
70 | prefix | |
71 | for prefix in prefixes | |
72 | if _is_parent(d, os.path.join(prefix, "PCbuild")) | |
73 | ) | |
74 | return next(matched, d) | |
75 | ||
76 | project_base = _fix_pcbuild(project_base) | |
77 | _sys_home = _fix_pcbuild(_sys_home) | |
78 | ||
79 | ||
80 | def _python_build(): | |
81 | if _sys_home: | |
82 | return _is_python_source_dir(_sys_home) | |
83 | return _is_python_source_dir(project_base) | |
84 | ||
85 | ||
86 | python_build = _python_build() | |
87 | ||
88 | ||
89 | # Calculate the build qualifier flags if they are defined. Adding the flags | |
90 | # to the include and lib directories only makes sense for an installation, not | |
91 | # an in-source build. | |
92 | build_flags = '' | |
93 | try: | |
94 | if not python_build: | |
95 | build_flags = sys.abiflags | |
96 | except AttributeError: | |
97 | # It's not a configure-based build, so the sys module doesn't have | |
98 | # this attribute, which is fine. | |
99 | pass | |
100 | ||
101 | ||
102 | def get_python_version(): | |
103 | """Return a string containing the major and minor Python version, | |
104 | leaving off the patchlevel. Sample return values could be '1.5' | |
105 | or '2.2'. | |
106 | """ | |
107 | return '%d.%d' % sys.version_info[:2] | |
108 | ||
109 | ||
110 | def get_python_inc(plat_specific=0, prefix=None): | |
111 | """Return the directory containing installed Python header files. | |
112 | ||
113 | If 'plat_specific' is false (the default), this is the path to the | |
114 | non-platform-specific header files, i.e. Python.h and so on; | |
115 | otherwise, this is the path to platform-specific header files | |
116 | (namely pyconfig.h). | |
117 | ||
118 | If 'prefix' is supplied, use it instead of sys.base_prefix or | |
119 | sys.base_exec_prefix -- i.e., ignore 'plat_specific'. | |
120 | """ | |
121 | default_prefix = BASE_EXEC_PREFIX if plat_specific else BASE_PREFIX | |
122 | resolved_prefix = prefix if prefix is not None else default_prefix | |
123 | try: | |
124 | getter = globals()[f'_get_python_inc_{os.name}'] | |
125 | except KeyError: | |
126 | raise DistutilsPlatformError( | |
127 | "I don't know where Python installs its C header files " | |
128 | "on platform '%s'" % os.name | |
129 | ) | |
130 | return getter(resolved_prefix, prefix, plat_specific) | |
131 | ||
132 | ||
133 | def _get_python_inc_posix(prefix, spec_prefix, plat_specific): | |
134 | if IS_PYPY and sys.version_info < (3, 8): | |
135 | return os.path.join(prefix, 'include') | |
136 | return ( | |
137 | _get_python_inc_posix_python(plat_specific) | |
138 | or _get_python_inc_from_config(plat_specific, spec_prefix) | |
139 | or _get_python_inc_posix_prefix(prefix) | |
140 | ) | |
141 | ||
142 | ||
143 | def _get_python_inc_posix_python(plat_specific): | |
144 | """ | |
145 | Assume the executable is in the build directory. The | |
146 | pyconfig.h file should be in the same directory. Since | |
147 | the build directory may not be the source directory, | |
148 | use "srcdir" from the makefile to find the "Include" | |
149 | directory. | |
150 | """ | |
151 | if not python_build: | |
152 | return | |
153 | if plat_specific: | |
154 | return _sys_home or project_base | |
155 | incdir = os.path.join(get_config_var('srcdir'), 'Include') | |
156 | return os.path.normpath(incdir) | |
157 | ||
158 | ||
159 | def _get_python_inc_from_config(plat_specific, spec_prefix): | |
160 | """ | |
161 | If no prefix was explicitly specified, provide the include | |
162 | directory from the config vars. Useful when | |
163 | cross-compiling, since the config vars may come from | |
164 | the host | |
165 | platform Python installation, while the current Python | |
166 | executable is from the build platform installation. | |
167 | ||
168 | >>> monkeypatch = getfixture('monkeypatch') | |
169 | >>> gpifc = _get_python_inc_from_config | |
170 | >>> monkeypatch.setitem(gpifc.__globals__, 'get_config_var', str.lower) | |
171 | >>> gpifc(False, '/usr/bin/') | |
172 | >>> gpifc(False, '') | |
173 | >>> gpifc(False, None) | |
174 | 'includepy' | |
175 | >>> gpifc(True, None) | |
176 | 'confincludepy' | |
177 | """ | |
178 | if spec_prefix is None: | |
179 | return get_config_var('CONF' * plat_specific + 'INCLUDEPY') | |
180 | ||
181 | ||
182 | def _get_python_inc_posix_prefix(prefix): | |
183 | implementation = 'pypy' if IS_PYPY else 'python' | |
184 | python_dir = implementation + get_python_version() + build_flags | |
185 | return os.path.join(prefix, "include", python_dir) | |
186 | ||
187 | ||
188 | def _get_python_inc_nt(prefix, spec_prefix, plat_specific): | |
189 | if python_build: | |
190 | # Include both the include and PC dir to ensure we can find | |
191 | # pyconfig.h | |
192 | return ( | |
193 | os.path.join(prefix, "include") | |
194 | + os.path.pathsep | |
195 | + os.path.join(prefix, "PC") | |
196 | ) | |
197 | return os.path.join(prefix, "include") | |
198 | ||
199 | ||
200 | # allow this behavior to be monkey-patched. Ref pypa/distutils#2. | |
201 | def _posix_lib(standard_lib, libpython, early_prefix, prefix): | |
202 | if standard_lib: | |
203 | return libpython | |
204 | else: | |
205 | return os.path.join(libpython, "site-packages") | |
206 | ||
207 | ||
208 | def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): | |
209 | """Return the directory containing the Python library (standard or | |
210 | site additions). | |
211 | ||
212 | If 'plat_specific' is true, return the directory containing | |
213 | platform-specific modules, i.e. any module from a non-pure-Python | |
214 | module distribution; otherwise, return the platform-shared library | |
215 | directory. If 'standard_lib' is true, return the directory | |
216 | containing standard Python library modules; otherwise, return the | |
217 | directory for site-specific modules. | |
218 | ||
219 | If 'prefix' is supplied, use it instead of sys.base_prefix or | |
220 | sys.base_exec_prefix -- i.e., ignore 'plat_specific'. | |
221 | """ | |
222 | ||
223 | if IS_PYPY and sys.version_info < (3, 8): | |
224 | # PyPy-specific schema | |
225 | if prefix is None: | |
226 | prefix = PREFIX | |
227 | if standard_lib: | |
228 | return os.path.join(prefix, "lib-python", sys.version[0]) | |
229 | return os.path.join(prefix, 'site-packages') | |
230 | ||
231 | early_prefix = prefix | |
232 | ||
233 | if prefix is None: | |
234 | if standard_lib: | |
235 | prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX | |
236 | else: | |
237 | prefix = plat_specific and EXEC_PREFIX or PREFIX | |
238 | ||
239 | if os.name == "posix": | |
240 | if plat_specific or standard_lib: | |
241 | # Platform-specific modules (any module from a non-pure-Python | |
242 | # module distribution) or standard Python library modules. | |
243 | libdir = getattr(sys, "platlibdir", "lib") | |
244 | else: | |
245 | # Pure Python | |
246 | libdir = "lib" | |
247 | implementation = 'pypy' if IS_PYPY else 'python' | |
248 | libpython = os.path.join(prefix, libdir, implementation + get_python_version()) | |
249 | return _posix_lib(standard_lib, libpython, early_prefix, prefix) | |
250 | elif os.name == "nt": | |
251 | if standard_lib: | |
252 | return os.path.join(prefix, "Lib") | |
253 | else: | |
254 | return os.path.join(prefix, "Lib", "site-packages") | |
255 | else: | |
256 | raise DistutilsPlatformError( | |
257 | "I don't know where Python installs its library " | |
258 | "on platform '%s'" % os.name | |
259 | ) | |
260 | ||
261 | ||
262 | def customize_compiler(compiler): # noqa: C901 | |
263 | """Do any platform-specific customization of a CCompiler instance. | |
264 | ||
265 | Mainly needed on Unix, so we can plug in the information that | |
266 | varies across Unices and is stored in Python's Makefile. | |
267 | """ | |
268 | if compiler.compiler_type == "unix": | |
269 | if sys.platform == "darwin": | |
270 | # Perform first-time customization of compiler-related | |
271 | # config vars on OS X now that we know we need a compiler. | |
272 | # This is primarily to support Pythons from binary | |
273 | # installers. The kind and paths to build tools on | |
274 | # the user system may vary significantly from the system | |
275 | # that Python itself was built on. Also the user OS | |
276 | # version and build tools may not support the same set | |
277 | # of CPU architectures for universal builds. | |
278 | global _config_vars | |
279 | # Use get_config_var() to ensure _config_vars is initialized. | |
280 | if not get_config_var('CUSTOMIZED_OSX_COMPILER'): | |
281 | import _osx_support | |
282 | ||
283 | _osx_support.customize_compiler(_config_vars) | |
284 | _config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True' | |
285 | ||
286 | ( | |
287 | cc, | |
288 | cxx, | |
289 | cflags, | |
290 | ccshared, | |
291 | ldshared, | |
292 | shlib_suffix, | |
293 | ar, | |
294 | ar_flags, | |
295 | ) = get_config_vars( | |
296 | 'CC', | |
297 | 'CXX', | |
298 | 'CFLAGS', | |
299 | 'CCSHARED', | |
300 | 'LDSHARED', | |
301 | 'SHLIB_SUFFIX', | |
302 | 'AR', | |
303 | 'ARFLAGS', | |
304 | ) | |
305 | ||
306 | if 'CC' in os.environ: | |
307 | newcc = os.environ['CC'] | |
308 | if 'LDSHARED' not in os.environ and ldshared.startswith(cc): | |
309 | # If CC is overridden, use that as the default | |
310 | # command for LDSHARED as well | |
311 | ldshared = newcc + ldshared[len(cc) :] | |
312 | cc = newcc | |
313 | if 'CXX' in os.environ: | |
314 | cxx = os.environ['CXX'] | |
315 | if 'LDSHARED' in os.environ: | |
316 | ldshared = os.environ['LDSHARED'] | |
317 | if 'CPP' in os.environ: | |
318 | cpp = os.environ['CPP'] | |
319 | else: | |
320 | cpp = cc + " -E" # not always | |
321 | if 'LDFLAGS' in os.environ: | |
322 | ldshared = ldshared + ' ' + os.environ['LDFLAGS'] | |
323 | if 'CFLAGS' in os.environ: | |
324 | cflags = cflags + ' ' + os.environ['CFLAGS'] | |
325 | ldshared = ldshared + ' ' + os.environ['CFLAGS'] | |
326 | if 'CPPFLAGS' in os.environ: | |
327 | cpp = cpp + ' ' + os.environ['CPPFLAGS'] | |
328 | cflags = cflags + ' ' + os.environ['CPPFLAGS'] | |
329 | ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] | |
330 | if 'AR' in os.environ: | |
331 | ar = os.environ['AR'] | |
332 | if 'ARFLAGS' in os.environ: | |
333 | archiver = ar + ' ' + os.environ['ARFLAGS'] | |
334 | else: | |
335 | archiver = ar + ' ' + ar_flags | |
336 | ||
337 | cc_cmd = cc + ' ' + cflags | |
338 | compiler.set_executables( | |
339 | preprocessor=cpp, | |
340 | compiler=cc_cmd, | |
341 | compiler_so=cc_cmd + ' ' + ccshared, | |
342 | compiler_cxx=cxx, | |
343 | linker_so=ldshared, | |
344 | linker_exe=cc, | |
345 | archiver=archiver, | |
346 | ) | |
347 | ||
348 | if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None): | |
349 | compiler.set_executables(ranlib=os.environ['RANLIB']) | |
350 | ||
351 | compiler.shared_lib_extension = shlib_suffix | |
352 | ||
353 | ||
354 | def get_config_h_filename(): | |
355 | """Return full pathname of installed pyconfig.h file.""" | |
356 | if python_build: | |
357 | if os.name == "nt": | |
358 | inc_dir = os.path.join(_sys_home or project_base, "PC") | |
359 | else: | |
360 | inc_dir = _sys_home or project_base | |
361 | return os.path.join(inc_dir, 'pyconfig.h') | |
362 | else: | |
363 | return sysconfig.get_config_h_filename() | |
364 | ||
365 | ||
366 | def get_makefile_filename(): | |
367 | """Return full pathname of installed Makefile from the Python build.""" | |
368 | return sysconfig.get_makefile_filename() | |
369 | ||
370 | ||
371 | def parse_config_h(fp, g=None): | |
372 | """Parse a config.h-style file. | |
373 | ||
374 | A dictionary containing name/value pairs is returned. If an | |
375 | optional dictionary is passed in as the second argument, it is | |
376 | used instead of a new dictionary. | |
377 | """ | |
378 | return sysconfig.parse_config_h(fp, vars=g) | |
379 | ||
380 | ||
381 | # Regexes needed for parsing Makefile (and similar syntaxes, | |
382 | # like old-style Setup files). | |
383 | _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") | |
384 | _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)") | |
385 | _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}") | |
386 | ||
387 | ||
388 | def parse_makefile(fn, g=None): # noqa: C901 | |
389 | """Parse a Makefile-style file. | |
390 | ||
391 | A dictionary containing name/value pairs is returned. If an | |
392 | optional dictionary is passed in as the second argument, it is | |
393 | used instead of a new dictionary. | |
394 | """ | |
395 | from distutils.text_file import TextFile | |
396 | ||
397 | fp = TextFile( | |
398 | fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape" | |
399 | ) | |
400 | ||
401 | if g is None: | |
402 | g = {} | |
403 | done = {} | |
404 | notdone = {} | |
405 | ||
406 | while True: | |
407 | line = fp.readline() | |
408 | if line is None: # eof | |
409 | break | |
410 | m = _variable_rx.match(line) | |
411 | if m: | |
412 | n, v = m.group(1, 2) | |
413 | v = v.strip() | |
414 | # `$$' is a literal `$' in make | |
415 | tmpv = v.replace('$$', '') | |
416 | ||
417 | if "$" in tmpv: | |
418 | notdone[n] = v | |
419 | else: | |
420 | try: | |
421 | v = int(v) | |
422 | except ValueError: | |
423 | # insert literal `$' | |
424 | done[n] = v.replace('$$', '$') | |
425 | else: | |
426 | done[n] = v | |
427 | ||
428 | # Variables with a 'PY_' prefix in the makefile. These need to | |
429 | # be made available without that prefix through sysconfig. | |
430 | # Special care is needed to ensure that variable expansion works, even | |
431 | # if the expansion uses the name without a prefix. | |
432 | renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS') | |
433 | ||
434 | # do variable interpolation here | |
435 | while notdone: | |
436 | for name in list(notdone): | |
437 | value = notdone[name] | |
438 | m = _findvar1_rx.search(value) or _findvar2_rx.search(value) | |
439 | if m: | |
440 | n = m.group(1) | |
441 | found = True | |
442 | if n in done: | |
443 | item = str(done[n]) | |
444 | elif n in notdone: | |
445 | # get it on a subsequent round | |
446 | found = False | |
447 | elif n in os.environ: | |
448 | # do it like make: fall back to environment | |
449 | item = os.environ[n] | |
450 | ||
451 | elif n in renamed_variables: | |
452 | if name.startswith('PY_') and name[3:] in renamed_variables: | |
453 | item = "" | |
454 | ||
455 | elif 'PY_' + n in notdone: | |
456 | found = False | |
457 | ||
458 | else: | |
459 | item = str(done['PY_' + n]) | |
460 | else: | |
461 | done[n] = item = "" | |
462 | if found: | |
463 | after = value[m.end() :] | |
464 | value = value[: m.start()] + item + after | |
465 | if "$" in after: | |
466 | notdone[name] = value | |
467 | else: | |
468 | try: | |
469 | value = int(value) | |
470 | except ValueError: | |
471 | done[name] = value.strip() | |
472 | else: | |
473 | done[name] = value | |
474 | del notdone[name] | |
475 | ||
476 | if name.startswith('PY_') and name[3:] in renamed_variables: | |
477 | ||
478 | name = name[3:] | |
479 | if name not in done: | |
480 | done[name] = value | |
481 | else: | |
482 | # bogus variable reference; just drop it since we can't deal | |
483 | del notdone[name] | |
484 | ||
485 | fp.close() | |
486 | ||
487 | # strip spurious spaces | |
488 | for k, v in done.items(): | |
489 | if isinstance(v, str): | |
490 | done[k] = v.strip() | |
491 | ||
492 | # save the results in the global dictionary | |
493 | g.update(done) | |
494 | return g | |
495 | ||
496 | ||
497 | def expand_makefile_vars(s, vars): | |
498 | """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in | |
499 | 'string' according to 'vars' (a dictionary mapping variable names to | |
500 | values). Variables not present in 'vars' are silently expanded to the | |
501 | empty string. The variable values in 'vars' should not contain further | |
502 | variable expansions; if 'vars' is the output of 'parse_makefile()', | |
503 | you're fine. Returns a variable-expanded version of 's'. | |
504 | """ | |
505 | ||
506 | # This algorithm does multiple expansion, so if vars['foo'] contains | |
507 | # "${bar}", it will expand ${foo} to ${bar}, and then expand | |
508 | # ${bar}... and so forth. This is fine as long as 'vars' comes from | |
509 | # 'parse_makefile()', which takes care of such expansions eagerly, | |
510 | # according to make's variable expansion semantics. | |
511 | ||
512 | while True: | |
513 | m = _findvar1_rx.search(s) or _findvar2_rx.search(s) | |
514 | if m: | |
515 | (beg, end) = m.span() | |
516 | s = s[0:beg] + vars.get(m.group(1)) + s[end:] | |
517 | else: | |
518 | break | |
519 | return s | |
520 | ||
521 | ||
522 | _config_vars = None | |
523 | ||
524 | ||
525 | def get_config_vars(*args): | |
526 | """With no arguments, return a dictionary of all configuration | |
527 | variables relevant for the current platform. Generally this includes | |
528 | everything needed to build extensions and install both pure modules and | |
529 | extensions. On Unix, this means every variable defined in Python's | |
530 | installed Makefile; on Windows it's a much smaller set. | |
531 | ||
532 | With arguments, return a list of values that result from looking up | |
533 | each argument in the configuration variable dictionary. | |
534 | """ | |
535 | global _config_vars | |
536 | if _config_vars is None: | |
537 | _config_vars = sysconfig.get_config_vars().copy() | |
538 | py39compat.add_ext_suffix(_config_vars) | |
539 | ||
540 | return [_config_vars.get(name) for name in args] if args else _config_vars | |
541 | ||
542 | ||
543 | def get_config_var(name): | |
544 | """Return the value of a single variable using the dictionary | |
545 | returned by 'get_config_vars()'. Equivalent to | |
546 | get_config_vars().get(name) | |
547 | """ | |
548 | if name == 'SO': | |
549 | import warnings | |
550 | ||
551 | warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2) | |
552 | return get_config_vars().get(name) |