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