Skip to content

Commit 9d269a1

Browse files
committed
pythonGH-127178: improve compatibility in _sysconfig_vars_(...).json
This patch improves environment and platform compatibility - Data now matches `sysconfig.get_config_vars` after install - `userbase` now correctly reflects the target platform when cross-compiling - `test_sysconfigdata_json` now takes into account the following situations: - Running with a non-default sys.executable path - Running under virtual environments - Running under relocatable installs To simplify the detection of relocatable installs, which needs to look at `_sysconfigdata_(...)`, this module is now saved in `sys.modules`. As part of this change, the code to import the module from a specific directory was refactored to use `PathFinder`, simplifying the implementation. Signed-off-by: Filipe Laíns <[email protected]
1 parent f826bec commit 9d269a1

File tree

3 files changed

+58
-23
lines changed

3 files changed

+58
-23
lines changed

Lib/sysconfig/__init__.py

+41-20
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 sysconfig.get_platform() to get the correct platform when cross-compiling.
121+
system_name = get_platform().split('-')[0]
122+
if system_name in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
121123
return None
122124

123125
def joinuser(*args):
@@ -335,34 +337,53 @@ def get_makefile_filename():
335337
return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
336338

337339

340+
def _import_from_directory(path, name):
341+
if name not in sys.modules:
342+
import importlib.machinery
343+
import importlib.util
344+
345+
spec = importlib.machinery.PathFinder.find_spec(name, [path])
346+
module = importlib.util.module_from_spec(spec)
347+
spec.loader.exec_module(module)
348+
sys.modules[name] = module
349+
return sys.modules[name]
350+
351+
338352
def _get_sysconfigdata_name():
339353
multiarch = getattr(sys.implementation, '_multiarch', '')
340354
return os.environ.get(
341355
'_PYTHON_SYSCONFIGDATA_NAME',
342356
f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}',
343357
)
344358

345-
def _init_posix(vars):
346-
"""Initialize the module as appropriate for POSIX systems."""
347-
# _sysconfigdata is generated at build time, see _generate_posix_vars()
359+
360+
def _get_sysconfigdata():
361+
import importlib
362+
348363
name = _get_sysconfigdata_name()
364+
path = os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')
365+
module = _import_from_directory(path, name) if path else importlib.import_module(name)
349366

350-
# For cross builds, the path to the target's sysconfigdata must be specified
351-
# so it can be imported. It cannot be in PYTHONPATH, as foreign modules in
352-
# sys.path can cause crashes when loaded by the host interpreter.
353-
# Rely on truthiness as a valueless env variable is still an empty string.
354-
# See OS X note in _generate_posix_vars re _sysconfigdata.
355-
if (path := os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')):
356-
from importlib.machinery import FileFinder, SourceFileLoader, SOURCE_SUFFIXES
357-
from importlib.util import module_from_spec
358-
spec = FileFinder(path, (SourceFileLoader, SOURCE_SUFFIXES)).find_spec(name)
359-
_temp = module_from_spec(spec)
360-
spec.loader.exec_module(_temp)
361-
else:
362-
_temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
363-
build_time_vars = _temp.build_time_vars
367+
return module.build_time_vars
368+
369+
370+
def _installation_is_relocated():
371+
"""Is the Python installation running from a different prefix than what was targetted when building?"""
372+
if os.name != 'posix':
373+
raise NotImplementedError('sysconfig._installation_is_relocated() is currently only supported on POSIX')
374+
375+
data = _get_sysconfigdata()
376+
return (
377+
data['prefix'] != getattr(sys, 'base_prefix', '')
378+
or data['exec_prefix'] != getattr(sys, 'base_exec_prefix', '')
379+
)
380+
381+
382+
def _init_posix(vars):
383+
"""Initialize the module as appropriate for POSIX systems."""
364384
# GH-126920: Make sure we don't overwrite any of the keys already set
365-
vars.update(build_time_vars | vars)
385+
vars.update(_get_sysconfigdata() | vars)
386+
366387

367388
def _init_non_posix(vars):
368389
"""Initialize the module as appropriate for NT"""

Lib/sysconfig/__main__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,14 @@ def _generate_posix_vars():
232232

233233
print(f'Written {destfile}')
234234

235+
install_vars = get_config_vars()
236+
# Fix config vars to match the values after install (of the default environment)
237+
install_vars['projectbase'] = install_vars['BINDIR']
238+
install_vars['srcdir'] = install_vars['LIBPL']
235239
# Write a JSON file with the output of sysconfig.get_config_vars
236240
jsonfile = os.path.join(pybuilddir, _get_json_data_name())
237241
with open(jsonfile, 'w') as f:
238-
json.dump(get_config_vars(), f, indent=2)
242+
json.dump(install_vars, f, indent=2)
239243

240244
print(f'Written {jsonfile}')
241245

Lib/test/test_sysconfig.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -650,8 +650,18 @@ def test_sysconfigdata_json(self):
650650

651651
system_config_vars = get_config_vars()
652652

653-
# Ignore keys in the check
654-
for key in ('projectbase', 'srcdir'):
653+
ignore_keys = {}
654+
# Keys dependent on the executable location
655+
if os.path.dirname(sys.executable) != system_config_vars['BINDIR']:
656+
ignore_keys += {'projectbase'}
657+
# Keys dependent on the environment (different inside virtual environments)
658+
if sys.prefix != sys.base_prefix:
659+
ignore_keys += {'prefix', 'exec_prefix', 'base', 'platbase'}
660+
# Keys dependent on Python being run from the prefix targetted when building (different on relocatable installs)
661+
if sysconfig._installation_is_relocated():
662+
ignore_keys += {'prefix', 'exec_prefix', 'base', 'platbase', 'installed_base', 'installed_platbase'}
663+
664+
for key in ignore_keys:
655665
json_config_vars.pop(key)
656666
system_config_vars.pop(key)
657667

0 commit comments

Comments
 (0)