interpreter: Extract dependency() logic into its own helper class

The dependency lookup is a lot of complex code. This refactor it all
into a single file/class outside of interpreter main class. This new
design allows adding more fallbacks candidates in the future (e.g. using
cc.find_library()) but does not yet add any extra API.
pull/8892/head
Xavier Claessens 3 years ago committed by Xavier Claessens
parent 3970f269fd
commit b6d754a40c
  1. 346
      mesonbuild/interpreter/dependencyfallbacks.py
  2. 318
      mesonbuild/interpreter/interpreter.py
  3. 14
      mesonbuild/wrap/wrap.py
  4. 2
      run_unittests.py
  5. 13
      test cases/common/88 dep fallback/meson.build
  6. 2
      test cases/common/88 dep fallback/subprojects/boblib/meson.build
  7. 7
      test cases/failing/100 fallback consistency/test.json
  8. 3
      test cases/failing/106 fallback consistency/meson.build
  9. 7
      test cases/failing/106 fallback consistency/test.json
  10. 2
      test cases/failing/36 pkgconfig dependency impossible conditions/test.json
  11. 2
      test cases/failing/66 dependency not-found and required/test.json
  12. 2
      test cases/failing/67 subproj different versions/test.json
  13. 2
      test cases/failing/84 gtest dependency with version/test.json
  14. 2
      test cases/failing/88 subproj not-found dep/test.json
  15. 7
      test cases/failing/98 fallback consistency/test.json
  16. 0
      test cases/warning/3 fallback consistency/meson.build
  17. 0
      test cases/warning/3 fallback consistency/subprojects/sub/meson.build
  18. 7
      test cases/warning/3 fallback consistency/test.json
  19. 2
      test cases/warning/4 fallback consistency/meson.build
  20. 0
      test cases/warning/4 fallback consistency/subprojects/sub/meson.build
  21. 7
      test cases/warning/4 fallback consistency/test.json
  22. 4
      test cases/warning/5 fallback consistency/meson.build
  23. 0
      test cases/warning/5 fallback consistency/subprojects/foo.wrap
  24. 0
      test cases/warning/5 fallback consistency/subprojects/foo/meson.build
  25. 7
      test cases/warning/5 fallback consistency/test.json

@ -0,0 +1,346 @@
from .interpreterobjects import DependencyHolder, SubprojectHolder, extract_required_kwarg
from .. import mlog
from .. import dependencies
from .. import build
from ..wrap import WrapMode
from ..mesonlib import OptionKey, extract_as_list, stringlistify, version_compare_many
from ..dependencies import DependencyException, NotFoundDependency
from ..interpreterbase import (InterpreterObject, FeatureNew,
InterpreterException, InvalidArguments,
TYPE_nkwargs, TYPE_nvar)
import typing as T
if T.TYPE_CHECKING:
from .interpreter import Interpreter
class DependencyFallbacksHolder(InterpreterObject):
def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None) -> None:
super().__init__()
self.interpreter = interpreter
self.subproject = interpreter.subproject
self.coredata = interpreter.coredata
self.build = interpreter.build
self.environment = interpreter.environment
self.wrap_resolver = interpreter.environment.wrap_resolver
self.allow_fallback = allow_fallback
self.subproject_name = None
self.subproject_varname = None
self.subproject_kwargs = None
self.names: T.List[str] = []
for name in names:
if not name:
raise InterpreterException('dependency_fallbacks empty name \'\' is not allowed')
if '<' in name or '>' in name or '=' in name:
raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
'version\n requirements use the \'version\' keyword argument instead.')
if name in self.names:
raise InterpreterException('dependency_fallbacks name {name!r} is duplicated')
self.names.append(name)
def set_fallback(self, fbinfo: T.Optional[T.Union[T.List[str], str]], default_options: T.Optional[T.List[str]] = None) -> None:
# Legacy: This converts dependency()'s fallback and default_options kwargs.
if fbinfo is None:
if default_options is not None:
mlog.warning('The "default_options" keyword argument does nothing without a fallback subproject.',
location=self.interpreter.current_node)
return
fbinfo = stringlistify(fbinfo)
if len(fbinfo) == 1:
FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject)
subp_name, varname = fbinfo[0], None
elif len(fbinfo) == 2:
subp_name, varname = fbinfo
else:
raise InterpreterException('Fallback info must have one or two items.')
kwargs = {'default_options': default_options or []}
self._subproject_impl(subp_name, varname, kwargs)
def _subproject_impl(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> None:
if not varname:
# If no variable name is specified, check if the wrap file has one.
# If the wrap file has a variable name, better use it because the
# subproject most probably is not using meson.override_dependency().
for name in self.names:
varname = self.wrap_resolver.get_varname(subp_name, name)
if varname:
break
assert self.subproject_name is None
self.subproject_name = subp_name
self.subproject_varname = varname
self.subproject_kwargs = kwargs
def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]:
name = func_args[0]
cached_dep = self._get_cached_dep(name, kwargs)
if cached_dep:
self._verify_fallback_consistency(cached_dep)
return cached_dep
def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]:
# Note that there is no df.dependency() method, this is called for names
# given as positional arguments to dependency_fallbacks(name1, ...).
# We use kwargs from the dependency() function, for things like version,
# module, etc.
name = func_args[0]
self._handle_featurenew_dependencies(name)
dep = dependencies.find_external_dependency(name, self.environment, kwargs)
if dep.found():
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = dependencies.get_dep_identifier(name, kwargs)
self.coredata.deps[for_machine].put(identifier, dep)
return DependencyHolder(dep, self.subproject)
return None
def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]:
subp_name = func_args[0]
varname = self.subproject_varname
if subp_name and self._get_subproject(subp_name):
return self._get_subproject_dep(subp_name, varname, kwargs)
return None
def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]:
if self.nofallback:
mlog.log('Not looking for a fallback subproject for the dependency',
mlog.bold(self.display_name), 'because:\nUse of fallback dependencies is disabled.')
return None
if self.forcefallback:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(self.display_name), 'because:\nUse of fallback dependencies is forced.')
else:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(self.display_name))
# Configure the subproject
subp_name = self.subproject_name
varname = self.subproject_varname
self.interpreter.do_subproject(subp_name, 'meson', func_kwargs)
return self._get_subproject_dep(subp_name, varname, kwargs)
def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]:
sub = self.interpreter.subprojects.get(subp_name)
if sub and sub.found():
return sub
return None
def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]:
# Verify the subproject is found
subproject = self._get_subproject(subp_name)
if not subproject:
mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
mlog.bold(subp_name), 'found:', mlog.red('NO'),
mlog.blue('(subproject failed to configure)'))
return None
# The subproject has been configured. If for any reason the dependency
# cannot be found in this subproject we have to return not-found object
# instead of None, because we don't want to continue the lookup on the
# system.
# Check if the subproject overridden at least one of the names we got.
cached_dep = None
for name in self.names:
cached_dep = self._get_cached_dep(name, kwargs)
if cached_dep:
break
# If we have cached_dep we did all the checks and logging already in
# self._get_cached_dep().
if cached_dep:
self._verify_fallback_consistency(cached_dep)
return cached_dep
# Legacy: Use the variable name if provided instead of relying on the
# subproject to override one of our dependency names
if not varname:
mlog.warning(f'Subproject {subp_name!r} did not override {self.display_name!r} dependency and no variable name specified')
mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
return self._notfound_dependency()
var_dep = self._get_subproject_variable(subproject, varname) or self._notfound_dependency()
if not var_dep.found():
mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
return var_dep
wanted = stringlistify(kwargs.get('version', []))
found = var_dep.held_object.get_version()
if not self._check_version(wanted, found):
mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted])))
return self._notfound_dependency()
mlog.log('Dependency', mlog.bold(self.display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.green('YES'),
mlog.normal_cyan(found) if found else None)
return var_dep
def _get_cached_dep(self, name: str, kwargs: TYPE_nkwargs) -> T.Optional[DependencyHolder]:
# Unlike other methods, this one returns not-found dependency instead
# of None in the case the dependency is cached as not-found, or if cached
# version does not match. In that case we don't want to continue with
# other candidates.
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = dependencies.get_dep_identifier(name, kwargs)
wanted_vers = stringlistify(kwargs.get('version', []))
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(self.display_name),
'found:', mlog.red('NO'), *info)
return DependencyHolder(cached_dep, self.subproject)
else:
info = [mlog.blue('(cached)')]
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):
mlog.log('Dependency', mlog.bold(name),
'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found_vers), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted_vers])),
*info)
return self._notfound_dependency()
if found_vers:
info = [mlog.normal_cyan(found_vers), *info]
mlog.log('Dependency', mlog.bold(self.display_name),
'found:', mlog.green('YES'), *info)
return DependencyHolder(cached_dep, self.subproject)
return None
def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -> T.Optional[DependencyHolder]:
var_dep = subproject.held_object.variables.get(varname)
if not isinstance(var_dep, DependencyHolder):
mlog.warning(f'Variable {varname!r} in the subproject {subproject.subdir!r} is',
'not found' if var_dep is None else 'not a dependency object')
return None
return var_dep
def _verify_fallback_consistency(self, cached_dep: DependencyHolder):
subp_name = self.subproject_name
varname = self.subproject_varname
subproject = self._get_subproject(subp_name)
if subproject and varname:
var_dep = self._get_subproject_variable(subproject, varname)
if var_dep and cached_dep.found() and var_dep.held_object != cached_dep.held_object:
mlog.warning(f'Inconsistency: Subproject has overridden the dependency with another variable than {varname!r}')
def _handle_featurenew_dependencies(self, name: str) -> None:
'Do a feature check on dependencies used by this subproject'
if name == 'mpi':
FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject)
elif name == 'pcap':
FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject)
elif name == 'vulkan':
FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject)
elif name == 'libwmf':
FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject)
elif name == 'openmp':
FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject)
def _notfound_dependency(self) -> DependencyHolder:
return DependencyHolder(NotFoundDependency(self.environment), self.subproject)
@staticmethod
def _check_version(wanted: T.Optional[str], found: str) -> bool:
if not wanted:
return True
if found == 'undefined' or not version_compare_many(found, wanted)[0]:
return False
return True
def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[DependencyHolder]], TYPE_nvar, TYPE_nkwargs]]:
candidates = []
# 1. check if any of the names is cached already.
for name in self.names:
candidates.append((self._do_dependency_cache, [name], {}))
# 2. check if the subproject fallback has already been configured.
if self.subproject_name:
candidates.append((self._do_existing_subproject, [self.subproject_name], self.subproject_kwargs))
# 3. check external dependency if we are not forced to use subproject
if not self.forcefallback or not self.subproject_name:
for name in self.names:
candidates.append((self._do_dependency, [name], {}))
# 4. configure the subproject
if self.subproject_name:
candidates.append((self._do_subproject, [self.subproject_name], self.subproject_kwargs))
return candidates
def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> DependencyHolder:
self.display_name = self.names[0] if self.names else '(anonymous)'
mods = extract_as_list(kwargs, 'modules')
if mods:
self.display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
mlog.log('Dependency', mlog.bold(self.display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
return self._notfound_dependency()
# Check if usage of the subproject fallback is forced
wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for'))
self.nofallback = wrap_mode == WrapMode.nofallback
self.forcefallback = (force_fallback or
wrap_mode == WrapMode.forcefallback or
any(name in force_fallback_for for name in self.names) or
self.subproject_name in force_fallback_for)
# Add an implicit subproject fallback if none has been set explicitly,
# unless implicit fallback is not allowed.
# Legacy: self.allow_fallback can be None when that kwarg is not defined
# in dependency('name'). In that case we don't want to use implicit
# fallback when required is false because user will typically fallback
# manually using cc.find_library() for example.
if not self.subproject_name and self.allow_fallback is not False:
for name in self.names:
subp_name, varname = self.wrap_resolver.find_dep_provider(name)
if subp_name:
self.forcefallback |= subp_name in force_fallback_for
if self.forcefallback or self.allow_fallback is True or required or self._get_subproject(subp_name):
self._subproject_impl(subp_name, varname, {})
break
candidates = self._get_candidates()
# writing just "dependency('')" is an error, because it can only fail
if not candidates and required:
raise InvalidArguments('Dependency is required but has no candidates.')
# Try all candidates, only the last one is really required.
last = len(candidates) - 1
for i, item in enumerate(candidates):
func, func_args, func_kwargs = item
func_kwargs['required'] = required and (i == last)
kwargs['required'] = required and (i == last)
dep = func(kwargs, func_args, func_kwargs)
if dep and dep.held_object.found():
# Override this dependency to have consistent results in subsequent
# dependency lookups.
for name in self.names:
for_machine = self.interpreter.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] = \
build.DependencyOverride(dep.held_object, self.interpreter.current_node, explicit=False)
return dep
elif required and (dep or i == last):
# This was the last candidate or the dependency has been cached
# as not-found, or cached dependency version does not match,
# otherwise func() would have returned None instead.
raise DependencyException(f'Dependency {self.display_name!r} is required but not found.')
elif dep:
# Same as above, but the dependency is not required.
return dep
return self._notfound_dependency()

@ -23,7 +23,7 @@ from ..wrap import wrap, WrapMode
from .. import mesonlib
from ..mesonlib import FileMode, MachineChoice, OptionKey, listify, extract_as_list, has_path_sep, unholder
from ..programs import ExternalProgram, NonExistingExternalProgram
from ..dependencies import Dependency, NotFoundDependency, DependencyException
from ..dependencies import Dependency
from ..depfile import DepFile
from ..interpreterbase import ContainerTypeInfo, InterpreterBase, KwargInfo, typed_kwargs, typed_pos_args
from ..interpreterbase import noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
@ -47,6 +47,7 @@ from .interpreterobjects import (SubprojectHolder, MachineHolder, EnvironmentVar
BuildTargetHolder, DataHolder, JarHolder, Test, RunProcess,
ManHolder, GeneratorHolder, InstallDirHolder, extract_required_kwarg,
extract_search_dirs, MutableModuleObjectHolder)
from .dependencyfallbacks import DependencyFallbacksHolder
from pathlib import Path
import os
@ -501,11 +502,12 @@ class Interpreter(InterpreterBase):
continue
if len(di) == 1:
FeatureNew.single_use('stdlib without variable name', '0.56.0', self.subproject)
kwargs = {'fallback': di,
'native': for_machine is MachineChoice.BUILD,
kwargs = {'native': for_machine is MachineChoice.BUILD,
}
name = display_name = l + '_stdlib'
dep = self.dependency_impl(name, display_name, kwargs, force_fallback=True)
name = l + '_stdlib'
df = DependencyFallbacksHolder(self, [name])
df.set_fallback(di)
dep = df.lookup(kwargs, force_fallback=True)
self.build.stdlibs[for_machine][l] = dep
def import_module(self, modname):
@ -734,12 +736,6 @@ external dependencies (including libraries) must go to "dependencies".''')
self.coredata.initialized_subprojects.add(subp_name)
return sub
def get_subproject(self, subp_name):
sub = self.subprojects.get(subp_name)
if sub and sub.found():
return sub
return None
def do_subproject(self, subp_name: str, method: str, kwargs):
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
@ -1430,155 +1426,6 @@ external dependencies (including libraries) must go to "dependencies".''')
'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n'
)
def _find_cached_dep(self, name, display_name, kwargs):
# 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)
wanted_vers = mesonlib.stringlistify(kwargs.get('version', []))
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(display_name),
'found:', mlog.red('NO'), *info)
return identifier, cached_dep
found_vers = cached_dep.get_version()
if not self.check_version(wanted_vers, found_vers):
mlog.log('Dependency', mlog.bold(name),
'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found_vers), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted_vers])),
*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()
if not self.check_version(wanted_vers, found_vers):
return identifier, None
if cached_dep:
if found_vers:
info = [mlog.normal_cyan(found_vers), *info]
mlog.log('Dependency', mlog.bold(display_name),
'found:', mlog.green('YES'), *info)
return identifier, cached_dep
return identifier, None
@staticmethod
def check_version(wanted, found):
if not wanted:
return True
if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]:
return False
return True
def notfound_dependency(self):
return DependencyHolder(NotFoundDependency(self.environment), self.subproject)
def verify_fallback_consistency(self, subp_name, varname, cached_dep):
subi = self.get_subproject(subp_name)
if not cached_dep or not varname or not subi or not cached_dep.found():
return
dep = subi.get_variable_method([varname], {})
if dep.held_object != cached_dep:
m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}'
raise DependencyException(m.format(varname))
def get_subproject_dep(self, name, display_name, subp_name, varname, kwargs):
required = kwargs.get('required', True)
wanted = mesonlib.stringlistify(kwargs.get('version', []))
dep = self.notfound_dependency()
# Verify the subproject is found
subproject = self.subprojects.get(subp_name)
if not subproject or not subproject.found():
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
mlog.blue('(subproject failed to configure)'))
if required:
m = 'Subproject {} failed to configure for dependency {}'
raise DependencyException(m.format(subproject.subdir, display_name))
return dep
extra_info = []
try:
# Check if the subproject overridden the dependency
_, cached_dep = self._find_cached_dep(name, display_name, kwargs)
if cached_dep:
if varname:
self.verify_fallback_consistency(subp_name, varname, 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)
elif varname is None:
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
if required:
m = 'Subproject {} did not override dependency {}'
raise DependencyException(m.format(subproject.subdir, display_name))
return self.notfound_dependency()
else:
# The subproject did not override the dependency, but we know the
# variable name to take.
dep = subproject.get_variable_method([varname], {})
except InvalidArguments:
# This is raised by get_variable_method() if varname does no exist
# in the subproject. Just add the reason in the not-found message
# that will be printed later.
extra_info.append(mlog.blue(f'(Variable {varname!r} not found)'))
if not isinstance(dep, DependencyHolder):
raise InvalidCode('Fetched variable {!r} in the subproject {!r} is '
'not a dependency object.'.format(varname, subp_name))
if not dep.found():
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'), *extra_info)
if required:
raise DependencyException('Could not find dependency {} in subproject {}'
''.format(varname, subp_name))
return dep
found = dep.held_object.get_version()
if not self.check_version(wanted, found):
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted])))
if required:
raise DependencyException('Version {} of subproject dependency {} already '
'cached, requested incompatible version {} for '
'dep {}'.format(found, subp_name, wanted, display_name))
return self.notfound_dependency()
found = mlog.normal_cyan(found) if found else None
mlog.log('Dependency', mlog.bold(display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.green('YES'), found)
return dep
def _handle_featurenew_dependencies(self, name):
'Do a feature check on dependencies used by this subproject'
if name == 'mpi':
FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject)
elif name == 'pcap':
FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject)
elif name == 'vulkan':
FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject)
elif name == 'libwmf':
FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject)
elif name == 'openmp':
FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject)
# When adding kwargs, please check if they make sense in dependencies.get_dep_identifier()
@FeatureNewKwargs('dependency', '0.57.0', ['cmake_package_version'])
@FeatureNewKwargs('dependency', '0.56.0', ['allow_fallback'])
@ -1590,18 +1437,22 @@ external dependencies (including libraries) must go to "dependencies".''')
@FeatureNewKwargs('dependency', '0.38.0', ['default_options'])
@disablerIfNotFound
@permittedKwargs(permitted_dependency_kwargs)
@typed_pos_args('dependency', str)
def func_dependency(self, node, args, kwargs):
self.validate_arguments(args, 1, [str])
name = args[0]
display_name = name if name else '(anonymous)'
mods = extract_as_list(kwargs, 'modules')
if mods:
display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
# Replace '' by empty list of names
names = [args[0]] if args[0] else []
allow_fallback = kwargs.get('allow_fallback')
if allow_fallback is not None and not isinstance(allow_fallback, bool):
raise InvalidArguments('"allow_fallback" argument must be boolean')
fallback = kwargs.get('fallback')
default_options = kwargs.get('default_options')
df = DependencyFallbacksHolder(self, names, allow_fallback)
df.set_fallback(fallback, default_options)
not_found_message = kwargs.get('not_found_message', '')
if not isinstance(not_found_message, str):
raise InvalidArguments('The not_found_message must be a string.')
try:
d = self.dependency_impl(name, display_name, kwargs)
d = df.lookup(kwargs)
except Exception:
if not_found_message:
self.message_impl([not_found_message])
@ -1610,152 +1461,21 @@ external dependencies (including libraries) must go to "dependencies".''')
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] = \
build.DependencyOverride(d.held_object, node, explicit=False)
# Ensure the correct include type
if 'include_type' in kwargs:
wanted = kwargs['include_type']
actual = d.include_type_method([], {})
if wanted != actual:
mlog.debug(f'Current include type of {name} is {actual}. Converting to requested {wanted}')
mlog.debug(f'Current include type of {names[0]} is {actual}. Converting to requested {wanted}')
d = d.as_system_method([wanted], {})
return d
def dependency_impl(self, name, display_name, kwargs, force_fallback=False):
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
mlog.log('Dependency', mlog.bold(display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
return self.notfound_dependency()
fallback = kwargs.get('fallback', None)
allow_fallback = kwargs.get('allow_fallback', None)
if allow_fallback is not None:
if fallback is not None:
raise InvalidArguments('"fallback" and "allow_fallback" arguments are mutually exclusive')
if not isinstance(allow_fallback, bool):
raise InvalidArguments('"allow_fallback" argument must be boolean')
wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for'))
force_fallback |= (wrap_mode == WrapMode.forcefallback or
name in force_fallback_for)
# If "fallback" is absent, look for an implicit fallback.
if name and fallback is None and allow_fallback is not False:
# Add an implicit fallback if we have a wrap file or a directory with the same name,
# but only if this dependency is required. It is common to first check for a pkg-config,
# then fallback to use find_library() and only afterward check again the dependency
# with a fallback. If the fallback has already been configured then we have to use it
# even if the dependency is not required.
provider = self.environment.wrap_resolver.find_dep_provider(name)
if not provider and allow_fallback is True:
raise InvalidArguments('Fallback wrap or subproject not found for dependency \'%s\'' % name)
subp_name = mesonlib.listify(provider)[0]
force_fallback |= subp_name in force_fallback_for
if provider and (allow_fallback is True or required or self.get_subproject(subp_name) or force_fallback):
fallback = provider
if 'default_options' in kwargs and not fallback:
mlog.warning('The "default_options" keyword argument does nothing without a fallback subproject.',
location=self.current_node)
# writing just "dependency('')" is an error, because it can only fail
if name == '' and required and not fallback:
raise InvalidArguments('Dependency is both required and not-found')
if '<' in name or '>' in name or '=' in name:
raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
'version\n requirements use the \'version\' keyword argument instead.')
identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs)
if cached_dep:
if fallback:
subp_name, varname = self.get_subproject_infos(fallback)
self.verify_fallback_consistency(subp_name, varname, cached_dep)
if required and not cached_dep.found():
m = 'Dependency {!r} was already checked and was not found'
raise DependencyException(m.format(display_name))
return DependencyHolder(cached_dep, self.subproject)
if fallback:
# If the dependency has already been configured, possibly by
# a higher level project, try to use it first.
subp_name, varname = self.get_subproject_infos(fallback)
if self.get_subproject(subp_name):
return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs)
force_fallback |= subp_name in force_fallback_for
if name != '' and (not fallback or not force_fallback):
self._handle_featurenew_dependencies(name)
kwargs['required'] = required and not fallback
dep = dependencies.find_external_dependency(name, self.environment, kwargs)
kwargs['required'] = required
# Only store found-deps in the cache
# Never add fallback deps to self.coredata.deps since we
# cannot cache them. They must always be evaluated else
# we won't actually read all the build files.
if dep.found():
for_machine = self.machine_from_native_kwarg(kwargs)
self.coredata.deps[for_machine].put(identifier, dep)
return DependencyHolder(dep, self.subproject)
if fallback:
return self.dependency_fallback(name, display_name, fallback, kwargs)
return self.notfound_dependency()
@FeatureNew('disabler', '0.44.0')
@noKwargs
@noPosargs
def func_disabler(self, node, args, kwargs):
return Disabler()
def get_subproject_infos(self, fbinfo):
fbinfo = mesonlib.stringlistify(fbinfo)
if len(fbinfo) == 1:
FeatureNew.single_use('Fallback without variable name', '0.53.0', 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, name, display_name, fallback, kwargs):
subp_name, varname = self.get_subproject_infos(fallback)
required = kwargs.get('required', True)
# Explicitly listed fallback preferences for specific subprojects
# take precedence over wrap-mode
force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for'))
if name in force_fallback_for or subp_name in force_fallback_for:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(display_name), 'because:\nUse of fallback was forced for that specific subproject')
elif self.coredata.get_option(OptionKey('wrap_mode')) == WrapMode.nofallback:
mlog.log('Not looking for a fallback subproject for the dependency',
mlog.bold(display_name), 'because:\nUse of fallback '
'dependencies is disabled.')
if required:
m = 'Dependency {!r} not found and fallback is disabled'
raise DependencyException(m.format(display_name))
return self.notfound_dependency()
elif self.coredata.get_option(OptionKey('wrap_mode')) == WrapMode.forcefallback:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.')
else:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(display_name))
sp_kwargs = {
'default_options': kwargs.get('default_options', []),
'required': required,
}
self.do_subproject(subp_name, 'meson', sp_kwargs)
return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs)
@FeatureNewKwargs('executable', '0.42.0', ['implib'])
@FeatureNewKwargs('executable', '0.56.0', ['win_subsystem'])
@FeatureDeprecatedKwargs('executable', '0.56.0', ['gui_app'], extra_message="Use 'win_subsystem' instead.")

@ -248,19 +248,19 @@ class Resolver:
for k, v in other_resolver.provided_programs.items():
self.provided_programs.setdefault(k, v)
def find_dep_provider(self, packagename: str) -> T.Optional[T.Union[str, T.List[str]]]:
def find_dep_provider(self, packagename: str) -> T.Tuple[T.Optional[str], T.Optional[str]]:
# Python's ini parser converts all key values to lowercase.
# Thus the query name must also be in lower case.
packagename = packagename.lower()
# Return value is in the same format as fallback kwarg:
# ['subproject_name', 'variable_name'], or 'subproject_name'.
wrap = self.provided_deps.get(packagename)
if wrap:
dep_var = wrap.provided_deps.get(packagename)
if dep_var:
return [wrap.name, dep_var]
return wrap.name
return None
return wrap.name, dep_var
return None, None
def get_varname(self, subp_name: str, depname: str) -> T.Optional[str]:
wrap = self.wraps.get(subp_name)
return wrap.provided_deps.get(depname) if wrap else None
def find_program_provider(self, names: T.List[str]) -> T.Optional[str]:
for name in names:

@ -6221,7 +6221,7 @@ class FailureTests(BasePlatformTests):
def test_wrap_nofallback(self):
self.assertMesonRaises("dependency('notfound', fallback : ['foo', 'foo_dep'])",
r"Dependency \'notfound\' not found and fallback is disabled",
r"Dependency 'notfound' is required but not found.",
extra_args=['--wrap-mode=nofallback'])
def test_message(self):

@ -5,8 +5,21 @@ bob = dependency('boblib', fallback : ['boblib', 'bob_dep'], required: false,
if not bob.found()
error('Bob is actually needed')
endif
# boblib subproject exists, but bobinc is not a dependency variable
sita = dependency('sitalib', fallback : ['boblib', 'bobinc'], required: false)
assert(not sita.found())
# boblib subproject exists, but sita_dep doesn't exist
sita = dependency('sitalib', fallback : ['boblib', 'sita_dep'], required: false)
assert(not sita.found())
# boblib has been configured so zlib cannot be searched on the system
zlib = dependency('zlib', fallback : ['boblib', 'notfound_dep'], required: false)
assert(not zlib.found())
# boblib has been configured so zlib cannot be searched on the system.
# Not variable name provided and the subproject does not override zlib.
zlib = dependency('zlib', fallback : 'boblib', required: false)
assert(not zlib.found())
# jimmylib subproject doesn't exist
jimmy = dependency('jimmylib', fallback : ['jimmylib', 'jimmy_dep'], required: false)
# dummylib subproject fails to configure

@ -14,3 +14,5 @@ bobinc = include_directories('.')
bob_dep = declare_dependency(link_with : boblib,
sources : [genbob_h],
include_directories : bobinc)
notfound_dep = dependency('', required: false)

@ -1,7 +0,0 @@
{
"stdout": [
{
"line": "test cases/failing/100 fallback consistency/meson.build:7:0: ERROR: Inconsistency: Subproject has overridden the dependency with another variable than 'dep2'"
}
]
}

@ -1,3 +0,0 @@
project('fallback consistency')
dependency('foo')

@ -1,7 +0,0 @@
{
"stdout": [
{
"line": "test cases/failing/106 fallback consistency/meson.build:3:0: ERROR: Inconsistency: Subproject has overridden the dependency with another variable than 'bar_dep'"
}
]
}

@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/36 pkgconfig dependency impossible conditions/meson.build:7:0: ERROR: Dependency 'zlib' was already checked and was not found"
"line": "test cases/failing/36 pkgconfig dependency impossible conditions/meson.build:7:0: ERROR: Dependency 'zlib' is required but not found."
}
]
}

@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/66 dependency not-found and required/meson.build:2:0: ERROR: Dependency is both required and not-found"
"line": "test cases/failing/66 dependency not-found and required/meson.build:2:0: ERROR: Dependency is required but has no candidates."
}
]
}

@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/67 subproj different versions/subprojects/b/meson.build:3:0: ERROR: Dependency 'c' was already checked and was not found"
"line": "test cases/failing/67 subproj different versions/subprojects/b/meson.build:3:0: ERROR: Dependency 'c' is required but not found."
}
]
}

@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/84 gtest dependency with version/meson.build:8:0: ERROR: Dependency 'gtest' was already checked and was not found"
"line": "test cases/failing/84 gtest dependency with version/meson.build:8:0: ERROR: Dependency 'gtest' is required but not found."
}
]
}

@ -1,7 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/88 subproj not-found dep/meson.build:2:0: ERROR: Could not find dependency notfound_dep in subproject somesubproj"
"line": "test cases/failing/88 subproj not-found dep/meson.build:2:0: ERROR: Dependency '(anonymous)' is required but not found."
}
]
}

@ -1,7 +0,0 @@
{
"stdout": [
{
"line": "test cases/failing/98 fallback consistency/meson.build:4:0: ERROR: Inconsistency: Subproject has overridden the dependency with another variable than 'dep2'"
}
]
}

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "WARNING: Inconsistency: Subproject has overridden the dependency with another variable than 'dep2'"
}
]
}

@ -1,4 +1,4 @@
project('proj', 'c')
# Subproject overrides 'sub' with another variable than dep2. This should fail.
# Subproject overrides 'sub' with another variable than dep2. This should warn.
dependency('sub', fallback : ['sub', 'dep2'])

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "WARNING: Inconsistency: Subproject has overridden the dependency with another variable than 'dep2'"
}
]
}

@ -0,0 +1,4 @@
project('fallback consistency')
# Subproject overrides foo with foo_dep but wrap file says it's bar_dep. This should warn.
dependency('foo')

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "WARNING: Inconsistency: Subproject has overridden the dependency with another variable than 'bar_dep'"
}
]
}
Loading…
Cancel
Save