qt module: allow has_tools to specify which tools to check

This allows checking for tools that may not be available in older version of qt
or avoiding requesting tools that may not be necessary for a given project

Co-authored-by: Nirbheek Chauhan <nirbheek@centricular.com>
pull/13712/head
Pierre Lamot 6 months ago committed by Dylan Baker
parent c572879bc9
commit 6797f9bc15
  1. 3
      docs/markdown/Qt6-module.md
  2. 12
      docs/markdown/snippets/qt_has_tools_ignore.md
  3. 27
      mesonbuild/modules/_qt.py
  4. 38
      test cases/frameworks/4 qt/meson.build
  5. 1
      test cases/frameworks/4 qt/meson_options.txt
  6. 14
      unittests/linuxliketests.py

@ -156,6 +156,9 @@ This method takes the following keyword arguments:
`true` or an enabled [`feature`](Build-options.md#features) and some tools are `true` or an enabled [`feature`](Build-options.md#features) and some tools are
missing Meson will abort. missing Meson will abort.
- `method` string: The method to use to detect Qt, see [[dependency]] - `method` string: The method to use to detect Qt, see [[dependency]]
- `tools`: string[]: *Since 1.6.0*. List of tools to check. Testable tools
are `moc`, `uic`, `rcc` and `lrelease`. By default `tools` is set to `['moc',
'uic', 'rcc', 'lrelease']`
## Dependencies ## Dependencies

@ -0,0 +1,12 @@
## Tools can be selected when calling `has_tools()` on the Qt modules
When checking for the presence of Qt tools, you can now explictly ask Meson
which tools you need. This is particularly useful when you do not need
`lrelease` because you are not shipping any translations. For example:
```meson
qt6_mod = import('qt6')
qt6_mod.has_tools(required: true, tools: ['moc', 'uic', 'rcc'])
```
valid tools are `moc`, `uic`, `rcc` and `lrelease`.

@ -27,6 +27,7 @@ if T.TYPE_CHECKING:
from ..interpreter import kwargs from ..interpreter import kwargs
from ..mesonlib import FileOrString from ..mesonlib import FileOrString
from ..programs import ExternalProgram from ..programs import ExternalProgram
from typing_extensions import Literal
QtDependencyType = T.Union[QtPkgConfigDependency, QmakeQtDependency] QtDependencyType = T.Union[QtPkgConfigDependency, QmakeQtDependency]
@ -80,6 +81,7 @@ if T.TYPE_CHECKING:
class HasToolKwArgs(kwargs.ExtractRequired): class HasToolKwArgs(kwargs.ExtractRequired):
method: str method: str
tools: T.List[Literal['moc', 'uic', 'rcc', 'lrelease']]
class CompileTranslationsKwArgs(TypedDict): class CompileTranslationsKwArgs(TypedDict):
@ -91,10 +93,21 @@ if T.TYPE_CHECKING:
rcc_extra_arguments: T.List[str] rcc_extra_arguments: T.List[str]
ts_files: T.List[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] ts_files: T.List[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]
def _list_in_set_validator(choices: T.Set[str]) -> T.Callable[[T.List[str]], T.Optional[str]]:
"""Check that the choice given was one of the given set."""
def inner(checklist: T.List[str]) -> T.Optional[str]:
invalid = set(checklist).difference(choices)
if invalid:
return f"invalid selections {', '.join(sorted(invalid))}, valid elements are {', '.join(sorted(choices))}."
return None
return inner
class QtBaseModule(ExtensionModule): class QtBaseModule(ExtensionModule):
_tools_detected = False _tools_detected = False
_rcc_supports_depfiles = False _rcc_supports_depfiles = False
_moc_supports_depfiles = False _moc_supports_depfiles = False
_set_of_qt_tools = {'moc', 'uic', 'rcc', 'lrelease'}
def __init__(self, interpreter: 'Interpreter', qt_version: int = 5): def __init__(self, interpreter: 'Interpreter', qt_version: int = 5):
ExtensionModule.__init__(self, interpreter) ExtensionModule.__init__(self, interpreter)
@ -102,10 +115,7 @@ class QtBaseModule(ExtensionModule):
# It is important that this list does not change order as the order of # It is important that this list does not change order as the order of
# the returned ExternalPrograms will change as well # the returned ExternalPrograms will change as well
self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = { self.tools: T.Dict[str, T.Union[ExternalProgram, build.Executable]] = {
'moc': NonExistingExternalProgram('moc'), tool: NonExistingExternalProgram(tool) for tool in self._set_of_qt_tools
'uic': NonExistingExternalProgram('uic'),
'rcc': NonExistingExternalProgram('rcc'),
'lrelease': NonExistingExternalProgram('lrelease'),
} }
self.methods.update({ self.methods.update({
'has_tools': self.has_tools, 'has_tools': self.has_tools,
@ -258,6 +268,10 @@ class QtBaseModule(ExtensionModule):
'qt.has_tools', 'qt.has_tools',
KwargInfo('required', (bool, options.UserFeatureOption), default=False), KwargInfo('required', (bool, options.UserFeatureOption), default=False),
KwargInfo('method', str, default='auto'), KwargInfo('method', str, default='auto'),
KwargInfo('tools', ContainerTypeInfo(list, str), listify=True,
default=['moc', 'uic', 'rcc', 'lrelease'],
validator=_list_in_set_validator(_set_of_qt_tools),
since='1.6.0'),
) )
def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs') -> bool: def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs') -> bool:
method = kwargs.get('method', 'auto') method = kwargs.get('method', 'auto')
@ -269,8 +283,9 @@ class QtBaseModule(ExtensionModule):
mlog.log('qt.has_tools skipped: feature', mlog.bold(feature), 'disabled') mlog.log('qt.has_tools skipped: feature', mlog.bold(feature), 'disabled')
return False return False
self._detect_tools(state, method, required=False) self._detect_tools(state, method, required=False)
for tool in self.tools.values(): for tool in kwargs['tools']:
if not tool.found(): assert tool in self._set_of_qt_tools, f'tools must be in {self._set_of_qt_tools}'
if not self.tools[tool].found():
if required: if required:
raise MesonException('Qt tools not found') raise MesonException('Qt tools not found')
return False return False

@ -48,7 +48,13 @@ foreach qt : ['qt4', 'qt5', 'qt6']
qtdep = dependency(qt, modules : qt_modules, main : true, private_headers: true, required : required, method : get_option('method')) qtdep = dependency(qt, modules : qt_modules, main : true, private_headers: true, required : required, method : get_option('method'))
if qtdep.found() if qtdep.found()
qtmodule = import(qt) qtmodule = import(qt)
assert(qtmodule.has_tools(), 'You may be missing a devel package. (qttools5-dev-tools on Debian based systems)') if get_option('expect_lrelease')
assert(qtmodule.has_tools(), 'You may be missing a devel package. (qttools5-dev-tools on Debian based systems)')
else
assert(not qtmodule.has_tools(), 'Unexpectedly found lrelease')
assert(not qtmodule.has_tools(tools: ['lrelease']), 'Unexpectedly found lrelease')
assert(qtmodule.has_tools(tools: ['moc', 'uic', 'rcc']), 'You may be missing a devel package. (qttools5-dev-tools on Debian based systems)')
endif
# Test that fetching a variable works and yields a non-empty value # Test that fetching a variable works and yields a non-empty value
assert(qtdep.get_variable('prefix', configtool: 'QT_INSTALL_PREFIX') != '') assert(qtdep.get_variable('prefix', configtool: 'QT_INSTALL_PREFIX') != '')
@ -91,23 +97,25 @@ foreach qt : ['qt4', 'qt5', 'qt6']
# qt4-rcc and qt5-rcc take different arguments, for example qt4: ['-compress', '3']; qt5: '--compress=3' # qt4-rcc and qt5-rcc take different arguments, for example qt4: ['-compress', '3']; qt5: '--compress=3'
qtmodule.preprocess(qt + 'testrccarg', qresources : files(['stuff.qrc', 'stuff2.qrc']), rcc_extra_arguments : '--compress=3', method : get_option('method')) qtmodule.preprocess(qt + 'testrccarg', qresources : files(['stuff.qrc', 'stuff2.qrc']), rcc_extra_arguments : '--compress=3', method : get_option('method'))
translations_cpp = qtmodule.compile_translations(qresource: qt+'_lang.qrc') if get_option('expect_lrelease')
# unity builds suck and definitely cannot handle two qrc embeds in one compilation unit translations_cpp = qtmodule.compile_translations(qresource: qt+'_lang.qrc')
unityproof_translations = static_library(qt+'unityproof_translations', translations_cpp, dependencies: qtdep) # unity builds suck and definitely cannot handle two qrc embeds in one compilation unit
unityproof_translations = static_library(qt+'unityproof_translations', translations_cpp, dependencies: qtdep)
extra_cpp_args += '-DQT="@0@"'.format(qt) extra_cpp_args += '-DQT="@0@"'.format(qt)
qexe = executable(qt + 'app', qexe = executable(qt + 'app',
sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing. sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing.
prep, prep_rcc], prep, prep_rcc],
dependencies : qtdep, dependencies : qtdep,
link_with: unityproof_translations, link_with: unityproof_translations,
cpp_args: extra_cpp_args, cpp_args: extra_cpp_args,
gui_app : true) gui_app : true)
# We need a console test application because some test environments # We need a console test application because some test environments
# do not have an X server. # do not have an X server.
translations = qtmodule.compile_translations(ts_files : qt+'core_fr.ts', build_by_default : true) translations = qtmodule.compile_translations(ts_files : qt+'core_fr.ts', build_by_default : true)
endif
qtcore = dependency(qt, modules : 'Core', method : get_option('method')) qtcore = dependency(qt, modules : 'Core', method : get_option('method'))

@ -1,2 +1,3 @@
option('method', type : 'string', value : 'auto', description : 'The method to use to find Qt') option('method', type : 'string', value : 'auto', description : 'The method to use to find Qt')
option('required', type : 'string', value : 'qt5', description : 'The version of Qt which is required to be present') option('required', type : 'string', value : 'qt5', description : 'The version of Qt which is required to be present')
option('expect_lrelease', type: 'boolean', value: true)

@ -36,6 +36,7 @@ from mesonbuild.compilers.cpp import AppleClangCPPCompiler
from mesonbuild.compilers.objc import AppleClangObjCCompiler from mesonbuild.compilers.objc import AppleClangObjCCompiler
from mesonbuild.compilers.objcpp import AppleClangObjCPPCompiler from mesonbuild.compilers.objcpp import AppleClangObjCPPCompiler
from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigCLI, PkgConfigInterface from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigCLI, PkgConfigInterface
from mesonbuild.programs import NonExistingExternalProgram
import mesonbuild.modules.pkgconfig import mesonbuild.modules.pkgconfig
PKG_CONFIG = os.environ.get('PKG_CONFIG', 'pkg-config') PKG_CONFIG = os.environ.get('PKG_CONFIG', 'pkg-config')
@ -317,6 +318,19 @@ class LinuxlikeTests(BasePlatformTests):
self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false'])
self.build() self.build()
def test_qt5dependency_no_lrelease(self):
'''
Test that qt5 detection with qmake works. This can't be an ordinary
test case because it involves setting the environment.
'''
testdir = os.path.join(self.framework_test_dir, '4 qt')
def _no_lrelease(self, prog, *args, **kwargs):
if 'lrelease' in prog:
return NonExistingExternalProgram(prog)
return self._interpreter.find_program_impl(prog, *args, **kwargs)
with mock.patch.object(mesonbuild.modules.ModuleState, 'find_program', _no_lrelease):
self.init(testdir, inprocess=True, extra_args=['-Dmethod=qmake', '-Dexpect_lrelease=false'])
def test_qt5dependency_qmake_detection(self): def test_qt5dependency_qmake_detection(self):
''' '''
Test that qt5 detection with qmake works. This can't be an ordinary Test that qt5 detection with qmake works. This can't be an ordinary

Loading…
Cancel
Save