diff options
author | Xavier Claessens <xavier.claessens@collabora.com> | 2018-12-19 20:56:07 -0500 |
---|---|---|
committer | Xavier Claessens <xavier.claessens@collabora.com> | 2020-03-06 15:25:46 -0500 |
commit | 2fdedc4d0fc73c509669bf9f89863017e0f0989b (patch) | |
tree | fcb933a45797f7a1a1ef4516ba709dfd0888f781 | |
parent | 74769617907f571d2099001d3a7443e23b4f6cda (diff) | |
download | meson-2fdedc4d0fc73c509669bf9f89863017e0f0989b.zip meson-2fdedc4d0fc73c509669bf9f89863017e0f0989b.tar.gz meson-2fdedc4d0fc73c509669bf9f89863017e0f0989b.tar.bz2 |
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.
-rw-r--r-- | docs/markdown/Reference-manual.md | 8 | ||||
-rw-r--r-- | docs/markdown/snippets/override_dependency.md | 59 | ||||
-rw-r--r-- | mesonbuild/build.py | 1 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 71 | ||||
-rwxr-xr-x | run_unittests.py | 4 | ||||
-rw-r--r-- | test cases/common/102 subproject subdir/meson.build | 13 | ||||
-rw-r--r-- | test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build | 1 | ||||
-rw-r--r-- | test cases/common/102 subproject subdir/subprojects/sub/meson.build | 2 | ||||
-rw-r--r-- | test cases/linuxlike/5 dependency versions/meson.build | 19 |
9 files changed, 151 insertions, 27 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index e43ef57..09d54be 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. diff --git a/docs/markdown/snippets/override_dependency.md b/docs/markdown/snippets/override_dependency.md new file mode 100644 index 0000000..875eef8 --- /dev/null +++ b/docs/markdown/snippets/override_dependency.md @@ -0,0 +1,59 @@ +## `dependency()` consistency + +The first time a dependency is found, using `dependency('foo', ...)`, the return +value is now cached. Any subsequent call will return the same value as long as +version requested match, otherwise not-found dependency is returned. This means +that if a system dependency is first found, it won't fallback to a subproject +in a subsequent call any more and will rather return not-found instead if the +system version does not match. Similarly, if the first call returns the subproject +fallback dependency, it will also return the subproject dependency in a subsequent +call even if no fallback is provided. + +For example, if the system has `foo` version 1.0: +```meson +# d2 is set to foo_dep and not the system dependency, even without fallback argument. +d1 = dependency('foo', version : '>=2.0', required : false, + fallback : ['foo', 'foo_dep']) +d2 = dependency('foo', version : '>=1.0', required : false) +``` +```meson +# d2 is not-found because the first call returned the system dependency, but its version is too old for 2nd call. +d1 = dependency('foo', version : '>=1.0', required : false) +d2 = dependency('foo', version : '>=2.0', required : false, + fallback : ['foo', 'foo_dep']) +``` + +## Override `dependency()` + +It is now possible to override the result of `dependency()` to point +to any dependency object you want. The overriding is global and applies to +every subproject from there on. + +For example, this subproject provides 2 libraries with version 2.0: + +```meson +project(..., version : '2.0') + +libfoo = library('foo', ...) +foo_dep = declare_dependency(link_with : libfoo) +meson.override_dependency('foo', foo_dep) + +libbar = library('bar', ...) +bar_dep = declare_dependency(link_with : libbar) +meson.override_dependency('bar', bar_dep) +``` + +Assuming the system has `foo` and `bar` 1.0 installed, and master project does this: +```meson +foo_dep = dependency('foo', version : '>=2.0', fallback : ['foo', 'foo_dep']) +bar_dep = dependency('bar') +``` + +This used to mix system 1.0 version and subproject 2.0 dependencies, but thanks +to the override `bar_dep` is now set to the subproject's version instead. + +Another case this can be useful is to force a subproject to use a specific dependency. +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. diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 48d21b1..33820b4 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -141,6 +141,7 @@ class Build: self.test_setup_default_name = None self.find_overrides = {} self.searched_programs = set() # The list of all programs that have been searched for. + self.dependency_overrides = PerMachine({}, {}) def copy(self): other = Build(self.environment) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 9e88975..4b978c4 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -1857,6 +1857,7 @@ class MesonMain(InterpreterObject): 'add_postconf_script': self.add_postconf_script_method, 'add_dist_script': self.add_dist_script_method, 'install_dependency_manifest': self.install_dependency_manifest_method, + 'override_dependency': self.override_dependency_method, 'override_find_program': self.override_find_program_method, 'project_version': self.project_version_method, 'project_license': self.project_license_method, @@ -2013,6 +2014,26 @@ class MesonMain(InterpreterObject): raise InterpreterException('Second argument must be an external program or executable.') self.interpreter.add_find_program_override(name, exe) + @FeatureNew('meson.override_dependency', '0.53.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: + raise InterpreterException('First argument must be not empty string') + 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 + @noPosargs @permittedKwargs({}) def project_version_method(self, args, kwargs): @@ -3218,30 +3239,38 @@ external dependencies (including libraries) must go to "dependencies".''') # Check if we want this as a build-time / build machine or runt-time / # host machine dep. for_machine = self.machine_from_native_kwarg(kwargs) - identifier = dependencies.get_dep_identifier(name, kwargs) - cached_dep = self.coredata.deps[for_machine].get(identifier) - if cached_dep: - if not cached_dep.found(): - mlog.log('Dependency', mlog.bold(name), - 'found:', mlog.red('NO'), mlog.blue('(cached)')) - return identifier, cached_dep + wanted_vers = mesonlib.stringlistify(kwargs.get('version', [])) - # Verify the cached dep version match - wanted_vers = mesonlib.stringlistify(kwargs.get('version', [])) + cached_dep = self.build.dependency_overrides[for_machine].get(identifier) + if cached_dep: found_vers = cached_dep.get_version() - if not wanted_vers or mesonlib.version_compare_many(found_vers, wanted_vers)[0]: - info = [mlog.blue('(cached)')] - if found_vers: - info = [mlog.normal_cyan(found_vers), *info] + if not self.check_version(wanted_vers, found_vers): mlog.log('Dependency', mlog.bold(name), - 'found:', mlog.green('YES'), *info) - return identifier, cached_dep + '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)')) + return identifier, NotFoundDependency(self.environment) + else: + cached_dep = self.coredata.deps[for_machine].get(identifier) + if cached_dep: + found_vers = cached_dep.get_version() + if not self.check_version(wanted_vers, found_vers): + 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), + 'found:', mlog.green('YES'), *info) + return identifier, cached_dep return identifier, None @staticmethod - def check_subproject_version(wanted, found): + def check_version(wanted, found): if not wanted: return True if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]: @@ -3278,7 +3307,7 @@ external dependencies (including libraries) must go to "dependencies".''') return dep found = dep.held_object.get_version() - if not self.check_subproject_version(wanted, found): + if not self.check_version(wanted, found): if required: raise DependencyException('Version {} of subproject dependency {} already ' 'cached, requested incompatible version {} for ' @@ -3330,6 +3359,14 @@ external dependencies (including libraries) must go to "dependencies".''') raise if not d.found() and not_found_message: self.message_impl([not_found_message]) + self.message_impl([not_found_message]) + # Override this dependency to have consistent results in subsequent + # dependency lookups. + if name and d.found(): + 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 return d def dependency_impl(self, name, display_name, kwargs): diff --git a/run_unittests.py b/run_unittests.py index 658da37..ee5a713 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -3980,7 +3980,7 @@ recommended as it is not supported on some platforms''') { 'descriptive_name': 'sub', 'name': 'sub', - 'version': 'undefined' + 'version': '1.0' } ] } @@ -4555,7 +4555,7 @@ class FailureTests(BasePlatformTests): raise unittest.SkipTest('zlib not found with pkg-config') a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"), ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"), - ("dependency('zlib', version : 1)", "[Vv]ersion.*string or list"), + ("dependency('zlib', version : 1)", "Item must be a list or one of <class 'str'>"), ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"), ("dependency('zlib', method : 1)", "[Mm]ethod.*string"), ("dependency('zlibfail')", self.dnf),) diff --git a/test cases/common/102 subproject subdir/meson.build b/test cases/common/102 subproject subdir/meson.build index ec9fad1..fc54db1 100644 --- a/test cases/common/102 subproject subdir/meson.build +++ b/test cases/common/102 subproject subdir/meson.build @@ -4,3 +4,16 @@ libSub = dependency('sub', fallback: ['sub', 'libSub']) exe = executable('prog', 'prog.c', dependencies: libSub) test('subproject subdir', exe) + +# Verify the subproject has placed dependency override. +dependency('sub-1.0') + +# Verify we can now take 'sub' dependency without fallback, but only version 1.0. +dependency('sub') +d = dependency('sub', version : '>=2.0', required : false) +assert(not d.found(), 'version should not match') + +# Verify that not-found does not get cached, we can still fallback afterward. +dependency('sub2', required : false) +d = dependency('sub2', fallback: ['sub', 'libSub']) +assert(d.found(), 'Should fallback even if a previous call returned not-found') diff --git a/test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build b/test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build index 731d22b..53233ab 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build +++ b/test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build @@ -1,2 +1,3 @@ lib = static_library('sub', 'sub.c') libSub = declare_dependency(include_directories: include_directories('.'), link_with: lib) +meson.override_dependency('sub-1.0', libSub) diff --git a/test cases/common/102 subproject subdir/subprojects/sub/meson.build b/test cases/common/102 subproject subdir/subprojects/sub/meson.build index bf69c25..d8c4dce 100644 --- a/test cases/common/102 subproject subdir/subprojects/sub/meson.build +++ b/test cases/common/102 subproject subdir/subprojects/sub/meson.build @@ -1,2 +1,2 @@ -project('sub', 'c') +project('sub', 'c', version : '1.0') subdir('lib') diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build index 087db5f..cb58a64 100644 --- a/test cases/linuxlike/5 dependency versions/meson.build +++ b/test cases/linuxlike/5 dependency versions/meson.build @@ -38,32 +38,32 @@ somelibver = dependency('somelib', fallback : ['somelibnover', 'some_dep']) assert(somelibver.type_name() == 'internal', 'somelibver should be of type "internal", not ' + somelibver.type_name()) # Find an internal dependency again with the same name and a specific version -somelib = dependency('somelib', +somelib = dependency('somelib2', version : '== 0.1', fallback : ['somelib', 'some_dep']) # Find an internal dependency again even if required = false -somelib_reqfalse = dependency('somelib', +somelib_reqfalse = dependency('somelib3', required: false, fallback : ['somelib', 'some_dep']) assert(somelib_reqfalse.found(), 'somelib should have been found') # Find an internal dependency again with the same name and incompatible version -somelibver = dependency('somelib', +somelibver = dependency('somelib4', version : '>= 0.3', fallback : ['somelibver', 'some_dep']) # Find an internal dependency again with impossible multi-version -somelibver = dependency('somelib', +somelibver = dependency('somelib5', version : ['>= 0.3', '<0.3'], required : false, fallback : ['somelibver', 'some_dep']) assert(not somelibver.found(), 'Dependency should not be found') # Find somelib again, but with a fallback that will fail because subproject does not exist -somelibfail = dependency('somelib', +somelibfail = dependency('somelib6', version : '>= 0.2', required : false, fallback : ['somelibfail', 'some_dep']) assert(somelibfail.found() == false, 'somelibfail found via wrong fallback') # Find somelib again, but with a fallback that will fail because dependency does not exist -somefail_dep = dependency('somelib', +somefail_dep = dependency('somelib7', version : '>= 0.2', required : false, fallback : ['somelib', 'somefail_dep']) @@ -79,6 +79,13 @@ fakezlib_dep = dependency('fakezlib', fallback : ['somelib', 'fakezlib_dep']) assert(fakezlib_dep.type_name() == 'internal', 'fakezlib_dep should be of type "internal", not ' + fakezlib_dep.type_name()) +# Verify that once we got a system dependency, we won't fallback if a newer +# version is requested. +d = dependency('zlib', version: '>= 999', + fallback : ['somelib', 'some_dep'], + required: false) +assert(not d.found(), 'version should not match and it should not fallback') + # Check that you can find a dependency by not specifying a version after not # finding it by specifying a version. We add `static: true` here so that the # previously cached zlib dependencies don't get checked. |