Skip to content

Commit b1cddc4

Browse files
authored
Merge pull request #7296 from youknowone/sysconfig
Update sysconfig from v3.14.3 and also fix _sysconfig
2 parents 3a81f94 + 5a11f33 commit b1cddc4

File tree

7 files changed

+370
-176
lines changed

7 files changed

+370
-176
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ flamescope.json
2222
extra_tests/snippets/resources
2323
extra_tests/not_impl.py
2424

25+
Lib/_sysconfig_vars*.json
2526
Lib/site-packages/*
2627
!Lib/site-packages/README.txt
2728
Lib/test/data/*

Lib/sysconfig/__init__.py

Lines changed: 138 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@ def _getuserbase():
116116
if env_base:
117117
return env_base
118118

119-
# Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
120-
if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
119+
# Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories.
120+
# Use _PYTHON_HOST_PLATFORM to get the correct platform when cross-compiling.
121+
system_name = os.environ.get('_PYTHON_HOST_PLATFORM', sys.platform).split('-')[0]
122+
if system_name in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
121123
return None
122124

123125
def joinuser(*args):
@@ -323,44 +325,72 @@ def get_default_scheme():
323325

324326
def get_makefile_filename():
325327
"""Return the path of the Makefile."""
328+
329+
# GH-127429: When cross-compiling, use the Makefile from the target, instead of the host Python.
330+
if cross_base := os.environ.get('_PYTHON_PROJECT_BASE'):
331+
return os.path.join(cross_base, 'Makefile')
332+
326333
if _PYTHON_BUILD:
327334
return os.path.join(_PROJECT_BASE, "Makefile")
335+
328336
if hasattr(sys, 'abiflags'):
329337
config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}'
330338
else:
331339
config_dir_name = 'config'
340+
332341
if hasattr(sys.implementation, '_multiarch'):
333342
config_dir_name += f'-{sys.implementation._multiarch}'
343+
334344
return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
335345

336346

347+
def _import_from_directory(path, name):
348+
if name not in sys.modules:
349+
import importlib.machinery
350+
import importlib.util
351+
352+
spec = importlib.machinery.PathFinder.find_spec(name, [path])
353+
module = importlib.util.module_from_spec(spec)
354+
spec.loader.exec_module(module)
355+
sys.modules[name] = module
356+
return sys.modules[name]
357+
358+
337359
def _get_sysconfigdata_name():
338360
multiarch = getattr(sys.implementation, '_multiarch', '')
339361
return os.environ.get(
340362
'_PYTHON_SYSCONFIGDATA_NAME',
341363
f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}',
342364
)
343365

366+
367+
def _get_sysconfigdata():
368+
import importlib
369+
370+
name = _get_sysconfigdata_name()
371+
path = os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')
372+
module = _import_from_directory(path, name) if path else importlib.import_module(name)
373+
374+
return module.build_time_vars
375+
376+
377+
def _installation_is_relocated():
378+
"""Is the Python installation running from a different prefix than what was targetted when building?"""
379+
if os.name != 'posix':
380+
raise NotImplementedError('sysconfig._installation_is_relocated() is currently only supported on POSIX')
381+
382+
data = _get_sysconfigdata()
383+
return (
384+
data['prefix'] != getattr(sys, 'base_prefix', '')
385+
or data['exec_prefix'] != getattr(sys, 'base_exec_prefix', '')
386+
)
387+
388+
344389
def _init_posix(vars):
345390
"""Initialize the module as appropriate for POSIX systems."""
346-
# _sysconfigdata is generated at build time, see _generate_posix_vars()
347-
name = _get_sysconfigdata_name()
391+
# GH-126920: Make sure we don't overwrite any of the keys already set
392+
vars.update(_get_sysconfigdata() | vars)
348393

349-
# For cross builds, the path to the target's sysconfigdata must be specified
350-
# so it can be imported. It cannot be in PYTHONPATH, as foreign modules in
351-
# sys.path can cause crashes when loaded by the host interpreter.
352-
# Rely on truthiness as a valueless env variable is still an empty string.
353-
# See OS X note in _generate_posix_vars re _sysconfigdata.
354-
if (path := os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')):
355-
from importlib.machinery import FileFinder, SourceFileLoader, SOURCE_SUFFIXES
356-
from importlib.util import module_from_spec
357-
spec = FileFinder(path, (SourceFileLoader, SOURCE_SUFFIXES)).find_spec(name)
358-
_temp = module_from_spec(spec)
359-
spec.loader.exec_module(_temp)
360-
else:
361-
_temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
362-
build_time_vars = _temp.build_time_vars
363-
vars.update(build_time_vars)
364394

365395
def _init_non_posix(vars):
366396
"""Initialize the module as appropriate for NT"""
@@ -371,9 +401,20 @@ def _init_non_posix(vars):
371401
vars['BINLIBDEST'] = get_path('platstdlib')
372402
vars['INCLUDEPY'] = get_path('include')
373403

374-
# Add EXT_SUFFIX, SOABI, and Py_GIL_DISABLED
404+
# Add EXT_SUFFIX, SOABI, Py_DEBUG, and Py_GIL_DISABLED
375405
vars.update(_sysconfig.config_vars())
376406

407+
# NOTE: ABIFLAGS is only an emulated value. It is not present during build
408+
# on Windows. sys.abiflags is absent on Windows and vars['abiflags']
409+
# is already widely used to calculate paths, so it should remain an
410+
# empty string.
411+
vars['ABIFLAGS'] = ''.join(
412+
(
413+
't' if vars['Py_GIL_DISABLED'] else '',
414+
'_d' if vars['Py_DEBUG'] else '',
415+
),
416+
)
417+
377418
vars['LIBDIR'] = _safe_realpath(os.path.join(get_config_var('installed_base'), 'libs'))
378419
if hasattr(sys, 'dllhandle'):
379420
dllhandle = _winapi.GetModuleFileName(sys.dllhandle)
@@ -427,7 +468,7 @@ def get_config_h_filename():
427468
"""Return the path of pyconfig.h."""
428469
if _PYTHON_BUILD:
429470
if os.name == "nt":
430-
inc_dir = os.path.dirname(sys._base_executable)
471+
inc_dir = os.path.join(_PROJECT_BASE, 'PC')
431472
else:
432473
inc_dir = _PROJECT_BASE
433474
else:
@@ -468,29 +509,44 @@ def get_path(name, scheme=get_default_scheme(), vars=None, expand=True):
468509
def _init_config_vars():
469510
global _CONFIG_VARS
470511
_CONFIG_VARS = {}
512+
513+
prefix = os.path.normpath(sys.prefix)
514+
exec_prefix = os.path.normpath(sys.exec_prefix)
515+
base_prefix = _BASE_PREFIX
516+
base_exec_prefix = _BASE_EXEC_PREFIX
517+
518+
try:
519+
abiflags = sys.abiflags
520+
except AttributeError:
521+
abiflags = ''
522+
523+
if os.name == 'posix':
524+
_init_posix(_CONFIG_VARS)
525+
# If we are cross-compiling, load the prefixes from the Makefile instead.
526+
if '_PYTHON_PROJECT_BASE' in os.environ:
527+
prefix = _CONFIG_VARS['host_prefix']
528+
exec_prefix = _CONFIG_VARS['host_exec_prefix']
529+
base_prefix = _CONFIG_VARS['host_prefix']
530+
base_exec_prefix = _CONFIG_VARS['host_exec_prefix']
531+
abiflags = _CONFIG_VARS['ABIFLAGS']
532+
471533
# Normalized versions of prefix and exec_prefix are handy to have;
472534
# in fact, these are the standard versions used most places in the
473535
# Distutils.
474-
_PREFIX = os.path.normpath(sys.prefix)
475-
_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
476-
_CONFIG_VARS['prefix'] = _PREFIX # FIXME: This gets overwriten by _init_posix.
477-
_CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX # FIXME: This gets overwriten by _init_posix.
536+
_CONFIG_VARS['prefix'] = prefix
537+
_CONFIG_VARS['exec_prefix'] = exec_prefix
478538
_CONFIG_VARS['py_version'] = _PY_VERSION
479539
_CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
480540
_CONFIG_VARS['py_version_nodot'] = _PY_VERSION_SHORT_NO_DOT
481-
_CONFIG_VARS['installed_base'] = _BASE_PREFIX
482-
_CONFIG_VARS['base'] = _PREFIX
483-
_CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX
484-
_CONFIG_VARS['platbase'] = _EXEC_PREFIX
541+
_CONFIG_VARS['installed_base'] = base_prefix
542+
_CONFIG_VARS['base'] = prefix
543+
_CONFIG_VARS['installed_platbase'] = base_exec_prefix
544+
_CONFIG_VARS['platbase'] = exec_prefix
485545
_CONFIG_VARS['projectbase'] = _PROJECT_BASE
486546
_CONFIG_VARS['platlibdir'] = sys.platlibdir
487547
_CONFIG_VARS['implementation'] = _get_implementation()
488548
_CONFIG_VARS['implementation_lower'] = _get_implementation().lower()
489-
try:
490-
_CONFIG_VARS['abiflags'] = sys.abiflags
491-
except AttributeError:
492-
# sys.abiflags may not be defined on all platforms.
493-
_CONFIG_VARS['abiflags'] = ''
549+
_CONFIG_VARS['abiflags'] = abiflags
494550
try:
495551
_CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '')
496552
except AttributeError:
@@ -499,8 +555,6 @@ def _init_config_vars():
499555
if os.name == 'nt':
500556
_init_non_posix(_CONFIG_VARS)
501557
_CONFIG_VARS['VPATH'] = sys._vpath
502-
if os.name == 'posix':
503-
_init_posix(_CONFIG_VARS)
504558
if _HAS_USER_BASE:
505559
# Setting 'userbase' is done below the call to the
506560
# init function to enable using 'get_config_var' in
@@ -550,23 +604,23 @@ def get_config_vars(*args):
550604
global _CONFIG_VARS_INITIALIZED
551605

552606
# Avoid claiming the lock once initialization is complete.
553-
if not _CONFIG_VARS_INITIALIZED:
607+
if _CONFIG_VARS_INITIALIZED:
608+
# GH-126789: If sys.prefix or sys.exec_prefix were updated, invalidate the cache.
609+
prefix = os.path.normpath(sys.prefix)
610+
exec_prefix = os.path.normpath(sys.exec_prefix)
611+
if _CONFIG_VARS['prefix'] != prefix or _CONFIG_VARS['exec_prefix'] != exec_prefix:
612+
with _CONFIG_VARS_LOCK:
613+
_CONFIG_VARS_INITIALIZED = False
614+
_init_config_vars()
615+
else:
616+
# Initialize the config_vars cache.
554617
with _CONFIG_VARS_LOCK:
555618
# Test again with the lock held to avoid races. Note that
556619
# we test _CONFIG_VARS here, not _CONFIG_VARS_INITIALIZED,
557620
# to ensure that recursive calls to get_config_vars()
558621
# don't re-enter init_config_vars().
559622
if _CONFIG_VARS is None:
560623
_init_config_vars()
561-
else:
562-
# If the site module initialization happened after _CONFIG_VARS was
563-
# initialized, a virtual environment might have been activated, resulting in
564-
# variables like sys.prefix changing their value, so we need to re-init the
565-
# config vars (see GH-126789).
566-
if _CONFIG_VARS['base'] != os.path.normpath(sys.prefix):
567-
with _CONFIG_VARS_LOCK:
568-
_CONFIG_VARS_INITIALIZED = False
569-
_init_config_vars()
570624

571625
if args:
572626
vals = []
@@ -627,34 +681,34 @@ def get_platform():
627681

628682
# Set for cross builds explicitly
629683
if "_PYTHON_HOST_PLATFORM" in os.environ:
630-
return os.environ["_PYTHON_HOST_PLATFORM"]
631-
632-
# Try to distinguish various flavours of Unix
633-
osname, host, release, version, machine = os.uname()
634-
635-
# Convert the OS name to lowercase, remove '/' characters, and translate
636-
# spaces (for "Power Macintosh")
637-
osname = osname.lower().replace('/', '')
638-
machine = machine.replace(' ', '_')
639-
machine = machine.replace('/', '-')
640-
641-
if osname[:5] == "linux":
642-
if sys.platform == "android":
643-
osname = "android"
644-
release = get_config_var("ANDROID_API_LEVEL")
645-
646-
# Wheel tags use the ABI names from Android's own tools.
647-
machine = {
648-
"x86_64": "x86_64",
649-
"i686": "x86",
650-
"aarch64": "arm64_v8a",
651-
"armv7l": "armeabi_v7a",
652-
}[machine]
653-
else:
654-
# At least on Linux/Intel, 'machine' is the processor --
655-
# i386, etc.
656-
# XXX what about Alpha, SPARC, etc?
657-
return f"{osname}-{machine}"
684+
osname, _, machine = os.environ["_PYTHON_HOST_PLATFORM"].partition('-')
685+
release = None
686+
else:
687+
# Try to distinguish various flavours of Unix
688+
osname, host, release, version, machine = os.uname()
689+
690+
# Convert the OS name to lowercase, remove '/' characters, and translate
691+
# spaces (for "Power Macintosh")
692+
osname = osname.lower().replace('/', '')
693+
machine = machine.replace(' ', '_')
694+
machine = machine.replace('/', '-')
695+
696+
if osname == "android" or sys.platform == "android":
697+
osname = "android"
698+
release = get_config_var("ANDROID_API_LEVEL")
699+
700+
# Wheel tags use the ABI names from Android's own tools.
701+
machine = {
702+
"x86_64": "x86_64",
703+
"i686": "x86",
704+
"aarch64": "arm64_v8a",
705+
"armv7l": "armeabi_v7a",
706+
}[machine]
707+
elif osname == "linux":
708+
# At least on Linux/Intel, 'machine' is the processor --
709+
# i386, etc.
710+
# XXX what about Alpha, SPARC, etc?
711+
return f"{osname}-{machine}"
658712
elif osname[:5] == "sunos":
659713
if release[0] >= "5": # SunOS 5 == Solaris 2
660714
osname = "solaris"
@@ -686,7 +740,7 @@ def get_platform():
686740
get_config_vars(),
687741
osname, release, machine)
688742

689-
return f"{osname}-{release}-{machine}"
743+
return '-'.join(map(str, filter(None, (osname, release, machine))))
690744

691745

692746
def get_python_version():
@@ -705,6 +759,15 @@ def expand_makefile_vars(s, vars):
705759
variable expansions; if 'vars' is the output of 'parse_makefile()',
706760
you're fine. Returns a variable-expanded version of 's'.
707761
"""
762+
763+
import warnings
764+
warnings.warn(
765+
'sysconfig.expand_makefile_vars is deprecated and will be removed in '
766+
'Python 3.16. Use sysconfig.get_paths(vars=...) instead.',
767+
DeprecationWarning,
768+
stacklevel=2,
769+
)
770+
708771
import re
709772

710773
_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)"

0 commit comments

Comments
 (0)