|  | from __future__ import annotations | 
|  |  | 
|  | from .interpreterobjects import 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, listify | 
|  | from ..dependencies import Dependency, DependencyException, NotFoundDependency | 
|  | from ..interpreterbase import (MesonInterpreterObject, FeatureNew, | 
|  | InterpreterException, InvalidArguments) | 
|  |  | 
|  | import typing as T | 
|  | if T.TYPE_CHECKING: | 
|  | from .interpreter import Interpreter | 
|  | from ..interpreterbase import TYPE_nkwargs, TYPE_nvar | 
|  | from .interpreterobjects import SubprojectHolder | 
|  |  | 
|  |  | 
|  | class DependencyFallbacksHolder(MesonInterpreterObject): | 
|  | def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None, | 
|  | default_options: T.Optional[T.List[str]] = None) -> None: | 
|  | super().__init__(subproject=interpreter.subproject) | 
|  | 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: T.Optional[str] = None | 
|  | self.subproject_varname: T.Optional[str] = None | 
|  | self.subproject_kwargs = {'default_options': default_options or []} | 
|  | self.names: T.List[str] = [] | 
|  | self.forcefallback: bool = False | 
|  | self.nofallback: bool = False | 
|  | 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(f'dependency_fallbacks name {name!r} is duplicated') | 
|  | self.names.append(name) | 
|  | self._display_name = self.names[0] if self.names else '(anonymous)' | 
|  |  | 
|  | def set_fallback(self, fbinfo: T.Optional[T.Union[T.List[str], str]]) -> None: | 
|  | # Legacy: This converts dependency()'s fallback kwargs. | 
|  | if fbinfo is None: | 
|  | return | 
|  | if self.allow_fallback is not None: | 
|  | raise InvalidArguments('"fallback" and "allow_fallback" arguments are mutually exclusive') | 
|  | fbinfo = stringlistify(fbinfo) | 
|  | if len(fbinfo) == 0: | 
|  | # dependency('foo', fallback: []) is the same as dependency('foo', allow_fallback: false) | 
|  | self.allow_fallback = False | 
|  | return | 
|  | 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.') | 
|  | self._subproject_impl(subp_name, varname) | 
|  |  | 
|  | def _subproject_impl(self, subp_name: str, varname: str) -> None: | 
|  | assert self.subproject_name is None | 
|  | self.subproject_name = subp_name | 
|  | self.subproject_varname = varname | 
|  |  | 
|  | def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: | 
|  | 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[Dependency]: | 
|  | # 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 dep | 
|  | return None | 
|  |  | 
|  | def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: | 
|  | 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[Dependency]: | 
|  | 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.') | 
|  | elif 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 | 
|  | else: | 
|  | mlog.log('Looking for a fallback subproject for the dependency', | 
|  | mlog.bold(self._display_name)) | 
|  |  | 
|  | # dependency('foo', static: true) should implicitly add | 
|  | # default_options: ['default_library=static'] | 
|  | static = kwargs.get('static') | 
|  | default_options = stringlistify(func_kwargs.get('default_options', [])) | 
|  | if static is not None and not any('default_library' in i for i in default_options): | 
|  | default_library = 'static' if static else 'shared' | 
|  | opt = f'default_library={default_library}' | 
|  | mlog.log(f'Building fallback subproject with {opt}') | 
|  | default_options.append(opt) | 
|  | func_kwargs['default_options'] = default_options | 
|  |  | 
|  | # Configure the subproject | 
|  | subp_name = self.subproject_name | 
|  | varname = self.subproject_varname | 
|  | func_kwargs.setdefault('version', []) | 
|  | if 'default_options' in kwargs and isinstance(kwargs['default_options'], str): | 
|  | func_kwargs['default_options'] = listify(kwargs['default_options']) | 
|  | 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[Dependency]: | 
|  | # 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: | 
|  | # 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 | 
|  | 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.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[Dependency]: | 
|  | # 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 cached_dep | 
|  | elif self.forcefallback and self.subproject_name: | 
|  | cached_dep = None | 
|  | 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): | 
|  | if not override: | 
|  | # We cached this dependency on disk from a previous run, | 
|  | # but it could got updated on the system in the meantime. | 
|  | return None | 
|  | 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 cached_dep | 
|  | return None | 
|  |  | 
|  | def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -> T.Optional[Dependency]: | 
|  | try: | 
|  | var_dep = subproject.get_variable_method([varname], {}) | 
|  | except InvalidArguments: | 
|  | var_dep = None | 
|  | if not isinstance(var_dep, Dependency): | 
|  | 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: Dependency) -> None: | 
|  | 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 != cached_dep: | 
|  | 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) -> NotFoundDependency: | 
|  | return NotFoundDependency(self.names[0] if self.names else '', self.environment) | 
|  |  | 
|  | @staticmethod | 
|  | def _check_version(wanted: T.List[str], found: str) -> bool: | 
|  | if not wanted: | 
|  | return True | 
|  | return not (found == 'undefined' or not version_compare_many(found, wanted)[0]) | 
|  |  | 
|  | def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[Dependency]], 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) -> Dependency: | 
|  | 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')) | 
|  | assert isinstance(wrap_mode, WrapMode), 'for mypy' | 
|  | force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for')) | 
|  | assert isinstance(force_fallback_for, list), 'for mypy' | 
|  | 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.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, 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() |