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/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/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. 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, '*')]) diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index c5a186bda8d..aae32a6dd98 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -1350,7 +1350,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; } @@ -1368,6 +1378,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/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index b1e0aa2f473..9bc1b80ff41 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -3973,10 +3973,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(); +} diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 35d764a33e5..318464fb7f9 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -5302,7 +5302,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) { @@ -5363,7 +5373,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); } } 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 55cc5589ce6..87fbc33e1e9 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..c8dd3a7fd07 --- /dev/null +++ b/src/plugins/updateinfo/updateinfotools.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** 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 + +#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, + // 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; +} + +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/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 { 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..1fa1686919b --- /dev/null +++ b/tests/auto/updateinfo/tst_updateinfo.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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"]) +}