aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Reference-manual.md8
-rw-r--r--docs/markdown/snippets/override_dependency.md59
-rw-r--r--mesonbuild/build.py1
-rw-r--r--mesonbuild/interpreter.py71
-rwxr-xr-xrun_unittests.py4
-rw-r--r--test cases/common/102 subproject subdir/meson.build13
-rw-r--r--test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build1
-rw-r--r--test cases/common/102 subproject subdir/subprojects/sub/meson.build2
-rw-r--r--test cases/linuxlike/5 dependency versions/meson.build19
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.