From 2fdedc4d0fc73c509669bf9f89863017e0f0989b Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Wed, 19 Dec 2018 20:56:07 -0500 Subject: Add meson.override_dependency() Similar to meson.override_find_program() but overrides the result of the dependency() function. Also ensure that dependency() always returns the same result when looking for the same dependency, this fixes cases where parts of the project could be using a system library and other parts use the library provided by a subproject. --- docs/markdown/Reference-manual.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'docs/markdown/Reference-manual.md') diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index e43ef5731..09d54bead 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1825,12 +1825,18 @@ the following methods. - `override_find_program(progname, program)` [*(Added 0.46.0)*](Release-notes-for-0.46.0.md#can-override-find_program) specifies that whenever `find_program` is used to find a program - named `progname`, Meson should not not look it up on the system but + named `progname`, Meson should not look it up on the system but instead return `program`, which may either be the result of `find_program`, `configure_file` or `executable`. If `program` is an `executable`, it cannot be used during configure. +- `override_dependency(name, dep_object)` [*(Added + 0.54.0)*](Release-notes-for-0.54.0.md#override-dependency) + specifies that whenever `dependency(name, ...)` is used, Meson should not + look it up on the system but instead return `dep_object`, which may either be + the result of `dependency()` or `declare_dependency()`. + - `project_version()` returns the version string specified in `project` function call. -- cgit v1.2.3 From 943e9368f7198b6c2b069ad024ee798037f3c35e Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Mon, 25 Nov 2019 14:29:05 -0500 Subject: Simplify dependency() fallback Now that subprojects can override the dependency name, there is no need to provide a variable name for the fallback any more. --- docs/markdown/Reference-manual.md | 4 +++ docs/markdown/snippets/override_dependency.md | 6 ++++ mesonbuild/interpreter.py | 38 ++++++++++++++-------- run_unittests.py | 10 ++++-- .../common/102 subproject subdir/meson.build | 4 +++ .../subprojects/sub_novar/meson.build | 3 ++ 6 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 test cases/common/102 subproject subdir/subprojects/sub_novar/meson.build (limited to 'docs/markdown/Reference-manual.md') diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 09d54bead..b4d233342 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -445,6 +445,10 @@ arguments: [`dependency()`](#dependency), etc. Note that this means the fallback dependency may be a not-found dependency, in which case the value of the `required:` kwarg will be obeyed. + *Since 0.54.0* `'subproj_dep'` argument can be omitted in the case the + subproject used `meson.override_dependency('dependency_name', subproj_dep)`. + In that case, the `fallback` keyword argument can be a single string instead + of a list of 2 strings. - `language` *(added 0.42.0)* defines what language-specific dependency to find if it's available for multiple languages. - `method` defines the way the dependency is detected, the default is diff --git a/docs/markdown/snippets/override_dependency.md b/docs/markdown/snippets/override_dependency.md index 875eef8c2..ca420bc84 100644 --- a/docs/markdown/snippets/override_dependency.md +++ b/docs/markdown/snippets/override_dependency.md @@ -57,3 +57,9 @@ If the subproject does `dependency('foo')` but the main project wants to provide its own implementation of `foo`, it can for example call `meson.override_dependency('foo', declare_dependency(...))` before configuring the subproject. + +## Simplified `dependency()` fallback + +In the case a subproject `foo` calls `meson.override_dependency('foo-2.0', foo_dep)`, +the parent project can omit the dependency variable name in fallback keyword +argument: `dependency('foo-2.0', fallback : 'foo')`. diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 4b978c47a..420114149 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -3280,10 +3280,24 @@ external dependencies (including libraries) must go to "dependencies".''') def notfound_dependency(self): return DependencyHolder(NotFoundDependency(self.environment), self.subproject) - def get_subproject_dep(self, display_name, dirname, varname, kwargs): + def get_subproject_dep(self, name, display_name, dirname, varname, kwargs): + required = kwargs.get('required', True) + wanted = mesonlib.stringlistify(kwargs.get('version', [])) + subproj_path = os.path.join(self.subproject_dir, dirname) dep = self.notfound_dependency() try: subproject = self.subprojects[dirname] + if varname is None: + # Assuming the subproject overriden the dependency we want + _, cached_dep = self._find_cached_dep(name, kwargs) + if cached_dep: + if required and not cached_dep.found(): + m = 'Dependency {!r} is not satisfied' + raise DependencyException(m.format(display_name)) + return DependencyHolder(cached_dep, self.subproject) + else: + m = 'Subproject {} did not override dependency {}' + raise DependencyException(m.format(subproj_path, display_name)) if subproject.found(): dep = self.subprojects[dirname].get_variable_method([varname], {}) except InvalidArguments: @@ -3293,10 +3307,6 @@ external dependencies (including libraries) must go to "dependencies".''') raise InvalidCode('Fetched variable {!r} in the subproject {!r} is ' 'not a dependency object.'.format(varname, dirname)) - required = kwargs.get('required', True) - wanted = mesonlib.stringlistify(kwargs.get('version', [])) - subproj_path = os.path.join(self.subproject_dir, dirname) - if not dep.found(): if required: raise DependencyException('Could not find dependency {} in subproject {}' @@ -3400,7 +3410,7 @@ external dependencies (including libraries) must go to "dependencies".''') if has_fallback: dirname, varname = self.get_subproject_infos(kwargs) if dirname in self.subprojects: - return self.get_subproject_dep(name, dirname, varname, kwargs) + return self.get_subproject_dep(name, display_name, dirname, varname, kwargs) wrap_mode = self.coredata.get_builtin_option('wrap_mode') forcefallback = wrap_mode == WrapMode.forcefallback and has_fallback @@ -3420,7 +3430,7 @@ external dependencies (including libraries) must go to "dependencies".''') return DependencyHolder(dep, self.subproject) if has_fallback: - return self.dependency_fallback(display_name, kwargs) + return self.dependency_fallback(name, display_name, kwargs) return self.notfound_dependency() @@ -3448,13 +3458,15 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.warning(*message, location=self.current_node) def get_subproject_infos(self, kwargs): - fbinfo = kwargs['fallback'] - check_stringlist(fbinfo) - if len(fbinfo) != 2: - raise InterpreterException('Fallback info must have exactly two items.') + fbinfo = mesonlib.stringlistify(kwargs['fallback']) + if len(fbinfo) == 1: + FeatureNew('Fallback without variable name', '0.53.0').use(self.subproject) + return fbinfo[0], None + elif len(fbinfo) != 2: + raise InterpreterException('Fallback info must have one or two items.') return fbinfo - def dependency_fallback(self, display_name, kwargs): + def dependency_fallback(self, name, display_name, kwargs): required = kwargs.get('required', True) if self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback: mlog.log('Not looking for a fallback subproject for the dependency', @@ -3476,7 +3488,7 @@ external dependencies (including libraries) must go to "dependencies".''') 'required': required, } self.do_subproject(dirname, 'meson', sp_kwargs) - return self.get_subproject_dep(display_name, dirname, varname, kwargs) + return self.get_subproject_dep(name, display_name, dirname, varname, kwargs) @FeatureNewKwargs('executable', '0.42.0', ['implib']) @permittedKwargs(permitted_kwargs['executable']) diff --git a/run_unittests.py b/run_unittests.py index ee5a7131d..8479e3491 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -3981,10 +3981,16 @@ recommended as it is not supported on some platforms''') 'descriptive_name': 'sub', 'name': 'sub', 'version': '1.0' - } + }, + { + 'descriptive_name': 'sub-novar', + 'name': 'sub_novar', + 'version': '1.0', + }, ] } - self.assertDictEqual(res, expected) + res['subprojects'] = sorted(res['subprojects'], key=lambda i: i['name']) + self.assertDictEqual(expected, res) def test_introspection_target_subproject(self): testdir = os.path.join(self.common_test_dir, '45 subproject') diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index fc54db18f..3004f570b 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -17,3 +17,7 @@ assert(not d.found(), 'version should not match') dependency('sub2', required : false) d = dependency('sub2', fallback: ['sub', 'libSub']) assert(d.found(), 'Should fallback even if a previous call returned not-found') + +# Verify we can get a fallback dependency without specifying the variable name, +# because the subproject overridden 'sub-novar'. +dependency('sub-novar', fallback : 'sub_novar') diff --git a/test cases/common/102 subproject subdir/subprojects/sub_novar/meson.build b/test cases/common/102 subproject subdir/subprojects/sub_novar/meson.build new file mode 100644 index 000000000..0126b5fba --- /dev/null +++ b/test cases/common/102 subproject subdir/subprojects/sub_novar/meson.build @@ -0,0 +1,3 @@ +project('sub-novar', 'c', version : '1.0') + +meson.override_dependency('sub-novar', declare_dependency()) -- cgit v1.2.3 From 8edc6d655d6069dd5e6e7b531701086d774d7529 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Mon, 25 Nov 2019 20:16:54 -0500 Subject: Improve logged messages for overriden dependencies --- docs/markdown/Reference-manual.md | 5 ++++- mesonbuild/build.py | 6 ++++++ mesonbuild/interpreter.py | 28 +++++++++++++++++----------- 3 files changed, 27 insertions(+), 12 deletions(-) (limited to 'docs/markdown/Reference-manual.md') diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index b4d233342..9bb791117 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1839,7 +1839,10 @@ the following methods. 0.54.0)*](Release-notes-for-0.54.0.md#override-dependency) specifies that whenever `dependency(name, ...)` is used, Meson should not look it up on the system but instead return `dep_object`, which may either be - the result of `dependency()` or `declare_dependency()`. + the result of `dependency()` or `declare_dependency()`. It takes optional + `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)`. - `project_version()` returns the version string specified in `project` function call. diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 33820b4b2..2b5c0ea59 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -106,6 +106,12 @@ def get_target_macos_dylib_install_name(ld) -> str: class InvalidArguments(MesonException): pass +class DependencyOverride: + def __init__(self, dep, node, explicit=True): + self.dep = dep + self.node = node + self.explicit = explicit + class Build: """A class that holds the status of one build including all dependencies and so on. diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b3789c3b1..c247e6303 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2022,17 +2022,20 @@ class MesonMain(InterpreterObject): name = args[0] dep = args[1] if not isinstance(name, str) or not name: - raise InterpreterException('First argument must be not empty string') + raise InterpreterException('First argument must be a string and cannot be empty') if hasattr(dep, 'held_object'): dep = dep.held_object if not isinstance(dep, dependencies.Dependency): raise InterpreterException('Second argument must be a dependency object') identifier = dependencies.get_dep_identifier(name, kwargs) for_machine = self.interpreter.machine_from_native_kwarg(kwargs) - if identifier in self.build.dependency_overrides[for_machine]: - raise InterpreterException('Tried to override dependency "%s" which has already been overridden.' - % name) - self.build.dependency_overrides[for_machine][identifier] = dep + override = self.build.dependency_overrides[for_machine].get(identifier) + if override: + 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)) + self.build.dependency_overrides[for_machine][identifier] = \ + build.DependencyOverride(dep, self.interpreter.current_node) @noPosargs @permittedKwargs({}) @@ -3242,14 +3245,16 @@ external dependencies (including libraries) must go to "dependencies".''') identifier = dependencies.get_dep_identifier(name, kwargs) wanted_vers = mesonlib.stringlistify(kwargs.get('version', [])) - cached_dep = self.build.dependency_overrides[for_machine].get(identifier) - if cached_dep: + override = self.build.dependency_overrides[for_machine].get(identifier) + if override: + info = [mlog.blue('(overridden)' if override.explicit else '(cached)')] + cached_dep = override.dep # We don't implicitly override not-found dependencies, but user could # have explicitly called meson.override_dependency() with a not-found # dep. if not cached_dep.found(): mlog.log('Dependency', mlog.bold(name), - 'found:', mlog.red('NO'), mlog.blue('(cached)')) + 'found:', mlog.red('NO'), *info) return identifier, cached_dep found_vers = cached_dep.get_version() if not self.check_version(wanted_vers, found_vers): @@ -3257,9 +3262,10 @@ external dependencies (including libraries) must go to "dependencies".''') 'found:', mlog.red('NO'), 'found', mlog.normal_cyan(found_vers), 'but need:', mlog.bold(', '.join(["'{}'".format(e) for e in wanted_vers])), - mlog.blue('(cached)')) + *info) return identifier, NotFoundDependency(self.environment) else: + info = [mlog.blue('(cached)')] cached_dep = self.coredata.deps[for_machine].get(identifier) if cached_dep: found_vers = cached_dep.get_version() @@ -3267,7 +3273,6 @@ external dependencies (including libraries) must go to "dependencies".''') return identifier, None if cached_dep: - info = [mlog.blue('(cached)')] if found_vers: info = [mlog.normal_cyan(found_vers), *info] mlog.log('Dependency', mlog.bold(name), @@ -3383,7 +3388,8 @@ external dependencies (including libraries) must go to "dependencies".''') for_machine = self.machine_from_native_kwarg(kwargs) identifier = dependencies.get_dep_identifier(name, kwargs) if identifier not in self.build.dependency_overrides[for_machine]: - self.build.dependency_overrides[for_machine][identifier] = d.held_object + self.build.dependency_overrides[for_machine][identifier] = \ + build.DependencyOverride(d.held_object, node, explicit=False) return d def dependency_impl(self, name, display_name, kwargs): -- cgit v1.2.3