Merge pull request #10043 from dcbaker/submit/type-checking-for-subproject

Add typing for subproject()
pull/10081/head
Jussi Pakkanen 3 years ago committed by GitHub
commit ade6e3a19e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      mesonbuild/build.py
  2. 2
      mesonbuild/coredata.py
  3. 51
      mesonbuild/interpreter/dependencyfallbacks.py
  4. 148
      mesonbuild/interpreter/interpreter.py
  5. 9
      mesonbuild/interpreter/interpreterobjects.py
  6. 15
      mesonbuild/interpreter/kwargs.py
  7. 2
      mesonbuild/mintro.py
  8. 62
      mesonbuild/modules/cmake.py
  9. 4
      mesonbuild/modules/keyval.py
  10. 2
      test cases/failing/120 subproject version conflict/test.json
  11. 2
      test cases/failing/21 subver/test.json

@ -255,7 +255,7 @@ class Build:
self.test_setups: T.Dict[str, TestSetup] = {} self.test_setups: T.Dict[str, TestSetup] = {}
self.test_setup_default_name = None self.test_setup_default_name = None
self.find_overrides: T.Dict[str, T.Union['Executable', programs.ExternalProgram, programs.OverrideProgram]] = {} 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 # 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. # build == host compilation the both caches should point to the same place.
@ -279,7 +279,7 @@ class Build:
custom_targets[name] = t custom_targets[name] = t
return custom_targets return custom_targets
def copy(self): def copy(self) -> Build:
other = Build(self.environment) other = Build(self.environment)
for k, v in self.__dict__.items(): for k, v in self.__dict__.items():
if isinstance(v, (list, dict, set, OrderedDict)): if isinstance(v, (list, dict, set, OrderedDict)):
@ -288,11 +288,11 @@ class Build:
other.__dict__[k] = v other.__dict__[k] = v
return other return other
def merge(self, other): def merge(self, other: Build) -> None:
for k, v in other.__dict__.items(): for k, v in other.__dict__.items():
self.__dict__[k] = v 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(): 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) self.static_linker[compiler.for_machine] = detect_static_linker(self.environment, compiler)

@ -613,7 +613,7 @@ class CoreData:
'Default project to execute in Visual Studio', '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: try:
v = self.options[key].value v = self.options[key].value
if key.name == 'wrap_mode': if key.name == 'wrap_mode':

@ -4,7 +4,7 @@ from .. import mlog
from .. import dependencies from .. import dependencies
from .. import build from .. import build
from ..wrap import WrapMode 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 ..dependencies import Dependency, DependencyException, NotFoundDependency
from ..interpreterbase import (MesonInterpreterObject, FeatureNew, from ..interpreterbase import (MesonInterpreterObject, FeatureNew,
InterpreterException, InvalidArguments, InterpreterException, InvalidArguments,
@ -26,10 +26,12 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
self.environment = interpreter.environment self.environment = interpreter.environment
self.wrap_resolver = interpreter.environment.wrap_resolver self.wrap_resolver = interpreter.environment.wrap_resolver
self.allow_fallback = allow_fallback self.allow_fallback = allow_fallback
self.subproject_name = None self.subproject_name: T.Optional[str] = None
self.subproject_varname = None self.subproject_varname: T.Optional[str] = None
self.subproject_kwargs = {'default_options': default_options or []} self.subproject_kwargs = {'default_options': default_options or []}
self.names: T.List[str] = [] self.names: T.List[str] = []
self.forcefallback: bool = False
self.nofallback: bool = False
for name in names: for name in names:
if not name: if not name:
raise InterpreterException('dependency_fallbacks empty name \'\' is not allowed') raise InterpreterException('dependency_fallbacks empty name \'\' is not allowed')
@ -39,6 +41,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
if name in self.names: if name in self.names:
raise InterpreterException('dependency_fallbacks name {name!r} is duplicated') raise InterpreterException('dependency_fallbacks name {name!r} is duplicated')
self.names.append(name) 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: def set_fallback(self, fbinfo: T.Optional[T.Union[T.List[str], str]]) -> None:
# Legacy: This converts dependency()'s fallback kwargs. # Legacy: This converts dependency()'s fallback kwargs.
@ -105,14 +108,14 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]: def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
if self.forcefallback: if self.forcefallback:
mlog.log('Looking for a fallback subproject for the dependency', 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: elif self.nofallback:
mlog.log('Not looking for a fallback subproject for the dependency', 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 return None
else: else:
mlog.log('Looking for a fallback subproject for the dependency', 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 # dependency('foo', static: true) should implicitly add
# default_options: ['default_library=static'] # default_options: ['default_library=static']
@ -128,6 +131,9 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
# Configure the subproject # Configure the subproject
subp_name = self.subproject_name subp_name = self.subproject_name
varname = self.subproject_varname 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) self.interpreter.do_subproject(subp_name, 'meson', func_kwargs)
return self._get_subproject_dep(subp_name, varname, kwargs) return self._get_subproject_dep(subp_name, varname, kwargs)
@ -141,7 +147,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
# Verify the subproject is found # Verify the subproject is found
subproject = self._get_subproject(subp_name) subproject = self._get_subproject(subp_name)
if not subproject: 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.bold(subp_name), 'found:', mlog.red('NO'),
mlog.blue('(subproject failed to configure)')) mlog.blue('(subproject failed to configure)'))
return None return None
@ -167,27 +173,27 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
# Legacy: Use the variable name if provided instead of relying on the # Legacy: Use the variable name if provided instead of relying on the
# subproject to override one of our dependency names # subproject to override one of our dependency names
if not varname: if not varname:
mlog.warning(f'Subproject {subp_name!r} did not override {self.display_name!r} dependency and no variable name specified') 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.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO')) mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
return self._notfound_dependency() return self._notfound_dependency()
var_dep = self._get_subproject_variable(subproject, varname) or self._notfound_dependency() var_dep = self._get_subproject_variable(subproject, varname) or self._notfound_dependency()
if not var_dep.found(): 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')) mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
return var_dep return var_dep
wanted = stringlistify(kwargs.get('version', [])) wanted = stringlistify(kwargs.get('version', []))
found = var_dep.get_version() found = var_dep.get_version()
if not self._check_version(wanted, found): 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'), mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found), 'but need:', 'found', mlog.normal_cyan(found), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted]))) mlog.bold(', '.join([f"'{e}'" for e in wanted])))
return self._notfound_dependency() 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.bold(subproject.subdir), 'found:', mlog.green('YES'),
mlog.normal_cyan(found) if found else None) mlog.normal_cyan(found) if found else None)
return var_dep return var_dep
@ -209,7 +215,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
# have explicitly called meson.override_dependency() with a not-found # have explicitly called meson.override_dependency() with a not-found
# dep. # dep.
if not cached_dep.found(): 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) 'found:', mlog.red('NO'), *info)
return cached_dep return cached_dep
else: else:
@ -231,7 +237,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
return self._notfound_dependency() return self._notfound_dependency()
if found_vers: if found_vers:
info = [mlog.normal_cyan(found_vers), *info] 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) 'found:', mlog.green('YES'), *info)
return cached_dep return cached_dep
return None return None
@ -247,7 +253,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
return None return None
return var_dep 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 subp_name = self.subproject_name
varname = self.subproject_varname varname = self.subproject_varname
subproject = self._get_subproject(subp_name) subproject = self._get_subproject(subp_name)
@ -273,12 +279,10 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
return NotFoundDependency(self.names[0] if self.names else '', self.environment) return NotFoundDependency(self.names[0] if self.names else '', self.environment)
@staticmethod @staticmethod
def _check_version(wanted: T.Optional[str], found: str) -> bool: def _check_version(wanted: T.List[str], found: str) -> bool:
if not wanted: if not wanted:
return True return True
if found == 'undefined' or not version_compare_many(found, wanted)[0]: return not (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[Dependency]], TYPE_nvar, TYPE_nkwargs]]: def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[Dependency]], TYPE_nvar, TYPE_nkwargs]]:
candidates = [] candidates = []
@ -298,19 +302,20 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
return candidates return candidates
def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency: 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') mods = extract_as_list(kwargs, 'modules')
if mods: 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) disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled: 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() return self._notfound_dependency()
# Check if usage of the subproject fallback is forced # Check if usage of the subproject fallback is forced
wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) 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')) 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.nofallback = wrap_mode == WrapMode.nofallback
self.forcefallback = (force_fallback or self.forcefallback = (force_fallback or
wrap_mode == WrapMode.forcefallback or wrap_mode == WrapMode.forcefallback or
@ -359,7 +364,7 @@ class DependencyFallbacksHolder(MesonInterpreterObject):
# This was the last candidate or the dependency has been cached # This was the last candidate or the dependency has been cached
# as not-found, or cached dependency version does not match, # as not-found, or cached dependency version does not match,
# otherwise func() would have returned None instead. # 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: elif dep:
# Same as above, but the dependency is not required. # Same as above, but the dependency is not required.
return dep return dep

@ -33,7 +33,7 @@ from ..interpreterbase import InterpreterException, InvalidArguments, InvalidCod
from ..interpreterbase import Disabler, disablerIfNotFound from ..interpreterbase import Disabler, disablerIfNotFound
from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs from ..interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
from ..interpreterbase import ObjectHolder 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 ..modules import ExtensionModule, ModuleObject, MutableModuleObject, NewExtensionModule, NotFoundExtensionModule
from ..cmake import CMakeInterpreter from ..cmake import CMakeInterpreter
from ..backend.backends import Backend, ExecutableSerialisation from ..backend.backends import Backend, ExecutableSerialisation
@ -93,6 +93,8 @@ import importlib
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
import argparse import argparse
from typing_extensions import Literal
from . import kwargs from . import kwargs
from ..programs import OverrideProgram from ..programs import OverrideProgram
@ -115,7 +117,7 @@ def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None])
return None return None
def stringifyUserArguments(args, quote=False): def stringifyUserArguments(args: T.List[T.Any], quote: bool = False) -> str:
if isinstance(args, list): if isinstance(args, list):
return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args]) return '[%s]' % ', '.join([stringifyUserArguments(x, True) for x in args])
elif isinstance(args, dict): elif isinstance(args, dict):
@ -258,7 +260,7 @@ class Interpreter(InterpreterBase, HoldableObject):
subproject: str = '', subproject: str = '',
subdir: str = '', subdir: str = '',
subproject_dir: str = 'subprojects', 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, mock: bool = False,
ast: T.Optional[mparser.CodeBlockNode] = None, ast: T.Optional[mparser.CodeBlockNode] = None,
is_translated: bool = False, is_translated: bool = False,
@ -271,7 +273,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.coredata = self.environment.get_coredata() self.coredata = self.environment.get_coredata()
self.backend = backend self.backend = backend
self.summary: T.Dict[str, 'Summary'] = {} 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 # Subproject directory is usually the name of the subproject, but can
# be different for dependencies provided by wrap files. # be different for dependencies provided by wrap files.
self.subproject_directory_name = subdir.split(os.path.sep)[-1] self.subproject_directory_name = subdir.split(os.path.sep)[-1]
@ -289,14 +291,14 @@ class Interpreter(InterpreterBase, HoldableObject):
self.project_args_frozen = False self.project_args_frozen = False
self.global_args_frozen = False # implies self.project_args_frozen self.global_args_frozen = False # implies self.project_args_frozen
self.subprojects: T.Dict[str, SubprojectHolder] = {} self.subprojects: T.Dict[str, SubprojectHolder] = {}
self.subproject_stack = [] 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. # Passed from the outside, only used in subprojects.
if default_project_options: if default_project_options:
self.default_project_options = default_project_options.copy() self.default_project_options = default_project_options.copy()
else: else:
self.default_project_options = {} self.default_project_options = {}
self.project_default_options = {} self.project_default_options: T.Dict[OptionKey, str] = {}
self.build_func_dict() self.build_func_dict()
self.build_holder_map() self.build_holder_map()
self.user_defined_options = user_defined_options self.user_defined_options = user_defined_options
@ -306,10 +308,10 @@ class Interpreter(InterpreterBase, HoldableObject):
# For non-meson subprojects, we'll be using the ast. Even if it does # 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 # exist we don't want to add a dependency on it, it's autogenerated
# from the actual build files, and is just for reference. # 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) build_filename = os.path.join(self.subdir, environment.build_filename)
if not is_translated: if not is_translated:
self.build_def_files.append(build_filename) self.build_def_files.add(build_filename)
if not mock: if not mock:
self.parse_project() self.parse_project()
self._redetect_machines() self._redetect_machines()
@ -317,7 +319,7 @@ class Interpreter(InterpreterBase, HoldableObject):
def __getnewargs_ex__(self) -> T.Tuple[T.Tuple[object], T.Dict[str, object]]: def __getnewargs_ex__(self) -> T.Tuple[T.Tuple[object], T.Dict[str, object]]:
raise MesonBugException('This class is unpicklable') 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 # 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 # have the compilers needed to gain more knowledge, so wipe out old
# inference and start over. # inference and start over.
@ -335,7 +337,7 @@ class Interpreter(InterpreterBase, HoldableObject):
self.builtin['target_machine'] = \ self.builtin['target_machine'] = \
OBJ.MachineHolder(self.build.environment.machines.target, self) 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, self.funcs.update({'add_global_arguments': self.func_add_global_arguments,
'add_global_link_arguments': self.func_add_global_link_arguments, 'add_global_link_arguments': self.func_add_global_link_arguments,
'add_languages': self.func_add_languages, 'add_languages': self.func_add_languages,
@ -501,7 +503,7 @@ class Interpreter(InterpreterBase, HoldableObject):
else: else:
raise InterpreterException(f'Module returned a value of unknown type {v!r}.') 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 return self.build_def_files
def add_build_def_file(self, f: mesonlib.FileOrString) -> None: def add_build_def_file(self, f: mesonlib.FileOrString) -> None:
@ -517,32 +519,32 @@ class Interpreter(InterpreterBase, HoldableObject):
srcdir = Path(self.environment.get_source_dir()) srcdir = Path(self.environment.get_source_dir())
builddir = Path(self.environment.get_build_dir()) builddir = Path(self.environment.get_build_dir())
try: try:
f = Path(f).resolve() f_ = Path(f).resolve()
except OSError: except OSError:
f = Path(f) f_ = Path(f)
s = f.stat() s = f_.stat()
if (hasattr(s, 'st_file_attributes') and if (hasattr(s, 'st_file_attributes') and
s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and
s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK): s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK):
# This is a Windows Store link which we can't # This is a Windows Store link which we can't
# resolve, so just do our best otherwise. # resolve, so just do our best otherwise.
f = f.parent.resolve() / f.name f_ = f_.parent.resolve() / f_.name
else: else:
raise raise
if builddir in f.parents: if builddir in f_.parents:
return return
if srcdir in f.parents: if srcdir in f_.parents:
f = f.relative_to(srcdir) f_ = f_.relative_to(srcdir)
f = str(f) f = str(f_)
else: else:
return return
if f not in self.build_def_files: if f not in self.build_def_files:
self.build_def_files.append(f) self.build_def_files.add(f)
def get_variables(self): def get_variables(self) -> T.Dict[str, InterpreterObject]:
return self.variables return self.variables
def check_stdlibs(self): def check_stdlibs(self) -> None:
machine_choices = [MachineChoice.HOST] machine_choices = [MachineChoice.HOST]
if self.coredata.is_cross_build(): if self.coredata.is_cross_build():
machine_choices.append(MachineChoice.BUILD) machine_choices.append(MachineChoice.BUILD)
@ -563,7 +565,7 @@ class Interpreter(InterpreterBase, HoldableObject):
dep = df.lookup(kwargs, force_fallback=True) dep = df.lookup(kwargs, force_fallback=True)
self.build.stdlibs[for_machine][l] = dep 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: if modname in self.modules:
return self.modules[modname] return self.modules[modname]
try: try:
@ -797,24 +799,37 @@ external dependencies (including libraries) must go to "dependencies".''')
@FeatureNewKwargs('subproject', '0.38.0', ['default_options']) @FeatureNewKwargs('subproject', '0.38.0', ['default_options'])
@permittedKwargs({'version', 'default_options', 'required'}) @permittedKwargs({'version', 'default_options', 'required'})
@typed_pos_args('subproject', str) @typed_pos_args('subproject', str)
def func_subproject(self, nodes: mparser.BaseNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> SubprojectHolder: @typed_kwargs(
return self.do_subproject(args[0], 'meson', 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, disabled_feature=None, exception=None): def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str] = None,
exception: T.Optional[Exception] = None) -> SubprojectHolder:
sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name), sub = SubprojectHolder(NullSubprojectInterpreter(), os.path.join(self.subproject_dir, subp_name),
disabled_feature=disabled_feature, exception=exception) disabled_feature=disabled_feature, exception=exception)
self.subprojects[subp_name] = sub self.subprojects[subp_name] = sub
self.coredata.initialized_subprojects.add(subp_name) self.coredata.initialized_subprojects.add(subp_name)
return sub return sub
def do_subproject(self, subp_name: str, method: str, kwargs): def do_subproject(self, subp_name: str, method: Literal['meson', 'cmake'], kwargs: kwargs.DoSubproject) -> SubprojectHolder:
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled: if disabled:
mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled') mlog.log('Subproject', mlog.bold(subp_name), ':', 'skipped: feature', mlog.bold(feature), 'disabled')
return self.disabled_subproject(subp_name, disabled_feature=feature) return self.disabled_subproject(subp_name, disabled_feature=feature)
default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) default_options = coredata.create_options_dict(kwargs['default_options'], subp_name)
default_options = coredata.create_options_dict(default_options, subp_name)
if subp_name == '': if subp_name == '':
raise InterpreterException('Subproject name must not be empty.') raise InterpreterException('Subproject name must not be empty.')
@ -835,7 +850,7 @@ external dependencies (including libraries) must go to "dependencies".''')
subproject = self.subprojects[subp_name] subproject = self.subprojects[subp_name]
if required and not subproject.found(): if required and not subproject.found():
raise InterpreterException(f'Subproject "{subproject.subdir}" required but not found.') raise InterpreterException(f'Subproject "{subproject.subdir}" required but not found.')
if 'version' in kwargs: if kwargs['version']:
pv = self.build.subprojects[subp_name] pv = self.build.subprojects[subp_name]
wanted = kwargs['version'] wanted = kwargs['version']
if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]: if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
@ -882,7 +897,9 @@ external dependencies (including libraries) must go to "dependencies".''')
return self.disabled_subproject(subp_name, exception=e) return self.disabled_subproject(subp_name, exception=e)
raise 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, ast: T.Optional[mparser.CodeBlockNode] = None,
build_def_files: T.Optional[T.List[str]] = None, build_def_files: T.Optional[T.List[str]] = None,
is_translated: bool = False) -> SubprojectHolder: is_translated: bool = False) -> SubprojectHolder:
@ -911,7 +928,7 @@ external dependencies (including libraries) must go to "dependencies".''')
mlog.log() mlog.log()
if 'version' in kwargs: if kwargs['version']:
pv = subi.project_version pv = subi.project_version
wanted = kwargs['version'] wanted = kwargs['version']
if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]: if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]:
@ -921,27 +938,24 @@ external dependencies (including libraries) must go to "dependencies".''')
self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings) self.subprojects[subp_name] = SubprojectHolder(subi, subdir, warnings=subi_warnings)
# Duplicates are possible when subproject uses files from project root # Duplicates are possible when subproject uses files from project root
if build_def_files: 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 # 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.merge(subi.build)
self.build.subprojects[subp_name] = subi.project_version self.build.subprojects[subp_name] = subi.project_version
self.coredata.initialized_subprojects.add(subp_name) self.coredata.initialized_subprojects.add(subp_name)
return self.subprojects[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: T.Dict[OptionKey, str],
kwargs: kwargs.DoSubproject) -> SubprojectHolder:
with mlog.nested(subp_name): with mlog.nested(subp_name):
new_build = self.build.copy() new_build = self.build.copy()
prefix = self.coredata.options[OptionKey('prefix')].value prefix = self.coredata.options[OptionKey('prefix')].value
from ..modules.cmake import CMakeSubprojectOptions from ..modules.cmake import CMakeSubprojectOptions
options = kwargs.get('options', CMakeSubprojectOptions()) options = kwargs['options'] or CMakeSubprojectOptions()
if not isinstance(options, CMakeSubprojectOptions): cmake_options = kwargs['cmake_options'] + options.cmake_options
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
cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend) cm_int = CMakeInterpreter(new_build, Path(subdir), Path(subdir_abs), Path(prefix), new_build.environment, self.backend)
cm_int.initialise(cmake_options) cm_int.initialise(cmake_options)
cm_int.analyse() cm_int.analyse()
@ -967,13 +981,13 @@ external dependencies (including libraries) must go to "dependencies".''')
mlog.cmd_ci_include(meson_filename) mlog.cmd_ci_include(meson_filename)
mlog.log() 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 result.cm_interpreter = cm_int
mlog.log() mlog.log()
return result 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) key = OptionKey.from_string(optname).evolve(subproject=self.subproject)
if not key.is_project(): if not key.is_project():
@ -982,6 +996,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if v is None or v.yielding: if v is None or v.yielding:
v = opts.get(key.as_root()) v = opts.get(key.as_root())
if v is not None: if v is not None:
assert isinstance(v, coredata.UserOption), 'for mypy'
return v return v
try: try:
@ -1037,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}"') f'"configuration_data": initial value dictionary key "{k!r}"" must be "str | int | bool", not "{v!r}"')
return build.ConfigurationData(initial_values) return build.ConfigurationData(initial_values)
def set_backend(self): def set_backend(self) -> None:
# The backend is already set when parsing subprojects # The backend is already set when parsing subprojects
if self.backend is not None: if self.backend is not None:
return return
@ -1326,7 +1341,7 @@ external dependencies (including libraries) must go to "dependencies".''')
return False return False
return should 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] args = [a.lower() for a in args]
langs = set(self.coredata.compilers[for_machine].keys()) langs = set(self.coredata.compilers[for_machine].keys())
langs.update(args) langs.update(args)
@ -1378,7 +1393,8 @@ external dependencies (including libraries) must go to "dependencies".''')
return success 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: for p in prognames:
if isinstance(p, mesonlib.File): if isinstance(p, mesonlib.File):
continue # Always points to a local (i.e. self generated) file. continue # Always points to a local (i.e. self generated) file.
@ -1389,7 +1405,8 @@ external dependencies (including libraries) must go to "dependencies".''')
return prog return prog
return None 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. # Search for scripts relative to current subdir.
# Do not cache found programs because find_program('foobar') # Do not cache found programs because find_program('foobar')
# might give different results when run from different source dirs. # might give different results when run from different source dirs.
@ -1415,9 +1432,11 @@ external dependencies (including libraries) must go to "dependencies".''')
if extprog.found(): if extprog.found():
extra_info.append(f"({' '.join(extprog.get_command())})") extra_info.append(f"({' '.join(extprog.get_command())})")
return extprog return extprog
return None
def program_from_overrides(self, command_names: T.List[str], extra_info: T.List['mlog.TV_Loggable']) -> \ def program_from_overrides(self, command_names: T.List[mesonlib.FileOrString],
T.Optional[T.Union[ExternalProgram, 'OverrideProgram', 'build.Executable']]: extra_info: T.List['mlog.TV_Loggable']
) -> T.Optional[T.Union[ExternalProgram, OverrideProgram, build.Executable]]:
for name in command_names: for name in command_names:
if not isinstance(name, str): if not isinstance(name, str):
continue continue
@ -1427,7 +1446,7 @@ external dependencies (including libraries) must go to "dependencies".''')
return exe return exe
return None 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: for name in command_names:
if isinstance(name, str): if isinstance(name, str):
self.build.searched_programs.add(name) self.build.searched_programs.add(name)
@ -1455,7 +1474,7 @@ external dependencies (including libraries) must go to "dependencies".''')
) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']: ) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']:
args = mesonlib.listify(args) 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) progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info)
if progobj is None: if progobj is None:
progobj = self.notfound_program(args) progobj = self.notfound_program(args)
@ -1472,14 +1491,15 @@ external dependencies (including libraries) must go to "dependencies".''')
if version_func: if version_func:
version = version_func(progobj) version = version_func(progobj)
elif isinstance(progobj, build.Executable): elif isinstance(progobj, build.Executable):
interp = self
if progobj.subproject: if progobj.subproject:
interp = self.subprojects[progobj.subproject].held_object interp = self.subprojects[progobj.subproject].held_object
else:
interp = self
assert isinstance(interp, Interpreter) assert isinstance(interp, Interpreter)
version = interp.project_version version = interp.project_version
else: else:
version = progobj.get_version(self) 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: if not is_found:
mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'), mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(version), 'but need:', 'found', mlog.normal_cyan(version), 'but need:',
@ -1498,7 +1518,9 @@ external dependencies (including libraries) must go to "dependencies".''')
progobj.was_returned_by_find_program = True progobj.was_returned_by_find_program = True
return progobj return progobj
def program_lookup(self, args, for_machine, required, 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) progobj = self.program_from_overrides(args, extra_info)
if progobj: if progobj:
return progobj return progobj
@ -1521,10 +1543,18 @@ external dependencies (including libraries) must go to "dependencies".''')
return progobj return progobj
def find_program_fallback(self, fallback, args, required, 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.log('Fallback to subproject', mlog.bold(fallback), 'which provides program',
mlog.bold(' '.join(args))) 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) self.do_subproject(fallback, 'meson', sp_kwargs)
return self.program_from_overrides(args, extra_info) return self.program_from_overrides(args, extra_info)
@ -2115,7 +2145,7 @@ external dependencies (including libraries) must go to "dependencies".''')
self.subdir = subdir self.subdir = subdir
os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True) os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True)
buildfilename = os.path.join(self.subdir, environment.build_filename) 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) absname = os.path.join(self.environment.get_source_dir(), buildfilename)
if not os.path.isfile(absname): if not os.path.isfile(absname):
self.subdir = prev_subdir self.subdir = prev_subdir

@ -1,3 +1,4 @@
from __future__ import annotations
import os import os
import shlex import shlex
import subprocess import subprocess
@ -23,15 +24,16 @@ from ..interpreterbase import (
from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW from ..interpreter.type_checking import NoneType, ENV_SEPARATOR_KW
from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..dependencies import Dependency, ExternalLibrary, InternalDependency
from ..programs import ExternalProgram 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 import typing as T
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from . import kwargs from . import kwargs
from .interpreter import Interpreter from ..cmake.interpreter import CMakeInterpreter
from ..envconfig import MachineInfo from ..envconfig import MachineInfo
from ..interpreterbase import SubProject from ..interpreterbase import SubProject
from .interpreter import Interpreter
from typing_extensions import TypedDict from typing_extensions import TypedDict
@ -683,13 +685,14 @@ class SubprojectHolder(MesonInterpreterObject):
subdir: str, subdir: str,
warnings: int = 0, warnings: int = 0,
disabled_feature: T.Optional[str] = None, disabled_feature: T.Optional[str] = None,
exception: T.Optional[MesonException] = None) -> None: exception: T.Optional[Exception] = None) -> None:
super().__init__() super().__init__()
self.held_object = subinterpreter self.held_object = subinterpreter
self.warnings = warnings self.warnings = warnings
self.disabled_feature = disabled_feature self.disabled_feature = disabled_feature
self.exception = exception self.exception = exception
self.subdir = PurePath(subdir).as_posix() self.subdir = PurePath(subdir).as_posix()
self.cm_interpreter: T.Optional[CMakeInterpreter] = None
self.methods.update({'get_variable': self.get_variable_method, self.methods.update({'get_variable': self.get_variable_method,
'found': self.found_method, 'found': self.found_method,
}) })

@ -12,6 +12,7 @@ from .. import build
from .. import coredata from .. import coredata
from ..compilers import Compiler from ..compilers import Compiler
from ..mesonlib import MachineChoice, File, FileMode, FileOrString, OptionKey from ..mesonlib import MachineChoice, File, FileMode, FileOrString, OptionKey
from ..modules.cmake import CMakeSubprojectOptions
from ..programs import ExternalProgram from ..programs import ExternalProgram
@ -293,3 +294,17 @@ class ConfigureFile(TypedDict):
command: T.Optional[T.List[T.Union[build.Executable, ExternalProgram, Compiler, File, str]]] command: T.Optional[T.List[T.Union[build.Executable, ExternalProgram, Compiler, File, str]]]
input: T.List[FileOrString] input: T.List[FileOrString]
configuration: T.Optional[T.Union[T.Dict[str, T.Union[str, int, bool]], build.ConfigurationData]] 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]

@ -326,7 +326,7 @@ def find_buildsystem_files_list(src_dir: str) -> T.List[str]:
def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]: def list_buildsystem_files(builddata: build.Build, interpreter: Interpreter) -> T.List[str]:
src_dir = builddata.environment.get_source_dir() 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] filelist = [PurePath(src_dir, x).as_posix() for x in filelist]
return filelist return filelist

@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from __future__ import annotations
import re import re
import os, os.path, pathlib import os, os.path, pathlib
import shutil import shutil
@ -21,11 +23,10 @@ from . import ExtensionModule, ModuleReturnValue, ModuleObject
from .. import build, mesonlib, mlog, dependencies from .. import build, mesonlib, mlog, dependencies
from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args
from ..interpreter import SubprojectHolder 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 ( from ..interpreterbase import (
FeatureNew, FeatureNew,
FeatureNewKwargs, FeatureNewKwargs,
FeatureDeprecatedKwargs,
stringArgs, stringArgs,
permittedKwargs, permittedKwargs,
@ -35,6 +36,7 @@ from ..interpreterbase import (
InvalidArguments, InvalidArguments,
InterpreterException, InterpreterException,
typed_pos_args,
typed_kwargs, typed_kwargs,
KwargInfo, KwargInfo,
ContainerTypeInfo, ContainerTypeInfo,
@ -43,6 +45,9 @@ from ..interpreterbase import (
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from typing_extensions import TypedDict from typing_extensions import TypedDict
from . import ModuleState
from ..interpreter import kwargs
class WriteBasicPackageVersionFile(TypedDict): class WriteBasicPackageVersionFile(TypedDict):
arch_independent: bool arch_independent: bool
@ -58,6 +63,12 @@ if T.TYPE_CHECKING:
install_dir: T.Optional[str] install_dir: T.Optional[str]
name: str name: str
class Subproject(kwargs.ExtractRequired):
options: T.Optional[CMakeSubprojectOptions]
cmake_options: T.List[str]
COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion'] COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion']
# Taken from https://github.com/Kitware/CMake/blob/master/Modules/CMakePackageConfigHelpers.cmake # Taken from https://github.com/Kitware/CMake/blob/master/Modules/CMakePackageConfigHelpers.cmake
@ -91,11 +102,12 @@ endmacro()
''' '''
class CMakeSubproject(ModuleObject): class CMakeSubproject(ModuleObject):
def __init__(self, subp, pv): def __init__(self, subp: SubprojectHolder):
assert isinstance(subp, SubprojectHolder) assert isinstance(subp, SubprojectHolder)
assert hasattr(subp, 'cm_interpreter') assert subp.cm_interpreter is not None
super().__init__() super().__init__()
self.subp = subp self.subp = subp
self.cm_interpreter = subp.cm_interpreter
self.methods.update({'get_variable': self.get_variable, self.methods.update({'get_variable': self.get_variable,
'dependency': self.dependency, 'dependency': self.dependency,
'include_directories': self.include_directories, 'include_directories': self.include_directories,
@ -110,7 +122,7 @@ class CMakeSubproject(ModuleObject):
raise InterpreterException('Exactly one argument is required.') raise InterpreterException('Exactly one argument is required.')
tgt = args[0] tgt = args[0]
res = self.subp.cm_interpreter.target_info(tgt) res = self.cm_interpreter.target_info(tgt)
if res is None: if res is None:
raise InterpreterException(f'The CMake target {tgt} does not exist\n' + 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' + ' Use the following command in your meson.build to list all available targets:\n\n' +
@ -161,7 +173,7 @@ class CMakeSubproject(ModuleObject):
@noPosargs @noPosargs
@noKwargs @noKwargs
def target_list(self, state, args, kwargs): def target_list(self, state, args, kwargs):
return self.subp.cm_interpreter.target_list() return self.cm_interpreter.target_list()
@noPosargs @noPosargs
@noKwargs @noKwargs
@ -391,8 +403,7 @@ class CmakeModule(ExtensionModule):
conf.used = True conf.used = True
conffile = os.path.normpath(inputfile.relative_name()) conffile = os.path.normpath(inputfile.relative_name())
if conffile not in self.interpreter.build_def_files: self.interpreter.build_def_files.add(conffile)
self.interpreter.build_def_files.append(conffile)
res = build.Data([mesonlib.File(True, ofile_path, ofile_fname)], install_dir, install_dir, None, state.subproject) res = build.Data([mesonlib.File(True, ofile_path, ofile_fname)], install_dir, install_dir, None, state.subproject)
self.interpreter.build.data.append(res) self.interpreter.build.data.append(res)
@ -400,20 +411,35 @@ class CmakeModule(ExtensionModule):
return res return res
@FeatureNew('subproject', '0.51.0') @FeatureNew('subproject', '0.51.0')
@FeatureNewKwargs('subproject', '0.55.0', ['options']) @typed_pos_args('cmake.subproject', str)
@FeatureDeprecatedKwargs('subproject', '0.55.0', ['cmake_options']) @typed_kwargs(
@permittedKwargs({'cmake_options', 'required', 'options'}) 'cmake.subproject',
@stringArgs REQUIRED_KW,
def subproject(self, state, args, kwargs): KwargInfo('options', (CMakeSubprojectOptions, NoneType), since='0.55.0'),
if len(args) != 1: KwargInfo(
raise InterpreterException('Subproject takes exactly one argument') 'cmake_options',
if 'cmake_options' in kwargs and 'options' in kwargs: 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"') raise InterpreterException('"options" cannot be used together with "cmake_options"')
dirname = args[0] 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(): if not subp.found():
return subp return subp
return CMakeSubproject(subp, dirname) return CMakeSubproject(subp)
@FeatureNew('subproject_options', '0.55.0') @FeatureNew('subproject_options', '0.55.0')
@noKwargs @noKwargs

@ -63,8 +63,8 @@ class KeyvalModule(ExtensionModule):
else: else:
s = os.path.join(self.interpreter.environment.source_dir, s) s = os.path.join(self.interpreter.environment.source_dir, s)
if s not in self.interpreter.build_def_files and not is_built: if not is_built:
self.interpreter.build_def_files.append(s) self.interpreter.build_def_files.add(s)
return self._load_file(s) return self._load_file(s)

@ -1,7 +1,7 @@
{ {
"stdout": [ "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."
} }
] ]
} }

@ -1,7 +1,7 @@
{ {
"stdout": [ "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."
} }
] ]
} }

Loading…
Cancel
Save