From f4ae32c2ad4f4912867e1be6333371955bdaf83b Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:12:35 -0800 Subject: [PATCH 01/15] modules/cmake: add type annotations for subproject method This will be used to handle the interpreter subproject as well --- mesonbuild/interpreter/dependencyfallbacks.py | 30 ++++++------- mesonbuild/modules/cmake.py | 43 +++++++++++++------ 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 417c8cd9d..9194978bb 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -105,14 +105,14 @@ class DependencyFallbacksHolder(MesonInterpreterObject): 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.') + 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.') + 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)) + mlog.bold(self._display_name)) # dependency('foo', static: true) should implicitly add # default_options: ['default_library=static'] @@ -141,7 +141,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # 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.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 @@ -167,27 +167,27 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # 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.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.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.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.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 @@ -209,7 +209,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # have explicitly called meson.override_dependency() with a not-found # dep. if not cached_dep.found(): - mlog.log('Dependency', mlog.bold(self.display_name), + mlog.log('Dependency', mlog.bold(self._display_name), 'found:', mlog.red('NO'), *info) return cached_dep else: @@ -231,7 +231,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return self._notfound_dependency() if found_vers: info = [mlog.normal_cyan(found_vers), *info] - mlog.log('Dependency', mlog.bold(self.display_name), + mlog.log('Dependency', mlog.bold(self._display_name), 'found:', mlog.green('YES'), *info) return cached_dep return None @@ -298,14 +298,14 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return candidates def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency: - self.display_name = self.names[0] if self.names else '(anonymous)' + 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)) + 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') + 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 @@ -359,7 +359,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # 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.') + 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 diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index b046371a5..f996087de 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +from __future__ import annotations import re import os, os.path, pathlib import shutil @@ -21,11 +23,10 @@ from . import ExtensionModule, ModuleReturnValue, ModuleObject from .. import build, mesonlib, mlog, dependencies from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args from ..interpreter import SubprojectHolder -from ..interpreter.type_checking import NoneType, in_set_validator +from ..interpreter.type_checking import REQUIRED_KW, NoneType, in_set_validator from ..interpreterbase import ( FeatureNew, FeatureNewKwargs, - FeatureDeprecatedKwargs, stringArgs, permittedKwargs, @@ -35,6 +36,7 @@ from ..interpreterbase import ( InvalidArguments, InterpreterException, + typed_pos_args, typed_kwargs, KwargInfo, ContainerTypeInfo, @@ -43,6 +45,9 @@ from ..interpreterbase import ( if T.TYPE_CHECKING: from typing_extensions import TypedDict + from . import ModuleState + from ..interpreter import kwargs + class WriteBasicPackageVersionFile(TypedDict): arch_independent: bool @@ -58,6 +63,12 @@ if T.TYPE_CHECKING: install_dir: T.Optional[str] name: str + class Subproject(kwargs.ExtractRequired): + + options: T.Optional[CMakeSubprojectOptions] + cmake_options: T.List[str] + + COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion'] # Taken from https://github.com/Kitware/CMake/blob/master/Modules/CMakePackageConfigHelpers.cmake @@ -91,7 +102,7 @@ endmacro() ''' class CMakeSubproject(ModuleObject): - def __init__(self, subp, pv): + def __init__(self, subp: SubprojectHolder): assert isinstance(subp, SubprojectHolder) assert hasattr(subp, 'cm_interpreter') super().__init__() @@ -400,20 +411,28 @@ class CmakeModule(ExtensionModule): return res @FeatureNew('subproject', '0.51.0') - @FeatureNewKwargs('subproject', '0.55.0', ['options']) - @FeatureDeprecatedKwargs('subproject', '0.55.0', ['cmake_options']) - @permittedKwargs({'cmake_options', 'required', 'options'}) - @stringArgs - def subproject(self, state, args, kwargs): - if len(args) != 1: - raise InterpreterException('Subproject takes exactly one argument') - if 'cmake_options' in kwargs and 'options' in kwargs: + @typed_pos_args('cmake.subproject', str) + @typed_kwargs( + 'cmake.subproject', + REQUIRED_KW, + KwargInfo('options', (CMakeSubprojectOptions, NoneType), since='0.55.0'), + KwargInfo( + 'cmake_options', + ContainerTypeInfo(list, str), + default=[], + listify=True, + deprecated='0.55.0', + deprecated_message='Use options instead', + ), + ) + def subproject(self, state: ModuleState, args: T.Tuple[str], kwargs: Subproject) -> T.Union[SubprojectHolder, CMakeSubproject]: + if kwargs['cmake_options'] and kwargs['options'] is not None: raise InterpreterException('"options" cannot be used together with "cmake_options"') dirname = args[0] subp = self.interpreter.do_subproject(dirname, 'cmake', kwargs) if not subp.found(): return subp - return CMakeSubproject(subp, dirname) + return CMakeSubproject(subp) @FeatureNew('subproject_options', '0.55.0') @noKwargs From 5f595f0d1b97b4b948b570faaa3a13f4cf50d597 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 1 Mar 2022 10:51:55 -0800 Subject: [PATCH 02/15] coredata: fix annotations of get_option --- mesonbuild/coredata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index fa20a666a..f4365e19a 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -613,7 +613,7 @@ class CoreData: 'Default project to execute in Visual Studio', '') - def get_option(self, key: OptionKey) -> T.Union[str, int, bool, WrapMode]: + def get_option(self, key: OptionKey) -> T.Union[T.List[str], str, int, bool, WrapMode]: try: v = self.options[key].value if key.name == 'wrap_mode': From ae8fb74e575270b25203a0cb8283e33501b8eab6 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 1 Mar 2022 10:41:26 -0800 Subject: [PATCH 03/15] interpreter/dependencyfallbacks: fix a number of low hanging type issues --- mesonbuild/interpreter/dependencyfallbacks.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 9194978bb..8a0d35131 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -26,10 +26,12 @@ class DependencyFallbacksHolder(MesonInterpreterObject): 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_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') @@ -39,6 +41,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): if name in self.names: raise InterpreterException('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. @@ -247,7 +250,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return None return var_dep - def _verify_fallback_consistency(self, cached_dep: Dependency): + def _verify_fallback_consistency(self, cached_dep: Dependency) -> None: subp_name = self.subproject_name varname = self.subproject_varname subproject = self._get_subproject(subp_name) @@ -273,12 +276,10 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return NotFoundDependency(self.names[0] if self.names else '', self.environment) @staticmethod - def _check_version(wanted: T.Optional[str], found: str) -> bool: + def _check_version(wanted: T.List[str], found: str) -> bool: if not wanted: return True - if found == 'undefined' or not version_compare_many(found, wanted)[0]: - return False - 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 = [] @@ -298,7 +299,6 @@ class DependencyFallbacksHolder(MesonInterpreterObject): return candidates def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency: - 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)) @@ -310,7 +310,9 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # 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 From ca64777315ce031face69c263b9e69f0c55b93c1 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:19:54 -0800 Subject: [PATCH 04/15] interpreter: annotate subproject_stack --- mesonbuild/interpreter/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 0bace8648..daa2ca5f3 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -289,7 +289,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.project_args_frozen = False self.global_args_frozen = False # implies self.project_args_frozen self.subprojects: T.Dict[str, SubprojectHolder] = {} - self.subproject_stack = [] + self.subproject_stack: T.List[str] = [] self.configure_file_outputs = {} # Passed from the outside, only used in subprojects. if default_project_options: From 122b9db7b00ef55b154fbc414eb18e50fcfc987f Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:20:19 -0800 Subject: [PATCH 05/15] interpreter: Use a literal for the do_subproject helper Since we only have two valid options and we don't want to allow any others. --- mesonbuild/interpreter/interpreter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index daa2ca5f3..4d5bf1b3b 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -93,6 +93,8 @@ import importlib if T.TYPE_CHECKING: import argparse + from typing_extensions import Literal + from . import kwargs from ..programs import OverrideProgram @@ -807,7 +809,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.coredata.initialized_subprojects.add(subp_name) return sub - def do_subproject(self, subp_name: str, method: str, kwargs): + def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs) -> SubprojectHolder: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled') From 71c65392a83f3f293fa70e7fadc610f95a8e8d4e Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:20:49 -0800 Subject: [PATCH 06/15] interpreter: add some simple annotations to subproject methods --- mesonbuild/interpreter/interpreter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 4d5bf1b3b..943f49eaf 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -802,7 +802,8 @@ external dependencies (including libraries) must go to "dependencies".''') def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> SubprojectHolder: return self.do_subproject(args[0], 'meson', kwargs) - def disabled_subproject(self, subp_name, disabled_feature=None, exception=None): + def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None, + exception: T.Optional[mesonlib.MesonException] = None) -> SubprojectHolder: sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name), disabled_feature=disabled_feature, exception=exception) self.subprojects[subp_name] = sub @@ -931,7 +932,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.coredata.initialized_subprojects.add(subp_name) return self.subprojects[subp_name] - def _do_subproject_cmake(self, subp_name, subdir, subdir_abs, default_options, kwargs): + def _do_subproject_cmake(self, subp_name: str, subdir: str, subdir_abs: str, default_options, kwargs): with mlog.nested(subp_name): new_build = self.build.copy() prefix = self.coredata.options[OptionKey('prefix')].value From 4a2058cb836242a6423870e671b6b76fa48167f3 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:44:39 -0800 Subject: [PATCH 07/15] interpreter: use typed_kwargs for subproject() --- mesonbuild/interpreter/dependencyfallbacks.py | 5 +- mesonbuild/interpreter/interpreter.py | 55 ++++++++++++------- mesonbuild/interpreter/kwargs.py | 15 +++++ mesonbuild/modules/cmake.py | 13 ++++- .../120 subproject version conflict/test.json | 2 +- test cases/failing/21 subver/test.json | 2 +- 6 files changed, 67 insertions(+), 25 deletions(-) diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py index 8a0d35131..e02985480 100644 --- a/mesonbuild/interpreter/dependencyfallbacks.py +++ b/mesonbuild/interpreter/dependencyfallbacks.py @@ -4,7 +4,7 @@ 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 ..mesonlib import OptionKey, extract_as_list, stringlistify, version_compare_many, listify from ..dependencies import Dependency, DependencyException, NotFoundDependency from ..interpreterbase import (MesonInterpreterObject, FeatureNew, InterpreterException, InvalidArguments, @@ -131,6 +131,9 @@ class DependencyFallbacksHolder(MesonInterpreterObject): # 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) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 943f49eaf..1f778c849 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -799,8 +799,21 @@ external dependencies (including libraries) must go to "dependencies".''') @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) @permittedKwargs({'version', 'default_options', 'required'}) @typed_pos_args('subproject', str) - def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> SubprojectHolder: - return self.do_subproject(args[0], 'meson', kwargs) + @typed_kwargs( + 'subproject', + REQUIRED_KW, + DEFAULT_OPTIONS.evolve(since='0.38.0'), + KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True), + ) + def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs_: kwargs.Subproject) -> SubprojectHolder: + kw: kwargs.DoSubproject = { + 'required': kwargs_['required'], + 'default_options': kwargs_['default_options'], + 'version': kwargs_['version'], + 'options': None, + 'cmake_options': [], + } + return self.do_subproject(args[0], 'meson', kw) def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None, exception: T.Optional[mesonlib.MesonException] = None) -> SubprojectHolder: @@ -810,14 +823,13 @@ external dependencies (including libraries) must go to "dependencies".''') self.coredata.initialized_subprojects.add(subp_name) return sub - def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs) -> SubprojectHolder: + def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs: kwargs.DoSubproject) -> SubprojectHolder: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled') return self.disabled_subproject(subp_name, disabled_feature=feature) - default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) - default_options = coredata.create_options_dict(default_options, subp_name) + default_options = coredata.create_options_dict(kwargs['default_options'], subp_name) if subp_name == '': raise InterpreterException('Subproject name must not be empty.') @@ -838,7 +850,7 @@ external dependencies (including libraries) must go to "dependencies".''') subproject = self.subprojects[subp_name] if required and not subproject.found(): raise InterpreterException(f'Subproject "{subproject.subdir}" required but not found.') - if 'version' in kwargs: + if kwargs['version']: pv = self.build.subprojects[subp_name] wanted = kwargs['version'] if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]: @@ -885,7 +897,9 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(subp_name, exception=e) raise e - def _do_subproject_meson(self, subp_name: str, subdir: str, default_options, kwargs, + def _do_subproject_meson(self, subp_name: str, subdir: str, + default_options: T.Dict[OptionKey, str], + kwargs: kwargs.DoSubproject, ast: T.Optional[mparser.CodeBlockNode] = None, build_def_files: T.Optional[T.List[str]] = None, is_translated: bool = False) -> SubprojectHolder: @@ -914,7 +928,7 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.log() - if 'version' in kwargs: + if kwargs['version']: pv = subi.project_version wanted = kwargs['version'] if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]: @@ -932,19 +946,16 @@ external dependencies (including libraries) must go to "dependencies".''') self.coredata.initialized_subprojects.add(subp_name) return self.subprojects[subp_name] - def _do_subproject_cmake(self, subp_name: str, subdir: str, subdir_abs: str, default_options, kwargs): + def _do_subproject_cmake(self, subp_name: str, subdir: str, subdir_abs: str, + default_options: T.Dict[OptionKey, str], + kwargs: kwargs.DoSubproject) -> SubprojectHolder: with mlog.nested(subp_name): new_build = self.build.copy() prefix = self.coredata.options[OptionKey('prefix')].value from ..modules.cmake import CMakeSubprojectOptions - options = kwargs.get('options', CMakeSubprojectOptions()) - if not isinstance(options, CMakeSubprojectOptions): - raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions' - ' object (created by cmake.subproject_options())') - - cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', [])) - cmake_options += options.cmake_options + options = kwargs['options'] or CMakeSubprojectOptions() + cmake_options = kwargs['cmake_options'] + options.cmake_options cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend) cm_int.initialise(cmake_options) cm_int.analyse() @@ -1501,7 +1512,7 @@ external dependencies (including libraries) must go to "dependencies".''') progobj.was_returned_by_find_program = True return progobj - def program_lookup(self, args, for_machine, required, search_dirs, extra_info): + def program_lookup(self, args, for_machine, required: bool, search_dirs, extra_info): progobj = self.program_from_overrides(args, extra_info) if progobj: return progobj @@ -1524,10 +1535,16 @@ external dependencies (including libraries) must go to "dependencies".''') return progobj - def find_program_fallback(self, fallback, args, required, extra_info): + def find_program_fallback(self, fallback: str, args, required: bool, extra_info): mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', mlog.bold(' '.join(args))) - sp_kwargs = {'required': required} + sp_kwargs: kwargs.DoSubproject = { + 'required': required, + 'default_options': [], + 'version': [], + 'cmake_options': [], + 'options': None, + } self.do_subproject(fallback, 'meson', sp_kwargs) return self.program_from_overrides(args, extra_info) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 981f50d27..f2460ac29 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -12,6 +12,7 @@ from .. import build from .. import coredata from ..compilers import Compiler from ..mesonlib import MachineChoice, File, FileMode, FileOrString, OptionKey +from ..modules.cmake import CMakeSubprojectOptions from ..programs import ExternalProgram @@ -293,3 +294,17 @@ class ConfigureFile(TypedDict): command: T.Optional[T.List[T.Union[build.Executable, ExternalProgram, Compiler, File, str]]] input: T.List[FileOrString] configuration: T.Optional[T.Union[T.Dict[str, T.Union[str, int, bool]], build.ConfigurationData]] + + +class Subproject(ExtractRequired): + + default_options: T.List[str] + version: T.List[str] + + +class DoSubproject(ExtractRequired): + + default_options: T.List[str] + version: T.List[str] + cmake_options: T.List[str] + options: T.Optional[CMakeSubprojectOptions] diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index f996087de..a58ad54c0 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -425,11 +425,18 @@ class CmakeModule(ExtensionModule): deprecated_message='Use options instead', ), ) - def subproject(self, state: ModuleState, args: T.Tuple[str], kwargs: Subproject) -> T.Union[SubprojectHolder, CMakeSubproject]: - if kwargs['cmake_options'] and kwargs['options'] is not None: + def subproject(self, state: ModuleState, args: T.Tuple[str], kwargs_: Subproject) -> T.Union[SubprojectHolder, CMakeSubproject]: + if kwargs_['cmake_options'] and kwargs_['options'] is not None: raise InterpreterException('"options" cannot be used together with "cmake_options"') dirname = args[0] - subp = self.interpreter.do_subproject(dirname, 'cmake', kwargs) + kw: kwargs.DoSubproject = { + 'required': kwargs_['required'], + 'options': kwargs_['options'], + 'cmake_options': kwargs_['cmake_options'], + 'default_options': [], + 'version': [], + } + subp = self.interpreter.do_subproject(dirname, 'cmake', kw) if not subp.found(): return subp return CMakeSubproject(subp) diff --git a/test cases/failing/120 subproject version conflict/test.json b/test cases/failing/120 subproject version conflict/test.json index 5d445e524..7807d6878 100644 --- a/test cases/failing/120 subproject version conflict/test.json +++ b/test cases/failing/120 subproject version conflict/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/120 subproject version conflict/meson.build:4:0: ERROR: Subproject B version is 100 but 1 required." + "line": "test cases/failing/120 subproject version conflict/meson.build:4:0: ERROR: Subproject B version is 100 but ['1'] required." } ] } diff --git a/test cases/failing/21 subver/test.json b/test cases/failing/21 subver/test.json index f8cfd3a1f..a197b362e 100644 --- a/test cases/failing/21 subver/test.json +++ b/test cases/failing/21 subver/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/21 subver/meson.build:3:0: ERROR: Subproject foo version is 1.0.0 but >1.0.0 required." + "line": "test cases/failing/21 subver/meson.build:3:0: ERROR: Subproject foo version is 1.0.0 but ['>1.0.0'] required." } ] } From 5787f816159a96d4654cef276866dfbcae7619d8 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:46:45 -0800 Subject: [PATCH 08/15] interpreter: add cm_interpreter to SubprojectHolder This is used in the cmake module, as an extra attribute we just tack on. Instead, let's actually define and type it. --- mesonbuild/interpreter/interpreterobjects.py | 5 ++++- mesonbuild/modules/cmake.py | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index c7ddb382b..eed750e54 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os import shlex import subprocess @@ -29,9 +30,10 @@ import typing as T if T.TYPE_CHECKING: from . import kwargs - from .interpreter import Interpreter + from ..cmake.interpreter import CMakeInterpreter from ..envconfig import MachineInfo from ..interpreterbase import SubProject + from .interpreter import Interpreter from typing_extensions import TypedDict @@ -678,6 +680,7 @@ class SubprojectHolder(MesonInterpreterObject): self.disabled_feature = disabled_feature self.exception = exception self.subdir = PurePath(subdir).as_posix() + self.cm_interpreter: T.Optional[CMakeInterpreter] = None self.methods.update({'get_variable': self.get_variable_method, 'found': self.found_method, }) diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index a58ad54c0..0f54d30fa 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -104,9 +104,10 @@ endmacro() class CMakeSubproject(ModuleObject): def __init__(self, subp: SubprojectHolder): assert isinstance(subp, SubprojectHolder) - assert hasattr(subp, 'cm_interpreter') + assert subp.cm_interpreter is not None super().__init__() self.subp = subp + self.cm_interpreter = subp.cm_interpreter self.methods.update({'get_variable': self.get_variable, 'dependency': self.dependency, 'include_directories': self.include_directories, @@ -121,7 +122,7 @@ class CMakeSubproject(ModuleObject): raise InterpreterException('Exactly one argument is required.') tgt = args[0] - res = self.subp.cm_interpreter.target_info(tgt) + res = self.cm_interpreter.target_info(tgt) if res is None: raise InterpreterException(f'The CMake target {tgt} does not exist\n' + ' Use the following command in your meson.build to list all available targets:\n\n' + @@ -172,7 +173,7 @@ class CMakeSubproject(ModuleObject): @noPosargs @noKwargs def target_list(self, state, args, kwargs): - return self.subp.cm_interpreter.target_list() + return self.cm_interpreter.target_list() @noPosargs @noKwargs From 82f43ba8c5a1140456407d6cfa98f401d3657c7b Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:52:42 -0800 Subject: [PATCH 09/15] interpreter: fix Interpreter type annotations default_options should be `T.Dict[OptionKey, str]`, not `T.Dict[str, str]`. --- mesonbuild/interpreter/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 1f778c849..5e8240eae 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -260,7 +260,7 @@ class Interpreter(InterpreterBase, HoldableObject): subproject: str = '', subdir: str = '', subproject_dir: str = 'subprojects', - default_project_options: T.Optional[T.Dict[str, str]] = None, + default_project_options: T.Optional[T.Dict[OptionKey, str]] = None, mock: bool = False, ast: T.Optional[mparser.CodeBlockNode] = None, is_translated: bool = False, From b07d4bb2068f90bfbb0aa6e6ea91412db29388ea Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 15:55:19 -0800 Subject: [PATCH 10/15] build: Add a couple of type annotations for the Interpreter to use --- mesonbuild/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index bb57ec854..b9a3dc29d 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -279,7 +279,7 @@ class Build: custom_targets[name] = t return custom_targets - def copy(self): + def copy(self) -> Build: other = Build(self.environment) for k, v in self.__dict__.items(): if isinstance(v, (list, dict, set, OrderedDict)): @@ -288,7 +288,7 @@ class Build: other.__dict__[k] = v return other - def merge(self, other): + def merge(self, other: Build) -> None: for k, v in other.__dict__.items(): self.__dict__[k] = v From 3e90f277032d8ce91d99ed1fe62567701ce62622 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 16:01:11 -0800 Subject: [PATCH 11/15] interpreter: replace build_def_files with OrderedSet We do a bunch of backbending to make sure we don't have duplicates, let's just use the right datastructure to begin with. --- mesonbuild/interpreter/interpreter.py | 14 +++++++------- mesonbuild/mintro.py | 2 +- mesonbuild/modules/cmake.py | 3 +-- mesonbuild/modules/keyval.py | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 5e8240eae..5d2e476d1 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -308,10 +308,10 @@ class Interpreter(InterpreterBase, HoldableObject): # For non-meson subprojects, we'll be using the ast. Even if it does # exist we don't want to add a dependency on it, it's autogenerated # from the actual build files, and is just for reference. - self.build_def_files = [] + self.build_def_files: mesonlib.OrderedSet[str] = mesonlib.OrderedSet() build_filename = os.path.join(self.subdir, environment.build_filename) if not is_translated: - self.build_def_files.append(build_filename) + self.build_def_files.add(build_filename) if not mock: self.parse_project() self._redetect_machines() @@ -503,7 +503,7 @@ class Interpreter(InterpreterBase, HoldableObject): else: raise InterpreterException(f'Module returned a value of unknown type {v!r}.') - def get_build_def_files(self) -> T.List[str]: + def get_build_def_files(self) -> mesonlib.OrderedSet[str]: return self.build_def_files def add_build_def_file(self, f: mesonlib.FileOrString) -> None: @@ -539,7 +539,7 @@ class Interpreter(InterpreterBase, HoldableObject): else: return if f not in self.build_def_files: - self.build_def_files.append(f) + self.build_def_files.add(f) def get_variables(self): return self.variables @@ -938,9 +938,9 @@ external dependencies (including libraries) must go to "dependencies".''') self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings) # Duplicates are possible when subproject uses files from project root if build_def_files: - self.build_def_files = list(set(self.build_def_files + build_def_files)) + self.build_def_files.update(build_def_files) # We always need the subi.build_def_files, to propgate sub-sub-projects - self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) + self.build_def_files.update(subi.build_def_files) self.build.merge(subi.build) self.build.subprojects[subp_name] = subi.project_version self.coredata.initialized_subprojects.add(subp_name) @@ -2135,7 +2135,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.subdir = subdir os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True) buildfilename = os.path.join(self.subdir, environment.build_filename) - self.build_def_files.append(buildfilename) + self.build_def_files.add(buildfilename) absname = os.path.join(self.environment.get_source_dir(), buildfilename) if not os.path.isfile(absname): self.subdir = prev_subdir diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 130cf9313..2ec727436 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -318,7 +318,7 @@ def find_buildsystem_files_list(src_dir: str) -> T.List[str]: def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]: src_dir = builddata.environment.get_source_dir() - filelist = interpreter.get_build_def_files() # type: T.List[str] + filelist = list(interpreter.get_build_def_files()) filelist = [PurePath(src_dir, x).as_posix() for x in filelist] return filelist diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index 0f54d30fa..a263e5231 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -403,8 +403,7 @@ class CmakeModule(ExtensionModule): conf.used = True conffile = os.path.normpath(inputfile.relative_name()) - if conffile not in self.interpreter.build_def_files: - self.interpreter.build_def_files.append(conffile) + self.interpreter.build_def_files.add(conffile) res = build.Data([mesonlib.File(True, ofile_path, ofile_fname)], install_dir, install_dir, None, state.subproject) self.interpreter.build.data.append(res) diff --git a/mesonbuild/modules/keyval.py b/mesonbuild/modules/keyval.py index 7cebc91bd..36daea8ca 100644 --- a/mesonbuild/modules/keyval.py +++ b/mesonbuild/modules/keyval.py @@ -63,8 +63,8 @@ class KeyvalModule(ExtensionModule): else: s = os.path.join(self.interpreter.environment.source_dir, s) - if s not in self.interpreter.build_def_files and not is_built: - self.interpreter.build_def_files.append(s) + if not is_built: + self.interpreter.build_def_files.add(s) return self._load_file(s) From a760ef5b60e885c0655645970c211639e8c75757 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 16:03:03 -0800 Subject: [PATCH 12/15] interpreter: Fix type violation in do_subproject_cmake --- mesonbuild/interpreter/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 5d2e476d1..ee125bcb3 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -981,7 +981,7 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.cmd_ci_include(meson_filename) mlog.log() - result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, cm_int.bs_files, is_translated=True) + result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, [str(f) for f in cm_int.bs_files], is_translated=True) result.cm_interpreter = cm_int mlog.log() From 643b3f227edfbac590fa37c248f14d7172ee1066 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 28 Feb 2022 16:04:30 -0800 Subject: [PATCH 13/15] interpreter: fix mismatched type expectations --- mesonbuild/interpreter/interpreter.py | 2 +- mesonbuild/interpreter/interpreterobjects.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index ee125bcb3..7c4f38174 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -816,7 +816,7 @@ external dependencies (including libraries) must go to "dependencies".''') return self.do_subproject(args[0], 'meson', kw) def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None, - exception: T.Optional[mesonlib.MesonException] = None) -> SubprojectHolder: + exception: T.Optional[Exception] = None) -> SubprojectHolder: sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name), disabled_feature=disabled_feature, exception=exception) self.subprojects[subp_name] = sub diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index eed750e54..a3c0beff9 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -24,7 +24,7 @@ from ..interpreterbase import ( from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..programs import ExternalProgram -from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe +from ..mesonlib import HoldableObject, OptionKey, listify, Popen_safe import typing as T @@ -673,7 +673,7 @@ class SubprojectHolder(MesonInterpreterObject): subdir: str, warnings: int = 0, disabled_feature: T.Optional[str] = None, - exception: T.Optional[MesonException] = None) -> None: + exception: T.Optional[Exception] = None) -> None: super().__init__() self.held_object = subinterpreter self.warnings = warnings From 5e89953dbb116c61944aa7e300c37a60c6fa07e3 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 1 Mar 2022 09:56:16 -0800 Subject: [PATCH 14/15] interpreter: fix some low hanging type annotations issues --- mesonbuild/interpreter/interpreter.py | 43 ++++++++++++++------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 7c4f38174..397ea92f5 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -33,7 +33,7 @@ from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCod from ..interpreterbase import Disabler, disablerIfNotFound from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs from ..interpreterbase import ObjectHolder -from ..interpreterbase.baseobjects import TYPE_var, TYPE_kwargs +from ..interpreterbase.baseobjects import InterpreterObject, TYPE_var, TYPE_kwargs from ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule from ..cmake import CMakeInterpreter from ..backend.backends import Backend, ExecutableSerialisation @@ -117,7 +117,7 @@ def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None]) return None -def stringifyUserArguments(args, quote=False): +def stringifyUserArguments(args: T.List[T.Any], quote: bool = False) -> str: if isinstance(args, list): return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args]) elif isinstance(args, dict): @@ -273,7 +273,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.coredata = self.environment.get_coredata() self.backend = backend self.summary: T.Dict[str, 'Summary'] = {} - self.modules = {} + self.modules: T.Dict[str, NewExtensionModule] = {} # Subproject directory is usually the name of the subproject, but can # be different for dependencies provided by wrap files. self.subproject_directory_name = subdir.split(os.path.sep)[-1] @@ -292,13 +292,13 @@ class Interpreter(InterpreterBase, HoldableObject): self.global_args_frozen = False # implies self.project_args_frozen self.subprojects: T.Dict[str, SubprojectHolder] = {} self.subproject_stack: T.List[str] = [] - self.configure_file_outputs = {} + self.configure_file_outputs: T.Dict[str, int] = {} # Passed from the outside, only used in subprojects. if default_project_options: self.default_project_options = default_project_options.copy() else: self.default_project_options = {} - self.project_default_options = {} + self.project_default_options: T.Dict[OptionKey, str] = {} self.build_func_dict() self.build_holder_map() self.user_defined_options = user_defined_options @@ -319,7 +319,7 @@ class Interpreter(InterpreterBase, HoldableObject): def __getnewargs_ex__(self) -> T.Tuple[T.Tuple[object], T.Dict[str, object]]: raise MesonBugException('This class is unpicklable') - def _redetect_machines(self): + def _redetect_machines(self) -> None: # Re-initialize machine descriptions. We can do a better job now because we # have the compilers needed to gain more knowledge, so wipe out old # inference and start over. @@ -337,7 +337,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.builtin['target_machine'] = \ OBJ.MachineHolder(self.build.environment.machines.target, self) - def build_func_dict(self): + def build_func_dict(self) -> None: self.funcs.update({'add_global_arguments': self.func_add_global_arguments, 'add_global_link_arguments': self.func_add_global_link_arguments, 'add_languages': self.func_add_languages, @@ -519,32 +519,32 @@ class Interpreter(InterpreterBase, HoldableObject): srcdir = Path(self.environment.get_source_dir()) builddir = Path(self.environment.get_build_dir()) try: - f = Path(f).resolve() + f_ = Path(f).resolve() except OSError: - f = Path(f) - s = f.stat() + f_ = Path(f) + s = f_.stat() if (hasattr(s, 'st_file_attributes') and s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK): # This is a Windows Store link which we can't # resolve, so just do our best otherwise. - f = f.parent.resolve() / f.name + f_ = f_.parent.resolve() / f_.name else: raise - if builddir in f.parents: + if builddir in f_.parents: return - if srcdir in f.parents: - f = f.relative_to(srcdir) - f = str(f) + if srcdir in f_.parents: + f_ = f_.relative_to(srcdir) + f = str(f_) else: return if f not in self.build_def_files: self.build_def_files.add(f) - def get_variables(self): + def get_variables(self) -> T.Dict[str, InterpreterObject]: return self.variables - def check_stdlibs(self): + def check_stdlibs(self) -> None: machine_choices = [MachineChoice.HOST] if self.coredata.is_cross_build(): machine_choices.append(MachineChoice.BUILD) @@ -565,7 +565,7 @@ class Interpreter(InterpreterBase, HoldableObject): dep = df.lookup(kwargs, force_fallback=True) self.build.stdlibs[for_machine][l] = dep - def _import_module(self, modname: str, required: bool) -> T.Union[ExtensionModule, NewExtensionModule, NotFoundExtensionModule]: + def _import_module(self, modname: str, required: bool) -> NewExtensionModule: if modname in self.modules: return self.modules[modname] try: @@ -987,7 +987,7 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.log() return result - def get_option_internal(self, optname: str): + def get_option_internal(self, optname: str) -> coredata.UserOption: key = OptionKey.from_string(optname).evolve(subproject=self.subproject) if not key.is_project(): @@ -996,6 +996,7 @@ external dependencies (including libraries) must go to "dependencies".''') if v is None or v.yielding: v = opts.get(key.as_root()) if v is not None: + assert isinstance(v, coredata.UserOption), 'for mypy' return v try: @@ -1051,7 +1052,7 @@ external dependencies (including libraries) must go to "dependencies".''') f'"configuration_data": initial value dictionary key "{k!r}"" must be "str | int | bool", not "{v!r}"') return build.ConfigurationData(initial_values) - def set_backend(self): + def set_backend(self) -> None: # The backend is already set when parsing subprojects if self.backend is not None: return @@ -1340,7 +1341,7 @@ external dependencies (including libraries) must go to "dependencies".''') return False return should - def add_languages_for(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> None: + def add_languages_for(self, args: T.List[str], required: bool, for_machine: MachineChoice) -> bool: args = [a.lower() for a in args] langs = set(self.coredata.compilers[for_machine].keys()) langs.update(args) From bcf924dc7b7eebfdca83e74da400a060b6be4319 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 1 Mar 2022 12:46:02 -0800 Subject: [PATCH 15/15] interpreter: annotate the find_program chain --- mesonbuild/build.py | 4 ++-- mesonbuild/interpreter/interpreter.py | 31 +++++++++++++++++---------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index b9a3dc29d..bd6661bc3 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -255,7 +255,7 @@ class Build: self.test_setups: T.Dict[str, TestSetup] = {} self.test_setup_default_name = None self.find_overrides: T.Dict[str, T.Union['Executable', programs.ExternalProgram, programs.OverrideProgram]] = {} - self.searched_programs = set() # The list of all programs that have been searched for. + self.searched_programs: T.Set[str] = set() # The list of all programs that have been searched for. # If we are doing a cross build we need two caches, if we're doing a # build == host compilation the both caches should point to the same place. @@ -292,7 +292,7 @@ class Build: for k, v in other.__dict__.items(): self.__dict__[k] = v - def ensure_static_linker(self, compiler): + def ensure_static_linker(self, compiler: Compiler) -> None: if self.static_linker[compiler.for_machine] is None and compiler.needs_static_linker(): self.static_linker[compiler.for_machine] = detect_static_linker(self.environment, compiler) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 397ea92f5..9f1271900 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1393,7 +1393,8 @@ external dependencies (including libraries) must go to "dependencies".''') return success - def program_from_file_for(self, for_machine, prognames): + def program_from_file_for(self, for_machine: MachineChoice, prognames: T.List[mesonlib.FileOrString] + ) -> T.Optional[ExternalProgram]: for p in prognames: if isinstance(p, mesonlib.File): continue # Always points to a local (i.e. self generated) file. @@ -1404,7 +1405,8 @@ external dependencies (including libraries) must go to "dependencies".''') return prog return None - def program_from_system(self, args, search_dirs, extra_info): + def program_from_system(self, args: T.List[mesonlib.FileOrString], search_dirs: T.List[str], + extra_info: T.List[mlog.TV_Loggable]) -> T.Optional[ExternalProgram]: # Search for scripts relative to current subdir. # Do not cache found programs because find_program('foobar') # might give different results when run from different source dirs. @@ -1430,9 +1432,11 @@ external dependencies (including libraries) must go to "dependencies".''') if extprog.found(): extra_info.append(f"({' '.join(extprog.get_command())})") return extprog + return None - def program_from_overrides(self, command_names: T.List[str], extra_info: T.List['mlog.TV_Loggable']) -> \ - T.Optional[T.Union[ExternalProgram, 'OverrideProgram', 'build.Executable']]: + def program_from_overrides(self, command_names: T.List[mesonlib.FileOrString], + extra_info: T.List['mlog.TV_Loggable'] + ) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.Executable]]: for name in command_names: if not isinstance(name, str): continue @@ -1442,7 +1446,7 @@ external dependencies (including libraries) must go to "dependencies".''') return exe return None - def store_name_lookups(self, command_names): + def store_name_lookups(self, command_names: T.List[mesonlib.FileOrString]) -> None: for name in command_names: if isinstance(name, str): self.build.searched_programs.add(name) @@ -1470,7 +1474,7 @@ external dependencies (including libraries) must go to "dependencies".''') ) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']: args = mesonlib.listify(args) - extra_info = [] + extra_info: T.List[mlog.TV_Loggable] = [] progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info) if progobj is None: progobj = self.notfound_program(args) @@ -1487,14 +1491,15 @@ external dependencies (including libraries) must go to "dependencies".''') if version_func: version = version_func(progobj) elif isinstance(progobj, build.Executable): - interp = self if progobj.subproject: interp = self.subprojects[progobj.subproject].held_object - assert isinstance(interp, Interpreter) + else: + interp = self + assert isinstance(interp, Interpreter) version = interp.project_version else: version = progobj.get_version(self) - is_found, not_found, found = mesonlib.version_compare_many(version, wanted) + is_found, not_found, _ = mesonlib.version_compare_many(version, wanted) if not is_found: mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'), 'found', mlog.normal_cyan(version), 'but need:', @@ -1513,7 +1518,9 @@ external dependencies (including libraries) must go to "dependencies".''') progobj.was_returned_by_find_program = True return progobj - def program_lookup(self, args, for_machine, required: bool, search_dirs, extra_info): + def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice, + required: bool, search_dirs: T.List[str], extra_info: T.List[mlog.TV_Loggable] + ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: progobj = self.program_from_overrides(args, extra_info) if progobj: return progobj @@ -1536,7 +1543,9 @@ external dependencies (including libraries) must go to "dependencies".''') return progobj - def find_program_fallback(self, fallback: str, args, required: bool, extra_info): + def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrString], + required: bool, extra_info: T.List[mlog.TV_Loggable] + ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', mlog.bold(' '.join(args))) sp_kwargs: kwargs.DoSubproject = {