From 288d2e76bcf8695c885d4c585a3a1fc2da9e1b20 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Wed, 27 Sep 2023 08:43:35 +0200 Subject: [PATCH 01/21] FancyLineEdit: Fix some warnings - initialize opacity (was never used uninitialized though) - fix include Change-Id: Ic3a9f8154f1c4bd77e759b6b45ee06b20a394c8d Reviewed-by: Marcus Tillmanns --- src/libs/utils/fancylineedit.cpp | 3 +-- src/libs/utils/fancylineedit.h | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index f30fa4bd841..f1e177e0633 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -7,8 +7,8 @@ #include "execmenu.h" #include "historycompleter.h" #include "hostosinfo.h" +#include "icon.h" #include "qtcassert.h" -#include "utilsicons.h" #include "utilstr.h" #include @@ -621,7 +621,6 @@ QString FancyLineEdit::fixInputString(const QString &string) FancyIconButton::FancyIconButton(QWidget *parent) : QAbstractButton(parent) - , m_autoHide(false) { setCursor(Qt::ArrowCursor); setFocusPolicy(Qt::NoFocus); diff --git a/src/libs/utils/fancylineedit.h b/src/libs/utils/fancylineedit.h index 1b9cf4d5e52..a8bd9501274 100644 --- a/src/libs/utils/fancylineedit.h +++ b/src/libs/utils/fancylineedit.h @@ -45,8 +45,8 @@ protected: void keyReleaseEvent(QKeyEvent *ke) override; private: - float m_iconOpacity; - bool m_autoHide; + float m_iconOpacity = 1.0f; + bool m_autoHide = false; QIcon m_icon; }; From d04585b519f51b47cfc4200a048acc416c187b86 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 27 Sep 2023 09:44:35 +0200 Subject: [PATCH 02/21] CMakeProjectManager: Remove unused arg Make some methods static. Amends 05614ab7402623eb0a69ae7d0301044f70f93e91 Change-Id: Ia72a95c0c3bf0acf7a94e1bac0a162db23aa0c77 Reviewed-by: Cristian Adam --- .../cmakeprojectmanager/cmakefilecompletionassist.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index 84902f9d495..62cc290aa8d 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -143,7 +143,7 @@ static int findPathStart(const AssistInterface *interface) } template -QList generateList(const T &words, const QIcon &icon) +static QList generateList(const T &words, const QIcon &icon) { return transform(words, [&icon](const QString &word) -> AssistProposalItemInterface * { AssistProposalItem *item = new AssistProposalItem(); @@ -153,7 +153,7 @@ QList generateList(const T &words, const QIcon &i }); } -QString readFirstParagraphs(const QString &element, const FilePath &helpFile) +static QString readFirstParagraphs(const FilePath &helpFile) { static QMap map; if (map.contains(helpFile)) @@ -167,8 +167,8 @@ QString readFirstParagraphs(const QString &element, const FilePath &helpFile) return firstParagraphs; } -QList generateList(const QMap &words, - const QIcon &icon) +static QList generateList(const QMap &words, + const QIcon &icon) { struct MarkDownAssitProposalItem : public AssistProposalItem { @@ -180,7 +180,7 @@ QList generateList(const QMap MarkDownAssitProposalItem *item = new MarkDownAssitProposalItem(); item->setText(it.key()); if (!it.value().isEmpty()) - item->setDetail(readFirstParagraphs(it.key(), it.value())); + item->setDetail(readFirstParagraphs(it.value())); item->setIcon(icon); list << item; }; From cd5cbd64f7e50db9c1a31333b309db177d46fa52 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 26 Sep 2023 15:46:04 +0200 Subject: [PATCH 03/21] SquishTests: Fix finding line edit on wizard Change-Id: I26be629e11e2d768b3afed518cd6026028d2e17a Reviewed-by: David Schulz --- src/libs/utils/projectintropage.cpp | 1 + tests/system/shared/project.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp index 0b1a0a63557..b710e68723b 100644 --- a/src/libs/utils/projectintropage.cpp +++ b/src/libs/utils/projectintropage.cpp @@ -79,6 +79,7 @@ ProjectIntroPage::ProjectIntroPage(QWidget *parent) : d->m_nameLineEdit = new Utils::FancyLineEdit(frame); d->m_pathChooser = new Utils::PathChooser(frame); + d->m_pathChooser->setObjectName("baseFolder"); // used by Squish d->m_pathChooser->setExpectedKind(PathChooser::Directory); d->m_pathChooser->setDisabled(d->m_forceSubProject); diff --git a/tests/system/shared/project.py b/tests/system/shared/project.py index b39ef8060ee..317067142a4 100644 --- a/tests/system/shared/project.py +++ b/tests/system/shared/project.py @@ -81,8 +81,8 @@ def __createProjectOrFileSelectType__(category, template, fromWelcome = False, i return __getSupportedPlatforms__(str(text), template)[0] def __createProjectSetNameAndPath__(path, projectName = None, checks = True): - directoryEdit = waitForObject("{type='Utils::FancyLineEdit' unnamed='1' visible='1' " - "toolTip~='Full path: .*'}") + pathChooser = waitForObject("{type='Utils::PathChooser' name='baseFolder' visible='1'}") + directoryEdit = getChildByClass(pathChooser, "Utils::FancyLineEdit") replaceEditorContent(directoryEdit, path) projectNameEdit = waitForObject("{name='nameLineEdit' visible='1' " "type='Utils::FancyLineEdit'}") From dda6d05282e078b5295843915bbc56ad5fe84365 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 26 Sep 2023 16:01:24 +0200 Subject: [PATCH 04/21] SquishTests: Adapt to changed ui Change-Id: I906f4f9dc07f905b9efa71b1ada60409349a1b05 Reviewed-by: David Schulz --- tests/system/objects.map | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/objects.map b/tests/system/objects.map index cc359d6b7fc..940739813a9 100644 --- a/tests/system/objects.map +++ b/tests/system/objects.map @@ -126,7 +126,7 @@ :Qt Creator.DragDoc_QToolButton {toolTip='Drag to drag documents between splits' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :Qt Creator.Events_QDockWidget {name='QmlProfiler.Statistics.DockDockWidget' type='QDockWidget' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :Qt Creator.Events_QTabBar {aboveWidget=':Qt Creator.Events_QDockWidget' type='QTabBar' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} -:Qt Creator.Issues_QListView {type='Utils::TreeView' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow' windowTitle='Issues'} +:Qt Creator.Issues_QListView {type='QTreeView' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow' windowTitle='Issues'} :Qt Creator.Project.Menu.File_QMenu {name='Project.Menu.File' type='QMenu'} :Qt Creator.Project.Menu.Folder_QMenu {name='Project.Menu.Folder' type='QMenu' visible='1'} :Qt Creator.QML debugging and profiling:_QComboBox {leftWidget=':Qt Creator.QML debugging and profiling:_QLabel' type='QComboBox' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} From 621a492b6a2aa1c691c454894abf2ea6d65bc76f Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 27 Sep 2023 09:57:59 +0200 Subject: [PATCH 05/21] AutoTest: Guard against concurrent access While parsing for Qt Quick Tests it may happen that an internal map is accessed concurrently by different threads. Guard against this. Minor drive-by optimizations. Change-Id: Ic3b62c27feddb9a5ac5588a6c9643fc0e623ce19 Reviewed-by: David Schulz --- src/plugins/autotest/quick/quicktestparser.cpp | 15 ++++++++++----- src/plugins/autotest/quick/quicktestparser.h | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/plugins/autotest/quick/quicktestparser.cpp b/src/plugins/autotest/quick/quicktestparser.cpp index ef0bba8cc89..017f9f4c6dd 100644 --- a/src/plugins/autotest/quick/quicktestparser.cpp +++ b/src/plugins/autotest/quick/quicktestparser.cpp @@ -261,7 +261,10 @@ bool QuickTestParser::handleQtQuickTest(QPromise &promise, return false; const FilePath cppFileName = document->filePath(); const FilePath proFile = FilePath::fromString(ppList.at(0)->projectFile); - m_mainCppFiles.insert(cppFileName, proFile); + { + QWriteLocker lock(&m_parseLock); + m_mainCppFiles.insert(cppFileName, proFile); + } const FilePath srcDir = FilePath::fromString(quickTestSrcDir(cppFileName)); if (srcDir.isEmpty()) return false; @@ -340,13 +343,13 @@ QuickTestParser::QuickTestParser(ITestFramework *framework) void QuickTestParser::init(const QSet &filesToParse, bool fullParse) { m_qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); + QWriteLocker lock(&m_parseLock); // should not be necessary if (!fullParse) { // in a full parse we get the correct entry points by the respective main m_proFilesForQmlFiles = QuickTestUtils::proFilesForQmlFiles(framework(), filesToParse); // get rid of cached main cpp files that are going to get processed anyhow for (const FilePath &file : filesToParse) { - if (m_mainCppFiles.contains(file)) { - m_mainCppFiles.remove(file); + if (m_mainCppFiles.remove(file) == 1) { if (m_mainCppFiles.isEmpty()) break; } @@ -355,6 +358,7 @@ void QuickTestParser::init(const QSet &filesToParse, bool fullParse) // get rid of all cached main cpp files m_mainCppFiles.clear(); } + lock.unlock(); m_checkForDerivedTests = theQtTestFramework().quickCheckForDerivedTests(); @@ -399,9 +403,10 @@ bool QuickTestParser::processDocument(QPromise &promise, return handleQtQuickTest(promise, cppdoc, framework()); } -FilePath QuickTestParser::projectFileForMainCppFile(const FilePath &fileName) const +FilePath QuickTestParser::projectFileForMainCppFile(const FilePath &fileName) { - return m_mainCppFiles.contains(fileName) ? m_mainCppFiles.value(fileName) : FilePath(); + QReadLocker lock(&m_parseLock); + return m_mainCppFiles.value(fileName); } } // namespace Autotest::Internal diff --git a/src/plugins/autotest/quick/quicktestparser.h b/src/plugins/autotest/quick/quicktestparser.h index e93fa9605f4..c22e1d480e1 100644 --- a/src/plugins/autotest/quick/quicktestparser.h +++ b/src/plugins/autotest/quick/quicktestparser.h @@ -8,6 +8,7 @@ #include #include +#include namespace Autotest { namespace Internal { @@ -28,7 +29,7 @@ public: void release() override; bool processDocument(QPromise &promise, const Utils::FilePath &fileName) override; - Utils::FilePath projectFileForMainCppFile(const Utils::FilePath &fileName) const; + Utils::FilePath projectFileForMainCppFile(const Utils::FilePath &fileName); QStringList supportedExtensions() const override { return {"qml"}; }; private: @@ -45,6 +46,7 @@ private: QMap > m_watchedFiles; QMap m_mainCppFiles; QSet m_prefilteredFiles; + QReadWriteLock m_parseLock; // guard for m_mainCppFiles bool m_checkForDerivedTests = false; }; From 8f741e3ab0ca7d8eb38db6a2e2848e9a7147f723 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 26 Sep 2023 11:07:16 +0200 Subject: [PATCH 06/21] AutoTest: Split and re-organize wizard Split off the single wizard into separate wizards listed inside a separate projects category. Simplifies maintenance and additional handling to be added in a later patch. Minor drive-by fixes for the cmake based projects. Change-Id: I4f2f83c3892ea9a0f31691770a927294922c382d Reviewed-by: David Schulz --- .../autotest/{ => boosttest}/wizard.json | 168 ++------------- .../wizards/autotest/catch/wizard.json | 184 +++++++++++++++++ .../templates/wizards/autotest/files/tst.txt | 5 +- .../wizards/autotest/gtest/wizard.json | 192 ++++++++++++++++++ .../wizards/autotest/qttest/wizard.json | 180 ++++++++++++++++ .../wizards/autotest/quicktest/wizard.json | 192 ++++++++++++++++++ 6 files changed, 765 insertions(+), 156 deletions(-) rename share/qtcreator/templates/wizards/autotest/{ => boosttest}/wizard.json (50%) create mode 100644 share/qtcreator/templates/wizards/autotest/catch/wizard.json create mode 100644 share/qtcreator/templates/wizards/autotest/gtest/wizard.json create mode 100644 share/qtcreator/templates/wizards/autotest/qttest/wizard.json create mode 100644 share/qtcreator/templates/wizards/autotest/quicktest/wizard.json diff --git a/share/qtcreator/templates/wizards/autotest/wizard.json b/share/qtcreator/templates/wizards/autotest/boosttest/wizard.json similarity index 50% rename from share/qtcreator/templates/wizards/autotest/wizard.json rename to share/qtcreator/templates/wizards/autotest/boosttest/wizard.json index 71dd8de4b7a..4b674389bc4 100644 --- a/share/qtcreator/templates/wizards/autotest/wizard.json +++ b/share/qtcreator/templates/wizards/autotest/boosttest/wizard.json @@ -1,12 +1,12 @@ { "version": 1, "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], - "id": "R.AutoTest", - "category": "H.Project", - "trDescription": "Creates a new unit test project. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", - "trDisplayName": "Auto Test Project", - "trDisplayCategory": "Other Project", - "icon": "autotest.png", + "id": "M.BoostAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Boost. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Boost Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", "iconKind": "Themed", "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", @@ -33,10 +33,6 @@ { "key": "MainCppName", "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" }, - { - "key": "TestCaseFileGTestWithCppSuffix", - "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" - }, { "key": "GUARD", "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" @@ -44,10 +40,6 @@ { "key": "TestCaseFileWithCppSuffix", "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" - }, - { - "key": "TestCaseFileWithQmlSuffix", - "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.qml' }" } ], @@ -59,7 +51,7 @@ "typeId": "Project", "data": { - "trDescription": "This wizard creates a simple unit test project." + "trDescription": "This wizard creates a simple unit test project using Boost." } }, { @@ -77,18 +69,6 @@ "index": 0, "items": [ - { - "trKey": "Qt Test", - "value": "QtTest" - }, - { - "trKey": "Google Test", - "value": "GTest" - }, - { - "trKey": "Qt Quick Test", - "value": "QtQuickTest" - }, { "trKey": "Boost Test (header only)", "value": "BoostTest" @@ -96,28 +76,13 @@ { "trKey": "Boost Test (shared libraries)", "value": "BoostTest_dyn" - }, - { - "trKey": "Catch2", - "value": "Catch2" } - ] } }, - { - "name": "RequireGUI", - "trDisplayName": "GUI Application", - "visible": "%{JS: value('TestFrameWork') === 'QtTest'}", - "type": "CheckBox", - "data": { - "checked": false - } - }, { "name": "TestSuiteName", "trDisplayName": "Test suite name:", - "visible": "%{JS: ['BoostTest', 'BoostTest_dyn', 'GTest'].indexOf(value('TestFrameWork')) >= 0}", "mandatory": true, "type": "LineEdit", "data": { "validator": "^[a-zA-Z_0-9]+$" } @@ -129,43 +94,6 @@ "type": "LineEdit", "data": { "validator": "^[a-zA-Z_0-9]+$" } }, - { - "name": "RequireApplication", - "trDisplayName": "Requires QApplication", - "visible": "%{JS: value('TestFrameWork') === 'QtTest'}", - "type": "CheckBox", - "data": { - "checked": false - } - }, - { - "name": "UseSetupCode", - "trDisplayName": "Generate setup code", - "visible": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "type": "CheckBox", - "data": { - "checked": false - } - }, - { - "name": "GenerateInitAndCleanup", - "trDisplayName": "Generate initialization and cleanup code", - "visible": "%{JS: [ 'QtTest', 'QtQuickTest' ].indexOf(value('TestFrameWork')) >= 0 }", - "type": "CheckBox", - "data": { - "checked": false - } - }, - { - "name": "GTestRepository", - "trDisplayName": "Googletest source directory (optional):", - "visible": "%{JS: value('TestFrameWork') === 'GTest'}", - "mandatory": false, - "type": "PathChooser", - "data": { - "kind": "existingDirectory" - } - }, { "name": "BoostIncDir", "trDisplayName": "Boost include directory (optional):", @@ -186,25 +114,6 @@ "kind": "existingDirectory" } }, - { - "name": "CatchIncDir", - "trDisplayName": "Catch2 include directory (optional):", - "visible": "%{JS: value('TestFrameWork') === 'Catch2'}", - "mandatory": false, - "type": "PathChooser", - "data": { - "kind": "existingDirectory" - } - }, - { - "name": "Catch2NeedsQt", - "trDisplayName": "Use Qt libraries", - "visible": "%{JS: '%{TestFrameWork}' === 'Catch2'}", - "type": "CheckBox", - "data": { - "checked": true - } - }, { "name": "BuildSystem", "trDisplayName": "Build system:", @@ -242,7 +151,7 @@ "enabled": "%{IsTopLevelProject}", "data": { "projectFilePath": "%{ProjectFilePath}", - "requiredFeatures": [ "%{JS: (value('TestFrameWork') === 'QtQuickTest' ? 'QtSupport.Wizards.FeatureQtQuick.2' : ((value('BuildSystem') === 'qmake' || value('TestFrameWork') === 'QtTest') ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' )) }" ] + "requiredFeatures": [ "%{JS: value('BuildSystem') === 'qmake' ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' }" ] } }, { @@ -258,87 +167,38 @@ "data": [ { - "source": "files/gtest_dependency.pri", - "target": "gtest_dependency.pri", - "condition": "%{JS: value('TestFrameWork') == 'GTest' && value('BuildSystem') == 'qmake'}", - "openInEditor": false - }, - { - "source": "files/googlecommon.js", - "target": "googlecommon.js", - "condition": "%{JS: value('TestFrameWork') == 'GTest' && value('BuildSystem') == 'qbs'}", - "openInEditor": false - }, - { - "source": "files/tst.pro", + "source": "../files/tst.pro", "target": "%{ProjectFilePath}", "condition": "%{JS: value('BuildSystem') == 'qmake'}", "openInEditor": false, "openAsProject": true }, { - "source": "files/tst.qbs", + "source": "../files/tst.qbs", "target": "%{ProjectFilePath}", "condition": "%{JS: value('BuildSystem') == 'qbs'}", "openInEditor": false, "openAsProject": true }, { - "source": "files/tst.txt", + "source": "../files/tst.txt", "target": "CMakeLists.txt", "condition": "%{JS: value('BuildSystem') == 'cmake'}", "openInEditor": false, "openAsProject": true }, { - "source": "files/tst_src_gt.cpp", - "target": "%{TestCaseFileGTestWithCppSuffix}", - "condition": "%{JS: value('TestFrameWork') == 'GTest'}", - "openInEditor": true - }, - { - "source": "files/tst_src.cpp", - "target": "%{TestCaseFileWithCppSuffix}", - "condition": "%{JS: value('TestFrameWork') == 'QtTest'}", - "openInEditor": true - }, - { - "source": "files/tst_main.cpp", + "source": "../files/tst_main.cpp", "target": "%{MainCppName}", - "condition": "%{JS: ['GTest', 'QtQuickTest', 'BoostTest', 'BoostTest_dyn', 'Catch2'].indexOf(value('TestFrameWork')) >= 0}", "openInEditor": true }, { - "source": "files/tst_src_boost.cpp", + "source": "../files/tst_src_boost.cpp", "target": "%{TestCaseFileWithCppSuffix}", "condition": "%{JS: value('TestFrameWork') === 'BoostTest_dyn'}" }, { - "source": "files/tst_qml.tmpl", - "target": "%{TestCaseFileWithQmlSuffix}", - "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "openInEditor": true - }, - { - "source": "files/setup.cpp", - "target": "setup.cpp", - "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "openInEditor": true - }, - { - "source": "files/setup.h", - "target": "setup.h", - "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "openInEditor": true - }, - { - "source": "files/catch2_tst.cpp", - "target": "%{TestCaseFileWithCppSuffix}", - "condition": "%{JS: '%{TestFrameWork}' === 'Catch2'}", - "openInEditor": true - }, - { - "source": "../projects/git.ignore", + "source": "../../projects/git.ignore", "target": ".gitignore", "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" } diff --git a/share/qtcreator/templates/wizards/autotest/catch/wizard.json b/share/qtcreator/templates/wizards/autotest/catch/wizard.json new file mode 100644 index 00000000000..d9ca160459a --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/catch/wizard.json @@ -0,0 +1,184 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "R.CatchAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Catch2. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Catch2 Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "Catch2" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { "key": "MainCppName", + "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + }, + { + "key": "TestCaseFileWithCppSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Catch2." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "CatchIncDir", + "trDisplayName": "Catch2 include directory (optional):", + "visible": "%{JS: value('TestFrameWork') === 'Catch2'}", + "mandatory": false, + "type": "PathChooser", + "data": { + "kind": "existingDirectory" + } + }, + { + "name": "Catch2NeedsQt", + "trDisplayName": "Use Qt libraries", + "visible": "%{JS: '%{TestFrameWork}' === 'Catch2'}", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "%{JS: (value('Catch2NeedsQt') || value('BuildSystem') === 'qmake') ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' }" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_main.cpp", + "target": "%{MainCppName}", + "openInEditor": true + }, + { + "source": "../files/catch2_tst.cpp", + "target": "%{TestCaseFileWithCppSuffix}", + "condition": "%{JS: '%{TestFrameWork}' === 'Catch2'}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/autotest/files/tst.txt b/share/qtcreator/templates/wizards/autotest/files/tst.txt index 5e486318ce5..f451cd1ba75 100644 --- a/share/qtcreator/templates/wizards/autotest/files/tst.txt +++ b/share/qtcreator/templates/wizards/autotest/files/tst.txt @@ -150,7 +150,7 @@ find_package(Boost COMPONENTS unit_test_framework REQUIRED) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_executable(%{TestCaseName} %{MainCppName} %{TestCaseFileGTestWithCppSuffix}) +add_executable(%{TestCaseName} %{MainCppName} %{TestCaseFileWithCppSuffix}) add_test(NAME %{TestCaseName} COMMAND %{TestCaseName}) if (Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) @@ -165,7 +165,8 @@ find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Gui) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui) @endif -add_executable(${PROJECT_NAME} %{TestCaseFileWithCppSuffix} main.cpp) +add_executable(%{TestCaseName} %{TestCaseFileWithCppSuffix} main.cpp) +add_test(NAME %{TestCaseName} COMMAND %{TestCaseName}) @if "%{Catch2NeedsQt}" == "true" target_link_libraries(%{TestCaseName} PRIVATE Qt${QT_VERSION_MAJOR}::Gui) diff --git a/share/qtcreator/templates/wizards/autotest/gtest/wizard.json b/share/qtcreator/templates/wizards/autotest/gtest/wizard.json new file mode 100644 index 00000000000..5e79ab5539a --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/gtest/wizard.json @@ -0,0 +1,192 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "G.AutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Google Test. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Google Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "GTest" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { "key": "MainCppName", + "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "TestCaseFileGTestWithCppSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Google Test." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestSuiteName", + "trDisplayName": "Test suite name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "GTestRepository", + "trDisplayName": "Googletest source directory (optional):", + "mandatory": false, + "type": "PathChooser", + "data": { + "kind": "existingDirectory" + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "%{JS: value('BuildSystem') === 'qmake' ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' }" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/gtest_dependency.pri", + "target": "gtest_dependency.pri", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false + }, + { + "source": "../files/googlecommon.js", + "target": "googlecommon.js", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false + }, + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_src_gt.cpp", + "target": "%{TestCaseFileGTestWithCppSuffix}", + "openInEditor": true + }, + { + "source": "../files/tst_main.cpp", + "target": "%{MainCppName}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/autotest/qttest/wizard.json b/share/qtcreator/templates/wizards/autotest/qttest/wizard.json new file mode 100644 index 00000000000..75de005b3ad --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/qttest/wizard.json @@ -0,0 +1,180 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "A.QtTestAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Qt Test. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Qt Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "QtTest" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + }, + { + "key": "TestCaseFileWithCppSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Qt Test." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "RequireApplication", + "trDisplayName": "Requires QApplication", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "RequireGUI", + "trDisplayName": "GUI Application", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "GenerateInitAndCleanup", + "trDisplayName": "Generate initialization and cleanup code", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "QtSupport.Wizards.FeatureQt", "QtSupport.Wizards.FeatureDesktop" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_src.cpp", + "target": "%{TestCaseFileWithCppSuffix}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/autotest/quicktest/wizard.json b/share/qtcreator/templates/wizards/autotest/quicktest/wizard.json new file mode 100644 index 00000000000..e23fca42d98 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/quicktest/wizard.json @@ -0,0 +1,192 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "C.QuickAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Qt Quick Test. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Qt Quick Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "QtQuickTest" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { "key": "MainCppName", + "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + }, + { + "key": "TestCaseFileWithQmlSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.qml' }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Qt Quick Test." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "UseSetupCode", + "trDisplayName": "Generate setup code", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "GenerateInitAndCleanup", + "trDisplayName": "Generate initialization and cleanup code", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "QtSupport.Wizards.FeatureQtQuick.2", "QtSupport.Wizards.FeatureDesktop" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_main.cpp", + "target": "%{MainCppName}", + "openInEditor": true + }, + { + "source": "../files/tst_qml.tmpl", + "target": "%{TestCaseFileWithQmlSuffix}", + "openInEditor": true + }, + { + "source": "../files/setup.cpp", + "target": "setup.cpp", + "condition": "%{JS: value('UseSetupCode')}", + "openInEditor": true + }, + { + "source": "../files/setup.h", + "target": "setup.h", + "condition": "%{JS: value('UseSetupCode')}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} From 3e0f7773b83b096ed96a97c5f4f56f0c4a1c7b0f Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 27 Sep 2023 12:11:00 +0200 Subject: [PATCH 07/21] Autotest: Ensure m_checkStateCache isn't nullptr Fixes: QTCREATORBUG-29654 Change-Id: Ia1864756a232663c4109fdae61de1dcaf1fbcd14 Reviewed-by: Christian Stenger --- src/plugins/autotest/testtreemodel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/autotest/testtreemodel.cpp b/src/plugins/autotest/testtreemodel.cpp index fb06b40a07b..85b8156ff39 100644 --- a/src/plugins/autotest/testtreemodel.cpp +++ b/src/plugins/autotest/testtreemodel.cpp @@ -611,7 +611,8 @@ void TestTreeModel::insertItemInParent(TestTreeItem *item, TestTreeItem *root, b delete item; } else { // restore former check state if available - std::optional cached = m_checkStateCache->get(item); + std::optional cached = m_checkStateCache ? m_checkStateCache->get(item) + : std::optional{}; if (cached.has_value()) item->setData(0, cached.value(), Qt::CheckStateRole); else From cbc6809b28a0a0aaa3e8399601ce5275184443ed Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Wed, 27 Sep 2023 09:52:20 +0200 Subject: [PATCH 08/21] SSH: Fix Askpass on Windows 11 The standard openssh on windows 11 will not use the "askpass" unless SSH_ASKPASS_REQUIRE is set to "force". see: https://github.com/PowerShell/Win32-OpenSSH/issues/2115 Change-Id: I96a32e333a39e0cf5f5dab4c0f9c201b20daf533 Reviewed-by: Christian Kandeler --- src/plugins/projectexplorer/devicesupport/sshparameters.cpp | 1 + src/plugins/vcsbase/vcsbaseplugin.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp index 744e350eaef..22055cf7946 100644 --- a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp +++ b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp @@ -77,6 +77,7 @@ bool SshParameters::setupSshEnvironment(Process *process) const bool hasDisplay = env.hasKey("DISPLAY") && (env.value("DISPLAY") != QString(":0")); if (SshSettings::askpassFilePath().exists()) { env.set("SSH_ASKPASS", SshSettings::askpassFilePath().toUserOutput()); + env.set("SSH_ASKPASS_REQUIRE", "force"); // OpenSSH only uses the askpass program if DISPLAY is set, regardless of the platform. if (!env.hasKey("DISPLAY")) diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp index 6880e42e5a2..29a25e1627b 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.cpp +++ b/src/plugins/vcsbase/vcsbaseplugin.cpp @@ -751,8 +751,10 @@ FilePath source(IDocument *document) void setProcessEnvironment(Environment *e) { const QString prompt = Internal::commonSettings().sshPasswordPrompt().path(); - if (!prompt.isEmpty()) + if (!prompt.isEmpty()) { e->set("SSH_ASKPASS", prompt); + e->set("SSH_ASKPASS_REQUIRE", "force"); + } } } // namespace VcsBase From 4e90ca1b59a3ed3e4abbac81d47d752de569cd60 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 27 Sep 2023 13:01:18 +0200 Subject: [PATCH 09/21] CMakeToolManager: Add static readFirstParagraphs() Reuse it in CMakeEditor and CMakeFileCompletionAssistProvider. Amends d04585b519f51b47cfc4200a048acc416c187b86 Change-Id: I8dbd59c815e017404ff215ca1ff68740d6653e91 Reviewed-by: Cristian Adam --- src/plugins/cmakeprojectmanager/cmakeeditor.cpp | 5 +---- .../cmakefilecompletionassist.cpp | 16 +--------------- .../cmakeprojectmanager/cmaketoolmanager.cpp | 15 +++++++++++++++ .../cmakeprojectmanager/cmaketoolmanager.h | 2 ++ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index 1f1e873be52..9b363faebe3 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -9,7 +9,6 @@ #include "cmakefilecompletionassist.h" #include "cmakeindenter.h" #include "cmakekitaspect.h" -#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "3rdparty/cmake/cmListFileCache.h" @@ -347,8 +346,6 @@ const CMakeKeywords &CMakeHoverHandler::keywords() const return m_keywords; } -QString readFirstParagraphs(const QString &element, const FilePath &helpFile); - void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report) @@ -377,7 +374,7 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget } m_helpToolTip.clear(); if (!helpFile.isEmpty()) - m_helpToolTip = readFirstParagraphs(word, helpFile); + m_helpToolTip = CMakeToolManager::readFirstParagraphs(helpFile); setPriority(m_helpToolTip.isEmpty() ? Priority_Tooltip : Priority_None); } diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index 62cc290aa8d..f8d7bb7a3b1 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -153,20 +153,6 @@ static QList generateList(const T &words, const Q }); } -static QString readFirstParagraphs(const FilePath &helpFile) -{ - static QMap map; - if (map.contains(helpFile)) - return map.value(helpFile); - - auto content = helpFile.fileContents(1024).value_or(QByteArray()); - const QString firstParagraphs - = QString("```\n%1\n```").arg(QString::fromUtf8(content.left(content.lastIndexOf("\n")))); - - map[helpFile] = firstParagraphs; - return firstParagraphs; -} - static QList generateList(const QMap &words, const QIcon &icon) { @@ -180,7 +166,7 @@ static QList generateList(const QMapsetText(it.key()); if (!it.value().isEmpty()) - item->setDetail(readFirstParagraphs(it.value())); + item->setDetail(CMakeToolManager::readFirstParagraphs(it.value())); item->setIcon(icon); list << item; }; diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp index d8978e3bc61..da2bcff6e05 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp @@ -167,6 +167,21 @@ void CMakeToolManager::updateDocumentation() Core::HelpManager::registerDocumentation(docs); } +QString CMakeToolManager::readFirstParagraphs(const FilePath &helpFile) +{ + static QMap map; + if (map.contains(helpFile)) + return map.value(helpFile); + + auto content = helpFile.fileContents(1024).value_or(QByteArray()); + const QString firstParagraphs + = QString("```\n%1\n```").arg(QString::fromUtf8(content.left(content.lastIndexOf("\n")))); + + map[helpFile] = firstParagraphs; + return firstParagraphs; +} + + QList CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths, const QString &detectionSource, QString *logMessage) diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h index 1b5ee74c8f1..85dbcb53b9e 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h @@ -40,6 +40,8 @@ public: static void updateDocumentation(); + static QString readFirstParagraphs(const Utils::FilePath &helpFile); + public slots: QList autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths, const QString &detectionSource, From 2c298097e9a117835f1e10dfd3cf5ea8459f2cf8 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 20 Sep 2023 14:28:21 +0200 Subject: [PATCH 10/21] Python: detect pythons async Change-Id: I74484a4f2c33c4fd7754f87bfbf3b9d711542741 Reviewed-by: Christian Stenger --- src/plugins/python/pythonsettings.cpp | 84 ++++++++++++++++++++------- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp index 020aad52259..4aa8ef6181f 100644 --- a/src/plugins/python/pythonsettings.cpp +++ b/src/plugins/python/pythonsettings.cpp @@ -21,13 +21,14 @@ #include #include -#include +#include #include #include -#include #include +#include #include #include +#include #include #include @@ -366,14 +367,6 @@ private: InterpreterOptionsWidget *m_widget = nullptr; }; -static bool alreadyRegistered(const QList &pythons, const FilePath &pythonExecutable) -{ - return Utils::anyOf(pythons, [pythonExecutable](const Interpreter &interpreter) { - return interpreter.command.toFileInfo().canonicalFilePath() - == pythonExecutable.toFileInfo().canonicalFilePath(); - }); -} - static InterpreterOptionsPage &interpreterOptionsPage() { static InterpreterOptionsPage page; @@ -626,8 +619,9 @@ static void disableOutdatedPyls() } } -static void addPythonsFromRegistry(QList &pythons) +static QList pythonsFromRegistry() { + QList pythons; QSettings pythonRegistry("HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore", QSettings::NativeFormat); for (const QString &versionGroup : pythonRegistry.childGroups()) { @@ -636,7 +630,7 @@ static void addPythonsFromRegistry(QList &pythons) QVariant regVal = pythonRegistry.value("InstallPath/ExecutablePath"); if (regVal.isValid()) { const FilePath &executable = FilePath::fromUserInput(regVal.toString()); - if (executable.exists() && !alreadyRegistered(pythons, executable)) { + if (executable.exists()) { pythons << Interpreter{QUuid::createUuid().toString(), name, FilePath::fromUserInput(regVal.toString())}; @@ -645,7 +639,7 @@ static void addPythonsFromRegistry(QList &pythons) regVal = pythonRegistry.value("InstallPath/WindowedExecutablePath"); if (regVal.isValid()) { const FilePath &executable = FilePath::fromUserInput(regVal.toString()); - if (executable.exists() && !alreadyRegistered(pythons, executable)) { + if (executable.exists()) { pythons << Interpreter{QUuid::createUuid().toString(), //: (Windowed) Tr::tr("%1 (Windowed)").arg(name), @@ -656,28 +650,30 @@ static void addPythonsFromRegistry(QList &pythons) if (regVal.isValid()) { const FilePath &path = FilePath::fromUserInput(regVal.toString()); const FilePath python = path.pathAppended("python").withExecutableSuffix(); - if (python.exists() && !alreadyRegistered(pythons, python)) + if (python.exists()) pythons << createInterpreter(python, "Python " + versionGroup); const FilePath pythonw = path.pathAppended("pythonw").withExecutableSuffix(); - if (pythonw.exists() && !alreadyRegistered(pythons, pythonw)) + if (pythonw.exists()) pythons << createInterpreter(pythonw, "Python " + versionGroup, "(Windowed)"); } pythonRegistry.endGroup(); } + return pythons; } -static void addPythonsFromPath(QList &pythons) +static QList pythonsFromPath() { + QList pythons; if (HostOsInfo::isWindowsHost()) { for (const FilePath &executable : FilePath("python").searchAllInPath()) { // Windows creates empty redirector files that may interfere if (executable.toFileInfo().size() == 0) continue; - if (executable.exists() && !alreadyRegistered(pythons, executable)) + if (executable.exists()) pythons << createInterpreter(executable, "Python from Path"); } for (const FilePath &executable : FilePath("pythonw").searchAllInPath()) { - if (executable.exists() && !alreadyRegistered(pythons, executable)) + if (executable.exists()) pythons << createInterpreter(executable, "Python from Path", "(Windowed)"); } } else { @@ -690,11 +686,12 @@ static void addPythonsFromPath(QList &pythons) const QDir dir(path.toString()); for (const QFileInfo &fi : dir.entryInfoList(filters)) { const FilePath executable = Utils::FilePath::fromFileInfo(fi); - if (executable.exists() && !alreadyRegistered(pythons, executable)) + if (executable.exists()) pythons << createInterpreter(executable, "Python from Path"); } } } + return pythons; } static QString idForPythonFromPath(const QList &pythons) @@ -713,6 +710,51 @@ static QString idForPythonFromPath(const QList &pythons) static PythonSettings *settingsInstance = nullptr; +static bool alreadyRegistered(const Interpreter &candidate) +{ + return Utils::anyOf(settingsInstance->interpreters(), + [candidate = candidate.command](const Interpreter &interpreter) { + return interpreter.command.isSameDevice(candidate) + && interpreter.command.resolveSymlinks() + == candidate.resolveSymlinks(); + }); +} + +static void scanPath() +{ + auto watcher = new QFutureWatcher>(); + QObject::connect(watcher, &QFutureWatcher>::finished, [watcher]() { + for (const Interpreter &interpreter : watcher->result()) { + if (!alreadyRegistered(interpreter)) + settingsInstance->addInterpreter(interpreter); + } + watcher->deleteLater(); + }); + watcher->setFuture(Utils::asyncRun(pythonsFromPath)); +} + +static void scanRegistry() +{ + auto watcher = new QFutureWatcher>(); + QObject::connect(watcher, &QFutureWatcher>::finished, [watcher]() { + for (const Interpreter &interpreter : watcher->result()) { + if (!alreadyRegistered(interpreter)) + settingsInstance->addInterpreter(interpreter); + } + watcher->deleteLater(); + scanPath(); + }); + watcher->setFuture(Utils::asyncRun(pythonsFromRegistry)); +} + +static void scanSystemForInterpreters() +{ + if (Utils::HostOsInfo::isWindowsHost()) + scanRegistry(); + else + scanPath(); +} + PythonSettings::PythonSettings() { QTC_ASSERT(!settingsInstance, return); @@ -723,9 +765,7 @@ PythonSettings::PythonSettings() initFromSettings(Core::ICore::settings()); - if (HostOsInfo::isWindowsHost()) - addPythonsFromRegistry(m_interpreters); - addPythonsFromPath(m_interpreters); + scanSystemForInterpreters(); if (m_defaultInterpreterId.isEmpty()) m_defaultInterpreterId = idForPythonFromPath(m_interpreters); From 4924e5afec12c1b0aa100b79e5a5830e5c15a8af Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Wed, 27 Sep 2023 13:07:16 +0200 Subject: [PATCH 11/21] CMakeProjectManager: Fix warnings Change-Id: I190646684a1cadbcc1a5906642ed48e9dd84ed88 Reviewed-by: Cristian Adam --- .../cmakeprojectmanager/cmakefilecompletionassist.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index f8d7bb7a3b1..adf223e9a9b 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -158,7 +158,7 @@ static QList generateList(const QMap list; @@ -169,7 +169,7 @@ static QList generateList(const QMapsetDetail(CMakeToolManager::readFirstParagraphs(it.value())); item->setIcon(icon); list << item; - }; + } return list; } @@ -189,7 +189,7 @@ static int addFilePathItems(const AssistInterface *interface, const QString word = interface->textAt(startPos, interface->position() - startPos); FilePath baseDir = interface->filePath().absoluteFilePath().parentDir(); - const int lastSlashPos = word.lastIndexOf(QLatin1Char('/')); + const qsizetype lastSlashPos = word.lastIndexOf(QLatin1Char('/')); QString prefix = word; if (lastSlashPos != -1) { @@ -213,7 +213,7 @@ static int addFilePathItems(const AssistInterface *interface, return startPos; } -QPair getLocalFunctionsAndVariables(const QByteArray &content) +static QPair getLocalFunctionsAndVariables(const QByteArray &content) { cmListFile cmakeListFile; std::string errorString; From 94d7c76d674d90a680ed334f8b6601a77cd7478b Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Mon, 25 Sep 2023 23:07:02 +0200 Subject: [PATCH 12/21] CMakePM: Add missing features to RSTParser To be able to parse the rst help files from CMake Change-Id: Ibec21e8571324276d2080f81728b1268581601d0 Reviewed-by: Alessandro Portale --- .../3rdparty/rstparser/rstparser-test.cc | 89 ++++++++- .../3rdparty/rstparser/rstparser.cc | 185 +++++++++++++++++- .../3rdparty/rstparser/rstparser.h | 19 +- 3 files changed, 279 insertions(+), 14 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc index c70fc67c6da..70b9a6df337 100644 --- a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc +++ b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc @@ -48,6 +48,27 @@ class TestHandler : public rst::ContentHandler { void StartBlock(rst::BlockType type) { std::string tag; switch (type) { + case rst::REFERENCE_LINK: + // not used, HandleReferenceLink is used instead + break; + case rst::H1: + tag = "h1"; + break; + case rst::H2: + tag = "h2"; + break; + case rst::H3: + tag = "h3"; + break; + case rst::H4: + tag = "h4"; + break; + case rst::H5: + tag = "h5"; + break; + case rst::CODE: + tag = "code"; + break; case rst::PARAGRAPH: tag = "p"; break; @@ -80,8 +101,12 @@ class TestHandler : public rst::ContentHandler { content_.append(text, size); } - void HandleDirective(const char *type) { - content_ += std::string("<") + type + " />"; + void HandleDirective(const std::string &type, const std::string &name) { + content_ += std::string("
" + type + "
"; + } + + void HandleReferenceLink(const std::string &type, const std::string &text) { + content_ += std::string("" + text + ""; } }; @@ -93,6 +118,14 @@ std::string Parse(const char *s) { } } +TEST(ParserTest, HX) { + EXPECT_EQ("

test

", Parse("====\ntest\n====")); + EXPECT_EQ("

test

", Parse("test\n====")); + EXPECT_EQ("

test

", Parse("test\n----")); + EXPECT_EQ("

test

", Parse("test\n^^^^")); + EXPECT_EQ("
test
", Parse("test\n\"\"\"\"")); +} + TEST(ParserTest, Paragraph) { EXPECT_EQ("

test

", Parse("test")); EXPECT_EQ("

test

", Parse("\ntest")); @@ -143,6 +176,14 @@ TEST(ParserTest, Literal) { EXPECT_EQ("

::\nabc\ndef

", Parse("::\nabc\ndef")); } +TEST(ParserTest, InlineCode) { + EXPECT_EQ("

code

", Parse("``code``")); + EXPECT_EQ("

`code``

", Parse("`code``")); + EXPECT_EQ("

some code

", Parse("some ``code``")); + EXPECT_EQ("

code some

", Parse("``code`` some")); + EXPECT_EQ("

some code and more

", Parse("some ``code`` and more")); +} + TEST(ParserTest, Comment) { EXPECT_EQ("", Parse("..")); EXPECT_EQ("", Parse("..\n")); @@ -151,11 +192,49 @@ TEST(ParserTest, Comment) { } TEST(ParserTest, Directive) { - EXPECT_EQ("", Parse(".. test::")); - EXPECT_EQ("", Parse(".. test::")); - EXPECT_EQ("", Parse("..\ttest::")); + EXPECT_EQ("
test
", Parse(".. test::")); + EXPECT_EQ("
test
", Parse(".. test:: name")); + EXPECT_EQ("
test
", Parse(".. test::")); + EXPECT_EQ("
test
", Parse("..\ttest::")); + + EXPECT_EQ("
|from-text| replace
", Parse(".. |from-text| replace:: to-text")); + + std::string rst = +R"(.. code-block:: c++ + int main() { + if (false) + return 1; + return 0; + })"; + + std::string html = +R"(
code-block
int main() { + if (false) + return 1; + return 0; +}
)"; + + EXPECT_EQ(html, Parse(rst.c_str())); + + rst = +R"(.. note:: This is a cool + note. Such a cool note.)"; + + html = +R"(
note
This is a cool + note. Such a cool note.
)"; + + EXPECT_EQ(html, Parse(rst.c_str())); } +TEST(ParserTest, ReferenceLinks) { + EXPECT_EQ("

info

", Parse(":ref:`info`")); + EXPECT_EQ("

some info

", Parse("some :ref:`info`")); + EXPECT_EQ("

some info and more

", Parse("some :ref:`info` and more")); + EXPECT_EQ("

info.

", Parse(":ref:`info`.")); +} + + int main(int argc, char **argv) { #ifdef _WIN32 // Disable message boxes on assertion failures. diff --git a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc index 528c572f683..f430c40e95d 100644 --- a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc +++ b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc @@ -27,6 +27,7 @@ #include "rstparser.h" +#include #include #include @@ -55,15 +56,15 @@ void rst::Parser::SkipSpace() { std::string rst::Parser::ParseDirectiveType() { const char *s = ptr_; - if (!std::isalnum(*s)) + if (!std::isalnum(*s) && *s != '|') return std::string(); for (;;) { ++s; if (std::isalnum(*s)) continue; switch (*s) { - case '-': case '_': case '+': case ':': case '.': - if (std::isalnum(s[1])) { + case '-': case '_': case '+': case ':': case '.': case '|': + if (std::isalnum(s[1]) || (*s == '|' && IsSpace(s[1]))) { ++s; continue; } @@ -91,13 +92,28 @@ void rst::Parser::EnterBlock(rst::BlockType &prev_type, rst::BlockType type) { void rst::Parser::ParseBlock( rst::BlockType type, rst::BlockType &prev_type, int indent) { std::string text; + + struct InlineTags { + rst::BlockType type; + std::size_t pos {}; + std::string text; + std::string type_string; + }; + std::vector inline_tags; + + bool have_h1 = false; for (bool first = true; ; first = false) { const char *line_start = ptr_; if (!first) { // Check indentation. SkipSpace(); - if (ptr_ - line_start != indent) + const int new_indent = ptr_ - line_start; + if (new_indent < indent) break; + // Restore the indent + if (new_indent > indent) + std::advance(ptr_, indent - new_indent); + if (*ptr_ == '\n') { ++ptr_; break; // Empty line ends the block. @@ -119,9 +135,17 @@ void rst::Parser::ParseBlock( // Copy text converting all whitespace characters to spaces. text.reserve(end - line_start + 1); - if (!first) + if (!first && !have_h1) text.push_back('\n'); enum {TAB_WIDTH = 8}; + + // Used the sections mapping from https://docs.anaconda.com/restructuredtext/index.html + struct { + BlockType type; + int count = 0; + char c = 0; + } hx[] = { {H1, 0, '=' }, {H2, 0, '='}, {H3, 0, '-'}, {H4, 0, '^'}, {H5, 0, '\"'}}; + for (const char *s = line_start; s != end; ++s) { char c = *s; if (c == '\t') { @@ -129,10 +153,60 @@ void rst::Parser::ParseBlock( TAB_WIDTH - ((indent + s - line_start) % TAB_WIDTH)); } else if (IsSpace(c)) { text.push_back(' '); + } else if (c == hx[0].c) { + ++hx[0].count; + ++hx[1].count; + } else if (c == hx[2].c) { + ++hx[2].count; + } else if (c == hx[3].c) { + ++hx[3].count; + } else if (c == hx[4].c) { + ++hx[4].count; + } else if (c == '`') { + std::string code_tag_text; + if (ParseCode(s, end - s, code_tag_text)) { + InlineTags code; + code.type = rst::CODE; + code.pos = text.size(); + code.text = code_tag_text; + inline_tags.push_back(code); + const int tag_size = 4; + s = s + code_tag_text.size() + tag_size - 1; + } else { + text.push_back(*s); + } + } else if (c == ':') { + std::string link_type; + std::string link_text; + if (ParseReferenceLink(s, end - s, link_type, link_text)) { + InlineTags link; + link.type = rst::REFERENCE_LINK; + link.pos = text.size(); + link.text = link_text; + link.type_string = link_type; + inline_tags.push_back(link); + const int tag_size = 4; + s = s + link_type.size() + link_text.size() + tag_size - 1; + } else { + text.push_back(*s); + } } else { text.push_back(*s); } } + + for (int i = 0; i < 5; ++i) { + if (hx[i].count > 0 && hx[i].count == end - line_start) { + // h1 and h2 have the same underline character + // only if there was one ontop then is h1 otherwise h2 + if (i == 0 && first) + have_h1 = true; + if ((i == 0 && !have_h1) || (i == 1 && have_h1)) + continue; + type = hx[i].type; + } + } + if (*ptr_ == '\n') ++ptr_; } @@ -144,11 +218,35 @@ void rst::Parser::ParseBlock( bool literal = type == PARAGRAPH && EndsWith(text, "::"); if (!literal || text.size() != 2) { std::size_t size = text.size(); + if (size == 0 && inline_tags.size() == 0) + return; + if (literal) --size; EnterBlock(prev_type, type); handler_->StartBlock(type); - handler_->HandleText(text.c_str(), size); + + if (inline_tags.size() == 0) { + handler_->HandleText(text.c_str(), size); + } else { + std::size_t start = 0; + for (const InlineTags &in : inline_tags) { + if (in.pos > start) + handler_->HandleText(text.c_str() + start, in.pos - start); + if (in.type == rst::REFERENCE_LINK) { + handler_->HandleReferenceLink(in.type_string, in.text); + } else { + handler_->StartBlock(in.type); + handler_->HandleText(in.text.c_str(), in.text.size()); + handler_->EndBlock(); + } + start = in.pos; + } + + if (start < size) + handler_->HandleText(text.c_str() + start, size - start); + } + handler_->EndBlock(); } if (literal) { @@ -191,6 +289,58 @@ void rst::Parser::ParseLineBlock(rst::BlockType &prev_type, int indent) { handler_->EndBlock(); } +bool rst::Parser::ParseCode(const char *s, std::size_t size, std::string &code) +{ + // It requires at least four ticks ``text`` + if (s[0] != '`' || s[1] != '`') + return false; + + if (size < 4) + return false; + + std::size_t start_pos = 2; + std::size_t end_pos = 0; + for (std::size_t i = start_pos; i < size - 1; ++i) { + if (s[i] == '`' && s[i + 1] == '`') { + end_pos = i; + break; + } + } + + if (end_pos == 0) + return false; + + code.assign(s + start_pos, end_pos - start_pos); + + return true; +} + +bool rst::Parser::ParseReferenceLink(const char *s, std::size_t size, std::string &type, std::string &text) +{ + // :type:`text` + if (size < 4) + return false; + + auto start_type_tag = s + 1; + auto end_type_tag = std::find(start_type_tag, s + size, ':'); + if (end_type_tag == s + size) + return false; + + type.assign(start_type_tag, end_type_tag - start_type_tag); + + if (*(end_type_tag + 1) != '`') + return false; + + auto start_text_tag = end_type_tag + 2; + auto end_text_tag = std::find(start_text_tag, s + size, '`'); + if (end_text_tag == s + size) + return false; + + text.assign(start_text_tag, end_text_tag - start_text_tag); + + return true; +} + void rst::Parser::Parse(const char *s) { BlockType prev_type = PARAGRAPH; ptr_ = s; @@ -214,7 +364,28 @@ void rst::Parser::Parse(const char *s) { std::string type = ParseDirectiveType(); if (!type.empty() && ptr_[0] == ':' && ptr_[1] == ':') { ptr_ += 2; - handler_->HandleDirective(type.c_str()); + + const char* after_directive = ptr_; + + // Get the name of the directive + std::string name; + while (*ptr_ && *ptr_ != '\n') { + c = *ptr_++; + if (!IsSpace(c)) + name.push_back(c); + } + + // Special case for ".. note::" which can start directly after the :: + if (type == "note" && name.size() > 0) { + ptr_ = after_directive; + SkipSpace(); + handler_->HandleDirective(type, ""); + + ParseBlock(BLOCK_QUOTE, prev_type, 0); + break; + } + + handler_->HandleDirective(type, name); } // Skip everything till the end of the line. while (*ptr_ && *ptr_ != '\n') diff --git a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h index 547f128af7c..4fabdbf46ec 100644 --- a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h +++ b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h @@ -35,6 +35,13 @@ namespace rst { enum BlockType { + H1, + H2, + H3, + H4, + H5, + CODE, + REFERENCE_LINK, PARAGRAPH, LINE_BLOCK, BLOCK_QUOTE, @@ -58,7 +65,10 @@ class ContentHandler { virtual void HandleText(const char *text, std::size_t size) = 0; // Receives notification of a directive. - virtual void HandleDirective(const char *type) = 0; + virtual void HandleDirective(const std::string &type, const std::string &name) = 0; + + // Receives notification of a link. + virtual void HandleReferenceLink(const std::string &type, const std::string &text) = 0; }; // A parser for a subset of reStructuredText. @@ -85,6 +95,12 @@ class Parser { // Parses a line block. void ParseLineBlock(rst::BlockType &prev_type, int indent); + // Parses inline ``code`` + bool ParseCode(const char* s, std::size_t size, std::string &code); + + // Parses :reference:`link` + bool ParseReferenceLink(const char* s, std::size_t size, std::string &type, std::string &text); + public: explicit Parser(ContentHandler *h) : handler_(h), ptr_(0) {} @@ -94,4 +110,3 @@ class Parser { } #endif // RSTPARSER_H_ - From 695952f84bef0d88744b27965ec3c9522014012e Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Mon, 25 Sep 2023 23:08:38 +0200 Subject: [PATCH 13/21] CMakePM: Integrate RSTParser into hover help / code completion Change-Id: I1618be1aff83e8164c53040bb2c7230bcc1ec8ee Reviewed-by: Alessandro Portale --- .../cmakeprojectmanager/CMakeLists.txt | 1 + .../cmakeprojectmanager/cmakeeditor.cpp | 64 +++--- .../cmakefilecompletionassist.cpp | 16 +- .../cmakeprojectmanager.qbs | 14 +- .../cmakeprojectmanager/cmaketoolmanager.cpp | 186 +++++++++++++++++- .../cmakeprojectmanager/cmaketoolmanager.h | 4 +- 6 files changed, 227 insertions(+), 58 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index f1989cec6c7..010f948f8a9 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -46,4 +46,5 @@ add_qtc_plugin(CMakeProjectManager 3rdparty/cmake/cmListFileCache.cxx 3rdparty/cmake/cmListFileLexer.cxx 3rdparty/cmake/cmListFileCache.h + 3rdparty/rstparser/rstparser.cc 3rdparty/rstparser/rstparser.h ) diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index 9b363faebe3..86f8823fdee 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -8,7 +8,6 @@ #include "cmakebuildsystem.h" #include "cmakefilecompletionassist.h" #include "cmakeindenter.h" -#include "cmakekitaspect.h" #include "cmakeprojectconstants.h" #include "3rdparty/cmake/cmListFileCache.h" @@ -53,13 +52,7 @@ public: CMakeEditor::CMakeEditor() { - CMakeTool *tool = nullptr; - if (auto bs = ProjectTree::currentBuildSystem()) - tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); - if (!tool) - tool = CMakeToolManager::defaultCMakeTool(); - - if (tool) + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) m_keywords = tool->keywords(); } @@ -320,6 +313,7 @@ class CMakeHoverHandler : public TextEditor::BaseHoverHandler { mutable CMakeKeywords m_keywords; QString m_helpToolTip; + QString m_contextHelp; public: const CMakeKeywords &keywords() const; @@ -332,16 +326,9 @@ public: const CMakeKeywords &CMakeHoverHandler::keywords() const { - if (m_keywords.functions.isEmpty()) { - CMakeTool *tool = nullptr; - if (auto bs = ProjectTree::currentBuildSystem()) - tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); - if (!tool) - tool = CMakeToolManager::defaultCMakeTool(); - - if (tool) + if (m_keywords.functions.isEmpty()) + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) m_keywords = tool->keywords(); - } return m_keywords; } @@ -357,24 +344,39 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget const QString word = Utils::Text::wordUnderCursor(cursor); FilePath helpFile; - for (const auto &map : {keywords().functions, - keywords().variables, - keywords().directoryProperties, - keywords().sourceProperties, - keywords().targetProperties, - keywords().testProperties, - keywords().properties, - keywords().includeStandardModules, - keywords().findModules, - keywords().policies}) { - if (map.contains(word)) { - helpFile = map.value(word); + QString helpCategory; + struct + { + const QMap ↦ + QString helpCategory; + } keywordsListMaps[] = {{keywords().functions, "command"}, + {keywords().variables, "variable"}, + {keywords().directoryProperties, "prop_dir"}, + {keywords().sourceProperties, "prop_sf"}, + {keywords().targetProperties, "prop_tgt"}, + {keywords().testProperties, "prop_test"}, + {keywords().properties, "prop_gbl"}, + {keywords().includeStandardModules, "module"}, + {keywords().findModules, "module"}, + {keywords().policies, "policy"}}; + + for (const auto &pair : keywordsListMaps) { + if (pair.map.contains(word)) { + helpFile = pair.map.value(word); + helpCategory = pair.helpCategory; break; } } m_helpToolTip.clear(); if (!helpFile.isEmpty()) - m_helpToolTip = CMakeToolManager::readFirstParagraphs(helpFile); + m_helpToolTip = CMakeToolManager::toolTipForRstHelpFile(helpFile); + + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) + m_contextHelp = QString("%1/%2/%3") + .arg(tool->documentationUrl(tool->version(), + tool->qchFilePath().isEmpty()), + helpCategory, + word); setPriority(m_helpToolTip.isEmpty() ? Priority_Tooltip : Priority_None); } @@ -382,7 +384,7 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget void CMakeHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) { if (!m_helpToolTip.isEmpty()) - Utils::ToolTip::show(point, m_helpToolTip, Qt::MarkdownText, editorWidget); + Utils::ToolTip::show(point, m_helpToolTip, Qt::MarkdownText, editorWidget, m_contextHelp); else Utils::ToolTip::hide(); } diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index adf223e9a9b..dbaf380d250 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -5,8 +5,6 @@ #include "cmakebuildsystem.h" #include "cmakebuildtarget.h" -#include "cmakekitaspect.h" -#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmaketool.h" #include "cmaketoolmanager.h" @@ -166,7 +164,7 @@ static QList generateList(const QMapsetText(it.key()); if (!it.value().isEmpty()) - item->setDetail(CMakeToolManager::readFirstParagraphs(it.value())); + item->setDetail(CMakeToolManager::toolTipForRstHelpFile(it.value())); item->setIcon(icon); list << item; } @@ -244,16 +242,8 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync() Project *project = nullptr; const FilePath &filePath = interface()->filePath(); if (!filePath.isEmpty() && filePath.isFile()) { - CMakeTool *cmake = nullptr; - project = static_cast(ProjectManager::projectForFile(filePath)); - if (project && project->activeTarget()) - cmake = CMakeKitAspect::cmakeTool(project->activeTarget()->kit()); - - if (!cmake) - cmake = CMakeToolManager::defaultCMakeTool(); - - if (cmake && cmake->isValid()) - keywords = cmake->keywords(); + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) + keywords = tool->keywords(); } QStringList buildTargets; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 96520f9127d..94dfa408e15 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -93,13 +93,15 @@ QtcPlugin { name: "3rdparty" cpp.includePaths: base.concat("3rdparty/cmake") - prefix: "3rdparty/cmake/" + prefix: "3rdparty/" files: [ - "cmListFileCache.cxx", - "cmListFileCache.h", - "cmListFileLexer.cxx", - "cmListFileLexer.h", - "cmStandardLexer.h", + "cmake/cmListFileCache.cxx", + "cmake/cmListFileCache.h", + "cmake/cmListFileLexer.cxx", + "cmake/cmListFileLexer.h", + "cmake/cmStandardLexer.h", + "rstparser/rstparser.cc", + "rstparser/rstparser.h" ] } } diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp index da2bcff6e05..65a4f55aa24 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp @@ -8,11 +8,17 @@ #include "cmakespecificsettings.h" #include "cmaketoolsettingsaccessor.h" +#include "3rdparty/rstparser/rstparser.h" + #include #include #include +#include +#include +#include +#include #include #include #include @@ -32,6 +38,137 @@ public: Internal::CMakeToolSettingsAccessor m_accessor; }; +class HtmlHandler : public rst::ContentHandler +{ +private: + std::stack m_tags; + + QStringList m_p; + QStringList m_h3; + QStringList m_cmake_code; + + QString m_last_directive_type; + QString m_last_directive_class; + + void StartBlock(rst::BlockType type) final + { + QString tag; + switch (type) { + case rst::REFERENCE_LINK: + // not used, HandleReferenceLink is used instead + break; + case rst::H1: + tag = "h1"; + break; + case rst::H2: + tag = "h2"; + break; + case rst::H3: + tag = "h3"; + break; + case rst::H4: + tag = "h4"; + break; + case rst::H5: + tag = "h5"; + break; + case rst::CODE: + tag = "code"; + break; + case rst::PARAGRAPH: + tag = "p"; + break; + case rst::LINE_BLOCK: + tag = "pre"; + break; + case rst::BLOCK_QUOTE: + if (m_last_directive_type == "code-block" && m_last_directive_class == "cmake") + tag = "cmake-code"; + else + tag = "blockquote"; + break; + case rst::BULLET_LIST: + tag = "ul"; + break; + case rst::LIST_ITEM: + tag = "li"; + break; + case rst::LITERAL_BLOCK: + tag = "pre"; + break; + } + + if (tag == "p") + m_p.push_back(QString()); + if (tag == "h3") + m_h3.push_back(QString()); + if (tag == "cmake-code") + m_cmake_code.push_back(QString()); + + if (tag == "code" && m_tags.top() == "p") + m_p.last().append("`"); + + m_tags.push(tag); + } + + void EndBlock() final + { + // Add a new "p" collector for any `code` markup that comes afterwads + // since we are insterested only in the first paragraph. + if (m_tags.top() == "p") + m_p.push_back(QString()); + + if (m_tags.top() == "code" && !m_p.isEmpty()) { + m_tags.pop(); + + if (m_tags.size() > 0 && m_tags.top() == "p") + m_p.last().append("`"); + } else { + m_tags.pop(); + } + } + + void HandleText(const char *text, std::size_t size) final + { + if (m_last_directive_type.endsWith("replace")) + return; + + QString str = QString::fromUtf8(text, size); + + if (m_tags.top() == "h3") + m_h3.last().append(str); + if (m_tags.top() == "p") + m_p.last().append(str); + if (m_tags.top() == "cmake-code") + m_cmake_code.last().append(str); + if (m_tags.top() == "code" && !m_p.isEmpty()) + m_p.last().append(str); + } + + void HandleDirective(const std::string &type, const std::string &name) final + { + m_last_directive_type = QString::fromStdString(type); + m_last_directive_class = QString::fromStdString(name); + } + + void HandleReferenceLink(const std::string &type, const std::string &text) final + { + Q_UNUSED(type) + if (!m_p.isEmpty()) + m_p.last().append(QString::fromStdString(text)); + } + +public: + QString content() const + { + const QString title = m_h3.isEmpty() ? QString() : m_h3.first(); + const QString description = m_p.isEmpty() ? QString() : m_p.first(); + const QString cmakeCode = m_cmake_code.isEmpty() ? QString() : m_cmake_code.first(); + + return QString("### %1\n\n%2\n\n````\n%3\n````").arg(title, description, cmakeCode); + } +}; + static CMakeToolManagerPrivate *d = nullptr; CMakeToolManager *CMakeToolManager::m_instance = nullptr; @@ -108,6 +245,37 @@ void CMakeToolManager::deregisterCMakeTool(const Id &id) } } +CMakeTool *CMakeToolManager::defaultProjectOrDefaultCMakeTool() +{ + static CMakeTool *tool = nullptr; + + auto updateTool = [&] { + tool = nullptr; + if (auto bs = ProjectExplorer::ProjectTree::currentBuildSystem()) + tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); + if (!tool) + tool = CMakeToolManager::defaultCMakeTool(); + }; + + if (!tool) + updateTool(); + + QObject::connect(CMakeToolManager::instance(), + &CMakeToolManager::cmakeUpdated, + CMakeToolManager::instance(), + [&]() { updateTool(); }); + QObject::connect(CMakeToolManager::instance(), + &CMakeToolManager::cmakeRemoved, + CMakeToolManager::instance(), + [&]() { updateTool(); }); + QObject::connect(CMakeToolManager::instance(), + &CMakeToolManager::defaultCMakeChanged, + CMakeToolManager::instance(), + [&]() { updateTool(); }); + + return tool; +} + CMakeTool *CMakeToolManager::defaultCMakeTool() { return findById(d->m_defaultCMake); @@ -167,21 +335,25 @@ void CMakeToolManager::updateDocumentation() Core::HelpManager::registerDocumentation(docs); } -QString CMakeToolManager::readFirstParagraphs(const FilePath &helpFile) +QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile) { - static QMap map; + static QHash map; if (map.contains(helpFile)) return map.value(helpFile); auto content = helpFile.fileContents(1024).value_or(QByteArray()); - const QString firstParagraphs - = QString("```\n%1\n```").arg(QString::fromUtf8(content.left(content.lastIndexOf("\n")))); + content.replace("\r\n", "\n"); - map[helpFile] = firstParagraphs; - return firstParagraphs; + HtmlHandler handler; + rst::Parser parser(&handler); + parser.Parse(content.left(content.lastIndexOf('\n'))); + + const QString tooltip = handler.content(); + + map[helpFile] = tooltip; + return tooltip; } - QList CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths, const QString &detectionSource, QString *logMessage) diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h index 85dbcb53b9e..761ba22ad76 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h @@ -30,6 +30,8 @@ public: static bool registerCMakeTool(std::unique_ptr &&tool); static void deregisterCMakeTool(const Utils::Id &id); + static CMakeTool *defaultProjectOrDefaultCMakeTool(); + static CMakeTool *defaultCMakeTool(); static void setDefaultCMakeTool(const Utils::Id &id); static CMakeTool *findByCommand(const Utils::FilePath &command); @@ -40,7 +42,7 @@ public: static void updateDocumentation(); - static QString readFirstParagraphs(const Utils::FilePath &helpFile); + static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile); public slots: QList autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths, From c32022f0857dfca38359c7cc32a578a677af18d5 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Wed, 27 Sep 2023 14:29:35 +0200 Subject: [PATCH 14/21] GitHub/Coin: Update LLVM/Clang to 17.0.1 Change-Id: I0cc3e3640eb60cd991fba073f8d32d3de9b900fe Reviewed-by: Qt CI Bot Reviewed-by: Eike Ziller --- .github/workflows/build_cmake.yml | 2 +- coin/instructions/common_environment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index 52c2a0e4d29..3b2c1755b2f 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -9,7 +9,7 @@ on: env: QT_VERSION: 6.5.2 MACOS_DEPLOYMENT_TARGET: 10.15 - CLANG_VERSION: 17.0.0-rc4 + CLANG_VERSION: 17.0.1 ELFUTILS_VERSION: 0.175 CMAKE_VERSION: 3.21.1 NINJA_VERSION: 1.10.2 diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 2a56fe600e9..ea72589c1df 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -7,7 +7,7 @@ instructions: variableValue: "RelWithDebInfo" - type: EnvironmentVariable variableName: LLVM_BASE_URL - variableValue: https://ci-files02-hki.ci.qt.io/packages/jenkins/qtcreator_libclang/libclang-release_17.0.0-rc4-based + variableValue: https://ci-files02-hki.ci.qt.io/packages/jenkins/qtcreator_libclang/libclang-release_17.0.1-based - type: EnvironmentVariable variableName: QTC_QT_BASE_URL variableValue: "https://ci-files02-hki.ci.qt.io/packages/jenkins/archive/qt/6.5/6.5.2-released/Qt" From c05f9cacc6be1038a1a9ecee0af07ac84c01738c Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 27 Sep 2023 16:16:43 +0200 Subject: [PATCH 15/21] CtfVisualizer: Fix multithreading issues Simplify the CtfVisualizerTool::loadJson(). Don't create a QThread manually, but use TaskTree with AsyncTask instead. Move pure parsing into the separate thread, and leave the nlohmann::json event handling in the main thread. Avoid moving m_modelAggregator between threads. Fixes: QTCREATORBUG-29657 Change-Id: I0c6a9a4ea8298dbbdbafcddd338d39ad73c3f82b Reviewed-by: Alessandro Portale --- src/plugins/ctfvisualizer/ctftracemanager.cpp | 71 +--------- src/plugins/ctfvisualizer/ctftracemanager.h | 14 +- .../ctfvisualizer/ctfvisualizertool.cpp | 128 +++++++++++++----- src/plugins/ctfvisualizer/ctfvisualizertool.h | 10 +- 4 files changed, 103 insertions(+), 120 deletions(-) diff --git a/src/plugins/ctfvisualizer/ctftracemanager.cpp b/src/plugins/ctfvisualizer/ctftracemanager.cpp index 82e5b54b762..29dbed73c28 100644 --- a/src/plugins/ctfvisualizer/ctftracemanager.cpp +++ b/src/plugins/ctfvisualizer/ctftracemanager.cpp @@ -8,17 +8,11 @@ #include "ctfvisualizertr.h" #include + #include -#include -#include -#include -#include #include -#include - - namespace CtfVisualizer { namespace Internal { @@ -26,48 +20,6 @@ using json = nlohmann::json; using namespace Constants; - -class CtfJsonParserCallback -{ - -public: - - explicit CtfJsonParserCallback(CtfTraceManager *traceManager) - : m_traceManager(traceManager) - {} - - bool callback(int depth, nlohmann::json::parse_event_t event, nlohmann::json &parsed) - { - if ((event == json::parse_event_t::array_start && depth == 0) - || (event == json::parse_event_t::key && depth == 1 && parsed == json(CtfTraceEventsKey))) { - m_isInTraceArray = true; - m_traceArrayDepth = depth; - return true; - } - if (m_isInTraceArray && event == json::parse_event_t::array_end && depth == m_traceArrayDepth) { - m_isInTraceArray = false; - return false; - } - if (m_isInTraceArray && event == json::parse_event_t::object_end && depth == m_traceArrayDepth + 1) { - m_traceManager->addEvent(parsed); - return false; - } - if (m_isInTraceArray || (event == json::parse_event_t::object_start && depth == 0)) { - // keep outer object and values in trace objects: - return true; - } - // discard any objects outside of trace array: - // TODO: parse other data, e.g. stack frames - return false; - } - -protected: - CtfTraceManager *m_traceManager; - - bool m_isInTraceArray = false; - int m_traceArrayDepth = 0; -}; - CtfTraceManager::CtfTraceManager(QObject *parent, Timeline::TimelineModelAggregator *modelAggregator, CtfStatisticsModel *statisticsModel) @@ -75,7 +27,6 @@ CtfTraceManager::CtfTraceManager(QObject *parent, , m_modelAggregator(modelAggregator) , m_statisticsModel(statisticsModel) { - } qint64 CtfTraceManager::traceDuration() const @@ -142,26 +93,6 @@ void CtfTraceManager::addEvent(const json &event) } } -void CtfTraceManager::load(const QString &filename) -{ - clearAll(); - - std::ifstream file(filename.toStdString()); - if (!file.is_open()) { - QMessageBox::warning(Core::ICore::dialogParent(), - Tr::tr("CTF Visualizer"), - Tr::tr("Cannot read the CTF file.")); - return; - } - CtfJsonParserCallback ctfParser(this); - json::parser_callback_t callback = [&ctfParser](int depth, json::parse_event_t event, json &parsed) { - return ctfParser.callback(depth, event, parsed); - }; - json unusedValues = json::parse(file, callback, /*allow_exceptions*/ false); - file.close(); - updateStatistics(); -} - void CtfTraceManager::finalize() { bool userConsentToIgnoreDeepTraces = false; diff --git a/src/plugins/ctfvisualizer/ctftracemanager.h b/src/plugins/ctfvisualizer/ctftracemanager.h index d376d010b5c..29694740297 100644 --- a/src/plugins/ctfvisualizer/ctftracemanager.h +++ b/src/plugins/ctfvisualizer/ctftracemanager.h @@ -5,13 +5,11 @@ #include "json/json.hpp" #include +#include #include #include -#include -namespace Timeline { -class TimelineModelAggregator; -} +namespace Timeline { class TimelineModelAggregator; } namespace CtfVisualizer { namespace Internal { @@ -34,7 +32,6 @@ public: void addEvent(const nlohmann::json &event); - void load(const QString &filename); void finalize(); bool isEmpty() const; @@ -46,6 +43,9 @@ public: void setThreadRestriction(const QString &tid, bool restrictToThisThread); bool isRestrictedTo(const QString &tid) const; + void updateStatistics(); + void clearAll(); + signals: void detailsRequested(const QString &title); @@ -53,10 +53,6 @@ protected: void addModelForThread(const QString &threadId, const QString &processId); void addModelsToAggregator(); - void updateStatistics(); - - void clearAll(); - Timeline::TimelineModelAggregator *const m_modelAggregator; CtfStatisticsModel *const m_statisticsModel; diff --git a/src/plugins/ctfvisualizer/ctfvisualizertool.cpp b/src/plugins/ctfvisualizer/ctfvisualizertool.cpp index 22b6bd50473..6aee14e1236 100644 --- a/src/plugins/ctfvisualizer/ctfvisualizertool.cpp +++ b/src/plugins/ctfvisualizer/ctfvisualizertool.cpp @@ -13,30 +13,29 @@ #include #include #include -#include +#include + #include +#include #include #include -#include -#include #include -#include #include #include -#include + +#include using namespace Core; using namespace CtfVisualizer::Constants; - +using namespace Utils; namespace CtfVisualizer { namespace Internal { CtfVisualizerTool::CtfVisualizerTool() : QObject (nullptr) - , m_isLoading(false) , m_loadJson(nullptr) , m_traceView(nullptr) , m_modelAggregator(new Timeline::TimelineModelAggregator(this)) @@ -150,34 +149,84 @@ Timeline::TimelineZoomControl *CtfVisualizerTool::zoomControl() const return m_zoomControl.get(); } -void CtfVisualizerTool::loadJson(const QString &filename) +class CtfJsonParserFunctor { - if (m_isLoading) - return; +public: + CtfJsonParserFunctor(QPromise &promise) + : m_promise(promise) {} - if (filename.isEmpty()) { - m_isLoading = false; + bool operator()(int depth, nlohmann::json::parse_event_t event, nlohmann::json &parsed) + { + using json = nlohmann::json; + if ((event == json::parse_event_t::array_start && depth == 0) + || (event == json::parse_event_t::key && depth == 1 && parsed == json(CtfTraceEventsKey))) { + m_isInTraceArray = true; + m_traceArrayDepth = depth; + return true; + } + if (m_isInTraceArray && event == json::parse_event_t::array_end && depth == m_traceArrayDepth) { + m_isInTraceArray = false; + return false; + } + if (m_isInTraceArray && event == json::parse_event_t::object_end && depth == m_traceArrayDepth + 1) { + m_promise.addResult(parsed); + return false; + } + if (m_isInTraceArray || (event == json::parse_event_t::object_start && depth == 0)) { + // keep outer object and values in trace objects: + return true; + } + // discard any objects outside of trace array: + // TODO: parse other data, e.g. stack frames + return false; + } + +protected: + QPromise &m_promise; + bool m_isInTraceArray = false; + int m_traceArrayDepth = 0; +}; + +static void load(QPromise &promise, const QString &fileName) +{ + using json = nlohmann::json; + + std::ifstream file(fileName.toStdString()); + if (!file.is_open()) { + promise.future().cancel(); return; } - m_isLoading = true; + CtfJsonParserFunctor functor(promise); + json::parser_callback_t callback = [&functor](int depth, json::parse_event_t event, json &parsed) { + return functor(depth, event, parsed); + }; - auto *futureInterface = new QFutureInterface(); - auto *task = new QFuture(futureInterface); + try { + json unusedValues = json::parse(file, callback, /*allow_exceptions*/ false); + } catch (...) { + // nlohmann::json can throw exceptions when requesting type that is wrong + } - QThread *thread = QThread::create([this, filename, futureInterface]() { - try { - m_traceManager->load(filename); - } catch (...) { - // nlohmann::json can throw exceptions when requesting type that is wrong - } - m_modelAggregator->moveToThread(QApplication::instance()->thread()); - m_modelAggregator->setParent(this); - futureInterface->reportFinished(); - }); + file.close(); +} - connect(thread, &QThread::finished, this, [this, thread, task, futureInterface]() { - // in main thread: +void CtfVisualizerTool::loadJson(const QString &fileName) +{ + using namespace Tasking; + + if (m_loader || fileName.isEmpty()) + return; + + const auto onSetup = [this, fileName](Async &async) { + m_traceManager->clearAll(); + async.setConcurrentCallData(load, fileName); + connect(&async, &AsyncBase::resultReadyAt, this, [this, asyncPtr = &async](int index) { + m_traceManager->addEvent(asyncPtr->resultAt(index)); + }); + }; + const auto onDone = [this] { + m_traceManager->updateStatistics(); if (m_traceManager->isEmpty()) { QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("CTF Visualizer"), @@ -189,17 +238,22 @@ void CtfVisualizerTool::loadJson(const QString &filename) zoomControl()->setRange(m_traceManager->traceBegin(), m_traceManager->traceEnd() + m_traceManager->traceDuration() / 20); } setAvailableThreads(m_traceManager->getSortedThreads()); - thread->deleteLater(); - delete task; - delete futureInterface; - m_isLoading = false; - }, Qt::QueuedConnection); + m_loader.release()->deleteLater(); + }; + const auto onError = [this] { + QMessageBox::warning(Core::ICore::dialogParent(), + Tr::tr("CTF Visualizer"), + Tr::tr("Cannot read the CTF file.")); + m_loader.release()->deleteLater(); + }; - m_modelAggregator->setParent(nullptr); - m_modelAggregator->moveToThread(thread); - - thread->start(); - Core::ProgressManager::addTask(*task, Tr::tr("Loading CTF File"), CtfVisualizerTaskLoadJson); + const Group recipe { AsyncTask(onSetup) }; + m_loader.reset(new TaskTree(recipe)); + connect(m_loader.get(), &TaskTree::done, this, onDone); + connect(m_loader.get(), &TaskTree::errorOccurred, this, onError); + auto progress = new TaskProgress(m_loader.get()); + progress->setDisplayName(Tr::tr("Loading CTF File")); + m_loader->start(); } } // namespace Internal diff --git a/src/plugins/ctfvisualizer/ctfvisualizertool.h b/src/plugins/ctfvisualizer/ctfvisualizertool.h index baea670d5cf..00be211efac 100644 --- a/src/plugins/ctfvisualizer/ctfvisualizertool.h +++ b/src/plugins/ctfvisualizer/ctfvisualizertool.h @@ -6,12 +6,15 @@ #include "ctfvisualizerconstants.h" #include + #include #include #include #include +namespace Tasking { class TaskTree; } + namespace CtfVisualizer { namespace Internal { @@ -21,7 +24,6 @@ class CtfStatisticsView; class CtfTimelineModel; class CtfVisualizerTraceView; - class CtfVisualizerTool : public QObject { Q_OBJECT @@ -34,7 +36,7 @@ public: CtfTraceManager *traceManager() const; Timeline::TimelineZoomControl *zoomControl() const; - void loadJson(const QString &filename); + void loadJson(const QString &fileName); private: void createViews(); @@ -45,11 +47,11 @@ private: void setAvailableThreads(const QList &threads); void toggleThreadRestriction(QAction *action); - Utils::Perspective m_perspective{Constants::CtfVisualizerPerspectiveId, + Utils::Perspective m_perspective{CtfVisualizer::Constants::CtfVisualizerPerspectiveId, QCoreApplication::translate("QtC::CtfVisualizer", "Chrome Trace Format Visualizer")}; - bool m_isLoading; + std::unique_ptr m_loader; QScopedPointer m_loadJson; CtfVisualizerTraceView *m_traceView; From 895f8e73961d82a2a7971087df9248584eaa4eb0 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 27 Sep 2023 18:03:18 +0200 Subject: [PATCH 16/21] Meson: Merge MesonProcess into project parser file pair Only used there. Also remove the translation line in the test project to help with incomplete Qt 5 installations. Change-Id: Id7029b499cec69a7e1733e2ac4ade9026c522951 Reviewed-by: Marcus Tillmanns --- .../mesonprojectmanager/CMakeLists.txt | 2 - .../mesonprojectmanager/mesonprocess.cpp | 119 ------------- .../mesonprojectmanager/mesonprocess.h | 54 ------ .../mesonprojectmanager.qbs | 2 - .../mesonprojectparser.cpp | 159 ++++++++++++++---- .../mesonprojectmanager/mesonprojectparser.h | 18 +- .../meson/mesonsampleproject/meson.build | 2 +- 7 files changed, 143 insertions(+), 213 deletions(-) delete mode 100644 src/plugins/mesonprojectmanager/mesonprocess.cpp delete mode 100644 src/plugins/mesonprojectmanager/mesonprocess.h diff --git a/src/plugins/mesonprojectmanager/CMakeLists.txt b/src/plugins/mesonprojectmanager/CMakeLists.txt index 33fc6b25b71..9fc1f89b6f8 100644 --- a/src/plugins/mesonprojectmanager/CMakeLists.txt +++ b/src/plugins/mesonprojectmanager/CMakeLists.txt @@ -23,8 +23,6 @@ add_qtc_plugin(MesonProjectManager mesoninfoparser.h mesonoutputparser.cpp mesonoutputparser.h - mesonprocess.cpp - mesonprocess.h mesonproject.cpp mesonproject.h mesonprojectimporter.cpp diff --git a/src/plugins/mesonprojectmanager/mesonprocess.cpp b/src/plugins/mesonprojectmanager/mesonprocess.cpp deleted file mode 100644 index 2e4ba024559..00000000000 --- a/src/plugins/mesonprojectmanager/mesonprocess.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2020 Alexis Jeandet. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "mesonprocess.h" - -#include "mesonprojectmanagertr.h" -#include "toolwrapper.h" - -#include -#include - -#include -#include - -#include -#include -#include - -#include - -using namespace Core; -using namespace Utils; - -namespace MesonProjectManager { -namespace Internal { - -static Q_LOGGING_CATEGORY(mesonProcessLog, "qtc.meson.buildsystem", QtWarningMsg); - -MesonProcess::MesonProcess() = default; -MesonProcess::~MesonProcess() = default; - -bool MesonProcess::run(const Command &command, - const Environment &env, - const QString &projectName, - bool captureStdo) -{ - if (!sanityCheck(command)) - return false; - m_stdo.clear(); - ProjectExplorer::TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); - setupProcess(command, env, projectName, captureStdo); - m_elapsed.start(); - m_process->start(); - qCDebug(mesonProcessLog()) << "Starting:" << command.toUserOutput(); - return true; -} - -void MesonProcess::handleProcessDone() -{ - if (m_process->result() != ProcessResult::FinishedWithSuccess) { - ProjectExplorer::TaskHub::addTask(ProjectExplorer::BuildSystemTask{ - ProjectExplorer::Task::TaskType::Error, m_process->exitMessage()}); - } - m_stdo = m_process->readAllRawStandardOutput(); - m_stderr = m_process->readAllRawStandardError(); - const QString elapsedTime = formatElapsedTime(m_elapsed.elapsed()); - MessageManager::writeSilently(elapsedTime); - emit finished(m_process->exitCode(), m_process->exitStatus()); -} - -void MesonProcess::setupProcess(const Command &command, const Environment &env, - const QString &projectName, bool captureStdo) -{ - if (m_process) - m_process.release()->deleteLater(); - m_process.reset(new Process); - connect(m_process.get(), &Process::done, this, &MesonProcess::handleProcessDone); - if (!captureStdo) { - connect(m_process.get(), &Process::readyReadStandardOutput, - this, &MesonProcess::processStandardOutput); - connect(m_process.get(), &Process::readyReadStandardError, - this, &MesonProcess::processStandardError); - } - - m_process->setWorkingDirectory(command.workDir()); - m_process->setEnvironment(env); - MessageManager::writeFlashing(Tr::tr("Running %1 in %2.") - .arg(command.toUserOutput(), command.workDir().toUserOutput())); - m_process->setCommand(command.cmdLine()); - m_process->setTimeoutS(10); - ProcessProgress *progress = new ProcessProgress(m_process.get()); - progress->setDisplayName(Tr::tr("Configuring \"%1\".").arg(projectName)); -} - -bool MesonProcess::sanityCheck(const Command &command) const -{ - const auto &exe = command.cmdLine().executable(); - if (!exe.exists()) { - //Should only reach this point if Meson exe is removed while a Meson project is opened - ProjectExplorer::TaskHub::addTask( - ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, - Tr::tr("Executable does not exist: %1") - .arg(exe.toUserOutput())}); - return false; - } - if (!exe.toFileInfo().isExecutable()) { - ProjectExplorer::TaskHub::addTask( - ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, - Tr::tr("Command is not executable: %1") - .arg(exe.toUserOutput())}); - return false; - } - return true; -} - -void MesonProcess::processStandardOutput() -{ - const auto data = m_process->readAllRawStandardOutput(); - MessageManager::writeSilently(QString::fromLocal8Bit(data)); - emit readyReadStandardOutput(data); -} - -void MesonProcess::processStandardError() -{ - MessageManager::writeSilently(QString::fromLocal8Bit(m_process->readAllRawStandardError())); -} - -} // namespace Internal -} // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/mesonprocess.h b/src/plugins/mesonprojectmanager/mesonprocess.h deleted file mode 100644 index c01427c8fb9..00000000000 --- a/src/plugins/mesonprojectmanager/mesonprocess.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2020 Alexis Jeandet. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include -#include -#include - -#include - -namespace Utils { -class Environment; -class Process; -} - -namespace MesonProjectManager { -namespace Internal { - -class Command; - -class MesonProcess final : public QObject -{ - Q_OBJECT -public: - MesonProcess(); - ~MesonProcess(); - bool run(const Command &command, const Utils::Environment &env, - const QString &projectName, bool captureStdo = false); - - const QByteArray &stdOut() const { return m_stdo; } - const QByteArray &stdErr() const { return m_stderr; } -signals: - void finished(int exitCode, QProcess::ExitStatus exitStatus); - void readyReadStandardOutput(const QByteArray &data); - -private: - void handleProcessDone(); - void setupProcess(const Command &command, const Utils::Environment &env, - const QString &projectName, bool captureStdo); - bool sanityCheck(const Command &command) const; - - void processStandardOutput(); - void processStandardError(); - - std::unique_ptr m_process; - QElapsedTimer m_elapsed; - QByteArray m_stdo; - QByteArray m_stderr; -}; - -} // namespace Internal -} // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs b/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs index 9167f025b20..df1c076eb60 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs +++ b/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs @@ -49,8 +49,6 @@ Project { "mesonbuildconfiguration.h", "mesonbuildsystem.cpp", "mesonbuildsystem.h", - "mesonprocess.cpp", - "mesonprocess.h", "mesonproject.cpp", "mesonproject.h", "mesonprojectimporter.cpp", diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp index 9de424bf282..a4ac7a13252 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp @@ -4,25 +4,36 @@ #include "mesonprojectparser.h" #include "mesoninfoparser.h" +#include "mesonprojectmanagertr.h" #include "mesonprojectnodes.h" #include "mesontools.h" #include "projecttree.h" +#include #include #include +#include +#include #include +#include #include - -#include -#include +#include #include +#include + +using namespace Core; +using namespace ProjectExplorer; +using namespace Utils; + namespace MesonProjectManager { namespace Internal { +static Q_LOGGING_CATEGORY(mesonProcessLog, "qtc.meson.buildsystem", QtWarningMsg); + struct CompilerArgs { QStringList args; @@ -30,8 +41,8 @@ struct CompilerArgs ProjectExplorer::Macros macros; }; -inline std::optional extractValueIfMatches(const QString &arg, - const QStringList &candidates) +static std::optional extractValueIfMatches(const QString &arg, + const QStringList &candidates) { for (const auto &flag : candidates) { if (arg.startsWith(flag)) @@ -40,11 +51,12 @@ inline std::optional extractValueIfMatches(const QString &arg, return std::nullopt; } -inline std::optional extractInclude(const QString &arg) +static std::optional extractInclude(const QString &arg) { return extractValueIfMatches(arg, {"-I", "/I", "-isystem", "-imsvc", "/imsvc"}); } -inline std::optional extractMacro(const QString &arg) + +static std::optional extractMacro(const QString &arg) { auto define = extractValueIfMatches(arg, {"-D", "/D"}); if (define) @@ -93,18 +105,12 @@ MesonProjectParser::MesonProjectParser(const Utils::Id &meson, , m_meson{meson} , m_projectName{project->displayName()} { - connect(&m_process, &MesonProcess::finished, this, &MesonProjectParser::processFinished); - connect(&m_process, - &MesonProcess::readyReadStandardOutput, - &m_outputParser, - &MesonOutputParser::readStdo); - // TODO re-think the way all BuildSystem/ProjectParser are tied // I take project info here, I also take build and src dir later from // functions args. auto fileFinder = new Utils::FileInProjectFinder; fileFinder->setProjectDirectory(project->projectDirectory()); - fileFinder->setProjectFiles(project->files(ProjectExplorer::Project::AllFiles)); + fileFinder->setProjectFiles(project->files(Project::AllFiles)); m_outputParser.setFileFinder(fileFinder); } @@ -126,7 +132,7 @@ bool MesonProjectParser::configure(const Utils::FilePath &sourcePath, m_pendingCommands.enqueue( std::make_tuple(MesonTools::mesonWrapper(m_meson)->regenerate(sourcePath, buildPath), false)); - return m_process.run(cmd, m_env, m_projectName); + return run(cmd, m_env, m_projectName); } bool MesonProjectParser::wipe(const Utils::FilePath &sourcePath, @@ -149,7 +155,7 @@ bool MesonProjectParser::setup(const Utils::FilePath &sourcePath, if (forceWipe || isSetup(buildPath)) cmdArgs << "--wipe"; auto cmd = MesonTools::mesonWrapper(m_meson)->setup(sourcePath, buildPath, cmdArgs); - return m_process.run(cmd, m_env, m_projectName); + return run(cmd, m_env, m_projectName); } bool MesonProjectParser::parse(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath) @@ -170,18 +176,18 @@ bool MesonProjectParser::parse(const Utils::FilePath &sourcePath) m_srcDir = sourcePath; m_introType = IntroDataType::stdo; m_outputParser.setSourceDirectory(sourcePath); - return m_process.run(MesonTools::mesonWrapper(m_meson)->introspect(sourcePath), - m_env, - m_projectName, - true); + return run(MesonTools::mesonWrapper(m_meson)->introspect(sourcePath), + m_env, + m_projectName, + true); } -QList MesonProjectParser::appsTargets() const +QList MesonProjectParser::appsTargets() const { - QList apps; + QList apps; for (const Target &target : m_parserResult.targets) { if (target.type == Target::Type::executable) { - ProjectExplorer::BuildTargetInfo bti; + BuildTargetInfo bti; bti.displayName = target.name; bti.buildKey = Target::fullName(m_buildDir, target); bti.displayNameUniquifier = bti.buildKey; @@ -198,8 +204,8 @@ QList MesonProjectParser::appsTargets() const bool MesonProjectParser::startParser() { m_parserFutureResult = Utils::asyncRun( - ProjectExplorer::ProjectExplorerPlugin::sharedThreadPool(), - [processOutput = m_process.stdOut(), introType = m_introType, + ProjectExplorerPlugin::sharedThreadPool(), + [processOutput = m_stdo, introType = m_introType, buildDir = m_buildDir, srcDir = m_srcDir] { if (introType == IntroDataType::file) return extractParserResults(srcDir, MesonInfoParser::parse(buildDir)); @@ -245,13 +251,13 @@ void MesonProjectParser::update(const QFuture emit parsingCompleted(true); } -ProjectExplorer::RawProjectPart MesonProjectParser::buildRawPart( +RawProjectPart MesonProjectParser::buildRawPart( const Target &target, const Target::SourceGroup &sources, - const ProjectExplorer::ToolChain *cxxToolChain, - const ProjectExplorer::ToolChain *cToolChain) + const ToolChain *cxxToolChain, + const ToolChain *cToolChain) { - ProjectExplorer::RawProjectPart part; + RawProjectPart part; part.setDisplayName(target.name); part.setBuildSystemTarget(Target::fullName(m_buildDir, target)); part.setFiles(sources.sources + sources.generatedSources); @@ -275,13 +281,12 @@ void MesonProjectParser::processFinished(int exitCode, QProcess::ExitStatus exit else { // see comment near m_pendingCommands declaration std::tuple args = m_pendingCommands.dequeue(); - m_process.run(std::get<0>(args), m_env, m_projectName, std::get<1>(args)); + run(std::get<0>(args), m_env, m_projectName, std::get<1>(args)); } } else { if (m_introType == IntroDataType::stdo) { - auto data = m_process.stdErr(); - Core::MessageManager::writeSilently(QString::fromLocal8Bit(data)); - m_outputParser.readStdo(data); + Core::MessageManager::writeSilently(QString::fromLocal8Bit(m_stderr)); + m_outputParser.readStdo(m_stderr); } emit parsingCompleted(false); } @@ -327,5 +332,93 @@ bool MesonProjectParser::usesSameMesonVersion(const Utils::FilePath &buildPath) auto meson = MesonTools::mesonWrapper(m_meson); return info && meson && info->mesonVersion == meson->version(); } + + +bool MesonProjectParser::run(const Command &command, + const Environment &env, + const QString &projectName, + bool captureStdo) +{ + if (!sanityCheck(command)) + return false; + m_stdo.clear(); + ProjectExplorer::TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + setupProcess(command, env, projectName, captureStdo); + m_elapsed.start(); + m_process->start(); + qCDebug(mesonProcessLog()) << "Starting:" << command.toUserOutput(); + return true; +} + +void MesonProjectParser::handleProcessDone() +{ + if (m_process->result() != ProcessResult::FinishedWithSuccess) { + ProjectExplorer::TaskHub::addTask(ProjectExplorer::BuildSystemTask{ + ProjectExplorer::Task::TaskType::Error, m_process->exitMessage()}); + } + m_stdo = m_process->readAllRawStandardOutput(); + m_stderr = m_process->readAllRawStandardError(); + const QString elapsedTime = formatElapsedTime(m_elapsed.elapsed()); + MessageManager::writeSilently(elapsedTime); + processFinished(m_process->exitCode(), m_process->exitStatus()); +} + +void MesonProjectParser::setupProcess(const Command &command, const Environment &env, + const QString &projectName, bool captureStdo) +{ + if (m_process) + m_process.release()->deleteLater(); + m_process.reset(new Process); + connect(m_process.get(), &Process::done, this, &MesonProjectParser::handleProcessDone); + if (!captureStdo) { + connect(m_process.get(), &Process::readyReadStandardOutput, + this, &MesonProjectParser::processStandardOutput); + connect(m_process.get(), &Process::readyReadStandardError, + this, &MesonProjectParser::processStandardError); + } + + m_process->setWorkingDirectory(command.workDir()); + m_process->setEnvironment(env); + MessageManager::writeFlashing(Tr::tr("Running %1 in %2.") + .arg(command.toUserOutput(), command.workDir().toUserOutput())); + m_process->setCommand(command.cmdLine()); + m_process->setTimeoutS(10); + ProcessProgress *progress = new ProcessProgress(m_process.get()); + progress->setDisplayName(Tr::tr("Configuring \"%1\".").arg(projectName)); +} + +bool MesonProjectParser::sanityCheck(const Command &command) const +{ + const auto &exe = command.cmdLine().executable(); + if (!exe.exists()) { + //Should only reach this point if Meson exe is removed while a Meson project is opened + ProjectExplorer::TaskHub::addTask( + ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, + Tr::tr("Executable does not exist: %1") + .arg(exe.toUserOutput())}); + return false; + } + if (!exe.toFileInfo().isExecutable()) { + ProjectExplorer::TaskHub::addTask( + ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, + Tr::tr("Command is not executable: %1") + .arg(exe.toUserOutput())}); + return false; + } + return true; +} + +void MesonProjectParser::processStandardOutput() +{ + const auto data = m_process->readAllRawStandardOutput(); + MessageManager::writeSilently(QString::fromLocal8Bit(data)); + m_outputParser.readStdo(data); +} + +void MesonProjectParser::processStandardError() +{ + MessageManager::writeSilently(QString::fromLocal8Bit(m_process->readAllRawStandardError())); +} + } // namespace Internal } // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.h b/src/plugins/mesonprojectmanager/mesonprojectparser.h index 168b4f1448d..3a129d60af1 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.h +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.h @@ -6,7 +6,6 @@ #include "kitdata.h" #include "mesoninfoparser.h" #include "mesonoutputparser.h" -#include "mesonprocess.h" #include "mesonprojectnodes.h" #include "mesonwrapper.h" @@ -91,7 +90,6 @@ private: const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); - MesonProcess m_process; MesonOutputParser m_outputParser; Utils::Environment m_env; Utils::Id m_meson; @@ -108,6 +106,22 @@ private: // maybe moving meson to build step could make this class simpler // also this should ease command dependencies QQueue> m_pendingCommands; + + bool run(const Command &command, const Utils::Environment &env, + const QString &projectName, bool captureStdo = false); + + void handleProcessDone(); + void setupProcess(const Command &command, const Utils::Environment &env, + const QString &projectName, bool captureStdo); + bool sanityCheck(const Command &command) const; + + void processStandardOutput(); + void processStandardError(); + + std::unique_ptr m_process; + QElapsedTimer m_elapsed; + QByteArray m_stdo; + QByteArray m_stderr; }; } // namespace Internal diff --git a/tests/manual/meson/mesonsampleproject/meson.build b/tests/manual/meson/mesonsampleproject/meson.build index 901285a587e..8edea0d4d53 100644 --- a/tests/manual/meson/mesonsampleproject/meson.build +++ b/tests/manual/meson/mesonsampleproject/meson.build @@ -4,7 +4,7 @@ project('mesonsampleproject', 'cpp',default_options : ['cpp_std=c++11']) qt5 = import('qt5') qt5dep = dependency('qt5', modules : ['Core', 'Widgets']) -translations = qt5.compile_translations(ts_files : 'mesonsampleproject_fr_FR.ts', build_by_default : true) +#translations = qt5.compile_translations(ts_files : 'mesonsampleproject_fr_FR.ts', build_by_default : true) generated_files = qt5.preprocess( moc_headers : 'mesonsampleproject.h', From 7bf1b6e586e8e56a1923419f58672772e575d7a6 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 27 Sep 2023 18:31:52 +0200 Subject: [PATCH 17/21] Meson: De-noise project parser a bit Change-Id: Ic0b19e5a1e10057f750c2ad13f61c09f1105d05d Reviewed-by: Marcus Tillmanns --- .../mesonprojectparser.cpp | 131 +++++++++--------- .../mesonprojectmanager/mesonprojectparser.h | 37 ++--- 2 files changed, 76 insertions(+), 92 deletions(-) diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp index a4ac7a13252..5fb39a5f845 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp @@ -38,7 +38,7 @@ struct CompilerArgs { QStringList args; QStringList includePaths; - ProjectExplorer::Macros macros; + Macros macros; }; static std::optional extractValueIfMatches(const QString &arg, @@ -56,18 +56,18 @@ static std::optional extractInclude(const QString &arg) return extractValueIfMatches(arg, {"-I", "/I", "-isystem", "-imsvc", "/imsvc"}); } -static std::optional extractMacro(const QString &arg) +static std::optional extractMacro(const QString &arg) { auto define = extractValueIfMatches(arg, {"-D", "/D"}); if (define) - return ProjectExplorer::Macro::fromKeyValue(define->toLatin1()); + return Macro::fromKeyValue(define->toLatin1()); auto undef = extractValueIfMatches(arg, {"-U", "/U"}); if (undef) - return ProjectExplorer::Macro(undef->toLatin1(), ProjectExplorer::MacroType::Undefine); + return Macro(undef->toLatin1(), MacroType::Undefine); return std::nullopt; } -CompilerArgs splitArgs(const QStringList &args) +static CompilerArgs splitArgs(const QStringList &args) { CompilerArgs splited; for (const QString &arg : args) { @@ -86,7 +86,7 @@ CompilerArgs splitArgs(const QStringList &args) return splited; } -QStringList toAbsolutePath(const Utils::FilePath &refPath, QStringList &pathList) +static QStringList toAbsolutePath(const FilePath &refPath, QStringList &pathList) { QStringList allAbs; std::transform(std::cbegin(pathList), @@ -98,9 +98,7 @@ QStringList toAbsolutePath(const Utils::FilePath &refPath, QStringList &pathList return allAbs; } -MesonProjectParser::MesonProjectParser(const Utils::Id &meson, - Utils::Environment env, - ProjectExplorer::Project *project) +MesonProjectParser::MesonProjectParser(const Id &meson, const Environment &env, Project *project) : m_env{env} , m_meson{meson} , m_projectName{project->displayName()} @@ -108,19 +106,14 @@ MesonProjectParser::MesonProjectParser(const Utils::Id &meson, // TODO re-think the way all BuildSystem/ProjectParser are tied // I take project info here, I also take build and src dir later from // functions args. - auto fileFinder = new Utils::FileInProjectFinder; + auto fileFinder = new FileInProjectFinder; fileFinder->setProjectDirectory(project->projectDirectory()); fileFinder->setProjectFiles(project->files(Project::AllFiles)); m_outputParser.setFileFinder(fileFinder); } -void MesonProjectParser::setMesonTool(const Utils::Id &meson) -{ - m_meson = meson; -} - -bool MesonProjectParser::configure(const Utils::FilePath &sourcePath, - const Utils::FilePath &buildPath, +bool MesonProjectParser::configure(const FilePath &sourcePath, + const FilePath &buildPath, const QStringList &args) { m_introType = IntroDataType::file; @@ -135,15 +128,15 @@ bool MesonProjectParser::configure(const Utils::FilePath &sourcePath, return run(cmd, m_env, m_projectName); } -bool MesonProjectParser::wipe(const Utils::FilePath &sourcePath, - const Utils::FilePath &buildPath, +bool MesonProjectParser::wipe(const FilePath &sourcePath, + const FilePath &buildPath, const QStringList &args) { return setup(sourcePath, buildPath, args, true); } -bool MesonProjectParser::setup(const Utils::FilePath &sourcePath, - const Utils::FilePath &buildPath, +bool MesonProjectParser::setup(const FilePath &sourcePath, + const FilePath &buildPath, const QStringList &args, bool forceWipe) { @@ -158,7 +151,7 @@ bool MesonProjectParser::setup(const Utils::FilePath &sourcePath, return run(cmd, m_env, m_projectName); } -bool MesonProjectParser::parse(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath) +bool MesonProjectParser::parse(const FilePath &sourcePath, const FilePath &buildPath) { m_srcDir = sourcePath; m_buildDir = buildPath; @@ -171,7 +164,7 @@ bool MesonProjectParser::parse(const Utils::FilePath &sourcePath, const Utils::F } } -bool MesonProjectParser::parse(const Utils::FilePath &sourcePath) +bool MesonProjectParser::parse(const FilePath &sourcePath) { m_srcDir = sourcePath; m_introType = IntroDataType::stdo; @@ -191,9 +184,9 @@ QList MesonProjectParser::appsTargets() const bti.displayName = target.name; bti.buildKey = Target::fullName(m_buildDir, target); bti.displayNameUniquifier = bti.buildKey; - bti.targetFilePath = Utils::FilePath::fromString(target.fileName.first()); - bti.workingDirectory = Utils::FilePath::fromString(target.fileName.first()).absolutePath(); - bti.projectFilePath = Utils::FilePath::fromString(target.definedIn); + bti.targetFilePath = FilePath::fromString(target.fileName.first()); + bti.workingDirectory = FilePath::fromString(target.fileName.first()).absolutePath(); + bti.projectFilePath = FilePath::fromString(target.definedIn); bti.usesTerminal = true; apps.append(bti); } @@ -218,7 +211,7 @@ bool MesonProjectParser::startParser() } MesonProjectParser::ParserData *MesonProjectParser::extractParserResults( - const Utils::FilePath &srcDir, MesonInfoParser::Result &&parserResult) + const FilePath &srcDir, MesonInfoParser::Result &&parserResult) { auto rootNode = ProjectTree::buildTree(srcDir, parserResult.targets, @@ -226,13 +219,20 @@ MesonProjectParser::ParserData *MesonProjectParser::extractParserResults( return new ParserData{std::move(parserResult), std::move(rootNode)}; } -void MesonProjectParser::addMissingTargets(QStringList &targetList) +static void addMissingTargets(QStringList &targetList) { // Not all targets are listed in introspection data - for (const auto &target : additionalTargets()) { - if (!targetList.contains(target)) { + static const QString additionalTargets[] { + Constants::Targets::all, + Constants::Targets::clean, + Constants::Targets::install, + Constants::Targets::benchmark, + Constants::Targets::scan_build + }; + + for (const QString &target : additionalTargets) { + if (!targetList.contains(target)) targetList.append(target); - } } } @@ -261,7 +261,7 @@ RawProjectPart MesonProjectParser::buildRawPart( part.setDisplayName(target.name); part.setBuildSystemTarget(Target::fullName(m_buildDir, target)); part.setFiles(sources.sources + sources.generatedSources); - auto flags = splitArgs(sources.parameters); + CompilerArgs flags = splitArgs(sources.parameters); part.setMacros(flags.macros); part.setIncludePaths(toAbsolutePath(m_buildDir, flags.includePaths)); part.setProjectFileLocation(target.definedIn); @@ -273,29 +273,10 @@ RawProjectPart MesonProjectParser::buildRawPart( return part; } -void MesonProjectParser::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +RawProjectParts MesonProjectParser::buildProjectParts( + const ToolChain *cxxToolChain, const ToolChain *cToolChain) { - if (exitCode == 0 && exitStatus == QProcess::NormalExit) { - if (m_pendingCommands.isEmpty()) - startParser(); - else { - // see comment near m_pendingCommands declaration - std::tuple args = m_pendingCommands.dequeue(); - run(std::get<0>(args), m_env, m_projectName, std::get<1>(args)); - } - } else { - if (m_introType == IntroDataType::stdo) { - Core::MessageManager::writeSilently(QString::fromLocal8Bit(m_stderr)); - m_outputParser.readStdo(m_stderr); - } - emit parsingCompleted(false); - } -} - -ProjectExplorer::RawProjectParts MesonProjectParser::buildProjectParts( - const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain) -{ - ProjectExplorer::RawProjectParts parts; + RawProjectParts parts; for_each_source_group(m_parserResult.targets, [&parts, &cxxToolChain, @@ -326,7 +307,7 @@ bool MesonProjectParser::matchesKit(const KitData &kit) return matches; } -bool MesonProjectParser::usesSameMesonVersion(const Utils::FilePath &buildPath) +bool MesonProjectParser::usesSameMesonVersion(const FilePath &buildPath) { auto info = MesonInfoParser::mesonInfo(buildPath); auto meson = MesonTools::mesonWrapper(m_meson); @@ -342,7 +323,7 @@ bool MesonProjectParser::run(const Command &command, if (!sanityCheck(command)) return false; m_stdo.clear(); - ProjectExplorer::TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); setupProcess(command, env, projectName, captureStdo); m_elapsed.start(); m_process->start(); @@ -352,15 +333,29 @@ bool MesonProjectParser::run(const Command &command, void MesonProjectParser::handleProcessDone() { - if (m_process->result() != ProcessResult::FinishedWithSuccess) { - ProjectExplorer::TaskHub::addTask(ProjectExplorer::BuildSystemTask{ - ProjectExplorer::Task::TaskType::Error, m_process->exitMessage()}); - } + if (m_process->result() != ProcessResult::FinishedWithSuccess) + TaskHub::addTask(BuildSystemTask{Task::TaskType::Error, m_process->exitMessage()}); + m_stdo = m_process->readAllRawStandardOutput(); m_stderr = m_process->readAllRawStandardError(); const QString elapsedTime = formatElapsedTime(m_elapsed.elapsed()); MessageManager::writeSilently(elapsedTime); - processFinished(m_process->exitCode(), m_process->exitStatus()); + + if (m_process->exitCode() == 0 && m_process->exitStatus() == QProcess::NormalExit) { + if (m_pendingCommands.isEmpty()) + startParser(); + else { + // see comment near m_pendingCommands declaration + std::tuple args = m_pendingCommands.dequeue(); + run(std::get<0>(args), m_env, m_projectName, std::get<1>(args)); + } + } else { + if (m_introType == IntroDataType::stdo) { + MessageManager::writeSilently(QString::fromLocal8Bit(m_stderr)); + m_outputParser.readStdo(m_stderr); + } + emit parsingCompleted(false); + } } void MesonProjectParser::setupProcess(const Command &command, const Environment &env, @@ -392,17 +387,15 @@ bool MesonProjectParser::sanityCheck(const Command &command) const const auto &exe = command.cmdLine().executable(); if (!exe.exists()) { //Should only reach this point if Meson exe is removed while a Meson project is opened - ProjectExplorer::TaskHub::addTask( - ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, - Tr::tr("Executable does not exist: %1") - .arg(exe.toUserOutput())}); + TaskHub::addTask( + BuildSystemTask{Task::TaskType::Error, + Tr::tr("Executable does not exist: %1").arg(exe.toUserOutput())}); return false; } if (!exe.toFileInfo().isExecutable()) { - ProjectExplorer::TaskHub::addTask( - ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, - Tr::tr("Command is not executable: %1") - .arg(exe.toUserOutput())}); + TaskHub::addTask( + BuildSystemTask{Task::TaskType::Error, + Tr::tr("Command is not executable: %1").arg(exe.toUserOutput())}); return false; } return true; diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.h b/src/plugins/mesonprojectmanager/mesonprojectparser.h index 3a129d60af1..a027aaa4d03 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.h +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.h @@ -13,9 +13,6 @@ #include #include -#include -#include - #include #include @@ -25,6 +22,7 @@ namespace Internal { class MesonProjectParser : public QObject { Q_OBJECT + enum class IntroDataType { file, stdo }; struct ParserData { @@ -33,8 +31,10 @@ class MesonProjectParser : public QObject }; public: - MesonProjectParser(const Utils::Id &meson, Utils::Environment env, ProjectExplorer::Project* project); - void setMesonTool(const Utils::Id &meson); + MesonProjectParser(const Utils::Id &meson, + const Utils::Environment &env, + ProjectExplorer::Project *project); + bool configure(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath, const QStringList &args); @@ -48,22 +48,11 @@ public: bool parse(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath); bool parse(const Utils::FilePath &sourcePath); - Q_SIGNAL void parsingCompleted(bool success); - std::unique_ptr takeProjectNode() { return std::move(m_rootNode); } - inline const BuildOptionsList &buildOptions() const { return m_parserResult.buildOptions; }; - inline const TargetsList &targets() const { return m_parserResult.targets; } - inline const QStringList &targetsNames() const { return m_targetsNames; } - - static inline QStringList additionalTargets() - { - return QStringList{Constants::Targets::all, - Constants::Targets::clean, - Constants::Targets::install, - Constants::Targets::benchmark, - Constants::Targets::scan_build}; - } + const BuildOptionsList &buildOptions() const { return m_parserResult.buildOptions; }; + const TargetsList &targets() const { return m_parserResult.targets; } + const QStringList &targetsNames() const { return m_targetsNames; } QList appsTargets() const; @@ -71,25 +60,27 @@ public: const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain); - inline void setEnvironment(const Utils::Environment &environment) { m_env = environment; } + void setEnvironment(const Utils::Environment &environment) { m_env = environment; } - inline void setQtVersion(Utils::QtMajorVersion v) { m_qtVersion = v; } + void setQtVersion(Utils::QtMajorVersion v) { m_qtVersion = v; } bool matchesKit(const KitData &kit); bool usesSameMesonVersion(const Utils::FilePath &buildPath); +signals: + void parsingCompleted(bool success); + private: bool startParser(); static ParserData *extractParserResults(const Utils::FilePath &srcDir, MesonInfoParser::Result &&parserResult); - static void addMissingTargets(QStringList &targetList); void update(const QFuture &data); ProjectExplorer::RawProjectPart buildRawPart(const Target &target, const Target::SourceGroup &sources, const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain); - void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + MesonOutputParser m_outputParser; Utils::Environment m_env; Utils::Id m_meson; From 071416fde32fc52cd089327b1c95e918fba112af Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 28 Sep 2023 07:56:57 +0200 Subject: [PATCH 18/21] Meson: Move some code out of visible plugin setup Background for the activity here is that this plugin's setup activities take on my machine around 0.2s on Creator during Creator startup and I'd like to reduce that a bit. Change-Id: I880a0b5ce5c4dce52a041b6a3ef9dc3cef346adb Reviewed-by: Marcus Tillmanns --- .../mesonprojectplugin.cpp | 25 +------------------ .../toolssettingsaccessor.cpp | 19 +++++++++----- .../toolssettingsaccessor.h | 5 ++-- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp b/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp index cc1e3eb1e27..71ef57462b2 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp @@ -12,36 +12,20 @@ #include "toolssettingsaccessor.h" #include "toolssettingspage.h" -#include - #include #include #include #include -using namespace Core; using namespace ProjectExplorer; using namespace Utils; namespace MesonProjectManager::Internal { -class MesonProjectPluginPrivate : public QObject +class MesonProjectPluginPrivate { - Q_OBJECT public: - MesonProjectPluginPrivate() - { - MesonTools::setTools(m_toolsSettings.loadMesonTools(ICore::dialogParent())); - connect(ICore::instance(), - &ICore::saveSettingsRequested, - this, - &MesonProjectPluginPrivate::saveAll); - } - - ~MesonProjectPluginPrivate() {} - -private: ToolsSettingsPage m_toolslSettingsPage; ToolsSettingsAccessor m_toolsSettings; MesonBuildStepFactory m_buildStepFactory; @@ -50,11 +34,6 @@ private: MesonActionsManager m_actions; MachineFileManager m_machineFilesManager; SimpleTargetRunnerFactory m_mesonRunWorkerFactory{{m_runConfigurationFactory.runConfigurationId()}}; - - void saveAll() - { - m_toolsSettings.saveMesonTools(MesonTools::tools(), ICore::dialogParent()); - } }; MesonProjectPlugin::~MesonProjectPlugin() @@ -72,5 +51,3 @@ void MesonProjectPlugin::initialize() } } // MesonProjectManager::Internal - -#include "mesonprojectplugin.moc" diff --git a/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp b/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp index 3f5cdfa45a7..80e5274c481 100644 --- a/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp +++ b/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp @@ -6,6 +6,7 @@ #include "mesonpluginconstants.h" #include "mesonprojectmanagertr.h" +#include #include #include @@ -16,6 +17,7 @@ #include #include +using namespace Core; using namespace Utils; namespace MesonProjectManager { @@ -30,11 +32,16 @@ ToolsSettingsAccessor::ToolsSettingsAccessor() { setDocType("QtCreatorMesonTools"); setApplicationDisplayName(QGuiApplication::applicationDisplayName()); - setBaseFilePath(Core::ICore::userResourcePath(Constants::ToolsSettings::FILENAME)); + setBaseFilePath(ICore::userResourcePath(Constants::ToolsSettings::FILENAME)); + + MesonTools::setTools(loadMesonTools()); + + QObject::connect(ICore::instance(), &ICore::saveSettingsRequested, [this] { + saveMesonTools(MesonTools::tools()); + }); } -void ToolsSettingsAccessor::saveMesonTools(const std::vector &tools, - QWidget *parent) +void ToolsSettingsAccessor::saveMesonTools(const std::vector &tools) { using namespace Constants; Store data; @@ -51,13 +58,13 @@ void ToolsSettingsAccessor::saveMesonTools(const std::vector entry_count++; } data.insert(ToolsSettings::ENTRY_COUNT, entry_count); - saveSettings(data, parent); + saveSettings(data, ICore::dialogParent()); } -std::vector ToolsSettingsAccessor::loadMesonTools(QWidget *parent) +std::vector ToolsSettingsAccessor::loadMesonTools() { using namespace Constants; - auto data = restoreSettings(parent); + auto data = restoreSettings(ICore::dialogParent()); auto entry_count = data.value(ToolsSettings::ENTRY_COUNT, 0).toInt(); std::vector result; for (auto toolIndex = 0; toolIndex < entry_count; toolIndex++) { diff --git a/src/plugins/mesonprojectmanager/toolssettingsaccessor.h b/src/plugins/mesonprojectmanager/toolssettingsaccessor.h index 61cc505d3a7..10fce88099d 100644 --- a/src/plugins/mesonprojectmanager/toolssettingsaccessor.h +++ b/src/plugins/mesonprojectmanager/toolssettingsaccessor.h @@ -14,8 +14,9 @@ class ToolsSettingsAccessor final : public Utils::UpgradingSettingsAccessor { public: ToolsSettingsAccessor(); - void saveMesonTools(const std::vector &tools, QWidget *parent); - std::vector loadMesonTools(QWidget *parent); + + void saveMesonTools(const std::vector &tools); + std::vector loadMesonTools(); }; } // namespace Internal From b9e327bb10c7cbe1c3fed403957c7489c6ea04c3 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 8 Sep 2023 13:31:41 +0200 Subject: [PATCH 19/21] Python: read installer settings for pyside wheel requirement This should be more stable than relying on a specific folder structure in the Qt Package. Change-Id: I20dea176df43c9f6e768f4db69ac4eb70633f01a Reviewed-by: Christian Stenger --- src/plugins/python/pyside.cpp | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp index 896620ae32e..bae0c0753af 100644 --- a/src/plugins/python/pyside.cpp +++ b/src/plugins/python/pyside.cpp @@ -86,17 +86,36 @@ void PySideInstaller::installPyside(const FilePath &python, { QMap availablePySides; - const std::optional qtInstallDir - = QtSupport::LinkWithQtSupport::linkedQt().tailRemoved("Tools/sdktool/share/qtcreator"); - if (qtInstallDir) { - const FilePath qtForPythonDir = qtInstallDir->pathAppended("QtForPython"); - for (const FilePath &versionDir : qtForPythonDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot)) { - FilePath requirements = versionDir.pathAppended("requirements.txt"); - if (requirements.exists()) - availablePySides[QVersionNumber::fromString(versionDir.fileName())] = requirements; + const Utils::QtcSettings *settings = Core::ICore::settings(QSettings::SystemScope); + + const FilePaths requirementsList + = Utils::transform(settings->value("Python/PySideWheelsRequirements").toList(), + &FilePath::fromSettings); + for (const FilePath &requirements : requirementsList) { + if (requirements.exists()) { + auto version = QVersionNumber::fromString(requirements.parentDir().fileName()); + availablePySides[version] = requirements; } } + if (requirementsList.isEmpty()) { // fallback remove in Qt Creator 13 + const QString hostQtTail = HostOsInfo::isMacHost() + ? QString("Tools/sdktool") + : QString("Tools/sdktool/share/qtcreator"); + + const std::optional qtInstallDir + = QtSupport::LinkWithQtSupport::linkedQt().tailRemoved(hostQtTail); + if (qtInstallDir) { + const FilePath qtForPythonDir = qtInstallDir->pathAppended("QtForPython"); + for (const FilePath &versionDir : + qtForPythonDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot)) { + FilePath requirements = versionDir.pathAppended("requirements.txt"); + if (!requirementsList.contains(requirements) && requirements.exists()) + availablePySides[QVersionNumber::fromString(versionDir.fileName())] + = requirements; + } + } + } auto install = new PipInstallTask(python); connect(install, &PipInstallTask::finished, install, &QObject::deleteLater); From 7033bbf7e0542b6ab7616859b048e458e19396b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 22 Sep 2023 22:18:48 +0200 Subject: [PATCH 20/21] lldb: Set environment variable to detect when running lldb from Creator Instead of relying on __name__ being 'lldbbridge', which can also be the case if the lldbbridge.py script is imported into a plain LLDB process, we can now look at the environment variable. This also lets us break out early from __lldb_init_module if the user has a `command script import llbdbridge.py` in their .lldbinit, or is automatically loading the bridge via the Qt Core debug script. Change-Id: Id8168c692ef66ce50119b7426ca85c7bc99d9503 Reviewed-by: Christian Stenger --- share/qtcreator/debugger/lldbbridge.py | 17 ++++++++++------- src/plugins/debugger/lldb/lldbengine.cpp | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index df1f57dc1c4..a421f829330 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -2068,6 +2068,13 @@ class Tester(Dumper): lldb.SBDebugger.Destroy(self.debugger) +if 'QT_CREATOR_LLDB_PROCESS' in os.environ: + # Initialize Qt Creator dumper + try: + theDumper = Dumper() + except Exception as error: + print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error)) + # ------------------------------ For use in LLDB ------------------------------ @@ -2437,6 +2444,9 @@ class SyntheticChildrenProvider(SummaryProvider): def __lldb_init_module(debugger, internal_dict): # Module is being imported in an LLDB session + if 'QT_CREATOR_LLDB_PROCESS' in os.environ: + # Let Qt Creator take care of its own dumper + return if not __name__ == 'qt': # Make available under global 'qt' name for consistency, @@ -2471,10 +2481,3 @@ def __lldb_init_module(debugger, internal_dict): % ("qt.SyntheticChildrenProvider", type_category)) debugger.HandleCommand('type category enable %s' % type_category) - - -if __name__ == "lldbbridge": - try: - theDumper = Dumper() - except Exception as error: - print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error)) diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 2d90f1e76c9..9d1717fff54 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -179,6 +179,7 @@ void LldbEngine::setupEngine() showMessage("STARTING LLDB: " + lldbCmd.toUserOutput()); Environment environment = runParameters().debugger.environment; + environment.appendOrSet("QT_CREATOR_LLDB_PROCESS", "1"); environment.appendOrSet("PYTHONUNBUFFERED", "1"); // avoid flushing problem on macOS DebuggerItem::addAndroidLldbPythonEnv(lldbCmd, environment); From 182d4540b3d5d5e28820f821757c724408080889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 22 Sep 2023 22:28:07 +0200 Subject: [PATCH 21/21] lldb: Add summary provider basic debugging facility Change-Id: I166ad0c2e7d4ed508b1f4a8eeaf5e1e2488a3b70 Reviewed-by: Christian Stenger --- share/qtcreator/debugger/lldbbridge.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index a421f829330..8c25b90c380 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -2077,18 +2077,14 @@ if 'QT_CREATOR_LLDB_PROCESS' in os.environ: # ------------------------------ For use in LLDB ------------------------------ +debug = print if 'QT_LLDB_SUMMARY_PROVIDER_DEBUG' in os.environ \ + else lambda *a, **k: None -from pprint import pprint - -__module__ = sys.modules[__name__] -DEBUG = False if not hasattr(__module__, 'DEBUG') else DEBUG - +debug(f"Loading lldbbridge.py from {__file__}") class LogMixin(): @staticmethod def log(message='', log_caller=False, frame=1, args=''): - if not DEBUG: - return if log_caller: message = ": " + message if len(message) else '' # FIXME: Compute based on first frame not in this class? @@ -2097,7 +2093,7 @@ class LogMixin(): localz = frame.f_locals instance = str(localz["self"]) + "." if 'self' in localz else '' message = "%s%s(%s)%s" % (instance, fn, args, message) - print(message) + debug(message) @staticmethod def log_fn(arg_str=''): @@ -2448,6 +2444,8 @@ def __lldb_init_module(debugger, internal_dict): # Let Qt Creator take care of its own dumper return + debug("Initializing module with", debugger) + if not __name__ == 'qt': # Make available under global 'qt' name for consistency, # and so we can refer to SyntheticChildrenProvider below.