Skip to content

Commit

Permalink
qt module: allow has_tools to specify which tools to check
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
chubinou and nirbheek committed Sep 24, 2024
1 parent 18427ad commit f7d2ccf
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 21 deletions.
3 changes: 3 additions & 0 deletions docs/markdown/Qt6-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ This method takes the following keyword arguments:
`true` or an enabled [`feature`](Build-options.md#features) and some tools are
missing Meson will abort.
- `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

Expand Down
12 changes: 12 additions & 0 deletions docs/markdown/snippets/qt_has_tools_ignore.md
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 6 deletions mesonbuild/modules/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from ..interpreter import kwargs
from ..mesonlib import FileOrString
from ..programs import ExternalProgram
from typing_extensions import Literal

QtDependencyType = T.Union[QtPkgConfigDependency, QmakeQtDependency]

Expand Down Expand Up @@ -80,6 +81,7 @@ class PreprocessKwArgs(TypedDict):
class HasToolKwArgs(kwargs.ExtractRequired):

method: str
tools: T.List[Literal['moc', 'uic', 'rcc', 'lrelease']]

class CompileTranslationsKwArgs(TypedDict):

Expand All @@ -91,21 +93,29 @@ class CompileTranslationsKwArgs(TypedDict):
rcc_extra_arguments: T.List[str]
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):
_tools_detected = False
_rcc_supports_depfiles = False
_moc_supports_depfiles = False
_set_of_qt_tools = {'moc', 'uic', 'rcc', 'lrelease'}

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'),
tool: NonExistingExternalProgram(tool) for tool in self._set_of_qt_tools
}
self.methods.update({
'has_tools': self.has_tools,
Expand Down Expand Up @@ -258,6 +268,10 @@ def _parse_qrc_deps(self, state: 'ModuleState',
'qt.has_tools',
KwargInfo('required', (bool, options.UserFeatureOption), default=False),
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:
method = kwargs.get('method', 'auto')
Expand All @@ -269,8 +283,9 @@ def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs'
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():
for tool in kwargs['tools']:
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:
raise MesonException('Qt tools not found')
return False
Expand Down
38 changes: 23 additions & 15 deletions test cases/frameworks/4 qt/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
if qtdep.found()
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
assert(qtdep.get_variable('prefix', configtool: 'QT_INSTALL_PREFIX') != '')
Expand Down Expand Up @@ -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'
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')
# 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)
if get_option('expect_lrelease')
translations_cpp = qtmodule.compile_translations(qresource: qt+'_lang.qrc')
# 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)
qexe = executable(qt + 'app',
sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing.
prep, prep_rcc],
dependencies : qtdep,
link_with: unityproof_translations,
cpp_args: extra_cpp_args,
gui_app : true)
extra_cpp_args += '-DQT="@0@"'.format(qt)
qexe = executable(qt + 'app',
sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing.
prep, prep_rcc],
dependencies : qtdep,
link_with: unityproof_translations,
cpp_args: extra_cpp_args,
gui_app : true)

# We need a console test application because some test environments
# do not have an X server.
# We need a console test application because some test environments
# 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'))

Expand Down
1 change: 1 addition & 0 deletions test cases/frameworks/4 qt/meson_options.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
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('expect_lrelease', type: 'boolean', value: true)
14 changes: 14 additions & 0 deletions unittests/linuxliketests.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import mesonbuild.environment
import mesonbuild.coredata
import mesonbuild.modules.gnome
import mesonbuild.programs
from mesonbuild.mesonlib import (
MachineChoice, is_windows, is_osx, is_cygwin, is_openbsd, is_haiku,
is_sunos, windows_proof_rmtree, version_compare, is_linux,
Expand Down Expand Up @@ -317,6 +318,19 @@ def test_generate_gir_with_address_sanitizer(self):
self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false'])
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 programs.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):
'''
Test that qt5 detection with qmake works. This can't be an ordinary
Expand Down

0 comments on commit f7d2ccf

Please sign in to comment.