From 361e0f0eb4a9d6a3277cf05993163b6bc5f27d21 Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 16 Jun 2021 22:10:04 +0200 Subject: [PATCH 01/11] QmlDesigner: Fix testRewriterView and add testRewriterView2 The new test testRewriterView2 is a variant with slightly different setup. We have to wait for the type information to be parsed. Change-Id: I231b8d13e6be5ad52a8c3b72ebaf81c13a24f782 Reviewed-by: Thomas Hartmann Reviewed-by: hjk --- .../qmldesigner/coretests/tst_testcore.cpp | 108 ++++++++++++++++-- .../qml/qmldesigner/coretests/tst_testcore.h | 1 + 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index b3633ff614a..c512b372cd0 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -272,14 +272,14 @@ void tst_TestCore::loadEmptyCoreModel() QVERIFY(compareTree(testRewriterView1->rootModelNode(), testRewriterView2->rootModelNode())); } -void tst_TestCore::testRewriterView() +void tst_TestCore::testRewriterView2() { try { QPlainTextEdit textEdit; - textEdit.setPlainText("import QtQuick 2.15;\n\nItem {\n}\n"); + textEdit.setPlainText("import QtQuick 2.15;\n\nRectangle {\n}\n"); NotIndentingTextEditModifier textModifier(&textEdit); - QScopedPointer model(Model::create("QtQuick.Item")); + QScopedPointer model(Model::create("QtQuick.Rectangle", 2, 1)); QVERIFY(model.data()); QScopedPointer view(new TestView(model.data())); @@ -292,15 +292,20 @@ void tst_TestCore::testRewriterView() testRewriterView->setTextModifier(&textModifier); model->attachView(testRewriterView.data()); - ModelNode childNode(addNodeListChild(rootModelNode, "QtQuick.Rectangle", 1, 0, "data")); + while (testRewriterView->hasIncompleteTypeInformation()) { + QApplication::processEvents(QEventLoop::AllEvents, 1000); + } + + ModelNode childNode(addNodeListChild(rootModelNode, "QtQuick.Rectangle", 2, 11, "data")); + QVERIFY(childNode.isValid()); childNode.setIdWithoutRefactoring("childNode"); - ModelNode childNode2(addNodeListChild(childNode, "QtQuick.Rectangle", 1, 0, "data")); + ModelNode childNode2(addNodeListChild(childNode, "QtQuick.Rectangle", 2, 11, "data")); childNode2.setIdWithoutRefactoring("childNode2"); - ModelNode childNode3(addNodeListChild(childNode2, "QtQuick.Rectangle", 1, 0, "data")); + ModelNode childNode3(addNodeListChild(childNode2, "QtQuick.Rectangle", 2, 11, "data")); childNode3.setIdWithoutRefactoring("childNode3"); - ModelNode childNode4(addNodeListChild(childNode3, "QtQuick.Rectangle", 1, 0, "data")); + ModelNode childNode4(addNodeListChild(childNode3, "QtQuick.Rectangle", 2, 11, "data")); childNode4.setIdWithoutRefactoring("childNode4"); QVERIFY(childNode.isValid()); @@ -326,7 +331,7 @@ void tst_TestCore::testRewriterView() testRewriterView->modelToTextMerger()->applyChanges(); - childNode = addNodeListChild(rootModelNode, "QtQuick.Rectangle", 2, 0, "data"); + childNode = addNodeListChild(rootModelNode, "QtQuick.Rectangle", 2, 11, "data"); QVERIFY(testRewriterView->modelToTextMerger()->isNodeScheduledForAddition(childNode)); testRewriterView->modelToTextMerger()->applyChanges(); @@ -334,6 +339,93 @@ void tst_TestCore::testRewriterView() childNode.variantProperty("x").setValue(70); childNode.variantProperty("y").setValue(90); + QCOMPARE(testRewriterView->modelToTextMerger() + ->findAddedVariantProperty(childNode.variantProperty("x")) + .value(), + QVariant(70)); + QCOMPARE(testRewriterView->modelToTextMerger() + ->findAddedVariantProperty(childNode.variantProperty("y")) + .value(), + QVariant(90)); + + model->detachView(testRewriterView.data()); + } catch (Exception &e) { + QFAIL(qPrintable(e.description())); + } +} + +void tst_TestCore::testRewriterView() +{ + try { + const QLatin1String qmlString("import QtQuick 2.15\n" + "Rectangle {\n" + "}\n"); + + QPlainTextEdit textEdit; + textEdit.setPlainText(qmlString); + NotIndentingTextEditModifier textModifier(&textEdit); + + QScopedPointer model(Model::create("QtQuick.Item", 2, 15)); + QVERIFY(model.data()); + + QScopedPointer testRewriterView(new TestRewriterView()); + testRewriterView->setTextModifier(&textModifier); + testRewriterView->setCheckSemanticErrors(true); + model->attachView(testRewriterView.data()); + + while (testRewriterView->hasIncompleteTypeInformation()) { + QApplication::processEvents(QEventLoop::AllEvents, 1000); + } + + textEdit.setPlainText(qmlString); + + ModelNode rootModelNode = testRewriterView->rootModelNode(); + + ModelNode childNode(addNodeListChild(rootModelNode, "QtQuick.Rectangle", 2, 11, "data")); + + QVERIFY(childNode.isValid()); + childNode.setIdWithoutRefactoring("childNode"); + + ModelNode childNode2(addNodeListChild(childNode, "QtQuick.Rectangle", 2, 11, "data")); + childNode2.setIdWithoutRefactoring("childNode2"); + ModelNode childNode3(addNodeListChild(childNode2, "QtQuick.Rectangle", 2, 11, "data")); + childNode3.setIdWithoutRefactoring("childNode3"); + ModelNode childNode4(addNodeListChild(childNode3, "QtQuick.Rectangle", 2, 11, "data")); + childNode4.setIdWithoutRefactoring("childNode4"); + + QVERIFY(childNode.isValid()); + QVERIFY(childNode2.isValid()); + QVERIFY(childNode3.isValid()); + QVERIFY(childNode4.isValid()); + + testRewriterView->setModificationGroupActive(true); + + childNode.destroy(); + + QVERIFY(!childNode.isValid()); + QVERIFY(!childNode2.isValid()); + QVERIFY(!childNode3.isValid()); + QVERIFY(!childNode4.isValid()); + + QVERIFY(testRewriterView->modelToTextMerger()->isNodeScheduledForRemoval(childNode)); + QVERIFY(!testRewriterView->modelToTextMerger()->isNodeScheduledForRemoval(childNode2)); + QVERIFY(!testRewriterView->modelToTextMerger()->isNodeScheduledForRemoval(childNode3)); + QVERIFY(!testRewriterView->modelToTextMerger()->isNodeScheduledForRemoval(childNode4)); + + QVERIFY(!rootModelNode.hasProperty("data")); + + testRewriterView->modelToTextMerger()->applyChanges(); + + childNode = addNodeListChild(rootModelNode, "QtQuick.Rectangle", 2, 11, "data"); + QVERIFY(testRewriterView->modelToTextMerger()->isNodeScheduledForAddition(childNode)); + QVERIFY(childNode.isValid()); + + testRewriterView->modelToTextMerger()->applyChanges(); + + QVERIFY(childNode.isValid()); + childNode.variantProperty("x").setValue(70); + childNode.variantProperty("y").setValue(90); + QCOMPARE(testRewriterView->modelToTextMerger()->findAddedVariantProperty(childNode.variantProperty("x")).value(), QVariant(70)); QCOMPARE(testRewriterView->modelToTextMerger()->findAddedVariantProperty(childNode.variantProperty("y")).value(), QVariant(90)); diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h index 51bd8f496d6..d052bf950e5 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.h +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.h @@ -95,6 +95,7 @@ private slots: // unit tests Rewriter // void testRewriterView(); + void testRewriterView2(); void testRewriterErrors(); void testRewriterChangeId(); void testRewriterRemoveId(); From f787afe732a44996fb36e94218a090ff100dc716 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 15 Jul 2021 10:31:05 +0200 Subject: [PATCH 02/11] Tracing: Fix a couple of warnings regarding parameter injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameter "XYZ" is not declared. Injection of parameters into signal handlers is deprecated. Use JavaScript functions with formal parameters instead. Change-Id: I3aea1af6bb6559f26dc83b8c20ada11885b715f9 Reviewed-by: Henning Gründl --- src/libs/tracing/qml/CategoryLabel.qml | 4 ++-- src/libs/tracing/qml/MainView.qml | 10 +++++++--- src/libs/tracing/qml/Overview.qml | 4 ++-- src/libs/tracing/qml/TimelineLabels.qml | 4 ++-- src/libs/tracing/qml/TimelineRulers.qml | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/libs/tracing/qml/CategoryLabel.qml b/src/libs/tracing/qml/CategoryLabel.qml index cfd98c9a31f..e70dba108c8 100644 --- a/src/libs/tracing/qml/CategoryLabel.qml +++ b/src/libs/tracing/qml/CategoryLabel.qml @@ -72,7 +72,7 @@ Item { DropArea { id: dropArea - onPositionChanged: { + onPositionChanged: (drag) => { var sourceIndex = drag.source.visualIndex; if (drag.source.y === 0) { // special case for first position: Always swap, no matter if upper border touched. @@ -129,7 +129,7 @@ Item { labelContainer.selectNextBySelectionId(label.id); } } - onSetRowHeight: { + onSetRowHeight: (newHeight) => { labelsArea.parentModel.setExpandedRowHeight(index + 1, newHeight); loader.height = labelsArea.parentModel.rowHeight(index + 1); } diff --git a/src/libs/tracing/qml/MainView.qml b/src/libs/tracing/qml/MainView.qml index 0bdf9346ec7..17a6a29e3c0 100644 --- a/src/libs/tracing/qml/MainView.qml +++ b/src/libs/tracing/qml/MainView.qml @@ -151,8 +151,12 @@ Rectangle { zoomer: zoomControl reverseSelect: shiftPressed - onMoveCategories: content.moveCategories(sourceIndex, targetIndex) - onSelectItem: content.select(modelIndex, eventIndex) + onMoveCategories: (sourceIndex, targetIndex) => { + content.moveCategories(sourceIndex, targetIndex) + } + onSelectItem: (modelIndex, eventIndex) => { + content.select(modelIndex, eventIndex) + } } TimeDisplay { @@ -206,7 +210,7 @@ Rectangle { onWidthChanged: selectionRange.update(); - onPropagateSelection: { + onPropagateSelection: (newModel, newItem) => { if (lockItemSelection || (newModel === selectedModel && newItem === selectedItem)) return; diff --git a/src/libs/tracing/qml/Overview.qml b/src/libs/tracing/qml/Overview.qml index d2b3eb29417..5152e4bc965 100644 --- a/src/libs/tracing/qml/Overview.qml +++ b/src/libs/tracing/qml/Overview.qml @@ -168,10 +168,10 @@ Rectangle { } } - onPressed: { + onPressed: (mouse) => { jumpTo(mouse.x); } - onPositionChanged: { + onPositionChanged: (mouse) => { jumpTo(mouse.x); } } diff --git a/src/libs/tracing/qml/TimelineLabels.qml b/src/libs/tracing/qml/TimelineLabels.qml index c675ce1b6d9..88fadfa6342 100644 --- a/src/libs/tracing/qml/TimelineLabels.qml +++ b/src/libs/tracing/qml/TimelineLabels.qml @@ -115,14 +115,14 @@ Flickable { categories.selectItem(index, eventId) } - onSelectNextBySelectionId: { + onSelectNextBySelectionId: (selectionId) => { categories.selectItem(index, modelData.nextItemBySelectionId( selectionId, zoomer.rangeStart, categories.selectedModel === index ? categories.selectedItem : -1)); } - onSelectPrevBySelectionId: { + onSelectPrevBySelectionId: (selectionId) => { categories.selectItem(index, modelData.prevItemBySelectionId( selectionId, zoomer.rangeStart, categories.selectedModel === index ? categories.selectedItem : diff --git a/src/libs/tracing/qml/TimelineRulers.qml b/src/libs/tracing/qml/TimelineRulers.qml index 0e052e716ff..99c9d533a37 100644 --- a/src/libs/tracing/qml/TimelineRulers.qml +++ b/src/libs/tracing/qml/TimelineRulers.qml @@ -44,7 +44,7 @@ Item { anchors.right: parent.right height: scaleHeight - onClicked: { + onClicked: (mouse) => { rulersModel.append({ timestamp: (mouse.x + contentX) * viewTimePerPixel + windowStart }); From 88310df13d63a33ce20325e141122215d7bf6091 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 15 Jul 2021 13:56:06 +0200 Subject: [PATCH 03/11] Docker: Handle docker daemon Avoid calling the docker daemon again and again if it is currently not running. Change-Id: I17afa2eca098589bd362149d18e090252ef83f6e Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 72 +++++++++++++++++++++++++++-- src/plugins/docker/dockerdevice.h | 2 + 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index b533c67d335..b355eb5d9b3 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,7 @@ #include #include #include +#include #include #ifdef Q_OS_UNIX @@ -312,6 +314,14 @@ public: QFileSystemWatcher m_mergedDirWatcher; Environment m_cachedEnviroment; + + enum LocalAccessState + { + NotEvaluated, + NoDaemon, + Accessible, + NotAccessible + } m_accessible = NotEvaluated; }; class DockerDeviceWidget final : public IDeviceWidget @@ -337,6 +347,17 @@ public: m_repoLineEdit->setText(data.repo); m_repoLineEdit->setEnabled(false); + auto daemonStateLabel = new QLabel(tr("Daemon state:")); + m_daemonReset = new QToolButton; + m_daemonReset->setIcon(Icons::INFO.icon()); + m_daemonReset->setToolTip(tr("Daemon state not evaluated.")); + + connect(m_daemonReset, &QToolButton::clicked, this, [this, dockerDevice] { + dockerDevice->resetDaemonState(); + m_daemonReset->setIcon(Icons::INFO.icon()); + m_daemonReset->setToolTip(tr("Daemon state not evaluated.")); + }); + m_runAsOutsideUser = new QCheckBox(tr("Run as outside user")); m_runAsOutsideUser->setToolTip(tr("Use user ID and group ID of the user running Qt Creator " "in the Docker container.")); @@ -369,6 +390,16 @@ public: logView->clear(); dockerDevice->tryCreateLocalFileAccess(); m_kitItemDetector.autoDetect(id); + + if (!dockerDevice->isDaemonRunning()) { + logView->append(tr("Docker daemon appears to be not running.")); + m_daemonReset->setToolTip(tr("Daemon not running. Push to reset the state.")); + m_daemonReset->setIcon(Icons::CRITICAL.icon()); + } else { + m_daemonReset->setToolTip(tr("Docker daemon running.")); + m_daemonReset->setIcon(Icons::OK.icon()); + + } }); connect(undoAutoDetectButton, &QPushButton::clicked, this, [this, logView, id = data.id()] { @@ -381,6 +412,7 @@ public: Form { idLabel, m_idLineEdit, Break(), repoLabel, m_repoLineEdit, Break(), + daemonStateLabel, m_daemonReset, Break(), m_runAsOutsideUser, Break(), tr("Paths to mount:"), m_pathsLineEdit, Break(), Column { @@ -397,6 +429,7 @@ public: private: QLineEdit *m_idLineEdit; QLineEdit *m_repoLineEdit; + QToolButton *m_daemonReset; QCheckBox *m_runAsOutsideUser; QLineEdit *m_pathsLineEdit; @@ -648,7 +681,7 @@ void DockerDevice::tryCreateLocalFileAccess() const void DockerDevicePrivate::stopCurrentContainer() { - if (m_container.isEmpty()) + if (m_container.isEmpty() || m_accessible == NoDaemon) return; QtcProcess proc; @@ -662,7 +695,7 @@ void DockerDevicePrivate::stopCurrentContainer() void DockerDevicePrivate::tryCreateLocalFileAccess() { - if (!m_container.isEmpty()) + if (!m_container.isEmpty() || m_accessible == NoDaemon) return; QString tempFileName; @@ -701,6 +734,14 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() LOG("RES: " << m_shell->result() << " STDOUT: " << m_shell->readAllStandardOutput() << " STDERR: " << m_shell->readAllStandardError()); + if (m_shell->exitCode() != 0) { + m_accessible = NoDaemon; + LOG("DOCKER DAEMON NOT RUNNING?"); + MessageManager::writeFlashing(tr("Docker Daemon appears to be not running. " + "Verify daemon is up and running and reset the " + "docker daemon on the docker device settings page " + "or restart Qt Creator.")); + } } m_container.clear(); }); @@ -723,10 +764,11 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() break; } } - if (i == 20) { + if (i == 20 || m_accessible == NoDaemon) { qWarning("Docker cid file empty."); return; // No } + qApp->processEvents(); // FIXME turn this for-loop into QEventLoop QThread::msleep(100); } @@ -753,11 +795,13 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() // of using wsl or a named pipe. // TODO investigate how to make it possible nevertheless. m_mergedDir.clear(); + m_accessible = NotAccessible; MessageManager::writeSilently(tr("This is expected on Windows.")); return; } } + m_accessible = Accessible; m_mergedDirWatcher.addPath(m_mergedDir); } @@ -766,6 +810,24 @@ bool DockerDevice::hasLocalFileAccess() const return !d->m_mergedDir.isEmpty(); } +bool DockerDevice::isDaemonRunning() const +{ + switch (d->m_accessible) { + case DockerDevicePrivate::NoDaemon: + return false; + case DockerDevicePrivate::NotEvaluated: // FIXME? + case DockerDevicePrivate::Accessible: + case DockerDevicePrivate::NotAccessible: + return true; + } + return false; +} + +void DockerDevice::resetDaemonState() +{ + d->m_accessible = DockerDevicePrivate::NotEvaluated; +} + void DockerDevice::setMounts(const QStringList &mounts) const { d->m_data.mounts = mounts; @@ -1183,7 +1245,7 @@ bool DockerDevice::writeFileContents(const Utils::FilePath &filePath, const QByt void DockerDevice::runProcess(QtcProcess &process) const { tryCreateLocalFileAccess(); - if (d->m_container.isEmpty()) { + if (d->m_container.isEmpty() || d->m_accessible == DockerDevicePrivate::NoDaemon) { LOG("No container set to run " << process.commandLine().toUserOutput()); QTC_CHECK(false); process.setResult(QtcProcess::StartFailed); @@ -1237,6 +1299,8 @@ void DockerDevicePrivate::fetchSystemEnviroment() int DockerDevicePrivate::runSynchronously(const CommandLine &cmd) const { + if (m_accessible == NoDaemon) + return -1; CommandLine dcmd{"docker", {"exec", m_container}}; dcmd.addArgs(cmd); diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 35ba3f1c87f..0c3eb5cb654 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -107,6 +107,8 @@ public: void tryCreateLocalFileAccess() const; bool hasLocalFileAccess() const; + bool isDaemonRunning() const; + void resetDaemonState(); void setMounts(const QStringList &mounts) const; Utils::FilePath mapToLocalAccess(const Utils::FilePath &filePath) const; From 64da7b15d9d39a4580d5316d9bc59849e19c2621 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 15 Jul 2021 11:52:40 +0200 Subject: [PATCH 04/11] Disambiguate the build actions in locator and shortcut settings By setting a whatsThis text and using that in locator. Amends 3bd490acdb41d287c1870a8b064b92a858d35352 Fixes: QTCREATORBUG-26002 Change-Id: I55c196456eb0eaec880da4479a0a75105f649b01 Reviewed-by: Christian Kandeler --- src/plugins/coreplugin/menubarfilter.cpp | 4 ++- .../projectexplorer/projectexplorer.cpp | 28 +++++++++++++++---- .../qbsprojectmanagerplugin.cpp | 4 +++ .../qmakeprojectmanagerplugin.cpp | 6 ++-- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/plugins/coreplugin/menubarfilter.cpp b/src/plugins/coreplugin/menubarfilter.cpp index 354a0e7b367..207959f2d74 100644 --- a/src/plugins/coreplugin/menubarfilter.cpp +++ b/src/plugins/coreplugin/menubarfilter.cpp @@ -101,7 +101,9 @@ QList MenuBarFilter::matchesForAction(QAction *action, QList entries; if (!m_enabledActions.contains(action)) return entries; - const QString text = Utils::stripAccelerator(action->text()); + const QString whatsThis = action->whatsThis(); + const QString text = Utils::stripAccelerator(action->text()) + + (whatsThis.isEmpty() ? QString() : QString(" (" + whatsThis + ")")); if (QMenu *menu = action->menu()) { if (processedMenus.contains(menu)) return entries; diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index c52154cb59f..a6e7046c098 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -1137,36 +1137,48 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er // deploy session dd->m_deploySessionAction = new QAction(tr("Deploy"), this); + dd->m_deploySessionAction->setWhatsThis(tr("Deploy All Projects")); cmd = ActionManager::registerAction(dd->m_deploySessionAction, Constants::DEPLOYSESSION); + cmd->setDescription(dd->m_deploySessionAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_ALLPROJECTS); msessionContextMenu->addAction(cmd, Constants::G_SESSION_BUILD); // rebuild session action dd->m_rebuildSessionAction = new QAction(Icons::REBUILD.icon(), tr("Rebuild"), this); + dd->m_rebuildSessionAction->setWhatsThis(tr("Rebuild All Projects")); cmd = ActionManager::registerAction(dd->m_rebuildSessionAction, Constants::REBUILDSESSION); + cmd->setDescription(dd->m_rebuildSessionAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_ALLPROJECTS); msessionContextMenu->addAction(cmd, Constants::G_SESSION_REBUILD); dd->m_rebuildSessionForAllConfigsAction = new QAction(Icons::REBUILD.icon(), tr("Rebuild"), this); + dd->m_rebuildSessionForAllConfigsAction->setWhatsThis( + tr("Rebuild All Projects for All Configurations")); cmd = ActionManager::registerAction(dd->m_rebuildSessionForAllConfigsAction, Constants::REBUILDSESSIONALLCONFIGS); + cmd->setDescription(dd->m_rebuildSessionForAllConfigsAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_ALLPROJECTS_ALLCONFIGURATIONS); msessionContextMenu->addAction(cmd, Constants::G_SESSION_REBUILD); // clean session dd->m_cleanSessionAction = new QAction(Utils::Icons::CLEAN.icon(), tr("Clean"), this); + dd->m_cleanSessionAction->setWhatsThis(tr("Clean All Projects")); cmd = ActionManager::registerAction(dd->m_cleanSessionAction, Constants::CLEANSESSION); + cmd->setDescription(dd->m_cleanSessionAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_ALLPROJECTS); msessionContextMenu->addAction(cmd, Constants::G_SESSION_REBUILD); dd->m_cleanSessionForAllConfigsAction = new QAction(Utils::Icons::CLEAN.icon(), tr("Clean"), this); + dd->m_cleanSessionForAllConfigsAction->setWhatsThis( + tr("Clean All Projects for All Configurations")); cmd = ActionManager::registerAction(dd->m_cleanSessionForAllConfigsAction, Constants::CLEANSESSIONALLCONFIGS); + cmd->setDescription(dd->m_cleanSessionForAllConfigsAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_ALLPROJECTS_ALLCONFIGURATIONS); msessionContextMenu->addAction(cmd, Constants::G_SESSION_REBUILD); @@ -1212,39 +1224,45 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er // deploy action dd->m_deployAction = new QAction(tr("Deploy"), this); + dd->m_deployAction->setWhatsThis(tr("Deploy Project")); cmd = ActionManager::registerAction(dd->m_deployAction, Constants::DEPLOY); cmd->setAttribute(Command::CA_UpdateText); - cmd->setDescription(dd->m_deployAction->text()); + cmd->setDescription(dd->m_deployAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_PROJECT); // rebuild action dd->m_rebuildAction = new QAction(Icons::REBUILD.icon(), tr("Rebuild"), this); + dd->m_rebuildAction->setWhatsThis(tr("Rebuild Project")); cmd = ActionManager::registerAction(dd->m_rebuildAction, Constants::REBUILD); cmd->setAttribute(Command::CA_UpdateText); - cmd->setDescription(dd->m_rebuildAction->text()); + cmd->setDescription(dd->m_rebuildAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_PROJECT); dd->m_rebuildProjectForAllConfigsAction = new QAction(Icons::REBUILD.icon(), tr("Rebuild"), this); + dd->m_rebuildProjectForAllConfigsAction->setWhatsThis( + tr("Rebuild Project for All Configurations")); cmd = ActionManager::registerAction(dd->m_rebuildProjectForAllConfigsAction, Constants::REBUILDALLCONFIGS); cmd->setAttribute(Command::CA_UpdateText); - cmd->setDescription(dd->m_rebuildProjectForAllConfigsAction->text()); + cmd->setDescription(dd->m_rebuildProjectForAllConfigsAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_PROJECT_ALLCONFIGURATIONS); // clean action dd->m_cleanAction = new QAction(Utils::Icons::CLEAN.icon(), tr("Clean"), this); + dd->m_cleanAction->setWhatsThis(tr("Clean Project")); cmd = ActionManager::registerAction(dd->m_cleanAction, Constants::CLEAN); cmd->setAttribute(Command::CA_UpdateText); - cmd->setDescription(dd->m_cleanAction->text()); + cmd->setDescription(dd->m_cleanAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_PROJECT); dd->m_cleanProjectForAllConfigsAction = new QAction(Utils::Icons::CLEAN.icon(), tr("Clean"), this); + dd->m_cleanProjectForAllConfigsAction->setWhatsThis(tr("Clean Project for All Configurations")); cmd = ActionManager::registerAction(dd->m_cleanProjectForAllConfigsAction, Constants::CLEANALLCONFIGS); cmd->setAttribute(Command::CA_UpdateText); - cmd->setDescription(dd->m_cleanProjectForAllConfigsAction->text()); + cmd->setDescription(dd->m_cleanProjectForAllConfigsAction->whatsThis()); mbuild->addAction(cmd, Constants::G_BUILD_PROJECT_ALLCONFIGURATIONS); // cancel build action diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp index 992047bb1fa..f96a86656eb 100644 --- a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp +++ b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp @@ -190,9 +190,11 @@ bool QbsProjectManagerPlugin::initialize(const QStringList &arguments, QString * this, &QbsProjectManagerPlugin::cleanProductContextMenu); m_cleanProduct = new QAction(Utils::Icons::CLEAN.icon(), tr("Clean"), this); + m_cleanProduct->setWhatsThis(tr("Clean Product")); command = Core::ActionManager::registerAction(m_cleanProduct, Constants::ACTION_CLEAN_PRODUCT); command->setAttribute(Core::Command::CA_Hide); command->setAttribute(Core::Command::CA_UpdateText); + command->setDescription(m_cleanProduct->whatsThis()); mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_PRODUCT); connect(m_cleanProduct, &QAction::triggered, this, &QbsProjectManagerPlugin::cleanProduct); @@ -205,10 +207,12 @@ bool QbsProjectManagerPlugin::initialize(const QStringList &arguments, QString * this, &QbsProjectManagerPlugin::rebuildProductContextMenu); m_rebuildProduct = new QAction(ProjectExplorer::Icons::REBUILD.icon(), tr("Rebuild"), this); + m_rebuildProduct->setWhatsThis(tr("Rebuild Product")); command = Core::ActionManager::registerAction(m_rebuildProduct, Constants::ACTION_REBUILD_PRODUCT); command->setAttribute(Core::Command::CA_Hide); command->setAttribute(Core::Command::CA_UpdateText); + command->setDescription(m_rebuildProduct->whatsThis()); mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_PRODUCT); connect(m_rebuildProduct, &QAction::triggered, this, &QbsProjectManagerPlugin::rebuildProduct); diff --git a/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp b/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp index 01da9cad48f..603f683f304 100644 --- a/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp @@ -246,19 +246,21 @@ bool QmakeProjectManagerPlugin::initialize(const QStringList &arguments, QString d->m_rebuildSubProjectAction = new QAction(Icons::REBUILD.icon(), tr("Rebuild"), this); + d->m_rebuildSubProjectAction->setWhatsThis(tr("Rebuild Subproject")); command = ActionManager::registerAction(d->m_rebuildSubProjectAction, Constants::REBUILDSUBDIR, projectContext); command->setAttribute(Command::CA_Hide); command->setAttribute(Command::CA_UpdateText); - command->setDescription(d->m_rebuildSubProjectAction->text()); + command->setDescription(d->m_rebuildSubProjectAction->whatsThis()); mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_SUBPROJECT); connect(d->m_rebuildSubProjectAction, &QAction::triggered, d, &QmakeProjectManagerPluginPrivate::rebuildSubDirContextMenu); d->m_cleanSubProjectAction = new QAction(Utils::Icons::CLEAN.icon(),tr("Clean"), this); + d->m_cleanSubProjectAction->setWhatsThis(tr("Clean Subproject")); command = ActionManager::registerAction(d->m_cleanSubProjectAction, Constants::CLEANSUBDIR, projectContext); command->setAttribute(Command::CA_Hide); command->setAttribute(Command::CA_UpdateText); - command->setDescription(d->m_cleanSubProjectAction->text()); + command->setDescription(d->m_cleanSubProjectAction->whatsThis()); mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_SUBPROJECT); connect(d->m_cleanSubProjectAction, &QAction::triggered, d, &QmakeProjectManagerPluginPrivate::cleanSubDirContextMenu); From 037d1283beff991c67798c63be21d34ba46f4cbc Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 15 Jul 2021 16:09:35 +0200 Subject: [PATCH 05/11] Docker: Disable msvc toolchain detection on container This avoids detecting desktop toolchains as on device and mixing them up with toolchains used inside kits. This also disables clang-cl toolchains on device as they are Windows specific as well. Change-Id: Icd098d605e3bcf8015f96fbef90e9f9ae2496603 Reviewed-by: hjk --- src/plugins/projectexplorer/msvctoolchain.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp index d7f12d4a7ca..cc637f17fe1 100644 --- a/src/plugins/projectexplorer/msvctoolchain.cpp +++ b/src/plugins/projectexplorer/msvctoolchain.cpp @@ -1861,7 +1861,10 @@ static void detectCppBuildTools2015(QList *list) QList MsvcToolChainFactory::autoDetect(const QList &alreadyKnown, const IDevice::Ptr &device) { - Q_UNUSED(device) + if (!device.isNull()) { + // FIXME currently no support for msvc toolchains on a device + return {}; + } QList results; @@ -1969,8 +1972,10 @@ QList ClangClToolChainFactory::autoDetect(const QList const IDevice::Ptr &device) { Q_UNUSED(alreadyKnown) - Q_UNUSED(device) // FIXME: Use it. - + if (!device.isNull()) { + // FIXME currently no support for msvc toolchains on a device + return {}; + } #ifdef Q_OS_WIN64 const char registryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\LLVM\\LLVM"; #else From 9857985013edc09e7cf1a24887e82c7fb70cbcd9 Mon Sep 17 00:00:00 2001 From: Tim Jenssen Date: Thu, 15 Jul 2021 14:09:20 +0200 Subject: [PATCH 06/11] macOS: workaround for missing libqsqlite.dylib Change-Id: I667d144035a36aed55f2bdc3e6e021832c058236 Reviewed-by: Tim Jenssen --- scripts/deployqtHelper_mac.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/deployqtHelper_mac.sh b/scripts/deployqtHelper_mac.sh index 867fa5c6988..97cefee46bf 100755 --- a/scripts/deployqtHelper_mac.sh +++ b/scripts/deployqtHelper_mac.sh @@ -67,6 +67,18 @@ if [ -d "$assetimporterSrcDir" ]; then fi fi +# workaround for Qt 6.2: +# - QTBUG-94796 macdeployqt does not deploy /Contents/PlugIns/sqldrivers/libqsqlite.dylib anymore +sqldriversDestDir="$app_path/Contents/PlugIns/sqldrivers" +sqldriversSrcDir="$plugin_src/sqldrivers" +if [ -d "$sqldriversSrcDir" ]; then + if [ ! -d "$sqldriversDestDir" ]; then + echo "- Copying sqlitedriver plugin" + mkdir -p "$sqldriversDestDir" + cp "$sqldriversSrcDir/libqsqlite.dylib" "$sqldriversDestDir/libqsqlite.dylib" + fi +fi + # copy Qt Quick 2 imports imports2Dir="$app_path/Contents/Imports/qtquick2" if [ -d "$quick2_src" ]; then From f29d7b896f4757719c68b5ed14235ea64f4124cd Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 6 Jul 2021 14:00:45 +0200 Subject: [PATCH 07/11] Restore support for CMake < 3.11 Prior to CMake 3.11, the SOURCES could not be omitted when calling add_executable. This change ensures that SOURCES are passed, at least when building against Qt < 6.2. Change-Id: I83bd82fe12364523ce6954d145b632df7210d118 Reviewed-by: Christian Stenger --- src/libs/tracing/CMakeLists.txt | 22 +++++++++++-------- src/plugins/perfprofiler/CMakeLists.txt | 19 +++++++++------- src/plugins/qmlprofiler/CMakeLists.txt | 19 +++++++++------- .../tracing/flamegraphview/CMakeLists.txt | 11 +++++----- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/libs/tracing/CMakeLists.txt b/src/libs/tracing/CMakeLists.txt index cb46eb21b03..e44c9dc57b3 100644 --- a/src/libs/tracing/CMakeLists.txt +++ b/src/libs/tracing/CMakeLists.txt @@ -32,17 +32,13 @@ set(TRACING_CPP_SOURCES tracing_global.h ) -add_qtc_library(Tracing - FEATURE_INFO - DEPENDS Utils Qt5::Qml Qt5::Quick - PUBLIC_DEPENDS Qt5::Widgets - SOURCES - ${TEST_SOURCES} -) - if(${Qt5_VERSION} VERSION_LESS "6.2.0") - extend_qtc_library(Tracing + add_qtc_library(Tracing + FEATURE_INFO + DEPENDS Utils Qt5::Qml Qt5::Quick + PUBLIC_DEPENDS Qt5::Widgets SOURCES + ${TEST_SOURCES} ${TRACING_CPP_SOURCES} qml/tracing.qrc ) @@ -51,6 +47,14 @@ else() # < Qt 6.2 return() endif() + add_qtc_library(Tracing + FEATURE_INFO + DEPENDS Utils Qt5::Qml Qt5::Quick + PUBLIC_DEPENDS Qt5::Widgets + SOURCES + ${TEST_SOURCES} + ) + set(TRACING_QML_FILES qml/ButtonsBar.qml qml/CategoryLabel.qml diff --git a/src/plugins/perfprofiler/CMakeLists.txt b/src/plugins/perfprofiler/CMakeLists.txt index f7f6c91aa04..d83e9e65523 100644 --- a/src/plugins/perfprofiler/CMakeLists.txt +++ b/src/plugins/perfprofiler/CMakeLists.txt @@ -38,16 +38,12 @@ set(PERFPROFILER_CPP_SOURCES perftracepointdialog.cpp perftracepointdialog.h perftracepointdialog.ui ) -add_qtc_plugin(PerfProfiler - DEPENDS Tracing Qt5::QuickWidgets - PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport - SOURCES - ${TEST_SOURCES} -) - if(${Qt5_VERSION} VERSION_LESS "6.2.0") - extend_qtc_plugin(PerfProfiler + add_qtc_plugin(PerfProfiler + DEPENDS Tracing Qt5::QuickWidgets + PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport SOURCES + ${TEST_SOURCES} ${PERFPROFILER_CPP_SOURCES} perfprofiler.qrc ) @@ -56,6 +52,13 @@ else() # < Qt 6.2 return() endif() + add_qtc_plugin(PerfProfiler + DEPENDS Tracing Qt5::QuickWidgets + PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport + SOURCES + ${TEST_SOURCES} + ) + qt_add_resources(PerfProfiler perfprofiler PREFIX "/perfprofiler" tracepoints.sh diff --git a/src/plugins/qmlprofiler/CMakeLists.txt b/src/plugins/qmlprofiler/CMakeLists.txt index 295bc3d0bb9..c5fa5399f86 100644 --- a/src/plugins/qmlprofiler/CMakeLists.txt +++ b/src/plugins/qmlprofiler/CMakeLists.txt @@ -69,16 +69,12 @@ set(QMLPROFILER_CPP_SOURCES scenegraphtimelinemodel.cpp scenegraphtimelinemodel.h ) -add_qtc_plugin(QmlProfiler - DEPENDS QmlDebug QmlJS Tracing Qt5::QuickWidgets - PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport TextEditor - SOURCES - ${TEST_SOURCES} -) - if(${Qt5_VERSION} VERSION_LESS "6.2.0") - extend_qtc_plugin(QmlProfiler + add_qtc_plugin(QmlProfiler + DEPENDS QmlDebug QmlJS Tracing Qt5::QuickWidgets + PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport TextEditor SOURCES + ${TEST_SOURCES} ${QMLPROFILER_CPP_SOURCES} qml/qmlprofiler.qrc ) @@ -87,6 +83,13 @@ else() # < Qt 6.2 return() endif() + add_qtc_plugin(QmlProfiler + DEPENDS QmlDebug QmlJS Tracing Qt5::QuickWidgets + PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport TextEditor + SOURCES + ${TEST_SOURCES} + ) + set(QMLPROFILER_QML_FILES qml/QmlProfilerFlameGraphView.qml ) diff --git a/tests/auto/tracing/flamegraphview/CMakeLists.txt b/tests/auto/tracing/flamegraphview/CMakeLists.txt index f64a90d4d3b..a1e900b7288 100644 --- a/tests/auto/tracing/flamegraphview/CMakeLists.txt +++ b/tests/auto/tracing/flamegraphview/CMakeLists.txt @@ -1,19 +1,20 @@ -add_qtc_test(tst_tracing_flamegraphview - DEPENDS Tracing Qt5::QuickWidgets Qt5::Quick Utils -) - set(TSTFLAMEGRAPHVIEW_CPP_SOURCES testflamegraphmodel.h tst_flamegraphview.cpp ) if(${Qt5_VERSION} VERSION_LESS "6.2.0") - extend_qtc_test(tst_tracing_flamegraphview + add_qtc_test(tst_tracing_flamegraphview + DEPENDS Tracing Qt5::QuickWidgets Qt5::Quick Utils SOURCES ${TSTFLAMEGRAPHVIEW_CPP_SOURCES} flamegraphview.qrc ) else() # < Qt 6.2 + add_qtc_test(tst_tracing_flamegraphview + DEPENDS Tracing Qt5::QuickWidgets Qt5::Quick Utils + ) + qt_add_qml_module(tst_tracing_flamegraphview URI "QtCreator.TstTracingFlameGraphView" VERSION "1.0" From 9d3100ff1e5837f115d26806b465e1265e6f9dd3 Mon Sep 17 00:00:00 2001 From: Wojciech Smigaj Date: Sat, 10 Jul 2021 22:34:36 +0100 Subject: [PATCH 08/11] ProjectTree: Merge descendants of hidden Sources and Headers virtual folders Unchecking the recently added "Show Source and Header Groups" option removes the Sources and Headers virtual folders from the Projects tree. This patch additionally merges descendants of these folders with identical priorities, display names and file paths. For example, if a project contains a folder including both source and header files, both the Sources and Headers virtual folders will have child nodes representing that folder. Previously, unchecking "Show Source and Header Groups" kept both these identically named nodes in the Projects tree (one containing sources, the other headers). With this patch, these nodes are merged into a single node containing both sources and headers. Change-Id: I4786eee4a528ea141a7fe117e14a050f68411890 Reviewed-by: Christian Kandeler --- src/libs/utils/CMakeLists.txt | 1 + .../indexedcontainerproxyconstiterator.h | 188 ++++++++++++++++ src/libs/utils/treemodel.h | 10 + src/libs/utils/utils-lib.pri | 1 + src/libs/utils/utils.qbs | 1 + src/plugins/projectexplorer/projectmodels.cpp | 103 ++++++++- src/plugins/projectexplorer/projectmodels.h | 2 + src/plugins/projectexplorer/projectnodes.h | 2 +- tests/auto/utils/CMakeLists.txt | 1 + .../CMakeLists.txt | 4 + .../indexedcontainerproxyconstiterator.pro | 4 + .../indexedcontainerproxyconstiterator.qbs | 7 + ...tst_indexedcontainerproxyconstiterator.cpp | 209 ++++++++++++++++++ tests/auto/utils/utils.pro | 1 + tests/auto/utils/utils.qbs | 1 + 15 files changed, 530 insertions(+), 5 deletions(-) create mode 100644 src/libs/utils/indexedcontainerproxyconstiterator.h create mode 100644 tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt create mode 100644 tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro create mode 100644 tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs create mode 100644 tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index e78809ff850..a8461a61f33 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -71,6 +71,7 @@ add_qtc_library(Utils htmldocextractor.cpp htmldocextractor.h icon.cpp icon.h id.cpp id.h + indexedcontainerproxyconstiterator.h infobar.cpp infobar.h infolabel.cpp infolabel.h itemviews.cpp itemviews.h diff --git a/src/libs/utils/indexedcontainerproxyconstiterator.h b/src/libs/utils/indexedcontainerproxyconstiterator.h new file mode 100644 index 00000000000..7e9dd4c82a6 --- /dev/null +++ b/src/libs/utils/indexedcontainerproxyconstiterator.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "utils_global.h" + +#include + +#include + +namespace Utils { + +/// A class useful for the implementation of the -> operator of proxy iterators. +template +struct ArrowProxy { + Reference r; + Reference *operator->() { + return &r; + } +}; + +/// Random-access const iterator over elements of a container providing an overloaded operator[] +/// (which may return a proxy object, like std::vector, rather than a reference). +template +class IndexedContainerProxyConstIterator +{ +public: + typedef std::random_access_iterator_tag iterator_category; + typedef typename std::make_signed::type difference_type; + typedef typename Container::value_type value_type; + typedef value_type reference; + typedef ArrowProxy pointer; + typedef typename Container::size_type size_type; + + IndexedContainerProxyConstIterator() + : m_container(nullptr), m_index(0) + {} + + IndexedContainerProxyConstIterator(const Container &container, size_type index) + : m_container(&container), m_index(index) + {} + + reference operator*() const + { + Q_ASSERT(m_container); + return (*m_container)[m_index]; + } + + pointer operator->() const + { + Q_ASSERT(m_container); + return pointer{(*m_container)[m_index]}; + } + + reference operator[](difference_type j) const + { + Q_ASSERT(m_container); + return (*m_container)[m_index + j]; + } + + bool operator==(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index == other.m_index; + } + + bool operator!=(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index != other.m_index; + } + + bool operator<(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index < other.m_index; + } + + bool operator<=(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index <= other.m_index; + } + + bool operator>(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index > other.m_index; + } + + bool operator>=(const IndexedContainerProxyConstIterator &other) const + { + Q_ASSERT(m_container == other.m_container); + return m_index >= other.m_index; + } + + IndexedContainerProxyConstIterator &operator++() + { + ++m_index; + return *this; + } + + IndexedContainerProxyConstIterator operator++(int) + { + IndexedContainerProxyConstIterator copy(*this); + ++m_index; + return copy; + } + + IndexedContainerProxyConstIterator &operator--() + { + --m_index; + return *this; + } + + IndexedContainerProxyConstIterator operator--(int) + { + IndexedContainerProxyConstIterator copy(*this); + --m_index; + return copy; + } + + IndexedContainerProxyConstIterator &operator+=(difference_type j) + { + m_index += j; + return *this; + } + + IndexedContainerProxyConstIterator &operator-=(difference_type j) + { + m_index -= j; + return *this; + } + + IndexedContainerProxyConstIterator operator+(difference_type j) const + { + IndexedContainerProxyConstIterator result(*this); + result += j; + return result; + } + + IndexedContainerProxyConstIterator operator-(difference_type j) const + { + IndexedContainerProxyConstIterator result(*this); + result -= j; + return result; + } + + friend IndexedContainerProxyConstIterator operator+( + difference_type j, const IndexedContainerProxyConstIterator &other) + { + return other + j; + } + + difference_type operator-(const IndexedContainerProxyConstIterator &other) const + { + return static_cast(m_index) - static_cast(other.m_index); + } + +private: + const Container *m_container; + size_type m_index; +}; + +} // namespace Utils diff --git a/src/libs/utils/treemodel.h b/src/libs/utils/treemodel.h index 53a6a3900ea..7240d8d6b6b 100644 --- a/src/libs/utils/treemodel.h +++ b/src/libs/utils/treemodel.h @@ -26,6 +26,7 @@ #pragma once #include "utils_global.h" +#include "indexedcontainerproxyconstiterator.h" #include @@ -119,6 +120,15 @@ public: }); } + using value_type = ChildType *; + using const_iterator = IndexedContainerProxyConstIterator; + using size_type = int; + + ChildType *operator[](int index) const { return childAt(index); } + int size() const { return childCount(); } + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return const_iterator(*this, size()); } + template void forAllChildren(const Predicate &pred) const { const auto pred0 = [pred](TreeItem *treeItem) { pred(static_cast(treeItem)); }; diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index 872675f306d..b3d6a7a6dd0 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -150,6 +150,7 @@ HEADERS += \ $$PWD/environmentfwd.h \ $$PWD/genericconstants.h \ $$PWD/globalfilechangeblocker.h \ + $$PWD/indexedcontainerproxyconstiterator.h \ $$PWD/benchmarker.h \ $$PWD/displayname.h \ $$PWD/environment.h \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index d9211602086..7a78cb1f414 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -149,6 +149,7 @@ Project { "icon.h", "id.cpp", "id.h", + "indexedcontainerproxyconstiterator.h", "infobar.cpp", "infobar.h", "infolabel.cpp", diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp index 1ba9625ecf2..0565edcc523 100644 --- a/src/plugins/projectexplorer/projectmodels.cpp +++ b/src/plugins/projectexplorer/projectmodels.cpp @@ -61,6 +61,7 @@ #include #include +#include #include #include @@ -69,6 +70,28 @@ using namespace Utils; namespace ProjectExplorer { namespace Internal { +/// An output iterator whose assignment operator appends a clone of the operand to the list of +/// children of the WrapperNode passed to the constructor. +class Appender : public std::iterator +{ +public: + explicit Appender(WrapperNode *parent) : m_parent(parent) {} + + Appender &operator=(const WrapperNode *node) + { + if (node) + m_parent->appendClone(*node); + return *this; + } + + Appender &operator*() { return *this; } + Appender &operator++() { return *this; } + Appender &operator++(int) { return *this; } + +private: + WrapperNode *m_parent; +}; + bool compareNodes(const Node *n1, const Node *n2) { if (n1->priority() > n2->priority()) @@ -82,9 +105,7 @@ bool compareNodes(const Node *n1, const Node *n2) const int filePathResult = caseFriendlyCompare(n1->filePath().toString(), n2->filePath().toString()); - if (filePathResult != 0) - return filePathResult < 0; - return n1 < n2; // sort by pointer value + return filePathResult < 0; } static bool sortWrapperNodes(const WrapperNode *w1, const WrapperNode *w2) @@ -92,6 +113,71 @@ static bool sortWrapperNodes(const WrapperNode *w1, const WrapperNode *w2) return compareNodes(w1->m_node, w2->m_node); } +/// Appends to `dest` clones of children of `first` and `second`, removing duplicates (recursively). +/// +/// \param first, second +/// Nodes with children sorted by sortWrapperNodes. +/// \param dest +/// Node to which to append clones of children of `first` and `second`, with duplicates removed. +static void appendMergedChildren(const WrapperNode *first, const WrapperNode *second, WrapperNode *dest) +{ + setUnionMerge(first->begin(), first->end(), + second->begin(), second->end(), + Appender(dest), + [dest](const WrapperNode *childOfFirst, const WrapperNode *childOfSecond) + -> const WrapperNode * { + if (childOfSecond->hasChildren()) { + if (childOfFirst->hasChildren()) { + WrapperNode *mergeResult = new WrapperNode(childOfFirst->m_node); + dest->appendChild(mergeResult); + appendMergedChildren(childOfFirst, childOfSecond, mergeResult); + // mergeResult has already been appended to the parent's list of + // children -- there's no need for the Appender to do it again. + // That's why we return a null pointer. + return nullptr; + } else { + return childOfSecond; + } + } else { + return childOfFirst; + } + }, + sortWrapperNodes); +} + +/// Given a node `parent` with children sorted by the criteria defined in sortWrapperNodes(), merge +/// any children that are equal according to those criteria. +static void mergeDuplicates(WrapperNode *parent) +{ + // We assume all descendants of 'parent' are sorted + int childIndex = 0; + while (childIndex + 1 < parent->childCount()) { + const WrapperNode *child = parent->childAt(childIndex); + const WrapperNode *nextChild = parent->childAt(childIndex + 1); + Q_ASSERT_X(!sortWrapperNodes(nextChild, child), __func__, "Children are not sorted"); + if (!sortWrapperNodes(child, nextChild)) { + // child and nextChild must have the same priorities, display names and folder paths. + // Replace them by a single node 'mergeResult` containing the union of their children. + auto mergeResult = new WrapperNode(child->m_node); + parent->insertChild(childIndex, mergeResult); + appendMergedChildren(child, nextChild, mergeResult); + // Now we can remove the original children + parent->removeChildAt(childIndex + 2); + parent->removeChildAt(childIndex + 1); + } else { + ++childIndex; + } + } +} + +void WrapperNode::appendClone(const WrapperNode &node) +{ + WrapperNode *clone = new WrapperNode(node.m_node); + appendChild(clone); + for (const WrapperNode *child : node) + clone->appendClone(*child); +} + FlatModel::FlatModel(QObject *parent) : TreeModel(new WrapperNode(nullptr), parent) { @@ -393,6 +479,8 @@ void FlatModel::saveExpandData() void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet *seen) { + bool hasHiddenSourcesOrHeaders = false; + for (Node *node : folderNode->nodes()) { if (m_filterGeneratedFiles && node->isGenerated()) continue; @@ -403,8 +491,10 @@ void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet< if (!m_showSourceGroups) { if (subFolderNode->isVirtualFolderType()) { auto vnode = static_cast(subFolderNode); - if (vnode->isSourcesOrHeaders()) + if (vnode->isSourcesOrHeaders()) { isHidden = true; + hasHiddenSourcesOrHeaders = true; + } } } if (!isHidden && !seen->contains(subFolderNode)) { @@ -423,6 +513,11 @@ void FlatModel::addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet< } } } + + if (hasHiddenSourcesOrHeaders) { + parent->sortChildren(&sortWrapperNodes); + mergeDuplicates(parent); + } } bool FlatModel::trimEmptyDirectories(WrapperNode *parent) diff --git a/src/plugins/projectexplorer/projectmodels.h b/src/plugins/projectexplorer/projectmodels.h index 4c7b4012706..2da85b1a858 100644 --- a/src/plugins/projectexplorer/projectmodels.h +++ b/src/plugins/projectexplorer/projectmodels.h @@ -52,6 +52,8 @@ class WrapperNode : public Utils::TypedTreeItem public: explicit WrapperNode(Node *node) : m_node(node) {} Node *m_node = nullptr; + + void appendClone(const WrapperNode &node); }; class FlatModel : public Utils::TreeModel diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h index bba13fe5f15..b739a93f1ca 100644 --- a/src/plugins/projectexplorer/projectnodes.h +++ b/src/plugins/projectexplorer/projectnodes.h @@ -361,7 +361,7 @@ public: void setIsSourcesOrHeaders(bool on) { m_isSourcesOrHeaders = on; } private: - bool m_isSourcesOrHeaders; // "Sources" or "Headers" + bool m_isSourcesOrHeaders = false; // "Sources" or "Headers" }; // Documentation inside. diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt index 4b341d6b3c1..63eb475063b 100644 --- a/tests/auto/utils/CMakeLists.txt +++ b/tests/auto/utils/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(ansiescapecodehandler) add_subdirectory(fileutils) add_subdirectory(fuzzymatcher) +add_subdirectory(indexedcontainerproxyconstiterator) add_subdirectory(persistentsettings) add_subdirectory(qtcprocess) add_subdirectory(settings) diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt b/tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt new file mode 100644 index 00000000000..00a6adb4ed9 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_indexedcontainerproxyconstiterator + DEPENDS Utils + SOURCES tst_indexedcontainerproxyconstiterator.cpp +) diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro new file mode 100644 index 00000000000..b0f9d7cc7e3 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.pro @@ -0,0 +1,4 @@ +QTC_LIB_DEPENDS += utils +include(../../qttest.pri) + +SOURCES += tst_indexedcontainerproxyconstiterator.cpp diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs new file mode 100644 index 00000000000..461c89e4866 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs @@ -0,0 +1,7 @@ +import qbs + +QtcAutotest { + name: "IndexedContainerProxyConstIterator autotest" + Depends { name: "Utils" } + files: "tst_indexedcontainerproxyconstiterator.cpp" +} diff --git a/tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp b/tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp new file mode 100644 index 00000000000..f0dceb0eb24 --- /dev/null +++ b/tests/auto/utils/indexedcontainerproxyconstiterator/tst_indexedcontainerproxyconstiterator.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include + +#include + +#include +#include + +using namespace Utils; + +class tst_IndexedContainerProxyConstIterator : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testConstruction(); + void testDereference(); + void testIndexing(); + void testComparisons(); + void testIncrement(); + void testDecrement(); + void testPlus(); + void testMinus(); + void testIteration(); + +private: + using StringContainer = std::vector; + using StringIterator = IndexedContainerProxyConstIterator; + StringContainer strings; + + using BoolContainer = std::vector; + using BoolIterator = IndexedContainerProxyConstIterator; + BoolContainer bools; +}; + +void tst_IndexedContainerProxyConstIterator::initTestCase() +{ + strings = {"abc", "defgh", "ijk"}; + bools = {false, true, false}; +} + +void tst_IndexedContainerProxyConstIterator::testConstruction() +{ + StringIterator strIt(strings, 0); + QCOMPARE(*strIt, "abc"); + + StringIterator strIt2(strings, 1); + QCOMPARE(*strIt2, "defgh"); + + BoolIterator boolIt(bools, 0); + QCOMPARE(*boolIt, false); + + BoolIterator boolIt2(bools, 1); + QCOMPARE(*boolIt2, true); +} + +void tst_IndexedContainerProxyConstIterator::testDereference() +{ + StringIterator strIt(strings, 0); + QCOMPARE(*strIt, "abc"); + QCOMPARE(strIt->length(), 3); + + BoolIterator boolIt(bools, 0); + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testIndexing() +{ + StringIterator strIt(strings, 0); + QCOMPARE(strIt[2], "ijk"); + + BoolIterator boolIt(bools, 0); + QCOMPARE(boolIt[2], false); +} + +void tst_IndexedContainerProxyConstIterator::testComparisons() +{ + StringIterator strIt(strings, 0); + StringIterator strIt2(strings, 0); + StringIterator strIt3(strings, 1); + + QVERIFY(strIt == strIt); + QVERIFY(!(strIt != strIt)); + QVERIFY(!(strIt < strIt)); + QVERIFY(strIt <= strIt); + QVERIFY(!(strIt > strIt)); + QVERIFY(strIt >= strIt); + + QVERIFY(strIt == strIt2); + QVERIFY(!(strIt != strIt2)); + QVERIFY(!(strIt < strIt2)); + QVERIFY(strIt <= strIt2); + QVERIFY(!(strIt > strIt2)); + QVERIFY(strIt >= strIt2); + + QVERIFY(!(strIt == strIt3)); + QVERIFY(strIt != strIt3); + QVERIFY(strIt < strIt3); + QVERIFY(strIt <= strIt3); + QVERIFY(!(strIt > strIt3)); + QVERIFY(!(strIt >= strIt3)); + + QVERIFY(!(strIt3 == strIt)); + QVERIFY(strIt3 != strIt); + QVERIFY(!(strIt3 < strIt)); + QVERIFY(!(strIt3 <= strIt)); + QVERIFY(strIt3 > strIt); + QVERIFY(strIt3 >= strIt); +} + +void tst_IndexedContainerProxyConstIterator::testIncrement() +{ + StringIterator strIt(strings, 0); + QCOMPARE(*(++strIt), "defgh"); + QCOMPARE(*(strIt++), "defgh"); + QCOMPARE(*strIt, "ijk"); + + BoolIterator boolIt(bools, 0); + QCOMPARE(*(++boolIt), true); + QCOMPARE(*(boolIt++), true); + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testDecrement() +{ + StringIterator strIt(strings, 3); + QCOMPARE(*(--strIt), "ijk"); + QCOMPARE(*(strIt--), "ijk"); + QCOMPARE(*strIt, "defgh"); + + BoolIterator boolIt(bools, 3); + QCOMPARE(*(--boolIt), false); + QCOMPARE(*(boolIt--), false); + QCOMPARE(*boolIt, true); +} + +void tst_IndexedContainerProxyConstIterator::testPlus() +{ + StringIterator strIt(strings, 1); + QCOMPARE(*(strIt + 1), "ijk"); + QCOMPARE(*(1 + strIt), "ijk"); + strIt += 1; + QCOMPARE(*strIt, "ijk"); + + BoolIterator boolIt(bools, 1); + QCOMPARE(*(boolIt + 1), false); + QCOMPARE(*(1 + boolIt), false); + boolIt += 1; + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testMinus() +{ + StringIterator strIt(strings, 1); + QCOMPARE(*(strIt - 1), "abc"); + strIt -= 1; + QCOMPARE(*strIt, "abc"); + + BoolIterator boolIt(bools, 1); + QCOMPARE(*(boolIt - 1), false); + boolIt -= 1; + QCOMPARE(*boolIt, false); +} + +void tst_IndexedContainerProxyConstIterator::testIteration() +{ + StringIterator strBegin(strings, 0); + StringIterator strEnd(strings, strings.size()); + StringContainer stringsCopy; + for (StringIterator it = strBegin; it != strEnd; ++it) + stringsCopy.push_back(*it); + QCOMPARE(stringsCopy, strings); + + BoolIterator boolBegin(bools, 0); + BoolIterator boolEnd(bools, bools.size()); + BoolContainer boolsCopy; + for (BoolIterator it = boolBegin; it != boolEnd; ++it) + boolsCopy.push_back(*it); + QCOMPARE(boolsCopy, bools); +} + +QTEST_MAIN(tst_IndexedContainerProxyConstIterator) + +#include "tst_indexedcontainerproxyconstiterator.moc" diff --git a/tests/auto/utils/utils.pro b/tests/auto/utils/utils.pro index 2e15c4379ed..e0f630ad2b5 100644 --- a/tests/auto/utils/utils.pro +++ b/tests/auto/utils/utils.pro @@ -4,6 +4,7 @@ SUBDIRS = \ fileutils \ ansiescapecodehandler \ fuzzymatcher \ + indexedcontainerproxyconstiterator \ persistentsettings \ qtcprocess \ settings \ diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs index 5b11f46e7bc..b9a4f9a6116 100644 --- a/tests/auto/utils/utils.qbs +++ b/tests/auto/utils/utils.qbs @@ -6,6 +6,7 @@ Project { "fileutils/fileutils.qbs", "ansiescapecodehandler/ansiescapecodehandler.qbs", "fuzzymatcher/fuzzymatcher.qbs", + "indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs", "persistentsettings/persistentsettings.qbs", "qtcprocess/qtcprocess.qbs", "settings/settings.qbs", From 38f23a9a4a34a1ecaab341818c4f1361aa06bc97 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 14 Jul 2021 12:10:24 +0200 Subject: [PATCH 09/11] Utils: Make FilePath::resolvePath() work with remote paths QDir::cleanPath() destroys the :// separator. Change-Id: I9a4d7cb93fba8a16f6fbbf94fa934459e3667a43 Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 9f8c7f67d5a..22589c9ed34 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -390,7 +390,9 @@ FilePath FilePath::resolvePath(const QString &fileName) const { if (FileUtils::isAbsolutePath(fileName)) return FilePath::fromString(QDir::cleanPath(fileName)); - return FilePath::fromString(QDir::cleanPath(toString() + QLatin1Char('/') + fileName)); + FilePath result = *this; + result.setPath(QDir::cleanPath(m_data + '/' + fileName)); + return result; } FilePath FilePath::cleanPath() const From cb96b1f89bdc2109281169002091c77e2b34bada Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 14 Jul 2021 08:29:18 +0200 Subject: [PATCH 10/11] Debugger: Make version autodetection work with remote instances Change-Id: I8a532a81beebc5a0080e63f1cf76db7cbf81c52b Reviewed-by: Christian Stenger --- src/plugins/debugger/debuggeritemmanager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/debugger/debuggeritemmanager.cpp b/src/plugins/debugger/debuggeritemmanager.cpp index fd0bfd91178..55d7833761c 100644 --- a/src/plugins/debugger/debuggeritemmanager.cpp +++ b/src/plugins/debugger/debuggeritemmanager.cpp @@ -432,8 +432,7 @@ void DebuggerItemConfigWidget::binaryPathHasChanged() return; DebuggerItem tmp; - QFileInfo fi = QFileInfo(m_binaryChooser->filePath().toString()); - if (fi.isExecutable()) { + if (m_binaryChooser->filePath().isExecutableFile()) { tmp = item(); tmp.reinitializeFromFile(); } @@ -800,7 +799,7 @@ void DebuggerItemManagerPrivate::autoDetectGdbOrLldbDebuggers(const FilePath &de const QString name = detectionSource.isEmpty() ? tr("System %1 at %2") : tr("Detected %1 at %2"); item.setUnexpandedDisplayName(name.arg(item.engineTypeName()).arg(command.toUserOutput())); m_model->addDebugger(item); - logMessages.append(tr("Found: \"%1\"").arg(name)); + logMessages.append(tr("Found: \"%1\"").arg(command.toUserOutput())); } if (logMessage) *logMessage = logMessages.join('\n'); From 4a08edf78dd7ef79c7fb598ab3b1b9ef751c3f92 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 14 Jul 2021 12:22:42 +0200 Subject: [PATCH 11/11] QMake: Use FilePath::resolvePath to resolve files Works with remote files, too. Change-Id: I90244e456633aec1c2a750b4be3c707efeede42c Reviewed-by: Christian Stenger --- src/plugins/qmakeprojectmanager/qmakenodes.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.cpp b/src/plugins/qmakeprojectmanager/qmakenodes.cpp index 5063d88ae74..da1f89f4eee 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakenodes.cpp @@ -377,11 +377,9 @@ QVariant QmakeProFileNode::data(Utils::Id role) const if (role == Android::Constants::AndroidSoLibPath) { TargetInformation info = targetInformation(); QStringList res = {info.buildDir.toString()}; - Utils::FilePath destDir = info.destDir; + FilePath destDir = info.destDir; if (!destDir.isEmpty()) { - if (destDir.toFileInfo().isRelative()) - destDir = Utils::FilePath::fromString(QDir::cleanPath(info.buildDir.toString() - + '/' + destDir.toString())); + destDir = info.buildDir.resolvePath(destDir.path()); res.append(destDir.toString()); } res.removeDuplicates();