Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-127178: improve compatibility in _sysconfig_vars_(...).json #128558

Merged
merged 4 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 41 additions & 20 deletions Lib/sysconfig/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ def _getuserbase():
if env_base:
return env_base

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

def joinuser(*args):
Expand Down Expand Up @@ -342,34 +344,53 @@ def get_makefile_filename():
return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')


def _import_from_directory(path, name):
if name not in sys.modules:
import importlib.machinery
import importlib.util

spec = importlib.machinery.PathFinder.find_spec(name, [path])
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules[name] = module
return sys.modules[name]


def _get_sysconfigdata_name():
multiarch = getattr(sys.implementation, '_multiarch', '')
return os.environ.get(
'_PYTHON_SYSCONFIGDATA_NAME',
f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}',
)

def _init_posix(vars):
"""Initialize the module as appropriate for POSIX systems."""
# _sysconfigdata is generated at build time, see _generate_posix_vars()

def _get_sysconfigdata():
import importlib

name = _get_sysconfigdata_name()
path = os.environ.get('_PYTHON_SYSCONFIGDATA_PATH')
module = _import_from_directory(path, name) if path else importlib.import_module(name)

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


def _installation_is_relocated():
"""Is the Python installation running from a different prefix than what was targetted when building?"""
if os.name != 'posix':
raise NotImplementedError('sysconfig._installation_is_relocated() is currently only supported on POSIX')

data = _get_sysconfigdata()
return (
data['prefix'] != getattr(sys, 'base_prefix', '')
or data['exec_prefix'] != getattr(sys, 'base_exec_prefix', '')
)


def _init_posix(vars):
"""Initialize the module as appropriate for POSIX systems."""
# GH-126920: Make sure we don't overwrite any of the keys already set
vars.update(build_time_vars | vars)
vars.update(_get_sysconfigdata() | vars)


def _init_non_posix(vars):
"""Initialize the module as appropriate for NT"""
Expand Down
6 changes: 5 additions & 1 deletion Lib/sysconfig/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,14 @@ def _generate_posix_vars():

print(f'Written {destfile}')

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

print(f'Written {jsonfile}')

Expand Down
17 changes: 15 additions & 2 deletions Lib/test/test_sysconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,8 +650,21 @@ def test_sysconfigdata_json(self):

system_config_vars = get_config_vars()

# Ignore keys in the check
for key in ('projectbase', 'srcdir'):
ignore_keys = set()
# Keys dependent on Python being run outside the build directrory
if sysconfig.is_python_build():
ignore_keys |= {'srcdir'}
# Keys dependent on the executable location
if os.path.dirname(sys.executable) != system_config_vars['BINDIR']:
ignore_keys |= {'projectbase'}
# Keys dependent on the environment (different inside virtual environments)
if sys.prefix != sys.base_prefix:
ignore_keys |= {'prefix', 'exec_prefix', 'base', 'platbase'}
# Keys dependent on Python being run from the prefix targetted when building (different on relocatable installs)
if sysconfig._installation_is_relocated():
ignore_keys |= {'prefix', 'exec_prefix', 'base', 'platbase', 'installed_base', 'installed_platbase'}

for key in ignore_keys:
json_config_vars.pop(key)
system_config_vars.pop(key)

Expand Down
Loading