fix: noconfirm auto-selects first AUR match
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
# Copyright 2022 The Meson development team
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
import functools, json, os, textwrap
|
||||
from pathlib import Path
|
||||
import typing as T
|
||||
|
||||
from .. import mesonlib, mlog
|
||||
from .base import process_method_kw, DependencyMethods, DependencyTypeName, ExternalDependency, SystemDependency
|
||||
from .configtool import ConfigToolDependency
|
||||
from .detect import packages
|
||||
from .factory import DependencyFactory
|
||||
from .framework import ExtraFrameworkDependency
|
||||
from .pkgconfig import PkgConfigDependency
|
||||
from ..environment import detect_cpu_family
|
||||
from ..programs import ExternalProgram
|
||||
|
||||
if T.TYPE_CHECKING:
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from .factory import DependencyGenerator
|
||||
from ..environment import Environment
|
||||
from ..mesonlib import MachineChoice
|
||||
|
||||
class PythonIntrospectionDict(TypedDict):
|
||||
|
||||
install_paths: T.Dict[str, str]
|
||||
is_pypy: bool
|
||||
is_venv: bool
|
||||
link_libpython: bool
|
||||
sysconfig_paths: T.Dict[str, str]
|
||||
paths: T.Dict[str, str]
|
||||
platform: str
|
||||
suffix: str
|
||||
limited_api_suffix: str
|
||||
variables: T.Dict[str, str]
|
||||
version: str
|
||||
|
||||
_Base = ExternalDependency
|
||||
else:
|
||||
_Base = object
|
||||
|
||||
|
||||
class Pybind11ConfigToolDependency(ConfigToolDependency):
|
||||
|
||||
tools = ['pybind11-config']
|
||||
|
||||
# any version of the tool is valid, since this is header-only
|
||||
allow_default_for_cross = True
|
||||
|
||||
# pybind11 in 2.10.4 added --version, sanity-check another flag unique to it
|
||||
# in the meantime
|
||||
skip_version = '--pkgconfigdir'
|
||||
|
||||
def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any]):
|
||||
super().__init__(name, environment, kwargs)
|
||||
if not self.is_found:
|
||||
return
|
||||
self.compile_args = self.get_config_value(['--includes'], 'compile_args')
|
||||
|
||||
|
||||
class BasicPythonExternalProgram(ExternalProgram):
|
||||
def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
|
||||
ext_prog: T.Optional[ExternalProgram] = None):
|
||||
if ext_prog is None:
|
||||
super().__init__(name, command=command, silent=True)
|
||||
else:
|
||||
self.name = name
|
||||
self.command = ext_prog.command
|
||||
self.path = ext_prog.path
|
||||
self.cached_version = None
|
||||
|
||||
# We want strong key values, so we always populate this with bogus data.
|
||||
# Otherwise to make the type checkers happy we'd have to do .get() for
|
||||
# everycall, even though we know that the introspection data will be
|
||||
# complete
|
||||
self.info: 'PythonIntrospectionDict' = {
|
||||
'install_paths': {},
|
||||
'is_pypy': False,
|
||||
'is_venv': False,
|
||||
'link_libpython': False,
|
||||
'sysconfig_paths': {},
|
||||
'paths': {},
|
||||
'platform': 'sentinel',
|
||||
'suffix': 'sentinel',
|
||||
'limited_api_suffix': 'sentinel',
|
||||
'variables': {},
|
||||
'version': '0.0',
|
||||
}
|
||||
self.pure: bool = True
|
||||
|
||||
def _check_version(self, version: str) -> bool:
|
||||
if self.name == 'python2':
|
||||
return mesonlib.version_compare(version, '< 3.0')
|
||||
elif self.name == 'python3':
|
||||
return mesonlib.version_compare(version, '>= 3.0')
|
||||
return True
|
||||
|
||||
def sanity(self) -> bool:
|
||||
# Sanity check, we expect to have something that at least quacks in tune
|
||||
|
||||
import importlib.resources
|
||||
|
||||
with importlib.resources.path('mesonbuild.scripts', 'python_info.py') as f:
|
||||
cmd = self.get_command() + [str(f)]
|
||||
env = os.environ.copy()
|
||||
env['SETUPTOOLS_USE_DISTUTILS'] = 'stdlib'
|
||||
p, stdout, stderr = mesonlib.Popen_safe(cmd, env=env)
|
||||
|
||||
try:
|
||||
info = json.loads(stdout)
|
||||
except json.JSONDecodeError:
|
||||
info = None
|
||||
mlog.debug('Could not introspect Python (%s): exit code %d' % (str(p.args), p.returncode))
|
||||
mlog.debug('Program stdout:\n')
|
||||
mlog.debug(stdout)
|
||||
mlog.debug('Program stderr:\n')
|
||||
mlog.debug(stderr)
|
||||
|
||||
if info is not None and self._check_version(info['version']):
|
||||
self.info = T.cast('PythonIntrospectionDict', info)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class _PythonDependencyBase(_Base):
|
||||
|
||||
def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool):
|
||||
self.embed = embed
|
||||
self.version: str = python_holder.info['version']
|
||||
self.platform = python_holder.info['platform']
|
||||
self.variables = python_holder.info['variables']
|
||||
self.paths = python_holder.info['paths']
|
||||
self.is_pypy = python_holder.info['is_pypy']
|
||||
# The "-embed" version of python.pc / python-config was introduced in 3.8,
|
||||
# and distutils extension linking was changed to be considered a non embed
|
||||
# usage. Before then, this dependency always uses the embed=True handling
|
||||
# because that is the only one that exists.
|
||||
#
|
||||
# On macOS and some Linux distros (Debian) distutils doesn't link extensions
|
||||
# against libpython, even on 3.7 and below. We call into distutils and
|
||||
# mirror its behavior. See https://github.com/mesonbuild/meson/issues/4117
|
||||
self.link_libpython = python_holder.info['link_libpython'] or embed
|
||||
self.info: T.Optional[T.Dict[str, str]] = None
|
||||
if mesonlib.version_compare(self.version, '>= 3.0'):
|
||||
self.major_version = 3
|
||||
else:
|
||||
self.major_version = 2
|
||||
|
||||
|
||||
class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase):
|
||||
|
||||
def __init__(self, name: str, environment: 'Environment',
|
||||
kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram',
|
||||
libpc: bool = False):
|
||||
if libpc:
|
||||
mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC')
|
||||
else:
|
||||
mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths')
|
||||
|
||||
PkgConfigDependency.__init__(self, name, environment, kwargs)
|
||||
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
|
||||
|
||||
if libpc and not self.is_found:
|
||||
mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation')
|
||||
|
||||
# pkg-config files are usually accurate starting with python 3.8
|
||||
if not self.link_libpython and mesonlib.version_compare(self.version, '< 3.8'):
|
||||
self.link_args = []
|
||||
|
||||
|
||||
class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase):
|
||||
|
||||
def __init__(self, name: str, environment: 'Environment',
|
||||
kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'):
|
||||
ExtraFrameworkDependency.__init__(self, name, environment, kwargs)
|
||||
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
|
||||
|
||||
|
||||
class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
|
||||
|
||||
def __init__(self, name: str, environment: 'Environment',
|
||||
kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'):
|
||||
SystemDependency.__init__(self, name, environment, kwargs)
|
||||
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
|
||||
|
||||
# match pkg-config behavior
|
||||
if self.link_libpython:
|
||||
# link args
|
||||
if mesonlib.is_windows():
|
||||
self.find_libpy_windows(environment, limited_api=False)
|
||||
else:
|
||||
self.find_libpy(environment)
|
||||
else:
|
||||
self.is_found = True
|
||||
|
||||
# compile args
|
||||
inc_paths = mesonlib.OrderedSet([
|
||||
self.variables.get('INCLUDEPY'),
|
||||
self.paths.get('include'),
|
||||
self.paths.get('platinclude')])
|
||||
|
||||
self.compile_args += ['-I' + path for path in inc_paths if path]
|
||||
|
||||
# https://sourceforge.net/p/mingw-w64/mailman/message/30504611/
|
||||
# https://github.com/python/cpython/pull/100137
|
||||
if mesonlib.is_windows() and self.get_windows_python_arch().endswith('64') and mesonlib.version_compare(self.version, '<3.12'):
|
||||
self.compile_args += ['-DMS_WIN64=']
|
||||
|
||||
if not self.clib_compiler.has_header('Python.h', '', environment, extra_args=self.compile_args):
|
||||
self.is_found = False
|
||||
|
||||
def find_libpy(self, environment: 'Environment') -> None:
|
||||
if self.is_pypy:
|
||||
if self.major_version == 3:
|
||||
libname = 'pypy3-c'
|
||||
else:
|
||||
libname = 'pypy-c'
|
||||
libdir = os.path.join(self.variables.get('base'), 'bin')
|
||||
libdirs = [libdir]
|
||||
else:
|
||||
libname = f'python{self.version}'
|
||||
if 'DEBUG_EXT' in self.variables:
|
||||
libname += self.variables['DEBUG_EXT']
|
||||
if 'ABIFLAGS' in self.variables:
|
||||
libname += self.variables['ABIFLAGS']
|
||||
libdirs = []
|
||||
|
||||
largs = self.clib_compiler.find_library(libname, environment, libdirs)
|
||||
if largs is not None:
|
||||
self.link_args = largs
|
||||
self.is_found = True
|
||||
|
||||
def get_windows_python_arch(self) -> T.Optional[str]:
|
||||
if self.platform == 'mingw':
|
||||
pycc = self.variables.get('CC')
|
||||
if pycc.startswith('x86_64'):
|
||||
return 'x86_64'
|
||||
elif pycc.startswith(('i686', 'i386')):
|
||||
return 'x86'
|
||||
else:
|
||||
mlog.log(f'MinGW Python built with unknown CC {pycc!r}, please file a bug')
|
||||
return None
|
||||
elif self.platform == 'win32':
|
||||
return 'x86'
|
||||
elif self.platform in {'win64', 'win-amd64'}:
|
||||
return 'x86_64'
|
||||
elif self.platform in {'win-arm64'}:
|
||||
return 'aarch64'
|
||||
mlog.log(f'Unknown Windows Python platform {self.platform!r}')
|
||||
return None
|
||||
|
||||
def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]:
|
||||
if self.platform.startswith('win'):
|
||||
vernum = self.variables.get('py_version_nodot')
|
||||
verdot = self.variables.get('py_version_short')
|
||||
imp_lower = self.variables.get('implementation_lower', 'python')
|
||||
if self.static:
|
||||
libpath = Path('libs') / f'libpython{vernum}.a'
|
||||
else:
|
||||
comp = self.get_compiler()
|
||||
if comp.id == "gcc":
|
||||
if imp_lower == 'pypy' and verdot == '3.8':
|
||||
# The naming changed between 3.8 and 3.9
|
||||
libpath = Path('libpypy3-c.dll')
|
||||
elif imp_lower == 'pypy':
|
||||
libpath = Path(f'libpypy{verdot}-c.dll')
|
||||
else:
|
||||
libpath = Path(f'python{vernum}.dll')
|
||||
else:
|
||||
if limited_api:
|
||||
vernum = vernum[0]
|
||||
libpath = Path('libs') / f'python{vernum}.lib'
|
||||
# For a debug build, pyconfig.h may force linking with
|
||||
# pythonX_d.lib (see meson#10776). This cannot be avoided
|
||||
# and won't work unless we also have a debug build of
|
||||
# Python itself (except with pybind11, which has an ugly
|
||||
# hack to work around this) - so emit a warning to explain
|
||||
# the cause of the expected link error.
|
||||
buildtype = self.env.coredata.get_option(mesonlib.OptionKey('buildtype'))
|
||||
assert isinstance(buildtype, str)
|
||||
debug = self.env.coredata.get_option(mesonlib.OptionKey('debug'))
|
||||
# `debugoptimized` buildtype may not set debug=True currently, see gh-11645
|
||||
is_debug_build = debug or buildtype == 'debug'
|
||||
vscrt_debug = False
|
||||
if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options:
|
||||
vscrt = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value
|
||||
if vscrt in {'mdd', 'mtd', 'from_buildtype', 'static_from_buildtype'}:
|
||||
vscrt_debug = True
|
||||
if is_debug_build and vscrt_debug and not self.variables.get('Py_DEBUG'):
|
||||
mlog.warning(textwrap.dedent('''\
|
||||
Using a debug build type with MSVC or an MSVC-compatible compiler
|
||||
when the Python interpreter is not also a debug build will almost
|
||||
certainly result in a failed build. Prefer using a release build
|
||||
type or a debug Python interpreter.
|
||||
'''))
|
||||
# base_prefix to allow for virtualenvs.
|
||||
lib = Path(self.variables.get('base_prefix')) / libpath
|
||||
elif self.platform == 'mingw':
|
||||
if self.static:
|
||||
libname = self.variables.get('LIBRARY')
|
||||
else:
|
||||
libname = self.variables.get('LDLIBRARY')
|
||||
lib = Path(self.variables.get('LIBDIR')) / libname
|
||||
else:
|
||||
raise mesonlib.MesonBugException(
|
||||
'On a Windows path, but the OS doesn\'t appear to be Windows or MinGW.')
|
||||
if not lib.exists():
|
||||
mlog.log('Could not find Python3 library {!r}'.format(str(lib)))
|
||||
return None
|
||||
return [str(lib)]
|
||||
|
||||
def find_libpy_windows(self, env: 'Environment', limited_api: bool = False) -> None:
|
||||
'''
|
||||
Find python3 libraries on Windows and also verify that the arch matches
|
||||
what we are building for.
|
||||
'''
|
||||
pyarch = self.get_windows_python_arch()
|
||||
if pyarch is None:
|
||||
self.is_found = False
|
||||
return
|
||||
arch = detect_cpu_family(env.coredata.compilers.host)
|
||||
if arch != pyarch:
|
||||
mlog.log('Need', mlog.bold(self.name), f'for {arch}, but found {pyarch}')
|
||||
self.is_found = False
|
||||
return
|
||||
# This can fail if the library is not found
|
||||
largs = self.get_windows_link_args(limited_api)
|
||||
if largs is None:
|
||||
self.is_found = False
|
||||
return
|
||||
self.link_args = largs
|
||||
self.is_found = True
|
||||
|
||||
@staticmethod
|
||||
def log_tried() -> str:
|
||||
return 'sysconfig'
|
||||
|
||||
def python_factory(env: 'Environment', for_machine: 'MachineChoice',
|
||||
kwargs: T.Dict[str, T.Any],
|
||||
installation: T.Optional['BasicPythonExternalProgram'] = None) -> T.List['DependencyGenerator']:
|
||||
# We can't use the factory_methods decorator here, as we need to pass the
|
||||
# extra installation argument
|
||||
methods = process_method_kw({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}, kwargs)
|
||||
embed = kwargs.get('embed', False)
|
||||
candidates: T.List['DependencyGenerator'] = []
|
||||
from_installation = installation is not None
|
||||
# When not invoked through the python module, default installation.
|
||||
if installation is None:
|
||||
installation = BasicPythonExternalProgram('python3', mesonlib.python_command)
|
||||
installation.sanity()
|
||||
pkg_version = installation.info['variables'].get('LDVERSION') or installation.info['version']
|
||||
|
||||
if DependencyMethods.PKGCONFIG in methods:
|
||||
if from_installation:
|
||||
pkg_libdir = installation.info['variables'].get('LIBPC')
|
||||
pkg_embed = '-embed' if embed and mesonlib.version_compare(installation.info['version'], '>=3.8') else ''
|
||||
pkg_name = f'python-{pkg_version}{pkg_embed}'
|
||||
|
||||
# If python-X.Y.pc exists in LIBPC, we will try to use it
|
||||
def wrap_in_pythons_pc_dir(name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
|
||||
installation: 'BasicPythonExternalProgram') -> 'ExternalDependency':
|
||||
if not pkg_libdir:
|
||||
# there is no LIBPC, so we can't search in it
|
||||
empty = ExternalDependency(DependencyTypeName('pkgconfig'), env, {})
|
||||
empty.name = 'python'
|
||||
return empty
|
||||
|
||||
old_pkg_libdir = os.environ.pop('PKG_CONFIG_LIBDIR', None)
|
||||
old_pkg_path = os.environ.pop('PKG_CONFIG_PATH', None)
|
||||
os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir
|
||||
try:
|
||||
return PythonPkgConfigDependency(name, env, kwargs, installation, True)
|
||||
finally:
|
||||
def set_env(name: str, value: str) -> None:
|
||||
if value is not None:
|
||||
os.environ[name] = value
|
||||
elif name in os.environ:
|
||||
del os.environ[name]
|
||||
set_env('PKG_CONFIG_LIBDIR', old_pkg_libdir)
|
||||
set_env('PKG_CONFIG_PATH', old_pkg_path)
|
||||
|
||||
candidates.append(functools.partial(wrap_in_pythons_pc_dir, pkg_name, env, kwargs, installation))
|
||||
# We only need to check both, if a python install has a LIBPC. It might point to the wrong location,
|
||||
# e.g. relocated / cross compilation, but the presence of LIBPC indicates we should definitely look for something.
|
||||
if pkg_libdir is not None:
|
||||
candidates.append(functools.partial(PythonPkgConfigDependency, pkg_name, env, kwargs, installation))
|
||||
else:
|
||||
candidates.append(functools.partial(PkgConfigDependency, 'python3', env, kwargs))
|
||||
|
||||
if DependencyMethods.SYSTEM in methods:
|
||||
candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation))
|
||||
|
||||
if DependencyMethods.EXTRAFRAMEWORK in methods:
|
||||
nkwargs = kwargs.copy()
|
||||
if mesonlib.version_compare(pkg_version, '>= 3'):
|
||||
# There is a python in /System/Library/Frameworks, but that's python 2.x,
|
||||
# Python 3 will always be in /Library
|
||||
nkwargs['paths'] = ['/Library/Frameworks']
|
||||
candidates.append(functools.partial(PythonFrameworkDependency, 'Python', env, nkwargs, installation))
|
||||
|
||||
return candidates
|
||||
|
||||
packages['python3'] = python_factory
|
||||
|
||||
packages['pybind11'] = pybind11_factory = DependencyFactory(
|
||||
'pybind11',
|
||||
[DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.CMAKE],
|
||||
configtool_class=Pybind11ConfigToolDependency,
|
||||
)
|
||||
Reference in New Issue
Block a user