From 73eb24432b16e53bd6f9d12969ce8697ca2ba2ea Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 10:56:13 -0700 Subject: [PATCH 01/12] dependencies: split qt out of the ui module It's a big enough and complicated enough bit of code that it deserves its own module. --- mesonbuild/dependencies/__init__.py | 3 +- mesonbuild/dependencies/qt.py | 448 ++++++++++++++++++++++++++++ mesonbuild/dependencies/ui.py | 422 +------------------------- 3 files changed, 451 insertions(+), 422 deletions(-) create mode 100644 mesonbuild/dependencies/qt.py diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index 91d3d8732..416325d4c 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -31,7 +31,8 @@ from .misc import ( shaderc_factory, threads_factory, ) from .platform import AppleFrameworks -from .ui import GnuStepDependency, Qt4Dependency, Qt5Dependency, Qt6Dependency, WxDependency, gl_factory, sdl2_factory, vulkan_factory +from .qt import Qt4Dependency, Qt5Dependency, Qt6Dependency +from .ui import GnuStepDependency, WxDependency, gl_factory, sdl2_factory, vulkan_factory """Dependency representations and discovery logic. diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py new file mode 100644 index 000000000..1764f92ab --- /dev/null +++ b/mesonbuild/dependencies/qt.py @@ -0,0 +1,448 @@ +# Copyright 2013-2017 The Meson development team +# Copyright © 2021 Intel Corporation +# SPDX-license-identifier: Apache-2.0 + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Dependency finders for the Qt framework.""" + +import collections +import re +import os +import typing as T + +from . import ( + ExtraFrameworkDependency, ExternalDependency, DependencyException, DependencyMethods, + PkgConfigDependency, +) +from .. import mlog +from .. import mesonlib +from ..programs import NonExistingExternalProgram, find_external_program + +if T.TYPE_CHECKING: + from ..programs import ExternalProgram + + +def _qt_get_private_includes(mod_inc_dir, module, mod_version): + # usually Qt5 puts private headers in /QT_INSTALL_HEADERS/module/VERSION/module/private + # except for at least QtWebkit and Enginio where the module version doesn't match Qt version + # as an example with Qt 5.10.1 on linux you would get: + # /usr/include/qt5/QtCore/5.10.1/QtCore/private/ + # /usr/include/qt5/QtWidgets/5.10.1/QtWidgets/private/ + # /usr/include/qt5/QtWebKit/5.212.0/QtWebKit/private/ + + # on Qt4 when available private folder is directly in module folder + # like /usr/include/QtCore/private/ + if int(mod_version.split('.')[0]) < 5: + return tuple() + + private_dir = os.path.join(mod_inc_dir, mod_version) + # fallback, let's try to find a directory with the latest version + if not os.path.exists(private_dir): + dirs = [filename for filename in os.listdir(mod_inc_dir) + if os.path.isdir(os.path.join(mod_inc_dir, filename))] + dirs.sort(reverse=True) + + for dirname in dirs: + if len(dirname.split('.')) == 3: + private_dir = dirname + break + return (private_dir, + os.path.join(private_dir, 'Qt' + module)) + +class QtExtraFrameworkDependency(ExtraFrameworkDependency): + def __init__(self, name, env, kwargs, language: T.Optional[str] = None): + super().__init__(name, env, kwargs, language=language) + self.mod_name = name[2:] + + def get_compile_args(self, with_private_headers=False, qt_version="0"): + if self.found(): + mod_inc_dir = os.path.join(self.framework_path, 'Headers') + args = ['-I' + mod_inc_dir] + if with_private_headers: + args += ['-I' + dirname for dirname in _qt_get_private_includes(mod_inc_dir, self.mod_name, qt_version)] + return args + return [] + +class QtBaseDependency(ExternalDependency): + def __init__(self, name, env, kwargs): + super().__init__(name, env, kwargs, language='cpp') + self.qtname = name.capitalize() + self.qtver = name[-1] + if self.qtver == "4": + self.qtpkgname = 'Qt' + else: + self.qtpkgname = self.qtname + self.root = '/usr' + self.bindir = None + self.private_headers = kwargs.get('private_headers', False) + mods = mesonlib.extract_as_list(kwargs, 'modules') + self.requested_modules = mods + if not mods: + raise DependencyException('No ' + self.qtname + ' modules specified.') + self.from_text = 'pkg-config' + + self.qtmain = kwargs.get('main', False) + if not isinstance(self.qtmain, bool): + raise DependencyException('"main" argument must be a boolean') + + # Keep track of the detection methods used, for logging purposes. + methods = [] + # Prefer pkg-config, then fallback to `qmake -query` + if DependencyMethods.PKGCONFIG in self.methods: + mlog.debug('Trying to find qt with pkg-config') + self._pkgconfig_detect(mods, kwargs) + methods.append('pkgconfig') + if not self.is_found and DependencyMethods.QMAKE in self.methods: + mlog.debug('Trying to find qt with qmake') + self.from_text = self._qmake_detect(mods, kwargs) + methods.append('qmake-' + self.name) + methods.append('qmake') + if not self.is_found: + # Reset compile args and link args + self.compile_args = [] + self.link_args = [] + self.from_text = mlog.format_list(methods) + self.version = None + + def compilers_detect(self, interp_obj): + "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 + bins = ['moc', 'uic', 'rcc', 'lrelease'] + found = {b: NonExistingExternalProgram(name=f'{b}-{self.name}') + for b in bins} + wanted = f'== {self.version}' + + def gen_bins(): + for b in bins: + if self.bindir: + yield os.path.join(self.bindir, b), b + # prefer the -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}-{self.name}', b + yield b, b + + for b, name in gen_bins(): + if found[name].found(): + continue + + if name == 'lrelease': + arg = ['-version'] + elif mesonlib.version_compare(self.version, '>= 5'): + arg = ['--version'] + else: + arg = ['-v'] + + # Ensure that the version of qt and each tool are the same + def get_version(p): + _, out, err = mesonlib.Popen_safe(p.get_command() + arg) + if b.startswith('lrelease') or not self.version.startswith('4'): + care = out + else: + care = err + return care.split(' ')[-1].replace(')', '').strip() + + p = interp_obj.find_program_impl([b], required=False, + version_func=get_version, + wanted=wanted).held_object + if p.found(): + found[name] = p + + return tuple([found[b] for b in bins]) + + def _pkgconfig_detect(self, mods, kwargs): + # We set the value of required to False so that we can try the + # qmake-based fallback if pkg-config fails. + kwargs['required'] = False + modules = collections.OrderedDict() + for module in mods: + modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, + kwargs, language=self.language) + for m_name, m in modules.items(): + if not m.found(): + self.is_found = False + return + self.compile_args += m.get_compile_args() + if self.private_headers: + qt_inc_dir = m.get_pkgconfig_variable('includedir', dict()) + mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m_name) + if not os.path.isdir(mod_private_dir): + # At least some versions of homebrew don't seem to set this + # up correctly. /usr/local/opt/qt/include/Qt + m_name is a + # symlink to /usr/local/opt/qt/include, but the pkg-config + # file points to /usr/local/Cellar/qt/x.y.z/Headers/, and + # the Qt + m_name there is not a symlink, it's a file + mod_private_dir = qt_inc_dir + mod_private_inc = _qt_get_private_includes(mod_private_dir, m_name, m.version) + for directory in mod_private_inc: + self.compile_args.append('-I' + directory) + self.link_args += m.get_link_args() + + if 'Core' in modules: + core = modules['Core'] + else: + corekwargs = {'required': 'false', 'silent': 'true'} + core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs, + language=self.language) + modules['Core'] = core + + if self.env.machines[self.for_machine].is_windows() and self.qtmain: + # Check if we link with debug binaries + debug_lib_name = self.qtpkgname + 'Core' + self._get_modules_lib_suffix(True) + is_debug = False + for arg in core.get_link_args(): + if arg == '-l%s' % debug_lib_name or arg.endswith('%s.lib' % debug_lib_name) or arg.endswith('%s.a' % debug_lib_name): + is_debug = True + break + libdir = core.get_pkgconfig_variable('libdir', {}) + if not self._link_with_qtmain(is_debug, libdir): + self.is_found = False + return + + self.is_found = True + self.version = m.version + self.pcdep = list(modules.values()) + # Try to detect moc, uic, rcc + # Used by self.compilers_detect() + self.bindir = self.get_pkgconfig_host_bins(core) + if not self.bindir: + # If exec_prefix is not defined, the pkg-config file is broken + prefix = core.get_pkgconfig_variable('exec_prefix', {}) + if prefix: + self.bindir = os.path.join(prefix, 'bin') + + def search_qmake(self) -> T.Generator['ExternalProgram', None, None]: + for qmake in ('qmake-' + self.name, 'qmake'): + yield from find_external_program(self.env, self.for_machine, qmake, 'QMake', [qmake]) + + def _qmake_detect(self, mods, kwargs): + for qmake in self.search_qmake(): + if not qmake.found(): + continue + # Check that the qmake is for qt5 + pc, stdo = mesonlib.Popen_safe(qmake.get_command() + ['-v'])[0:2] + if pc.returncode != 0: + continue + if not 'Qt version ' + self.qtver in stdo: + mlog.log('QMake is not for ' + self.qtname) + continue + # Found qmake for Qt5! + self.qmake = qmake + break + else: + # Didn't find qmake :( + self.is_found = False + return + self.version = re.search(self.qtver + r'(\.\d+)+', stdo).group(0) + # Query library path, header path, and binary path + mlog.log("Found qmake:", mlog.bold(self.qmake.get_path()), '(%s)' % self.version) + stdo = mesonlib.Popen_safe(self.qmake.get_command() + ['-query'])[1] + qvars = {} + for line in stdo.split('\n'): + line = line.strip() + if line == '': + continue + (k, v) = tuple(line.split(':', 1)) + qvars[k] = v + # Qt on macOS uses a framework, but Qt for iOS/tvOS does not + xspec = qvars.get('QMAKE_XSPEC', '') + if self.env.machines.host.is_darwin() and not any(s in xspec for s in ['ios', 'tvos']): + mlog.debug("Building for macOS, looking for framework") + self._framework_detect(qvars, mods, kwargs) + # Sometimes Qt is built not as a framework (for instance, when using conan pkg manager) + # skip and fall back to normal procedure then + if self.is_found: + return self.qmake.name + else: + mlog.debug("Building for macOS, couldn't find framework, falling back to library search") + incdir = qvars['QT_INSTALL_HEADERS'] + self.compile_args.append('-I' + incdir) + libdir = qvars['QT_INSTALL_LIBS'] + # Used by self.compilers_detect() + self.bindir = self.get_qmake_host_bins(qvars) + self.is_found = True + + # Use the buildtype by default, but look at the b_vscrt option if the + # compiler supports it. + is_debug = self.env.coredata.get_option(mesonlib.OptionKey('buildtype')) == 'debug' + if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options: + if self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: + is_debug = True + modules_lib_suffix = self._get_modules_lib_suffix(is_debug) + + for module in mods: + mincdir = os.path.join(incdir, 'Qt' + module) + self.compile_args.append('-I' + mincdir) + + if module == 'QuickTest': + define_base = 'QMLTEST' + elif module == 'Test': + define_base = 'TESTLIB' + else: + define_base = module.upper() + self.compile_args.append('-DQT_%s_LIB' % define_base) + + if self.private_headers: + priv_inc = self.get_private_includes(mincdir, module) + for directory in priv_inc: + self.compile_args.append('-I' + directory) + libfile = self.clib_compiler.find_library(self.qtpkgname + module + modules_lib_suffix, + self.env, + libdir) + if libfile: + libfile = libfile[0] + else: + mlog.log("Could not find:", module, + self.qtpkgname + module + modules_lib_suffix, + 'in', libdir) + self.is_found = False + break + self.link_args.append(libfile) + + if self.env.machines[self.for_machine].is_windows() and self.qtmain: + if not self._link_with_qtmain(is_debug, libdir): + self.is_found = False + + return self.qmake.name + + def _get_modules_lib_suffix(self, is_debug): + suffix = '' + if self.env.machines[self.for_machine].is_windows(): + if is_debug: + suffix += 'd' + if self.qtver == '4': + suffix += '4' + if self.env.machines[self.for_machine].is_darwin(): + if is_debug: + suffix += '_debug' + if mesonlib.version_compare(self.version, '>= 5.14.0'): + if self.env.machines[self.for_machine].is_android(): + cpu_family = self.env.machines[self.for_machine].cpu_family + if cpu_family == 'x86': + suffix += '_x86' + elif cpu_family == 'x86_64': + suffix += '_x86_64' + elif cpu_family == 'arm': + suffix += '_armeabi-v7a' + elif cpu_family == 'aarch64': + suffix += '_arm64-v8a' + else: + mlog.warning('Android target arch {!r} for Qt5 is unknown, ' + 'module detection may not work'.format(cpu_family)) + return suffix + + def _link_with_qtmain(self, is_debug, libdir): + base_name = 'qtmaind' if is_debug else 'qtmain' + qtmain = self.clib_compiler.find_library(base_name, self.env, libdir) + if qtmain: + self.link_args.append(qtmain[0]) + return True + return False + + def _framework_detect(self, qvars, modules, kwargs): + libdir = qvars['QT_INSTALL_LIBS'] + + # ExtraFrameworkDependency doesn't support any methods + fw_kwargs = kwargs.copy() + fw_kwargs.pop('method', None) + fw_kwargs['paths'] = [libdir] + + for m in modules: + fname = 'Qt' + m + mlog.debug('Looking for qt framework ' + fname) + fwdep = QtExtraFrameworkDependency(fname, self.env, fw_kwargs, language=self.language) + if fwdep.found(): + self.compile_args.append('-F' + libdir) + self.compile_args += fwdep.get_compile_args(with_private_headers=self.private_headers, + qt_version=self.version) + self.link_args += fwdep.get_link_args() + else: + break + else: + self.is_found = True + # Used by self.compilers_detect() + self.bindir = self.get_qmake_host_bins(qvars) + + def get_qmake_host_bins(self, qvars): + # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling) + # but fall back to QT_INSTALL_BINS (qt4) + if 'QT_HOST_BINS' in qvars: + return qvars['QT_HOST_BINS'] + else: + return qvars['QT_INSTALL_BINS'] + + @staticmethod + def get_methods(): + return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE] + + def get_exe_args(self, compiler): + # Originally this was -fPIE but nowadays the default + # for upstream and distros seems to be -reduce-relocations + # which requires -fPIC. This may cause a performance + # penalty when using self-built Qt or on platforms + # where -fPIC is not required. If this is an issue + # for you, patches are welcome. + return compiler.get_pic_args() + + def get_private_includes(self, mod_inc_dir, module): + return tuple() + + def log_details(self): + module_str = ', '.join(self.requested_modules) + return 'modules: ' + module_str + + def log_info(self): + return f'{self.from_text}' + + def log_tried(self): + return self.from_text + + +class Qt4Dependency(QtBaseDependency): + def __init__(self, env, kwargs): + QtBaseDependency.__init__(self, 'qt4', env, kwargs) + + def get_pkgconfig_host_bins(self, core): + # Only return one bins dir, because the tools are generally all in one + # directory for Qt4, in Qt5, they must all be in one directory. Return + # the first one found among the bin variables, in case one tool is not + # configured to be built. + applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease'] + for application in applications: + try: + return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application, {})) + except mesonlib.MesonException: + pass + + +class Qt5Dependency(QtBaseDependency): + def __init__(self, env, kwargs): + QtBaseDependency.__init__(self, 'qt5', env, kwargs) + + def get_pkgconfig_host_bins(self, core): + return core.get_pkgconfig_variable('host_bins', {}) + + def get_private_includes(self, mod_inc_dir, module): + return _qt_get_private_includes(mod_inc_dir, module, self.version) + + +class Qt6Dependency(QtBaseDependency): + def __init__(self, env, kwargs): + QtBaseDependency.__init__(self, 'qt6', env, kwargs) + + def get_pkgconfig_host_bins(self, core): + return core.get_pkgconfig_variable('host_bins', {}) + + def get_private_includes(self, mod_inc_dir, module): + return _qt_get_private_includes(mod_inc_dir, module, self.version) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 40455b834..26103cee2 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -15,27 +15,22 @@ # This file contains the detection logic for external dependencies that # are UI-related. import os -import re import subprocess import typing as T -from collections import OrderedDict from .. import mlog from .. import mesonlib from ..mesonlib import ( - MesonException, Popen_safe, extract_as_list, version_compare_many + Popen_safe, extract_as_list, version_compare_many ) from ..environment import detect_cpu_family from .base import DependencyException, DependencyMethods from .base import ExternalDependency -from .base import ExtraFrameworkDependency, PkgConfigDependency from .base import ConfigToolDependency, DependencyFactory -from ..programs import find_external_program, NonExistingExternalProgram if T.TYPE_CHECKING: from ..environment import Environment - from ..programs import ExternalProgram class GLDependencySystem(ExternalDependency): @@ -140,421 +135,6 @@ class GnuStepDependency(ConfigToolDependency): return version -def _qt_get_private_includes(mod_inc_dir, module, mod_version): - # usually Qt5 puts private headers in /QT_INSTALL_HEADERS/module/VERSION/module/private - # except for at least QtWebkit and Enginio where the module version doesn't match Qt version - # as an example with Qt 5.10.1 on linux you would get: - # /usr/include/qt5/QtCore/5.10.1/QtCore/private/ - # /usr/include/qt5/QtWidgets/5.10.1/QtWidgets/private/ - # /usr/include/qt5/QtWebKit/5.212.0/QtWebKit/private/ - - # on Qt4 when available private folder is directly in module folder - # like /usr/include/QtCore/private/ - if int(mod_version.split('.')[0]) < 5: - return tuple() - - private_dir = os.path.join(mod_inc_dir, mod_version) - # fallback, let's try to find a directory with the latest version - if not os.path.exists(private_dir): - dirs = [filename for filename in os.listdir(mod_inc_dir) - if os.path.isdir(os.path.join(mod_inc_dir, filename))] - dirs.sort(reverse=True) - - for dirname in dirs: - if len(dirname.split('.')) == 3: - private_dir = dirname - break - return (private_dir, - os.path.join(private_dir, 'Qt' + module)) - -class QtExtraFrameworkDependency(ExtraFrameworkDependency): - def __init__(self, name, env, kwargs, language: T.Optional[str] = None): - super().__init__(name, env, kwargs, language=language) - self.mod_name = name[2:] - - def get_compile_args(self, with_private_headers=False, qt_version="0"): - if self.found(): - mod_inc_dir = os.path.join(self.framework_path, 'Headers') - args = ['-I' + mod_inc_dir] - if with_private_headers: - args += ['-I' + dirname for dirname in _qt_get_private_includes(mod_inc_dir, self.mod_name, qt_version)] - return args - return [] - -class QtBaseDependency(ExternalDependency): - def __init__(self, name, env, kwargs): - super().__init__(name, env, kwargs, language='cpp') - self.qtname = name.capitalize() - self.qtver = name[-1] - if self.qtver == "4": - self.qtpkgname = 'Qt' - else: - self.qtpkgname = self.qtname - self.root = '/usr' - self.bindir = None - self.private_headers = kwargs.get('private_headers', False) - mods = extract_as_list(kwargs, 'modules') - self.requested_modules = mods - if not mods: - raise DependencyException('No ' + self.qtname + ' modules specified.') - self.from_text = 'pkg-config' - - self.qtmain = kwargs.get('main', False) - if not isinstance(self.qtmain, bool): - raise DependencyException('"main" argument must be a boolean') - - # Keep track of the detection methods used, for logging purposes. - methods = [] - # Prefer pkg-config, then fallback to `qmake -query` - if DependencyMethods.PKGCONFIG in self.methods: - mlog.debug('Trying to find qt with pkg-config') - self._pkgconfig_detect(mods, kwargs) - methods.append('pkgconfig') - if not self.is_found and DependencyMethods.QMAKE in self.methods: - mlog.debug('Trying to find qt with qmake') - self.from_text = self._qmake_detect(mods, kwargs) - methods.append('qmake-' + self.name) - methods.append('qmake') - if not self.is_found: - # Reset compile args and link args - self.compile_args = [] - self.link_args = [] - self.from_text = mlog.format_list(methods) - self.version = None - - def compilers_detect(self, interp_obj): - "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 - bins = ['moc', 'uic', 'rcc', 'lrelease'] - found = {b: NonExistingExternalProgram(name=f'{b}-{self.name}') - for b in bins} - wanted = f'== {self.version}' - - def gen_bins(): - for b in bins: - if self.bindir: - yield os.path.join(self.bindir, b), b - # prefer the -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}-{self.name}', b - yield b, b - - for b, name in gen_bins(): - if found[name].found(): - continue - - if name == 'lrelease': - arg = ['-version'] - elif mesonlib.version_compare(self.version, '>= 5'): - arg = ['--version'] - else: - arg = ['-v'] - - # Ensure that the version of qt and each tool are the same - def get_version(p): - _, out, err = mesonlib.Popen_safe(p.get_command() + arg) - if b.startswith('lrelease') or not self.version.startswith('4'): - care = out - else: - care = err - return care.split(' ')[-1].replace(')', '').strip() - - p = interp_obj.find_program_impl([b], required=False, - version_func=get_version, - wanted=wanted).held_object - if p.found(): - found[name] = p - - return tuple([found[b] for b in bins]) - - def _pkgconfig_detect(self, mods, kwargs): - # We set the value of required to False so that we can try the - # qmake-based fallback if pkg-config fails. - kwargs['required'] = False - modules = OrderedDict() - for module in mods: - modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, - kwargs, language=self.language) - for m_name, m in modules.items(): - if not m.found(): - self.is_found = False - return - self.compile_args += m.get_compile_args() - if self.private_headers: - qt_inc_dir = m.get_pkgconfig_variable('includedir', dict()) - mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m_name) - if not os.path.isdir(mod_private_dir): - # At least some versions of homebrew don't seem to set this - # up correctly. /usr/local/opt/qt/include/Qt + m_name is a - # symlink to /usr/local/opt/qt/include, but the pkg-config - # file points to /usr/local/Cellar/qt/x.y.z/Headers/, and - # the Qt + m_name there is not a symlink, it's a file - mod_private_dir = qt_inc_dir - mod_private_inc = _qt_get_private_includes(mod_private_dir, m_name, m.version) - for directory in mod_private_inc: - self.compile_args.append('-I' + directory) - self.link_args += m.get_link_args() - - if 'Core' in modules: - core = modules['Core'] - else: - corekwargs = {'required': 'false', 'silent': 'true'} - core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs, - language=self.language) - modules['Core'] = core - - if self.env.machines[self.for_machine].is_windows() and self.qtmain: - # Check if we link with debug binaries - debug_lib_name = self.qtpkgname + 'Core' + self._get_modules_lib_suffix(True) - is_debug = False - for arg in core.get_link_args(): - if arg == '-l%s' % debug_lib_name or arg.endswith('%s.lib' % debug_lib_name) or arg.endswith('%s.a' % debug_lib_name): - is_debug = True - break - libdir = core.get_pkgconfig_variable('libdir', {}) - if not self._link_with_qtmain(is_debug, libdir): - self.is_found = False - return - - self.is_found = True - self.version = m.version - self.pcdep = list(modules.values()) - # Try to detect moc, uic, rcc - # Used by self.compilers_detect() - self.bindir = self.get_pkgconfig_host_bins(core) - if not self.bindir: - # If exec_prefix is not defined, the pkg-config file is broken - prefix = core.get_pkgconfig_variable('exec_prefix', {}) - if prefix: - self.bindir = os.path.join(prefix, 'bin') - - def search_qmake(self) -> T.Generator['ExternalProgram', None, None]: - for qmake in ('qmake-' + self.name, 'qmake'): - yield from find_external_program(self.env, self.for_machine, qmake, 'QMake', [qmake]) - - def _qmake_detect(self, mods, kwargs): - for qmake in self.search_qmake(): - if not qmake.found(): - continue - # Check that the qmake is for qt5 - pc, stdo = Popen_safe(qmake.get_command() + ['-v'])[0:2] - if pc.returncode != 0: - continue - if not 'Qt version ' + self.qtver in stdo: - mlog.log('QMake is not for ' + self.qtname) - continue - # Found qmake for Qt5! - self.qmake = qmake - break - else: - # Didn't find qmake :( - self.is_found = False - return - self.version = re.search(self.qtver + r'(\.\d+)+', stdo).group(0) - # Query library path, header path, and binary path - mlog.log("Found qmake:", mlog.bold(self.qmake.get_path()), '(%s)' % self.version) - stdo = Popen_safe(self.qmake.get_command() + ['-query'])[1] - qvars = {} - for line in stdo.split('\n'): - line = line.strip() - if line == '': - continue - (k, v) = tuple(line.split(':', 1)) - qvars[k] = v - # Qt on macOS uses a framework, but Qt for iOS/tvOS does not - xspec = qvars.get('QMAKE_XSPEC', '') - if self.env.machines.host.is_darwin() and not any(s in xspec for s in ['ios', 'tvos']): - mlog.debug("Building for macOS, looking for framework") - self._framework_detect(qvars, mods, kwargs) - # Sometimes Qt is built not as a framework (for instance, when using conan pkg manager) - # skip and fall back to normal procedure then - if self.is_found: - return self.qmake.name - else: - mlog.debug("Building for macOS, couldn't find framework, falling back to library search") - incdir = qvars['QT_INSTALL_HEADERS'] - self.compile_args.append('-I' + incdir) - libdir = qvars['QT_INSTALL_LIBS'] - # Used by self.compilers_detect() - self.bindir = self.get_qmake_host_bins(qvars) - self.is_found = True - - # Use the buildtype by default, but look at the b_vscrt option if the - # compiler supports it. - is_debug = self.env.coredata.get_option(mesonlib.OptionKey('buildtype')) == 'debug' - if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options: - if self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: - is_debug = True - modules_lib_suffix = self._get_modules_lib_suffix(is_debug) - - for module in mods: - mincdir = os.path.join(incdir, 'Qt' + module) - self.compile_args.append('-I' + mincdir) - - if module == 'QuickTest': - define_base = 'QMLTEST' - elif module == 'Test': - define_base = 'TESTLIB' - else: - define_base = module.upper() - self.compile_args.append('-DQT_%s_LIB' % define_base) - - if self.private_headers: - priv_inc = self.get_private_includes(mincdir, module) - for directory in priv_inc: - self.compile_args.append('-I' + directory) - libfile = self.clib_compiler.find_library(self.qtpkgname + module + modules_lib_suffix, - self.env, - libdir) - if libfile: - libfile = libfile[0] - else: - mlog.log("Could not find:", module, - self.qtpkgname + module + modules_lib_suffix, - 'in', libdir) - self.is_found = False - break - self.link_args.append(libfile) - - if self.env.machines[self.for_machine].is_windows() and self.qtmain: - if not self._link_with_qtmain(is_debug, libdir): - self.is_found = False - - return self.qmake.name - - def _get_modules_lib_suffix(self, is_debug): - suffix = '' - if self.env.machines[self.for_machine].is_windows(): - if is_debug: - suffix += 'd' - if self.qtver == '4': - suffix += '4' - if self.env.machines[self.for_machine].is_darwin(): - if is_debug: - suffix += '_debug' - if mesonlib.version_compare(self.version, '>= 5.14.0'): - if self.env.machines[self.for_machine].is_android(): - cpu_family = self.env.machines[self.for_machine].cpu_family - if cpu_family == 'x86': - suffix += '_x86' - elif cpu_family == 'x86_64': - suffix += '_x86_64' - elif cpu_family == 'arm': - suffix += '_armeabi-v7a' - elif cpu_family == 'aarch64': - suffix += '_arm64-v8a' - else: - mlog.warning('Android target arch {!r} for Qt5 is unknown, ' - 'module detection may not work'.format(cpu_family)) - return suffix - - def _link_with_qtmain(self, is_debug, libdir): - base_name = 'qtmaind' if is_debug else 'qtmain' - qtmain = self.clib_compiler.find_library(base_name, self.env, libdir) - if qtmain: - self.link_args.append(qtmain[0]) - return True - return False - - def _framework_detect(self, qvars, modules, kwargs): - libdir = qvars['QT_INSTALL_LIBS'] - - # ExtraFrameworkDependency doesn't support any methods - fw_kwargs = kwargs.copy() - fw_kwargs.pop('method', None) - fw_kwargs['paths'] = [libdir] - - for m in modules: - fname = 'Qt' + m - mlog.debug('Looking for qt framework ' + fname) - fwdep = QtExtraFrameworkDependency(fname, self.env, fw_kwargs, language=self.language) - if fwdep.found(): - self.compile_args.append('-F' + libdir) - self.compile_args += fwdep.get_compile_args(with_private_headers=self.private_headers, - qt_version=self.version) - self.link_args += fwdep.get_link_args() - else: - break - else: - self.is_found = True - # Used by self.compilers_detect() - self.bindir = self.get_qmake_host_bins(qvars) - - def get_qmake_host_bins(self, qvars): - # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling) - # but fall back to QT_INSTALL_BINS (qt4) - if 'QT_HOST_BINS' in qvars: - return qvars['QT_HOST_BINS'] - else: - return qvars['QT_INSTALL_BINS'] - - @staticmethod - def get_methods(): - return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE] - - def get_exe_args(self, compiler): - # Originally this was -fPIE but nowadays the default - # for upstream and distros seems to be -reduce-relocations - # which requires -fPIC. This may cause a performance - # penalty when using self-built Qt or on platforms - # where -fPIC is not required. If this is an issue - # for you, patches are welcome. - return compiler.get_pic_args() - - def get_private_includes(self, mod_inc_dir, module): - return tuple() - - def log_details(self): - module_str = ', '.join(self.requested_modules) - return 'modules: ' + module_str - - def log_info(self): - return f'{self.from_text}' - - def log_tried(self): - return self.from_text - - -class Qt4Dependency(QtBaseDependency): - def __init__(self, env, kwargs): - QtBaseDependency.__init__(self, 'qt4', env, kwargs) - - def get_pkgconfig_host_bins(self, core): - # Only return one bins dir, because the tools are generally all in one - # directory for Qt4, in Qt5, they must all be in one directory. Return - # the first one found among the bin variables, in case one tool is not - # configured to be built. - applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease'] - for application in applications: - try: - return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application, {})) - except MesonException: - pass - - -class Qt5Dependency(QtBaseDependency): - def __init__(self, env, kwargs): - QtBaseDependency.__init__(self, 'qt5', env, kwargs) - - def get_pkgconfig_host_bins(self, core): - return core.get_pkgconfig_variable('host_bins', {}) - - def get_private_includes(self, mod_inc_dir, module): - return _qt_get_private_includes(mod_inc_dir, module, self.version) - - -class Qt6Dependency(QtBaseDependency): - def __init__(self, env, kwargs): - QtBaseDependency.__init__(self, 'qt6', env, kwargs) - - def get_pkgconfig_host_bins(self, core): - return core.get_pkgconfig_variable('host_bins', {}) - - def get_private_includes(self, mod_inc_dir, module): - return _qt_get_private_includes(mod_inc_dir, module, self.version) - - class SDL2DependencyConfigTool(ConfigToolDependency): tools = ['sdl2-config'] From 8771045d797b442f1243916bf5e94a263a928149 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 11:25:27 -0700 Subject: [PATCH 02/12] dependencies/qt: Add type annotations And fix some style and correctness issues --- mesonbuild/dependencies/qt.py | 121 ++++++++++++++++++++-------------- run_mypy.py | 1 + 2 files changed, 72 insertions(+), 50 deletions(-) diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index 1764f92ab..15cc97fbc 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -16,6 +16,7 @@ """Dependency finders for the Qt framework.""" +import abc import collections import re import os @@ -30,10 +31,13 @@ from .. import mesonlib from ..programs import NonExistingExternalProgram, find_external_program if T.TYPE_CHECKING: + from ..compilers import Compiler + from ..environment import Environment + from ..interpreter import Interpreter from ..programs import ExternalProgram -def _qt_get_private_includes(mod_inc_dir, module, mod_version): +def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> T.List[str]: # usually Qt5 puts private headers in /QT_INSTALL_HEADERS/module/VERSION/module/private # except for at least QtWebkit and Enginio where the module version doesn't match Qt version # as an example with Qt 5.10.1 on linux you would get: @@ -44,28 +48,26 @@ def _qt_get_private_includes(mod_inc_dir, module, mod_version): # on Qt4 when available private folder is directly in module folder # like /usr/include/QtCore/private/ if int(mod_version.split('.')[0]) < 5: - return tuple() + return [] private_dir = os.path.join(mod_inc_dir, mod_version) # fallback, let's try to find a directory with the latest version if not os.path.exists(private_dir): dirs = [filename for filename in os.listdir(mod_inc_dir) if os.path.isdir(os.path.join(mod_inc_dir, filename))] - dirs.sort(reverse=True) - for dirname in dirs: + for dirname in sorted(dirs, reverse=True): if len(dirname.split('.')) == 3: private_dir = dirname break - return (private_dir, - os.path.join(private_dir, 'Qt' + module)) + return [private_dir, os.path.join(private_dir, 'Qt' + module)] class QtExtraFrameworkDependency(ExtraFrameworkDependency): - def __init__(self, name, env, kwargs, language: T.Optional[str] = None): + def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): super().__init__(name, env, kwargs, language=language) self.mod_name = name[2:] - def get_compile_args(self, with_private_headers=False, qt_version="0"): + def get_compile_args(self, with_private_headers: bool = False, qt_version: str = "0") -> T.List[str]: if self.found(): mod_inc_dir = os.path.join(self.framework_path, 'Headers') args = ['-I' + mod_inc_dir] @@ -74,8 +76,12 @@ class QtExtraFrameworkDependency(ExtraFrameworkDependency): return args return [] -class QtBaseDependency(ExternalDependency): - def __init__(self, name, env, kwargs): + +class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): + + version: T.Optional[str] + + def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs, language='cpp') self.qtname = name.capitalize() self.qtver = name[-1] @@ -84,20 +90,20 @@ class QtBaseDependency(ExternalDependency): else: self.qtpkgname = self.qtname self.root = '/usr' - self.bindir = None - self.private_headers = kwargs.get('private_headers', False) - mods = mesonlib.extract_as_list(kwargs, 'modules') + self.bindir: T.Optional[str] = None + self.private_headers = T.cast(bool, kwargs.get('private_headers', False)) + mods = mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules')) self.requested_modules = mods if not mods: raise DependencyException('No ' + self.qtname + ' modules specified.') self.from_text = 'pkg-config' - self.qtmain = kwargs.get('main', False) + self.qtmain = T.cast(bool, kwargs.get('main', False)) if not isinstance(self.qtmain, bool): raise DependencyException('"main" argument must be a boolean') # Keep track of the detection methods used, for logging purposes. - methods = [] + methods: T.List[str] = [] # Prefer pkg-config, then fallback to `qmake -query` if DependencyMethods.PKGCONFIG in self.methods: mlog.debug('Trying to find qt with pkg-config') @@ -115,8 +121,12 @@ class QtBaseDependency(ExternalDependency): self.from_text = mlog.format_list(methods) self.version = None - def compilers_detect(self, interp_obj): - "Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH" + @abc.abstractmethod + def get_pkgconfig_host_bins(self, core: PkgConfigDependency) -> T.Optional[str]: + pass + + def compilers_detect(self, interp_obj: 'Interpreter') -> T.Tuple['ExternalProgram', 'ExternalProgram', 'ExternalProgram', 'ExternalProgram']: + """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 bins = ['moc', 'uic', 'rcc', 'lrelease'] @@ -124,7 +134,7 @@ class QtBaseDependency(ExternalDependency): for b in bins} wanted = f'== {self.version}' - def gen_bins(): + def gen_bins() -> T.Generator[T.Tuple[str, str], None, None]: for b in bins: if self.bindir: yield os.path.join(self.bindir, b), b @@ -145,7 +155,7 @@ class QtBaseDependency(ExternalDependency): arg = ['-v'] # Ensure that the version of qt and each tool are the same - def get_version(p): + def get_version(p: 'ExternalProgram') -> str: _, out, err = mesonlib.Popen_safe(p.get_command() + arg) if b.startswith('lrelease') or not self.version.startswith('4'): care = out @@ -159,13 +169,18 @@ class QtBaseDependency(ExternalDependency): if p.found(): found[name] = p - return tuple([found[b] for b in bins]) + # Since we're converting from a list (no size constraints) to a tuple + # (size constrained), we have to cast. We can impsect the code to see + # that obviously this is correct since `len(bins) == 4`, but static + # type checkers can't + return T.cast(T.Tuple['ExternalProgram', 'ExternalProgram', 'ExternalProgram', 'ExternalProgram'], + tuple([found[b] for b in bins])) - def _pkgconfig_detect(self, mods, kwargs): + def _pkgconfig_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> None: # We set the value of required to False so that we can try the # qmake-based fallback if pkg-config fails. kwargs['required'] = False - modules = collections.OrderedDict() + modules: T.MutableMapping[str, PkgConfigDependency] = collections.OrderedDict() for module in mods: modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, kwargs, language=self.language) @@ -226,7 +241,7 @@ class QtBaseDependency(ExternalDependency): for qmake in ('qmake-' + self.name, 'qmake'): yield from find_external_program(self.env, self.for_machine, qmake, 'QMake', [qmake]) - def _qmake_detect(self, mods, kwargs): + def _qmake_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> T.Optional[str]: for qmake in self.search_qmake(): if not qmake.found(): continue @@ -243,7 +258,7 @@ class QtBaseDependency(ExternalDependency): else: # Didn't find qmake :( self.is_found = False - return + return None self.version = re.search(self.qtver + r'(\.\d+)+', stdo).group(0) # Query library path, header path, and binary path mlog.log("Found qmake:", mlog.bold(self.qmake.get_path()), '(%s)' % self.version) @@ -297,11 +312,11 @@ class QtBaseDependency(ExternalDependency): priv_inc = self.get_private_includes(mincdir, module) for directory in priv_inc: self.compile_args.append('-I' + directory) - libfile = self.clib_compiler.find_library(self.qtpkgname + module + modules_lib_suffix, - self.env, - libdir) - if libfile: - libfile = libfile[0] + libfiles = self.clib_compiler.find_library( + self.qtpkgname + module + modules_lib_suffix, self.env, + mesonlib.listify(libdir)) # TODO: shouldn't be necissary + if libfiles: + libfile = libfiles[0] else: mlog.log("Could not find:", module, self.qtpkgname + module + modules_lib_suffix, @@ -316,7 +331,7 @@ class QtBaseDependency(ExternalDependency): return self.qmake.name - def _get_modules_lib_suffix(self, is_debug): + def _get_modules_lib_suffix(self, is_debug: bool) -> str: suffix = '' if self.env.machines[self.for_machine].is_windows(): if is_debug: @@ -342,7 +357,8 @@ class QtBaseDependency(ExternalDependency): 'module detection may not work'.format(cpu_family)) return suffix - def _link_with_qtmain(self, is_debug, libdir): + def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool: + libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary base_name = 'qtmaind' if is_debug else 'qtmain' qtmain = self.clib_compiler.find_library(base_name, self.env, libdir) if qtmain: @@ -350,7 +366,7 @@ class QtBaseDependency(ExternalDependency): return True return False - def _framework_detect(self, qvars, modules, kwargs): + def _framework_detect(self, qvars: T.Dict[str, str], modules: T.List[str], kwargs: T.Dict[str, T.Any]) -> None: libdir = qvars['QT_INSTALL_LIBS'] # ExtraFrameworkDependency doesn't support any methods @@ -374,19 +390,20 @@ class QtBaseDependency(ExternalDependency): # Used by self.compilers_detect() self.bindir = self.get_qmake_host_bins(qvars) - def get_qmake_host_bins(self, qvars): + @staticmethod + def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str: # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling) # but fall back to QT_INSTALL_BINS (qt4) if 'QT_HOST_BINS' in qvars: return qvars['QT_HOST_BINS'] - else: - return qvars['QT_INSTALL_BINS'] + return qvars['QT_INSTALL_BINS'] @staticmethod - def get_methods(): + def get_methods() -> T.List[DependencyMethods]: return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE] - def get_exe_args(self, compiler): + @staticmethod + def get_exe_args(compiler: 'Compiler') -> T.List[str]: # Originally this was -fPIE but nowadays the default # for upstream and distros seems to be -reduce-relocations # which requires -fPIC. This may cause a performance @@ -395,25 +412,26 @@ class QtBaseDependency(ExternalDependency): # for you, patches are welcome. return compiler.get_pic_args() - def get_private_includes(self, mod_inc_dir, module): - return tuple() + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: + return [] - def log_details(self): + def log_details(self) -> str: module_str = ', '.join(self.requested_modules) return 'modules: ' + module_str - def log_info(self): + def log_info(self) -> str: return f'{self.from_text}' - def log_tried(self): + def log_tried(self) -> str: return self.from_text class Qt4Dependency(QtBaseDependency): - def __init__(self, env, kwargs): + def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): QtBaseDependency.__init__(self, 'qt4', env, kwargs) - def get_pkgconfig_host_bins(self, core): + @staticmethod + def get_pkgconfig_host_bins(core: PkgConfigDependency) -> T.Optional[str]: # Only return one bins dir, because the tools are generally all in one # directory for Qt4, in Qt5, they must all be in one directory. Return # the first one found among the bin variables, in case one tool is not @@ -424,25 +442,28 @@ class Qt4Dependency(QtBaseDependency): return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application, {})) except mesonlib.MesonException: pass + return None class Qt5Dependency(QtBaseDependency): - def __init__(self, env, kwargs): + def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): QtBaseDependency.__init__(self, 'qt5', env, kwargs) - def get_pkgconfig_host_bins(self, core): + @staticmethod + def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str: return core.get_pkgconfig_variable('host_bins', {}) - def get_private_includes(self, mod_inc_dir, module): + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: return _qt_get_private_includes(mod_inc_dir, module, self.version) class Qt6Dependency(QtBaseDependency): - def __init__(self, env, kwargs): + def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): QtBaseDependency.__init__(self, 'qt6', env, kwargs) - def get_pkgconfig_host_bins(self, core): + @staticmethod + def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str: return core.get_pkgconfig_variable('host_bins', {}) - def get_private_includes(self, mod_inc_dir, module): + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: return _qt_get_private_includes(mod_inc_dir, module, self.version) diff --git a/run_mypy.py b/run_mypy.py index 75c825e57..65de41484 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -21,6 +21,7 @@ modules = [ 'mesonbuild/dependencies/boost.py', 'mesonbuild/dependencies/hdf5.py', 'mesonbuild/dependencies/mpi.py', + 'mesonbuild/dependencies/qt.py', 'mesonbuild/envconfig.py', 'mesonbuild/interpreterbase.py', 'mesonbuild/linkers.py', From d2046540890007cd921c60484a274d6aef17bdda Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 11:28:01 -0700 Subject: [PATCH 03/12] dependencies/base: Use Compiler instead of CompilerType Since Compiler is actually useful now as a base class, unlike when this code was written. --- mesonbuild/dependencies/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 27c33b625..0045805dd 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -39,7 +39,7 @@ from ..mesondata import mesondata from ..programs import ExternalProgram, find_external_program if T.TYPE_CHECKING: - from ..compilers.compilers import CompilerType # noqa: F401 + from ..compilers.compilers import Compiler DependencyType = T.TypeVar('DependencyType', bound='Dependency') # These must be defined in this file to avoid cyclical references. @@ -2313,7 +2313,7 @@ def factory_methods(methods: T.Set[DependencyMethods]) -> T.Callable[['FactoryTy def detect_compiler(name: str, env: Environment, for_machine: MachineChoice, - language: T.Optional[str]) -> T.Optional['CompilerType']: + language: T.Optional[str]) -> T.Optional['Compiler']: """Given a language and environment find the compiler used.""" compilers = env.coredata.compilers[for_machine] From e37b020cef4498891c1def66f303ea155acb3055 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 11:29:34 -0700 Subject: [PATCH 04/12] dependencies/qt: split get_qmake_host_bins into a free function It's static anyway, and never overwritten. --- mesonbuild/dependencies/qt.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index 15cc97fbc..33b4c7c36 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -62,6 +62,15 @@ def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> break return [private_dir, os.path.join(private_dir, 'Qt' + module)] + +def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str: + # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling) + # but fall back to QT_INSTALL_BINS (qt4) + if 'QT_HOST_BINS' in qvars: + return qvars['QT_HOST_BINS'] + return qvars['QT_INSTALL_BINS'] + + class QtExtraFrameworkDependency(ExtraFrameworkDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): super().__init__(name, env, kwargs, language=language) @@ -285,7 +294,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): self.compile_args.append('-I' + incdir) libdir = qvars['QT_INSTALL_LIBS'] # Used by self.compilers_detect() - self.bindir = self.get_qmake_host_bins(qvars) + self.bindir = get_qmake_host_bins(qvars) self.is_found = True # Use the buildtype by default, but look at the b_vscrt option if the @@ -388,15 +397,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): else: self.is_found = True # Used by self.compilers_detect() - self.bindir = self.get_qmake_host_bins(qvars) - - @staticmethod - def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str: - # Prefer QT_HOST_BINS (qt5, correct for cross and native compiling) - # but fall back to QT_INSTALL_BINS (qt4) - if 'QT_HOST_BINS' in qvars: - return qvars['QT_HOST_BINS'] - return qvars['QT_INSTALL_BINS'] + self.bindir = get_qmake_host_bins(qvars) @staticmethod def get_methods() -> T.List[DependencyMethods]: From 3e86aecab8ee9667001756df6185e3c479442394 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 11:36:48 -0700 Subject: [PATCH 05/12] dependencies/base: Use FeatureDeprecated instead of mlog.warning This code is really old, and it might be fine to delete this altogether, but for now lets do this. --- mesonbuild/dependencies/base.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 0045805dd..6ec74ce1c 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -37,6 +37,7 @@ from ..mesonlib import Popen_safe, version_compare_many, version_compare, listif from ..mesonlib import Version, LibType, OptionKey from ..mesondata import mesondata from ..programs import ExternalProgram, find_external_program +from ..interpreterbase import FeatureDeprecated if T.TYPE_CHECKING: from ..compilers.compilers import Compiler @@ -2267,10 +2268,7 @@ def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs) -> T.List # generic CONFIG_TOOL value. if method in [DependencyMethods.SDLCONFIG, DependencyMethods.CUPSCONFIG, DependencyMethods.PCAPCONFIG, DependencyMethods.LIBWMFCONFIG]: - mlog.warning(textwrap.dedent("""\ - Configuration method {} has been deprecated in favor of - 'config-tool'. This will be removed in a future version of - meson.""".format(method))) + FeatureDeprecated.single_use(f'Configuration method {method.value}', '0.44', 'Use "config-tool" instead.') method = DependencyMethods.CONFIG_TOOL # Set the detection method. If the method is set to auto, use any available method. From c1daa4e892287e3cfff8817a927e573e97830a13 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 11:53:35 -0700 Subject: [PATCH 06/12] dependencies/qt: Split _get_modules_lib_suffix out of the class As we break these classes up we're going to need this. --- mesonbuild/dependencies/qt.py | 61 ++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index 33b4c7c36..54a86a312 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -24,14 +24,16 @@ import typing as T from . import ( ExtraFrameworkDependency, ExternalDependency, DependencyException, DependencyMethods, - PkgConfigDependency, + PkgConfigDependency ) +from .base import ConfigToolDependency from .. import mlog from .. import mesonlib from ..programs import NonExistingExternalProgram, find_external_program if T.TYPE_CHECKING: from ..compilers import Compiler + from ..envconfig import MachineInfo from ..environment import Environment from ..interpreter import Interpreter from ..programs import ExternalProgram @@ -71,6 +73,33 @@ def get_qmake_host_bins(qvars: T.Dict[str, str]) -> str: return qvars['QT_INSTALL_BINS'] +def _get_modules_lib_suffix(version: str, info: 'MachineInfo', is_debug: bool) -> str: + """Get the module suffix based on platform and debug type.""" + suffix = '' + if info.is_windows(): + if is_debug: + suffix += 'd' + if version.startswith('4'): + suffix += '4' + if info.is_darwin(): + if is_debug: + suffix += '_debug' + if mesonlib.version_compare(version, '>= 5.14.0'): + if info.is_android(): + if info.cpu_family == 'x86': + suffix += '_x86' + elif info.cpu_family == 'x86_64': + suffix += '_x86_64' + elif info.cpu_family == 'arm': + suffix += '_armeabi-v7a' + elif info.cpu_family == 'aarch64': + suffix += '_arm64-v8a' + else: + mlog.warning(f'Android target arch "{info.cpu_family}"" for Qt5 is unknown, ' + 'module detection may not work') + return suffix + + class QtExtraFrameworkDependency(ExtraFrameworkDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None): super().__init__(name, env, kwargs, language=language) @@ -223,7 +252,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): if self.env.machines[self.for_machine].is_windows() and self.qtmain: # Check if we link with debug binaries - debug_lib_name = self.qtpkgname + 'Core' + self._get_modules_lib_suffix(True) + debug_lib_name = self.qtpkgname + 'Core' + _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], True) is_debug = False for arg in core.get_link_args(): if arg == '-l%s' % debug_lib_name or arg.endswith('%s.lib' % debug_lib_name) or arg.endswith('%s.a' % debug_lib_name): @@ -303,7 +332,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options: if self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: is_debug = True - modules_lib_suffix = self._get_modules_lib_suffix(is_debug) + modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug) for module in mods: mincdir = os.path.join(incdir, 'Qt' + module) @@ -340,32 +369,6 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): return self.qmake.name - def _get_modules_lib_suffix(self, is_debug: bool) -> str: - suffix = '' - if self.env.machines[self.for_machine].is_windows(): - if is_debug: - suffix += 'd' - if self.qtver == '4': - suffix += '4' - if self.env.machines[self.for_machine].is_darwin(): - if is_debug: - suffix += '_debug' - if mesonlib.version_compare(self.version, '>= 5.14.0'): - if self.env.machines[self.for_machine].is_android(): - cpu_family = self.env.machines[self.for_machine].cpu_family - if cpu_family == 'x86': - suffix += '_x86' - elif cpu_family == 'x86_64': - suffix += '_x86_64' - elif cpu_family == 'arm': - suffix += '_armeabi-v7a' - elif cpu_family == 'aarch64': - suffix += '_arm64-v8a' - else: - mlog.warning('Android target arch {!r} for Qt5 is unknown, ' - 'module detection may not work'.format(cpu_family)) - return suffix - def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool: libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary base_name = 'qtmaind' if is_debug else 'qtmain' From d7ac2f10655433989e5e17867b6c8ef428fd39e8 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 12:07:04 -0700 Subject: [PATCH 07/12] qt: move compilers_detect to the qt module It's a method on the QtDependeny that exists purely for the consumption of the qt module (in the form, return some stuff the module makes into an instance variable). So put it where it actually belongs, and pass the qt dependency into it. --- mesonbuild/dependencies/qt.py | 54 +------------------------------ mesonbuild/modules/qt.py | 61 +++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index 54a86a312..97a8a7f2f 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -29,13 +29,12 @@ from . import ( from .base import ConfigToolDependency from .. import mlog from .. import mesonlib -from ..programs import NonExistingExternalProgram, find_external_program +from ..programs import find_external_program if T.TYPE_CHECKING: from ..compilers import Compiler from ..envconfig import MachineInfo from ..environment import Environment - from ..interpreter import Interpreter from ..programs import ExternalProgram @@ -163,57 +162,6 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): def get_pkgconfig_host_bins(self, core: PkgConfigDependency) -> T.Optional[str]: pass - def compilers_detect(self, interp_obj: 'Interpreter') -> T.Tuple['ExternalProgram', 'ExternalProgram', 'ExternalProgram', 'ExternalProgram']: - """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 - bins = ['moc', 'uic', 'rcc', 'lrelease'] - found = {b: NonExistingExternalProgram(name=f'{b}-{self.name}') - for b in bins} - wanted = f'== {self.version}' - - def gen_bins() -> T.Generator[T.Tuple[str, str], None, None]: - for b in bins: - if self.bindir: - yield os.path.join(self.bindir, b), b - # prefer the -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}-{self.name}', b - yield b, b - - for b, name in gen_bins(): - if found[name].found(): - continue - - if name == 'lrelease': - arg = ['-version'] - elif mesonlib.version_compare(self.version, '>= 5'): - arg = ['--version'] - else: - arg = ['-v'] - - # Ensure that the version of qt and each tool are the same - def get_version(p: 'ExternalProgram') -> str: - _, out, err = mesonlib.Popen_safe(p.get_command() + arg) - if b.startswith('lrelease') or not self.version.startswith('4'): - care = out - else: - care = err - return care.split(' ')[-1].replace(')', '').strip() - - p = interp_obj.find_program_impl([b], required=False, - version_func=get_version, - wanted=wanted).held_object - if p.found(): - found[name] = p - - # Since we're converting from a list (no size constraints) to a tuple - # (size constrained), we have to cast. We can impsect the code to see - # that obviously this is correct since `len(bins) == 4`, but static - # type checkers can't - return T.cast(T.Tuple['ExternalProgram', 'ExternalProgram', 'ExternalProgram', 'ExternalProgram'], - tuple([found[b] for b in bins])) - def _pkgconfig_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> None: # We set the value of required to False so that we can try the # qmake-based fallback if pkg-config fails. diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index de3a5b1a8..addc57172 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -14,8 +14,11 @@ import os import shutil +import typing as T + from .. import mlog from .. import build +from .. import mesonlib from ..mesonlib import MesonException, extract_as_list, File, unholder, version_compare from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency, Qt6Dependency import xml.etree.ElementTree as ET @@ -24,6 +27,11 @@ from ..interpreterbase import noPosargs, permittedKwargs, FeatureNew, FeatureNew from ..interpreter import extract_required_kwarg from ..programs import NonExistingExternalProgram +if T.TYPE_CHECKING: + from .. import Interpreter + from ..dependencies.qt import QtBaseDependency + from ..programs import ExternalProgram + _QT_DEPS_LUT = { 4: Qt4Dependency, 5: Qt5Dependency, @@ -35,10 +43,59 @@ class QtBaseModule(ExtensionModule): tools_detected = False rcc_supports_depfiles = False - def __init__(self, interpreter, qt_version=5): + def __init__(self, interpreter: 'Interpreter', qt_version=5): ExtensionModule.__init__(self, interpreter) self.snippets.add('has_tools') self.qt_version = qt_version + self.moc: 'ExternalProgram' = NonExistingExternalProgram('moc') + self.uic: 'ExternalProgram' = NonExistingExternalProgram('uic') + self.rcc: 'ExternalProgram' = NonExistingExternalProgram('rcc') + self.lrelease: 'ExternalProgram' = NonExistingExternalProgram('lrelease') + + def compilers_detect(self, qt_dep: 'QtBaseDependency') -> 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 + bins = ['moc', 'uic', 'rcc', 'lrelease'] + found = {b: NonExistingExternalProgram(name=f'{b}-{qt_dep.name}') + for b in bins} + wanted = f'== {qt_dep.version}' + + def gen_bins() -> T.Generator[T.Tuple[str, str], None, None]: + for b in bins: + if qt_dep.bindir: + yield os.path.join(qt_dep.bindir, b), b + # prefer the -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.name}', b + yield b, b + + for b, name in gen_bins(): + if found[name].found(): + continue + + if name == 'lrelease': + arg = ['-version'] + elif mesonlib.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: 'ExternalProgram') -> str: + _, out, err = mesonlib.Popen_safe(p.get_command() + arg) + if b.startswith('lrelease') or not qt_dep.version.startswith('4'): + care = out + else: + care = err + return care.split(' ')[-1].replace(')', '').strip() + + p = self.interpreter.find_program_impl( + [b], required=False, + version_func=get_version, + wanted=wanted).held_object + if p.found(): + setattr(self, name, p) def _detect_tools(self, env, method, required=True): if self.tools_detected: @@ -49,7 +106,7 @@ class QtBaseModule(ExtensionModule): qt = _QT_DEPS_LUT[self.qt_version](env, kwargs) if qt.found(): # Get all tools and then make sure that they are the right version - self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect(self.interpreter) + self.compilers_detect(qt) if version_compare(qt.version, '>=5.14.0'): self.rcc_supports_depfiles = True else: From 54c55f77a976deb0047a78468250b75525df6514 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 23 Mar 2021 15:26:55 -0700 Subject: [PATCH 08/12] run_project_tests.py: Allow qt tests to skip on !macOS We don't always have qmake installed (and it's good to test failure paths too!) so we can't expect this to succeed in all cases. With the following commit we'll use a test.json to test both pkg-config and qmake, so we need to be able to skip. --- run_project_tests.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/run_project_tests.py b/run_project_tests.py index ab97ca93b..ef800a0ce 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -865,11 +865,6 @@ def skippable(suite, test): if test.endswith('34 gir static lib'): return True - # No frameworks test should be skipped on linux CI, as we expect all - # prerequisites to be installed - if mesonlib.is_linux(): - return False - # Boost test should only be skipped for windows CI build matrix entries # which don't define BOOST_ROOT if test.endswith('1 boost'): @@ -877,14 +872,22 @@ def skippable(suite, test): return 'BOOST_ROOT' not in os.environ return False - # Qt is provided on macOS by Homebrew - if test.endswith('4 qt') and mesonlib.is_osx(): - return False + # Not all OSes have all of the methods for qt (qmake and pkg-config), don't + # fail if that happens. + # + # On macOS we should have all of the requirements at all times. + if test.endswith('4 qt'): + return not mesonlib.is_osx() # Bindgen isn't available in all distros if test.endswith('12 bindgen'): return False + # No frameworks test should be skipped on linux CI, as we expect all + # prerequisites to be installed + if mesonlib.is_linux(): + return False + # Other framework tests are allowed to be skipped on other platforms return True From c211fea51389728783cf59ae41156a5e0de9c41a Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 15:25:29 -0700 Subject: [PATCH 09/12] Refactor Qt Dependency into proper split classes with factories Currently the Qt Dependencies still use the old "combined" method for dependencies with multiple ways to be found. This is problematic as it means that `get_variable()` and friends don't work, as the dependency can't implement any of those methods. The correct solution is to make use of multiple Dependency instances, and a factory to tie them together. This does that. To handle QMake, I've leveraged the existing config-tool mechanism, which allows us to save a good deal of code, and use well tested code instead of rolling more of our own code. The one thing this doesn't do, but we probably should, is expose the macOS ExtraFrameworks directly, instead of forcing them to be found through QMake. That is a problem for another series, and someone who cares more about macOS than I do. --- docs/markdown/snippets/qt_factory.md | 6 + mesonbuild/dependencies/__init__.py | 8 +- mesonbuild/dependencies/base.py | 11 + mesonbuild/dependencies/qt.py | 297 ++++++++++++++------------- mesonbuild/modules/qt.py | 22 +- run_unittests.py | 8 +- test cases/frameworks/4 qt/test.json | 11 + 7 files changed, 202 insertions(+), 161 deletions(-) create mode 100644 docs/markdown/snippets/qt_factory.md create mode 100644 test cases/frameworks/4 qt/test.json diff --git a/docs/markdown/snippets/qt_factory.md b/docs/markdown/snippets/qt_factory.md new file mode 100644 index 000000000..11cc783dd --- /dev/null +++ b/docs/markdown/snippets/qt_factory.md @@ -0,0 +1,6 @@ +## Qt Dependency uses a Factory + +This separates the Pkg-config and QMake based discovery methods into two +distinct classes in the backend. This allows using +`dependency.get_variable()` and `dependency.get_pkg_config_variable()`, as +well as being a cleaner implementation. diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index 416325d4c..afd4adc2a 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -31,7 +31,7 @@ from .misc import ( shaderc_factory, threads_factory, ) from .platform import AppleFrameworks -from .qt import Qt4Dependency, Qt5Dependency, Qt6Dependency +from .qt import qt4_factory, qt5_factory, qt6_factory from .ui import GnuStepDependency, WxDependency, gl_factory, sdl2_factory, vulkan_factory """Dependency representations and discovery logic. @@ -226,9 +226,9 @@ packages.update({ # From ui: 'gl': gl_factory, 'gnustep': GnuStepDependency, - 'qt4': Qt4Dependency, - 'qt5': Qt5Dependency, - 'qt6': Qt6Dependency, + 'qt4': qt4_factory, + 'qt5': qt5_factory, + 'qt6': qt6_factory, 'sdl2': sdl2_factory, 'wxwidgets': WxDependency, 'vulkan': vulkan_factory, diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 6ec74ce1c..89447803a 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -18,6 +18,7 @@ import copy import functools import os import re +import itertools import json import shlex import shutil @@ -136,11 +137,21 @@ class Dependency: return converted return self.compile_args + def get_all_compile_args(self) -> T.List[str]: + """Get the compile arguments from this dependency and it's sub dependencies.""" + return list(itertools.chain(self.get_compile_args(), + *[d.get_all_compile_args() for d in self.ext_deps])) + def get_link_args(self, raw: bool = False) -> T.List[str]: if raw and self.raw_link_args is not None: return self.raw_link_args return self.link_args + def get_all_link_args(self) -> T.List[str]: + """Get the link arguments from this dependency and it's sub dependencies.""" + return list(itertools.chain(self.get_link_args(), + *[d.get_all_link_args() for d in self.ext_deps])) + def found(self) -> bool: return self.is_found diff --git a/mesonbuild/dependencies/qt.py b/mesonbuild/dependencies/qt.py index 97a8a7f2f..1059871af 100644 --- a/mesonbuild/dependencies/qt.py +++ b/mesonbuild/dependencies/qt.py @@ -17,25 +17,22 @@ """Dependency finders for the Qt framework.""" import abc -import collections import re import os import typing as T from . import ( - ExtraFrameworkDependency, ExternalDependency, DependencyException, DependencyMethods, - PkgConfigDependency + ExtraFrameworkDependency, DependencyException, DependencyMethods, + PkgConfigDependency, ) -from .base import ConfigToolDependency +from .base import ConfigToolDependency, DependencyFactory from .. import mlog from .. import mesonlib -from ..programs import find_external_program if T.TYPE_CHECKING: from ..compilers import Compiler from ..envconfig import MachineInfo from ..environment import Environment - from ..programs import ExternalProgram def _qt_get_private_includes(mod_inc_dir: str, module: str, mod_version: str) -> T.List[str]: @@ -114,70 +111,78 @@ class QtExtraFrameworkDependency(ExtraFrameworkDependency): return [] -class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): +class _QtBase: - version: T.Optional[str] + """Mixin class for shared componenets between PkgConfig and Qmake.""" - def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): - super().__init__(name, env, kwargs, language='cpp') + link_args: T.List[str] + clib_compiler: 'Compiler' + env: 'Environment' + + def __init__(self, name: str, kwargs: T.Dict[str, T.Any]): self.qtname = name.capitalize() self.qtver = name[-1] if self.qtver == "4": self.qtpkgname = 'Qt' else: self.qtpkgname = self.qtname - self.root = '/usr' - self.bindir: T.Optional[str] = None + self.private_headers = T.cast(bool, kwargs.get('private_headers', False)) - mods = mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules')) - self.requested_modules = mods - if not mods: + + self.requested_modules = mesonlib.stringlistify(mesonlib.extract_as_list(kwargs, 'modules')) + if not self.requested_modules: raise DependencyException('No ' + self.qtname + ' modules specified.') - self.from_text = 'pkg-config' self.qtmain = T.cast(bool, kwargs.get('main', False)) if not isinstance(self.qtmain, bool): raise DependencyException('"main" argument must be a boolean') - # Keep track of the detection methods used, for logging purposes. - methods: T.List[str] = [] - # Prefer pkg-config, then fallback to `qmake -query` - if DependencyMethods.PKGCONFIG in self.methods: - mlog.debug('Trying to find qt with pkg-config') - self._pkgconfig_detect(mods, kwargs) - methods.append('pkgconfig') - if not self.is_found and DependencyMethods.QMAKE in self.methods: - mlog.debug('Trying to find qt with qmake') - self.from_text = self._qmake_detect(mods, kwargs) - methods.append('qmake-' + self.name) - methods.append('qmake') - if not self.is_found: - # Reset compile args and link args + def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool: + libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary + base_name = 'qtmaind' if is_debug else 'qtmain' + qtmain = self.clib_compiler.find_library(base_name, self.env, libdir) + if qtmain: + self.link_args.append(qtmain[0]) + return True + return False + + def get_exe_args(self, compiler: 'Compiler') -> T.List[str]: + # Originally this was -fPIE but nowadays the default + # for upstream and distros seems to be -reduce-relocations + # which requires -fPIC. This may cause a performance + # penalty when using self-built Qt or on platforms + # where -fPIC is not required. If this is an issue + # for you, patches are welcome. + return compiler.get_pic_args() + + def log_details(self) -> str: + return f'modules: {", ".join(sorted(self.requested_modules))}' + + +class QtPkgConfigDependency(_QtBase, PkgConfigDependency, metaclass=abc.ABCMeta): + + """Specialization of the PkgConfigDependency for Qt.""" + + def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + _QtBase.__init__(self, name, kwargs) + + # Always use QtCore as the "main" dependency, since it has the extra + # pkg-config variables that a user would expect to get. If "Core" is + # not a requested module, delete the compile and link arguments to + # avoid linking with something they didn't ask for + PkgConfigDependency.__init__(self, self.qtpkgname + 'Core', env, kwargs) + if 'Core' not in self.requested_modules: self.compile_args = [] self.link_args = [] - self.from_text = mlog.format_list(methods) - self.version = None - - @abc.abstractmethod - def get_pkgconfig_host_bins(self, core: PkgConfigDependency) -> T.Optional[str]: - pass - def _pkgconfig_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> None: - # We set the value of required to False so that we can try the - # qmake-based fallback if pkg-config fails. - kwargs['required'] = False - modules: T.MutableMapping[str, PkgConfigDependency] = collections.OrderedDict() - for module in mods: - modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, - kwargs, language=self.language) - for m_name, m in modules.items(): - if not m.found(): + for m in self.requested_modules: + mod = PkgConfigDependency(self.qtpkgname + m, self.env, kwargs, language=self.language) + if not mod.found(): self.is_found = False return - self.compile_args += m.get_compile_args() if self.private_headers: - qt_inc_dir = m.get_pkgconfig_variable('includedir', dict()) - mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m_name) + qt_inc_dir = mod.get_pkgconfig_variable('includedir', {}) + mod_private_dir = os.path.join(qt_inc_dir, 'Qt' + m) if not os.path.isdir(mod_private_dir): # At least some versions of homebrew don't seem to set this # up correctly. /usr/local/opt/qt/include/Qt + m_name is a @@ -185,94 +190,93 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): # file points to /usr/local/Cellar/qt/x.y.z/Headers/, and # the Qt + m_name there is not a symlink, it's a file mod_private_dir = qt_inc_dir - mod_private_inc = _qt_get_private_includes(mod_private_dir, m_name, m.version) + mod_private_inc = _qt_get_private_includes(mod_private_dir, m, mod.version) for directory in mod_private_inc: - self.compile_args.append('-I' + directory) - self.link_args += m.get_link_args() - - if 'Core' in modules: - core = modules['Core'] - else: - corekwargs = {'required': 'false', 'silent': 'true'} - core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs, - language=self.language) - modules['Core'] = core + mod.compile_args.append('-I' + directory) + self._add_sub_dependency([lambda: mod]) if self.env.machines[self.for_machine].is_windows() and self.qtmain: # Check if we link with debug binaries debug_lib_name = self.qtpkgname + 'Core' + _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], True) is_debug = False - for arg in core.get_link_args(): - if arg == '-l%s' % debug_lib_name or arg.endswith('%s.lib' % debug_lib_name) or arg.endswith('%s.a' % debug_lib_name): + for arg in self.get_link_args(): + if arg == f'-l{debug_lib_name}' or arg.endswith(f'{debug_lib_name}.lib') or arg.endswith(f'{debug_lib_name}.a'): is_debug = True break - libdir = core.get_pkgconfig_variable('libdir', {}) + libdir = self.get_pkgconfig_variable('libdir', {}) if not self._link_with_qtmain(is_debug, libdir): self.is_found = False return - self.is_found = True - self.version = m.version - self.pcdep = list(modules.values()) - # Try to detect moc, uic, rcc - # Used by self.compilers_detect() - self.bindir = self.get_pkgconfig_host_bins(core) + self.bindir = self.get_pkgconfig_host_bins(self) if not self.bindir: # If exec_prefix is not defined, the pkg-config file is broken - prefix = core.get_pkgconfig_variable('exec_prefix', {}) + prefix = self.get_pkgconfig_variable('exec_prefix', {}) if prefix: self.bindir = os.path.join(prefix, 'bin') - def search_qmake(self) -> T.Generator['ExternalProgram', None, None]: - for qmake in ('qmake-' + self.name, 'qmake'): - yield from find_external_program(self.env, self.for_machine, qmake, 'QMake', [qmake]) + @staticmethod + @abc.abstractmethod + def get_pkgconfig_host_bins(core: PkgConfigDependency) -> T.Optional[str]: + pass + + @abc.abstractmethod + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: + pass + + def log_info(self) -> str: + return 'pkg-config' + + +class QmakeQtDependency(_QtBase, ConfigToolDependency, metaclass=abc.ABCMeta): + + """Find Qt using Qmake as a config-tool.""" + + tool_name = 'qmake' + version_arg = '-v' + + def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): + _QtBase.__init__(self, name, kwargs) + self.tools = [f'qmake-{self.qtname}', 'qmake'] + + # Add additional constraits that the Qt version is met, but preserve + # any version requrements the user has set as well. For exmaple, if Qt5 + # is requested, add "">= 5, < 6", but if the user has ">= 5.6", don't + # lose that. + kwargs = kwargs.copy() + _vers = mesonlib.listify(kwargs.get('version', [])) + _vers.extend([f'>= {self.qtver}', f'< {int(self.qtver) + 1}']) + kwargs['version'] = _vers + + ConfigToolDependency.__init__(self, name, env, kwargs) + if not self.found(): + return - def _qmake_detect(self, mods: T.List[str], kwargs: T.Dict[str, T.Any]) -> T.Optional[str]: - for qmake in self.search_qmake(): - if not qmake.found(): - continue - # Check that the qmake is for qt5 - pc, stdo = mesonlib.Popen_safe(qmake.get_command() + ['-v'])[0:2] - if pc.returncode != 0: - continue - if not 'Qt version ' + self.qtver in stdo: - mlog.log('QMake is not for ' + self.qtname) - continue - # Found qmake for Qt5! - self.qmake = qmake - break - else: - # Didn't find qmake :( - self.is_found = False - return None - self.version = re.search(self.qtver + r'(\.\d+)+', stdo).group(0) # Query library path, header path, and binary path - mlog.log("Found qmake:", mlog.bold(self.qmake.get_path()), '(%s)' % self.version) - stdo = mesonlib.Popen_safe(self.qmake.get_command() + ['-query'])[1] - qvars = {} - for line in stdo.split('\n'): + stdo = self.get_config_value(['-query'], 'args') + qvars: T.Dict[str, str] = {} + for line in stdo: line = line.strip() if line == '': continue - (k, v) = tuple(line.split(':', 1)) + k, v = line.split(':', 1) qvars[k] = v # Qt on macOS uses a framework, but Qt for iOS/tvOS does not xspec = qvars.get('QMAKE_XSPEC', '') if self.env.machines.host.is_darwin() and not any(s in xspec for s in ['ios', 'tvos']): mlog.debug("Building for macOS, looking for framework") - self._framework_detect(qvars, mods, kwargs) + self._framework_detect(qvars, self.requested_modules, kwargs) # Sometimes Qt is built not as a framework (for instance, when using conan pkg manager) # skip and fall back to normal procedure then if self.is_found: - return self.qmake.name + return else: mlog.debug("Building for macOS, couldn't find framework, falling back to library search") incdir = qvars['QT_INSTALL_HEADERS'] self.compile_args.append('-I' + incdir) libdir = qvars['QT_INSTALL_LIBS'] - # Used by self.compilers_detect() + # Used by qt.compilers_detect() self.bindir = get_qmake_host_bins(qvars) - self.is_found = True # Use the buildtype by default, but look at the b_vscrt option if the # compiler supports it. @@ -282,7 +286,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): is_debug = True modules_lib_suffix = _get_modules_lib_suffix(self.version, self.env.machines[self.for_machine], is_debug) - for module in mods: + for module in self.requested_modules: mincdir = os.path.join(incdir, 'Qt' + module) self.compile_args.append('-I' + mincdir) @@ -292,7 +296,7 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): define_base = 'TESTLIB' else: define_base = module.upper() - self.compile_args.append('-DQT_%s_LIB' % define_base) + self.compile_args.append(f'-DQT_{define_base}_LIB') if self.private_headers: priv_inc = self.get_private_includes(mincdir, module) @@ -315,16 +319,15 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): if not self._link_with_qtmain(is_debug, libdir): self.is_found = False - return self.qmake.name + def _sanitize_version(self, version: str) -> str: + m = re.search(rf'({self.qtver}(\.\d+)+)', version) + if m: + return m.group(0).rstrip('.') + return version - def _link_with_qtmain(self, is_debug: bool, libdir: T.Union[str, T.List[str]]) -> bool: - libdir = mesonlib.listify(libdir) # TODO: shouldn't be necessary - base_name = 'qtmaind' if is_debug else 'qtmain' - qtmain = self.clib_compiler.find_library(base_name, self.env, libdir) - if qtmain: - self.link_args.append(qtmain[0]) - return True - return False + @abc.abstractmethod + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: + pass def _framework_detect(self, qvars: T.Dict[str, str], modules: T.List[str], kwargs: T.Dict[str, T.Any]) -> None: libdir = qvars['QT_INSTALL_LIBS'] @@ -344,43 +347,36 @@ class QtBaseDependency(ExternalDependency, metaclass=abc.ABCMeta): qt_version=self.version) self.link_args += fwdep.get_link_args() else: + self.is_found = False break else: self.is_found = True # Used by self.compilers_detect() self.bindir = get_qmake_host_bins(qvars) - @staticmethod - def get_methods() -> T.List[DependencyMethods]: - return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE] + def log_info(self) -> str: + return 'qmake' - @staticmethod - def get_exe_args(compiler: 'Compiler') -> T.List[str]: - # Originally this was -fPIE but nowadays the default - # for upstream and distros seems to be -reduce-relocations - # which requires -fPIC. This may cause a performance - # penalty when using self-built Qt or on platforms - # where -fPIC is not required. If this is an issue - # for you, patches are welcome. - return compiler.get_pic_args() + +class Qt4ConfigToolDependency(QmakeQtDependency): def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: return [] - def log_details(self) -> str: - module_str = ', '.join(self.requested_modules) - return 'modules: ' + module_str - def log_info(self) -> str: - return f'{self.from_text}' +class Qt5ConfigToolDependency(QmakeQtDependency): + + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: + return _qt_get_private_includes(mod_inc_dir, module, self.version) + + +class Qt6ConfigToolDependency(QmakeQtDependency): - def log_tried(self) -> str: - return self.from_text + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: + return _qt_get_private_includes(mod_inc_dir, module, self.version) -class Qt4Dependency(QtBaseDependency): - def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): - QtBaseDependency.__init__(self, 'qt4', env, kwargs) +class Qt4PkgConfigDependency(QtPkgConfigDependency): @staticmethod def get_pkgconfig_host_bins(core: PkgConfigDependency) -> T.Optional[str]: @@ -396,10 +392,11 @@ class Qt4Dependency(QtBaseDependency): pass return None + def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: + return [] + -class Qt5Dependency(QtBaseDependency): - def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): - QtBaseDependency.__init__(self, 'qt5', env, kwargs) +class Qt5PkgConfigDependency(QtPkgConfigDependency): @staticmethod def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str: @@ -409,9 +406,7 @@ class Qt5Dependency(QtBaseDependency): return _qt_get_private_includes(mod_inc_dir, module, self.version) -class Qt6Dependency(QtBaseDependency): - def __init__(self, env: 'Environment', kwargs: T.Dict[str, T.Any]): - QtBaseDependency.__init__(self, 'qt6', env, kwargs) +class Qt6PkgConfigDependency(QtPkgConfigDependency): @staticmethod def get_pkgconfig_host_bins(core: PkgConfigDependency) -> str: @@ -419,3 +414,25 @@ class Qt6Dependency(QtBaseDependency): def get_private_includes(self, mod_inc_dir: str, module: str) -> T.List[str]: return _qt_get_private_includes(mod_inc_dir, module, self.version) + + +qt4_factory = DependencyFactory( + 'qt4', + [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], + pkgconfig_class=Qt4PkgConfigDependency, + configtool_class=Qt4ConfigToolDependency, +) + +qt5_factory = DependencyFactory( + 'qt5', + [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], + pkgconfig_class=Qt5PkgConfigDependency, + configtool_class=Qt5ConfigToolDependency, +) + +qt6_factory = DependencyFactory( + 'qt6', + [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL], + pkgconfig_class=Qt6PkgConfigDependency, + configtool_class=Qt6ConfigToolDependency, +) diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index addc57172..645696d2e 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from mesonbuild.dependencies.base import find_external_dependency import os import shutil import typing as T @@ -20,7 +21,7 @@ from .. import mlog from .. import build from .. import mesonlib from ..mesonlib import MesonException, extract_as_list, File, unholder, version_compare -from ..dependencies import Dependency, Qt4Dependency, Qt5Dependency, Qt6Dependency +from ..dependencies import Dependency import xml.etree.ElementTree as ET from . import ModuleReturnValue, get_include_args, ExtensionModule from ..interpreterbase import noPosargs, permittedKwargs, FeatureNew, FeatureNewKwargs @@ -28,16 +29,11 @@ from ..interpreter import extract_required_kwarg from ..programs import NonExistingExternalProgram if T.TYPE_CHECKING: - from .. import Interpreter + from ..interpreter import Interpreter from ..dependencies.qt import QtBaseDependency + from ..environment import Environment from ..programs import ExternalProgram -_QT_DEPS_LUT = { - 4: Qt4Dependency, - 5: Qt5Dependency, - 6: Qt6Dependency, -} - class QtBaseModule(ExtensionModule): tools_detected = False @@ -57,7 +53,7 @@ class QtBaseModule(ExtensionModule): # It is important that this list does not change order as the order of # the returned ExternalPrograms will change as well bins = ['moc', 'uic', 'rcc', 'lrelease'] - found = {b: NonExistingExternalProgram(name=f'{b}-{qt_dep.name}') + found = {b: NonExistingExternalProgram(name=f'{b}-qt{qt_dep.qtver}') for b in bins} wanted = f'== {qt_dep.version}' @@ -67,7 +63,7 @@ class QtBaseModule(ExtensionModule): yield os.path.join(qt_dep.bindir, b), b # prefer the -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.name}', b + yield f'{b}-qt{qt_dep.qtver}', b yield b, b for b, name in gen_bins(): @@ -97,13 +93,13 @@ class QtBaseModule(ExtensionModule): if p.found(): setattr(self, name, p) - def _detect_tools(self, env, method, required=True): + def _detect_tools(self, env: 'Environment', method, required=True): 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} - qt = _QT_DEPS_LUT[self.qt_version](env, kwargs) + qt = find_external_dependency(f'qt{self.qt_version}', env, kwargs) if qt.found(): # Get all tools and then make sure that they are the right version self.compilers_detect(qt) @@ -247,7 +243,7 @@ class QtBaseModule(ExtensionModule): compile_args = [] for dep in unholder(dependencies): if isinstance(dep, Dependency): - for arg in dep.get_compile_args(): + for arg in dep.get_all_compile_args(): if arg.startswith('-I') or arg.startswith('-D'): compile_args.append(arg) else: diff --git a/run_unittests.py b/run_unittests.py index c22f884b0..8ef907ed3 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -6604,10 +6604,10 @@ class LinuxlikeTests(BasePlatformTests): mesonlog = self.get_meson_log() if qt4 == 0: self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt4 \(modules: Core\) found: YES 4.* \(pkg-config\)\n') + r'Run-time dependency qt4 \(modules: Core\) found: YES 4.* \(pkg-config\)') if qt5 == 0: self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt5 \(modules: Core\) found: YES 5.* \(pkg-config\)\n') + r'Run-time dependency qt5 \(modules: Core\) found: YES 5.* \(pkg-config\)') @skip_if_not_base_option('b_sanitize') def test_generate_gir_with_address_sanitizer(self): @@ -6638,7 +6638,7 @@ class LinuxlikeTests(BasePlatformTests): # Confirm that the dependency was found with qmake mesonlog = self.get_meson_log() self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt5 \(modules: Core\) found: YES .* \((qmake|qmake-qt5)\)\n') + r'Run-time dependency qt5 \(modules: Core\) found: YES .* \(qmake\)\n') def test_qt6dependency_qmake_detection(self): ''' @@ -6658,7 +6658,7 @@ class LinuxlikeTests(BasePlatformTests): # Confirm that the dependency was found with qmake mesonlog = self.get_meson_log() self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt6 \(modules: Core\) found: YES .* \((qmake|qmake-qt6)\)\n') + r'Run-time dependency qt6 \(modules: Core\) found: YES .* \(qmake\)\n') def glob_sofiles_without_privdir(self, g): files = glob(g) diff --git a/test cases/frameworks/4 qt/test.json b/test cases/frameworks/4 qt/test.json new file mode 100644 index 000000000..4c7006047 --- /dev/null +++ b/test cases/frameworks/4 qt/test.json @@ -0,0 +1,11 @@ +{ + "matrix": { + "options": { + "method": [ + { "val": "config-tool" }, + { "val": "qmake" }, + { "val": "pkg-config" } + ] + } + } +} From c86c18066e8d163eabe18f5d6d024c7ac97df988 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Mon, 22 Mar 2021 11:56:49 -0700 Subject: [PATCH 10/12] deprecated QMAKE dependency type Instead of using qmake, use config-tool. This is no different than when we deprecated the other per-dependency config-tool types (sdl2-config, llvm-config, etc) for just config-tool --- mesonbuild/dependencies/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 89447803a..14380d416 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -57,7 +57,6 @@ class DependencyMethods(Enum): # Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best. AUTO = 'auto' PKGCONFIG = 'pkg-config' - QMAKE = 'qmake' CMAKE = 'cmake' # Just specify the standard link arguments, assuming the operating system provides the library. SYSTEM = 'system' @@ -72,6 +71,7 @@ class DependencyMethods(Enum): CUPSCONFIG = 'cups-config' PCAPCONFIG = 'pcap-config' LIBWMFCONFIG = 'libwmf-config' + QMAKE = 'qmake' # Misc DUB = 'dub' @@ -2281,6 +2281,9 @@ def process_method_kw(possible: T.Iterable[DependencyMethods], kwargs) -> T.List DependencyMethods.PCAPCONFIG, DependencyMethods.LIBWMFCONFIG]: FeatureDeprecated.single_use(f'Configuration method {method.value}', '0.44', 'Use "config-tool" instead.') method = DependencyMethods.CONFIG_TOOL + if method is DependencyMethods.QMAKE: + FeatureDeprecated.single_use(f'Configuration method "qmake"', '0.58', 'Use "config-tool" instead.') + method = DependencyMethods.CONFIG_TOOL # Set the detection method. If the method is set to auto, use any available method. # If method is set to a specific string, allow only that detection method. From adbfa12409dbbfc44fe48d12abd2d92bec9249ad Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 23 Mar 2021 15:08:32 -0700 Subject: [PATCH 11/12] run_single_test: Handle MESON_SKIP_TEST correctly --- run_single_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/run_single_test.py b/run_single_test.py index 9b3ed1854..c6018d5a3 100755 --- a/run_single_test.py +++ b/run_single_test.py @@ -16,7 +16,7 @@ import typing as T from mesonbuild import environment from mesonbuild import mlog from mesonbuild import mesonlib -from run_project_tests import TestDef, load_test_json, run_test, BuildStep +from run_project_tests import TestDef, load_test_json, run_test, BuildStep, skippable from run_tests import get_backend_commands, guess_backend, get_fake_options if T.TYPE_CHECKING: @@ -62,7 +62,7 @@ def main() -> None: results = [run_test(t, t.args, comp, backend, backend_args, commands, False, True) for t in tests] failed = False for test, result in zip(tests, results): - if result is None: + if (result is None) or (('MESON_SKIP_TEST' in result.stdo) and (skippable(str(args.case.parent), test.path.as_posix()))): msg = mlog.yellow('SKIP:') elif result.msg: msg = mlog.red('FAIL:') @@ -70,7 +70,7 @@ def main() -> None: else: msg = mlog.green('PASS:') mlog.log(msg, test.display_name()) - if result.msg: + if result.msg and 'MESON_SKIP_TEST' not in result.stdo: mlog.log('reason:', result.msg) if result.step is BuildStep.configure: # For configure failures, instead of printing stdout, From 1767c57492529fc7c3823871f861db35b89f5c1f Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 23 Mar 2021 15:19:17 -0700 Subject: [PATCH 12/12] run_project_tests: add annotations --- run_project_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_project_tests.py b/run_project_tests.py index ef800a0ce..6cc89f656 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -817,7 +817,7 @@ def have_java(): return True return False -def skippable(suite, test): +def skippable(suite: str, test: str) -> bool: # Everything is optional when not running on CI if not under_ci: return True