aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Qt4-module.md2
-rw-r--r--docs/markdown/Qt5-module.md92
-rw-r--r--docs/markdown/Qt6-module.md8
-rw-r--r--docs/markdown/_include_qt_base.md160
-rw-r--r--docs/markdown/snippets/qt_preprocess_separate.md5
-rw-r--r--docs/markdown/snippets/qt_preprocessed_varargs_deprecated.md33
-rw-r--r--docs/sitemap.txt1
-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
-rwxr-xr-xrun_mypy.py1
-rwxr-xr-xrun_unittests.py2
-rw-r--r--test cases/frameworks/4 qt/meson.build17
17 files changed, 708 insertions, 360 deletions
diff --git a/docs/markdown/Qt4-module.md b/docs/markdown/Qt4-module.md
index 4be1be5..6b62415 100644
--- a/docs/markdown/Qt4-module.md
+++ b/docs/markdown/Qt4-module.md
@@ -2,3 +2,5 @@
This module provides support for Qt4's `moc`, `uic` and `rcc`
tools. It is used identically to the [Qt 5 module](Qt5-module.md).
+
+{{ _include_qt_base.md }}
diff --git a/docs/markdown/Qt5-module.md b/docs/markdown/Qt5-module.md
index 32c34ea..8a5eb00 100644
--- a/docs/markdown/Qt5-module.md
+++ b/docs/markdown/Qt5-module.md
@@ -1,94 +1,6 @@
# Qt5 module
The Qt5 module provides tools to automatically deal with the various
-tools and steps required for Qt. The module has two methods.
+tools and steps required for Qt.
-## preprocess
-
-This method takes the following keyword arguments:
- - `moc_headers`, `moc_sources`, `ui_files`, `qresources`, which define the files that require preprocessing with `moc`, `uic` and `rcc`
- - `include_directories`, the directories to add to header search path for `moc` (optional)
- - `moc_extra_arguments`, any additional arguments to `moc` (optional). Available since v0.44.0.
- - `uic_extra_arguments`, any additional arguments to `uic` (optional). Available since v0.49.0.
- - `rcc_extra_arguments`, any additional arguments to `rcc` (optional). Available since v0.49.0.
- - `dependencies`, dependency objects needed by moc. Available since v0.48.0.
-
-It returns an opaque object that should be passed to a main build target.
-
-## compile_translations (since v0.44.0)
-
-This method generates the necessary targets to build translation files with lrelease, it takes the following keyword arguments:
- - `ts_files`, the list of input translation files produced by Qt's lupdate tool.
- - `install` when true, this target is installed during the install step (optional).
- - `install_dir` directory to install to (optional).
- - `build_by_default` when set to true, to have this target be built by default, that is, when invoking `meson compile`; the default value is false (optional).
- - `qresource` rcc source file to extract ts_files from; cannot be used with ts_files kwarg. Available since v0.56.0.
- - `rcc_extra_arguments`, any additional arguments to `rcc` (optional), when used with `qresource. Available since v0.56.0.
-
-Returns either: a list of custom targets for the compiled
-translations, or, if using a `qresource` file, a single custom target
-containing the processed source file, which should be passed to a main
-build target.
-
-## has_tools
-
-This method returns `true` if all tools used by this module are found,
-`false` otherwise.
-
-It should be used to compile optional Qt code:
-```meson
-qt5 = import('qt5')
-if qt5.has_tools(required: get_option('qt_feature'))
- moc_files = qt5.preprocess(...)
- ...
-endif
-```
-
-This method takes the following keyword arguments:
-- `required`: by default, `required` is set to `false`. If `required` is set to
- `true` or an enabled [`feature`](Build-options.md#features) and some tools are
- missing Meson will abort.
-- `method`: method used to find the Qt dependency (`auto` by default).
-
-*Since: 0.54.0*
-
-## Dependencies
-
-See [Qt dependencies](Dependencies.md#qt4-qt5)
-
-The 'modules' argument is used to include Qt modules in the project.
-See the Qt documentation for the [list of
-modules](http://doc.qt.io/qt-5/qtmodules.html).
-
-The 'private_headers' argument allows usage of Qt's modules private
-headers. (since v0.47.0)
-
-## Example
-A simple example would look like this:
-
-```meson
-qt5 = import('qt5')
-qt5_dep = dependency('qt5', modules: ['Core', 'Gui'])
-inc = include_directories('includes')
-moc_files = qt5.preprocess(moc_headers : 'myclass.h',
- moc_extra_arguments: ['-DMAKES_MY_MOC_HEADER_COMPILE'],
- include_directories: inc,
- dependencies: qt5_dep)
-translations = qt5.compile_translations(ts_files : 'myTranslation_fr.ts', build_by_default : true)
-executable('myprog', 'main.cpp', 'myclass.cpp', moc_files,
- include_directories: inc,
- dependencies : qt5_dep)
-```
-
-Sometimes, translations are embedded inside the binary using qresource
-files. In this case the ts files do not need to be explicitly listed,
-but will be inferred from the built qm files listed in the qresource
-file. For example:
-
-```meson
-qt5 = import('qt5')
-qt5_dep = dependency('qt5', modules: ['Core', 'Gui'])
-lang_cpp = qt5.compile_translations(qresource: 'lang.qrc')
-executable('myprog', 'main.cpp', lang_cpp,
- dependencies: qt5_dep)
-```
+{{ _include_qt_base.md }}
diff --git a/docs/markdown/Qt6-module.md b/docs/markdown/Qt6-module.md
new file mode 100644
index 0000000..4d40423
--- /dev/null
+++ b/docs/markdown/Qt6-module.md
@@ -0,0 +1,8 @@
+# Qt6 module
+
+*New in Meson 0.57.0*
+
+The Qt5 module provides tools to automatically deal with the various
+tools and steps required for Qt.
+
+{{ _include_qt_base.md }}
diff --git a/docs/markdown/_include_qt_base.md b/docs/markdown/_include_qt_base.md
new file mode 100644
index 0000000..4c50abc
--- /dev/null
+++ b/docs/markdown/_include_qt_base.md
@@ -0,0 +1,160 @@
+## compile_resources
+
+*New in 0.59.0*
+
+Compiles Qt's resources collection files (.qrc) into c++ files for compilation.
+
+It takes no positional arguments, and the following keyword arguments:
+ - `name` (string | empty): if provided a single .cpp file will be generated,
+ and the output of all qrc files will be combined in this file, otherwise
+ each qrc file be written to it's own cpp file.
+ - `sources` (File | string)[]: A list of sources to be transpiled. Required,
+ must have at least one source
+ - `extra_args` string[]: Extra arguments to pass directly to `qt-rcc`
+ - `method` string: The method to use to detect qt, see `dependency()` for more
+ information.
+
+## compile_ui
+
+*New in 0.59.0*
+
+Compiles Qt's ui files (.ui) into header files.
+
+It takes no positional arguments, and the following keyword arguments:
+ - `sources` (File | string)[]: A list of sources to be transpiled. Required,
+ must have at least one source
+ - `extra_args` string[]: Extra arguments to pass directly to `qt-uic`
+ - `method` string: The method to use to detect qt, see `dependency()` for more
+ information.
+
+## compile_moc
+
+*New in 0.59.0*
+
+Compiles Qt's moc files (.moc) into header and/or source files. At least one of
+the keyword arguments `headers` and `sources` must be provided.
+
+It takes no positional arguments, and the following keyword arguments:
+ - `sources` (File | string)[]: A list of sources to be transpiled into .moc
+ files for manual inclusion.
+ - `headers` (File | string)[]: A list of headers to be transpiled into .cpp files
+ - `extra_args` string[]: Extra arguments to pass directly to `qt-moc`
+ - `method` string: The method to use to detect qt, see `dependency()` for more
+ information.
+ - `include_directories` IncludeDirectory[]: A list of `include_directory()`
+ objects used when transpiling the .moc files
+
+## preprocess
+
+Consider using `compile_resources`, `compile_ui`, and `compile_moc` instead.
+
+Takes sources for moc, uic, and rcc, and converts them into c++ files for
+compilation.
+
+Has the following signature: `qt.preprocess(name: str | None, *sources: str)`
+
+If the `name` parameter is passed then all of the rcc files will be wirtten to a single output file
+
+The variadic `sources` arguments have been deprecated since Meson 0.59.0, as has the `sources` keyword argument. These passed files unmodified through the preprocessor, don't do this, just add the output of the generator to another sources list:
+```meson
+sources = files('a.cpp', 'main.cpp', 'bar.c')
+sources += qt.preprocess(qresources : ['resources'])
+```
+
+This method takes the following keyword arguments:
+ - `qresources`: a list of strings, Files, Custom Targets, or Build Targets to pass the `rcc` compiler
+ - `ui_files`: a list of strings, Files, Custom Targets, or Build Targets to pass the `uic` compiler
+ - `moc_sources`: a list of strings, Files, Custom Targets, or Build Targets to pass the `moc` compiler the
+ - `moc_headers`: a list of strings, Files, Custom Targets, or Build Targets to pass the `moc` compiler. These will be converted into .cpp files
+ - `include_directories`, the directories to add to header search path for `moc` (optional)
+ - `moc_extra_arguments`, any additional arguments to `moc` (optional). Available since v0.44.0.
+ - `uic_extra_arguments`, any additional arguments to `uic` (optional). Available since v0.49.0.
+ - `rcc_extra_arguments`, any additional arguments to `rcc` (optional). Available since v0.49.0.
+ - `dependencies`, dependency objects needed by moc. Available since v0.48.0.
+ - `sources`, a list of extra sources, which are added to the output unchaged. Deprecated in 0.59.0
+
+It returns an array of targets and sources to pass to a compilation target.
+
+## compile_translations (since v0.44.0)
+
+This method generates the necessary targets to build translation files with
+lrelease, it takes no positional arguments, and the following keyword arguments:
+
+ - `ts_files` (str | File)[], the list of input translation files produced by Qt's lupdate tool.
+ - `install` bool: when true, this target is installed during the install step (optional).
+ - `install_dir` string: directory to install to (optional).
+ - `build_by_default` bool: when set to true, to have this target be built by
+ default, that is, when invoking `meson compile`; the default value is false
+ (optional).
+ - `qresource` string: rcc source file to extract ts_files from; cannot be used
+ with ts_files kwarg. Available since v0.56.0.
+ - `rcc_extra_arguments` string[]: any additional arguments to `rcc` (optional),
+ when used with `qresource. Available since v0.56.0.
+
+Returns either: a list of custom targets for the compiled
+translations, or, if using a `qresource` file, a single custom target
+containing the processed source file, which should be passed to a main
+build target.
+
+## has_tools
+
+This method returns `true` if all tools used by this module are found,
+`false` otherwise.
+
+It should be used to compile optional Qt code:
+```meson
+qt5 = import('qt5')
+if qt5.has_tools(required: get_option('qt_feature'))
+ moc_files = qt5.preprocess(...)
+ ...
+endif
+```
+
+This method takes the following keyword arguments:
+- `required` bool | FeatureOption: by default, `required` is set to `false`. If `required` is set to
+ `true` or an enabled [`feature`](Build-options.md#features) and some tools are
+ missing Meson will abort.
+- `method` string: method used to find the Qt dependency (`auto` by default).
+
+*Since: 0.54.0*
+
+## Dependencies
+
+See [Qt dependencies](Dependencies.md#qt4-qt5)
+
+The 'modules' argument is used to include Qt modules in the project.
+See the Qt documentation for the [list of
+modules](http://doc.qt.io/qt-5/qtmodules.html).
+
+The 'private_headers' argument allows usage of Qt's modules private
+headers. (since v0.47.0)
+
+## Example
+A simple example would look like this:
+
+```meson
+qt5 = import('qt5')
+qt5_dep = dependency('qt5', modules: ['Core', 'Gui'])
+inc = include_directories('includes')
+moc_files = qt5.compile_moc(headers : 'myclass.h',
+ extra_arguments: ['-DMAKES_MY_MOC_HEADER_COMPILE'],
+ include_directories: inc,
+ dependencies: qt5_dep)
+translations = qt5.compile_translations(ts_files : 'myTranslation_fr.ts', build_by_default : true)
+executable('myprog', 'main.cpp', 'myclass.cpp', moc_files,
+ include_directories: inc,
+ dependencies : qt5_dep)
+```
+
+Sometimes, translations are embedded inside the binary using qresource
+files. In this case the ts files do not need to be explicitly listed,
+but will be inferred from the built qm files listed in the qresource
+file. For example:
+
+```meson
+qt5 = import('qt5')
+qt5_dep = dependency('qt5', modules: ['Core', 'Gui'])
+lang_cpp = qt5.compile_translations(qresource: 'lang.qrc')
+executable('myprog', 'main.cpp', lang_cpp,
+ dependencies: qt5_dep)
+```
diff --git a/docs/markdown/snippets/qt_preprocess_separate.md b/docs/markdown/snippets/qt_preprocess_separate.md
new file mode 100644
index 0000000..1035f9a
--- /dev/null
+++ b/docs/markdown/snippets/qt_preprocess_separate.md
@@ -0,0 +1,5 @@
+## Separate functions for qt preprocess
+
+`qt.preprocess` is a large, complicated function that does a lot of things,
+a new set of `compile_*` functions have been provided as well. These are
+conceptually simpler, as they do a single thing.
diff --git a/docs/markdown/snippets/qt_preprocessed_varargs_deprecated.md b/docs/markdown/snippets/qt_preprocessed_varargs_deprecated.md
new file mode 100644
index 0000000..5418eb3
--- /dev/null
+++ b/docs/markdown/snippets/qt_preprocessed_varargs_deprecated.md
@@ -0,0 +1,33 @@
+## Qt.preprocess source arguments deprecated
+
+The `qt.preprocess` method currently has this signature:
+`qt.preprocess(name: str | None, *srcs: str)`, this is not a nice signature
+because it's confusing, and there's a `sources` keyword argument as well.
+Both of these pass sources through unmodified, this is a bit of a historical
+accident, and not the way that any other module works. These have been
+deprecated, so instead of:
+```meson
+sources = qt.preprocess(
+ name,
+ list, of, sources,
+ sources : [more, sources],
+ ... # things to process,
+)
+
+executable(
+ 'foo',
+ sources,
+)
+```
+use
+```meson
+processed = qt.preprocess(
+ name,
+ ... # thins to process
+)
+
+executable(
+ 'foo',
+ 'list', 'of', 'sources', 'more', 'sources', processed,
+)
+```
diff --git a/docs/sitemap.txt b/docs/sitemap.txt
index 8014668..e1efe65 100644
--- a/docs/sitemap.txt
+++ b/docs/sitemap.txt
@@ -50,6 +50,7 @@ index.md
Python-module.md
Qt4-module.md
Qt5-module.md
+ Qt6-module.md
RPM-module.md
Rust-module.md
Simd-module.md
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):
diff --git a/run_mypy.py b/run_mypy.py
index 00daac6..982a3ae 100755
--- a/run_mypy.py
+++ b/run_mypy.py
@@ -34,6 +34,7 @@ modules = [
'mesonbuild/mlog.py',
'mesonbuild/modules/fs.py',
'mesonbuild/modules/unstable_rust.py',
+ 'mesonbuild/modules/qt.py',
'mesonbuild/mparser.py',
'mesonbuild/msetup.py',
'mesonbuild/mtest.py',
diff --git a/run_unittests.py b/run_unittests.py
index d17c243..92557b2 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -1846,7 +1846,7 @@ class DataTests(unittest.TestCase):
markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md']
exceptions = ['_Sidebar.md']
for f in markdownfiles:
- if f not in exceptions:
+ if f not in exceptions and not f.startswith('_include'):
self.assertIn(f, toc)
def test_vim_syntax_highlighting(self):
diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build
index fb7bd5a..2e85ddb 100644
--- a/test cases/frameworks/4 qt/meson.build
+++ b/test cases/frameworks/4 qt/meson.build
@@ -52,9 +52,10 @@ foreach qt : ['qt4', 'qt5', 'qt6']
prep = qtmodule.preprocess(
moc_headers : ['mainWindow.h'], # These need to be fed through the moc tool before use.
- ui_files : 'mainWindow.ui', # XML files that need to be compiled with the uic tol.
method : get_option('method')
)
+ # XML files that need to be compiled with the uic tol.
+ prep += qtmodule.compile_ui(sources : 'mainWindow.ui', method: get_option('method'))
# Resource file(s) for rcc compiler
extra_cpp_args = []
@@ -66,7 +67,11 @@ foreach qt : ['qt4', 'qt5', 'qt6']
endif
# Test that setting a unique name with a positional argument works
- qtmodule.preprocess(qt + 'teststuff', qresources : files(['stuff.qrc', 'stuff2.qrc']), method : get_option('method'))
+ qtmodule.compile_resources(
+ name : qt + 'teststuff',
+ sources : files(['stuff.qrc', 'stuff2.qrc']),
+ method : get_option('method')
+ )
# Test that passing extra arguments to rcc works
# qt4-rcc and qt5-rcc take different arguments, for example qt4: ['-compress', '3']; qt5: '--compress=3'
@@ -95,10 +100,10 @@ foreach qt : ['qt4', 'qt5', 'qt6']
# The build system needs to include the cpp files from
# headers but the user must manually include moc
# files from sources.
- manpreprocessed = qtmodule.preprocess(
- moc_extra_arguments : ['-DMOC_EXTRA_FLAG'], # This is just a random macro to test `moc_extra_arguments`
- moc_sources : 'manualinclude.cpp',
- moc_headers : 'manualinclude.h',
+ manpreprocessed = qtmodule.compile_moc(
+ extra_args : ['-DMOC_EXTRA_FLAG'], # This is just a random macro to test `extra_arguments`
+ sources : 'manualinclude.cpp',
+ headers : 'manualinclude.h',
method : get_option('method'))
qtmaninclude = executable(qt + 'maninclude',