diff options
Diffstat (limited to 'mesonbuild/modules/sourceset.py')
-rw-r--r-- | mesonbuild/modules/sourceset.py | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/mesonbuild/modules/sourceset.py b/mesonbuild/modules/sourceset.py new file mode 100644 index 0000000..c35416e --- /dev/null +++ b/mesonbuild/modules/sourceset.py @@ -0,0 +1,307 @@ +# Copyright 2019 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 typing as T + +from . import ExtensionModule, ModuleObject, MutableModuleObject, ModuleInfo +from .. import build +from .. import dependencies +from .. import mesonlib +from ..interpreterbase import ( + noPosargs, noKwargs, + InterpreterException, InvalidArguments, InvalidCode, FeatureNew, +) +from ..interpreterbase.decorators import ContainerTypeInfo, KwargInfo, typed_kwargs, typed_pos_args +from ..mesonlib import OrderedSet + +if T.TYPE_CHECKING: + from typing_extensions import TypedDict + + from . import ModuleState + from ..interpreter import Interpreter + from ..interpreterbase import TYPE_var, TYPE_kwargs + + class AddKwargs(TypedDict): + + when: T.List[T.Union[str, dependencies.Dependency]] + if_true: T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes, dependencies.Dependency]] + if_false: T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]] + + class AddAllKw(TypedDict): + + when: T.List[T.Union[str, dependencies.Dependency]] + if_true: T.List[SourceSetImpl] + + class ApplyKw(TypedDict): + + strict: bool + + +_WHEN_KW: KwargInfo[T.List[T.Union[str, dependencies.Dependency]]] = KwargInfo( + 'when', + ContainerTypeInfo(list, (str, dependencies.Dependency)), + listify=True, + default=[], +) + + +class SourceSetRule(T.NamedTuple): + keys: T.List[str] + """Configuration keys that enable this rule if true""" + + deps: T.List[dependencies.Dependency] + """Dependencies that enable this rule if true""" + + sources: T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]] + """Source files added when this rule's conditions are true""" + + extra_deps: T.List[dependencies.Dependency] + """Dependencies added when this rule's conditions are true, but + that do not make the condition false if they're absent.""" + + sourcesets: T.List[SourceSetImpl] + """Other sourcesets added when this rule's conditions are true""" + + if_false: T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]] + """Source files added when this rule's conditions are false""" + + +class SourceFiles(T.NamedTuple): + sources: OrderedSet[T.Union[mesonlib.FileOrString, build.GeneratedTypes]] + deps: OrderedSet[dependencies.Dependency] + + +class SourceSet: + """Base class to avoid circular references. + + Because of error messages, this class is called SourceSet, and the actual + implementation is an Impl. + """ + + +class SourceSetImpl(SourceSet, MutableModuleObject): + def __init__(self, interpreter: Interpreter): + super().__init__() + self.rules: T.List[SourceSetRule] = [] + self.subproject = interpreter.subproject + self.environment = interpreter.environment + self.subdir = interpreter.subdir + self.frozen = False + self.methods.update({ + 'add': self.add_method, + 'add_all': self.add_all_method, + 'all_sources': self.all_sources_method, + 'all_dependencies': self.all_dependencies_method, + 'apply': self.apply_method, + }) + + def check_source_files(self, args: T.Sequence[T.Union[mesonlib.FileOrString, build.GeneratedTypes, dependencies.Dependency]], + ) -> T.Tuple[T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]], T.List[dependencies.Dependency]]: + sources: T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]] = [] + deps: T.List[dependencies.Dependency] = [] + for x in args: + if isinstance(x, dependencies.Dependency): + deps.append(x) + else: + sources.append(x) + to_check: T.List[str] = [] + + # Get the actual output names to check + for s in sources: + if isinstance(s, str): + to_check.append(s) + elif isinstance(s, mesonlib.File): + to_check.append(s.fname) + else: + to_check.extend(s.get_outputs()) + mesonlib.check_direntry_issues(to_check) + return sources, deps + + def check_conditions(self, args: T.Sequence[T.Union[str, dependencies.Dependency]] + ) -> T.Tuple[T.List[str], T.List[dependencies.Dependency]]: + keys: T.List[str] = [] + deps: T.List[dependencies.Dependency] = [] + for x in args: + if isinstance(x, str): + keys.append(x) + else: + deps.append(x) + return keys, deps + + @typed_pos_args('sourceset.add', varargs=(str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex, dependencies.Dependency)) + @typed_kwargs( + 'sourceset.add', + _WHEN_KW, + KwargInfo( + 'if_true', + ContainerTypeInfo(list, (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex, dependencies.Dependency)), + listify=True, + default=[], + ), + KwargInfo( + 'if_false', + ContainerTypeInfo(list, (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex)), + listify=True, + default=[], + ), + ) + def add_method(self, state: ModuleState, + args: T.Tuple[T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes, dependencies.Dependency]]], + kwargs: AddKwargs) -> None: + if self.frozen: + raise InvalidCode('Tried to use \'add\' after querying the source set') + when = kwargs['when'] + if_true = kwargs['if_true'] + if_false = kwargs['if_false'] + if not any([when, if_true, if_false]): + if_true = args[0] + elif args[0]: + raise InterpreterException('add called with both positional and keyword arguments') + keys, dependencies = self.check_conditions(when) + sources, extra_deps = self.check_source_files(if_true) + if_false, _ = self.check_source_files(if_false) + self.rules.append(SourceSetRule(keys, dependencies, sources, extra_deps, [], if_false)) + + @typed_pos_args('sourceset.add_all', varargs=SourceSet) + @typed_kwargs( + 'sourceset.add_all', + _WHEN_KW, + KwargInfo( + 'if_true', + ContainerTypeInfo(list, SourceSet), + listify=True, + default=[], + ) + ) + def add_all_method(self, state: ModuleState, args: T.Tuple[T.List[SourceSetImpl]], + kwargs: AddAllKw) -> None: + if self.frozen: + raise InvalidCode('Tried to use \'add_all\' after querying the source set') + when = kwargs['when'] + if_true = kwargs['if_true'] + if not when and not if_true: + if_true = args[0] + elif args[0]: + raise InterpreterException('add_all called with both positional and keyword arguments') + keys, dependencies = self.check_conditions(when) + for s in if_true: + if not isinstance(s, SourceSetImpl): + raise InvalidCode('Arguments to \'add_all\' after the first must be source sets') + s.frozen = True + self.rules.append(SourceSetRule(keys, dependencies, [], [], if_true, [])) + + def collect(self, enabled_fn: T.Callable[[str], bool], + all_sources: bool, + into: T.Optional['SourceFiles'] = None) -> SourceFiles: + if not into: + into = SourceFiles(OrderedSet(), OrderedSet()) + for entry in self.rules: + if all(x.found() for x in entry.deps) and \ + all(enabled_fn(key) for key in entry.keys): + into.sources.update(entry.sources) + into.deps.update(entry.deps) + into.deps.update(entry.extra_deps) + for ss in entry.sourcesets: + ss.collect(enabled_fn, all_sources, into) + if not all_sources: + continue + into.sources.update(entry.if_false) + return into + + @noKwargs + @noPosargs + def all_sources_method(self, state: ModuleState, args: T.List[TYPE_var], kwargs: TYPE_kwargs + ) -> T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]]: + self.frozen = True + files = self.collect(lambda x: True, True) + return list(files.sources) + + @noKwargs + @noPosargs + @FeatureNew('source_set.all_dependencies() method', '0.52.0') + def all_dependencies_method(self, state: ModuleState, args: T.List[TYPE_var], kwargs: TYPE_kwargs + ) -> T.List[dependencies.Dependency]: + self.frozen = True + files = self.collect(lambda x: True, True) + return list(files.deps) + + @typed_pos_args('sourceset.apply', (build.ConfigurationData, dict)) + @typed_kwargs('sourceset.apply', KwargInfo('strict', bool, default=True)) + def apply_method(self, state: ModuleState, args: T.Tuple[T.Union[build.ConfigurationData, T.Dict[str, TYPE_var]]], kwargs: ApplyKw) -> SourceFilesObject: + config_data = args[0] + self.frozen = True + strict = kwargs['strict'] + if isinstance(config_data, dict): + def _get_from_config_data(key: str) -> bool: + assert isinstance(config_data, dict), 'for mypy' + if strict and key not in config_data: + raise InterpreterException(f'Entry {key} not in configuration dictionary.') + return bool(config_data.get(key, False)) + else: + config_cache: T.Dict[str, bool] = {} + + def _get_from_config_data(key: str) -> bool: + assert isinstance(config_data, build.ConfigurationData), 'for mypy' + if key not in config_cache: + if key in config_data: + config_cache[key] = bool(config_data.get(key)[0]) + elif strict: + raise InvalidArguments(f'sourceset.apply: key "{key}" not in passed configuration, and strict set.') + else: + config_cache[key] = False + return config_cache[key] + + files = self.collect(_get_from_config_data, False) + res = SourceFilesObject(files) + return res + +class SourceFilesObject(ModuleObject): + def __init__(self, files: SourceFiles): + super().__init__() + self.files = files + self.methods.update({ + 'sources': self.sources_method, + 'dependencies': self.dependencies_method, + }) + + @noPosargs + @noKwargs + def sources_method(self, state: ModuleState, args: T.List[TYPE_var], kwargs: TYPE_kwargs + ) -> T.List[T.Union[mesonlib.FileOrString, build.GeneratedTypes]]: + return list(self.files.sources) + + @noPosargs + @noKwargs + def dependencies_method(self, state: ModuleState, args: T.List[TYPE_var], kwargs: TYPE_kwargs + ) -> T.List[dependencies.Dependency]: + return list(self.files.deps) + +class SourceSetModule(ExtensionModule): + + INFO = ModuleInfo('sourceset', '0.51.0') + + def __init__(self, interpreter: Interpreter): + super().__init__(interpreter) + self.methods.update({ + 'source_set': self.source_set, + }) + + @noKwargs + @noPosargs + def source_set(self, state: ModuleState, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> SourceSetImpl: + return SourceSetImpl(self.interpreter) + +def initialize(interp: Interpreter) -> SourceSetModule: + return SourceSetModule(interp) |