diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2021-06-16 20:11:46 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-16 20:11:46 +0300 |
commit | 6fb2f86379c224e99652748eea94a03321b9bd11 (patch) | |
tree | a96fb2b8f6468d793b64b172abc67b8224e0edc3 /mesonbuild | |
parent | 537adce5d803ff4ae373d87671190a4a2682ff54 (diff) | |
parent | 0f5e55a749f0fed6330b216a82de941de3ccf9d6 (diff) | |
download | meson-6fb2f86379c224e99652748eea94a03321b9bd11.zip meson-6fb2f86379c224e99652748eea94a03321b9bd11.tar.gz meson-6fb2f86379c224e99652748eea94a03321b9bd11.tar.bz2 |
Merge pull request #8822 from dcbaker/submit/annotate-and-check-qt-module
Rewrite the Qt module for type safety!
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/build.py | 137 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 34 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreterobjects.py | 95 | ||||
-rw-r--r-- | mesonbuild/interpreter/kwargs.py | 35 | ||||
-rw-r--r-- | mesonbuild/modules/__init__.py | 8 | ||||
-rw-r--r-- | mesonbuild/modules/qt.py | 436 | ||||
-rw-r--r-- | mesonbuild/modules/unstable_rust.py | 2 |
7 files changed, 484 insertions, 263 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py index a685664..436a55d 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -43,6 +43,7 @@ if T.TYPE_CHECKING: from ._typing import ImmutableListProtocol, ImmutableSetProtocol from .interpreter.interpreter import Test, SourceOutputs, Interpreter from .mesonlib import FileMode, FileOrString + from .modules import ModuleState from .backend.backends import Backend from .interpreter.interpreterobjects import GeneratorHolder @@ -1498,113 +1499,71 @@ You probably should put it in link_with instead.''') return class Generator: - def __init__(self, args, kwargs): - if len(args) != 1: - raise InvalidArguments('Generator requires exactly one positional argument: the executable') - exe = unholder(args[0]) - if not isinstance(exe, (Executable, programs.ExternalProgram)): - raise InvalidArguments('First generator argument must be an executable.') + def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], + arguments: T.List[str], + output: T.List[str], + *, + depfile: T.Optional[str] = None, + capture: bool = False, + depends: T.Optional[T.List[T.Union[BuildTarget, 'CustomTarget']]] = None, + name: str = 'Generator'): self.exe = exe - self.depfile = None - self.capture = False - self.depends = [] - self.process_kwargs(kwargs) + self.depfile = depfile + self.capture = capture + self.depends: T.List[T.Union[BuildTarget, 'CustomTarget']] = depends or [] + self.arglist = arguments + self.outputs = output + self.name = name - def __repr__(self): + def __repr__(self) -> str: repr_str = "<{0}: {1}>" return repr_str.format(self.__class__.__name__, self.exe) - def get_exe(self): + def get_exe(self) -> T.Union['Executable', programs.ExternalProgram]: return self.exe - def process_kwargs(self, kwargs): - if 'arguments' not in kwargs: - raise InvalidArguments('Generator must have "arguments" keyword argument.') - args = kwargs['arguments'] - if isinstance(args, str): - args = [args] - if not isinstance(args, list): - raise InvalidArguments('"Arguments" keyword argument must be a string or a list of strings.') - for a in args: - if not isinstance(a, str): - raise InvalidArguments('A non-string object in "arguments" keyword argument.') - self.arglist = args - if 'output' not in kwargs: - raise InvalidArguments('Generator must have "output" keyword argument.') - outputs = listify(kwargs['output']) - for rule in outputs: - if not isinstance(rule, str): - raise InvalidArguments('"output" may only contain strings.') - if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: - raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.') - if has_path_sep(rule): - raise InvalidArguments('"outputs" must not contain a directory separator.') - if len(outputs) > 1: - for o in outputs: - if '@OUTPUT@' in o: - raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.') - self.outputs = outputs - if 'depfile' in kwargs: - depfile = kwargs['depfile'] - if not isinstance(depfile, str): - raise InvalidArguments('Depfile must be a string.') - if os.path.basename(depfile) != depfile: - raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') - self.depfile = depfile - if 'capture' in kwargs: - capture = kwargs['capture'] - if not isinstance(capture, bool): - raise InvalidArguments('Capture must be boolean.') - self.capture = capture - if 'depends' in kwargs: - depends = unholder(listify(kwargs['depends'])) - for d in depends: - if not (isinstance(d, (BuildTarget, CustomTarget))): - raise InvalidArguments('Depends entries must be build targets.') - self.depends.append(d) - - def get_base_outnames(self, inname) -> T.List[str]: + def get_base_outnames(self, inname: str) -> T.List[str]: plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] bases = [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] return bases - def get_dep_outname(self, inname): + def get_dep_outname(self, inname: str) -> T.List[str]: if self.depfile is None: raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.') plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) - def get_arglist(self, inname): + def get_arglist(self, inname: str) -> T.List[str]: plainname = os.path.basename(inname) basename = os.path.splitext(plainname)[0] return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] - def is_parent_path(self, parent, trial): + @staticmethod + def is_parent_path(parent: str, trial: str) -> bool: relpath = pathlib.PurePath(trial).relative_to(parent) return relpath.parts[0] != '..' # For subdirs we can only go "down". - def process_files(self, name, files, state: 'Interpreter', preserve_path_from=None, extra_args=None): - new = False + def process_files(self, files: T.Iterable[T.Union[str, File, 'CustomTarget', 'CustomTargetIndex', 'GeneratedList']], + state: T.Union['Interpreter', 'ModuleState'], + preserve_path_from: T.Optional[str] = None, + extra_args: T.Optional[T.List[str]] = None) -> 'GeneratedList': output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) - #XXX - for e in unholder(files): - fs = [e] + + for e in files: if isinstance(e, CustomTarget): output.depends.add(e) if isinstance(e, CustomTargetIndex): output.depends.add(e.target) + if isinstance(e, (CustomTarget, CustomTargetIndex, GeneratedList)): self.depends.append(e) # BUG: this should go in the GeneratedList object, not this object. - fs = [] - for f in e.get_outputs(): - fs.append(File.from_built_file(state.subdir, f)) - new = True + fs = [File.from_built_file(state.subdir, f) for f in e.get_outputs()] elif isinstance(e, str): fs = [File.from_source_file(state.environment.source_dir, state.subdir, e)] - elif not isinstance(e, File): - raise InvalidArguments(f'{name} arguments must be strings, files or CustomTargets, not {e!r}.') + else: + fs = [e] for f in fs: if preserve_path_from: @@ -1612,26 +1571,28 @@ class Generator: if not self.is_parent_path(preserve_path_from, abs_f): raise InvalidArguments('generator.process: When using preserve_path_from, all input files must be in a subdirectory of the given dir.') output.add_file(f, state) - if new: - FeatureNew.single_use( - f'Calling "{name}" with CustomTaget or Index of CustomTarget.', - '0.57.0', state.subproject) return output class GeneratedList: - def __init__(self, generator: 'GeneratorHolder', subdir: str, preserve_path_from=None, extra_args=None): - self.generator = unholder(generator) - self.name = self.generator.exe - self.depends = set() # Things this target depends on (because e.g. a custom target was used as input) + + """The output of generator.process.""" + + def __init__(self, generator: Generator, subdir: str, + preserve_path_from: T.Optional[str], + extra_args: T.List[str]): + self.generator = generator + self.name = generator.exe + self.depends: T.Set['CustomTarget'] = set() # Things this target depends on (because e.g. a custom target was used as input) self.subdir = subdir self.infilelist: T.List['File'] = [] self.outfilelist: T.List[str] = [] - self.outmap: T.Dict['File', str] = {} - self.extra_depends = [] - self.depend_files = [] + self.outmap: T.Dict[File, T.List[str]] = {} + self.extra_depends = [] # XXX: Doesn't seem to be used? + self.depend_files: T.List[File] = [] self.preserve_path_from = preserve_path_from - self.extra_args = extra_args if extra_args is not None else [] + self.extra_args: T.List[str] = extra_args if extra_args is not None else [] + if isinstance(self.generator.exe, programs.ExternalProgram): if not self.generator.exe.found(): raise InvalidArguments('Tried to use not-found external program as generator') @@ -1641,7 +1602,7 @@ class GeneratedList: # know the absolute path of self.depend_files.append(File.from_absolute_file(path)) - def add_preserved_path_segment(self, infile: 'File', outfiles: T.List[str], state: 'Interpreter') -> T.List[str]: + def add_preserved_path_segment(self, infile: File, outfiles: T.List[str], state: T.Union['Interpreter', 'ModuleState']) -> T.List[str]: result: T.List[str] = [] in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) assert os.path.isabs(self.preserve_path_from) @@ -1651,7 +1612,7 @@ class GeneratedList: result.append(os.path.join(path_segment, of)) return result - def add_file(self, newfile: 'File', state: 'Interpreter') -> None: + def add_file(self, newfile: File, state: T.Union['Interpreter', 'ModuleState']) -> None: self.infilelist.append(newfile) outfiles = self.generator.get_base_outnames(newfile.fname) if self.preserve_path_from: @@ -1671,7 +1632,7 @@ class GeneratedList: def get_generator(self) -> 'Generator': return self.generator - def get_extra_args(self): + def get_extra_args(self) -> T.List[str]: return self.extra_args class Executable(BuildTarget): diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 1a7d2fc..dce0391 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -267,7 +267,7 @@ class Interpreter(InterpreterBase): self.ast = ast self.sanity_check_ast() self.builtin.update({'meson': MesonMain(build, self)}) - self.generators = [] + self.generators: T.List['GeneratorHolder'] = [] self.processed_buildfiles = set() # type: T.Set[str] self.project_args_frozen = False self.global_args_frozen = False # implies self.project_args_frozen @@ -1954,10 +1954,34 @@ This will become a hard error in the future.''' % kwargs['input'], location=self @permittedKwargs({'arguments', 'output', 'depends', 'depfile', 'capture', 'preserve_path_from'}) - def func_generator(self, node, args, kwargs): - gen = GeneratorHolder(self, args, kwargs) - self.generators.append(gen) - return gen + @typed_pos_args('generator', (ExecutableHolder, ExternalProgramHolder)) + @typed_kwargs( + 'generator', + KwargInfo('arguments', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True), + KwargInfo('output', ContainerTypeInfo(list, str, allow_empty=False), required=True, listify=True), + KwargInfo('depfile', str, validator=lambda x: 'Depfile must be a plain filename with a subdirectory' if has_path_sep(x) else None), + KwargInfo('capture', bool, default=False, since='0.43.0'), + KwargInfo('depends', ContainerTypeInfo(list, (BuildTargetHolder, CustomTargetHolder)), default=[], listify=True), + ) + def func_generator(self, node: mparser.FunctionNode, + args: T.Tuple[T.Union[ExecutableHolder, ExternalProgramHolder]], + kwargs: 'kwargs.FuncGenerator') -> GeneratorHolder: + for rule in kwargs['output']: + if '@BASENAME@' not in rule and '@PLAINNAME@' not in rule: + raise InvalidArguments('Every element of "output" must contain @BASENAME@ or @PLAINNAME@.') + if has_path_sep(rule): + raise InvalidArguments('"output" must not contain a directory separator.') + if len(kwargs['output']) > 1: + for o in kwargs['output']: + if '@OUTPUT@' in o: + raise InvalidArguments('Tried to use @OUTPUT@ in a rule with more than one output.') + + depends = [d.held_object for d in kwargs.pop('depends')] + + gen = build.Generator(args[0].held_object, depends=depends, **kwargs) + holder = GeneratorHolder(gen, self) + self.generators.append(holder) + return holder @typed_pos_args('benchmark', str, (ExecutableHolder, JarHolder, ExternalProgramHolder, mesonlib.File)) @typed_kwargs('benchmark', *TEST_KWARGS) diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index e87feb9..7b59a24 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -13,22 +13,33 @@ from .. import mlog from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule from ..backend.backends import TestProtocol -from ..interpreterbase import (InterpreterObject, ObjectHolder, MutableInterpreterObject, +from ..interpreterbase import (ContainerTypeInfo, InterpreterObject, KwargInfo, + ObjectHolder, MutableInterpreterObject, FeatureNewKwargs, FeatureNew, FeatureDeprecated, - typed_pos_args, stringArgs, permittedKwargs, - noArgsFlattening, noPosargs, TYPE_var, TYPE_nkwargs, - flatten, InterpreterException, InvalidArguments, InvalidCode) + typed_kwargs, typed_pos_args, stringArgs, + permittedKwargs, noArgsFlattening, noPosargs, + TYPE_var, TYPE_nkwargs, flatten, + InterpreterException, InvalidArguments, + InvalidCode) +from ..interpreterbase.decorators import FeatureCheckBase from ..dependencies import Dependency, ExternalLibrary, InternalDependency from ..programs import ExternalProgram from ..mesonlib import FileMode, OptionKey, listify, Popen_safe import typing as T -def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True): +if T.TYPE_CHECKING: + from . import kwargs + from .interpreter import Interpreter + + +def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', subproject: str, + feature_check: T.Optional['FeatureCheckBase'] = None, + default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]: val = kwargs.get('required', default) disabled = False required = False - feature = None + feature: T.Optional[str] = None if isinstance(val, FeatureOptionHolder): if not feature_check: feature_check = FeatureNew('User option "feature"', '0.47.0') @@ -46,6 +57,7 @@ def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True) # Keep boolean value in kwargs to simplify other places where this kwarg is # checked. + # TODO: this should be removed, and those callers should learn about FeatureOptions kwargs['required'] = required return disabled, required, feature @@ -611,47 +623,16 @@ class ExternalLibraryHolder(InterpreterObject, ObjectHolder[ExternalLibrary]): pdep = self.held_object.get_partial_dependency(**kwargs) return DependencyHolder(pdep, self.subproject) -class GeneratorHolder(InterpreterObject, ObjectHolder[build.Generator]): - @FeatureNewKwargs('generator', '0.43.0', ['capture']) - def __init__(self, interp, args, kwargs): - self.interpreter = interp - InterpreterObject.__init__(self) - ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject) - self.methods.update({'process': self.process_method}) - - @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from']) - @permittedKwargs({'extra_args', 'preserve_path_from'}) - def process_method(self, args, kwargs): - extras = mesonlib.stringlistify(kwargs.get('extra_args', [])) - if 'preserve_path_from' in kwargs: - preserve_path_from = kwargs['preserve_path_from'] - if not isinstance(preserve_path_from, str): - raise InvalidArguments('Preserve_path_from must be a string.') - preserve_path_from = os.path.normpath(preserve_path_from) - if not os.path.isabs(preserve_path_from): - # This is a bit of a hack. Fix properly before merging. - raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') - else: - preserve_path_from = None - gl = self.held_object.process_files('Generator', args, self.interpreter, - preserve_path_from, extra_args=extras) - return GeneratedListHolder(gl) - class GeneratedListHolder(InterpreterObject, ObjectHolder[build.GeneratedList]): - def __init__(self, arg1, extra_args=None): + def __init__(self, arg1: 'build.GeneratedList'): InterpreterObject.__init__(self) - if isinstance(arg1, GeneratorHolder): - ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else [])) - else: - ObjectHolder.__init__(self, arg1) + ObjectHolder.__init__(self, arg1) - def __repr__(self): + def __repr__(self) -> str: r = '<{}: {!r}>' return r.format(self.__class__.__name__, self.held_object.get_outputs()) - def add_file(self, a): - self.held_object.add_file(a) # A machine that's statically known from the cross file class MachineHolder(InterpreterObject, ObjectHolder['MachineInfo']): @@ -1036,3 +1017,37 @@ class RunTargetHolder(TargetHolder): r = '<{} {}: {}>' h = self.held_object return r.format(self.__class__.__name__, h.get_id(), h.command) + + +class GeneratorHolder(InterpreterObject, ObjectHolder[build.Generator]): + + def __init__(self, gen: 'build.Generator', interpreter: 'Interpreter'): + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, gen, interpreter.subproject) + self.interpreter = interpreter + self.methods.update({'process': self.process_method}) + + @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, GeneratedListHolder)) + @typed_kwargs( + 'generator.process', + KwargInfo('preserve_path_from', str, since='0.45.0'), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + ) + def process_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, GeneratedListHolder]]], + kwargs: 'kwargs.GeneratorProcess') -> GeneratedListHolder: + preserve_path_from = kwargs['preserve_path_from'] + if preserve_path_from is not None: + preserve_path_from = os.path.normpath(preserve_path_from) + if not os.path.isabs(preserve_path_from): + # This is a bit of a hack. Fix properly before merging. + raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') + + if any(isinstance(a, (CustomTargetHolder, CustomTargetIndexHolder, GeneratedListHolder)) for a in args[0]): + FeatureNew.single_use( + f'Calling generator.process with CustomTaget or Index of CustomTarget.', + '0.57.0', self.interpreter.subproject) + + gl = self.held_object.process_files(mesonlib.unholder(args[0]), self.interpreter, + preserve_path_from, extra_args=kwargs['extra_args']) + + return GeneratedListHolder(gl) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index db6b02b..9734caa 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -9,7 +9,10 @@ import typing as T from typing_extensions import TypedDict, Literal from ..mesonlib import MachineChoice, File -from .interpreterobjects import BuildTargetHolder, CustomTargetHolder, EnvironmentVariablesHolder, TargetHolder +from .interpreterobjects import ( + BuildTargetHolder, CustomTargetHolder, EnvironmentVariablesHolder, + FeatureOptionHolder, TargetHolder +) class FuncAddProjectArgs(TypedDict): @@ -57,3 +60,33 @@ class FuncTest(FuncBenchmark): """ is_parallel: bool + + +class ExtractRequired(TypedDict): + + """Keyword Arguments consumed by the `extract_required_kwargs` function. + + Any function that uses the `required` keyword argument which accepts either + a boolean or a feature option should inherit it's arguments from this class. + """ + + required: T.Union[bool, 'FeatureOptionHolder'] + + +class FuncGenerator(TypedDict): + + """Keyword rguments for the generator function.""" + + arguments: T.List[str] + output: T.List[str] + depfile: bool + capture: bool + depends: T.List[T.Union['BuildTargetHolder', 'CustomTargetHolder']] + + +class GeneratorProcess(TypedDict): + + """Keyword Arguments for generator.process.""" + + preserve_path_from: T.Optional[str] + extra_args: T.List[str] diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index e95b9c6..69bb552 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -23,7 +23,9 @@ import typing as T if T.TYPE_CHECKING: from ..interpreter import Interpreter + from ..interpreter.interpreterobjects import IncludeDirsHolder, ExternalProgramHolder from ..interpreterbase import TYPE_var, TYPE_nvar, TYPE_nkwargs + from ..programs import ExternalProgram class ModuleState: """Object passed to all module methods. @@ -59,14 +61,14 @@ class ModuleState: self.target_machine = interpreter.builtin['target_machine'].held_object self.current_node = interpreter.current_node - def get_include_args(self, include_dirs, prefix='-I'): + def get_include_args(self, include_dirs: T.Iterable[T.Union[str, 'IncludeDirsHolder']], prefix: str = '-I') -> T.List[str]: if not include_dirs: return [] srcdir = self.environment.get_source_dir() builddir = self.environment.get_build_dir() - dirs_str = [] + dirs_str: T.List[str] = [] for dirs in unholder(include_dirs): if isinstance(dirs, str): dirs_str += [f'{prefix}{dirs}'] @@ -88,7 +90,7 @@ class ModuleState: def find_program(self, prog: T.Union[str, T.List[str]], required: bool = True, version_func: T.Optional[T.Callable[['ExternalProgram'], str]] = None, wanted: T.Optional[str] = None) -> 'ExternalProgramHolder': - return self._interpreter.find_program_impl(prog, required=required) + return self._interpreter.find_program_impl(prog, required=required, version_func=version_func, wanted=wanted) class ModuleObject: """Base class for all objects returned by modules diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index aecfe50..7d752db 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -1,4 +1,5 @@ # Copyright 2015 The Meson development team +# Copyright © 2021 Intel Corporation # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,34 +13,95 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mesonbuild.dependencies import find_external_dependency import os import shutil import typing as T +import xml.etree.ElementTree as ET -from .. import mlog +from . import ModuleReturnValue, ExtensionModule from .. import build from .. import mesonlib -from ..mesonlib import MesonException, extract_as_list, File, unholder, version_compare -from ..dependencies import Dependency -import xml.etree.ElementTree as ET -from . import ModuleReturnValue, ExtensionModule -from ..interpreterbase import noPosargs, permittedKwargs, FeatureNew, FeatureNewKwargs +from .. import mlog +from ..dependencies import find_external_dependency from ..interpreter import extract_required_kwarg +from ..interpreter.interpreterobjects import DependencyHolder, ExternalLibraryHolder, IncludeDirsHolder, FeatureOptionHolder, GeneratedListHolder +from ..interpreterbase import ContainerTypeInfo, FeatureDeprecated, KwargInfo, noPosargs, FeatureNew, typed_kwargs +from ..mesonlib import MesonException, File from ..programs import NonExistingExternalProgram if T.TYPE_CHECKING: + from . import ModuleState + from ..dependencies.qt import QtPkgConfigDependency, QmakeQtDependency from ..interpreter import Interpreter - from ..dependencies.qt import QtBaseDependency - from ..environment import Environment + from ..interpreter import kwargs from ..programs import ExternalProgram + QtDependencyType = T.Union[QtPkgConfigDependency, QmakeQtDependency] + + from typing_extensions import TypedDict + + class ResourceCompilerKwArgs(TypedDict): + + """Keyword arguments for the Resource Compiler method.""" + + name: T.Optional[str] + sources: T.List[mesonlib.FileOrString] + extra_args: T.List[str] + method: str + + class UICompilerKwArgs(TypedDict): + + """Keyword arguments for the Ui Compiler method.""" + + sources: T.List[mesonlib.FileOrString] + extra_args: T.List[str] + method: str + + class MocCompilerKwArgs(TypedDict): + + """Keyword arguments for the Moc Compiler method.""" + + sources: T.List[mesonlib.FileOrString] + headers: T.List[mesonlib.FileOrString] + extra_args: T.List[str] + method: str + include_directories: T.List[IncludeDirsHolder] + dependencies: T.List[T.Union[DependencyHolder, ExternalLibraryHolder]] + + class PreprocessKwArgs(TypedDict): + + sources: T.List[mesonlib.FileOrString] + moc_sources: T.List[mesonlib.FileOrString] + moc_headers: T.List[mesonlib.FileOrString] + qresources: T.List[mesonlib.FileOrString] + ui_files: T.List[mesonlib.FileOrString] + moc_extra_arguments: T.List[str] + rcc_extra_arguments: T.List[str] + uic_extra_arguments: T.List[str] + include_directories: T.List[IncludeDirsHolder] + dependencies: T.List[T.Union[DependencyHolder, ExternalLibraryHolder]] + method: str + + class HasToolKwArgs(kwargs.ExtractRequired): + + method: str + + class CompileTranslationsKwArgs(TypedDict): + + build_by_default: bool + install: bool + install_dir: T.Optional[str] + method: str + qresource: T.Optional[str] + rcc_extra_arguments: T.List[str] + ts_files: T.List[str] + class QtBaseModule(ExtensionModule): - tools_detected = False - rcc_supports_depfiles = False + _tools_detected = False + _rcc_supports_depfiles = False - def __init__(self, interpreter: 'Interpreter', qt_version=5): + def __init__(self, interpreter: 'Interpreter', qt_version: int = 5): ExtensionModule.__init__(self, interpreter) self.qt_version = qt_version self.moc: 'ExternalProgram' = NonExistingExternalProgram('moc') @@ -50,9 +112,12 @@ class QtBaseModule(ExtensionModule): 'has_tools': self.has_tools, 'preprocess': self.preprocess, 'compile_translations': self.compile_translations, + 'compile_resources': self.compile_resources, + 'compile_ui': self.compile_ui, + 'compile_moc': self.compile_moc, }) - def compilers_detect(self, state, qt_dep: 'QtBaseDependency') -> None: + def compilers_detect(self, state: 'ModuleState', qt_dep: 'QtDependencyType') -> None: """Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH""" # It is important that this list does not change order as the order of # the returned ExternalPrograms will change as well @@ -96,18 +161,19 @@ class QtBaseModule(ExtensionModule): if p.found(): setattr(self, name, p) - def _detect_tools(self, state, method, required=True): - if self.tools_detected: + def _detect_tools(self, state: 'ModuleState', method: str, required: bool = True) -> None: + if self._tools_detected: return - self.tools_detected = True + self._tools_detected = True mlog.log(f'Detecting Qt{self.qt_version} tools') kwargs = {'required': required, 'modules': 'Core', 'method': method} - qt = find_external_dependency(f'qt{self.qt_version}', state.environment, kwargs) + # Just pick one to make mypy happy + qt = T.cast('QtPkgConfigDependency', find_external_dependency(f'qt{self.qt_version}', state.environment, kwargs)) if qt.found(): # Get all tools and then make sure that they are the right version self.compilers_detect(state, qt) - if version_compare(qt.version, '>=5.14.0'): - self.rcc_supports_depfiles = True + if mesonlib.version_compare(qt.version, '>=5.14.0'): + self._rcc_supports_depfiles = True else: mlog.warning('rcc dependencies will not work properly until you move to Qt >= 5.14:', mlog.bold('https://bugreports.qt.io/browse/QTBUG-45460'), fatal=False) @@ -118,21 +184,24 @@ class QtBaseModule(ExtensionModule): self.rcc = NonExistingExternalProgram(name='rcc' + suffix) self.lrelease = NonExistingExternalProgram(name='lrelease' + suffix) - def qrc_nodes(self, state, rcc_file): - if type(rcc_file) is str: + @staticmethod + def _qrc_nodes(state: 'ModuleState', rcc_file: 'mesonlib.FileOrString') -> T.Tuple[str, T.List[str]]: + abspath: str + if isinstance(rcc_file, str): abspath = os.path.join(state.environment.source_dir, state.subdir, rcc_file) rcc_dirname = os.path.dirname(abspath) - elif type(rcc_file) is File: + else: abspath = rcc_file.absolute_path(state.environment.source_dir, state.environment.build_dir) rcc_dirname = os.path.dirname(abspath) + # FIXME: what error are we actually tring to check here? try: tree = ET.parse(abspath) root = tree.getroot() - result = [] + result: T.List[str] = [] for child in root[0]: if child.tag != 'file': - mlog.warning("malformed rcc file: ", os.path.join(state.subdir, rcc_file)) + mlog.warning("malformed rcc file: ", os.path.join(state.subdir, str(rcc_file))) break else: result.append(child.text) @@ -141,9 +210,9 @@ class QtBaseModule(ExtensionModule): except Exception: raise MesonException(f'Unable to parse resource file {abspath}') - def parse_qrc_deps(self, state, rcc_file): - rcc_dirname, nodes = self.qrc_nodes(state, rcc_file) - result = [] + def _parse_qrc_deps(self, state: 'ModuleState', rcc_file: 'mesonlib.FileOrString') -> T.List[File]: + rcc_dirname, nodes = self._qrc_nodes(state, rcc_file) + result: T.List[File] = [] for resource_path in nodes: # We need to guess if the pointed resource is: # a) in build directory -> implies a generated file @@ -170,11 +239,18 @@ class QtBaseModule(ExtensionModule): result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc)) return result - @noPosargs - @permittedKwargs({'method', 'required'}) @FeatureNew('qt.has_tools', '0.54.0') - def has_tools(self, state, args, kwargs): + @noPosargs + @typed_kwargs( + 'qt.has_tools', + KwargInfo('required', (bool, FeatureOptionHolder), default=False), + KwargInfo('method', str, default='auto'), + ) + def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs') -> bool: method = kwargs.get('method', 'auto') + # We have to cast here because TypedDicts are invariant, even though + # ExtractRequiredKwArgs is a subset of HasToolKwArgs, type checkers + # will insist this is wrong disabled, required, feature = extract_required_kwarg(kwargs, state.subproject, default=False) if disabled: mlog.log('qt.has_tools skipped: feature', mlog.bold(feature), 'disabled') @@ -187,117 +263,227 @@ class QtBaseModule(ExtensionModule): return False return True - @FeatureNewKwargs('qt.preprocess', '0.49.0', ['uic_extra_arguments']) - @FeatureNewKwargs('qt.preprocess', '0.44.0', ['moc_extra_arguments']) - @FeatureNewKwargs('qt.preprocess', '0.49.0', ['rcc_extra_arguments']) - @permittedKwargs({'moc_headers', 'moc_sources', 'uic_extra_arguments', 'moc_extra_arguments', 'rcc_extra_arguments', 'include_directories', 'dependencies', 'ui_files', 'qresources', 'method'}) - def preprocess(self, state, args, kwargs): - rcc_files, ui_files, moc_headers, moc_sources, uic_extra_arguments, moc_extra_arguments, rcc_extra_arguments, sources, include_directories, dependencies \ - = [extract_as_list(kwargs, c, pop=True) for c in ['qresources', 'ui_files', 'moc_headers', 'moc_sources', 'uic_extra_arguments', 'moc_extra_arguments', 'rcc_extra_arguments', 'sources', 'include_directories', 'dependencies']] - sources += args[1:] - method = kwargs.get('method', 'auto') - self._detect_tools(state, method) - err_msg = "{0} sources specified and couldn't find {1}, " \ - "please check your qt{2} installation" - if (moc_headers or moc_sources) and not self.moc.found(): - raise MesonException(err_msg.format('MOC', f'moc-qt{self.qt_version}', self.qt_version)) - if rcc_files: - if not self.rcc.found(): - raise MesonException(err_msg.format('RCC', f'rcc-qt{self.qt_version}', self.qt_version)) + @FeatureNew('qt.compile_resources', '0.59.0') + @noPosargs + @typed_kwargs( + 'qt.compile_resources', + KwargInfo('name', str), + KwargInfo('sources', ContainerTypeInfo(list, (File, str), allow_empty=False), listify=True, required=True), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('method', str, default='auto') + ) + def compile_resources(self, state: 'ModuleState', args: T.Tuple, kwargs: 'ResourceCompilerKwArgs') -> ModuleReturnValue: + """Compile Qt resources files. + + Uses CustomTargets to generate .cpp files from .qrc files. + """ + self._detect_tools(state, kwargs['method']) + if not self.rcc.found(): + err_msg = ("{0} sources specified and couldn't find {1}, " + "please check your qt{2} installation") + raise MesonException(err_msg.format('RCC', f'rcc-qt{self.qt_version}', self.qt_version)) + + # List of generated CustomTargets + targets: T.List[build.CustomTarget] = [] + + # depfile arguments + DEPFILE_ARGS: T.List[str] = ['--depfile', '@DEPFILE@'] if self._rcc_supports_depfiles else [] + + name = kwargs['name'] + sources = kwargs['sources'] + extra_args = kwargs['extra_args'] + + # If a name was set generate a single .cpp file from all of the qrc + # files, otherwise generate one .cpp file per qrc file. + if name: + qrc_deps: T.List[File] = [] + for s in sources: + qrc_deps.extend(self._parse_qrc_deps(state, s)) + + rcc_kwargs: T.Dict[str, T.Any] = { # TODO: if CustomTarget had typing information we could use that here... + 'input': sources, + 'output': name + '.cpp', + 'command': self.rcc.get_command() + ['-name', name, '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, + 'depend_files': qrc_deps, + 'depfile': f'{name}.d', + } + res_target = build.CustomTarget(name, state.subdir, state.subproject, rcc_kwargs) + targets.append(res_target) + else: + for rcc_file in sources: + qrc_deps = self._parse_qrc_deps(state, rcc_file) + if isinstance(rcc_file, str): + basename = os.path.basename(rcc_file) + else: + basename = os.path.basename(rcc_file.fname) + name = f'qt{self.qt_version}-{basename.replace(".", "_")}' + rcc_kwargs = { + 'input': rcc_file, + 'output': f'{name}.cpp', + 'command': self.rcc.get_command() + ['-name', '@BASENAME@', '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, + 'depend_files': qrc_deps, + 'depfile': f'{name}.d', + } + res_target = build.CustomTarget(name, state.subdir, state.subproject, rcc_kwargs) + targets.append(res_target) + + return ModuleReturnValue(targets, [targets]) + + @FeatureNew('qt.compile_ui', '0.59.0') + @noPosargs + @typed_kwargs( + 'qt.compile_ui', + KwargInfo('sources', ContainerTypeInfo(list, (File, str), allow_empty=False), listify=True, required=True), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('method', str, default='auto') + ) + def compile_ui(self, state: 'ModuleState', args: T.Tuple, kwargs: 'ResourceCompilerKwArgs') -> ModuleReturnValue: + """Compile UI resources into cpp headers.""" + self._detect_tools(state, kwargs['method']) + if not self.uic.found(): + err_msg = ("{0} sources specified and couldn't find {1}, " + "please check your qt{2} installation") + raise MesonException(err_msg.format('UIC', f'uic-qt{self.qt_version}', self.qt_version)) + + # TODO: This generator isn't added to the generator list in the Interpreter + gen = build.Generator( + self.uic, + kwargs['extra_args'] + ['-o', '@OUTPUT@', '@INPUT@'], + ['ui_@BASENAME@.h'], + name=f'Qt{self.qt_version} ui') + out = GeneratedListHolder(gen.process_files(kwargs['sources'], state)) + return ModuleReturnValue(out, [out]) + + @FeatureNew('qt.compile_moc', '0.59.0') + @noPosargs + @typed_kwargs( + 'qt.compile_moc', + KwargInfo('sources', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), + KwargInfo('headers', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('method', str, default='auto'), + KwargInfo('include_directories', ContainerTypeInfo(list, IncludeDirsHolder), listify=True, default=[]), + KwargInfo('dependencies', ContainerTypeInfo(list, (DependencyHolder, ExternalLibraryHolder)), listify=True, default=[]), + ) + def compile_moc(self, state: 'ModuleState', args: T.Tuple, kwargs: 'MocCompilerKwArgs') -> ModuleReturnValue: + self._detect_tools(state, kwargs['method']) + if not self.moc.found(): + err_msg = ("{0} sources specified and couldn't find {1}, " + "please check your qt{2} installation") + raise MesonException(err_msg.format('MOC', f'uic-qt{self.qt_version}', self.qt_version)) + + if not (kwargs['headers'] or kwargs['sources']): + raise build.InvalidArguments('At least one of the "headers" or "sources" keyword arguments must be provied and not empty') + + inc = state.get_include_args(include_dirs=kwargs['include_directories']) + compile_args: T.List[str] = [] + for dep in kwargs['dependencies']: + compile_args.extend([a for a in dep.held_object.get_all_compile_args() if a.startswith(('-I', '-D'))]) + + output: T.List[build.GeneratedList] = [] + + arguments = kwargs['extra_args'] + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] + if kwargs['headers']: + moc_gen = build.Generator( + self.moc, arguments, ['moc_@BASENAME@.cpp'], + name=f'Qt{self.qt_version} moc header') + output.append(moc_gen.process_files(kwargs['headers'], state)) + if kwargs['sources']: + moc_gen = build.Generator( + self.moc, arguments, ['@BASENAME@.moc'], + name=f'Qt{self.qt_version} moc source') + output.append(moc_gen.process_files(kwargs['sources'], state)) + + return ModuleReturnValue(output, [output]) + + # We can't use typed_pos_args here, the signature is ambiguious + @typed_kwargs( + 'qt.preprocess', + KwargInfo('sources', ContainerTypeInfo(list, (File, str)), listify=True, default=[], deprecated='0.59.0'), + KwargInfo('qresources', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), + KwargInfo('ui_files', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), + KwargInfo('moc_sources', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), + KwargInfo('moc_headers', ContainerTypeInfo(list, (File, str)), listify=True, default=[]), + KwargInfo('moc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.44.0'), + KwargInfo('rcc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.49.0'), + KwargInfo('uic_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.49.0'), + KwargInfo('method', str, default='auto'), + KwargInfo('include_directories', ContainerTypeInfo(list, IncludeDirsHolder), listify=True, default=[]), + KwargInfo('dependencies', ContainerTypeInfo(list, (DependencyHolder, ExternalLibraryHolder)), listify=True, default=[]), + ) + def preprocess(self, state: 'ModuleState', args: T.List[T.Union[str, File]], kwargs: 'PreprocessKwArgs') -> ModuleReturnValue: + _sources = args[1:] + if _sources: + FeatureDeprecated.single_use('qt.preprocess positional sources', '0.59', state.subproject) + sources = _sources + kwargs['sources'] + for s in sources: + if not isinstance(s, (str, File)): + raise build.InvalidArguments('Variadic arguments to qt.preprocess must be Strings or Files') + method = kwargs['method'] + + if kwargs['qresources']: # custom output name set? -> one output file, multiple otherwise + rcc_kwargs: 'ResourceCompilerKwArgs' = {'name': '', 'sources': kwargs['qresources'], 'extra_args': kwargs['rcc_extra_arguments'], 'method': method} if args: - qrc_deps = [] - for i in rcc_files: - qrc_deps += self.parse_qrc_deps(state, i) - name = args[0] - rcc_kwargs = {'input': rcc_files, - 'output': name + '.cpp', - 'command': [self.rcc, '-name', name, '-o', '@OUTPUT@', rcc_extra_arguments, '@INPUT@'], - 'depend_files': qrc_deps} - res_target = build.CustomTarget(name, state.subdir, state.subproject, rcc_kwargs) - sources.append(res_target) - else: - for rcc_file in rcc_files: - qrc_deps = self.parse_qrc_deps(state, rcc_file) - if type(rcc_file) is str: - basename = os.path.basename(rcc_file) - elif type(rcc_file) is File: - basename = os.path.basename(rcc_file.fname) - name = 'qt' + str(self.qt_version) + '-' + basename.replace('.', '_') - rcc_kwargs = {'input': rcc_file, - 'output': name + '.cpp', - 'command': [self.rcc, '-name', '@BASENAME@', '-o', '@OUTPUT@', rcc_extra_arguments, '@INPUT@'], - 'depend_files': qrc_deps} - if self.rcc_supports_depfiles: - rcc_kwargs['depfile'] = name + '.d' - rcc_kwargs['command'] += ['--depfile', '@DEPFILE@'] - res_target = build.CustomTarget(name, state.subdir, state.subproject, rcc_kwargs) - sources.append(res_target) - if ui_files: - if not self.uic.found(): - raise MesonException(err_msg.format('UIC', f'uic-qt{self.qt_version}', self.qt_version)) - arguments = uic_extra_arguments + ['-o', '@OUTPUT@', '@INPUT@'] - ui_kwargs = {'output': 'ui_@BASENAME@.h', - 'arguments': arguments} - ui_gen = build.Generator([self.uic], ui_kwargs) - ui_output = ui_gen.process_files(f'Qt{self.qt_version} ui', ui_files, state) - sources.append(ui_output) - inc = state.get_include_args(include_dirs=include_directories) - compile_args = [] - for dep in unholder(dependencies): - if isinstance(dep, Dependency): - for arg in dep.get_all_compile_args(): - if arg.startswith('-I') or arg.startswith('-D'): - compile_args.append(arg) - else: - raise MesonException('Argument is of an unacceptable type {!r}.\nMust be ' - 'either an external dependency (returned by find_library() or ' - 'dependency()) or an internal dependency (returned by ' - 'declare_dependency()).'.format(type(dep).__name__)) - if moc_headers: - arguments = moc_extra_arguments + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] - moc_kwargs = {'output': 'moc_@BASENAME@.cpp', - 'arguments': arguments} - moc_gen = build.Generator([self.moc], moc_kwargs) - moc_output = moc_gen.process_files(f'Qt{self.qt_version} moc header', moc_headers, state) - sources.append(moc_output) - if moc_sources: - arguments = moc_extra_arguments + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] - moc_kwargs = {'output': '@BASENAME@.moc', - 'arguments': arguments} - moc_gen = build.Generator([self.moc], moc_kwargs) - moc_output = moc_gen.process_files(f'Qt{self.qt_version} moc source', moc_sources, state) - sources.append(moc_output) - return ModuleReturnValue(sources, sources) + if not isinstance(args[0], str): + raise build.InvalidArguments('First argument to qt.preprocess must be a string') + rcc_kwargs['name'] = args[0] + sources.extend(self.compile_resources(state, tuple(), rcc_kwargs).return_value) + + if kwargs['ui_files']: + ui_kwargs: 'UICompilerKwArgs' = {'sources': kwargs['ui_files'], 'extra_args': kwargs['uic_extra_arguments'], 'method': method} + sources.extend(self.compile_ui(state, tuple(), ui_kwargs).return_value) + + if kwargs['moc_headers'] or kwargs['moc_sources']: + moc_kwargs: 'MocCompilerKwArgs' = { + 'extra_args': kwargs['moc_extra_arguments'], + 'sources': kwargs['moc_sources'], + 'headers': kwargs['moc_headers'], + 'include_directories': kwargs['include_directories'], + 'dependencies': kwargs['dependencies'], + 'method': method, + } + sources.extend(self.compile_moc(state, tuple(), moc_kwargs).return_value) + + return ModuleReturnValue(sources, [sources]) @FeatureNew('qt.compile_translations', '0.44.0') - @FeatureNewKwargs('qt.compile_translations', '0.56.0', ['qresource']) - @FeatureNewKwargs('qt.compile_translations', '0.56.0', ['rcc_extra_arguments']) - @permittedKwargs({'ts_files', 'qresource', 'rcc_extra_arguments', 'install', 'install_dir', 'build_by_default', 'method'}) - def compile_translations(self, state, args, kwargs): - ts_files, install_dir = [extract_as_list(kwargs, c, pop=True) for c in ['ts_files', 'install_dir']] - qresource = kwargs.get('qresource') + @noPosargs + @typed_kwargs( + 'qt.compile_translations', + KwargInfo('build_by_default', bool, default=False), + KwargInfo('install', bool, default=False), + KwargInfo('install_dir', str), + KwargInfo('method', str, default='auto'), + KwargInfo('qresource', str, since='0.56.0'), + KwargInfo('rcc_extra_arguments', ContainerTypeInfo(list, str), listify=True, default=[], since='0.56.0'), + KwargInfo('ts_files', ContainerTypeInfo(list, (str, File)), listify=True, default=[]), + ) + def compile_translations(self, state: 'ModuleState', args: T.Tuple, kwargs: 'CompileTranslationsKwArgs') -> ModuleReturnValue: + ts_files = kwargs['ts_files'] + install_dir = kwargs['install_dir'] + qresource = kwargs['qresource'] if qresource: if ts_files: raise MesonException('qt.compile_translations: Cannot specify both ts_files and qresource') if os.path.dirname(qresource) != '': raise MesonException('qt.compile_translations: qresource file name must not contain a subdirectory.') - qresource = File.from_built_file(state.subdir, qresource) - infile_abs = os.path.join(state.environment.source_dir, qresource.relative_name()) - outfile_abs = os.path.join(state.environment.build_dir, qresource.relative_name()) + qresource_file = File.from_built_file(state.subdir, qresource) + infile_abs = os.path.join(state.environment.source_dir, qresource_file.relative_name()) + outfile_abs = os.path.join(state.environment.build_dir, qresource_file.relative_name()) os.makedirs(os.path.dirname(outfile_abs), exist_ok=True) shutil.copy2(infile_abs, outfile_abs) self.interpreter.add_build_def_file(infile_abs) - rcc_file, nodes = self.qrc_nodes(state, qresource) + _, nodes = self._qrc_nodes(state, qresource_file) for c in nodes: if c.endswith('.qm'): - ts_files.append(c.rstrip('.qm')+'.ts') + ts_files.append(c.rstrip('.qm') + '.ts') else: raise MesonException(f'qt.compile_translations: qresource can only contain qm files, found {c}') - results = self.preprocess(state, [], {'qresources': qresource, 'rcc_extra_arguments': kwargs.get('rcc_extra_arguments', [])}) - self._detect_tools(state, kwargs.get('method', 'auto')) - translations = [] + results = self.preprocess(state, [], {'qresources': qresource, 'rcc_extra_arguments': kwargs['rcc_extra_arguments']}) + self._detect_tools(state, kwargs['method']) + translations: T.List[build.CustomTarget] = [] for ts in ts_files: if not self.lrelease.found(): raise MesonException('qt.compile_translations: ' + @@ -320,4 +506,4 @@ class QtBaseModule(ExtensionModule): if qresource: return ModuleReturnValue(results.return_value[0], [results.new_objects, translations]) else: - return ModuleReturnValue(translations, translations) + return ModuleReturnValue(translations, [translations]) diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py index d5678ea..f602e09 100644 --- a/mesonbuild/modules/unstable_rust.py +++ b/mesonbuild/modules/unstable_rust.py @@ -199,7 +199,7 @@ class RustModule(ExtensionModule): if self._bindgen_bin is None: # there's some bugs in the interpreter typeing. - self._bindgen_bin = T.cast('ExternalProgram', state.find_program('bindgen').held_object) + self._bindgen_bin = state.find_program('bindgen').held_object name: str if isinstance(header, File): |