diff --git a/docs/markdown/snippets/pkgconfig_define_variable.md b/docs/markdown/snippets/pkgconfig_define_variable.md new file mode 100644 index 000000000..0f7082b0d --- /dev/null +++ b/docs/markdown/snippets/pkgconfig_define_variable.md @@ -0,0 +1,10 @@ +## pkg-config dependencies can now get a variable with multiple replacements + +When using [[dep.get_variable]] and defining a `pkgconfig_define`, it is +sometimes useful to remap multiple dependency variables. For example, if the +upstream project changed the variable name that is interpolated and it is +desirable to support both versions. + +It is now possible to pass multiple pairs of variable/value. + +The same applies to the compatibility [[dep.get_pkgconfig_variable]] method. diff --git a/docs/yaml/objects/dep.yaml b/docs/yaml/objects/dep.yaml index d847690f0..52e28faca 100644 --- a/docs/yaml/objects/dep.yaml +++ b/docs/yaml/objects/dep.yaml @@ -38,6 +38,8 @@ methods: variable by passing a list to this kwarg that can affect the retrieved variable: `['prefix', '/'])`. + *(Since 1.3.0)* Multiple variables can be specified in pairs. + default: type: str since: 0.45.0 @@ -174,6 +176,12 @@ methods: from the object then `default_value` is returned, if it is not set then an error is raised. + warnings: + - Before 1.3.0, specifying multiple pkgconfig_define pairs would silently + malform the results. Only the first variable would be redefined, but + its value would contain both the second variable name, as well as its + value. + optargs: varname: type: str diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index 93422defd..e86206b88 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -139,9 +139,11 @@ class PkgConfigCLI(PkgConfigInterface): @staticmethod def _define_variable_args(define_variable: PkgConfigDefineType) -> T.List[str]: + ret = [] if define_variable: - return ['--define-variable=' + '='.join(define_variable)] - return [] + for pair in define_variable: + ret.append('--define-variable=' + '='.join(pair)) + return ret @lru_cache(maxsize=None) def cflags(self, name: str, allow_system: bool = False, diff --git a/mesonbuild/dependencies/scalapack.py b/mesonbuild/dependencies/scalapack.py index 158056fa0..a8e20f405 100644 --- a/mesonbuild/dependencies/scalapack.py +++ b/mesonbuild/dependencies/scalapack.py @@ -148,5 +148,5 @@ class MKLPkgConfigDependency(PkgConfigDependency): # 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 allow_system = True - cflags = self.pkgconfig.cflags(self.name, allow_system, define_variable=('prefix', self.__mklroot.as_posix())) + 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/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index effeebb20..f13e3ff4a 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -494,6 +494,9 @@ class DependencyHolder(ObjectHolder[Dependency]): from ..dependencies.pkgconfig import PkgConfigDependency if not isinstance(self.held_object, PkgConfigDependency): raise InvalidArguments(f'{self.held_object.get_name()!r} is not a pkgconfig dependency') + if kwargs['define_variable'] and len(kwargs['define_variable']) > 1: + FeatureNew.single_use('dependency.get_pkgconfig_variable keyword argument "define_variable" with more than one pair', + '1.3.0', self.subproject, location=self.current_node) return self.held_object.get_variable( pkgconfig=args[0], default_value=kwargs['default'], @@ -536,6 +539,10 @@ class DependencyHolder(ObjectHolder[Dependency]): default_varname = args[0] if default_varname is not None: FeatureNew('Positional argument to dependency.get_variable()', '0.58.0').use(self.subproject, self.current_node) + if kwargs['pkgconfig_define'] and len(kwargs['pkgconfig_define']) > 1: + FeatureNew.single_use('dependency.get_variable keyword argument "pkgconfig_define" with more than one pair', + '1.3.0', self.subproject, 'In previous versions, this silently returned a malformed value.', + self.current_node) return self.held_object.get_variable( cmake=kwargs['cmake'] or default_varname, pkgconfig=kwargs['pkgconfig'] or default_varname, diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 5212a85ba..616f4efbb 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -4,8 +4,7 @@ """Helpers for strict type checking.""" from __future__ import annotations -import os -import re +import itertools, os, re import typing as T from .. import compilers @@ -30,7 +29,7 @@ if T.TYPE_CHECKING: from ..mesonlib import EnvInitValueType _FullEnvInitValueType = T.Union[EnvironmentVariables, T.List[str], T.List[T.List[str]], EnvInitValueType, str, None] - PkgConfigDefineType = T.Optional[T.Tuple[str, str]] + PkgConfigDefineType = T.Optional[T.Tuple[T.Tuple[str, str], ...]] SourcesVarargsType = T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget]] @@ -841,10 +840,16 @@ BUILD_TARGET_KWS = [ ) ] +def _pkgconfig_define_convertor(x: T.List[str]) -> PkgConfigDefineType: + if x: + keys = itertools.islice(x, 0, None, 2) + vals = itertools.islice(x, 1, None, 2) + return tuple(zip(keys, vals)) + return None + PKGCONFIG_DEFINE_KW: KwargInfo = KwargInfo( 'pkgconfig_define', ContainerTypeInfo(list, str, pairs=True), default=[], - validator=lambda x: 'must be of length 2 or empty' if len(x) not in {0, 2} else None, - convertor=lambda x: tuple(x) if x else None + convertor=_pkgconfig_define_convertor, ) diff --git a/mesonbuild/mdevenv.py b/mesonbuild/mdevenv.py index b60d7a572..9f3d1b973 100644 --- a/mesonbuild/mdevenv.py +++ b/mesonbuild/mdevenv.py @@ -87,7 +87,7 @@ def bash_completion_files(b: build.Build, install_data: 'InstallData') -> T.List datadir = b.environment.coredata.get_option(OptionKey('datadir')) assert isinstance(datadir, str), 'for mypy' datadir_abs = os.path.join(prefix, datadir) - completionsdir = dep.get_variable(pkgconfig='completionsdir', pkgconfig_define=('datadir', datadir_abs)) + completionsdir = dep.get_variable(pkgconfig='completionsdir', pkgconfig_define=(('datadir', datadir_abs),)) assert isinstance(completionsdir, str), 'for mypy' completionsdir_path = Path(completionsdir) for f in install_data.data: diff --git a/test cases/linuxlike/1 pkg-config/meson.build b/test cases/linuxlike/1 pkg-config/meson.build index ca48e9bd0..b09630a16 100644 --- a/test cases/linuxlike/1 pkg-config/meson.build +++ b/test cases/linuxlike/1 pkg-config/meson.build @@ -22,6 +22,13 @@ assert(dep.get_pkgconfig_variable('nonexisting') == '', 'Value of unknown variab assert(dep.get_pkgconfig_variable('nonexisting', default: 'foo') == 'foo', 'Value of unknown variable is not defaulted.') # pkg-config is able to replace variables assert(dep.get_pkgconfig_variable('prefix', define_variable: ['prefix', '/tmp']) == '/tmp', 'prefix variable has not been replaced.') +assert(dep.get_variable('prefix', pkgconfig_define: ['prefix', '/tmp']) == '/tmp', 'prefix variable has not been replaced.') +# pkg-config can replace multiple variables at once +assert(dep.get_variable('prefix', pkgconfig_define: ['prefix', '/tmp', 'libdir', '/bad/libdir']) == '/tmp', 'first variable has not been replaced.') +assert(dep.get_variable('prefix', pkgconfig_define: ['libdir', '/bad/libdir', 'prefix', '/tmp']) == '/tmp', 'second variable has not been replaced.') +assert(dep.get_pkgconfig_variable('prefix', define_variable: ['prefix', '/tmp', 'libdir', '/bad/libdir']) == '/tmp', 'first variable has not been replaced.') +assert(dep.get_pkgconfig_variable('prefix', define_variable: ['libdir', '/bad/libdir', 'prefix', '/tmp']) == '/tmp', 'second variable has not been replaced.') + # Test that dependencies of dependencies work. dep2 = declare_dependency(dependencies : dep)