From 9fff9083da29661269f15c4451c425c561fd4567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Fri, 1 Oct 2021 12:43:58 +0100 Subject: [PATCH] modules/python: detect debian distutils missing and show an error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe LaĆ­ns --- mesonbuild/modules/python.py | 93 +++++++++++++++++++++++------------- unittests/pythontests.py | 10 ++++ 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 2d5eaead20d7..f2ccde089a85 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -280,7 +280,21 @@ def set_env(name, value): import sysconfig import json import sys -import distutils.command.install + +def debian_distutils_missing(): + # Debian partially splits the distutils module from the python3, they + # keep distutils and distutils.version in python3 and put the rest of + # the submodules in python3-distutils + try: + import distutils + import distutils.version + try: + import distutils.core + except ImportError: + return True + except ImportError: + return False + return False def get_distutils_paths(scheme=None, prefix=None): import distutils.dist @@ -299,20 +313,22 @@ def get_distutils_paths(scheme=None, prefix=None): 'scripts': install_cmd.install_scripts, } -# On Debian derivatives, the Python interpreter shipped by the distribution uses -# a custom install scheme, deb_system, for the system install, and changes the -# default scheme to a custom one pointing to /usr/local and replacing -# site-packages with dist-packages. -# See https://github.com/mesonbuild/meson/issues/8739. -# XXX: We should be using sysconfig, but Debian only patches distutils. - -if 'deb_system' in distutils.command.install.INSTALL_SCHEMES: - paths = get_distutils_paths(scheme='deb_system') - install_paths = get_distutils_paths(scheme='deb_system', prefix='') -else: - paths = sysconfig.get_paths() - empty_vars = {'base': '', 'platbase': '', 'installed_base': ''} - install_paths = sysconfig.get_paths(vars=empty_vars) +def get_paths(): + # On Debian derivatives, the Python interpreter shipped by the distribution uses + # a custom install scheme, deb_system, for the system install, and changes the + # default scheme to a custom one pointing to /usr/local and replacing + # site-packages with dist-packages. + # See https://github.com/mesonbuild/meson/issues/8739. + # XXX: We should be using sysconfig, but Debian only patches distutils. + import distutils.command.install + if 'deb_system' in distutils.command.install.INSTALL_SCHEMES: + paths = get_distutils_paths(scheme='deb_system') + install_paths = get_distutils_paths(scheme='deb_system', prefix='') + else: + paths = sysconfig.get_paths() + empty_vars = {'base': '', 'platbase': '', 'installed_base': ''} + install_paths = sysconfig.get_paths(vars=empty_vars) + return paths, install_paths def links_against_libpython(): from distutils.core import Distribution, Extension @@ -320,16 +336,24 @@ def links_against_libpython(): cmd.ensure_finalized() return bool(cmd.get_libraries(Extension('dummy', []))) -print(json.dumps({ - 'variables': sysconfig.get_config_vars(), - 'paths': paths, - 'install_paths': install_paths, - 'sys_paths': sys.path, - 'version': sysconfig.get_python_version(), - 'platform': sysconfig.get_platform(), - 'is_pypy': '__pypy__' in sys.builtin_module_names, - 'link_libpython': links_against_libpython(), -})) +if debian_distutils_missing(): + data = { + 'error': 'Debian distutils is missing, please install python3-distutils', + } +else: + paths, install_paths = get_paths() + data = { + 'variables': sysconfig.get_config_vars(), + 'paths': paths, + 'install_paths': install_paths, + 'sys_paths': sys.path, + 'version': sysconfig.get_python_version(), + 'platform': sysconfig.get_platform(), + 'is_pypy': '__pypy__' in sys.builtin_module_names, + 'link_libpython': links_against_libpython(), + } + +print(json.dumps(data)) ''' if T.TYPE_CHECKING: @@ -390,13 +414,18 @@ def sanity(self) -> bool: mlog.debug('Program stderr:\n') mlog.debug(stderr) - if info is not None and self._check_version(info['version']): - variables = info['variables'] - info['suffix'] = variables.get('EXT_SUFFIX') or variables.get('SO') or variables.get('.so') - self.info = T.cast('PythonIntrospectionDict', info) - self.platlib = self._get_path('platlib') - self.purelib = self._get_path('purelib') - return True + if info is not None: + if 'error' in info: + assert isinstance(info['error'], str) + mlog.error('Python interpreter introspection failed: %s' % info['error']) + return False + elif self._check_version(info['version']): + variables = info['variables'] + info['suffix'] = variables.get('EXT_SUFFIX') or variables.get('SO') or variables.get('.so') + self.info = T.cast('PythonIntrospectionDict', info) + self.platlib = self._get_path('platlib') + self.purelib = self._get_path('purelib') + return True else: return False diff --git a/unittests/pythontests.py b/unittests/pythontests.py index c8efbdd3b148..4eb40ee36aa2 100644 --- a/unittests/pythontests.py +++ b/unittests/pythontests.py @@ -14,11 +14,13 @@ import os import unittest +import unittest.mock from run_tests import ( Backend ) +from mesonbuild.modules.python import PythonExternalProgram from .baseplatformtests import BasePlatformTests class PythonTests(BasePlatformTests): @@ -80,3 +82,11 @@ def test_versions(self): with self.assertRaises(unittest.SkipTest): self.init(testdir, extra_args=['-Dpython=dir']) self.wipe() + + @unittest.mock.patch('mesonbuild.mesonlib.Popen_safe', return_value=(None, '{"error": "hello!"}', None)) + @unittest.mock.patch('mesonbuild.mlog.error') + def test_introspection_error(self, mlog_error, Popen_safe): + python = PythonExternalProgram('some-python', ['some-python']) + + assert not python.sanity() + mlog_error.assert_called_with('Python interpreter introspection failed: hello!')