aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/interpreter/interpreter.py
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2021-06-09 15:13:17 -0400
committerXavier Claessens <xclaesse@gmail.com>2021-06-16 19:04:03 -0400
commitb6d754a40c618fe280af8f8527add2078a261a72 (patch)
treef943f54fe685dbb9a039d415f6201c09d8220d35 /mesonbuild/interpreter/interpreter.py
parent3970f269fd23c148e94800ca01b6a2d76003a3a2 (diff)
downloadmeson-b6d754a40c618fe280af8f8527add2078a261a72.zip
meson-b6d754a40c618fe280af8f8527add2078a261a72.tar.gz
meson-b6d754a40c618fe280af8f8527add2078a261a72.tar.bz2
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.
Diffstat (limited to 'mesonbuild/interpreter/interpreter.py')
-rw-r--r--mesonbuild/interpreter/interpreter.py318
1 files changed, 19 insertions, 299 deletions
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index dce0391..b5335e6 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -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.")