Merge pull request #8822 from dcbaker/submit/annotate-and-check-qt-module

Rewrite the Qt module for type safety!
pull/8892/head
Jussi Pakkanen 4 years ago committed by GitHub
commit 6fb2f86379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docs/markdown/Qt4-module.md
  2. 92
      docs/markdown/Qt5-module.md
  3. 8
      docs/markdown/Qt6-module.md
  4. 160
      docs/markdown/_include_qt_base.md
  5. 5
      docs/markdown/snippets/qt_preprocess_separate.md
  6. 33
      docs/markdown/snippets/qt_preprocessed_varargs_deprecated.md
  7. 1
      docs/sitemap.txt
  8. 137
      mesonbuild/build.py
  9. 34
      mesonbuild/interpreter/interpreter.py
  10. 95
      mesonbuild/interpreter/interpreterobjects.py
  11. 35
      mesonbuild/interpreter/kwargs.py
  12. 8
      mesonbuild/modules/__init__.py
  13. 436
      mesonbuild/modules/qt.py
  14. 2
      mesonbuild/modules/unstable_rust.py
  15. 1
      run_mypy.py
  16. 2
      run_unittests.py
  17. 17
      test cases/frameworks/4 qt/meson.build

@ -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 }}

@ -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 }}

@ -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 }}

@ -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)
```

@ -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.

@ -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,
)
```

@ -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

@ -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):

@ -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)

@ -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)

@ -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]

@ -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

@ -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])

@ -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):

@ -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',

@ -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):

@ -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',

Loading…
Cancel
Save