diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2023-06-06 12:10:35 -0400 |
---|---|---|
committer | Xavier Claessens <xclaesse@gmail.com> | 2023-08-03 16:27:52 -0400 |
commit | 183e4b8e903c6c4c057d09549e669d6292478a4e (patch) | |
tree | 02e0e2306ef78a797d992914c4badd2c34b180ed | |
parent | f077cb2ee3fba55fc0e1be327ad791c19f3c3b5e (diff) | |
download | meson-183e4b8e903c6c4c057d09549e669d6292478a4e.zip meson-183e4b8e903c6c4c057d09549e669d6292478a4e.tar.gz meson-183e4b8e903c6c4c057d09549e669d6292478a4e.tar.bz2 |
PkgConfigDependency: Move CLI handling into its own abstraction
This makes the code cleaner and will allow to have other implementations
in the future.
-rw-r--r-- | mesonbuild/dependencies/hdf5.py | 25 | ||||
-rw-r--r-- | mesonbuild/dependencies/pkgconfig.py | 347 | ||||
-rw-r--r-- | mesonbuild/dependencies/scalapack.py | 16 | ||||
-rw-r--r-- | mesonbuild/modules/external_project.py | 6 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 4 | ||||
-rw-r--r-- | mesonbuild/modules/pkgconfig.py | 4 | ||||
-rwxr-xr-x | run_tests.py | 6 | ||||
-rw-r--r-- | unittests/internaltests.py | 38 | ||||
-rw-r--r-- | unittests/linuxliketests.py | 7 |
9 files changed, 254 insertions, 199 deletions
diff --git a/mesonbuild/dependencies/hdf5.py b/mesonbuild/dependencies/hdf5.py index 84f2812..501e89d 100644 --- a/mesonbuild/dependencies/hdf5.py +++ b/mesonbuild/dependencies/hdf5.py @@ -18,15 +18,13 @@ from __future__ import annotations import functools import os import re -import subprocess from pathlib import Path -from ..mesonlib import Popen_safe, OrderedSet, join_args -from ..programs import ExternalProgram +from ..mesonlib import OrderedSet, join_args from .base import DependencyException, DependencyMethods from .configtool import ConfigToolDependency from .detect import packages -from .pkgconfig import PkgConfigDependency +from .pkgconfig import PkgConfigDependency, PkgConfigInterface from .factory import factory_methods import typing as T @@ -157,19 +155,14 @@ def hdf5_factory(env: 'Environment', for_machine: 'MachineChoice', if DependencyMethods.PKGCONFIG in methods: # Use an ordered set so that these remain the first tried pkg-config files pkgconfig_files = OrderedSet(['hdf5', 'hdf5-serial']) - PCEXE = PkgConfigDependency._detect_pkgbin(False, env, for_machine) - pcenv = PkgConfigDependency.setup_env(os.environ, env, for_machine) - if PCEXE: - assert isinstance(PCEXE, ExternalProgram) + pkg = PkgConfigInterface.instance(env, for_machine, silent=False) + if pkg: # some distros put hdf5-1.2.3.pc with version number in .pc filename. - ret, stdout, _ = Popen_safe(PCEXE.get_command() + ['--list-all'], stderr=subprocess.DEVNULL, env=pcenv) - if ret.returncode == 0: - for pkg in stdout.split('\n'): - if pkg.startswith('hdf5'): - pkgconfig_files.add(pkg.split(' ', 1)[0]) - - for pkg in pkgconfig_files: - candidates.append(functools.partial(HDF5PkgConfigDependency, pkg, env, kwargs, language)) + for mod in pkg.list_all(): + if mod.startswith('hdf5'): + pkgconfig_files.add(mod) + for mod in pkgconfig_files: + candidates.append(functools.partial(HDF5PkgConfigDependency, mod, env, kwargs, language)) if DependencyMethods.CONFIG_TOOL in methods: candidates.append(functools.partial(HDF5ConfigToolDependency, 'hdf5', env, kwargs, language)) diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index 37f2ecb..cfe9cbb 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -31,64 +31,142 @@ if T.TYPE_CHECKING: from ..utils.core import EnvironOrDict from .._typing import ImmutableListProtocol -class PkgConfigDependency(ExternalDependency): +class PkgConfigInterface: + '''Base class wrapping a pkg-config implementation''' + + @staticmethod + def instance(env: Environment, for_machine: MachineChoice, silent: bool) -> T.Optional[PkgConfigInterface]: + impl = PkgConfigCLI(env, for_machine, silent) + if not impl.found(): + return None + return impl + + def found(self) -> bool: + '''Return whether pkg-config is supported''' + raise NotImplementedError + + def version(self, name: str) -> T.Optional[str]: + '''Return module version or None if not found''' + raise NotImplementedError + + def cflags(self, name: str, allow_system: bool = False, + define_variable: T.Optional[ImmutableListProtocol[str]] = None) -> T.List[str]: + '''Return module cflags + @allow_system: If False, remove default system include paths + ''' + raise NotImplementedError + + def libs(self, name: str, static: bool = False, allow_system: bool = False, + define_variable: T.Optional[ImmutableListProtocol[str]] = None) -> T.List[str]: + '''Return module libs + @static: If True, also include private libraries + @allow_system: If False, remove default system libraries search paths + ''' + raise NotImplementedError + + def variable(self, name: str, variable_name: str, + define_variable: ImmutableListProtocol[str]) -> T.Optional[str]: + '''Return module variable or None if variable is not defined''' + raise NotImplementedError + + def list_all(self) -> T.List[str]: + '''Return all available pkg-config modules''' + raise NotImplementedError + +class PkgConfigCLI(PkgConfigInterface): + '''pkg-config CLI implementation''' + # The class's copy of the pkg-config path. Avoids having to search for it # multiple times in the same Meson invocation. - class_pkgbin: PerMachine[T.Union[None, bool, ExternalProgram]] = PerMachine(None, None) + class_pkgbin: PerMachine[T.Union[None, T.Literal[False], ExternalProgram]] = PerMachine(None, None) # We cache all pkg-config subprocess invocations to avoid redundant calls pkgbin_cache: T.Dict[ T.Tuple[ExternalProgram, T.Tuple[str, ...], T.FrozenSet[T.Tuple[str, str]]], T.Tuple[int, str, str] ] = {} - def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: - super().__init__(DependencyTypeName('pkgconfig'), environment, kwargs, language=language) - self.name = name - self.is_libtool = False + def __init__(self, env: Environment, for_machine: MachineChoice, silent: bool) -> None: + self.env = env + self.for_machine = for_machine # Store a copy of the pkg-config path on the object itself so it is # stored in the pickled coredata and recovered. - self.pkgbin = self._detect_pkgbin(self.silent, self.env, self.for_machine) - if self.pkgbin is False: - self.pkgbin = None - msg = f'Pkg-config binary for machine {self.for_machine} not found. Giving up.' - if self.required: - raise DependencyException(msg) - else: - mlog.debug(msg) - return + self.pkgbin = self._detect_pkgbin(env, for_machine, silent) - assert isinstance(self.pkgbin, ExternalProgram) - mlog.debug('Determining dependency {!r} with pkg-config executable ' - '{!r}'.format(name, self.pkgbin.get_path())) - ret, self.version, _ = self._call_pkgbin(['--modversion', name]) + def found(self) -> bool: + return bool(self.pkgbin) + + def version(self, name: str) -> T.Optional[str]: + mlog.debug(f'Determining dependency {name!r} with pkg-config executable {self.pkgbin.get_path()!r}') + ret, version, _ = self._call_pkgbin(['--modversion', name]) + return version if ret == 0 else None + + @staticmethod + def _define_variable_args(define_variable: T.Optional[ImmutableListProtocol[str]]) -> T.List[str]: + if define_variable: + return ['--define-variable=' + '='.join(define_variable)] + return [] + + def cflags(self, name: str, allow_system: bool = False, + define_variable: T.Optional[ImmutableListProtocol[str]] = None) -> T.List[str]: + env = None + if allow_system: + env = os.environ.copy() + env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1' + args: T.List[str] = [] + args += self._define_variable_args(define_variable) + args += ['--cflags', name] + ret, out, err = self._call_pkgbin(args, env=env) if ret != 0: - return + raise DependencyException(f'Could not generate cflags for {name}:\n{err}\n') + return self._split_args(out) - self.is_found = True + def libs(self, name: str, static: bool = False, allow_system: bool = False, + define_variable: T.Optional[ImmutableListProtocol[str]] = None) -> T.List[str]: + env = None + if allow_system: + env = os.environ.copy() + env['PKG_CONFIG_ALLOW_SYSTEM_LIBS'] = '1' + args: T.List[str] = [] + args += self._define_variable_args(define_variable) + if static: + args.append('--static') + args += ['--libs', name] + ret, out, err = self._call_pkgbin(args, env=env) + if ret != 0: + raise DependencyException(f'Could not generate libs for {name}:\n{err}\n') + return self._split_args(out) + + def variable(self, name: str, variable_name: str, + define_variable: ImmutableListProtocol[str]) -> T.Optional[str]: + args: T.List[str] = [] + args += self._define_variable_args(define_variable) + args += ['--variable=' + variable_name, name] + ret, out, err = self._call_pkgbin(args) + if ret != 0: + raise DependencyException(f'Could not get variable for {name}:\n{err}\n') + variable = out.strip() + # pkg-config doesn't distinguish between empty and nonexistent variables + # use the variable list to check for variable existence + if not variable: + ret, out, _ = self._call_pkgbin(['--print-variables', name]) + if not re.search(rf'^{variable_name}$', out, re.MULTILINE): + return None + mlog.debug(f'Got pkg-config variable {variable_name} : {variable}') + return variable - try: - # Fetch cargs to be used while using this dependency - self._set_cargs() - # Fetch the libraries and library paths needed for using this - self._set_libs() - except DependencyException as e: - mlog.debug(f"pkg-config error with '{name}': {e}") - if self.required: - raise - else: - self.compile_args = [] - self.link_args = [] - self.is_found = False - self.reason = e + def list_all(self) -> T.List[str]: + ret, out, err = self._call_pkgbin(['--list-all']) + if ret != 0: + raise DependencyException(f'could not list modules:\n{err}\n') + return [i.split(' ', 1)[0] for i in out.splitlines()] - def __repr__(self) -> str: - s = '<{0} {1}: {2} {3}>' - return s.format(self.__class__.__name__, self.name, self.is_found, - self.version_reqs) + def _split_args(self, cmd: str) -> T.List[str]: + # pkg-config paths follow Unix conventions, even on Windows; split the + # output using shlex.split rather than mesonlib.split_args + return shlex.split(cmd) @classmethod - def _detect_pkgbin(cls, silent: bool, env: Environment, - for_machine: MachineChoice) -> T.Union[None, bool, ExternalProgram]: + def _detect_pkgbin(cls, env: Environment, for_machine: MachineChoice, silent: bool) -> T.Optional[ExternalProgram]: # Only search for pkg-config for each machine the first time and store # the result in the class definition if cls.class_pkgbin[for_machine] is False: @@ -111,12 +189,12 @@ class PkgConfigDependency(ExternalDependency): break else: if not silent: - mlog.log('Found Pkg-config:', mlog.red('NO')) + mlog.log('Found pkg-config:', mlog.red('NO')) # Set to False instead of None to signify that we've already # searched for it and not found it cls.class_pkgbin[for_machine] = False - return cls.class_pkgbin[for_machine] + return cls.class_pkgbin[for_machine] or None def _call_pkgbin_real(self, args: T.List[str], env: T.Dict[str, str]) -> T.Tuple[int, str, str]: assert isinstance(self.pkgbin, ExternalProgram) @@ -125,8 +203,34 @@ class PkgConfigDependency(ExternalDependency): return p.returncode, out.strip(), err.strip() @staticmethod - def get_env(environment: 'Environment', for_machine: MachineChoice, - uninstalled: bool = False) -> 'EnvironmentVariables': + def check_pkgconfig(env: Environment, pkgbin: ExternalProgram) -> T.Optional[str]: + if not pkgbin.found(): + mlog.log(f'Did not find pkg-config by name {pkgbin.name!r}') + return None + command_as_string = ' '.join(pkgbin.get_command()) + try: + helptext = Popen_safe(pkgbin.get_command() + ['--help'])[1] + if 'Pure-Perl' in helptext: + mlog.log(f'Found pkg-config {command_as_string!r} but it is Strawberry Perl and thus broken. Ignoring...') + return None + p, out = Popen_safe(pkgbin.get_command() + ['--version'])[0:2] + if p.returncode != 0: + mlog.warning(f'Found pkg-config {command_as_string!r} but it failed when ran') + return None + except FileNotFoundError: + mlog.warning(f'We thought we found pkg-config {command_as_string!r} but now it\'s not there. How odd!') + return None + except PermissionError: + msg = f'Found pkg-config {command_as_string!r} but didn\'t have permissions to run it.' + if not env.machines.build.is_windows(): + msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.' + mlog.warning(msg) + return None + return out.strip() + + @staticmethod + def get_env(environment: Environment, for_machine: MachineChoice, + uninstalled: bool = False) -> EnvironmentVariables: env = EnvironmentVariables() key = OptionKey('pkg_config_path', machine=for_machine) extra_paths: T.List[str] = environment.coredata.options[key].value[:] @@ -144,9 +248,9 @@ class PkgConfigDependency(ExternalDependency): return env @staticmethod - def setup_env(env: EnvironOrDict, environment: 'Environment', for_machine: MachineChoice, + def setup_env(env: EnvironOrDict, environment: Environment, for_machine: MachineChoice, uninstalled: bool = False) -> T.Dict[str, str]: - envvars = PkgConfigDependency.get_env(environment, for_machine, uninstalled) + envvars = PkgConfigCLI.get_env(environment, for_machine, uninstalled) env = envvars.get_env(env) # Dump all PKG_CONFIG environment variables for key, value in env.items(): @@ -157,15 +261,57 @@ class PkgConfigDependency(ExternalDependency): def _call_pkgbin(self, args: T.List[str], env: T.Optional[EnvironOrDict] = None) -> T.Tuple[int, str, str]: assert isinstance(self.pkgbin, ExternalProgram) env = env or os.environ - env = PkgConfigDependency.setup_env(env, self.env, self.for_machine) + env = self.setup_env(env, self.env, self.for_machine) fenv = frozenset(env.items()) targs = tuple(args) - cache = PkgConfigDependency.pkgbin_cache + cache = self.pkgbin_cache if (self.pkgbin, targs, fenv) not in cache: cache[(self.pkgbin, targs, fenv)] = self._call_pkgbin_real(args, env) return cache[(self.pkgbin, targs, fenv)] + +class PkgConfigDependency(ExternalDependency): + + def __init__(self, name: str, environment: Environment, kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None: + super().__init__(DependencyTypeName('pkgconfig'), environment, kwargs, language=language) + self.name = name + self.is_libtool = False + self.pkgconfig = PkgConfigInterface.instance(self.env, self.for_machine, self.silent) + if not self.pkgconfig: + msg = f'Pkg-config for machine {self.for_machine} not found. Giving up.' + if self.required: + raise DependencyException(msg) + mlog.debug(msg) + return + + version = self.pkgconfig.version(name) + if version is None: + return + + self.version = version + self.is_found = True + + try: + # Fetch cargs to be used while using this dependency + self._set_cargs() + # Fetch the libraries and library paths needed for using this + self._set_libs() + except DependencyException as e: + mlog.debug(f"Pkg-config error with '{name}': {e}") + if self.required: + raise + else: + self.compile_args = [] + self.link_args = [] + self.is_found = False + self.reason = e + + def __repr__(self) -> str: + s = '<{0} {1}: {2} {3}>' + return s.format(self.__class__.__name__, self.name, self.is_found, + self.version_reqs) + def _convert_mingw_paths(self, args: T.List[str]) -> T.List[str]: ''' Both MSVC and native Python on Windows cannot handle MinGW-esque /c/foo @@ -197,27 +343,19 @@ class PkgConfigDependency(ExternalDependency): converted.append(arg) return converted - def _split_args(self, cmd: str) -> T.List[str]: - # pkg-config paths follow Unix conventions, even on Windows; split the - # output using shlex.split rather than mesonlib.split_args - return shlex.split(cmd) - def _set_cargs(self) -> None: - env = None + allow_system = False if self.language == 'fortran': # gfortran doesn't appear to look in system paths for INCLUDE files, # so don't allow pkg-config to suppress -I flags for system paths - env = os.environ.copy() - env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1' - ret, out, err = self._call_pkgbin(['--cflags', self.name], env=env) - if ret != 0: - raise DependencyException(f'Could not generate cargs for {self.name}:\n{err}\n') - self.compile_args = self._convert_mingw_paths(self._split_args(out)) + allow_system = True + cflags = self.pkgconfig.cflags(self.name, allow_system) + self.compile_args = self._convert_mingw_paths(cflags) - def _search_libs(self, out: str, out_raw: str) -> T.Tuple[T.List[str], T.List[str]]: + def _search_libs(self, libs_in: T.List[str], raw_libs_in: T.List[str]) -> T.Tuple[T.List[str], T.List[str]]: ''' - @out: PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs - @out_raw: pkg-config --libs + @libs_in: PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs + @raw_libs_in: pkg-config --libs We always look for the file ourselves instead of depending on the compiler to find it with -lfoo or foo.lib (if possible) because: @@ -241,7 +379,7 @@ class PkgConfigDependency(ExternalDependency): # always searched first. prefix_libpaths: OrderedSet[str] = OrderedSet() # We also store this raw_link_args on the object later - raw_link_args = self._convert_mingw_paths(self._split_args(out_raw)) + raw_link_args = self._convert_mingw_paths(raw_libs_in) for arg in raw_link_args: if arg.startswith('-L') and not arg.startswith(('-L-l', '-L-L')): path = arg[2:] @@ -262,7 +400,7 @@ class PkgConfigDependency(ExternalDependency): pkg_config_path = self._convert_mingw_paths(pkg_config_path) prefix_libpaths = OrderedSet(sort_libpaths(list(prefix_libpaths), pkg_config_path)) system_libpaths: OrderedSet[str] = OrderedSet() - full_args = self._convert_mingw_paths(self._split_args(out)) + full_args = self._convert_mingw_paths(libs_in) for arg in full_args: if arg.startswith(('-L-l', '-L-L')): # These are D language arguments, not library paths @@ -367,84 +505,25 @@ class PkgConfigDependency(ExternalDependency): return link_args, raw_link_args def _set_libs(self) -> None: - env = None - libcmd = ['--libs'] - - if self.static: - libcmd.append('--static') - - libcmd.append(self.name) - # Force pkg-config to output -L fields even if they are system # paths so we can do manual searching with cc.find_library() later. - env = os.environ.copy() - env['PKG_CONFIG_ALLOW_SYSTEM_LIBS'] = '1' - ret, out, err = self._call_pkgbin(libcmd, env=env) - if ret != 0: - raise DependencyException(f'Could not generate libs for {self.name}:\n{err}\n') + libs = self.pkgconfig.libs(self.name, self.static, allow_system=True) # Also get the 'raw' output without -Lfoo system paths for adding -L # args with -lfoo when a library can't be found, and also in # gnome.generate_gir + gnome.gtkdoc which need -L -l arguments. - ret, out_raw, err_raw = self._call_pkgbin(libcmd) - if ret != 0: - raise DependencyException(f'Could not generate libs for {self.name}:\n\n{out_raw}') - self.link_args, self.raw_link_args = self._search_libs(out, out_raw) + raw_libs = self.pkgconfig.libs(self.name, self.static, allow_system=False) + self.link_args, self.raw_link_args = self._search_libs(libs, raw_libs) def get_pkgconfig_variable(self, variable_name: str, - define_variable: 'ImmutableListProtocol[str]', + define_variable: ImmutableListProtocol[str], default: T.Optional[str]) -> str: - options = ['--variable=' + variable_name, self.name] - - if define_variable: - options = ['--define-variable=' + '='.join(define_variable)] + options - - ret, out, err = self._call_pkgbin(options) - variable = '' - if ret != 0: - if self.required: - raise DependencyException(f'dependency {self.name} not found:\n{err}\n') - else: - variable = out.strip() - - # pkg-config doesn't distinguish between empty and nonexistent variables - # use the variable list to check for variable existence - if not variable: - ret, out, _ = self._call_pkgbin(['--print-variables', self.name]) - if not re.search(r'^' + variable_name + r'$', out, re.MULTILINE): - if default is not None: - variable = default - else: - mlog.warning(f"pkgconfig variable '{variable_name}' not defined for dependency {self.name}.") - - mlog.debug(f'Got pkgconfig variable {variable_name} : {variable}') + variable = self.pkgconfig.variable(self.name, variable_name, define_variable) + if variable is None: + if default is None: + mlog.warning(f'Pkg-config variable {variable_name!r} not defined for dependency {self.name}.') + variable = default or '' return variable - @staticmethod - def check_pkgconfig(env: Environment, pkgbin: ExternalProgram) -> T.Optional[str]: - if not pkgbin.found(): - mlog.log(f'Did not find pkg-config by name {pkgbin.name!r}') - return None - command_as_string = ' '.join(pkgbin.get_command()) - try: - helptext = Popen_safe(pkgbin.get_command() + ['--help'])[1] - if 'Pure-Perl' in helptext: - mlog.log(f'found pkg-config {command_as_string!r} but it is Strawberry Perl and thus broken. Ignoring...') - return None - p, out = Popen_safe(pkgbin.get_command() + ['--version'])[0:2] - if p.returncode != 0: - mlog.warning(f'Found pkg-config {command_as_string!r} but it failed when run') - return None - except FileNotFoundError: - mlog.warning(f'We thought we found pkg-config {command_as_string!r} but now it\'s not there. How odd!') - return None - except PermissionError: - msg = f'Found pkg-config {command_as_string!r} but didn\'t have permissions to run it.' - if not env.machines.build.is_windows(): - msg += '\n\nOn Unix-like systems this is often caused by scripts that are not executable.' - mlog.warning(msg) - return None - return out.strip() - def extract_field(self, la_file: str, fieldname: str) -> T.Optional[str]: with open(la_file, encoding='utf-8') as f: for line in f: diff --git a/mesonbuild/dependencies/scalapack.py b/mesonbuild/dependencies/scalapack.py index 2442ede..fc2f720 100644 --- a/mesonbuild/dependencies/scalapack.py +++ b/mesonbuild/dependencies/scalapack.py @@ -20,7 +20,6 @@ import typing as T from ..mesonlib import OptionKey from .base import DependencyMethods -from .base import DependencyException from .cmake import CMakeDependency from .detect import packages from .pkgconfig import PkgConfigDependency @@ -144,17 +143,10 @@ class MKLPkgConfigDependency(PkgConfigDependency): self.link_args.insert(i + 1, '-lmkl_blacs_intelmpi_lp64') def _set_cargs(self) -> None: - env = None + allow_system = False if self.language == 'fortran': # gfortran doesn't appear to look in system paths for INCLUDE files, # so don't allow pkg-config to suppress -I flags for system paths - env = os.environ.copy() - env['PKG_CONFIG_ALLOW_SYSTEM_CFLAGS'] = '1' - ret, out, err = self._call_pkgbin([ - '--cflags', self.name, - '--define-variable=prefix=' + self.__mklroot.as_posix()], - env=env) - if ret != 0: - raise DependencyException('Could not generate cargs for %s:\n%s\n' % - (self.name, err)) - self.compile_args = self._convert_mingw_paths(self._split_args(out)) + allow_system = True + cflags = self.pkgconfig.cflags(self.name, allow_system, define_variable=['prefix', self.__mklroot.as_posix()]) + self.compile_args = self._convert_mingw_paths(cflags) diff --git a/mesonbuild/modules/external_project.py b/mesonbuild/modules/external_project.py index fd7e7c8..f7a72bc 100644 --- a/mesonbuild/modules/external_project.py +++ b/mesonbuild/modules/external_project.py @@ -24,7 +24,7 @@ from .. import mlog, build from ..compilers.compilers import CFLAGS_MAPPING from ..envconfig import ENV_VAR_PROG_MAP from ..dependencies import InternalDependency -from ..dependencies.pkgconfig import PkgConfigDependency +from ..dependencies.pkgconfig import PkgConfigCLI from ..interpreterbase import FeatureNew from ..interpreter.type_checking import ENV_KW, DEPENDS_KW from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args @@ -165,8 +165,8 @@ class ExternalProject(NewExtensionModule): self.run_env['LDFLAGS'] = self._quote_and_join(link_args) self.run_env = self.user_env.get_env(self.run_env) - self.run_env = PkgConfigDependency.setup_env(self.run_env, self.env, MachineChoice.HOST, - uninstalled=True) + self.run_env = PkgConfigCLI.setup_env(self.run_env, self.env, MachineChoice.HOST, + uninstalled=True) self.build_dir.mkdir(parents=True, exist_ok=True) self._run('configure', configure_cmd, workdir) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 3249212..18862e7 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -34,7 +34,7 @@ from .. import mesonlib from .. import mlog from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments from ..dependencies import Dependency, InternalDependency -from ..dependencies.pkgconfig import PkgConfigDependency +from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigCLI from ..interpreter.type_checking import DEPENDS_KW, DEPEND_FILES_KW, ENV_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, SOURCES_KW, in_set_validator from ..interpreterbase import noPosargs, noKwargs, FeatureNew, FeatureDeprecated from ..interpreterbase import typed_kwargs, KwargInfo, ContainerTypeInfo @@ -967,7 +967,7 @@ class GnomeModule(ExtensionModule): # -uninstalled.pc files Meson generated. It also must respect pkgconfig # settings user could have set in machine file, like PKG_CONFIG_LIBDIR, # SYSROOT, etc. - run_env = PkgConfigDependency.get_env(state.environment, MachineChoice.HOST, uninstalled=True) + run_env = PkgConfigCLI.get_env(state.environment, MachineChoice.HOST, uninstalled=True) # g-ir-scanner uses Python's distutils to find the compiler, which uses 'CC' cc_exelist = state.environment.coredata.compilers.host['c'].get_exelist() run_env.set('CC', [quote_arg(x) for x in cc_exelist], ' ') diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index dd2efad..44182d7 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -26,7 +26,7 @@ from .. import dependencies from .. import mesonlib from .. import mlog from ..coredata import BUILTIN_DIR_OPTIONS -from ..dependencies.pkgconfig import PkgConfigDependency +from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigCLI from ..interpreter.type_checking import D_MODULE_VERSIONS_KW, INSTALL_DIR_KW, VARIABLES_KW, NoneType from ..interpreterbase import FeatureNew, FeatureDeprecated from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args @@ -741,7 +741,7 @@ class PkgConfigModule(NewExtensionModule): self._metadata[lib.get_id()] = MetaData( filebase, name, state.current_node) if self.devenv is None: - self.devenv = PkgConfigDependency.get_env(state.environment, mesonlib.MachineChoice.HOST, uninstalled=True) + self.devenv = PkgConfigCLI.get_env(state.environment, mesonlib.MachineChoice.HOST, uninstalled=True) return ModuleReturnValue(res, [res]) diff --git a/run_tests.py b/run_tests.py index 66341e4..cf06337 100755 --- a/run_tests.py +++ b/run_tests.py @@ -36,7 +36,7 @@ import typing as T from mesonbuild.compilers.c import CCompiler from mesonbuild.compilers.detect import detect_c_compiler -from mesonbuild.dependencies.pkgconfig import PkgConfigDependency +from mesonbuild.dependencies.pkgconfig import PkgConfigCLI from mesonbuild import mesonlib from mesonbuild import mesonmain from mesonbuild import mtest @@ -302,8 +302,8 @@ def run_mtest_inprocess(commandlist: T.List[str]) -> T.Tuple[int, str, str]: def clear_meson_configure_class_caches() -> None: CCompiler.find_library_cache = {} CCompiler.find_framework_cache = {} - PkgConfigDependency.pkgbin_cache = {} - PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None) + PkgConfigCLI.pkgbin_cache = {} + PkgConfigCLI.class_pkgbin = mesonlib.PerMachine(None, None) mesonlib.project_meson_versions = collections.defaultdict(str) def run_configure_inprocess(commandlist: T.List[str], env: T.Optional[T.Dict[str, str]] = None, catch_exception: bool = False) -> T.Tuple[int, str, str]: diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 672a5a0..86a30f8 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -47,7 +47,7 @@ from mesonbuild.mesonlib import ( OptionType ) from mesonbuild.interpreter.type_checking import in_set_validator, NoneType -from mesonbuild.dependencies.pkgconfig import PkgConfigDependency +from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface, PkgConfigCLI from mesonbuild.programs import ExternalProgram import mesonbuild.modules.pkgconfig @@ -640,22 +640,19 @@ class InternalTests(unittest.TestCase): create_static_lib(p1 / 'libdl.a') create_static_lib(p1 / 'librt.a') - def fake_call_pkgbin(self, args, env=None): - if '--libs' not in args: - return 0, '', '' - if args[-1] == 'foo': - return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', '' - if args[-1] == 'bar': - return 0, f'-L{p2.as_posix()} -lbar', '' - if args[-1] == 'internal': - return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', '' - - old_call = PkgConfigDependency._call_pkgbin - old_check = PkgConfigDependency.check_pkgconfig - PkgConfigDependency._call_pkgbin = fake_call_pkgbin - PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin - # Test begins - try: + class FakeInstance(PkgConfigCLI): + def _call_pkgbin(self, args, env=None): + if '--libs' not in args: + return 0, '', '' + if args[-1] == 'foo': + return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', '' + if args[-1] == 'bar': + return 0, f'-L{p2.as_posix()} -lbar', '' + if args[-1] == 'internal': + return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', '' + + with mock.patch.object(PkgConfigInterface, 'instance') as instance_method: + instance_method.return_value = FakeInstance(env, MachineChoice.HOST, silent=True) kwargs = {'required': True, 'silent': True} foo_dep = PkgConfigDependency('foo', env, kwargs) self.assertEqual(foo_dep.get_link_args(), @@ -670,13 +667,6 @@ class InternalTests(unittest.TestCase): for link_arg in link_args: for lib in ('pthread', 'm', 'c', 'dl', 'rt'): self.assertNotIn(f'lib{lib}.a', link_arg, msg=link_args) - finally: - # Test ends - PkgConfigDependency._call_pkgbin = old_call - PkgConfigDependency.check_pkgconfig = old_check - # Reset dependency class to ensure that in-process configure doesn't mess up - PkgConfigDependency.pkgbin_cache = {} - PkgConfigDependency.class_pkgbin = PerMachine(None, None) def test_version_compare(self): comparefunc = mesonbuild.mesonlib.version_compare_many diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 7ebb575..bd73857 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -45,7 +45,7 @@ from mesonbuild.compilers.c import AppleClangCCompiler from mesonbuild.compilers.cpp import AppleClangCPPCompiler from mesonbuild.compilers.objc import AppleClangObjCCompiler from mesonbuild.compilers.objcpp import AppleClangObjCPPCompiler -from mesonbuild.dependencies.pkgconfig import PkgConfigDependency +from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigCLI import mesonbuild.modules.pkgconfig PKG_CONFIG = os.environ.get('PKG_CONFIG', 'pkg-config') @@ -173,7 +173,8 @@ class LinuxlikeTests(BasePlatformTests): self.assertEqual(libhello_nolib.get_compile_args(), []) self.assertEqual(libhello_nolib.get_pkgconfig_variable('foo', [], None), 'bar') self.assertEqual(libhello_nolib.get_pkgconfig_variable('prefix', [], None), self.prefix) - if version_compare(PkgConfigDependency.check_pkgconfig(env, libhello_nolib.pkgbin),">=0.29.1"): + impl = libhello_nolib.pkgconfig + if not isinstance(impl, PkgConfigCLI) or version_compare(PkgConfigCLI.check_pkgconfig(env, impl.pkgbin),">=0.29.1"): self.assertEqual(libhello_nolib.get_pkgconfig_variable('escaped_var', [], None), r'hello\ world') self.assertEqual(libhello_nolib.get_pkgconfig_variable('unescaped_var', [], None), 'hello world') @@ -1168,7 +1169,7 @@ class LinuxlikeTests(BasePlatformTests): # Regression test: This used to modify the value of `pkg_config_path` # option, adding the meson-uninstalled directory to it. - PkgConfigDependency.setup_env({}, env, MachineChoice.HOST, uninstalled=True) + PkgConfigCLI.setup_env({}, env, MachineChoice.HOST, uninstalled=True) pkg_config_path = env.coredata.options[OptionKey('pkg_config_path')].value self.assertEqual(pkg_config_path, [pkg_dir]) |