From bbc728915c33611609e6e7c649750a45dc3c9d11 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Sun, 27 Aug 2023 20:53:26 +0100 Subject: [PATCH] python module: Use sys_root's Python sysconfigdata to get EXT_SUFFIX Until now, the build host's EXT_SUFFIX was used, resulting in native extensions being build and installed with the wrong filename. To do this, we load the sys_root sysconfigdata from its stdlib directory. We assume that the same Python version as the build host exists within the sys_root with the same stdlib directory, save for the prefix. If not, we fall back to the build host, as we did before. If the machine file points python at a Python within sys_root then this should still work. You should probably only run a sys_root's binary using some kind of wrapper, but either way, sysconfig will still return the correct values. We only read EXT_SUFFIX from the sys_root because paths are expected to be the same, save for the prefix, and other info such as the platform and limited API suffix cannot be determined this way. We could potentially guess the limited API suffix from the platform, as there are only a small number of possible values, but this can be done separately. Closes: https://github.com/mesonbuild/meson/issues/7049 --- mesonbuild/dependencies/python.py | 13 +++++++++++-- mesonbuild/modules/python.py | 2 +- mesonbuild/scripts/python_info.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/mesonbuild/dependencies/python.py b/mesonbuild/dependencies/python.py index efb904eca471..6e754cdb8111 100644 --- a/mesonbuild/dependencies/python.py +++ b/mesonbuild/dependencies/python.py @@ -33,6 +33,7 @@ from .factory import DependencyGenerator from ..environment import Environment from ..mesonlib import MachineChoice + from ..modules import ModuleState class PythonIntrospectionDict(TypedDict): @@ -108,13 +109,21 @@ def _check_version(self, version: str) -> bool: return mesonlib.version_compare(version, '>= 3.0') return True - def sanity(self) -> bool: + def sanity(self, state: T.Optional['ModuleState'] = None) -> bool: # Sanity check, we expect to have something that at least quacks in tune import importlib.resources + # Pass the sys_root with its prefix as the first arg to python_info.py + # if a sys_root is defined. Otherwise pass an empty string. + sys_root_prefix = '' + if state: + sys_root = state.environment.properties[mesonlib.MachineChoice.HOST].get_sys_root() + if sys_root: + sys_root_prefix = sys_root + state.environment.coredata.get_option(mesonlib.OptionKey('prefix')) + with importlib.resources.path('mesonbuild.scripts', 'python_info.py') as f: - cmd = self.get_command() + [str(f)] + cmd = self.get_command() + [str(f), sys_root_prefix] p, stdout, stderr = mesonlib.Popen_safe(cmd) try: diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index c8af224f8976..870f7176c5b8 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -80,7 +80,7 @@ class PythonExternalProgram(BasicPythonExternalProgram): run_bytecompile: T.ClassVar[T.Dict[str, bool]] = {} def sanity(self, state: T.Optional['ModuleState'] = None) -> bool: - ret = super().sanity() + ret = super().sanity(state) if ret: self.platlib = self._get_path(state, 'platlib') self.purelib = self._get_path(state, 'purelib') diff --git a/mesonbuild/scripts/python_info.py b/mesonbuild/scripts/python_info.py index 0f7787c3ff21..0c5965733edf 100755 --- a/mesonbuild/scripts/python_info.py +++ b/mesonbuild/scripts/python_info.py @@ -63,6 +63,34 @@ def links_against_libpython(): from distutils.sysconfig import get_config_var suffix = get_config_var('EXT_SUFFIX') else: + # Determine some information from the sys_root if it is given as the first + # argument. When not given, it is expected to be an empty string. + if len(sys.argv) >= 1 and sys.argv[1]: + import importlib.util, pathlib + try: + # We need to load the sys_root sysconfigdata from its stdlib + # directory. We assume that the same Python version as the build + # host exists within the sys_root with the same stdlib directory, + # save for the prefix. If not, we fall back to the build host. + stdlib = sysconfig.get_path('stdlib', vars={'installed_base': sys.argv[1]}) + # We cannot predict exactly what the sys_root sysconfigdata filename + # will be, but there should only be one, so glob to find it. + sysconfigdatas = list(pathlib.Path(stdlib).glob('_sysconfigdata_*.py')) + + # Only use the sys_root sysconfigdata if we find exactly one. More + # than one is odd, so it is safer to fall back to the build host. + if len(sysconfigdatas) == 1: + data_spec = importlib.util.spec_from_file_location('_sysconfig_data', sysconfigdatas[0]) + data_mod = importlib.util.module_from_spec(data_spec) + data_spec.loader.exec_module(data_mod) + sys_root_variables = data_mod.build_time_vars + # We only read EXT_SUFFIX from the sys_root. Paths are expected + # to be the same, save for the prefix, and other info such as + # the platform cannot be determined this way. + variables['EXT_SUFFIX'] = sys_root_variables.get('EXT_SUFFIX') + except OSError: + pass + suffix = variables.get('EXT_SUFFIX') limited_api_suffix = None