aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2021-06-16 20:11:46 +0300
committerGitHub <noreply@github.com>2021-06-16 20:11:46 +0300
commit6fb2f86379c224e99652748eea94a03321b9bd11 (patch)
treea96fb2b8f6468d793b64b172abc67b8224e0edc3 /mesonbuild
parent537adce5d803ff4ae373d87671190a4a2682ff54 (diff)
parent0f5e55a749f0fed6330b216a82de941de3ccf9d6 (diff)
downloadmeson-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.py137
-rw-r--r--mesonbuild/interpreter/interpreter.py34
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py95
-rw-r--r--mesonbuild/interpreter/kwargs.py35
-rw-r--r--mesonbuild/modules/__init__.py8
-rw-r--r--mesonbuild/modules/qt.py436
-rw-r--r--mesonbuild/modules/unstable_rust.py2
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):