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.
pull/6203/head
Xavier Claessens 6 years ago
parent 7476961790
commit 2fdedc4d0f
  1. 8
      docs/markdown/Reference-manual.md
  2. 59
      docs/markdown/snippets/override_dependency.md
  3. 1
      mesonbuild/build.py
  4. 71
      mesonbuild/interpreter.py
  5. 4
      run_unittests.py
  6. 13
      test cases/common/102 subproject subdir/meson.build
  7. 1
      test cases/common/102 subproject subdir/subprojects/sub/lib/meson.build
  8. 2
      test cases/common/102 subproject subdir/subprojects/sub/meson.build
  9. 19
      test cases/linuxlike/5 dependency versions/meson.build

@ -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.

@ -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.

@ -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)

@ -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):

@ -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),)

@ -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')

@ -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)

@ -1,2 +1,2 @@
project('sub', 'c')
project('sub', 'c', version : '1.0')
subdir('lib')

@ -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.

Loading…
Cancel
Save