diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index 67a071963d8c7d..0ccfa3da38e6b4 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -181,6 +181,7 @@ def joinuser(*args): # True iff _CONFIG_VARS has been fully initialized. _CONFIG_VARS_INITIALIZED = False _USER_BASE = None +_SYSCONFIGDATA = None def _safe_realpath(path): @@ -334,27 +335,39 @@ def _get_sysconfigdata_name(): f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}', ) + +def _get_sysconfigdata(): + # _sysconfigdata is generated at build time, see _generate_posix_vars() + global _SYSCONFIGDATA + + if _SYSCONFIGDATA is None: + name = _get_sysconfigdata_name() + '.json' + if '_PYTHON_SYSCONFIGDATA_PATH' in os.environ: + data_path = os.path.join(os.environ['_PYTHON_SYSCONFIGDATA_PATH'], name) + else: + # Search sys.path + # FIXME: We should not need this if we could reliably identify the project directory on source builds + for path in sys.path: + data_path = os.path.join(path, name) + if os.path.isfile(data_path): + break + else: + import warnings + warnings.warn(f'Could not find {name}', RuntimeWarning) + return {} + + import json + + with open(data_path) as f: + _SYSCONFIGDATA = json.load(f) + + return _SYSCONFIGDATA + + def _init_posix(vars): """Initialize the module as appropriate for POSIX systems.""" - # _sysconfigdata is generated at build time, see _generate_posix_vars() - name = _get_sysconfigdata_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 # 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""" diff --git a/Lib/sysconfig/__main__.py b/Lib/sysconfig/__main__.py index d7257b9d2d00db..2d5c71c2f9f26f 100644 --- a/Lib/sysconfig/__main__.py +++ b/Lib/sysconfig/__main__.py @@ -1,5 +1,7 @@ +import json import os import sys +import sysconfig from sysconfig import ( _ALWAYS_STR, _PYTHON_BUILD, @@ -192,28 +194,19 @@ def _generate_posix_vars(): # _sysconfigdata.py module being constructed. Unfortunately, # get_config_vars() eventually calls _init_posix(), which attempts # to import _sysconfigdata, which we won't have built yet. In order - # for _init_posix() to work, if we're on Darwin, just mock up the - # _sysconfigdata module manually and populate it with the build vars. - # This is more than sufficient for ensuring the subsequent call to - # get_platform() succeeds. + # for _init_posix() to work, we set the _SYSCONFIGDATA cache directly name = _get_sysconfigdata_name() if 'darwin' in sys.platform: - import types - module = types.ModuleType(name) - module.build_time_vars = vars - sys.modules[name] = module + sysconfig._SYSCONFIGDATA = vars pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}' if hasattr(sys, "gettotalrefcount"): pybuilddir += '-pydebug' os.makedirs(pybuilddir, exist_ok=True) - destfile = os.path.join(pybuilddir, name + '.py') + destfile = os.path.join(pybuilddir, name + '.json') with open(destfile, 'w', encoding='utf8') as f: - f.write('# system configuration generated and used by' - ' the sysconfig module\n') - f.write('build_time_vars = ') - _print_config_dict(vars, stream=f) + json.dump(vars, f, indent=2) # Create file used for sys.path fixup -- see Modules/getpath.c with open('pybuilddir.txt', 'w', encoding='utf8') as f: diff --git a/Makefile.pre.in b/Makefile.pre.in index 8d94ba361fd934..4736bcd1bb93ab 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2645,7 +2645,7 @@ libinstall: all $(srcdir)/Modules/xxmodule.c esac; \ done; \ done - $(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py \ + $(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json \ $(DESTDIR)$(LIBDEST); \ $(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt @ # If app store compliance has been configured, apply the patch to the diff --git a/Misc/NEWS.d/next/Library/2024-11-22-23-57-28.gh-issue-127178.2J-NbL.rst b/Misc/NEWS.d/next/Library/2024-11-22-23-57-28.gh-issue-127178.2J-NbL.rst new file mode 100644 index 00000000000000..4c122fefa7030c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-11-22-23-57-28.gh-issue-127178.2J-NbL.rst @@ -0,0 +1,2 @@ +The ``_sysconfigdata_*`` module that holds the :mod:`sysconfig` data on +POSIX has been converted to a JSON file.