From 9084ab1b64f3160d6b29d64f10befd069c23a721 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 19 Apr 2022 11:22:16 +0200 Subject: [PATCH 1/7] Remove some unused scripts These are no longer used, and instead solved via CMake or part of build(_plugin).py Change-Id: I86a829713fe32b9ff1386fc0bdfd23a88e3e00c4 Reviewed-by: Christian Stenger --- scripts/createDevPackage.py | 132 -------------------------------- scripts/createDistPackage.py | 62 --------------- scripts/createSourcePackages.py | 113 --------------------------- scripts/packagePlugins.py | 59 -------------- 4 files changed, 366 deletions(-) delete mode 100755 scripts/createDevPackage.py delete mode 100755 scripts/createDistPackage.py delete mode 100755 scripts/createSourcePackages.py delete mode 100755 scripts/packagePlugins.py diff --git a/scripts/createDevPackage.py b/scripts/createDevPackage.py deleted file mode 100755 index 663810cfd31..00000000000 --- a/scripts/createDevPackage.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python - -############################################################################ -# -# Copyright (C) 2016 The Qt Company Ltd. -# Contact: https://www.qt.io/licensing/ -# -# This file is part of Qt Creator. -# -# Commercial License Usage -# Licensees holding valid commercial Qt licenses may use this file in -# accordance with the commercial license agreement provided with the -# Software or, alternatively, in accordance with the terms contained in -# a written agreement between you and The Qt Company. For licensing terms -# and conditions see https://www.qt.io/terms-conditions. For further -# information use the contact form at https://www.qt.io/contact-us. -# -# GNU General Public License Usage -# Alternatively, this file may be used under the terms of the GNU -# General Public License version 3 as published by the Free Software -# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -# included in the packaging of this file. Please review the following -# information to ensure the GNU General Public License requirements will -# be met: https://www.gnu.org/licenses/gpl-3.0.html. -# -############################################################################ - - -import argparse -import os -import re -import subprocess -import sys - -import common - -# ========= COMMAND LINE ARGUMENTS ======== - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Create Qt Creator development package.") - parser.add_argument('--source', '-s', help='path to the Qt Creator sources', required=True, - metavar='') - parser.add_argument('--build', '-b', help='path to the Qt Creator build', required=True, - metavar='') - parser.add_argument('--verbose', '-v', help='verbose output', action='store_true', default=False) - parser.add_argument('--7z', help='path to 7z binary', - default='7z.exe' if common.is_windows_platform() else '7z', - metavar='<7z_binary>', dest='sevenzip') - parser.add_argument('--7z_out', '-o', help='output 7z file to create', metavar='', - dest='sevenzip_target') - parser.add_argument('target_directory') - return parser.parse_args() - -# ========= PATTERNS TO INCLUDE =========== - -# slash at the end matches directories, no slash at the end matches files - -source_include_patterns = [ - # directories - r"^(?!(share|tests)/.*$)(.*/)?$", # look into all directories except under share/ and tests/ - r"^share/(qtcreator/(qml/(qmlpuppet/(.*/)?)?)?)?$", # for shared headers for qt quick designer plugins - r"^share/qtcreator/qml/qmlpuppet/commands/.*\.(h|pri|cpp|c|txt|md)$", #used by extra plugins - r"^share/qtcreator/qml/qmlpuppet/container/.*\.(h|pri|cpp|c|txt|md)$", #used by extra plugins - r"^src/plugins/help/qlitehtml/.*\.(h|pri|cpp|c|txt|md)$", # litehtml is used by extra plugins - # files - r"^HACKING$", - r"^LICENSE.*$", - r"^README.md$", - r"^scripts/.*$", # include everything under scripts/ - r"^doc/.*$", # include everything under doc/ - r"^.*\.pri$", # .pri files in all directories that are looked into - r"^.*\.h$", # .h files in all directories that are looked into - r"^.*\.hpp$" # .hpp files in all directories that are looked into -] - -build_include_patterns = [ - # directories - r"^src/$", - r"^src/app/$", - # files - r"^src/app/app_version.h$" -] -if common.is_windows_platform(): - build_include_patterns.extend([ - r"^lib/(.*/)?$", # consider all directories under lib/ - r"^lib/.*\.lib$" - ]) - -# ========================================= - -def copy_regexp(regexp, source_directory, target_directory, verbose): - def ignore(directory, filenames): - relative_dir = os.path.relpath(directory, source_directory) - file_target_dir = os.path.join(target_directory, relative_dir) - ignored = [] - for filename in filenames: - relative_file_path = os.path.normpath(os.path.join(relative_dir, filename)) - relative_file_path = relative_file_path.replace(os.sep, '/') - if os.path.isdir(os.path.join(directory, filename)): - relative_file_path += '/' # let directories end with slash - if not regexp.match(relative_file_path): - ignored.append(filename) - elif verbose and os.path.isfile(os.path.join(directory, filename)): - print(os.path.normpath(os.path.join(file_target_dir, filename))) - return ignored - - common.copytree(source_directory, target_directory, symlinks=True, ignore=ignore) - -def sanity_check_arguments(arguments): - if os.path.exists(arguments.target_directory) and (os.path.isfile(arguments.target_directory) - or len(os.listdir(arguments.target_directory)) > 0): - print('error: target directory "{0}" exists and is not empty'.format(arguments.target_directory)) - return False - return True - -def main(): - arguments = parse_arguments() - if not sanity_check_arguments(arguments): - sys.exit(1) - - source_include_regexp = re.compile('(' + ')|('.join(source_include_patterns) + ')') - build_include_regexp = re.compile('(' + ')|('.join(build_include_patterns) + ')') - - copy_regexp(source_include_regexp, arguments.source, arguments.target_directory, arguments.verbose) - copy_regexp(build_include_regexp, arguments.build, arguments.target_directory, arguments.verbose) - - if arguments.sevenzip_target: - subprocess.check_call([arguments.sevenzip, 'a', '-mx9', '-mmt2', arguments.sevenzip_target, - os.path.join(arguments.target_directory, '*')]) - -if __name__ == "__main__": - main() diff --git a/scripts/createDistPackage.py b/scripts/createDistPackage.py deleted file mode 100755 index ad23a617e38..00000000000 --- a/scripts/createDistPackage.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python -############################################################################ -# -# Copyright (C) 2018 The Qt Company Ltd. -# Contact: https://www.qt.io/licensing/ -# -# This file is part of Qt Creator. -# -# Commercial License Usage -# Licensees holding valid commercial Qt licenses may use this file in -# accordance with the commercial license agreement provided with the -# Software or, alternatively, in accordance with the terms contained in -# a written agreement between you and The Qt Company. For licensing terms -# and conditions see https://www.qt.io/terms-conditions. For further -# information use the contact form at https://www.qt.io/contact-us. -# -# GNU General Public License Usage -# Alternatively, this file may be used under the terms of the GNU -# General Public License version 3 as published by the Free Software -# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -# included in the packaging of this file. Please review the following -# information to ensure the GNU General Public License requirements will -# be met: https://www.gnu.org/licenses/gpl-3.0.html. -# -############################################################################ - -import argparse -import os -import shutil -import subprocess -import tempfile - -import common - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Create Qt Creator package, filtering out debug information files.") - parser.add_argument('--7z', help='path to 7z binary', - default='7z.exe' if common.is_windows_platform() else '7z', - metavar='<7z_binary>', dest='sevenzip') - parser.add_argument('--debug', help='package only the files with debug information', - dest='debug', action='store_true', default=False) - parser.add_argument('--exclude-toplevel', help='do not include the toplevel source directory itself in the resulting archive, only its contents', - dest='exclude_toplevel', action='store_true', default=False) - parser.add_argument('target_archive', help='output 7z file to create') - parser.add_argument('source_directory', help='source directory with the Qt Creator installation') - return parser.parse_args() - -def main(): - arguments = parse_arguments() - tempdir_base = tempfile.mkdtemp() - tempdir = os.path.join(tempdir_base, os.path.basename(arguments.source_directory)) - try: - common.copytree(arguments.source_directory, tempdir, symlinks=True, - ignore=(common.is_not_debug if arguments.debug else common.is_debug)) - # package - zip_source = os.path.join(tempdir, '*') if arguments.exclude_toplevel else tempdir - subprocess.check_call([arguments.sevenzip, 'a', '-mmt2', - arguments.target_archive, zip_source]) - finally: - shutil.rmtree(tempdir_base) -if __name__ == "__main__": - main() diff --git a/scripts/createSourcePackages.py b/scripts/createSourcePackages.py deleted file mode 100755 index 1e1bdead926..00000000000 --- a/scripts/createSourcePackages.py +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env python -############################################################################ -# -# Copyright (C) 2016 The Qt Company Ltd. -# Contact: https://www.qt.io/licensing/ -# -# This file is part of Qt Creator. -# -# Commercial License Usage -# Licensees holding valid commercial Qt licenses may use this file in -# accordance with the commercial license agreement provided with the -# Software or, alternatively, in accordance with the terms contained in -# a written agreement between you and The Qt Company. For licensing terms -# and conditions see https://www.qt.io/terms-conditions. For further -# information use the contact form at https://www.qt.io/contact-us. -# -# GNU General Public License Usage -# Alternatively, this file may be used under the terms of the GNU -# General Public License version 3 as published by the Free Software -# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -# included in the packaging of this file. Please review the following -# information to ensure the GNU General Public License requirements will -# be met: https://www.gnu.org/licenses/gpl-3.0.html. -# -############################################################################ - -import argparse -import os -import shutil -import subprocess -import tempfile - -def archive(repository, target_file, prefix='', crlf=False): - crlf_args = (['-c', 'core.autocrlf=true', '-c', 'core.eol=crlf'] if crlf - else []) - subprocess.check_call(['git'] + crlf_args + - ['archive', '--format=tar', '--prefix=' + prefix, - '-o', target_file, 'HEAD'], - cwd=repository) - -def extract_combined(archive_list, target_dir): - if not os.path.exists(target_dir): - os.makedirs(target_dir) - for a in archive_list: - subprocess.check_call(['tar', 'xf', a], cwd=target_dir) - -def createTarGz(source_dir, target_file): - (cwd, path) = os.path.split(source_dir) - subprocess.check_call(['tar', 'czf', target_file, path], cwd=cwd) - -def createTarXz(source_dir, target_file): - (cwd, path) = os.path.split(source_dir) - subprocess.check_call(['tar', 'cJf', target_file, path], cwd=cwd) - -def createZip(source_dir, target_file): - (cwd, path) = os.path.split(source_dir) - subprocess.check_call(['zip', '-9qr', target_file, path], cwd=cwd) - -def package_repos(repos, combined_prefix, target_file_base): - workdir = tempfile.mkdtemp(suffix="-createQtcSource") - def crlf_postfix(crlf): - return '_win' if crlf else '' - def tar_name(name, crlf): - sanitized_name = name.replace('/', '_').replace('\\', '_') - return os.path.join(workdir, sanitized_name + crlf_postfix(crlf) + '.tar') - def archive_path(crlf=False): - return os.path.join(workdir, 'src' + crlf_postfix(crlf), combined_prefix) - print('Working in "' + workdir + '"') - print('Pre-packaging archives...') - for (name, repo, prefix) in repos: - print(' ' + name + '...') - for crlf in [False, True]: - archive(repo, tar_name(name, crlf), prefix, crlf=crlf) - print('Preparing for packaging...') - for crlf in [False, True]: - archive_list = [tar_name(name, crlf) for (name, _, _) in repos] - extract_combined(archive_list, archive_path(crlf)) - print('Creating .tar.gz...') - createTarGz(archive_path(crlf=False), target_file_base + '.tar.gz') - print('Creating .tar.xz...') - createTarXz(archive_path(crlf=False), target_file_base + '.tar.xz') - print('Creating .zip with CRLF...') - createZip(archive_path(crlf=True), target_file_base + '.zip') - print('Removing temporary directory...') - shutil.rmtree(workdir) - -def parse_arguments(): - script_path = os.path.dirname(os.path.realpath(__file__)) - qtcreator_repo = os.path.join(script_path, '..') - parser = argparse.ArgumentParser(description="Create Qt Creator source packages") - parser.add_argument('-p', default=qtcreator_repo, dest='repo', help='path to repository') - parser.add_argument('-n', default='', dest='name', help='name of plugin') - parser.add_argument('-s', action='append', default=[], dest='modules', help='submodule to add') - parser.add_argument('version', help='full version including tag, e.g. 4.2.0-beta1') - parser.add_argument('edition', help='(opensource | enterprise)') - return parser.parse_args() - -def main(): - args = parse_arguments() - base_repo_name = args.name if args.name else "qtcreator" - if not args.name and not args.modules: # default Qt Creator repository - submodules = [os.path.join('src', 'shared', 'qbs'), - os.path.join('src', 'tools', 'perfparser')] - args.modules = [path for path in submodules if os.path.exists(os.path.join(args.repo, path, '.git'))] - repos = [(base_repo_name, args.repo, '')] - for module in args.modules: - repos += [(module, os.path.join(args.repo, module), module + os.sep)] - name_part = '-' + args.name if args.name else '' - prefix = 'qt-creator-' + args.edition + name_part + '-src-' + args.version - package_repos(repos, prefix, os.path.join(os.getcwd(), prefix)) - -if __name__ == "__main__": - main() diff --git a/scripts/packagePlugins.py b/scripts/packagePlugins.py deleted file mode 100755 index 5cbace790c3..00000000000 --- a/scripts/packagePlugins.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python - -############################################################################ -# -# Copyright (C) 2016 The Qt Company Ltd. -# Contact: https://www.qt.io/licensing/ -# -# This file is part of Qt Creator. -# -# Commercial License Usage -# Licensees holding valid commercial Qt licenses may use this file in -# accordance with the commercial license agreement provided with the -# Software or, alternatively, in accordance with the terms contained in -# a written agreement between you and The Qt Company. For licensing terms -# and conditions see https://www.qt.io/terms-conditions. For further -# information use the contact form at https://www.qt.io/contact-us. -# -# GNU General Public License Usage -# Alternatively, this file may be used under the terms of the GNU -# General Public License version 3 as published by the Free Software -# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -# included in the packaging of this file. Please review the following -# information to ensure the GNU General Public License requirements will -# be met: https://www.gnu.org/licenses/gpl-3.0.html. -# -############################################################################ - -import argparse -import os -import subprocess - -import common - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Deploy and 7z a directory of plugins.") - parser.add_argument('--7z', help='path to 7z binary', - default='7z.exe' if common.is_windows_platform() else '7z', - metavar='<7z_binary>', dest='sevenzip') - parser.add_argument('--qmake_binary', help='path to qmake binary which was used for compilation', - required=common.is_linux_platform(), metavar='') - parser.add_argument('source_directory', help='directory to deploy and 7z') - parser.add_argument('target_file', help='target file path of the resulting 7z') - return parser.parse_args() - -if __name__ == "__main__": - arguments = parse_arguments() - qt_install_info = common.get_qt_install_info(arguments.qmake_binary) - if common.is_linux_platform(): - common.fix_rpaths(arguments.source_directory, - os.path.join(arguments.source_directory, 'lib', 'Qt', 'lib'), - qt_install_info) - if common.is_mac_platform(): - # remove Qt rpath - lib_path = qt_install_info['QT_INSTALL_LIBS'] - common.os_walk(arguments.source_directory, - lambda fp: fp.endswith('.dylib'), - lambda fp: subprocess.call(['install_name_tool', '-delete_rpath', lib_path, fp])) - subprocess.check_call([arguments.sevenzip, 'a', '-mx9', arguments.target_file, - os.path.join(arguments.source_directory, '*')]) From f5b77d87da1466e2176144741d5985cb54fe5169 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Tue, 19 Apr 2022 11:23:39 +0200 Subject: [PATCH 2/7] Move build scripts to Python3 On newer macOS versions there even is no unversioned python executable anymore, so we need to make it explicit Change-Id: Ic2260a06b859e949a42f7dc34f1ff4dd582ce635 Reviewed-by: Qt CI Bot Reviewed-by: Christian Stenger --- scripts/build.py | 2 +- scripts/build_plugin.py | 2 +- scripts/deployqt.py | 2 +- scripts/makedmg.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/build.py b/scripts/build.py index 8195459006d..e2037d8574c 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 ############################################################################# ## ## Copyright (C) 2020 The Qt Company Ltd. diff --git a/scripts/build_plugin.py b/scripts/build_plugin.py index cddb9f126a8..dadb274efa6 100755 --- a/scripts/build_plugin.py +++ b/scripts/build_plugin.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 ############################################################################# ## ## Copyright (C) 2020 The Qt Company Ltd. diff --git a/scripts/deployqt.py b/scripts/deployqt.py index 5ac56bedc3f..d18b3cc2893 100755 --- a/scripts/deployqt.py +++ b/scripts/deployqt.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 ################################################################################ # Copyright (C) The Qt Company Ltd. # All rights reserved. diff --git a/scripts/makedmg.py b/scripts/makedmg.py index fa0d4719ecf..d9178bc65c2 100755 --- a/scripts/makedmg.py +++ b/scripts/makedmg.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 ############################################################################ # # Copyright (C) 2018 The Qt Company Ltd. From 98dda165e4007d3eeda9896b21163d79693c94c0 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Fri, 8 Apr 2022 11:58:43 +0200 Subject: [PATCH 3/7] UpdateInfo: Add some auto test Change-Id: If97121bda98e1b09f093d0bcc8f60efb1aa18235 Reviewed-by: Reviewed-by: Christian Stenger --- src/plugins/updateinfo/CMakeLists.txt | 8 +- src/plugins/updateinfo/updateinfo.qbs | 5 +- src/plugins/updateinfo/updateinfoplugin.cpp | 119 +------------- src/plugins/updateinfo/updateinfotools.h | 163 ++++++++++++++++++++ tests/auto/CMakeLists.txt | 1 + tests/auto/auto.qbs | 1 + tests/auto/updateinfo/CMakeLists.txt | 5 + tests/auto/updateinfo/tst_updateinfo.cpp | 78 ++++++++++ tests/auto/updateinfo/updateinfo.qbs | 7 + 9 files changed, 271 insertions(+), 116 deletions(-) create mode 100644 src/plugins/updateinfo/updateinfotools.h create mode 100644 tests/auto/updateinfo/CMakeLists.txt create mode 100644 tests/auto/updateinfo/tst_updateinfo.cpp create mode 100644 tests/auto/updateinfo/updateinfo.qbs diff --git a/src/plugins/updateinfo/CMakeLists.txt b/src/plugins/updateinfo/CMakeLists.txt index fe43e0be09b..fadca2cd6a4 100644 --- a/src/plugins/updateinfo/CMakeLists.txt +++ b/src/plugins/updateinfo/CMakeLists.txt @@ -3,6 +3,10 @@ add_qtc_plugin(UpdateInfo PLUGIN_DEPENDS Core PLUGIN_JSON_IN UPDATEINFO_EXPERIMENTAL_STR=true SOURCES - settingspage.cpp settingspage.h settingspage.ui - updateinfoplugin.cpp updateinfoplugin.h + settingspage.cpp + settingspage.h + settingspage.ui + updateinfoplugin.cpp + updateinfoplugin.h + updateinfotools.h ) diff --git a/src/plugins/updateinfo/updateinfo.qbs b/src/plugins/updateinfo/updateinfo.qbs index 5253039dbc9..2dbae1933a8 100644 --- a/src/plugins/updateinfo/updateinfo.qbs +++ b/src/plugins/updateinfo/updateinfo.qbs @@ -12,10 +12,11 @@ QtcPlugin { pluginJsonReplacements: ({"UPDATEINFO_EXPERIMENTAL_STR": (enable ? "false": "true")}) files: [ - "updateinfoplugin.cpp", - "updateinfoplugin.h", "settingspage.cpp", "settingspage.h", "settingspage.ui", + "updateinfoplugin.cpp", + "updateinfoplugin.h", + "updateinfotools.h", ] } diff --git a/src/plugins/updateinfo/updateinfoplugin.cpp b/src/plugins/updateinfo/updateinfoplugin.cpp index e2271ad396f..743802f3700 100644 --- a/src/plugins/updateinfo/updateinfoplugin.cpp +++ b/src/plugins/updateinfo/updateinfoplugin.cpp @@ -23,9 +23,11 @@ ** ****************************************************************************/ -#include "settingspage.h" #include "updateinfoplugin.h" +#include "settingspage.h" +#include "updateinfotools.h" + #include #include #include @@ -181,107 +183,6 @@ void UpdateInfoPlugin::collectCheckForUpdatesOutput(const QString &contents) d->m_collectedOutput += contents; } -struct Update -{ - QString name; - QString version; -}; - -static QList availableUpdates(const QDomDocument &document) -{ - if (document.isNull() || !document.firstChildElement().hasChildNodes()) - return {}; - QList result; - const QDomNodeList updates = document.firstChildElement().elementsByTagName("update"); - for (int i = 0; i < updates.size(); ++i) { - const QDomNode node = updates.item(i); - if (node.isElement()) { - const QDomElement element = node.toElement(); - if (element.hasAttribute("name")) - result.append({element.attribute("name"), element.attribute("version")}); - } - } - return result; -} - -struct QtPackage -{ - QString displayName; - QVersionNumber version; - bool installed; - bool isPrerelease = false; -}; - -static QList availableQtPackages(const QDomDocument &document) -{ - if (document.isNull() || !document.firstChildElement().hasChildNodes()) - return {}; - QList result; - const QDomNodeList packages = document.firstChildElement().elementsByTagName("package"); - for (int i = 0; i < packages.size(); ++i) { - const QDomNode node = packages.item(i); - if (node.isElement()) { - const QDomElement element = node.toElement(); - if (element.hasAttribute("displayname") && element.hasAttribute("name") - && element.hasAttribute("version")) { - QtPackage package{element.attribute("displayname"), - QVersionNumber::fromString(element.attribute("version")), - element.hasAttribute("installedVersion")}; - // Heuristic: Prerelease if the name is not "Qt x.y.z" - // (prereleases are named "Qt x.y.z-alpha" etc) - package.isPrerelease = package.displayName - != QString("Qt %1").arg(package.version.toString()); - result.append(package); - } - } - } - std::sort(result.begin(), result.end(), [](const QtPackage &p1, const QtPackage &p2) { - return p1.version > p2.version; - }); - return result; -} - -// Expects packages to be sorted, high version first. -static Utils::optional highestInstalledQt(const QList &packages) -{ - const auto highestInstalledIt = std::find_if(packages.cbegin(), - packages.cend(), - [](const QtPackage &p) { return p.installed; }); - if (highestInstalledIt == packages.cend()) // Qt not installed - return {}; - return *highestInstalledIt; -} - -// Expects packages to be sorted, high version first. -static Utils::optional qtToNagAbout(const QList &allPackages, - QVersionNumber *highestSeen) -{ - // Filter out any Qt prereleases - const QList packages = Utils::filtered(allPackages, [](const QtPackage &p) { - return !p.isPrerelease; - }); - if (packages.isEmpty()) - return {}; - const QtPackage highest = packages.constFirst(); - qCDebug(log) << "Highest available (non-prerelease) Qt:" << highest.version; - qCDebug(log) << "Highest previously seen (non-prerelease) Qt:" << *highestSeen; - // if the highestSeen version is null, we don't know if the Qt version is new, and better don't nag - const bool isNew = !highestSeen->isNull() && highest.version > *highestSeen; - if (highestSeen->isNull() || isNew) - *highestSeen = highest.version; - if (!isNew) - return {}; - const Utils::optional highestInstalled = highestInstalledQt(packages); - qCDebug(log) << "Highest installed Qt:" - << qPrintable(highestInstalled ? highestInstalled->version.toString() - : QString("none")); - if (!highestInstalled) // don't nag if no Qt is installed at all - return {}; - if (highestInstalled->version == highest.version) - return {}; - return highest; -} - static void showUpdateInfo(const QList &updates, const std::function &startUpdater) { Utils::InfoBarEntry info(InstallUpdates, @@ -334,20 +235,14 @@ void UpdateInfoPlugin::checkForUpdatesFinished() { setLastCheckDate(QDate::currentDate()); - QDomDocument document; - // since the output can contain two toplevel items from the two separate MaintenanceTool runs, - // surround with a toplevel element - const QString xml = d->m_collectedOutput.isEmpty() - ? QString() - : ("" + d->m_collectedOutput + ""); qCDebug(log) << "--- MaintenanceTool output (combined):"; - qCDebug(log) << qPrintable(xml); - document.setContent(xml); + qCDebug(log) << qPrintable(d->m_collectedOutput); + std::unique_ptr document = documentForResponse(d->m_collectedOutput); stopCheckForUpdates(); - const QList updates = availableUpdates(document); - const QList qtPackages = availableQtPackages(document); + const QList updates = availableUpdates(*document); + const QList qtPackages = availableQtPackages(*document); if (log().isDebugEnabled()) { qCDebug(log) << "--- Available updates:"; for (const Update &u : updates) diff --git a/src/plugins/updateinfo/updateinfotools.h b/src/plugins/updateinfo/updateinfotools.h new file mode 100644 index 00000000000..66ad2eb3aca --- /dev/null +++ b/src/plugins/updateinfo/updateinfotools.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2022 the Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(log) + +std::unique_ptr documentForResponse(const QString &response) +{ + // since the output can contain two toplevel items from the two separate MaintenanceTool runs, + // surround with a toplevel element + const QString xml = response.isEmpty() ? QString() : ("" + response + ""); + std::unique_ptr doc(new QDomDocument); + doc->setContent(xml); + return doc; +} + +struct Update +{ + QString name; + QString version; + + bool operator==(const Update &other) const + { + return other.name == name && other.version == version; + }; +}; + +QList availableUpdates(const QDomDocument &document) +{ + if (document.isNull() || !document.firstChildElement().hasChildNodes()) + return {}; + QList result; + const QDomNodeList updates = document.firstChildElement().elementsByTagName("update"); + for (int i = 0; i < updates.size(); ++i) { + const QDomNode node = updates.item(i); + if (node.isElement()) { + const QDomElement element = node.toElement(); + if (element.hasAttribute("name")) + result.append({element.attribute("name"), element.attribute("version")}); + } + } + return result; +} + +struct QtPackage +{ + QString displayName; + QVersionNumber version; + bool installed; + bool isPrerelease = false; + + bool operator==(const QtPackage &other) const + { + return other.installed == installed && other.isPrerelease == isPrerelease + && other.version == version && other.displayName == displayName; + } +}; + +QList availableQtPackages(const QDomDocument &document) +{ + if (document.isNull() || !document.firstChildElement().hasChildNodes()) + return {}; + QList result; + const QDomNodeList packages = document.firstChildElement().elementsByTagName("package"); + for (int i = 0; i < packages.size(); ++i) { + const QDomNode node = packages.item(i); + if (node.isElement()) { + const QDomElement element = node.toElement(); + if (element.hasAttribute("displayname") && element.hasAttribute("name") + && element.hasAttribute("version")) { + QtPackage package{element.attribute("displayname"), + QVersionNumber::fromString(element.attribute("version")), + element.hasAttribute("installedVersion")}; + // Heuristic: Prerelease if the name is not "Qt x.y.z" + // (prereleases are named "Qt x.y.z-alpha" etc) + package.isPrerelease = package.displayName + != QString("Qt %1").arg(package.version.toString()); + result.append(package); + } + } + } + std::sort(result.begin(), result.end(), [](const QtPackage &p1, const QtPackage &p2) { + return p1.version > p2.version; + }); + return result; +} + +// Expects packages to be sorted, high version first. +Utils::optional highestInstalledQt(const QList &packages) +{ + const auto highestInstalledIt = std::find_if(packages.cbegin(), + packages.cend(), + [](const QtPackage &p) { return p.installed; }); + if (highestInstalledIt == packages.cend()) // Qt not installed + return {}; + return *highestInstalledIt; +} + +// Expects packages to be sorted, high version first. +Utils::optional qtToNagAbout(const QList &allPackages, + QVersionNumber *highestSeen) +{ + // Filter out any Qt prereleases + const QList packages = Utils::filtered(allPackages, [](const QtPackage &p) { + return !p.isPrerelease; + }); + if (packages.isEmpty()) + return {}; + const QtPackage highest = packages.constFirst(); + qCDebug(log) << "Highest available (non-prerelease) Qt:" << highest.version; + qCDebug(log) << "Highest previously seen (non-prerelease) Qt:" << *highestSeen; + // if the highestSeen version is null, we don't know if the Qt version is new, and better don't nag + const bool isNew = !highestSeen->isNull() && highest.version > *highestSeen; + if (highestSeen->isNull() || isNew) + *highestSeen = highest.version; + if (!isNew) + return {}; + const Utils::optional highestInstalled = highestInstalledQt(packages); + qCDebug(log) << "Highest installed Qt:" + << qPrintable(highestInstalled ? highestInstalled->version.toString() + : QString("none")); + if (!highestInstalled) // don't nag if no Qt is installed at all + return {}; + if (highestInstalled->version == highest.version) + return {}; + return highest; +} + +Q_DECLARE_METATYPE(Update) +Q_DECLARE_METATYPE(QtPackage) diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index dcf51ff0a2e..bc58a5b953d 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -21,5 +21,6 @@ add_subdirectory(ssh) add_subdirectory(toolchaincache) add_subdirectory(tracing) add_subdirectory(treeviewfind) +add_subdirectory(updateinfo) add_subdirectory(utils) add_subdirectory(valgrind) diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs index c82d1df0941..6526653ab8e 100644 --- a/tests/auto/auto.qbs +++ b/tests/auto/auto.qbs @@ -24,6 +24,7 @@ Project { "toolchaincache/toolchaincache.qbs", "tracing/tracing.qbs", "treeviewfind/treeviewfind.qbs", + "updateinfo/updateinfo.qbs", "utils/utils.qbs", "valgrind/valgrind.qbs", ].concat(project.additionalAutotests) diff --git a/tests/auto/updateinfo/CMakeLists.txt b/tests/auto/updateinfo/CMakeLists.txt new file mode 100644 index 00000000000..e44adb8ff81 --- /dev/null +++ b/tests/auto/updateinfo/CMakeLists.txt @@ -0,0 +1,5 @@ +add_qtc_test(tst_updateinfo + INCLUDES ${PROJECT_SOURCE_DIR}/src/plugins + DEPENDS Utils Qt5::Xml + SOURCES tst_updateinfo.cpp +) diff --git a/tests/auto/updateinfo/tst_updateinfo.cpp b/tests/auto/updateinfo/tst_updateinfo.cpp new file mode 100644 index 00000000000..55e61a16c23 --- /dev/null +++ b/tests/auto/updateinfo/tst_updateinfo.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include + +Q_LOGGING_CATEGORY(log, "qtc.updateinfo", QtWarningMsg) + +class tst_UpdateInfo : public QObject +{ + Q_OBJECT + +private slots: + void updates_data(); + void updates(); +}; + +void tst_UpdateInfo::updates_data() +{ + QTest::addColumn("xml"); + QTest::addColumn>("xupdates"); + QTest::addColumn>("xpackages"); + + QTest::newRow("updates and packages") + << R"raw( + + + + + + + )raw" + << QList({{"Qt Design Studio 3.2.0", "3.2.0-0-202203291247"}}) + << QList( + {{"Qt 6.2.1", QVersionNumber::fromString("6.2.1-0-202110220854"), false, false}, + {"Qt 6.1.0-beta1", QVersionNumber::fromString("6.1.0-0-202105040922"), false, true}, + {"Qt 5.15.2", QVersionNumber::fromString("5.15.2-0-202011130607"), true, false}}); +} + +void tst_UpdateInfo::updates() +{ + QFETCH(QString, xml); + QFETCH(QList, xupdates); + QFETCH(QList, xpackages); + + std::unique_ptr doc = documentForResponse(xml); + const QList updates = availableUpdates(*doc); + QCOMPARE(updates, xupdates); + const QList packages = availableQtPackages(*doc); + QCOMPARE(packages, xpackages); +} + +QTEST_GUILESS_MAIN(tst_UpdateInfo) + +#include "tst_updateinfo.moc" diff --git a/tests/auto/updateinfo/updateinfo.qbs b/tests/auto/updateinfo/updateinfo.qbs new file mode 100644 index 00000000000..9aa07dd28fc --- /dev/null +++ b/tests/auto/updateinfo/updateinfo.qbs @@ -0,0 +1,7 @@ +QtcAutotest { + name: "UpdateInfo autotest" + Depends { name: "Qt.xml" } + Depends { name: "Utils" } + files: "tst_updateinfo.cpp" + cpp.includePaths: base.concat(["../../../src/plugins"]) +} From d1686e18679fd229d2bfa45828a79004cad1e052 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 20 Apr 2022 10:27:18 +0200 Subject: [PATCH 4/7] UpdateInfo: Fix parsing of update info It was no longer showing any updates. At some point MaintenanceTool added headers to its responses, which broke our combine-and- parse hack, since such a header in the middle of XML is invalid. Add a hack that removes these headers first. This should be refactored without the use of ShellCommand in master, because that doesn't allow us to access the output of the two jobs separately. Change-Id: I7248b070a8edb1a45248b3531ed50bb0d94eef73 Reviewed-by: Reviewed-by: Christian Stenger --- src/plugins/updateinfo/updateinfotools.h | 8 ++++++-- tests/auto/updateinfo/tst_updateinfo.cpp | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/updateinfo/updateinfotools.h b/src/plugins/updateinfo/updateinfotools.h index 66ad2eb3aca..c8dd3a7fd07 100644 --- a/src/plugins/updateinfo/updateinfotools.h +++ b/src/plugins/updateinfo/updateinfotools.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -40,8 +41,11 @@ Q_DECLARE_LOGGING_CATEGORY(log) std::unique_ptr documentForResponse(const QString &response) { // since the output can contain two toplevel items from the two separate MaintenanceTool runs, - // surround with a toplevel element - const QString xml = response.isEmpty() ? QString() : ("" + response + ""); + // clean up any and surround with a toplevel element + QString responseWithoutHeader = response; + responseWithoutHeader.remove(QRegularExpression("<\\?xml.*\\?>")); + const QString xml = response.isEmpty() ? QString() + : ("" + responseWithoutHeader + ""); std::unique_ptr doc(new QDomDocument); doc->setContent(xml); return doc; diff --git a/tests/auto/updateinfo/tst_updateinfo.cpp b/tests/auto/updateinfo/tst_updateinfo.cpp index 55e61a16c23..1fa1686919b 100644 --- a/tests/auto/updateinfo/tst_updateinfo.cpp +++ b/tests/auto/updateinfo/tst_updateinfo.cpp @@ -45,9 +45,11 @@ void tst_UpdateInfo::updates_data() QTest::addColumn>("xpackages"); QTest::newRow("updates and packages") - << R"raw( + << R"raw( + + From 2529b62315edc7aafe8407ea2de898923d085f9e Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 20 Apr 2022 15:10:43 +0200 Subject: [PATCH 5/7] Editor: improve open link via mouse press It improve in the situations where the mouse move slightly between press and release event, this should be mostly notable for trackpad users. Fixes: QTCREATORBUG-26595 Change-Id: I36ef7d23c80c09b9248abd8313c040993ece92a4 Reviewed-by: Marcus Tillmanns Reviewed-by: Christian Stenger --- src/plugins/texteditor/texteditor.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index e0f1afe2d6f..74cfe43cfce 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -5296,7 +5296,17 @@ void TextEditorWidgetPrivate::clearVisibleFoldedBlock() void TextEditorWidget::mouseMoveEvent(QMouseEvent *e) { d->requestUpdateLink(e); - d->m_linkPressed = false; + + bool onLink = false; + if (d->m_linkPressed && d->m_currentLink.hasValidTarget()) { + const int eventCursorPosition = cursorForPosition(e->pos()).position(); + if (eventCursorPosition < d->m_currentLink.linkTextStart + || eventCursorPosition > d->m_currentLink.linkTextEnd) { + d->m_linkPressed = false; + } else { + onLink = true; + } + } static Utils::optional startMouseMoveCursor; if (e->buttons() == Qt::LeftButton && e->modifiers() & Qt::AltModifier) { @@ -5357,7 +5367,8 @@ void TextEditorWidget::mouseMoveEvent(QMouseEvent *e) d->m_mouseOnFoldedMarker = false; viewport()->setCursor(Qt::IBeamCursor); } - } else { + } else if (!onLink || e->buttons() != Qt::LeftButton + || e->modifiers() != Qt::ControlModifier) { QPlainTextEdit::mouseMoveEvent(e); } } From cf96a91b69d6d9c8d8f8db3e3eadaa24d25d5f32 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 20 Apr 2022 17:27:51 +0200 Subject: [PATCH 6/7] ClangCodeModel: Fix mis-detection of class members as operators The name check was not tight enough. Change-Id: I5d813a29525bd5b5c23ce04f0bd9e5982a36536e Reviewed-by: Reviewed-by: Qt CI Bot Reviewed-by: Christian Stenger --- src/plugins/clangcodemodel/clangdclient.cpp | 14 +++++++++++--- src/plugins/clangcodemodel/test/clangdtests.cpp | 8 ++++++++ .../test/data/highlighting/highlighting.cpp | 9 +++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index cc8e7c8dc06..b12c63c8591 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -3839,10 +3839,18 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) QString detail = node.detail().value_or(QString()); const bool isCallToNew = node.kind() == "CXXNew"; const bool isCallToDelete = node.kind() == "CXXDelete"; - if (!isCallToNew && !isCallToDelete - && (!detail.startsWith(operatorPrefix) || detail == operatorPrefix)) { + const auto isProperOperator = [&] { + if (isCallToNew || isCallToDelete) + return true; + if (!detail.startsWith(operatorPrefix)) + return false; + if (detail == operatorPrefix) + return false; + const QChar nextChar = detail.at(operatorPrefix.length()); + return !nextChar.isLetterOrNumber() && nextChar != '_'; + }; + if (!isProperOperator()) return; - } if (!isCallToNew && !isCallToDelete) detail.remove(0, operatorPrefix.length()); diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index c0b0805ca32..e22f2dc6fff 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -1329,6 +1329,14 @@ void ClangdTestHighlighting::test_data() << QList{C_FIELD} << 0; QTest::newRow("pass inherited member by value") << 1038 << 21 << 1038 << 26 << QList{C_FIELD} << 0; + QTest::newRow("fake operator member declaration") << 1045 << 9 << 1045 << 23 + << QList{C_FIELD, C_DECLARATION} << 0; + QTest::newRow("fake operator method declaration") << 1046 << 10 << 1046 << 24 + << QList{C_FUNCTION, C_DECLARATION} << 0; + QTest::newRow("fake operator member access") << 1049 << 8 << 1049 << 22 + << QList{C_FIELD} << 0; + QTest::newRow("fake operator method call") << 1050 << 8 << 1050 << 22 + << QList{C_FUNCTION} << 0; } void ClangdTestHighlighting::test() diff --git a/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp b/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp index cd4ecb46cec..9d1ba0d5e74 100644 --- a/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp +++ b/src/plugins/clangcodemodel/test/data/highlighting/highlighting.cpp @@ -1040,3 +1040,12 @@ template class Derived2 : public BaseWithMember2 return false; } }; + +struct StructWithMisleadingMemberNames { + int operatormember; + void operatorMethod(); +}; +void useStrangeStruct(StructWithMisleadingMemberNames *s) { + s->operatormember = 5; + s->operatorMethod(); +} From 9656eb9e7a13213c51a9674a50f710b02abbe124 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 13 Apr 2022 13:11:57 +0200 Subject: [PATCH 7/7] QmlJS: Fix more invalid M325 cases Fixes: QTCREATORBUG-27380 Change-Id: I76d1ef3d2f2a4cc9d930a006ecb3b564efea3fbc Reviewed-by: Reviewed-by: Fawzi Mohamed --- src/libs/qmljs/qmljscheck.cpp | 14 +++++++++++++- tests/auto/qml/codemodel/check/equality-checks.qml | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index d9fe7df36c6..519706c326d 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -1306,7 +1306,17 @@ static bool isStringValue(const Value *value) if (value->asStringValue()) return true; if (auto obj = value->asObjectValue()) - return obj->className() == "QString" || obj->className() == "string"; + return obj->className() == "QString" || obj->className() == "string" || obj->className() == "String"; + + return false; +} + +static bool isBooleanValue(const Value *value) +{ + if (value->asBooleanValue()) + return true; + if (auto obj = value->asObjectValue()) + return obj->className() == "boolean" || obj->className() == "Boolean"; return false; } @@ -1324,6 +1334,8 @@ static bool strictCompareConstant(const Value *lhs, const Value *rhs) return false; if (isStringValue(lhs) && isStringValue(rhs)) return false; + if (isBooleanValue(lhs) && isBooleanValue(rhs)) + return false; if (lhs->asBooleanValue() && !rhs->asBooleanValue()) return true; if (lhs->asNumberValue() && !rhs->asNumberValue()) diff --git a/tests/auto/qml/codemodel/check/equality-checks.qml b/tests/auto/qml/codemodel/check/equality-checks.qml index 1a2c1f7580b..9355142df40 100644 --- a/tests/auto/qml/codemodel/check/equality-checks.qml +++ b/tests/auto/qml/codemodel/check/equality-checks.qml @@ -139,6 +139,12 @@ Rectangle { if (nObj === 1) {} if (nNum === 1) {} + var bObj = Boolean(1 > 0); + if (bObj === b) {} + + var sBool = String(b); + if (sBool === s) {} + } ListView {