From d617dc6869bcd5afcd784ff39cc5b6597b1912a2 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 9 Apr 2024 09:50:13 -0700 Subject: modules: rename qt.py -> _qt.py Since it's an implementation detail, and shouldn't be exposed. This also helps give better error messages when a user writes `import('qt')`, since otherwise you get an error about `qt doesn't have an initialize method`, and now you get `qt module doesn't exist`. --- mesonbuild/modules/_qt.py | 618 ++++++++++++++++++++++++++++++++++++++++++++++ mesonbuild/modules/qt.py | 618 ---------------------------------------------- mesonbuild/modules/qt4.py | 2 +- mesonbuild/modules/qt5.py | 2 +- mesonbuild/modules/qt6.py | 2 +- 5 files changed, 621 insertions(+), 621 deletions(-) create mode 100644 mesonbuild/modules/_qt.py delete mode 100644 mesonbuild/modules/qt.py (limited to 'mesonbuild/modules') diff --git a/mesonbuild/modules/_qt.py b/mesonbuild/modules/_qt.py new file mode 100644 index 0000000..7effa1f --- /dev/null +++ b/mesonbuild/modules/_qt.py @@ -0,0 +1,618 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2015 The Meson development team +# Copyright © 2021-2023 Intel Corporation + +from __future__ import annotations + +import os +import shutil +import typing as T +import xml.etree.ElementTree as ET + +from . import ModuleReturnValue, ExtensionModule +from .. import build +from .. import coredata +from .. import mlog +from ..dependencies import find_external_dependency, Dependency, ExternalLibrary, InternalDependency +from ..mesonlib import MesonException, File, version_compare, Popen_safe +from ..interpreter import extract_required_kwarg +from ..interpreter.type_checking import INSTALL_DIR_KW, INSTALL_KW, NoneType +from ..interpreterbase import ContainerTypeInfo, FeatureDeprecated, KwargInfo, noPosargs, FeatureNew, typed_kwargs +from ..programs import NonExistingExternalProgram + +if T.TYPE_CHECKING: + from . import ModuleState + from ..dependencies.qt import QtPkgConfigDependency, QmakeQtDependency + from ..interpreter import Interpreter + from ..interpreter import kwargs + from ..mesonlib import FileOrString + 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.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + extra_args: T.List[str] + method: str + + class UICompilerKwArgs(TypedDict): + + """Keyword arguments for the Ui Compiler method.""" + + sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + extra_args: T.List[str] + method: str + preserve_paths: bool + + class MocCompilerKwArgs(TypedDict): + + """Keyword arguments for the Moc Compiler method.""" + + sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + headers: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + extra_args: T.List[str] + method: str + include_directories: T.List[T.Union[str, build.IncludeDirs]] + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] + preserve_paths: bool + + class PreprocessKwArgs(TypedDict): + + sources: T.List[FileOrString] + moc_sources: T.List[T.Union[FileOrString, build.CustomTarget]] + moc_headers: T.List[T.Union[FileOrString, build.CustomTarget]] + qresources: T.List[FileOrString] + ui_files: T.List[T.Union[FileOrString, build.CustomTarget]] + moc_extra_arguments: T.List[str] + rcc_extra_arguments: T.List[str] + uic_extra_arguments: T.List[str] + include_directories: T.List[T.Union[str, build.IncludeDirs]] + dependencies: T.List[T.Union[Dependency, ExternalLibrary]] + method: str + preserve_paths: bool + + 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[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] + +class QtBaseModule(ExtensionModule): + _tools_detected = False + _rcc_supports_depfiles = False + _moc_supports_depfiles = False + + def __init__(self, interpreter: 'Interpreter', qt_version: int = 5): + ExtensionModule.__init__(self, interpreter) + self.qt_version = qt_version + # It is important that this list does not change order as the order of + # the returned ExternalPrograms will change as well + self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = { + 'moc': NonExistingExternalProgram('moc'), + 'uic': NonExistingExternalProgram('uic'), + 'rcc': NonExistingExternalProgram('rcc'), + 'lrelease': NonExistingExternalProgram('lrelease'), + } + self.methods.update({ + '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: 'ModuleState', qt_dep: 'QtDependencyType') -> None: + """Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH""" + wanted = f'== {qt_dep.version}' + + def gen_bins() -> T.Generator[T.Tuple[str, str], None, None]: + for b in self.tools: + if qt_dep.bindir: + yield os.path.join(qt_dep.bindir, b), b + if qt_dep.libexecdir: + yield os.path.join(qt_dep.libexecdir, b), b + # prefer the (official) or (unofficial) -qt + # of the tool to the plain one, as we + # don't know what the unsuffixed one points to without calling it. + yield f'{b}{qt_dep.qtver}', b + yield f'{b}-qt{qt_dep.qtver}', b + yield b, b + + for b, name in gen_bins(): + if self.tools[name].found(): + continue + + if name == 'lrelease': + arg = ['-version'] + elif version_compare(qt_dep.version, '>= 5'): + arg = ['--version'] + else: + arg = ['-v'] + + # Ensure that the version of qt and each tool are the same + def get_version(p: T.Union[ExternalProgram, build.Executable]) -> str: + _, out, err = Popen_safe(p.get_command() + arg) + if name == 'lrelease' or not qt_dep.version.startswith('4'): + care = out + else: + care = err + return care.rsplit(' ', maxsplit=1)[-1].replace(')', '').strip() + + p = state.find_program(b, required=False, + version_func=get_version, + wanted=wanted) + if p.found(): + self.tools[name] = p + + def _detect_tools(self, state: 'ModuleState', method: str, required: bool = True) -> None: + if self._tools_detected: + return + self._tools_detected = True + mlog.log(f'Detecting Qt{self.qt_version} tools') + kwargs = {'required': required, 'modules': 'Core', 'method': method} + # 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.15.0'): + self._moc_supports_depfiles = True + else: + mlog.warning('moc dependencies will not work properly until you move to Qt >= 5.15', fatal=False) + if 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) + else: + suffix = f'-qt{self.qt_version}' + self.tools['moc'] = NonExistingExternalProgram(name='moc' + suffix) + self.tools['uic'] = NonExistingExternalProgram(name='uic' + suffix) + self.tools['rcc'] = NonExistingExternalProgram(name='rcc' + suffix) + self.tools['lrelease'] = NonExistingExternalProgram(name='lrelease' + suffix) + + @staticmethod + def _qrc_nodes(state: 'ModuleState', rcc_file: '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) + 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 trying to check here? (probably parse errors?) + try: + tree = ET.parse(abspath) + root = tree.getroot() + result: T.List[str] = [] + for child in root[0]: + if child.tag != 'file': + mlog.warning("malformed rcc file: ", os.path.join(state.subdir, str(rcc_file))) + break + elif child.text is None: + raise MesonException(f' element without a path in {os.path.join(state.subdir, str(rcc_file))}') + else: + result.append(child.text) + + return rcc_dirname, result + except MesonException: + raise + except Exception: + raise MesonException(f'Unable to parse resource file {abspath}') + + def _parse_qrc_deps(self, state: 'ModuleState', + rcc_file_: T.Union['FileOrString', build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]) -> T.List[File]: + result: T.List[File] = [] + inputs: T.Sequence['FileOrString'] = [] + if isinstance(rcc_file_, (str, File)): + inputs = [rcc_file_] + else: + inputs = rcc_file_.get_outputs() + + for rcc_file in inputs: + rcc_dirname, nodes = self._qrc_nodes(state, rcc_file) + for resource_path in nodes: + # We need to guess if the pointed resource is: + # a) in build directory -> implies a generated file + # b) in source directory + # c) somewhere else external dependency file to bundle + # + # Also from qrc documentation: relative path are always from qrc file + # So relative path must always be computed from qrc file ! + if os.path.isabs(resource_path): + # a) + if resource_path.startswith(os.path.abspath(state.environment.build_dir)): + resource_relpath = os.path.relpath(resource_path, state.environment.build_dir) + result.append(File(is_built=True, subdir='', fname=resource_relpath)) + # either b) or c) + else: + result.append(File(is_built=False, subdir=state.subdir, fname=resource_path)) + else: + path_from_rcc = os.path.normpath(os.path.join(rcc_dirname, resource_path)) + # a) + if path_from_rcc.startswith(state.environment.build_dir): + result.append(File(is_built=True, subdir=state.subdir, fname=resource_path)) + # b) + else: + result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc)) + return result + + @FeatureNew('qt.has_tools', '0.54.0') + @noPosargs + @typed_kwargs( + 'qt.has_tools', + KwargInfo('required', (bool, coredata.UserFeatureOption), 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') + return False + self._detect_tools(state, method, required=False) + for tool in self.tools.values(): + if not tool.found(): + if required: + raise MesonException('Qt tools not found') + return False + return True + + @FeatureNew('qt.compile_resources', '0.59.0') + @noPosargs + @typed_kwargs( + 'qt.compile_resources', + KwargInfo('name', (str, NoneType)), + KwargInfo( + 'sources', + ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), 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. + """ + if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['sources']): + FeatureNew.single_use('qt.compile_resources: custom_target or generator for "sources" keyword argument', + '0.60.0', state.subproject, location=state.current_node) + out = self._compile_resources_impl(state, kwargs) + return ModuleReturnValue(out, [out]) + + def _compile_resources_impl(self, state: 'ModuleState', kwargs: 'ResourceCompilerKwArgs') -> T.List[build.CustomTarget]: + # Avoid the FeatureNew when dispatching from preprocess + self._detect_tools(state, kwargs['method']) + if not self.tools['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: T.List['FileOrString'] = [] + for s in kwargs['sources']: + if isinstance(s, (str, File)): + sources.append(s) + else: + sources.extend(s.get_outputs()) + 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)) + + res_target = build.CustomTarget( + name, + state.subdir, + state.subproject, + state.environment, + self.tools['rcc'].get_command() + ['-name', name, '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, + sources, + [f'{name}.cpp'], + depend_files=qrc_deps, + depfile=f'{name}.d', + description='Compiling Qt resources {}', + ) + 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(".", "_")}' + res_target = build.CustomTarget( + name, + state.subdir, + state.subproject, + state.environment, + self.tools['rcc'].get_command() + ['-name', '@BASENAME@', '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, + [rcc_file], + [f'{name}.cpp'], + depend_files=qrc_deps, + depfile=f'{name}.d', + description='Compiling Qt resources {}', + ) + targets.append(res_target) + + return targets + + @FeatureNew('qt.compile_ui', '0.59.0') + @noPosargs + @typed_kwargs( + 'qt.compile_ui', + KwargInfo( + 'sources', + ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), allow_empty=False), + listify=True, + required=True, + ), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('method', str, default='auto'), + KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), + ) + def compile_ui(self, state: ModuleState, args: T.Tuple, kwargs: UICompilerKwArgs) -> ModuleReturnValue: + """Compile UI resources into cpp headers.""" + if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['sources']): + FeatureNew.single_use('qt.compile_ui: custom_target or generator for "sources" keyword argument', + '0.60.0', state.subproject, location=state.current_node) + out = self._compile_ui_impl(state, kwargs) + return ModuleReturnValue(out, [out]) + + def _compile_ui_impl(self, state: ModuleState, kwargs: UICompilerKwArgs) -> build.GeneratedList: + # Avoid the FeatureNew when dispatching from preprocess + self._detect_tools(state, kwargs['method']) + if not self.tools['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)) + + preserve_path_from = os.path.join(state.source_root, state.subdir) if kwargs['preserve_paths'] else None + # TODO: This generator isn't added to the generator list in the Interpreter + gen = build.Generator( + self.tools['uic'], + kwargs['extra_args'] + ['-o', '@OUTPUT@', '@INPUT@'], + ['ui_@BASENAME@.h'], + name=f'Qt{self.qt_version} ui') + return gen.process_files(kwargs['sources'], state, preserve_path_from) + + @FeatureNew('qt.compile_moc', '0.59.0') + @noPosargs + @typed_kwargs( + 'qt.compile_moc', + KwargInfo( + 'sources', + ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), + listify=True, + default=[], + ), + KwargInfo( + 'headers', + ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), + listify=True, + default=[] + ), + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('method', str, default='auto'), + KwargInfo('include_directories', ContainerTypeInfo(list, (build.IncludeDirs, str)), listify=True, default=[]), + KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), + KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), + ) + def compile_moc(self, state: ModuleState, args: T.Tuple, kwargs: MocCompilerKwArgs) -> ModuleReturnValue: + if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['headers']): + FeatureNew.single_use('qt.compile_moc: custom_target or generator for "headers" keyword argument', + '0.60.0', state.subproject, location=state.current_node) + if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['sources']): + FeatureNew.single_use('qt.compile_moc: custom_target or generator for "sources" keyword argument', + '0.60.0', state.subproject, location=state.current_node) + out = self._compile_moc_impl(state, kwargs) + return ModuleReturnValue(out, [out]) + + def _compile_moc_impl(self, state: ModuleState, kwargs: MocCompilerKwArgs) -> T.List[build.GeneratedList]: + # Avoid the FeatureNew when dispatching from preprocess + self._detect_tools(state, kwargs['method']) + if not self.tools['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 provided 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.get_all_compile_args() if a.startswith(('-I', '-D'))) + if isinstance(dep, InternalDependency): + for incl in dep.include_directories: + compile_args.extend(f'-I{i}' for i in incl.to_string_list(self.interpreter.source_root, self.interpreter.environment.build_dir)) + + output: T.List[build.GeneratedList] = [] + + # depfile arguments (defaults to .d) + DEPFILE_ARGS: T.List[str] = ['--output-dep-file'] if self._moc_supports_depfiles else [] + + arguments = kwargs['extra_args'] + DEPFILE_ARGS + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] + preserve_path_from = os.path.join(state.source_root, state.subdir) if kwargs['preserve_paths'] else None + if kwargs['headers']: + moc_gen = build.Generator( + self.tools['moc'], arguments, ['moc_@BASENAME@.cpp'], + depfile='moc_@BASENAME@.cpp.d', + name=f'Qt{self.qt_version} moc header') + output.append(moc_gen.process_files(kwargs['headers'], state, preserve_path_from)) + if kwargs['sources']: + moc_gen = build.Generator( + self.tools['moc'], arguments, ['@BASENAME@.moc'], + depfile='@BASENAME@.moc.d', + name=f'Qt{self.qt_version} moc source') + output.append(moc_gen.process_files(kwargs['sources'], state, preserve_path_from)) + + return output + + # We can't use typed_pos_args here, the signature is ambiguous + @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, build.CustomTarget)), listify=True, default=[]), + KwargInfo('moc_sources', ContainerTypeInfo(list, (File, str, build.CustomTarget)), listify=True, default=[]), + KwargInfo('moc_headers', ContainerTypeInfo(list, (File, str, build.CustomTarget)), 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, (build.IncludeDirs, str)), listify=True, default=[]), + KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), + KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), + ) + 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, location=state.current_node) + # List is invariant, os we have to cast... + sources = T.cast('T.List[T.Union[str, File, build.GeneratedList, build.CustomTarget]]', + _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: + name = args[0] + if not isinstance(name, str): + raise build.InvalidArguments('First argument to qt.preprocess must be a string') + rcc_kwargs['name'] = name + sources.extend(self._compile_resources_impl(state, rcc_kwargs)) + + if kwargs['ui_files']: + ui_kwargs: UICompilerKwArgs = { + 'sources': kwargs['ui_files'], + 'extra_args': kwargs['uic_extra_arguments'], + 'method': method, + 'preserve_paths': kwargs['preserve_paths'], + } + sources.append(self._compile_ui_impl(state, ui_kwargs)) + + 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, + 'preserve_paths': kwargs['preserve_paths'], + } + sources.extend(self._compile_moc_impl(state, moc_kwargs)) + + return ModuleReturnValue(sources, [sources]) + + @FeatureNew('qt.compile_translations', '0.44.0') + @noPosargs + @typed_kwargs( + 'qt.compile_translations', + KwargInfo('build_by_default', bool, default=False), + INSTALL_KW, + INSTALL_DIR_KW, + KwargInfo('method', str, default='auto'), + KwargInfo('qresource', (str, NoneType), 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, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), listify=True, default=[]), + ) + def compile_translations(self, state: 'ModuleState', args: T.Tuple, kwargs: 'CompileTranslationsKwArgs') -> ModuleReturnValue: + ts_files = kwargs['ts_files'] + if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in ts_files): + FeatureNew.single_use('qt.compile_translations: custom_target or generator for "ts_files" keyword argument', + '0.60.0', state.subproject, location=state.current_node) + if kwargs['install'] and not kwargs['install_dir']: + raise MesonException('qt.compile_translations: "install_dir" keyword argument must be set when "install" is true.') + 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 = 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) + + _, nodes = self._qrc_nodes(state, qresource_file) + for c in nodes: + if c.endswith('.qm'): + 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_file, '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.tools['lrelease'].found(): + raise MesonException('qt.compile_translations: ' + + self.tools['lrelease'].name + ' not found') + if qresource: + # In this case we know that ts_files is always a List[str], as + # it's generated above and no ts_files are passed in. However, + # mypy can't figure that out so we use assert to assure it that + # what we're doing is safe + assert isinstance(ts, str), 'for mypy' + outdir = os.path.dirname(os.path.normpath(os.path.join(state.subdir, ts))) + ts = os.path.basename(ts) + else: + outdir = state.subdir + cmd: T.List[T.Union[ExternalProgram, build.Executable, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] + lrelease_target = build.CustomTarget( + f'qt{self.qt_version}-compile-{ts}', + outdir, + state.subproject, + state.environment, + cmd, + [ts], + ['@BASENAME@.qm'], + install=kwargs['install'], + install_dir=[kwargs['install_dir']], + install_tag=['i18n'], + build_by_default=kwargs['build_by_default'], + description='Compiling Qt translations {}', + ) + translations.append(lrelease_target) + if qresource: + return ModuleReturnValue(results.return_value[0], [results.new_objects, translations]) + else: + return ModuleReturnValue(translations, [translations]) diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py deleted file mode 100644 index 7effa1f..0000000 --- a/mesonbuild/modules/qt.py +++ /dev/null @@ -1,618 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright 2015 The Meson development team -# Copyright © 2021-2023 Intel Corporation - -from __future__ import annotations - -import os -import shutil -import typing as T -import xml.etree.ElementTree as ET - -from . import ModuleReturnValue, ExtensionModule -from .. import build -from .. import coredata -from .. import mlog -from ..dependencies import find_external_dependency, Dependency, ExternalLibrary, InternalDependency -from ..mesonlib import MesonException, File, version_compare, Popen_safe -from ..interpreter import extract_required_kwarg -from ..interpreter.type_checking import INSTALL_DIR_KW, INSTALL_KW, NoneType -from ..interpreterbase import ContainerTypeInfo, FeatureDeprecated, KwargInfo, noPosargs, FeatureNew, typed_kwargs -from ..programs import NonExistingExternalProgram - -if T.TYPE_CHECKING: - from . import ModuleState - from ..dependencies.qt import QtPkgConfigDependency, QmakeQtDependency - from ..interpreter import Interpreter - from ..interpreter import kwargs - from ..mesonlib import FileOrString - 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.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] - extra_args: T.List[str] - method: str - - class UICompilerKwArgs(TypedDict): - - """Keyword arguments for the Ui Compiler method.""" - - sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] - extra_args: T.List[str] - method: str - preserve_paths: bool - - class MocCompilerKwArgs(TypedDict): - - """Keyword arguments for the Moc Compiler method.""" - - sources: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] - headers: T.Sequence[T.Union[FileOrString, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] - extra_args: T.List[str] - method: str - include_directories: T.List[T.Union[str, build.IncludeDirs]] - dependencies: T.List[T.Union[Dependency, ExternalLibrary]] - preserve_paths: bool - - class PreprocessKwArgs(TypedDict): - - sources: T.List[FileOrString] - moc_sources: T.List[T.Union[FileOrString, build.CustomTarget]] - moc_headers: T.List[T.Union[FileOrString, build.CustomTarget]] - qresources: T.List[FileOrString] - ui_files: T.List[T.Union[FileOrString, build.CustomTarget]] - moc_extra_arguments: T.List[str] - rcc_extra_arguments: T.List[str] - uic_extra_arguments: T.List[str] - include_directories: T.List[T.Union[str, build.IncludeDirs]] - dependencies: T.List[T.Union[Dependency, ExternalLibrary]] - method: str - preserve_paths: bool - - 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[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] - -class QtBaseModule(ExtensionModule): - _tools_detected = False - _rcc_supports_depfiles = False - _moc_supports_depfiles = False - - def __init__(self, interpreter: 'Interpreter', qt_version: int = 5): - ExtensionModule.__init__(self, interpreter) - self.qt_version = qt_version - # It is important that this list does not change order as the order of - # the returned ExternalPrograms will change as well - self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = { - 'moc': NonExistingExternalProgram('moc'), - 'uic': NonExistingExternalProgram('uic'), - 'rcc': NonExistingExternalProgram('rcc'), - 'lrelease': NonExistingExternalProgram('lrelease'), - } - self.methods.update({ - '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: 'ModuleState', qt_dep: 'QtDependencyType') -> None: - """Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH""" - wanted = f'== {qt_dep.version}' - - def gen_bins() -> T.Generator[T.Tuple[str, str], None, None]: - for b in self.tools: - if qt_dep.bindir: - yield os.path.join(qt_dep.bindir, b), b - if qt_dep.libexecdir: - yield os.path.join(qt_dep.libexecdir, b), b - # prefer the (official) or (unofficial) -qt - # of the tool to the plain one, as we - # don't know what the unsuffixed one points to without calling it. - yield f'{b}{qt_dep.qtver}', b - yield f'{b}-qt{qt_dep.qtver}', b - yield b, b - - for b, name in gen_bins(): - if self.tools[name].found(): - continue - - if name == 'lrelease': - arg = ['-version'] - elif version_compare(qt_dep.version, '>= 5'): - arg = ['--version'] - else: - arg = ['-v'] - - # Ensure that the version of qt and each tool are the same - def get_version(p: T.Union[ExternalProgram, build.Executable]) -> str: - _, out, err = Popen_safe(p.get_command() + arg) - if name == 'lrelease' or not qt_dep.version.startswith('4'): - care = out - else: - care = err - return care.rsplit(' ', maxsplit=1)[-1].replace(')', '').strip() - - p = state.find_program(b, required=False, - version_func=get_version, - wanted=wanted) - if p.found(): - self.tools[name] = p - - def _detect_tools(self, state: 'ModuleState', method: str, required: bool = True) -> None: - if self._tools_detected: - return - self._tools_detected = True - mlog.log(f'Detecting Qt{self.qt_version} tools') - kwargs = {'required': required, 'modules': 'Core', 'method': method} - # 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.15.0'): - self._moc_supports_depfiles = True - else: - mlog.warning('moc dependencies will not work properly until you move to Qt >= 5.15', fatal=False) - if 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) - else: - suffix = f'-qt{self.qt_version}' - self.tools['moc'] = NonExistingExternalProgram(name='moc' + suffix) - self.tools['uic'] = NonExistingExternalProgram(name='uic' + suffix) - self.tools['rcc'] = NonExistingExternalProgram(name='rcc' + suffix) - self.tools['lrelease'] = NonExistingExternalProgram(name='lrelease' + suffix) - - @staticmethod - def _qrc_nodes(state: 'ModuleState', rcc_file: '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) - 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 trying to check here? (probably parse errors?) - try: - tree = ET.parse(abspath) - root = tree.getroot() - result: T.List[str] = [] - for child in root[0]: - if child.tag != 'file': - mlog.warning("malformed rcc file: ", os.path.join(state.subdir, str(rcc_file))) - break - elif child.text is None: - raise MesonException(f' element without a path in {os.path.join(state.subdir, str(rcc_file))}') - else: - result.append(child.text) - - return rcc_dirname, result - except MesonException: - raise - except Exception: - raise MesonException(f'Unable to parse resource file {abspath}') - - def _parse_qrc_deps(self, state: 'ModuleState', - rcc_file_: T.Union['FileOrString', build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]) -> T.List[File]: - result: T.List[File] = [] - inputs: T.Sequence['FileOrString'] = [] - if isinstance(rcc_file_, (str, File)): - inputs = [rcc_file_] - else: - inputs = rcc_file_.get_outputs() - - for rcc_file in inputs: - rcc_dirname, nodes = self._qrc_nodes(state, rcc_file) - for resource_path in nodes: - # We need to guess if the pointed resource is: - # a) in build directory -> implies a generated file - # b) in source directory - # c) somewhere else external dependency file to bundle - # - # Also from qrc documentation: relative path are always from qrc file - # So relative path must always be computed from qrc file ! - if os.path.isabs(resource_path): - # a) - if resource_path.startswith(os.path.abspath(state.environment.build_dir)): - resource_relpath = os.path.relpath(resource_path, state.environment.build_dir) - result.append(File(is_built=True, subdir='', fname=resource_relpath)) - # either b) or c) - else: - result.append(File(is_built=False, subdir=state.subdir, fname=resource_path)) - else: - path_from_rcc = os.path.normpath(os.path.join(rcc_dirname, resource_path)) - # a) - if path_from_rcc.startswith(state.environment.build_dir): - result.append(File(is_built=True, subdir=state.subdir, fname=resource_path)) - # b) - else: - result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc)) - return result - - @FeatureNew('qt.has_tools', '0.54.0') - @noPosargs - @typed_kwargs( - 'qt.has_tools', - KwargInfo('required', (bool, coredata.UserFeatureOption), 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') - return False - self._detect_tools(state, method, required=False) - for tool in self.tools.values(): - if not tool.found(): - if required: - raise MesonException('Qt tools not found') - return False - return True - - @FeatureNew('qt.compile_resources', '0.59.0') - @noPosargs - @typed_kwargs( - 'qt.compile_resources', - KwargInfo('name', (str, NoneType)), - KwargInfo( - 'sources', - ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), 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. - """ - if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['sources']): - FeatureNew.single_use('qt.compile_resources: custom_target or generator for "sources" keyword argument', - '0.60.0', state.subproject, location=state.current_node) - out = self._compile_resources_impl(state, kwargs) - return ModuleReturnValue(out, [out]) - - def _compile_resources_impl(self, state: 'ModuleState', kwargs: 'ResourceCompilerKwArgs') -> T.List[build.CustomTarget]: - # Avoid the FeatureNew when dispatching from preprocess - self._detect_tools(state, kwargs['method']) - if not self.tools['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: T.List['FileOrString'] = [] - for s in kwargs['sources']: - if isinstance(s, (str, File)): - sources.append(s) - else: - sources.extend(s.get_outputs()) - 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)) - - res_target = build.CustomTarget( - name, - state.subdir, - state.subproject, - state.environment, - self.tools['rcc'].get_command() + ['-name', name, '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, - sources, - [f'{name}.cpp'], - depend_files=qrc_deps, - depfile=f'{name}.d', - description='Compiling Qt resources {}', - ) - 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(".", "_")}' - res_target = build.CustomTarget( - name, - state.subdir, - state.subproject, - state.environment, - self.tools['rcc'].get_command() + ['-name', '@BASENAME@', '-o', '@OUTPUT@'] + extra_args + ['@INPUT@'] + DEPFILE_ARGS, - [rcc_file], - [f'{name}.cpp'], - depend_files=qrc_deps, - depfile=f'{name}.d', - description='Compiling Qt resources {}', - ) - targets.append(res_target) - - return targets - - @FeatureNew('qt.compile_ui', '0.59.0') - @noPosargs - @typed_kwargs( - 'qt.compile_ui', - KwargInfo( - 'sources', - ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList), allow_empty=False), - listify=True, - required=True, - ), - KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), - KwargInfo('method', str, default='auto'), - KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), - ) - def compile_ui(self, state: ModuleState, args: T.Tuple, kwargs: UICompilerKwArgs) -> ModuleReturnValue: - """Compile UI resources into cpp headers.""" - if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['sources']): - FeatureNew.single_use('qt.compile_ui: custom_target or generator for "sources" keyword argument', - '0.60.0', state.subproject, location=state.current_node) - out = self._compile_ui_impl(state, kwargs) - return ModuleReturnValue(out, [out]) - - def _compile_ui_impl(self, state: ModuleState, kwargs: UICompilerKwArgs) -> build.GeneratedList: - # Avoid the FeatureNew when dispatching from preprocess - self._detect_tools(state, kwargs['method']) - if not self.tools['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)) - - preserve_path_from = os.path.join(state.source_root, state.subdir) if kwargs['preserve_paths'] else None - # TODO: This generator isn't added to the generator list in the Interpreter - gen = build.Generator( - self.tools['uic'], - kwargs['extra_args'] + ['-o', '@OUTPUT@', '@INPUT@'], - ['ui_@BASENAME@.h'], - name=f'Qt{self.qt_version} ui') - return gen.process_files(kwargs['sources'], state, preserve_path_from) - - @FeatureNew('qt.compile_moc', '0.59.0') - @noPosargs - @typed_kwargs( - 'qt.compile_moc', - KwargInfo( - 'sources', - ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), - listify=True, - default=[], - ), - KwargInfo( - 'headers', - ContainerTypeInfo(list, (File, str, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), - listify=True, - default=[] - ), - KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), - KwargInfo('method', str, default='auto'), - KwargInfo('include_directories', ContainerTypeInfo(list, (build.IncludeDirs, str)), listify=True, default=[]), - KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), - KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), - ) - def compile_moc(self, state: ModuleState, args: T.Tuple, kwargs: MocCompilerKwArgs) -> ModuleReturnValue: - if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['headers']): - FeatureNew.single_use('qt.compile_moc: custom_target or generator for "headers" keyword argument', - '0.60.0', state.subproject, location=state.current_node) - if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in kwargs['sources']): - FeatureNew.single_use('qt.compile_moc: custom_target or generator for "sources" keyword argument', - '0.60.0', state.subproject, location=state.current_node) - out = self._compile_moc_impl(state, kwargs) - return ModuleReturnValue(out, [out]) - - def _compile_moc_impl(self, state: ModuleState, kwargs: MocCompilerKwArgs) -> T.List[build.GeneratedList]: - # Avoid the FeatureNew when dispatching from preprocess - self._detect_tools(state, kwargs['method']) - if not self.tools['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 provided 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.get_all_compile_args() if a.startswith(('-I', '-D'))) - if isinstance(dep, InternalDependency): - for incl in dep.include_directories: - compile_args.extend(f'-I{i}' for i in incl.to_string_list(self.interpreter.source_root, self.interpreter.environment.build_dir)) - - output: T.List[build.GeneratedList] = [] - - # depfile arguments (defaults to .d) - DEPFILE_ARGS: T.List[str] = ['--output-dep-file'] if self._moc_supports_depfiles else [] - - arguments = kwargs['extra_args'] + DEPFILE_ARGS + inc + compile_args + ['@INPUT@', '-o', '@OUTPUT@'] - preserve_path_from = os.path.join(state.source_root, state.subdir) if kwargs['preserve_paths'] else None - if kwargs['headers']: - moc_gen = build.Generator( - self.tools['moc'], arguments, ['moc_@BASENAME@.cpp'], - depfile='moc_@BASENAME@.cpp.d', - name=f'Qt{self.qt_version} moc header') - output.append(moc_gen.process_files(kwargs['headers'], state, preserve_path_from)) - if kwargs['sources']: - moc_gen = build.Generator( - self.tools['moc'], arguments, ['@BASENAME@.moc'], - depfile='@BASENAME@.moc.d', - name=f'Qt{self.qt_version} moc source') - output.append(moc_gen.process_files(kwargs['sources'], state, preserve_path_from)) - - return output - - # We can't use typed_pos_args here, the signature is ambiguous - @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, build.CustomTarget)), listify=True, default=[]), - KwargInfo('moc_sources', ContainerTypeInfo(list, (File, str, build.CustomTarget)), listify=True, default=[]), - KwargInfo('moc_headers', ContainerTypeInfo(list, (File, str, build.CustomTarget)), 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, (build.IncludeDirs, str)), listify=True, default=[]), - KwargInfo('dependencies', ContainerTypeInfo(list, (Dependency, ExternalLibrary)), listify=True, default=[]), - KwargInfo('preserve_paths', bool, default=False, since='1.4.0'), - ) - 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, location=state.current_node) - # List is invariant, os we have to cast... - sources = T.cast('T.List[T.Union[str, File, build.GeneratedList, build.CustomTarget]]', - _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: - name = args[0] - if not isinstance(name, str): - raise build.InvalidArguments('First argument to qt.preprocess must be a string') - rcc_kwargs['name'] = name - sources.extend(self._compile_resources_impl(state, rcc_kwargs)) - - if kwargs['ui_files']: - ui_kwargs: UICompilerKwArgs = { - 'sources': kwargs['ui_files'], - 'extra_args': kwargs['uic_extra_arguments'], - 'method': method, - 'preserve_paths': kwargs['preserve_paths'], - } - sources.append(self._compile_ui_impl(state, ui_kwargs)) - - 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, - 'preserve_paths': kwargs['preserve_paths'], - } - sources.extend(self._compile_moc_impl(state, moc_kwargs)) - - return ModuleReturnValue(sources, [sources]) - - @FeatureNew('qt.compile_translations', '0.44.0') - @noPosargs - @typed_kwargs( - 'qt.compile_translations', - KwargInfo('build_by_default', bool, default=False), - INSTALL_KW, - INSTALL_DIR_KW, - KwargInfo('method', str, default='auto'), - KwargInfo('qresource', (str, NoneType), 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, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), listify=True, default=[]), - ) - def compile_translations(self, state: 'ModuleState', args: T.Tuple, kwargs: 'CompileTranslationsKwArgs') -> ModuleReturnValue: - ts_files = kwargs['ts_files'] - if any(isinstance(s, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for s in ts_files): - FeatureNew.single_use('qt.compile_translations: custom_target or generator for "ts_files" keyword argument', - '0.60.0', state.subproject, location=state.current_node) - if kwargs['install'] and not kwargs['install_dir']: - raise MesonException('qt.compile_translations: "install_dir" keyword argument must be set when "install" is true.') - 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 = 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) - - _, nodes = self._qrc_nodes(state, qresource_file) - for c in nodes: - if c.endswith('.qm'): - 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_file, '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.tools['lrelease'].found(): - raise MesonException('qt.compile_translations: ' + - self.tools['lrelease'].name + ' not found') - if qresource: - # In this case we know that ts_files is always a List[str], as - # it's generated above and no ts_files are passed in. However, - # mypy can't figure that out so we use assert to assure it that - # what we're doing is safe - assert isinstance(ts, str), 'for mypy' - outdir = os.path.dirname(os.path.normpath(os.path.join(state.subdir, ts))) - ts = os.path.basename(ts) - else: - outdir = state.subdir - cmd: T.List[T.Union[ExternalProgram, build.Executable, str]] = [self.tools['lrelease'], '@INPUT@', '-qm', '@OUTPUT@'] - lrelease_target = build.CustomTarget( - f'qt{self.qt_version}-compile-{ts}', - outdir, - state.subproject, - state.environment, - cmd, - [ts], - ['@BASENAME@.qm'], - install=kwargs['install'], - install_dir=[kwargs['install_dir']], - install_tag=['i18n'], - build_by_default=kwargs['build_by_default'], - description='Compiling Qt translations {}', - ) - translations.append(lrelease_target) - if qresource: - return ModuleReturnValue(results.return_value[0], [results.new_objects, translations]) - else: - return ModuleReturnValue(translations, [translations]) diff --git a/mesonbuild/modules/qt4.py b/mesonbuild/modules/qt4.py index 354b4a5..47eb007 100644 --- a/mesonbuild/modules/qt4.py +++ b/mesonbuild/modules/qt4.py @@ -4,7 +4,7 @@ from __future__ import annotations import typing as T -from .qt import QtBaseModule +from ._qt import QtBaseModule from . import ModuleInfo if T.TYPE_CHECKING: diff --git a/mesonbuild/modules/qt5.py b/mesonbuild/modules/qt5.py index 11958c9..f950c09 100644 --- a/mesonbuild/modules/qt5.py +++ b/mesonbuild/modules/qt5.py @@ -4,7 +4,7 @@ from __future__ import annotations import typing as T -from .qt import QtBaseModule +from ._qt import QtBaseModule from . import ModuleInfo if T.TYPE_CHECKING: diff --git a/mesonbuild/modules/qt6.py b/mesonbuild/modules/qt6.py index b232046..bf23305 100644 --- a/mesonbuild/modules/qt6.py +++ b/mesonbuild/modules/qt6.py @@ -4,7 +4,7 @@ from __future__ import annotations import typing as T -from .qt import QtBaseModule +from ._qt import QtBaseModule from . import ModuleInfo if T.TYPE_CHECKING: -- cgit v1.1