From e646130ef1a703a3624cff4ca11f926703a6fcf4 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Wed, 4 Aug 2021 11:30:27 -0400 Subject: [PATCH] interpreter: Fix dependency(..., static: true) fallback It should build the fallback subprject with default_library=static and override the dependency for both static=True and static kwarg not given. Fixes: #8050. --- docs/markdown/Reference-manual.md | 5 ++ .../snippets/static_fallback_override.md | 46 +++++++++++++++ mesonbuild/interpreter/dependencyfallbacks.py | 11 ++++ mesonbuild/interpreter/mesonmain.py | 58 +++++++++++++++---- .../common/98 subproject subdir/meson.build | 27 +++++++++ .../subprojects/sub_static/meson.build | 8 +++ unittests/allplatformstests.py | 5 ++ 7 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 docs/markdown/snippets/static_fallback_override.md create mode 100644 test cases/common/98 subproject subdir/subprojects/sub_static/meson.build diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index fd156f9f2..8ef36de68 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -568,6 +568,8 @@ This function supports the following keyword arguments: - `static`: tells the dependency provider to try to get static libraries instead of dynamic ones (note that this is not supported by all dependency backends) + *Since 0.60.0* it also sets `default_library` option accordingly on the fallback + subproject if it was not set explicitly in `default_options` keyword argument. - `version` *(since 0.37.0)*: specifies the required version, a string containing a comparison operator followed by the version string, examples include `>1.0.0`, `<=2.3.5` or `3.1.4` for exact matching. @@ -2151,6 +2153,9 @@ the following methods. `native` keyword arguments. Doing this in a subproject allows the parent project to retrieve the dependency without having to know the dependency variable name: `dependency(name, fallback : subproject_name)`. + *Since 0.60.0* `static` boolean keyword argument can be specified to override + static and/or shared dependencies separately. If not specified it is assumed + `dep_object` follows `default_library` option value. - `project_version()`: returns the version string specified in `project` function call. diff --git a/docs/markdown/snippets/static_fallback_override.md b/docs/markdown/snippets/static_fallback_override.md new file mode 100644 index 000000000..1a6faaf08 --- /dev/null +++ b/docs/markdown/snippets/static_fallback_override.md @@ -0,0 +1,46 @@ +## `static` keyword argument to `meson.override_dependency()` + +It is now possible to override shared and/or static dependencies separately. +When the `static` keyword argument is not specified in `dependency()`, the first +override will be used (`static_dep` in the example below). +```meson +static_lib = static_library() +static_dep = declare_dependency(link_with: static_lib) +meson.override_dependency('foo', static_dep, static: true) + +shared_lib = shared_library() +shared_dep = declare_dependency(link_with: shared_lib) +meson.override_dependency('foo', shared_dep, static: false) + +# Returns static_dep +dependency('foo') + +# Returns static_dep +dependency('foo', static: true) + +# Returns shared_dep +dependency('foo', static: false) +``` + +When the `static` keyword argument is not specified in `meson.override_dependency()`, +the dependency is assumed to follow the value of `default_library` option. +```meson +dep = declare_dependency(...) +meson.override_dependency('foo', dep) + +# Always works +dependency('foo') + +# Works only if default_library is 'static' or 'both' +dependency('foo', static: true) + +# Works only if default_library is 'shared' or 'both' +dependency('foo', static: false) +``` + +## `dependency()` sets `default_library` on fallback subproject + +When the `static` keyword argument is set but `default_library` is missing in +`default_options`, `dependency()` will set it when configuring fallback +subproject. `dependency('foo', static: true)` is now equivalent to +`dependency('foo', static: true, default_options: ['default_library=static'])`. diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 5934cbd90..4baa5cf42 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -116,6 +116,17 @@ class DependencyFallbacksHolder(MesonInterpreterObject): mlog.log('Looking for a fallback subproject for the dependency', mlog.bold(self.display_name)) + # dependency('foo', static: true) should implicitly add + # default_options: ['default_library=static'] + static = kwargs.get('static') + default_options = stringlistify(func_kwargs.get('default_options', [])) + if static is not None and not any('default_library' in i for i in default_options): + default_library = 'static' if static else 'shared' + opt = f'default_library={default_library}' + mlog.log(f'Building fallback subproject with {opt}') + default_options.append(opt) + func_kwargs['default_options'] = default_options + # Configure the subproject subp_name = self.subproject_name varname = self.subproject_varname diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index ea16b46a5..1c850b233 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -14,11 +14,16 @@ from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprec from .interpreterobjects import (ExecutableHolder, ExternalProgramHolder, CustomTargetHolder, CustomTargetIndexHolder, EnvironmentVariablesObject) +from .type_checking import NATIVE_KW import typing as T if T.TYPE_CHECKING: from .interpreter import Interpreter + from typing_extensions import TypedDict + class FuncOverrideDependency(TypedDict): + native: mesonlib.MachineChoice + static: T.Optional[bool] class MesonMain(MesonInterpreterObject): def __init__(self, build: 'build.Build', interpreter: 'Interpreter'): @@ -294,21 +299,54 @@ class MesonMain(MesonInterpreterObject): raise InterpreterException('Second argument must be an external program or executable.') self.interpreter.add_find_program_override(name, exe) + @typed_kwargs('meson.override_dependency', NATIVE_KW, + KwargInfo('static', bool, since='0.60.0')) + @typed_pos_args('meson.override_dependency', str, dependencies.Dependency) @FeatureNew('meson.override_dependency', '0.54.0') - @permittedKwargs({'native'}) - def override_dependency_method(self, args, kwargs): - if len(args) != 2: - raise InterpreterException('Override needs two arguments') - name = args[0] - dep = args[1] - if not isinstance(name, str) or not name: + def override_dependency_method(self, args: T.Tuple[str, dependencies.Dependency], kwargs: 'FuncOverrideDependency') -> None: + name, dep = args + if not name: raise InterpreterException('First argument must be a string and cannot be empty') - if not isinstance(dep, dependencies.Dependency): - raise InterpreterException('Second argument must be a dependency object') + + optkey = OptionKey('default_library', subproject=self.interpreter.subproject) + default_library = self.interpreter.coredata.get_option(optkey) + assert isinstance(default_library, str), 'for mypy' + static = kwargs['static'] + if static is None: + # We don't know if dep represents a static or shared library, could + # be a mix of both. We assume it is following default_library + # value. + self._override_dependency_impl(name, dep, kwargs, static=None) + if default_library == 'static': + self._override_dependency_impl(name, dep, kwargs, static=True) + elif default_library == 'shared': + self._override_dependency_impl(name, dep, kwargs, static=False) + else: + self._override_dependency_impl(name, dep, kwargs, static=True) + self._override_dependency_impl(name, dep, kwargs, static=False) + else: + # dependency('foo') without specifying static kwarg should find this + # override regardless of the static value here. But do not raise error + # if it has already been overridden, which would happend when overriding + # static and shared separately: + # meson.override_dependency('foo', shared_dep, static: false) + # meson.override_dependency('foo', static_dep, static: true) + # In that case dependency('foo') would return the first override. + self._override_dependency_impl(name, dep, kwargs, static=None, permissive=True) + self._override_dependency_impl(name, dep, kwargs, static=static) + + def _override_dependency_impl(self, name: str, dep: dependencies.Dependency, kwargs: 'FuncOverrideDependency', static: T.Optional[bool], permissive: bool = False) -> None: + kwargs = kwargs.copy() + if static is None: + del kwargs['static'] + else: + kwargs['static'] = static identifier = dependencies.get_dep_identifier(name, kwargs) - for_machine = self.interpreter.machine_from_native_kwarg(kwargs) + for_machine = kwargs['native'] override = self.build.dependency_overrides[for_machine].get(identifier) if override: + if permissive: + return m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}' location = mlog.get_error_location_string(override.node.filename, override.node.lineno) raise InterpreterException(m.format(name, location)) diff --git a/test cases/common/98 subproject subdir/meson.build b/test cases/common/98 subproject subdir/meson.build index 36e48a7fa..95bf34e42 100644 --- a/test cases/common/98 subproject subdir/meson.build +++ b/test cases/common/98 subproject subdir/meson.build @@ -65,3 +65,30 @@ assert(d.found(), 'Should be able to fallback to sub-subproject') # file exists. d = dependency('subsubsub') assert(d.found(), 'Should be able to fallback to sub-sub-subproject') + +# Verify that `static: true` implies 'default_library=static'. +d = dependency('sub_static', static: true) +assert(d.found()) +# Verify that when not specifying static kwarg we can still get fallback dep. +d = dependency('sub_static') +assert(d.found()) +# But when asking for shared library explicitly, it is not found. +d = dependency('sub_static', static: false, required: false) +assert(not d.found()) +# The subproject also overrides sub_static2 with `static: true` +d = dependency('sub_static2') +assert(d.found()) +d = dependency('sub_static2', static: true) +assert(d.found()) +d = dependency('sub_static2', static: false, required: false) +assert(not d.found()) +# sub_static3 is overridden twice with `static: true` and `static: false` +d = dependency('sub_static3') +assert(d.found()) +assert(d.get_variable('static') == 'true') +d = dependency('sub_static3', static: true) +assert(d.found()) +assert(d.get_variable('static') == 'true') +d = dependency('sub_static3', static: false) +assert(d.found()) +assert(d.get_variable('static') == 'false') diff --git a/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build b/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build new file mode 100644 index 000000000..6c00623a1 --- /dev/null +++ b/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build @@ -0,0 +1,8 @@ +project('sub_static') + +assert(get_option('default_library') == 'static') +meson.override_dependency('sub_static', declare_dependency()) +meson.override_dependency('sub_static2', declare_dependency(), static: true) +meson.override_dependency('sub_static3', declare_dependency(variables: {'static': 'true'}), static: true) +meson.override_dependency('sub_static3', declare_dependency(variables: {'static': 'false'}), static: false) + diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 32e084d19..911613e7e 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -2487,6 +2487,11 @@ class AllPlatformTests(BasePlatformTests): 'name': 'sub_novar', 'version': '1.0', }, + { + 'descriptive_name': 'sub_static', + 'name': 'sub_static', + 'version': 'undefined' + }, { 'descriptive_name': 'subsub', 'name': 'subsub',