diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index c3fc4d4e076..8059a240a6c 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -837,6 +837,8 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(bool timerC auto helper = qobject_cast(m_3dHelper); if (helper) helper->storeToolState(helper->globalStateId(), helper->lastSceneIdKey(), QVariant(sceneId), 0); +#else + Q_UNUSED(timerCall) #endif } @@ -1157,6 +1159,8 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView(const Reques {PuppetToCreatorCommand::RenderModelNodePreviewImage, QVariant::fromValue(imgContainer)}); } +#else + Q_UNUSED(cmd) #endif } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quick3dnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quick3dnodeinstance.cpp index 95dd4a7effb..eec52e99927 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quick3dnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quick3dnodeinstance.cpp @@ -82,6 +82,9 @@ void Quick3DNodeInstance::initialize(const ObjectNodeInstance::Pointer &objectNo m_dummyRootViewCreateFunction = "createViewForNode"; Quick3DRenderableNodeInstance::initialize(objectNodeInstance, flags); +#else + Q_UNUSED(objectNodeInstance) + Q_UNUSED(flags) #endif // QUICK3D_MODULE } diff --git a/share/qtcreator/translations/qtcreator_cs.ts b/share/qtcreator/translations/qtcreator_cs.ts index b7e0efc99e3..086b2dfea0b 100644 --- a/share/qtcreator/translations/qtcreator_cs.ts +++ b/share/qtcreator/translations/qtcreator_cs.ts @@ -32121,16 +32121,16 @@ Spustil jste Qemu? Qt %1 (%2) - qmake does not exist or is not executable - Soubor qmake neexistuje nebo není spustitelný + %1 does not exist or is not executable + Soubor %1 neexistuje nebo není spustitelný Qt version is not properly installed, please run make install Verze Qt není správně nainstalována. Proveďte, prosím, příkaz "make install" - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Cestu ke spustitelným souborům instalace Qt se nepodařilo určit. Možná je cesta k qmake chybná? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Cestu ke spustitelným souborům instalace Qt se nepodařilo určit. Možná je cesta k %1 chybná? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_da.ts b/share/qtcreator/translations/qtcreator_da.ts index 4cfd3841b60..21dd2fad82d 100644 --- a/share/qtcreator/translations/qtcreator_da.ts +++ b/share/qtcreator/translations/qtcreator_da.ts @@ -35382,16 +35382,16 @@ For flere detaljer, se /etc/sysctl.d/10-ptrace.conf Ingen qmake-sti sat - qmake does not exist or is not executable - qmake findes ikke eller er ikke eksekverbar + %1 does not exist or is not executable + %1 findes ikke eller er ikke eksekverbar Qt version is not properly installed, please run make install Qt version er ikke ordentligt installeret, kør venligst make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Kunne ikke beslutte stien til binærene af Qt installationen, måske er qmake-stien forkert? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Kunne ikke beslutte stien til binærene af Qt installationen, måske er %1-stien forkert? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_de.ts b/share/qtcreator/translations/qtcreator_de.ts index 8013bb9b20e..49d5e966da9 100644 --- a/share/qtcreator/translations/qtcreator_de.ts +++ b/share/qtcreator/translations/qtcreator_de.ts @@ -10113,8 +10113,8 @@ Dies ist unabhängig vom Wert der Eigenschaft "visible" in QML.Es ist keine qmake-Pfad gesetzt - qmake does not exist or is not executable - Die qmake-Datei existiert nicht oder ist nicht ausführbar + %1 does not exist or is not executable + Die %1-Datei existiert nicht oder ist nicht ausführbar Qt version has no name @@ -10141,8 +10141,8 @@ Dies ist unabhängig vom Wert der Eigenschaft "visible" in QML.Die Qt-Version ist nicht richtig installiert, führen Sie bitte make install aus - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Der Pfad zu den ausführbaren Dateien der Qt-Installation konnte nicht bestimmt werden, möglicherweise ist der Pfad zu qmake falsch? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Der Pfad zu den ausführbaren Dateien der Qt-Installation konnte nicht bestimmt werden, möglicherweise ist der Pfad zu %1 falsch? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_fr.ts b/share/qtcreator/translations/qtcreator_fr.ts index ec42c365792..2e632c21390 100644 --- a/share/qtcreator/translations/qtcreator_fr.ts +++ b/share/qtcreator/translations/qtcreator_fr.ts @@ -33604,8 +33604,8 @@ Nous allons essayer de travailler avec cela mais vous pourrez rencontrer des pro Chemin de qmake non spécifié - qmake does not exist or is not executable - qmake n'existe pas ou n'est pas exécutable + %1 does not exist or is not executable + %1 n'existe pas ou n'est pas exécutable Qt version has no name @@ -33633,8 +33633,8 @@ Nous allons essayer de travailler avec cela mais vous pourrez rencontrer des pro La version de Qt n'est pas correctement installée, veuillez exécuter make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Impossible de déterminer le chemin vers les programmes de Qt, peut-être que le chemin vers qmake est faux ? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Impossible de déterminer le chemin vers les programmes de Qt, peut-être que le chemin vers %1 est faux ? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_hr.ts b/share/qtcreator/translations/qtcreator_hr.ts index e7a14f06a9c..c60ce6f3c59 100644 --- a/share/qtcreator/translations/qtcreator_hr.ts +++ b/share/qtcreator/translations/qtcreator_hr.ts @@ -41628,7 +41628,7 @@ Saving failed. - qmake does not exist or is not executable + %1 does not exist or is not executable @@ -41636,7 +41636,7 @@ Saving failed. - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? diff --git a/share/qtcreator/translations/qtcreator_ja.ts b/share/qtcreator/translations/qtcreator_ja.ts index c65ccab4138..017e3ebc611 100644 --- a/share/qtcreator/translations/qtcreator_ja.ts +++ b/share/qtcreator/translations/qtcreator_ja.ts @@ -26108,16 +26108,16 @@ Do you want to save the data first? qmake のパスが設定されていません - qmake does not exist or is not executable - qmake が存在しないか実行可能ではありません + %1 does not exist or is not executable + %1 が存在しないか実行可能ではありません Qt version is not properly installed, please run make install Qt バージョンが正しくインストールされていません。make install を実行してください - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Qt インストール先のパスが特定できませんでした。qmake のパスが間違っていませんか? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Qt インストール先のパスが特定できませんでした。%1 のパスが間違っていませんか? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_pl.ts b/share/qtcreator/translations/qtcreator_pl.ts index 4bd2dfddc49..288c0333976 100644 --- a/share/qtcreator/translations/qtcreator_pl.ts +++ b/share/qtcreator/translations/qtcreator_pl.ts @@ -12382,8 +12382,8 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM Nie ustawiono ścieżki do qmake - qmake does not exist or is not executable - Brak qmake lub nie jest on plikiem wykonywalnym + %1 does not exist or is not executable + Brak %1 lub nie jest on plikiem wykonywalnym Qt version has no name @@ -12410,8 +12410,8 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM Wersja Qt zainstalowana niepoprawnie, uruchom komendę: make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do qmake. + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do %1. The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_ru.ts b/share/qtcreator/translations/qtcreator_ru.ts index ca1e07984e3..038c8b7d562 100644 --- a/share/qtcreator/translations/qtcreator_ru.ts +++ b/share/qtcreator/translations/qtcreator_ru.ts @@ -45319,8 +45319,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf Путь к qmake не указан - qmake does not exist or is not executable - qmake отсутствует или не запускается + %1 does not exist or is not executable + %1 отсутствует или не запускается Qt version has no name @@ -45347,8 +45347,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf Профиль Qt не установлен, пожалуйста выполните make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Не удалось определить путь к утилитам Qt. Может путь к qmake неверен? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Не удалось определить путь к утилитам Qt. Может путь к %1 неверен? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_sl.ts b/share/qtcreator/translations/qtcreator_sl.ts index ca8f7d46bb3..ed9bf70dfb4 100644 --- a/share/qtcreator/translations/qtcreator_sl.ts +++ b/share/qtcreator/translations/qtcreator_sl.ts @@ -21803,8 +21803,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi. - qmake does not exist or is not executable - Datoteka »qmake« ne obstaja ali pa ni izvršljiva + %1 does not exist or is not executable + Datoteka »%1« ne obstaja ali pa ni izvršljiva @@ -21813,8 +21813,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi. - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do qmake napačna? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do %1 napačna? diff --git a/share/qtcreator/translations/qtcreator_uk.ts b/share/qtcreator/translations/qtcreator_uk.ts index 7ed2f44691f..b3c68dd579c 100644 --- a/share/qtcreator/translations/qtcreator_uk.ts +++ b/share/qtcreator/translations/qtcreator_uk.ts @@ -22711,8 +22711,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf Шлях до qmake не задано - qmake does not exist or is not executable - qmake не існує або не є виконуваним модулем + %1 does not exist or is not executable + %1 не існує або не є виконуваним модулем ABI detection failed: Make sure to use a matching compiler when building. @@ -22763,8 +22763,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf Версія Qt не встановлена як слід, будь ласка, запустіть make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до qmake помилковий? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до %1 помилковий? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_zh_CN.ts b/share/qtcreator/translations/qtcreator_zh_CN.ts index 8e6d46bd95c..64fa094ec3c 100644 --- a/share/qtcreator/translations/qtcreator_zh_CN.ts +++ b/share/qtcreator/translations/qtcreator_zh_CN.ts @@ -27948,8 +27948,8 @@ Did you start Qemu? 没有设置qmake路径 - qmake does not exist or is not executable - qmake不存在或者不可执行 + %1 does not exist or is not executable + %1不存在或者不可执行 Qt version has no name @@ -27976,8 +27976,8 @@ Did you start Qemu? Qt没有被正确安装,请运行make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - 无法确定Qt安装的二进制所在的路径,或许qmake的路径设置出现了错误? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + 无法确定Qt安装的二进制所在的路径,或许%1的路径设置出现了错误? The default mkspec symlink is broken. diff --git a/share/qtcreator/translations/qtcreator_zh_TW.ts b/share/qtcreator/translations/qtcreator_zh_TW.ts index 253df6a4499..3aa3e0d04ff 100644 --- a/share/qtcreator/translations/qtcreator_zh_TW.ts +++ b/share/qtcreator/translations/qtcreator_zh_TW.ts @@ -15636,8 +15636,8 @@ Requires <b>Qt 4.7.4</b> or newer. 沒有設定 qmake 路徑 - qmake does not exist or is not executable - qmake 不存在或無法執行 + %1 does not exist or is not executable + %1 不存在或無法執行 Qt version has no name @@ -15664,8 +15664,8 @@ Requires <b>Qt 4.7.4</b> or newer. Qt 沒有被正確安裝,請執行 make install - Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong? - 無法決定 Qt 安裝版的路徑。也許是 qmake 的路徑設定有錯誤? + Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong? + 無法決定 Qt 安裝版的路徑。也許是 %1 的路徑設定有錯誤? The default mkspec symlink is broken. diff --git a/src/libs/cplusplus/LookupContext.cpp b/src/libs/cplusplus/LookupContext.cpp index 2ca935144f0..3aa39ea8678 100644 --- a/src/libs/cplusplus/LookupContext.cpp +++ b/src/libs/cplusplus/LookupContext.cpp @@ -629,7 +629,7 @@ ClassOrNamespace *ClassOrNamespace::parent() const return _parent; } -QList ClassOrNamespace::usings() const +const QList ClassOrNamespace::usings() const { const_cast(this)->flush(); return _usings; @@ -641,7 +641,7 @@ QList ClassOrNamespace::unscopedEnums() const return _enums; } -QList ClassOrNamespace::symbols() const +const QList ClassOrNamespace::symbols() const { const_cast(this)->flush(); return _symbols; diff --git a/src/libs/cplusplus/LookupContext.h b/src/libs/cplusplus/LookupContext.h index ce52feec426..cb6fbb3bfa8 100644 --- a/src/libs/cplusplus/LookupContext.h +++ b/src/libs/cplusplus/LookupContext.h @@ -70,9 +70,9 @@ public: ClassOrNamespace *instantiationOrigin() const; ClassOrNamespace *parent() const; - QList usings() const; + const QList usings() const; QList unscopedEnums() const; - QList symbols() const; + const QList symbols() const; ClassOrNamespace *globalNamespace() const; diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index c1030fd5e78..0ad53e52a73 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -41,7 +41,61 @@ bool BuildableHelperLibrary::isQtChooser(const FilePath &filePath) return filePath.symLinkTarget().endsWith("/qtchooser"); } -FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) +static const QStringList &queryToolNames() +{ + static const QStringList names = {"qmake", "qtpaths"}; + return names; +} + +static bool isQueryTool(FilePath path) +{ + if (path.isEmpty()) + return false; + if (BuildableHelperLibrary::isQtChooser(path)) + path = BuildableHelperLibrary::qtChooserToQueryToolPath(path.symLinkTarget()); + if (!path.exists()) + return false; + QtcProcess proc; + proc.setCommand({path, {"-query"}}); + proc.runBlocking(); + if (proc.result() != ProcessResult::FinishedWithSuccess) + return false; + const QString output = proc.stdOut(); + // Exemplary output of "[qmake|qtpaths] -query": + // QT_SYSROOT:... + // QT_INSTALL_PREFIX:... + // [...] + // QT_VERSION:6.4.0 + return output.contains("QT_VERSION:"); +} + +static FilePath findQueryToolInDir(const FilePath &dir) +{ + if (dir.isEmpty()) + return {}; + + for (const QString &queryTool : queryToolNames()) { + FilePath queryToolPath = dir.pathAppended(queryTool).withExecutableSuffix(); + if (queryToolPath.exists()) { + if (isQueryTool(queryToolPath)) + return queryToolPath; + } + + // Prefer qmake-qt5 to qmake-qt4 by sorting the filenames in reverse order. + const FilePaths candidates = dir.dirEntries( + {BuildableHelperLibrary::possibleQtQueryTools(queryTool), QDir::Files}, + QDir::Name | QDir::Reversed); + for (const FilePath &candidate : candidates) { + if (candidate == queryToolPath) + continue; + if (isQueryTool(candidate)) + return candidate; + } + } + return {}; +} + +FilePath BuildableHelperLibrary::qtChooserToQueryToolPath(const FilePath &qtChooser) { const QString toolDir = QLatin1String("QTTOOLDIR=\""); QtcProcess proc; @@ -51,6 +105,10 @@ FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) if (proc.result() != ProcessResult::FinishedWithSuccess) return {}; const QString output = proc.stdOut(); + // Exemplary output of "qtchooser -print-env": + // QT_SELECT="default" + // QTTOOLDIR="/usr/lib/qt5/bin" + // QTLIBDIR="/usr/lib/x86_64-linux-gnu" int pos = output.indexOf(toolDir); if (pos == -1) return {}; @@ -59,44 +117,13 @@ FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) if (end == -1) return {}; - FilePath qmake = qtChooser; - qmake.setPath(output.mid(pos, end - pos) + "/qmake"); - return qmake; -} - -static bool isQmake(FilePath path) -{ - if (path.isEmpty()) - return false; - if (BuildableHelperLibrary::isQtChooser(path)) - path = BuildableHelperLibrary::qtChooserToQmakePath(path.symLinkTarget()); - if (!path.exists()) - return false; - return !BuildableHelperLibrary::qtVersionForQMake(path).isEmpty(); -} - -static FilePath findQmakeInDir(const FilePath &dir) -{ - if (dir.isEmpty()) - return {}; - - FilePath qmakePath = dir.pathAppended("qmake").withExecutableSuffix(); - if (qmakePath.exists()) { - if (isQmake(qmakePath)) - return qmakePath; + FilePath queryToolPath = qtChooser; + for (const QString &queryTool : queryToolNames()) { + queryToolPath.setPath(output.mid(pos, end - pos) + "/" + queryTool); + if (queryToolPath.exists()) + return queryToolPath; } - - // Prefer qmake-qt5 to qmake-qt4 by sorting the filenames in reverse order. - const FilePaths candidates = dir.dirEntries( - {BuildableHelperLibrary::possibleQMakeCommands(), QDir::Files}, - QDir::Name | QDir::Reversed); - for (const FilePath &candidate : candidates) { - if (candidate == qmakePath) - continue; - if (isQmake(candidate)) - return candidate; - } - return {}; + return queryToolPath; } FilePath BuildableHelperLibrary::findSystemQt(const Environment &env) @@ -107,86 +134,55 @@ FilePath BuildableHelperLibrary::findSystemQt(const Environment &env) FilePaths BuildableHelperLibrary::findQtsInEnvironment(const Environment &env, int maxCount) { - FilePaths qmakeList; + FilePaths queryToolList; std::set canonicalEnvPaths; const FilePaths paths = env.path(); for (const FilePath &path : paths) { if (!canonicalEnvPaths.insert(path.toFileInfo().canonicalFilePath()).second) continue; - const FilePath qmake = findQmakeInDir(path); - if (qmake.isEmpty()) + const FilePath queryTool = findQueryToolInDir(path); + if (queryTool.isEmpty()) continue; - qmakeList << qmake; - if (maxCount != -1 && qmakeList.size() == maxCount) + queryToolList << queryTool; + if (maxCount != -1 && queryToolList.size() == maxCount) break; } - return qmakeList; + return queryToolList; } -QString BuildableHelperLibrary::qtVersionForQMake(const FilePath &qmakePath) +QString BuildableHelperLibrary::filterForQtQueryToolsFileDialog() { - if (qmakePath.isEmpty()) - return QString(); - - QtcProcess qmake; - qmake.setTimeoutS(5); - qmake.setCommand({qmakePath, {"--version"}}); - qmake.runBlocking(); - if (qmake.result() != ProcessResult::FinishedWithSuccess) { - qWarning() << qmake.exitMessage(); - return QString(); + QStringList toolFilters; + for (const QString &queryTool : queryToolNames()) { + for (const QString &tool: BuildableHelperLibrary::possibleQtQueryTools(queryTool)) { + QString toolFilter; + if (HostOsInfo::isMacHost()) + // work around QTBUG-7739 that prohibits filters that don't start with * + toolFilter += QLatin1Char('*'); + toolFilter += tool; + if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost()) + // kde bug, we need at least one wildcard character + // see QTCREATORBUG-7771 + toolFilter += QLatin1Char('*'); + toolFilters.append(toolFilter); + } } - - const QString output = qmake.allOutput(); - static const QRegularExpression regexp("(QMake version:?)[\\s]*([\\d.]*)", - QRegularExpression::CaseInsensitiveOption); - const QRegularExpressionMatch match = regexp.match(output); - const QString qmakeVersion = match.captured(2); - if (qmakeVersion.startsWith(QLatin1String("2.")) - || qmakeVersion.startsWith(QLatin1String("3."))) { - static const QRegularExpression regexp2("Using Qt version[\\s]*([\\d\\.]*)", - QRegularExpression::CaseInsensitiveOption); - const QRegularExpressionMatch match2 = regexp2.match(output); - const QString version = match2.captured(1); - return version; - } - return QString(); + return queryToolNames().join(", ") + " (" + toolFilters.join(" ") + ")"; } -QString BuildableHelperLibrary::filterForQmakeFileDialog() +QStringList BuildableHelperLibrary::possibleQtQueryTools(const QString &tool) { - QString filter = QLatin1String("qmake ("); - const QStringList commands = possibleQMakeCommands(); - for (int i = 0; i < commands.size(); ++i) { - if (i) - filter += QLatin1Char(' '); - if (HostOsInfo::isMacHost()) - // work around QTBUG-7739 that prohibits filters that don't start with * - filter += QLatin1Char('*'); - filter += commands.at(i); - if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost()) - // kde bug, we need at least one wildcard character - // see QTCREATORBUG-7771 - filter += QLatin1Char('*'); - } - filter += QLatin1Char(')'); - return filter; -} - - -QStringList BuildableHelperLibrary::possibleQMakeCommands() -{ - // On Windows it is always "qmake.exe" + // On Windows it is ".exe" or ".bat" // On Unix some distributions renamed qmake with a postfix to avoid clashes // On OS X, Qt 4 binary packages also has renamed qmake. There are also symbolic links that are - // named "qmake", but the file dialog always checks against resolved links (native Cocoa issue) - QStringList commands(HostOsInfo::withExecutableSuffix("qmake*")); + // named , but the file dialog always checks against resolved links (native Cocoa issue) + QStringList tools(HostOsInfo::withExecutableSuffix(tool + "*")); // Qt 6 CMake built targets, such as Android, are dependent on the host installation - // and use a script wrapper around the host qmake executable + // and use a script wrapper around the host queryTool executable if (HostOsInfo::isWindowsHost()) - commands.append("qmake*.bat"); - return commands; + tools.append(tool + "*.bat"); + return tools; } } // namespace Utils diff --git a/src/libs/utils/buildablehelperlibrary.h b/src/libs/utils/buildablehelperlibrary.h index 8b47bef1012..2b07ac009e9 100644 --- a/src/libs/utils/buildablehelperlibrary.h +++ b/src/libs/utils/buildablehelperlibrary.h @@ -45,12 +45,11 @@ public: static FilePath findSystemQt(const Environment &env); static FilePaths findQtsInEnvironment(const Environment &env, int maxCount = -1); static bool isQtChooser(const FilePath &filePath); - static FilePath qtChooserToQmakePath(const FilePath &path); - // return true if the qmake at qmakePath is a Qt (used by QtVersion) - static QString qtVersionForQMake(const FilePath &qmakePath); - // returns something like qmake4, qmake, qmake-qt4 or whatever distributions have chosen (used by QtVersion) - static QStringList possibleQMakeCommands(); - static QString filterForQmakeFileDialog(); + static FilePath qtChooserToQueryToolPath(const FilePath &path); + // returns something like qmake4, qmake, qmake-qt4, qtpaths + // or whatever distributions have chosen (used by QtVersion) + static QStringList possibleQtQueryTools(const QString &tool); + static QString filterForQtQueryToolsFileDialog(); }; } // Utils diff --git a/src/libs/utils/hostosinfo.cpp b/src/libs/utils/hostosinfo.cpp index 78ed7f22867..48518f8fc1a 100644 --- a/src/libs/utils/hostosinfo.cpp +++ b/src/libs/utils/hostosinfo.cpp @@ -65,6 +65,7 @@ HostOsInfo::HostArchitecture HostOsInfo::hostArchitecture() case PROCESSOR_ARCHITECTURE_IA64: return HostOsInfo::HostArchitectureItanium; case PROCESSOR_ARCHITECTURE_ARM: + case PROCESSOR_ARCHITECTURE_ARM64: return HostOsInfo::HostArchitectureArm; default: return HostOsInfo::HostArchitectureUnknown; diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 12bc2ac3095..27d08acb803 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -192,7 +192,7 @@ public: EnvironmentChange m_environmentChange; BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr; QList m_buttons; - MacroExpander *m_macroExpander = globalMacroExpander(); + const MacroExpander *m_macroExpander = globalMacroExpander(); std::function m_openTerminal; }; @@ -732,7 +732,7 @@ void PathChooser::setHistoryCompleter(const QString &historyKey, bool restoreLas d->m_lineEdit->setHistoryCompleter(historyKey, restoreLastItemFromHistory); } -void PathChooser::setMacroExpander(MacroExpander *macroExpander) +void PathChooser::setMacroExpander(const MacroExpander *macroExpander) { d->m_macroExpander = macroExpander; } diff --git a/src/libs/utils/pathchooser.h b/src/libs/utils/pathchooser.h index 6e1760d3914..698dc48951a 100644 --- a/src/libs/utils/pathchooser.h +++ b/src/libs/utils/pathchooser.h @@ -136,7 +136,7 @@ public: // Sets a macro expander that is used when producing path and fileName. // By default, the global expander is used. // nullptr can be passed to disable macro expansion. - void setMacroExpander(MacroExpander *macroExpander); + void setMacroExpander(const MacroExpander *macroExpander); bool isReadOnly() const; void setReadOnly(bool b); diff --git a/src/libs/utils/winutils.cpp b/src/libs/utils/winutils.cpp index afc642d5a8c..bda4c197336 100644 --- a/src/libs/utils/winutils.cpp +++ b/src/libs/utils/winutils.cpp @@ -145,7 +145,8 @@ QTCREATOR_UTILS_EXPORT bool is64BitWindowsSystem() SYSTEM_INFO systemInfo; GetNativeSystemInfo(&systemInfo); return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 - || systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64; + || systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64 + || systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64; #else return false; #endif diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp index 71e92a1fde3..a4e5a3c61f9 100644 --- a/src/plugins/android/androidrunnerworker.cpp +++ b/src/plugins/android/androidrunnerworker.cpp @@ -28,11 +28,11 @@ #include "androidconfigurations.h" #include "androidconstants.h" #include "androidmanager.h" -#include "androidrunconfiguration.h" #include #include +#include #include #include #include @@ -274,9 +274,8 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa qCDebug(androidRunWorkerLog) << "Environment variables for the app" << m_extraEnvVars.toStringList(); - if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release) { - m_extraAppParams = runControl->runnable().command.arguments(); - } + if (target->buildConfigurations().first()->buildType() != BuildConfiguration::BuildType::Release) + m_extraAppParams = runControl->commandLine().arguments(); if (auto aspect = runControl->aspect(Constants::ANDROID_AM_START_ARGS)) { QTC_CHECK(aspect->value.type() == QVariant::String); diff --git a/src/plugins/autotest/ctest/ctesttreeitem.cpp b/src/plugins/autotest/ctest/ctesttreeitem.cpp index 5e08d62c488..c374754d652 100644 --- a/src/plugins/autotest/ctest/ctesttreeitem.cpp +++ b/src/plugins/autotest/ctest/ctesttreeitem.cpp @@ -32,6 +32,7 @@ #include "../itestframework.h" #include "../testsettings.h" +#include #include #include #include diff --git a/src/plugins/baremetal/baremetalrunconfiguration.cpp b/src/plugins/baremetal/baremetalrunconfiguration.cpp index 8cfc8378641..f081520bebb 100644 --- a/src/plugins/baremetal/baremetalrunconfiguration.cpp +++ b/src/plugins/baremetal/baremetalrunconfiguration.cpp @@ -53,7 +53,7 @@ public: exeAspect->setPlaceHolderText(tr("Unknown")); addAspect(macroExpander()); - addAspect(nullptr); + addAspect(macroExpander(), nullptr); setUpdater([this, exeAspect] { const BuildTargetInfo bti = buildTargetInfo(); @@ -80,7 +80,7 @@ public: exeAspect->setExpectedKind(PathChooser::Any); addAspect(macroExpander()); - addAspect(nullptr); + addAspect(macroExpander(), nullptr); setDefaultDisplayName(RunConfigurationFactory::decoratedTargetName(tr("Custom Executable"), target)); } diff --git a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp index 2486ef3ecef..d103e86c0a8 100644 --- a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp +++ b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp @@ -179,8 +179,6 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool, Runnable inferior; inferior.command.setExecutable(bin); - inferior.extraData.insert(Debugger::Constants::kPeripheralDescriptionFile, - m_peripheralDescriptionFile.toVariant()); if (const auto argAspect = runControl->aspect()) inferior.command.setArguments(argAspect->arguments); runTool->setInferior(inferior); @@ -191,6 +189,7 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool, runTool->setRemoteChannel(channelString()); runTool->setUseContinueInsteadOfRun(true); runTool->setUseExtendedRemote(useExtendedRemote()); + runTool->runParameters().peripheralDescriptionFile = m_peripheralDescriptionFile; return true; } diff --git a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp index 19c4b07ad55..ed0c419e87e 100644 --- a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp +++ b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp @@ -216,11 +216,10 @@ bool UvscServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMess Runnable inferior; inferior.command.setExecutable(bin); - inferior.extraData.insert(Debugger::Constants::kPeripheralDescriptionFile, - peripheralDescriptionFile.toVariant()); - inferior.extraData.insert(Debugger::Constants::kUVisionProjectFilePath, projFilePath.toString()); - inferior.extraData.insert(Debugger::Constants::kUVisionOptionsFilePath, optFilePath.toString()); - inferior.extraData.insert(Debugger::Constants::kUVisionSimulator, isSimulator()); + runTool->runParameters().peripheralDescriptionFile = peripheralDescriptionFile; + runTool->runParameters().uVisionProjectFilePath = projFilePath; + runTool->runParameters().uVisionOptionsFilePath = optFilePath; + runTool->runParameters().uVisionSimulator = isSimulator(); runTool->setInferior(inferior); runTool->setSymbolFile(bin); runTool->setStartMode(AttachToRemoteServer); diff --git a/src/plugins/boot2qt/qdbdevicedebugsupport.cpp b/src/plugins/boot2qt/qdbdevicedebugsupport.cpp index d4c4d7dbd4b..44e70d01922 100644 --- a/src/plugins/boot2qt/qdbdevicedebugsupport.cpp +++ b/src/plugins/boot2qt/qdbdevicedebugsupport.cpp @@ -83,8 +83,6 @@ public: int lowerPort = 0; int upperPort = 0; - Runnable r = runnable(); - CommandLine cmd; cmd.setExecutable(device()->filePath(Constants::AppcontrollerFilepath)); @@ -118,11 +116,11 @@ public: } cmd.addArg("--port-range"); cmd.addArg(QString("%1-%2").arg(lowerPort).arg(upperPort)); - cmd.addCommandLineAsArgs(r.command); + cmd.addCommandLineAsArgs(runControl()->commandLine()); m_launcher.setCommand(cmd); - m_launcher.setWorkingDirectory(r.workingDirectory); - m_launcher.setEnvironment(r.environment); + m_launcher.setWorkingDirectory(runControl()->workingDirectory()); + m_launcher.setEnvironment(runControl()->environment()); m_launcher.start(); } diff --git a/src/plugins/boot2qt/qdbrunconfiguration.cpp b/src/plugins/boot2qt/qdbrunconfiguration.cpp index 5706116445d..98cae5734bc 100644 --- a/src/plugins/boot2qt/qdbrunconfiguration.cpp +++ b/src/plugins/boot2qt/qdbrunconfiguration.cpp @@ -102,7 +102,7 @@ QdbRunConfiguration::QdbRunConfiguration(Target *target, Id id) auto envAspect = addAspect(target); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(this); setUpdater([this, target, exeAspect, symbolsAspect] { diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index 7bcfb4e7a9c..ef813ff5ef4 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -15,11 +15,12 @@ add_qtc_plugin(ClangCodeModel clangcodemodelplugin.cpp clangcodemodelplugin.h clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h clangconstants.h - clangdclient.cpp clangdclient.h clangdast.cpp clangdast.h + clangdclient.cpp clangdclient.h clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h clangdquickfixfactory.cpp clangdquickfixfactory.h clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h + clangdsemantichighlighting.cpp clangdsemantichighlighting.h clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h clangfixitoperation.cpp clangfixitoperation.h clangdlocatorfilters.cpp clangdlocatorfilters.h @@ -28,6 +29,7 @@ add_qtc_plugin(ClangCodeModel clangtextmark.cpp clangtextmark.h clanguiheaderondiskmanager.cpp clanguiheaderondiskmanager.h clangutils.cpp clangutils.h + tasktimers.cpp tasktimers.h EXPLICIT_MOC clangcodemodelplugin.h ) diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index f1d2c89497a..b16c685c99c 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -42,6 +42,8 @@ QtcPlugin { "clangdqpropertyhighlighter.h", "clangdquickfixfactory.cpp", "clangdquickfixfactory.h", + "clangdsemantichighlighting.cpp", + "clangdsemantichighlighting.h", "clangeditordocumentprocessor.cpp", "clangeditordocumentprocessor.h", "clangfixitoperation.cpp", @@ -56,6 +58,8 @@ QtcPlugin { "clanguiheaderondiskmanager.h", "clangutils.cpp", "clangutils.h", + "tasktimers.cpp", + "tasktimers.h", ] Group { diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 8d18164090f..836ba30cfe3 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -29,11 +29,11 @@ #include "clangconstants.h" #include "clangdast.h" #include "clangdlocatorfilters.h" -#include "clangdqpropertyhighlighter.h" -#include "clangmodelmanagersupport.h" #include "clangpreprocessorassistproposalitem.h" #include "clangtextmark.h" #include "clangutils.h" +#include "clangdsemantichighlighting.h" +#include "tasktimers.h" #include #include @@ -89,7 +89,6 @@ #include #include -#include #include #include #include @@ -100,7 +99,6 @@ #include #include #include -#include #include #include @@ -120,8 +118,6 @@ namespace Internal { static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg); -static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg); -static Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion", QtWarningMsg); static QString indexingToken() { return "backgroundIndexProgress"; } @@ -733,121 +729,6 @@ private: std::unordered_map> m_data; }; -class TaskTimer -{ -public: - TaskTimer(const QString &task) : m_task(task) {} - - void stopTask() - { - // This can happen due to the RAII mechanism employed with SubtaskTimer. - // The subtask timers will expire immediately after, so this does not distort - // the timing data. - if (m_subtasks > 0) { - QTC_CHECK(m_timer.isValid()); - m_elapsedMs += m_timer.elapsed(); - m_timer.invalidate(); - m_subtasks = 0; - } - m_started = false; - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs - << " ms in UI thread"; - m_elapsedMs = 0; - } - void startSubtask() - { - // We have some callbacks that are either synchronous or asynchronous, depending on - // dynamic conditions. In the sync case, we will have nested subtasks, in which case - // the inner ones must not collect timing data, as their code blocks are already covered. - if (++m_subtasks > 1) - return; - if (!m_started) { - QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0); - m_started = true; - m_finalized = false; - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting"; - - // Used by ThreadedSubtaskTimer to mark the end of the whole highlighting operation - m_startTimer.restart(); - } - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at " - << QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz"); - QTC_CHECK(!m_timer.isValid()); - m_timer.start(); - } - void stopSubtask(bool isFinalizing) - { - if (m_subtasks == 0) // See stopTask(). - return; - if (isFinalizing) - m_finalized = true; - if (--m_subtasks > 0) // See startSubtask(). - return; - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at " - << QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz"); - QTC_CHECK(m_timer.isValid()); - m_elapsedMs += m_timer.elapsed(); - m_timer.invalidate(); - if (m_finalized) - stopTask(); - } - - QElapsedTimer startTimer() const { return m_startTimer; } - -private: - const QString m_task; - QElapsedTimer m_timer; - QElapsedTimer m_startTimer; - qint64 m_elapsedMs = 0; - int m_subtasks = 0; - bool m_started = false; - bool m_finalized = false; -}; - -class SubtaskTimer -{ -public: - SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); } - ~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); } - -protected: - void makeFinalizing() { m_isFinalizing = true; } - -private: - TaskTimer &m_timer; - bool m_isFinalizing = false; -}; - -class FinalizingSubtaskTimer : public SubtaskTimer -{ -public: - FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); } -}; - -class ThreadedSubtaskTimer -{ -public: - ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer) : m_task(task), m_taskTimer(taskTimer) - { - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread"; - m_timer.start(); - } - - ~ThreadedSubtaskTimer() - { - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed() - << " ms in dedicated thread"; - - qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": Start to end: " - << m_taskTimer.startTimer().elapsed() << " ms"; - } - -private: - const QString m_task; - QElapsedTimer m_timer; - const TaskTimer &m_taskTimer; -}; - class MemoryTreeModel; class MemoryUsageWidget : public QWidget { @@ -1550,11 +1431,29 @@ QString ClangdClient::displayNameFromDocumentSymbol(SymbolKind kind, const QStri case SymbolKind::Constructor: return name + detail; case SymbolKind::Method: - case LanguageServerProtocol::SymbolKind::Function: { - const int parenOffset = detail.indexOf(" ("); - if (parenOffset == -1) + case SymbolKind::Function: { + const int lastParenOffset = detail.lastIndexOf(')'); + if (lastParenOffset == -1) return name; - return name + detail.mid(parenOffset + 1) + " -> " + detail.mid(0, parenOffset); + int leftParensNeeded = 1; + int i = -1; + for (i = lastParenOffset - 1; i >= 0; --i) { + switch (detail.at(i).toLatin1()) { + case ')': + ++leftParensNeeded; + break; + case '(': + --leftParensNeeded; + break; + default: + break; + } + if (leftParensNeeded == 0) + break; + } + if (leftParensNeeded > 0) + return name; + return name + detail.mid(i) + " -> " + detail.left(i); } case SymbolKind::Variable: case SymbolKind::Field: @@ -2575,349 +2474,6 @@ void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const q->hoverHandler()->setHelpItem(token, helpItem); } -class ExtraHighlightingResultsCollector -{ -public: - ExtraHighlightingResultsCollector(QFutureInterface &future, - HighlightingResults &results, - const Utils::FilePath &filePath, const ClangdAstNode &ast, - const QTextDocument *doc, const QString &docContent); - - void collect(); -private: - static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2); - static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0); - int posForNodeStart(const ClangdAstNode &node) const; - int posForNodeEnd(const ClangdAstNode &node) const; - void insertResult(const HighlightingResult &result); - void insertResult(const ClangdAstNode &node, TextStyle style); - void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2); - void setResultPosFromRange(HighlightingResult &result, const Range &range); - void collectFromNode(const ClangdAstNode &node); - void visitNode(const ClangdAstNode&node); - - QFutureInterface &m_future; - HighlightingResults &m_results; - const Utils::FilePath m_filePath; - const ClangdAstNode &m_ast; - const QTextDocument * const m_doc; - const QString &m_docContent; - ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown; -}; - -// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled, -// and not even in a consistent manner. We don't want this, so we have to clean up here. -// But note that we require this behavior, as otherwise we would not be able to grey out -// e.g. empty lines after an #ifdef, due to the lack of symbols. -static QList cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc, - const QString &docContent) -{ - QList ifdefedOutRanges; - int rangeStartPos = -1; - for (auto it = results.begin(); it != results.end();) { - const bool wasIfdefedOut = rangeStartPos != -1; - const bool isIfDefedOut = it->textStyles.mainStyle == C_DISABLED_CODE; - if (!isIfDefedOut) { - if (wasIfdefedOut) { - const QTextBlock block = doc->findBlockByNumber(it->line - 1); - ifdefedOutRanges << BlockRange(rangeStartPos, block.position()); - rangeStartPos = -1; - } - ++it; - continue; - } - - if (!wasIfdefedOut) - rangeStartPos = doc->findBlockByNumber(it->line - 1).position(); - - // Does the current line contain a potential "ifdefed-out switcher"? - // If not, no state change is possible and we continue with the next line. - const auto isPreprocessorControlStatement = [&] { - const int pos = Utils::Text::positionInText(doc, it->line, it->column); - const QStringView content = subViewLen(docContent, pos, it->length).trimmed(); - if (content.isEmpty() || content.first() != '#') - return false; - int offset = 1; - while (offset < content.size() && content.at(offset).isSpace()) - ++offset; - if (offset == content.size()) - return false; - const QStringView ppDirective = content.mid(offset); - return ppDirective.startsWith(QLatin1String("if")) - || ppDirective.startsWith(QLatin1String("elif")) - || ppDirective.startsWith(QLatin1String("else")) - || ppDirective.startsWith(QLatin1String("endif")); - }; - if (!isPreprocessorControlStatement()) { - ++it; - continue; - } - - if (!wasIfdefedOut) { - // The #if or #else that starts disabled code should not be disabled. - const QTextBlock nextBlock = doc->findBlockByNumber(it->line); - rangeStartPos = nextBlock.isValid() ? nextBlock.position() : -1; - it = results.erase(it); - continue; - } - - if (wasIfdefedOut && (it + 1 == results.end() - || (it + 1)->textStyles.mainStyle != C_DISABLED_CODE - || (it + 1)->line != it->line + 1)) { - // The #else or #endif that ends disabled code should not be disabled. - const QTextBlock block = doc->findBlockByNumber(it->line - 1); - ifdefedOutRanges << BlockRange(rangeStartPos, block.position()); - rangeStartPos = -1; - it = results.erase(it); - continue; - } - ++it; - } - - if (rangeStartPos != -1) - ifdefedOutRanges << BlockRange(rangeStartPos, doc->characterCount()); - - qCDebug(clangdLogHighlight) << "found" << ifdefedOutRanges.size() << "ifdefed-out ranges"; - if (clangdLogHighlight().isDebugEnabled()) { - for (const BlockRange &r : qAsConst(ifdefedOutRanges)) - qCDebug(clangdLogHighlight) << r.first() << r.last(); - } - - return ifdefedOutRanges; -} - -static void semanticHighlighter(QFutureInterface &future, - const Utils::FilePath &filePath, - const QList &tokens, - const QString &docContents, const ClangdAstNode &ast, - const QPointer &textDocument, - int docRevision, const QVersionNumber &clangdVersion, - const TaskTimer &taskTimer) -{ - ThreadedSubtaskTimer t("highlighting", taskTimer); - if (future.isCanceled()) { - future.reportFinished(); - return; - } - - const QTextDocument doc(docContents); - const auto tokenRange = [&doc](const ExpandedSemanticToken &token) { - const Position startPos(token.line - 1, token.column - 1); - const Position endPos = startPos.withOffset(token.length, &doc); - return Range(startPos, endPos); - }; - const auto isOutputParameter = [&ast, &tokenRange](const ExpandedSemanticToken &token) { - if (token.modifiers.contains(QLatin1String("usedAsMutableReference"))) - return true; - if (token.type != "variable" && token.type != "property" && token.type != "parameter") - return false; - const Range range = tokenRange(token); - const ClangdAstPath path = getAstPath(ast, range); - if (path.size() < 2) - return false; - if (token.type == "property" - && (path.rbegin()->kind() == "MemberInitializer" - || path.rbegin()->kind() == "CXXConstruct")) { - return false; - } - if (path.rbegin()->hasConstType()) - return false; - for (auto it = path.rbegin() + 1; it != path.rend(); ++it) { - if (it->kind() == "CXXConstruct" || it->kind() == "MemberInitializer") - return true; - - if (it->kind() == "Call") { - // The first child is e.g. a called lambda or an object on which - // the call happens, and should not be highlighted as an output argument. - // If the call is not fully resolved (as in templates), we don't - // know whether the argument is passed as const or not. - if (it->arcanaContains("dependent type")) - return false; - const QList children = it->children().value_or(QList()); - return children.isEmpty() - || (children.first().range() != (it - 1)->range() - && children.first().kind() != "UnresolvedLookup"); - } - - // The token should get marked for e.g. lambdas, but not for assignment operators, - // where the user sees that it's being written. - if (it->kind() == "CXXOperatorCall") { - const QList children = it->children().value_or(QList()); - - // Child 1 is the call itself, Child 2 is the named entity on which the call happens - // (a lambda or a class instance), after that follow the actual call arguments. - if (children.size() < 2) - return false; - - // The call itself is never modifiable. - if (children.first().range() == range) - return false; - - // The callable is never displayed as an output parameter. - // TODO: A good argument can be made to display objects on which a non-const - // operator or function is called as output parameters. - if (children.at(1).range().contains(range)) - return false; - - QList firstChildTree{children.first()}; - while (!firstChildTree.isEmpty()) { - const ClangdAstNode n = firstChildTree.takeFirst(); - const QString detail = n.detail().value_or(QString()); - if (detail.startsWith("operator")) { - return !detail.contains('=') - && !detail.contains("++") && !detail.contains("--") - && !detail.contains("<<") && !detail.contains(">>") - && !detail.contains("*"); - } - firstChildTree << n.children().value_or(QList()); - } - return true; - } - - if (it->kind() == "Lambda") - return false; - if (it->kind() == "BinaryOperator") - return false; - if (it->hasConstType()) - return false; - - if (it->kind() == "CXXMemberCall") { - if (it == path.rbegin()) - return false; - const QList children = it->children().value_or(QList()); - QTC_ASSERT(!children.isEmpty(), return false); - - // The called object is never displayed as an output parameter. - // TODO: A good argument can be made to display objects on which a non-const - // operator or function is called as output parameters. - return (it - 1)->range() != children.first().range(); - } - - if (it->kind() == "Member" && it->arcanaContains("(") - && !it->arcanaContains("bound member function type")) { - return false; - } - } - return false; - }; - - const std::function toResult - = [&ast, &isOutputParameter, &clangdVersion, &tokenRange] - (const ExpandedSemanticToken &token) { - TextStyles styles; - if (token.type == "variable") { - if (token.modifiers.contains(QLatin1String("functionScope"))) { - styles.mainStyle = C_LOCAL; - } else if (token.modifiers.contains(QLatin1String("classScope"))) { - styles.mainStyle = C_FIELD; - } else if (token.modifiers.contains(QLatin1String("fileScope")) - || token.modifiers.contains(QLatin1String("globalScope"))) { - styles.mainStyle = C_GLOBAL; - } - } else if (token.type == "function" || token.type == "method") { - styles.mainStyle = token.modifiers.contains(QLatin1String("virtual")) - ? C_VIRTUAL_METHOD : C_FUNCTION; - if (ast.isValid()) { - const ClangdAstPath path = getAstPath(ast, tokenRange(token)); - if (path.length() > 1) { - const ClangdAstNode declNode = path.at(path.length() - 2); - if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") { - if (clangdVersion < QVersionNumber(14) - && declNode.arcanaContains("' virtual")) { - styles.mainStyle = C_VIRTUAL_METHOD; - } - if (declNode.hasChildWithRole("statement")) - styles.mixinStyles.push_back(C_FUNCTION_DEFINITION); - } - } - } - } else if (token.type == "class") { - styles.mainStyle = C_TYPE; - - // clang hardly ever differentiates between constructors and the associated class, - // whereas we highlight constructors as functions. - if (ast.isValid()) { - const ClangdAstPath path = getAstPath(ast, tokenRange(token)); - if (!path.isEmpty()) { - if (path.last().kind() == "CXXConstructor") { - if (!path.last().arcanaContains("implicit")) - styles.mainStyle = C_FUNCTION; - } else if (path.last().kind() == "Record" && path.length() > 1) { - const ClangdAstNode node = path.at(path.length() - 2); - if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) { - styles.mainStyle = C_FUNCTION; - - // https://github.com/clangd/clangd/issues/872 - if (node.role() == "declaration") - styles.mixinStyles.push_back(C_DECLARATION); - } - } - } - } - } else if (token.type == "comment") { // "comment" means code disabled via the preprocessor - styles.mainStyle = C_DISABLED_CODE; - } else if (token.type == "namespace") { - styles.mainStyle = C_NAMESPACE; - } else if (token.type == "property") { - styles.mainStyle = C_FIELD; - } else if (token.type == "enum") { - styles.mainStyle = C_TYPE; - } else if (token.type == "enumMember") { - styles.mainStyle = C_ENUMERATION; - } else if (token.type == "parameter") { - styles.mainStyle = C_PARAMETER; - } else if (token.type == "macro") { - styles.mainStyle = C_PREPROCESSOR; - } else if (token.type == "type") { - styles.mainStyle = C_TYPE; - } else if (token.type == "typeParameter") { - // clangd reports both type and non-type template parameters as type parameters, - // but the latter can be distinguished by the readonly modifier. - styles.mainStyle = token.modifiers.contains(QLatin1String("readonly")) - ? C_PARAMETER : C_TYPE; - } - if (token.modifiers.contains(QLatin1String("declaration"))) - styles.mixinStyles.push_back(C_DECLARATION); - if (token.modifiers.contains(QLatin1String("static"))) { - if (styles.mainStyle != C_FIELD && styles.mainStyle != C_TEXT) - styles.mixinStyles.push_back(styles.mainStyle); - styles.mainStyle = C_STATIC_MEMBER; - } - if (isOutputParameter(token)) - styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT); - qCDebug(clangdLogHighlight) << "adding highlighting result" - << token.line << token.column << token.length << int(styles.mainStyle); - return HighlightingResult(token.line, token.column, token.length, styles); - }; - - auto results = QtConcurrent::blockingMapped(tokens, toResult); - const QList ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents); - ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents).collect(); - if (!future.isCanceled()) { - qCDebug(clangdLog) << "reporting" << results.size() << "highlighting results"; - QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] { - if (textDocument && textDocument->document()->revision() == docRevision) - textDocument->setIfdefedOutBlocks(ifdefedOutBlocks); - }, Qt::QueuedConnection); - QList virtualRanges; - for (const HighlightingResult &r : results) { - if (r.textStyles.mainStyle != C_VIRTUAL_METHOD) - continue; - const Position startPos(r.line - 1, r.column - 1); - virtualRanges << Range(startPos, startPos.withOffset(r.length, &doc)); - } - QMetaObject::invokeMethod(ClangModelManagerSupport::instance(), - [filePath, virtualRanges, docRevision] { - if (ClangdClient * const client - = ClangModelManagerSupport::instance()->clientForFile(filePath)) { - client->setVirtualRanges(filePath, virtualRanges, docRevision); - } - }, Qt::QueuedConnection); - future.reportResults(QVector(results.cbegin(), results.cend())); - } - future.reportFinished(); -} - // Unfortunately, clangd ignores almost everything except symbols when sending // semantic token info, so we need to consult the AST for additional information. // In particular, we inspect the following constructs: @@ -2978,7 +2534,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, doc = QPointer(doc), rev = doc->document()->revision(), clangdVersion = q->versionNumber(), this] { - return Utils::runAsync(semanticHighlighter, filePath, tokens, text, ast, doc, rev, + return Utils::runAsync(doSemanticHighlighting, filePath, tokens, text, ast, doc, rev, clangdVersion, highlightingTimer); }; @@ -3454,526 +3010,6 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, return requestAst(q, filePath, range, wrapperHandler); } -ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( - QFutureInterface &future, HighlightingResults &results, - const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc, - const QString &docContent) - : m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc), - m_docContent(docContent) -{ -} - -void ExtraHighlightingResultsCollector::collect() -{ - for (int i = 0; i < m_results.length(); ++i) { - const HighlightingResult res = m_results.at(i); - if (res.textStyles.mainStyle != C_PREPROCESSOR || res.length != 10) - continue; - const int pos = Utils::Text::positionInText(m_doc, res.line, res.column); - if (subViewLen(m_docContent, pos, 10) != QLatin1String("Q_PROPERTY")) - continue; - int endPos; - if (i < m_results.length() - 1) { - const HighlightingResult nextRes = m_results.at(i + 1); - endPos = Utils::Text::positionInText(m_doc, nextRes.line, nextRes.column); - } else { - endPos = m_docContent.length(); - } - const QString qPropertyString = m_docContent.mid(pos, endPos - pos); - QPropertyHighlighter propHighlighter(m_doc, qPropertyString, pos); - for (const HighlightingResult &newRes : propHighlighter.highlight()) - m_results.insert(++i, newRes); - } - - if (!m_ast.isValid()) - return; - visitNode(m_ast); -} - -bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1, - const HighlightingResult &r2) -{ - return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column) - || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length); -} - -int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text, - const QStringView &subString, int from) -{ - const int firstIndex = text.indexOf(subString, from); - if (firstIndex == -1) - return -1; - const int nextIndex = text.indexOf(subString, firstIndex + 1); - - // The second condion deals with the off-by-one error in TemplateSpecialization nodes; - // see collectFromNode(). - return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1; -} - -// Unfortunately, the exact position of a specific token is usually not -// recorded in the AST, so if we need that, we have to search for it textually. -// In corner cases, this might get sabotaged by e.g. comments, in which case we give up. -int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const -{ - return Utils::Text::positionInText(m_doc, node.range().start().line() + 1, - node.range().start().character() + 1); -} - -int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const -{ - return Utils::Text::positionInText(m_doc, node.range().end().line() + 1, - node.range().end().character() + 1); -} - -void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result) -{ - if (!result.isValid()) // Some nodes don't have a range. - return; - const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan); - if (it == m_results.end() || *it != result) { - - // Prevent inserting expansions for function-like macros. For instance: - // #define TEST() "blubb" - // const char *s = TEST(); - // The macro name is always shorter than the expansion and starts at the same - // location, so it should occur right before the insertion position. - if (it > m_results.begin() && (it - 1)->line == result.line - && (it - 1)->column == result.column - && (it - 1)->textStyles.mainStyle == C_PREPROCESSOR) { - return; - } - - qCDebug(clangdLogHighlight) << "adding additional highlighting result" - << result.line << result.column << result.length; - m_results.insert(it, result); - return; - } - - // This is for conversion operators, whose type part is only reported as a type by clangd. - if ((it->textStyles.mainStyle == C_TYPE - || it->textStyles.mainStyle == C_PRIMITIVE_TYPE) - && !result.textStyles.mixinStyles.empty() - && result.textStyles.mixinStyles.at(0) == C_OPERATOR) { - it->textStyles.mixinStyles = result.textStyles.mixinStyles; - } -} - -void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style) -{ - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = style; - setResultPosFromRange(result, node.range()); - insertResult(result); - return; -} - -// For matching the "<" and ">" brackets of template declarations, specializations -// and instantiations. -void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1, - int searchStart2, int searchEnd2) -{ - const int openingAngleBracketPos = onlyIndexOf( - subViewEnd(m_docContent, searchStart1, searchEnd1), - QStringView(QStringLiteral("<"))); - if (openingAngleBracketPos == -1) - return; - const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos; - if (absOpeningAngleBracketPos > searchStart2) - searchStart2 = absOpeningAngleBracketPos + 1; - if (searchStart2 >= searchEnd2) - return; - const int closingAngleBracketPos = onlyIndexOf( - subViewEnd(m_docContent, searchStart2, searchEnd2), - QStringView(QStringLiteral(">"))); - if (closingAngleBracketPos == -1) - return; - - const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos; - if (absOpeningAngleBracketPos > absClosingAngleBracketPos) - return; - - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PUNCTUATION; - Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column); - result.length = 1; - result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; - insertResult(result); - Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column); - result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; - insertResult(result); -} - -void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result, - const Range &range) -{ - if (!range.isValid()) - return; - const Position startPos = range.start(); - const Position endPos = range.end(); - result.line = startPos.line() + 1; - result.column = startPos.character() + 1; - result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc); -} - -void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node) -{ - if (node.kind() == "UserDefinedLiteral") - return; - if (node.kind().endsWith("Literal")) { - const bool isKeyword = node.kind() == "CXXBoolLiteral" - || node.kind() == "CXXNullPtrLiteral"; - const bool isStringLike = !isKeyword && (node.kind().startsWith("String") - || node.kind().startsWith("Character")); - const TextStyle style = isKeyword ? C_KEYWORD : isStringLike ? C_STRING : C_NUMBER; - insertResult(node, style); - return; - } - if (node.role() == "type" && node.kind() == "Builtin") { - insertResult(node, C_PRIMITIVE_TYPE); - return; - } - if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) { - insertResult(node, C_KEYWORD); - return; - } - - const bool isExpression = node.role() == "expression"; - if (isExpression && node.kind() == "Predefined") { - insertResult(node, C_PREPROCESSOR); - return; - } - - const bool isDeclaration = node.role() == "declaration"; - const int nodeStartPos = posForNodeStart(node); - const int nodeEndPos = posForNodeEnd(node); - const QList children = node.children().value_or(QList()); - - // Match question mark and colon in ternary operators. - if (isExpression && node.kind() == "ConditionalOperator") { - if (children.size() != 3) - return; - - // The question mark is between sub-expressions 1 and 2, the colon is between - // sub-expressions 2 and 3. - const int searchStartPosQuestionMark = posForNodeEnd(children.first()); - const int searchEndPosQuestionMark = posForNodeStart(children.at(1)); - QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark, - searchEndPosQuestionMark); - const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?"))); - if (questionMarkPos == -1) - return; - const int searchStartPosColon = posForNodeEnd(children.at(1)); - const int searchEndPosColon = posForNodeStart(children.at(2)); - content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon); - const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":"))); - if (colonPos == -1) - return; - - const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos; - const int absColonPos = searchStartPosColon + colonPos; - if (absQuestionMarkPos > absColonPos) - return; - - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PUNCTUATION; - result.textStyles.mixinStyles.push_back(C_OPERATOR); - Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column); - result.length = 1; - result.kind = CppEditor::SemanticHighlighter::TernaryIf; - insertResult(result); - Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column); - result.kind = CppEditor::SemanticHighlighter::TernaryElse; - insertResult(result); - return; - } - - if (isDeclaration && (node.kind() == "FunctionTemplate" - || node.kind() == "ClassTemplate")) { - // The child nodes are the template parameters and and the function or class. - // The opening angle bracket is before the first child node, the closing angle - // bracket is before the function child node and after the last param node. - const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" - ? "Function" : "CXXRecord"); - const auto functionOrClassIt = std::find_if(children.begin(), children.end(), - [&classOrFunctionKind](const ClangdAstNode &n) { - return n.role() == "declaration" && n.kind() == classOrFunctionKind; - }); - if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) - return; - const int firstTemplateParamStartPos = posForNodeStart(children.first()); - const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1)); - const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, functionOrClassStartPos); - return; - } - - const auto isTemplateParamDecl = [](const ClangdAstNode &node) { - return node.isTemplateParameterDeclaration(); - }; - if (isDeclaration && node.kind() == "TypeAliasTemplate") { - // Children are one node of type TypeAlias and the template parameters. - // The opening angle bracket is before the first parameter and the closing - // angle bracket is after the last parameter. - // The TypeAlias node seems to appear first in the AST, even though lexically - // is comes after the parameters. We don't rely on the order here. - // Note that there is a second pair of angle brackets. That one is part of - // a TemplateSpecialization, which is handled further below. - const auto firstTemplateParam = std::find_if(children.begin(), children.end(), - isTemplateParamDecl); - if (firstTemplateParam == children.end()) - return; - const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), - isTemplateParamDecl); - QTC_ASSERT(lastTemplateParam != children.rend(), return); - const auto typeAlias = std::find_if(children.begin(), children.end(), - [](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; }); - if (typeAlias == children.end()) - return; - - const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); - const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); - const int searchEndPos = posForNodeStart(*typeAlias); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, searchEndPos); - return; - } - - if (isDeclaration && node.kind() == "ClassTemplateSpecialization") { - // There is one child of kind TemplateSpecialization. The first pair - // of angle brackets comes before that. - if (children.size() == 1) { - const int childNodePos = posForNodeStart(children.first()); - insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos); - } - return; - } - - if (isDeclaration && node.kind() == "TemplateTemplateParm") { - // The child nodes are template arguments and template parameters. - // Arguments seem to appear before parameters in the AST, even though they - // come after them in the source code. We don't rely on the order here. - const auto firstTemplateParam = std::find_if(children.begin(), children.end(), - isTemplateParamDecl); - if (firstTemplateParam == children.end()) - return; - const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), - isTemplateParamDecl); - QTC_ASSERT(lastTemplateParam != children.rend(), return); - const auto templateArg = std::find_if(children.begin(), children.end(), - [](const ClangdAstNode &n) { return n.role() == "template argument"; }); - - const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); - const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); - const int searchEndPos = templateArg == children.end() - ? nodeEndPos : posForNodeStart(*templateArg); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, searchEndPos); - return; - } - - // {static,dynamic,reinterpret}_cast<>(). - if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) { - // First child is type, second child is expression. - // The opening angle bracket is before the first child, the closing angle bracket - // is between the two children. - if (children.size() == 2) { - insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()), - posForNodeEnd(children.first()), - posForNodeStart(children.last())); - } - return; - } - - if (node.kind() == "TemplateSpecialization") { - // First comes the template type, then the template arguments. - // The opening angle bracket is before the first template argument, - // the closing angle bracket is after the last template argument. - // The first child node has no range, so we start searching at the parent node. - if (children.size() >= 2) { - int searchStart2 = posForNodeEnd(children.last()); - int searchEnd2 = nodeEndPos; - - // There is a weird off-by-one error on the clang side: If there is a - // nested template instantiation *and* there is no space between - // the closing angle brackets, then the inner TemplateSpecialization node's range - // will extend one character too far, covering the outer's closing angle bracket. - // This is what we are correcting for here. - // This issue is tracked at https://github.com/clangd/clangd/issues/871. - if (searchStart2 == searchEnd2) - --searchStart2; - insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)), - searchStart2, searchEnd2); - } - return; - } - - if (!isExpression && !isDeclaration) - return; - - // Operators, overloaded ones in particular. - static const QString operatorPrefix = "operator"; - QString detail = node.detail().value_or(QString()); - const bool isCallToNew = node.kind() == "CXXNew"; - const bool isCallToDelete = node.kind() == "CXXDelete"; - const auto isProperOperator = [&] { - if (isCallToNew || isCallToDelete) - return true; - if (!detail.startsWith(operatorPrefix)) - return false; - if (detail == operatorPrefix) - return false; - const QChar nextChar = detail.at(operatorPrefix.length()); - return !nextChar.isLetterOrNumber() && nextChar != '_'; - }; - if (!isProperOperator()) - return; - - if (!isCallToNew && !isCallToDelete) - detail.remove(0, operatorPrefix.length()); - - HighlightingResult result; - result.useTextSyles = true; - const bool isConversionOp = node.kind() == "CXXConversion"; - const bool isOverloaded = !isConversionOp - && (isDeclaration || ((!isCallToNew && !isCallToDelete) - || node.arcanaContains("CXXMethod"))); - result.textStyles.mainStyle = isConversionOp - ? C_PRIMITIVE_TYPE - : isCallToNew || isCallToDelete || detail.at(0).isSpace() - ? C_KEYWORD : C_PUNCTUATION; - result.textStyles.mixinStyles.push_back(C_OPERATOR); - if (isOverloaded) - result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR); - if (isDeclaration) - result.textStyles.mixinStyles.push_back(C_DECLARATION); - - const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos); - - if (isCallToNew || isCallToDelete) { - result.line = node.range().start().line() + 1; - result.column = node.range().start().character() + 1; - result.length = isCallToNew ? 3 : 6; - insertResult(result); - if (node.arcanaContains("array")) { - const int openingBracketOffset = nodeText.indexOf('['); - if (openingBracketOffset == -1) - return; - const int closingBracketOffset = nodeText.lastIndexOf(']'); - if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset) - return; - - result.textStyles.mainStyle = C_PUNCTUATION; - result.length = 1; - Utils::Text::convertPosition(m_doc, - nodeStartPos + openingBracketOffset, - &result.line, &result.column); - insertResult(result); - Utils::Text::convertPosition(m_doc, - nodeStartPos + closingBracketOffset, - &result.line, &result.column); - insertResult(result); - } - return; - } - - if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) { - result.line = node.range().start().line() + 1; - result.column = node.range().start().character() + 1; - result.length = 1; - insertResult(result); - result.line = node.range().end().line() + 1; - result.column = node.range().end().character(); - insertResult(result); - return; - } - - const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length(); - - // The simple case: Call to operator+, +=, * etc. - if (nodeEndPos - nodeStartPos == opStringLen) { - setResultPosFromRange(result, node.range()); - insertResult(result); - return; - } - - const int prefixOffset = nodeText.indexOf(operatorPrefix); - if (prefixOffset == -1) - return; - - const bool isArray = detail == "[]"; - const bool isCall = detail == "()"; - const bool isArrayNew = detail == " new[]"; - const bool isArrayDelete = detail == " delete[]"; - const QStringView searchTerm = isArray || isCall - ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete - ? QStringView(detail).chopped(2) : detail; - const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset - + operatorPrefix.length()); - if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1) - return; - - const int opStringOffsetInDoc = nodeStartPos + opStringOffset - + detail.length() - opStringLen; - Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column); - result.length = opStringLen; - if (isArray || isCall) - result.length = 1; - else if (isArrayNew || isArrayDelete) - result.length -= 2; - if (!isArray && !isCall) - insertResult(result); - if (!isArray && !isCall && !isArrayNew && !isArrayDelete) - return; - - result.textStyles.mainStyle = C_PUNCTUATION; - result.length = 1; - const int openingParenOffset = nodeText.indexOf( - isCall ? '(' : '[', prefixOffset + operatorPrefix.length()); - if (openingParenOffset == -1) - return; - const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1); - if (closingParenOffset == -1 || closingParenOffset < openingParenOffset) - return; - Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset, - &result.line, &result.column); - insertResult(result); - Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset, - &result.line, &result.column); - insertResult(result); -} - -void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node) -{ - if (m_future.isCanceled()) - return; - const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus; - m_currentFileStatus = node.fileStatus(m_filePath); - if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown - && prevFileStatus != ClangdAstNode::FileStatus::Ours) { - m_currentFileStatus = prevFileStatus; - } - switch (m_currentFileStatus) { - case ClangdAstNode::FileStatus::Ours: - case ClangdAstNode::FileStatus::Unknown: - collectFromNode(node); - [[fallthrough]]; - case ClangdAstNode::FileStatus::Foreign: - case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: { - const auto children = node.children(); - if (!children) - return; - for (const ClangdAstNode &childNode : *children) - visitNode(childNode); - break; - } - } - m_currentFileStatus = prevFileStatus; -} - bool ClangdClient::FollowSymbolData::defLinkIsAmbiguous() const { // Even if the call is to a virtual function, it might not be ambiguous: diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp new file mode 100644 index 00000000000..b37605af128 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp @@ -0,0 +1,925 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangdsemantichighlighting.h" + +#include "clangdast.h" +#include "clangdclient.h" +#include "clangdqpropertyhighlighter.h" +#include "clangmodelmanagersupport.h" +#include "tasktimers.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace LanguageClient; +using namespace LanguageServerProtocol; +using namespace TextEditor; + +namespace ClangCodeModel::Internal { +Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg); + +// clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled, +// and not even in a consistent manner. We don't want this, so we have to clean up here. +// But note that we require this behavior, as otherwise we would not be able to grey out +// e.g. empty lines after an #ifdef, due to the lack of symbols. +static QList cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc, + const QString &docContent) +{ + QList ifdefedOutRanges; + int rangeStartPos = -1; + for (auto it = results.begin(); it != results.end();) { + const bool wasIfdefedOut = rangeStartPos != -1; + const bool isIfDefedOut = it->textStyles.mainStyle == C_DISABLED_CODE; + if (!isIfDefedOut) { + if (wasIfdefedOut) { + const QTextBlock block = doc->findBlockByNumber(it->line - 1); + ifdefedOutRanges << BlockRange(rangeStartPos, block.position()); + rangeStartPos = -1; + } + ++it; + continue; + } + + if (!wasIfdefedOut) + rangeStartPos = doc->findBlockByNumber(it->line - 1).position(); + + // Does the current line contain a potential "ifdefed-out switcher"? + // If not, no state change is possible and we continue with the next line. + const auto isPreprocessorControlStatement = [&] { + const int pos = Utils::Text::positionInText(doc, it->line, it->column); + const QStringView content = subViewLen(docContent, pos, it->length).trimmed(); + if (content.isEmpty() || content.first() != '#') + return false; + int offset = 1; + while (offset < content.size() && content.at(offset).isSpace()) + ++offset; + if (offset == content.size()) + return false; + const QStringView ppDirective = content.mid(offset); + return ppDirective.startsWith(QLatin1String("if")) + || ppDirective.startsWith(QLatin1String("elif")) + || ppDirective.startsWith(QLatin1String("else")) + || ppDirective.startsWith(QLatin1String("endif")); + }; + if (!isPreprocessorControlStatement()) { + ++it; + continue; + } + + if (!wasIfdefedOut) { + // The #if or #else that starts disabled code should not be disabled. + const QTextBlock nextBlock = doc->findBlockByNumber(it->line); + rangeStartPos = nextBlock.isValid() ? nextBlock.position() : -1; + it = results.erase(it); + continue; + } + + if (wasIfdefedOut && (it + 1 == results.end() + || (it + 1)->textStyles.mainStyle != C_DISABLED_CODE + || (it + 1)->line != it->line + 1)) { + // The #else or #endif that ends disabled code should not be disabled. + const QTextBlock block = doc->findBlockByNumber(it->line - 1); + ifdefedOutRanges << BlockRange(rangeStartPos, block.position()); + rangeStartPos = -1; + it = results.erase(it); + continue; + } + ++it; + } + + if (rangeStartPos != -1) + ifdefedOutRanges << BlockRange(rangeStartPos, doc->characterCount()); + + qCDebug(clangdLogHighlight) << "found" << ifdefedOutRanges.size() << "ifdefed-out ranges"; + if (clangdLogHighlight().isDebugEnabled()) { + for (const BlockRange &r : qAsConst(ifdefedOutRanges)) + qCDebug(clangdLogHighlight) << r.first() << r.last(); + } + + return ifdefedOutRanges; +} + +class ExtraHighlightingResultsCollector +{ +public: + ExtraHighlightingResultsCollector(QFutureInterface &future, + HighlightingResults &results, + const Utils::FilePath &filePath, const ClangdAstNode &ast, + const QTextDocument *doc, const QString &docContent); + + void collect(); +private: + static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2); + static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0); + int posForNodeStart(const ClangdAstNode &node) const; + int posForNodeEnd(const ClangdAstNode &node) const; + void insertResult(const HighlightingResult &result); + void insertResult(const ClangdAstNode &node, TextStyle style); + void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2); + void setResultPosFromRange(HighlightingResult &result, const Range &range); + void collectFromNode(const ClangdAstNode &node); + void visitNode(const ClangdAstNode&node); + + QFutureInterface &m_future; + HighlightingResults &m_results; + const Utils::FilePath m_filePath; + const ClangdAstNode &m_ast; + const QTextDocument * const m_doc; + const QString &m_docContent; + ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown; +}; + +void doSemanticHighlighting( + QFutureInterface &future, + const Utils::FilePath &filePath, + const QList &tokens, + const QString &docContents, + const ClangdAstNode &ast, + const QPointer &textDocument, + int docRevision, + const QVersionNumber &clangdVersion, + const TaskTimer &taskTimer) +{ + ThreadedSubtaskTimer t("highlighting", taskTimer); + if (future.isCanceled()) { + future.reportFinished(); + return; + } + + const QTextDocument doc(docContents); + const auto tokenRange = [&doc](const ExpandedSemanticToken &token) { + const Position startPos(token.line - 1, token.column - 1); + const Position endPos = startPos.withOffset(token.length, &doc); + return Range(startPos, endPos); + }; + const auto isOutputParameter = [&ast, &tokenRange](const ExpandedSemanticToken &token) { + if (token.modifiers.contains(QLatin1String("usedAsMutableReference"))) + return true; + if (token.type != "variable" && token.type != "property" && token.type != "parameter") + return false; + const Range range = tokenRange(token); + const ClangdAstPath path = getAstPath(ast, range); + if (path.size() < 2) + return false; + if (token.type == "property" + && (path.rbegin()->kind() == "MemberInitializer" + || path.rbegin()->kind() == "CXXConstruct")) { + return false; + } + if (path.rbegin()->hasConstType()) + return false; + for (auto it = path.rbegin() + 1; it != path.rend(); ++it) { + if (it->kind() == "CXXConstruct" || it->kind() == "MemberInitializer") + return true; + + if (it->kind() == "Call") { + // The first child is e.g. a called lambda or an object on which + // the call happens, and should not be highlighted as an output argument. + // If the call is not fully resolved (as in templates), we don't + // know whether the argument is passed as const or not. + if (it->arcanaContains("dependent type")) + return false; + const QList children = it->children().value_or(QList()); + return children.isEmpty() + || (children.first().range() != (it - 1)->range() + && children.first().kind() != "UnresolvedLookup"); + } + + // The token should get marked for e.g. lambdas, but not for assignment operators, + // where the user sees that it's being written. + if (it->kind() == "CXXOperatorCall") { + const QList children = it->children().value_or(QList()); + + // Child 1 is the call itself, Child 2 is the named entity on which the call happens + // (a lambda or a class instance), after that follow the actual call arguments. + if (children.size() < 2) + return false; + + // The call itself is never modifiable. + if (children.first().range() == range) + return false; + + // The callable is never displayed as an output parameter. + // TODO: A good argument can be made to display objects on which a non-const + // operator or function is called as output parameters. + if (children.at(1).range().contains(range)) + return false; + + QList firstChildTree{children.first()}; + while (!firstChildTree.isEmpty()) { + const ClangdAstNode n = firstChildTree.takeFirst(); + const QString detail = n.detail().value_or(QString()); + if (detail.startsWith("operator")) { + return !detail.contains('=') + && !detail.contains("++") && !detail.contains("--") + && !detail.contains("<<") && !detail.contains(">>") + && !detail.contains("*"); + } + firstChildTree << n.children().value_or(QList()); + } + return true; + } + + if (it->kind() == "Lambda") + return false; + if (it->kind() == "BinaryOperator") + return false; + if (it->hasConstType()) + return false; + + if (it->kind() == "CXXMemberCall") { + if (it == path.rbegin()) + return false; + const QList children = it->children().value_or(QList()); + QTC_ASSERT(!children.isEmpty(), return false); + + // The called object is never displayed as an output parameter. + // TODO: A good argument can be made to display objects on which a non-const + // operator or function is called as output parameters. + return (it - 1)->range() != children.first().range(); + } + + if (it->kind() == "Member" && it->arcanaContains("(") + && !it->arcanaContains("bound member function type")) { + return false; + } + } + return false; + }; + + const std::function toResult + = [&ast, &isOutputParameter, &clangdVersion, &tokenRange] + (const ExpandedSemanticToken &token) { + TextStyles styles; + if (token.type == "variable") { + if (token.modifiers.contains(QLatin1String("functionScope"))) { + styles.mainStyle = C_LOCAL; + } else if (token.modifiers.contains(QLatin1String("classScope"))) { + styles.mainStyle = C_FIELD; + } else if (token.modifiers.contains(QLatin1String("fileScope")) + || token.modifiers.contains(QLatin1String("globalScope"))) { + styles.mainStyle = C_GLOBAL; + } + } else if (token.type == "function" || token.type == "method") { + styles.mainStyle = token.modifiers.contains(QLatin1String("virtual")) + ? C_VIRTUAL_METHOD : C_FUNCTION; + if (ast.isValid()) { + const ClangdAstPath path = getAstPath(ast, tokenRange(token)); + if (path.length() > 1) { + const ClangdAstNode declNode = path.at(path.length() - 2); + if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") { + if (clangdVersion < QVersionNumber(14) + && declNode.arcanaContains("' virtual")) { + styles.mainStyle = C_VIRTUAL_METHOD; + } + if (declNode.hasChildWithRole("statement")) + styles.mixinStyles.push_back(C_FUNCTION_DEFINITION); + } + } + } + } else if (token.type == "class") { + styles.mainStyle = C_TYPE; + + // clang hardly ever differentiates between constructors and the associated class, + // whereas we highlight constructors as functions. + if (ast.isValid()) { + const ClangdAstPath path = getAstPath(ast, tokenRange(token)); + if (!path.isEmpty()) { + if (path.last().kind() == "CXXConstructor") { + if (!path.last().arcanaContains("implicit")) + styles.mainStyle = C_FUNCTION; + } else if (path.last().kind() == "Record" && path.length() > 1) { + const ClangdAstNode node = path.at(path.length() - 2); + if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) { + styles.mainStyle = C_FUNCTION; + + // https://github.com/clangd/clangd/issues/872 + if (node.role() == "declaration") + styles.mixinStyles.push_back(C_DECLARATION); + } + } + } + } + } else if (token.type == "comment") { // "comment" means code disabled via the preprocessor + styles.mainStyle = C_DISABLED_CODE; + } else if (token.type == "namespace") { + styles.mainStyle = C_NAMESPACE; + } else if (token.type == "property") { + styles.mainStyle = C_FIELD; + } else if (token.type == "enum") { + styles.mainStyle = C_TYPE; + } else if (token.type == "enumMember") { + styles.mainStyle = C_ENUMERATION; + } else if (token.type == "parameter") { + styles.mainStyle = C_PARAMETER; + } else if (token.type == "macro") { + styles.mainStyle = C_PREPROCESSOR; + } else if (token.type == "type") { + styles.mainStyle = C_TYPE; + } else if (token.type == "typeParameter") { + // clangd reports both type and non-type template parameters as type parameters, + // but the latter can be distinguished by the readonly modifier. + styles.mainStyle = token.modifiers.contains(QLatin1String("readonly")) + ? C_PARAMETER : C_TYPE; + } + if (token.modifiers.contains(QLatin1String("declaration"))) + styles.mixinStyles.push_back(C_DECLARATION); + if (token.modifiers.contains(QLatin1String("static"))) { + if (styles.mainStyle != C_FIELD && styles.mainStyle != C_TEXT) + styles.mixinStyles.push_back(styles.mainStyle); + styles.mainStyle = C_STATIC_MEMBER; + } + if (isOutputParameter(token)) + styles.mixinStyles.push_back(C_OUTPUT_ARGUMENT); + qCDebug(clangdLogHighlight) << "adding highlighting result" + << token.line << token.column << token.length << int(styles.mainStyle); + return HighlightingResult(token.line, token.column, token.length, styles); + }; + + auto results = QtConcurrent::blockingMapped(tokens, toResult); + const QList ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents); + ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents).collect(); + if (!future.isCanceled()) { + qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results"; + QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] { + if (textDocument && textDocument->document()->revision() == docRevision) + textDocument->setIfdefedOutBlocks(ifdefedOutBlocks); + }, Qt::QueuedConnection); + QList virtualRanges; + for (const HighlightingResult &r : results) { + if (r.textStyles.mainStyle != C_VIRTUAL_METHOD) + continue; + const Position startPos(r.line - 1, r.column - 1); + virtualRanges << Range(startPos, startPos.withOffset(r.length, &doc)); + } + QMetaObject::invokeMethod(ClangModelManagerSupport::instance(), + [filePath, virtualRanges, docRevision] { + if (ClangdClient * const client + = ClangModelManagerSupport::instance()->clientForFile(filePath)) { + client->setVirtualRanges(filePath, virtualRanges, docRevision); + } + }, Qt::QueuedConnection); + future.reportResults(QVector(results.cbegin(), results.cend())); + } + future.reportFinished(); +} + +ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( + QFutureInterface &future, HighlightingResults &results, + const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc, + const QString &docContent) + : m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc), + m_docContent(docContent) +{ +} + +void ExtraHighlightingResultsCollector::collect() +{ + for (int i = 0; i < m_results.length(); ++i) { + const HighlightingResult res = m_results.at(i); + if (res.textStyles.mainStyle != C_PREPROCESSOR || res.length != 10) + continue; + const int pos = Utils::Text::positionInText(m_doc, res.line, res.column); + if (subViewLen(m_docContent, pos, 10) != QLatin1String("Q_PROPERTY")) + continue; + int endPos; + if (i < m_results.length() - 1) { + const HighlightingResult nextRes = m_results.at(i + 1); + endPos = Utils::Text::positionInText(m_doc, nextRes.line, nextRes.column); + } else { + endPos = m_docContent.length(); + } + const QString qPropertyString = m_docContent.mid(pos, endPos - pos); + QPropertyHighlighter propHighlighter(m_doc, qPropertyString, pos); + for (const HighlightingResult &newRes : propHighlighter.highlight()) + m_results.insert(++i, newRes); + } + + if (!m_ast.isValid()) + return; + visitNode(m_ast); +} + +bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1, + const HighlightingResult &r2) +{ + return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column) + || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length); +} + +int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text, + const QStringView &subString, int from) +{ + const int firstIndex = text.indexOf(subString, from); + if (firstIndex == -1) + return -1; + const int nextIndex = text.indexOf(subString, firstIndex + 1); + + // The second condion deals with the off-by-one error in TemplateSpecialization nodes; + // see collectFromNode(). + return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1; +} + +// Unfortunately, the exact position of a specific token is usually not +// recorded in the AST, so if we need that, we have to search for it textually. +// In corner cases, this might get sabotaged by e.g. comments, in which case we give up. +int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const +{ + return Utils::Text::positionInText(m_doc, node.range().start().line() + 1, + node.range().start().character() + 1); +} + +int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const +{ + return Utils::Text::positionInText(m_doc, node.range().end().line() + 1, + node.range().end().character() + 1); +} + +void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result) +{ + if (!result.isValid()) // Some nodes don't have a range. + return; + const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan); + if (it == m_results.end() || *it != result) { + + // Prevent inserting expansions for function-like macros. For instance: + // #define TEST() "blubb" + // const char *s = TEST(); + // The macro name is always shorter than the expansion and starts at the same + // location, so it should occur right before the insertion position. + if (it > m_results.begin() && (it - 1)->line == result.line + && (it - 1)->column == result.column + && (it - 1)->textStyles.mainStyle == C_PREPROCESSOR) { + return; + } + + // Bogus ranges; e.g. QTCREATORBUG-27601 + if (it != m_results.end()) { + const int nextStartPos = Utils::Text::positionInText(m_doc, it->line, it->column); + const int resultEndPos = Utils::Text::positionInText(m_doc, result.line, result.column) + + result.length; + if (resultEndPos > nextStartPos) + return; + } + + qCDebug(clangdLogHighlight) << "adding additional highlighting result" + << result.line << result.column << result.length; + m_results.insert(it, result); + return; + } + + // This is for conversion operators, whose type part is only reported as a type by clangd. + if ((it->textStyles.mainStyle == C_TYPE + || it->textStyles.mainStyle == C_PRIMITIVE_TYPE) + && !result.textStyles.mixinStyles.empty() + && result.textStyles.mixinStyles.at(0) == C_OPERATOR) { + it->textStyles.mixinStyles = result.textStyles.mixinStyles; + } +} + +void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style) +{ + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = style; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; +} + +// For matching the "<" and ">" brackets of template declarations, specializations +// and instantiations. +void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1, + int searchStart2, int searchEnd2) +{ + const int openingAngleBracketPos = onlyIndexOf( + subViewEnd(m_docContent, searchStart1, searchEnd1), + QStringView(QStringLiteral("<"))); + if (openingAngleBracketPos == -1) + return; + const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos; + if (absOpeningAngleBracketPos > searchStart2) + searchStart2 = absOpeningAngleBracketPos + 1; + if (searchStart2 >= searchEnd2) + return; + const int closingAngleBracketPos = onlyIndexOf( + subViewEnd(m_docContent, searchStart2, searchEnd2), + QStringView(QStringLiteral(">"))); + if (closingAngleBracketPos == -1) + return; + + const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos; + if (absOpeningAngleBracketPos > absClosingAngleBracketPos) + return; + + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PUNCTUATION; + Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column); + result.length = 1; + result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; + insertResult(result); + Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column); + result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; + insertResult(result); +} + +void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result, + const Range &range) +{ + if (!range.isValid()) + return; + const Position startPos = range.start(); + const Position endPos = range.end(); + result.line = startPos.line() + 1; + result.column = startPos.character() + 1; + result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc); +} + +void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node) +{ + if (node.kind() == "UserDefinedLiteral") + return; + if (node.kind().endsWith("Literal")) { + const bool isKeyword = node.kind() == "CXXBoolLiteral" + || node.kind() == "CXXNullPtrLiteral"; + const bool isStringLike = !isKeyword && (node.kind().startsWith("String") + || node.kind().startsWith("Character")); + const TextStyle style = isKeyword ? C_KEYWORD : isStringLike ? C_STRING : C_NUMBER; + insertResult(node, style); + return; + } + if (node.role() == "type" && node.kind() == "Builtin") { + insertResult(node, C_PRIMITIVE_TYPE); + return; + } + if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) { + insertResult(node, C_KEYWORD); + return; + } + + const bool isExpression = node.role() == "expression"; + if (isExpression && node.kind() == "Predefined") { + insertResult(node, C_PREPROCESSOR); + return; + } + + const bool isDeclaration = node.role() == "declaration"; + const int nodeStartPos = posForNodeStart(node); + const int nodeEndPos = posForNodeEnd(node); + const QList children = node.children().value_or(QList()); + + // Match question mark and colon in ternary operators. + if (isExpression && node.kind() == "ConditionalOperator") { + if (children.size() != 3) + return; + + // The question mark is between sub-expressions 1 and 2, the colon is between + // sub-expressions 2 and 3. + const int searchStartPosQuestionMark = posForNodeEnd(children.first()); + const int searchEndPosQuestionMark = posForNodeStart(children.at(1)); + QStringView content = subViewEnd(m_docContent, searchStartPosQuestionMark, + searchEndPosQuestionMark); + const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?"))); + if (questionMarkPos == -1) + return; + const int searchStartPosColon = posForNodeEnd(children.at(1)); + const int searchEndPosColon = posForNodeStart(children.at(2)); + content = subViewEnd(m_docContent, searchStartPosColon, searchEndPosColon); + const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":"))); + if (colonPos == -1) + return; + + const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos; + const int absColonPos = searchStartPosColon + colonPos; + if (absQuestionMarkPos > absColonPos) + return; + + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PUNCTUATION; + result.textStyles.mixinStyles.push_back(C_OPERATOR); + Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column); + result.length = 1; + result.kind = CppEditor::SemanticHighlighter::TernaryIf; + insertResult(result); + Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column); + result.kind = CppEditor::SemanticHighlighter::TernaryElse; + insertResult(result); + return; + } + + if (isDeclaration && (node.kind() == "FunctionTemplate" + || node.kind() == "ClassTemplate")) { + // The child nodes are the template parameters and and the function or class. + // The opening angle bracket is before the first child node, the closing angle + // bracket is before the function child node and after the last param node. + const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" + ? "Function" : "CXXRecord"); + const auto functionOrClassIt = std::find_if(children.begin(), children.end(), + [&classOrFunctionKind](const ClangdAstNode &n) { + return n.role() == "declaration" && n.kind() == classOrFunctionKind; + }); + if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) + return; + const int firstTemplateParamStartPos = posForNodeStart(children.first()); + const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1)); + const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, functionOrClassStartPos); + return; + } + + const auto isTemplateParamDecl = [](const ClangdAstNode &node) { + return node.isTemplateParameterDeclaration(); + }; + if (isDeclaration && node.kind() == "TypeAliasTemplate") { + // Children are one node of type TypeAlias and the template parameters. + // The opening angle bracket is before the first parameter and the closing + // angle bracket is after the last parameter. + // The TypeAlias node seems to appear first in the AST, even though lexically + // is comes after the parameters. We don't rely on the order here. + // Note that there is a second pair of angle brackets. That one is part of + // a TemplateSpecialization, which is handled further below. + const auto firstTemplateParam = std::find_if(children.begin(), children.end(), + isTemplateParamDecl); + if (firstTemplateParam == children.end()) + return; + const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), + isTemplateParamDecl); + QTC_ASSERT(lastTemplateParam != children.rend(), return); + const auto typeAlias = std::find_if(children.begin(), children.end(), + [](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; }); + if (typeAlias == children.end()) + return; + + const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); + const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); + const int searchEndPos = posForNodeStart(*typeAlias); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, searchEndPos); + return; + } + + if (isDeclaration && node.kind() == "ClassTemplateSpecialization") { + // There is one child of kind TemplateSpecialization. The first pair + // of angle brackets comes before that. + if (children.size() == 1) { + const int childNodePos = posForNodeStart(children.first()); + insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos); + } + return; + } + + if (isDeclaration && node.kind() == "TemplateTemplateParm") { + // The child nodes are template arguments and template parameters. + // Arguments seem to appear before parameters in the AST, even though they + // come after them in the source code. We don't rely on the order here. + const auto firstTemplateParam = std::find_if(children.begin(), children.end(), + isTemplateParamDecl); + if (firstTemplateParam == children.end()) + return; + const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), + isTemplateParamDecl); + QTC_ASSERT(lastTemplateParam != children.rend(), return); + const auto templateArg = std::find_if(children.begin(), children.end(), + [](const ClangdAstNode &n) { return n.role() == "template argument"; }); + + const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); + const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); + const int searchEndPos = templateArg == children.end() + ? nodeEndPos : posForNodeStart(*templateArg); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, searchEndPos); + return; + } + + // {static,dynamic,reinterpret}_cast<>(). + if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) { + // First child is type, second child is expression. + // The opening angle bracket is before the first child, the closing angle bracket + // is between the two children. + if (children.size() == 2) { + insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()), + posForNodeEnd(children.first()), + posForNodeStart(children.last())); + } + return; + } + + if (node.kind() == "TemplateSpecialization") { + // First comes the template type, then the template arguments. + // The opening angle bracket is before the first template argument, + // the closing angle bracket is after the last template argument. + // The first child node has no range, so we start searching at the parent node. + if (children.size() >= 2) { + int searchStart2 = posForNodeEnd(children.last()); + int searchEnd2 = nodeEndPos; + + // There is a weird off-by-one error on the clang side: If there is a + // nested template instantiation *and* there is no space between + // the closing angle brackets, then the inner TemplateSpecialization node's range + // will extend one character too far, covering the outer's closing angle bracket. + // This is what we are correcting for here. + // This issue is tracked at https://github.com/clangd/clangd/issues/871. + if (searchStart2 == searchEnd2) + --searchStart2; + insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)), + searchStart2, searchEnd2); + } + return; + } + + if (!isExpression && !isDeclaration) + return; + + // Operators, overloaded ones in particular. + static const QString operatorPrefix = "operator"; + QString detail = node.detail().value_or(QString()); + const bool isCallToNew = node.kind() == "CXXNew"; + const bool isCallToDelete = node.kind() == "CXXDelete"; + const auto isProperOperator = [&] { + if (isCallToNew || isCallToDelete) + return true; + if (!detail.startsWith(operatorPrefix)) + return false; + if (detail == operatorPrefix) + return false; + const QChar nextChar = detail.at(operatorPrefix.length()); + return !nextChar.isLetterOrNumber() && nextChar != '_'; + }; + if (!isProperOperator()) + return; + + if (!isCallToNew && !isCallToDelete) + detail.remove(0, operatorPrefix.length()); + + HighlightingResult result; + result.useTextSyles = true; + const bool isConversionOp = node.kind() == "CXXConversion"; + const bool isOverloaded = !isConversionOp + && (isDeclaration || ((!isCallToNew && !isCallToDelete) + || node.arcanaContains("CXXMethod"))); + result.textStyles.mainStyle = isConversionOp + ? C_PRIMITIVE_TYPE + : isCallToNew || isCallToDelete || detail.at(0).isSpace() + ? C_KEYWORD : C_PUNCTUATION; + result.textStyles.mixinStyles.push_back(C_OPERATOR); + if (isOverloaded) + result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR); + if (isDeclaration) + result.textStyles.mixinStyles.push_back(C_DECLARATION); + + const QStringView nodeText = subViewEnd(m_docContent, nodeStartPos, nodeEndPos); + + if (isCallToNew || isCallToDelete) { + result.line = node.range().start().line() + 1; + result.column = node.range().start().character() + 1; + result.length = isCallToNew ? 3 : 6; + insertResult(result); + if (node.arcanaContains("array")) { + const int openingBracketOffset = nodeText.indexOf('['); + if (openingBracketOffset == -1) + return; + const int closingBracketOffset = nodeText.lastIndexOf(']'); + if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset) + return; + + result.textStyles.mainStyle = C_PUNCTUATION; + result.length = 1; + Utils::Text::convertPosition(m_doc, + nodeStartPos + openingBracketOffset, + &result.line, &result.column); + insertResult(result); + Utils::Text::convertPosition(m_doc, + nodeStartPos + closingBracketOffset, + &result.line, &result.column); + insertResult(result); + } + return; + } + + if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) { + result.line = node.range().start().line() + 1; + result.column = node.range().start().character() + 1; + result.length = 1; + insertResult(result); + result.line = node.range().end().line() + 1; + result.column = node.range().end().character(); + insertResult(result); + return; + } + + const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length(); + + // The simple case: Call to operator+, +=, * etc. + if (nodeEndPos - nodeStartPos == opStringLen) { + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + + const int prefixOffset = nodeText.indexOf(operatorPrefix); + if (prefixOffset == -1) + return; + + const bool isArray = detail == "[]"; + const bool isCall = detail == "()"; + const bool isArrayNew = detail == " new[]"; + const bool isArrayDelete = detail == " delete[]"; + const QStringView searchTerm = isArray || isCall + ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete + ? QStringView(detail).chopped(2) : detail; + const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset + + operatorPrefix.length()); + if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1) + return; + + const int opStringOffsetInDoc = nodeStartPos + opStringOffset + + detail.length() - opStringLen; + Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column); + result.length = opStringLen; + if (isArray || isCall) + result.length = 1; + else if (isArrayNew || isArrayDelete) + result.length -= 2; + if (!isArray && !isCall) + insertResult(result); + if (!isArray && !isCall && !isArrayNew && !isArrayDelete) + return; + + result.textStyles.mainStyle = C_PUNCTUATION; + result.length = 1; + const int openingParenOffset = nodeText.indexOf( + isCall ? '(' : '[', prefixOffset + operatorPrefix.length()); + if (openingParenOffset == -1) + return; + const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1); + if (closingParenOffset == -1 || closingParenOffset < openingParenOffset) + return; + Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset, + &result.line, &result.column); + insertResult(result); + Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset, + &result.line, &result.column); + insertResult(result); +} + +void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node) +{ + if (m_future.isCanceled()) + return; + const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus; + m_currentFileStatus = node.fileStatus(m_filePath); + if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown + && prevFileStatus != ClangdAstNode::FileStatus::Ours) { + m_currentFileStatus = prevFileStatus; + } + switch (m_currentFileStatus) { + case ClangdAstNode::FileStatus::Ours: + case ClangdAstNode::FileStatus::Unknown: + collectFromNode(node); + [[fallthrough]]; + case ClangdAstNode::FileStatus::Foreign: + case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: { + const auto children = node.children(); + if (!children) + return; + for (const ClangdAstNode &childNode : *children) + visitNode(childNode); + break; + } + } + m_currentFileStatus = prevFileStatus; +} + +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/remotelinux/remotelinuxkillappservice.h b/src/plugins/clangcodemodel/clangdsemantichighlighting.h similarity index 55% rename from src/plugins/remotelinux/remotelinuxkillappservice.h rename to src/plugins/clangcodemodel/clangdsemantichighlighting.h index 042f4bac0ea..29a4446e9e1 100644 --- a/src/plugins/remotelinux/remotelinuxkillappservice.h +++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -25,34 +25,33 @@ #pragma once -#include "abstractremotelinuxdeployservice.h" +#include +#include +#include +#include -namespace RemoteLinux { -namespace Internal { class RemoteLinuxKillAppServicePrivate; } +namespace LanguageClient { class ExpandedSemanticToken; } +namespace TextEditor { +class HighlightingResult; +class TextDocument; +} +namespace Utils { class FilePath; } -class REMOTELINUX_EXPORT RemoteLinuxKillAppService : public AbstractRemoteLinuxDeployService -{ - Q_OBJECT -public: - RemoteLinuxKillAppService(); - ~RemoteLinuxKillAppService() override; +namespace ClangCodeModel::Internal { +class ClangdAstNode; +class TaskTimer; +Q_DECLARE_LOGGING_CATEGORY(clangdLogHighlight); - void setRemoteExecutable(const QString &filePath); +void doSemanticHighlighting( + QFutureInterface &future, + const Utils::FilePath &filePath, + const QList &tokens, + const QString &docContents, + const ClangdAstNode &ast, + const QPointer &textDocument, + int docRevision, + const QVersionNumber &clangdVersion, + const TaskTimer &taskTimer + ); -private: - void handleStdErr(); - void handleProcessFinished(); - - bool isDeploymentNecessary() const override; - - void doDeploy() override; - void stopDeployment() override; - - void handleSignalOpFinished(const QString &errorMessage); - void cleanup(); - void finishDeployment(); - - Internal::RemoteLinuxKillAppServicePrivate * const d; -}; - -} // namespace RemoteLinux +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/tasktimers.cpp b/src/plugins/clangcodemodel/tasktimers.cpp new file mode 100644 index 00000000000..e9e82d7d149 --- /dev/null +++ b/src/plugins/clangcodemodel/tasktimers.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "tasktimers.h" + +#include + +#include + +namespace ClangCodeModel::Internal { + +Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg); + +void ClangCodeModel::Internal::TaskTimer::stopTask() +{ + // This can happen due to the RAII mechanism employed with SubtaskTimer. + // The subtask timers will expire immediately after, so this does not distort + // the timing data. + if (m_subtasks > 0) { + QTC_CHECK(m_timer.isValid()); + m_elapsedMs += m_timer.elapsed(); + m_timer.invalidate(); + m_subtasks = 0; + } + m_started = false; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs + << " ms in UI thread"; + m_elapsedMs = 0; +} + +void TaskTimer::startSubtask() +{ + // We have some callbacks that are either synchronous or asynchronous, depending on + // dynamic conditions. In the sync case, we will have nested subtasks, in which case + // the inner ones must not collect timing data, as their code blocks are already covered. + if (++m_subtasks > 1) + return; + if (!m_started) { + QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0); + m_started = true; + m_finalized = false; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting"; + + // Used by ThreadedSubtaskTimer to mark the end of the whole highlighting operation + m_startTimer.restart(); + } + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at " + << QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz"); + QTC_CHECK(!m_timer.isValid()); + m_timer.start(); +} + +void TaskTimer::stopSubtask(bool isFinalizing) +{ + if (m_subtasks == 0) // See stopTask(). + return; + if (isFinalizing) + m_finalized = true; + if (--m_subtasks > 0) // See startSubtask(). + return; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at " + << QDateTime::currentDateTime().time().toString("hh:mm:ss.zzz"); + QTC_CHECK(m_timer.isValid()); + m_elapsedMs += m_timer.elapsed(); + m_timer.invalidate(); + if (m_finalized) + stopTask(); +} + +ThreadedSubtaskTimer::ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer) + : m_task(task), m_taskTimer(taskTimer) +{ + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread"; + m_timer.start(); +} + +ThreadedSubtaskTimer::~ThreadedSubtaskTimer() +{ + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed() + << " ms in dedicated thread"; + + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": Start to end: " + << m_taskTimer.startTimer().elapsed() << " ms"; +} + +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/tasktimers.h b/src/plugins/clangcodemodel/tasktimers.h new file mode 100644 index 00000000000..8995700d79b --- /dev/null +++ b/src/plugins/clangcodemodel/tasktimers.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace ClangCodeModel::Internal { + +Q_DECLARE_LOGGING_CATEGORY(clangdLogTiming); + +// TODO: Generalize, document interface and move to Utils? +class TaskTimer +{ +public: + TaskTimer(const QString &task) : m_task(task) {} + + void stopTask(); + void startSubtask(); + void stopSubtask(bool isFinalizing); + + QElapsedTimer startTimer() const { return m_startTimer; } + +private: + const QString m_task; + QElapsedTimer m_timer; + QElapsedTimer m_startTimer; + qint64 m_elapsedMs = 0; + int m_subtasks = 0; + bool m_started = false; + bool m_finalized = false; +}; + +class SubtaskTimer +{ +public: + SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); } + ~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); } + +protected: + void makeFinalizing() { m_isFinalizing = true; } + +private: + TaskTimer &m_timer; + bool m_isFinalizing = false; +}; + +class FinalizingSubtaskTimer : public SubtaskTimer +{ +public: + FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); } +}; + +class ThreadedSubtaskTimer +{ +public: + ThreadedSubtaskTimer(const QString &task, const TaskTimer &taskTimer); + ~ThreadedSubtaskTimer(); + +private: + const QString m_task; + QElapsedTimer m_timer; + const TaskTimer &m_taskTimer; +}; + +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangtools/clangtool.cpp b/src/plugins/clangtools/clangtool.cpp index 24b2822559b..cfcad7f87a7 100644 --- a/src/plugins/clangtools/clangtool.cpp +++ b/src/plugins/clangtools/clangtool.cpp @@ -51,6 +51,7 @@ #include +#include #include #include #include diff --git a/src/plugins/clangtools/clangtoolruncontrol.cpp b/src/plugins/clangtools/clangtoolruncontrol.cpp index 231e75bf4b0..5751ee5c3b2 100644 --- a/src/plugins/clangtools/clangtoolruncontrol.cpp +++ b/src/plugins/clangtools/clangtoolruncontrol.cpp @@ -253,10 +253,10 @@ void ClangToolRunWorker::start() // Collect files const auto clangIncludeDirAndVersion = - getClangIncludeDirAndVersion(runControl()->runnable().command.executable()); + getClangIncludeDirAndVersion(runControl()->commandLine().executable()); const AnalyzeUnits unitsToProcess = unitsToAnalyze(clangIncludeDirAndVersion.first, clangIncludeDirAndVersion.second); - qCDebug(LOG) << Q_FUNC_INFO << runControl()->runnable().command.executable() + qCDebug(LOG) << Q_FUNC_INFO << runControl()->commandLine().executable() << clangIncludeDirAndVersion.first << clangIncludeDirAndVersion.second; qCDebug(LOG) << "Files to process:" << unitsToProcess; diff --git a/src/plugins/clangtools/documentclangtoolrunner.cpp b/src/plugins/clangtools/documentclangtoolrunner.cpp index d62b882d4a2..b0794676d0a 100644 --- a/src/plugins/clangtools/documentclangtoolrunner.cpp +++ b/src/plugins/clangtools/documentclangtoolrunner.cpp @@ -26,7 +26,6 @@ #include "documentclangtoolrunner.h" #include "clangfileinfo.h" -#include "clangfixitsrefactoringchanges.h" #include "clangtidyclazyrunner.h" #include "clangtoolruncontrol.h" #include "clangtoolsconstants.h" @@ -39,13 +38,18 @@ #include #include #include + #include + +#include #include #include #include + #include #include #include + #include #include diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp index 05160ff152b..1c4112f882c 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp @@ -1196,7 +1196,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const if (!version || !version->isValid()) { addWarning(tr("CMake configuration has a path to a qmake binary set, " "even though the kit has no valid Qt version.")); - } else if (qmakePath != version->qmakeFilePath() && isQt4) { + } else if (qmakePath != version->queryToolFilePath() && isQt4) { addWarning(tr("CMake configuration has a path to a qmake binary set " "that does not match the qmake binary path " "configured in the Qt version.")); @@ -1219,7 +1219,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const if (!tcC || !tcC->isValid()) { addWarning(tr("CMake configuration has a path to a C compiler set, " "even though the kit has no valid tool chain.")); - } else if (tcCPath != tcC->compilerCommand()) { + } else if (tcCPath != tcC->compilerCommand() && tcCPath != tcC->compilerCommand().onDevice(tcCPath)) { addWarning(tr("CMake configuration has a path to a C compiler set " "that does not match the compiler path " "configured in the tool chain of the kit.")); @@ -1235,7 +1235,7 @@ Tasks CMakeConfigurationKitAspect::validate(const Kit *k) const if (!tcCxx || !tcCxx->isValid()) { addWarning(tr("CMake configuration has a path to a C++ compiler set, " "even though the kit has no valid tool chain.")); - } else if (tcCxxPath != tcCxx->compilerCommand()) { + } else if (tcCxxPath != tcCxx->compilerCommand() && tcCxxPath != tcCxx->compilerCommand().onDevice(tcCxxPath)) { addWarning(tr("CMake configuration has a path to a C++ compiler set " "that does not match the compiler path " "configured in the tool chain of the kit.")); diff --git a/src/plugins/debugger/analyzer/startremotedialog.cpp b/src/plugins/debugger/analyzer/startremotedialog.cpp index e148c0f5bff..9f96ab89a2a 100644 --- a/src/plugins/debugger/analyzer/startremotedialog.cpp +++ b/src/plugins/debugger/analyzer/startremotedialog.cpp @@ -129,18 +129,16 @@ void StartRemoteDialog::validate() d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); } -Runnable StartRemoteDialog::runnable() const +CommandLine StartRemoteDialog::commandLine() const { - Kit *kit = d->kitChooser->currentKit(); - IDevice::ConstPtr device = DeviceKitAspect::device(kit); - FilePath filePath = FilePath::fromString(d->executable->text()); - if (device) - filePath = device->filePath(d->arguments->text()); + const Kit *kit = d->kitChooser->currentKit(); + const FilePath filePath = DeviceKitAspect::deviceFilePath(kit, d->executable->text()); + return {filePath, d->arguments->text(), CommandLine::Raw}; +} - Runnable r; - r.command = {filePath, d->arguments->text(), CommandLine::Raw}; - r.workingDirectory = FilePath::fromString(d->workingDirectory->text()); - return r; +FilePath StartRemoteDialog::workingDirectory() const +{ + return FilePath::fromString(d->workingDirectory->text()); } } // namespace Debugger diff --git a/src/plugins/debugger/analyzer/startremotedialog.h b/src/plugins/debugger/analyzer/startremotedialog.h index b1b91bc62fd..72d7478fb7d 100644 --- a/src/plugins/debugger/analyzer/startremotedialog.h +++ b/src/plugins/debugger/analyzer/startremotedialog.h @@ -29,7 +29,10 @@ #include -namespace ProjectExplorer { class Runnable; } +namespace Utils { +class CommandLine; +class FilePath; +} // Utils namespace Debugger { @@ -43,7 +46,8 @@ public: explicit StartRemoteDialog(QWidget *parent = nullptr); ~StartRemoteDialog() override; - ProjectExplorer::Runnable runnable() const; + Utils::CommandLine commandLine() const; + Utils::FilePath workingDirectory() const; private: void validate(); diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h index 74ccf23e430..09ffe19365a 100644 --- a/src/plugins/debugger/debuggerconstants.h +++ b/src/plugins/debugger/debuggerconstants.h @@ -36,14 +36,6 @@ const char MODE_DEBUG[] = "Mode.Debug"; // Debug mode context const char C_DEBUGMODE[] = "Debugger.DebugMode"; -// Common debugger constants. -const char kPeripheralDescriptionFile[] = "PeripheralDescriptionFile"; - -// UVSC-specific debugger constants. -const char kUVisionProjectFilePath[] = "UVisionProjectFilePath"; -const char kUVisionOptionsFilePath[] = "UVisionOptionsFilePath"; -const char kUVisionSimulator[] = "UVisionSimulator"; - } // namespace Constants // Keep in sync with dumper.py diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index 34830aafaca..4205384cd99 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -206,6 +206,14 @@ public: Utils::FilePath dumperPath; int fallbackQtVersion = 0x50200; + + // Common debugger constants. + Utils::FilePath peripheralDescriptionFile; + + // UVSC-specific debugger constants. + Utils::FilePath uVisionProjectFilePath; + Utils::FilePath uVisionOptionsFilePath; + bool uVisionSimulator = false; }; class UpdateParameters diff --git a/src/plugins/debugger/debuggeritemmanager.cpp b/src/plugins/debugger/debuggeritemmanager.cpp index 9efc8df4a25..4f0d4702589 100644 --- a/src/plugins/debugger/debuggeritemmanager.cpp +++ b/src/plugins/debugger/debuggeritemmanager.cpp @@ -669,7 +669,9 @@ void DebuggerItemManagerPrivate::autoDetectCdbDebuggers() for (const QFileInfo &kitFolderFi : kitFolders) { const QString path = kitFolderFi.absoluteFilePath(); - const QStringList abis = {"x86", "x64", "arm64"}; + QStringList abis = {"x86", "x64"}; + if (HostOsInfo::hostArchitecture() == HostOsInfo::HostArchitectureArm) + abis << "arm64"; for (const QString &abi: abis) { const QFileInfo cdbBinary(path + "/Debuggers/" + abi + "/cdb.exe"); if (cdbBinary.isExecutable()) diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index 8d8c013c206..3accce69ebb 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -1736,8 +1736,7 @@ RunControl *DebuggerPluginPrivate::attachToRunningProcess(Kit *kit, runControl->setDisplayName(tr("Process %1").arg(processInfo.processId)); auto debugger = new DebuggerRunTool(runControl); debugger->setAttachPid(ProcessHandle(processInfo.processId)); - debugger->setInferiorExecutable(FilePath::fromString(processInfo.executable)); - debugger->setInferiorDevice(device); + debugger->setInferiorExecutable(device->filePath(processInfo.executable)); debugger->setStartMode(AttachToLocalProcess); debugger->setCloseMode(DetachAtClose); debugger->setContinueAfterAttach(contAfterAttach); diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp index 832f024c85a..132ac508543 100644 --- a/src/plugins/debugger/debuggerruncontrol.cpp +++ b/src/plugins/debugger/debuggerruncontrol.cpp @@ -395,11 +395,6 @@ void DebuggerRunTool::setInferiorEnvironment(const Utils::Environment &env) m_runParameters.inferior.environment = env; } -void DebuggerRunTool::setInferiorDevice(IDevice::ConstPtr device) -{ - m_runParameters.inferior.device = device; -} - void DebuggerRunTool::setRunControlName(const QString &name) { m_runParameters.displayName = name; @@ -910,7 +905,7 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm } } - Runnable inferior = runnable(); + Runnable inferior = runControl->runnable(); const FilePath &debuggerExecutable = m_runParameters.debugger.command.executable(); inferior.command.setExecutable(inferior.command.executable().onDevice(debuggerExecutable)); inferior.workingDirectory = inferior.workingDirectory.onDevice(debuggerExecutable); diff --git a/src/plugins/debugger/debuggerruncontrol.h b/src/plugins/debugger/debuggerruncontrol.h index a0664a267da..a58ffaff9f6 100644 --- a/src/plugins/debugger/debuggerruncontrol.h +++ b/src/plugins/debugger/debuggerruncontrol.h @@ -76,7 +76,6 @@ public: void setInferior(const ProjectExplorer::Runnable &runnable); void setInferiorExecutable(const Utils::FilePath &executable); void setInferiorEnvironment(const Utils::Environment &env); // Used by GammaRay plugin - void setInferiorDevice(ProjectExplorer::IDeviceConstPtr device); // Used by cdbengine void setRunControlName(const QString &name); void setStartMessage(const QString &msg); void addQmlServerInferiorCommandLineArgumentIfNeeded(); @@ -132,6 +131,8 @@ public: Internal::TerminalRunner *terminalRunner() const; DebuggerEngineType cppEngineType() const; + Internal::DebuggerRunParameters &runParameters() { return m_runParameters; } + private: bool fixupParameters(); void handleEngineStarted(Internal::DebuggerEngine *engine); diff --git a/src/plugins/debugger/loadcoredialog.cpp b/src/plugins/debugger/loadcoredialog.cpp index 71fc01641d0..ac0da69d36f 100644 --- a/src/plugins/debugger/loadcoredialog.cpp +++ b/src/plugins/debugger/loadcoredialog.cpp @@ -25,23 +25,21 @@ #include "loadcoredialog.h" -#include "debuggerdialogs.h" #include "debuggerkitinformation.h" #include "gdb/gdbengine.h" #include +#include #include -#include -#include +#include #include +#include #include -#include #include #include #include #include -#include #include #include #include @@ -69,7 +67,6 @@ class SelectRemoteFileDialog : public QDialog public: explicit SelectRemoteFileDialog(QWidget *parent); - ~SelectRemoteFileDialog(); void attachToDevice(Kit *k); FilePath localFile() const { return m_localFile; } @@ -77,7 +74,6 @@ public: private: void selectFile(); - void clearWatcher(); QSortFilterProxyModel m_model; DeviceFileSystemModel m_fileSystemModel; @@ -86,7 +82,7 @@ private: QDialogButtonBox *m_buttonBox; FilePath m_localFile; FilePath m_remoteFile; - QFutureWatcher *m_watcher = nullptr; + FileTransfer m_fileTransfer; }; SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent) @@ -119,11 +115,21 @@ SelectRemoteFileDialog::SelectRemoteFileDialog(QWidget *parent) connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &SelectRemoteFileDialog::selectFile); -} -SelectRemoteFileDialog::~SelectRemoteFileDialog() -{ - clearWatcher(); + connect(&m_fileTransfer, &FileTransfer::done, this, [this](const ProcessResultData &result) { + const bool success = result.m_error == QProcess::UnknownError + && result.m_exitStatus == QProcess::NormalExit + && result.m_exitCode == 0; + if (success) { + m_textBrowser->append(tr("Download of remote file succeeded.")); + accept(); + } else { + m_textBrowser->append(tr("Download of remote file failed: %1") + .arg(result.m_errorString)); + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + m_fileSystemView->setEnabled(true); + } + }); } void SelectRemoteFileDialog::attachToDevice(Kit *k) @@ -135,22 +141,6 @@ void SelectRemoteFileDialog::attachToDevice(Kit *k) m_fileSystemModel.setDevice(device); } -static void copyFile(QFutureInterface &futureInterface, const FilePath &remoteSource, - const FilePath &localDestination) -{ - // TODO: this should be implemented transparently in FilePath::copyFile() - // The code here should be just: - // - // futureInterface.reportResult(remoteSource.copyFile(localDestination)); - // - // The implementation below won't handle really big files, like core files. - - const QByteArray data = remoteSource.fileContents(); - if (futureInterface.isCanceled()) - return; - futureInterface.reportResult(localDestination.writeFileContents(data)); -} - void SelectRemoteFileDialog::selectFile() { QModelIndex idx = m_model.mapToSource(m_fileSystemView->currentIndex()); @@ -161,7 +151,7 @@ void SelectRemoteFileDialog::selectFile() m_fileSystemView->setEnabled(false); { - Utils::TemporaryFile localFile("remotecore-XXXXXX"); + TemporaryFile localFile("remotecore-XXXXXX"); localFile.open(); m_localFile = FilePath::fromString(localFile.fileName()); } @@ -169,33 +159,8 @@ void SelectRemoteFileDialog::selectFile() idx = idx.sibling(idx.row(), 1); m_remoteFile = FilePath::fromVariant(m_fileSystemModel.data(idx, DeviceFileSystemModel::PathRole)); - clearWatcher(); - m_watcher = new QFutureWatcher(this); - auto future = runAsync(copyFile, m_remoteFile, m_localFile); - connect(m_watcher, &QFutureWatcher::finished, this, [this] { - const bool success = m_watcher->result(); - if (success) { - m_textBrowser->append(tr("Download of remote file succeeded.")); - accept(); - } else { - m_textBrowser->append(tr("Download of remote file failed.")); - m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - m_fileSystemView->setEnabled(true); - } - }); - m_watcher->setFuture(future); -} - -void SelectRemoteFileDialog::clearWatcher() -{ - if (!m_watcher) - return; - - m_watcher->disconnect(); - m_watcher->future().cancel(); - m_watcher->future().waitForFinished(); - delete m_watcher; - m_watcher = nullptr; + m_fileTransfer.setFilesToTransfer({{m_remoteFile, m_localFile}}); + m_fileTransfer.start(); } /////////////////////////////////////////////////////////////////////// diff --git a/src/plugins/debugger/peripheralregisterhandler.cpp b/src/plugins/debugger/peripheralregisterhandler.cpp index 5c63c156cae..b1748d3b986 100644 --- a/src/plugins/debugger/peripheralregisterhandler.cpp +++ b/src/plugins/debugger/peripheralregisterhandler.cpp @@ -732,12 +732,9 @@ void PeripheralRegisterHandler::updateRegisterGroups() clear(); const DebuggerRunParameters &rp = m_engine->runParameters(); - const FilePath peripheralDescriptionFile = FilePath::fromVariant( - rp.inferior.extraData.value(Debugger::Constants::kPeripheralDescriptionFile)); - - if (!peripheralDescriptionFile.exists()) + if (!rp.peripheralDescriptionFile.exists()) return; - m_peripheralRegisterGroups = availablePeripheralRegisterGroups(peripheralDescriptionFile); + m_peripheralRegisterGroups = availablePeripheralRegisterGroups(rp.peripheralDescriptionFile); } void PeripheralRegisterHandler::updateRegister(quint64 address, quint64 value) diff --git a/src/plugins/debugger/uvsc/uvscengine.cpp b/src/plugins/debugger/uvsc/uvscengine.cpp index 915a6c970bd..6a6253c9dc9 100644 --- a/src/plugins/debugger/uvsc/uvscengine.cpp +++ b/src/plugins/debugger/uvsc/uvscengine.cpp @@ -536,10 +536,8 @@ void UvscEngine::updateAll() bool UvscEngine::configureProject(const DebuggerRunParameters &rp) { // Fetch patchs for the generated uVision project files. - const FilePath optionsPath = FilePath::fromString(rp.inferior.extraData.value( - Constants::kUVisionOptionsFilePath).toString()); - const FilePath projectPath = FilePath::fromString(rp.inferior.extraData.value( - Constants::kUVisionProjectFilePath).toString()); + const FilePath optionsPath = rp.uVisionOptionsFilePath; + const FilePath projectPath = rp.uVisionProjectFilePath; showMessage("UVSC: LOADING PROJECT..."); if (!optionsPath.exists()) { @@ -557,7 +555,7 @@ bool UvscEngine::configureProject(const DebuggerRunParameters &rp) } showMessage("UVSC: SETTING PROJECT DEBUG TARGET..."); - m_simulator = rp.inferior.extraData.value(Constants::kUVisionSimulator).toBool(); + m_simulator = rp.uVisionSimulator; if (!m_client->setProjectDebugTarget(m_simulator)) { handleSetupFailure(tr("Internal error: Unable to set the uVision debug target: %1.") .arg(m_client->errorString())); @@ -606,8 +604,7 @@ void UvscEngine::handleProjectClosed() m_loadingRequired = false; const DebuggerRunParameters &rp = runParameters(); - const FilePath projectPath = FilePath::fromString(rp.inferior.extraData.value( - Constants::kUVisionProjectFilePath).toString()); + const FilePath projectPath = rp.uVisionProjectFilePath; // This magic function removes specific files from the uVision // project directory. Without of this we can't enumerate the local diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 4fb75dc198c..d7a59aa38c0 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -27,6 +27,7 @@ #include #include +#include #include @@ -44,7 +45,7 @@ public: QString tag; QString size; bool useLocalUidGid = true; - QStringList mounts; + QStringList mounts = { Core::DocumentManager::projectsDirectory().toString() }; }; class DockerDevice : public ProjectExplorer::IDevice diff --git a/src/plugins/docker/kitdetector.cpp b/src/plugins/docker/kitdetector.cpp index 35f1a52f23d..474322a6043 100644 --- a/src/plugins/docker/kitdetector.cpp +++ b/src/plugins/docker/kitdetector.cpp @@ -25,8 +25,6 @@ #include "kitdetector.h" -#include "dockerconstants.h" - #include #include @@ -216,10 +214,10 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const QString error; const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) { - if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake, - false, - m_sharedId, - &error)) { + if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQueryToolPath(qmake, + false, + m_sharedId, + &error)) { if (qtVersion->isValid()) { if (!Utils::anyOf(qtVersions, [qtVersion](QtVersion* other) { @@ -229,7 +227,7 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const qtVersions.append(qtVersion); QtVersionManager::addVersion(qtVersion); emit q->logOutput( - tr("Found \"%1\"").arg(qtVersion->qmakeFilePath().toUserOutput())); + tr("Found \"%1\"").arg(qtVersion->queryToolFilePath().toUserOutput())); } } } @@ -337,7 +335,7 @@ void KitDetectorPrivate::autoDetect() if (cmakeId.isValid()) k->setValue(CMakeProjectManager::Constants::TOOL_ID, cmakeId.toSetting()); - DeviceTypeKitAspect::setDeviceTypeId(k, Constants::DOCKER_DEVICE_TYPE); + DeviceTypeKitAspect::setDeviceTypeId(k, m_device->type()); DeviceKitAspect::setDevice(k, m_device); BuildDeviceKitAspect::setDevice(k, m_device); diff --git a/src/plugins/gitlab/CMakeLists.txt b/src/plugins/gitlab/CMakeLists.txt index 4e45c77bb71..7bde1d67560 100644 --- a/src/plugins/gitlab/CMakeLists.txt +++ b/src/plugins/gitlab/CMakeLists.txt @@ -3,6 +3,8 @@ add_qtc_plugin(GitLab PLUGIN_DEPENDS Core ProjectExplorer Git VcsBase DEPENDS Utils SOURCES + gitlabclonedialog.cpp gitlabclonedialog.h + gitlabdialog.cpp gitlabdialog.h gitlabdialog.ui gitlaboptionspage.cpp gitlaboptionspage.h gitlabparameters.cpp gitlabparameters.h gitlabplugin.cpp gitlabplugin.h diff --git a/src/plugins/gitlab/gitlab.qbs b/src/plugins/gitlab/gitlab.qbs index 03a06ec5cc9..96076b85d1a 100644 --- a/src/plugins/gitlab/gitlab.qbs +++ b/src/plugins/gitlab/gitlab.qbs @@ -10,6 +10,11 @@ QtcPlugin { Depends { name: "Utils" } files: [ + "gitlabclonedialog.cpp", + "gitlabclonedialog.h", + "gitlabdialog.cpp", + "gitlabdialog.h", + "gitlabdialog.ui", "gitlaboptionspage.cpp", "gitlaboptionspage.h", "gitlabparameters.cpp", diff --git a/src/plugins/gitlab/gitlabclonedialog.cpp b/src/plugins/gitlab/gitlabclonedialog.cpp new file mode 100644 index 00000000000..e1dabccc28a --- /dev/null +++ b/src/plugins/gitlab/gitlabclonedialog.cpp @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "gitlabclonedialog.h" + +#include "gitlabprojectsettings.h" +#include "resultparser.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace GitLab { + +GitLabCloneDialog::GitLabCloneDialog(const Project &project, QWidget *parent) + : QDialog(parent) +{ + setWindowTitle(tr("Clone Repository")); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(new QLabel(tr("Specify repository URL, checkout path and directory."))); + QHBoxLayout *centerLayout = new QHBoxLayout; + QFormLayout *form = new QFormLayout; + m_repositoryCB = new QComboBox(this); + m_repositoryCB->addItems({project.sshUrl, project.httpUrl}); + form->addRow(tr("Repository"), m_repositoryCB); + m_pathChooser = new Utils::PathChooser(this); + m_pathChooser->setExpectedKind(Utils::PathChooser::ExistingDirectory); + form->addRow(tr("Path"), m_pathChooser); + m_directoryLE = new Utils::FancyLineEdit(this); + m_directoryLE->setValidationFunction([this](Utils::FancyLineEdit *e, QString *msg) { + const Utils::FilePath fullPath = m_pathChooser->filePath().pathAppended(e->text()); + bool alreadyExists = fullPath.exists(); + if (alreadyExists && msg) + *msg = tr("Path \"%1\" already exists.").arg(fullPath.toUserOutput()); + return !alreadyExists; + }); + form->addRow(tr("Directory"), m_directoryLE); + m_submodulesCB = new QCheckBox(this); + form->addRow(tr("Recursive"), m_submodulesCB); + centerLayout->addLayout(form); + m_cloneOutput = new QPlainTextEdit(this); + m_cloneOutput->setReadOnly(true); + centerLayout->addWidget(m_cloneOutput); + layout->addLayout(centerLayout); + m_infoLabel = new Utils::InfoLabel(this); + layout->addWidget(m_infoLabel); + layout->addStretch(1); + auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, this); + m_cloneButton = new QPushButton(tr("Clone"), this); + buttons->addButton(m_cloneButton, QDialogButtonBox::ActionRole); + m_cancelButton = buttons->button(QDialogButtonBox::Cancel); + layout->addWidget(buttons); + setLayout(layout); + + m_pathChooser->setFilePath(Core::DocumentManager::projectsDirectory()); + auto [host, path, port] + = GitLabProjectSettings::remotePartsFromRemote(m_repositoryCB->currentText()); + int slashIndex = path.indexOf('/'); + QTC_ASSERT(slashIndex > 0, return); + m_directoryLE->setText(path.mid(slashIndex + 1)); + + connect(m_pathChooser, &Utils::PathChooser::pathChanged, this, [this]() { + m_directoryLE->validate(); + GitLabCloneDialog::updateUi(); + }); + connect(m_directoryLE, &Utils::FancyLineEdit::textChanged, this, &GitLabCloneDialog::updateUi); + connect(m_cloneButton, &QPushButton::clicked, this, &GitLabCloneDialog::cloneProject); + connect(m_cancelButton, &QPushButton::clicked, + this, &GitLabCloneDialog::cancel); + connect(this, &QDialog::rejected, this, [this]() { + if (m_commandRunning) { + cancel(); + QApplication::restoreOverrideCursor(); + return; + } + }); + + updateUi(); + resize(575, 265); +} + +void GitLabCloneDialog::updateUi() +{ + bool pathValid = m_pathChooser->isValid(); + bool directoryValid = m_directoryLE->isValid(); + m_cloneButton->setEnabled(pathValid && directoryValid); + if (!pathValid) { + m_infoLabel->setText(m_pathChooser->errorMessage()); + m_infoLabel->setType(Utils::InfoLabel::Error); + } else if (!directoryValid) { + m_infoLabel->setText(m_directoryLE->errorMessage()); + m_infoLabel->setType(Utils::InfoLabel::Error); + } + m_infoLabel->setVisible(!pathValid || !directoryValid); +} + +void GitLabCloneDialog::cloneProject() +{ + Core::IVersionControl *vc = Core::VcsManager::versionControl(Utils::Id::fromString("G.Git")); + QTC_ASSERT(vc, return); + const QStringList extraArgs = m_submodulesCB->isChecked() ? QStringList{ "--recursive" } + : QStringList{}; + m_command = vc->createInitialCheckoutCommand(m_repositoryCB->currentText(), + m_pathChooser->absoluteFilePath(), + m_directoryLE->text(), extraArgs); + const Utils::FilePath workingDirectory = m_pathChooser->absoluteFilePath(); + m_command->setProgressiveOutput(true); + connect(m_command, &Utils::ShellCommand::stdOutText, this, [this](const QString &text) { + m_cloneOutput->appendPlainText(text); + }); + connect(m_command, &Utils::ShellCommand::stdErrText, this, [this](const QString &text) { + m_cloneOutput->appendPlainText(text); + }); + connect(m_command, &Utils::ShellCommand::finished, this, &GitLabCloneDialog::cloneFinished); + QApplication::setOverrideCursor(Qt::WaitCursor); + + m_cloneOutput->clear(); + m_cloneButton->setEnabled(false); + m_pathChooser->setReadOnly(true); + m_directoryLE->setReadOnly(true); + m_commandRunning = true; + m_command->execute(); +} + +void GitLabCloneDialog::cancel() +{ + if (m_commandRunning) { + m_cloneOutput->appendPlainText(tr("User canceled process.")); + m_cancelButton->setEnabled(false); + m_command->cancel(); // FIXME does not cancel the git processes... QTCREATORBUG-27567 + } else { + reject(); + } +} + +static Utils::FilePaths scanDirectoryForFiles(const Utils::FilePath &directory) +{ + Utils::FilePaths result; + const Utils::FilePaths entries = directory.dirEntries(QDir::AllEntries | QDir::NoDotAndDotDot); + + for (const Utils::FilePath &entry : entries) { + if (entry.isDir()) + result.append(scanDirectoryForFiles(entry)); + else + result.append(entry); + } + return result; +} + +void GitLabCloneDialog::cloneFinished(bool ok, int exitCode) +{ + const bool success = (ok && exitCode == 0); + m_commandRunning = false; + delete m_command; + m_command = nullptr; + + const QString emptyLine("\n\n"); + m_cloneOutput->appendPlainText(emptyLine); + QApplication::restoreOverrideCursor(); + + if (success) { + m_cloneOutput->appendPlainText(tr("Cloning succeeded.") + emptyLine); + m_cloneButton->setEnabled(false); + + const Utils::FilePath base = m_pathChooser->filePath().pathAppended(m_directoryLE->text()); + Utils::FilePaths filesWeMayOpen + = Utils::filtered(scanDirectoryForFiles(base), [](const Utils::FilePath &f) { + return ProjectExplorer::ProjectManager::canOpenProjectForMimeType( + Utils::mimeTypeForFile(f)); + }); + + // limit the files to the most top-level item(s) + int minimum = std::numeric_limits::max(); + for (const Utils::FilePath &f : filesWeMayOpen) { + int parentCount = f.toString().count('/'); + if (parentCount < minimum) + minimum = parentCount; + } + filesWeMayOpen = Utils::filtered(filesWeMayOpen, [minimum](const Utils::FilePath &f) { + return f.toString().count('/') == minimum; + }); + + hide(); // avoid to many dialogs.. FIXME: maybe change to some wizard approach? + if (filesWeMayOpen.isEmpty()) { + QMessageBox::warning(this, tr("Warning"), + tr("Cloned project does not have a project file that can be " + "opened. Try importing the project as a generic project.")); + accept(); + } else { + const QStringList pFiles = Utils::transform(filesWeMayOpen, + [base](const Utils::FilePath &f) { + return f.relativePath(base).toUserOutput(); + }); + bool ok = false; + const QString fileToOpen + = QInputDialog::getItem(this, tr("Open Project"), + tr("Choose the project file to be opened."), + pFiles, 0, false, &ok); + accept(); + if (ok && !fileToOpen.isEmpty()) + ProjectExplorer::ProjectExplorerPlugin::openProject(base.pathAppended(fileToOpen)); + } + } else { + m_cloneOutput->appendPlainText(tr("Cloning failed.") + emptyLine); + const Utils::FilePath fullPath = m_pathChooser->filePath() + .pathAppended(m_directoryLE->text()); + fullPath.removeRecursively(); + m_cloneButton->setEnabled(true); + m_cancelButton->setEnabled(true); + m_pathChooser->setReadOnly(false); + m_directoryLE->setReadOnly(false); + m_directoryLE->validate(); + } +} + +} // namespace GitLab diff --git a/src/plugins/gitlab/gitlabclonedialog.h b/src/plugins/gitlab/gitlabclonedialog.h new file mode 100644 index 00000000000..5d3b19543aa --- /dev/null +++ b/src/plugins/gitlab/gitlabclonedialog.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +QT_BEGIN_NAMESPACE +class QCheckBox; +class QComboBox; +class QPlainTextEdit; +class QPushButton; +QT_END_NAMESPACE + +namespace Core { class ShellCommand; } + +namespace Utils { +class FancyLineEdit; +class InfoLabel; +class PathChooser; +} + +namespace GitLab { + +class Project; + +class GitLabCloneDialog : public QDialog +{ + Q_DECLARE_TR_FUNCTIONS(GitLab::GitLabCloneDialog) +public: + explicit GitLabCloneDialog(const Project &project, QWidget *parent = nullptr); + +private: + void updateUi(); + void cloneProject(); + void cancel(); + void cloneFinished(bool ok, int exitCode); + + QComboBox * m_repositoryCB = nullptr; + QCheckBox *m_submodulesCB = nullptr; + QPushButton *m_cloneButton = nullptr; + QPushButton *m_cancelButton = nullptr; + QPlainTextEdit *m_cloneOutput = nullptr; + Utils::PathChooser *m_pathChooser = nullptr; + Utils::FancyLineEdit *m_directoryLE = nullptr; + Utils::InfoLabel *m_infoLabel = nullptr; + Core::ShellCommand *m_command = nullptr; + bool m_commandRunning = false; +}; + +} // namespace GitLab diff --git a/src/plugins/gitlab/gitlabdialog.cpp b/src/plugins/gitlab/gitlabdialog.cpp new file mode 100644 index 00000000000..6af65fef2dc --- /dev/null +++ b/src/plugins/gitlab/gitlabdialog.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "gitlabdialog.h" + +#include "gitlabclonedialog.h" +#include "gitlabparameters.h" +#include "gitlabplugin.h" +#include "gitlabprojectsettings.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace GitLab { + +GitLabDialog::GitLabDialog(QWidget *parent) + : QDialog(parent) + , m_lastTreeViewQuery(Query::NoQuery) +{ + m_ui.setupUi(this); + m_clonePB = new QPushButton(Utils::Icons::DOWNLOAD.icon(), tr("Clone..."), this); + m_ui.buttonBox->addButton(m_clonePB, QDialogButtonBox::ActionRole); + m_clonePB->setEnabled(false); + + updateRemotes(); + + connect(m_ui.remoteCB, QOverload::of(&QComboBox::currentIndexChanged), + this, &GitLabDialog::requestMainViewUpdate); + connect(m_ui.searchLE, &QLineEdit::returnPressed, this, &GitLabDialog::querySearch); + connect(m_ui.searchPB, &QPushButton::clicked, this, &GitLabDialog::querySearch); + connect(m_clonePB, &QPushButton::clicked, this, &GitLabDialog::cloneSelected); + connect(m_ui.firstTB, &QToolButton::clicked, this, &GitLabDialog::queryFirstPage); + connect(m_ui.previousTB, &QToolButton::clicked, this, &GitLabDialog::queryPreviousPage); + connect(m_ui.nextTB, &QToolButton::clicked, this, &GitLabDialog::queryNextPage); + connect(m_ui.lastTB, &QToolButton::clicked, this, &GitLabDialog::queryLastPage); + requestMainViewUpdate(); +} + +void GitLabDialog::resetTreeView(QTreeView *treeView, QAbstractItemModel *model) +{ + auto oldModel = treeView->model(); + treeView->setModel(model); + delete oldModel; + if (model) { + connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, [this](const QItemSelection &selected, const QItemSelection &) { + m_clonePB->setEnabled(!selected.isEmpty()); + }); + m_clonePB->setEnabled(!treeView->selectionModel()->selectedIndexes().isEmpty()); + } +} + +void GitLabDialog::updateRemotes() +{ + m_ui.remoteCB->clear(); + const GitLabParameters *global = GitLabPlugin::globalParameters(); + for (const GitLabServer &server : qAsConst(global->gitLabServers)) + m_ui.remoteCB->addItem(server.displayString(), QVariant::fromValue(server)); + + m_ui.remoteCB->setCurrentIndex(m_ui.remoteCB->findData( + QVariant::fromValue(global->currentDefaultServer()))); +} + +void GitLabDialog::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) + return; + QDialog::keyPressEvent(event); +} + +void GitLabDialog::requestMainViewUpdate() +{ + m_lastPageInformation = PageInformation(); + m_lastTreeViewQuery = Query(Query::NoQuery); + + m_ui.mainLabel->setText({}); + m_ui.detailsLabel->setText({}); + m_ui.treeViewTitle->setText({}); + m_ui.searchLE->setText({}); + resetTreeView(m_ui.treeView, nullptr); + updatePageButtons(); + + bool linked = false; + m_currentServerId = Utils::Id(); + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project); + if (projSettings->isLinked()) { + m_currentServerId = projSettings->currentServer(); + linked = true; + } + } + if (!m_currentServerId.isValid()) + m_currentServerId = m_ui.remoteCB->currentData().value().id; + if (m_currentServerId.isValid()) { + const GitLabParameters *global = GitLabPlugin::globalParameters(); + const GitLabServer server = global->serverForId(m_currentServerId); + m_ui.remoteCB->setCurrentIndex(m_ui.remoteCB->findData(QVariant::fromValue(server))); + } + m_ui.remoteCB->setEnabled(!linked); + + const Query query(Query::User); + QueryRunner *runner = new QueryRunner(query, m_currentServerId, this); + connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) { + handleUser(ResultParser::parseUser(result)); + }); + connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); }); + runner->start(); +} + +void GitLabDialog::updatePageButtons() +{ + if (m_lastPageInformation.currentPage == -1) { + m_ui.currentPage->setVisible(false); + m_ui.firstTB->setVisible(false); + m_ui.lastTB->setVisible(false); + m_ui.previousTB->setVisible(false); + m_ui.nextTB->setVisible(false); + } else { + m_ui.currentPage->setText(QString::number(m_lastPageInformation.currentPage)); + m_ui.currentPage->setVisible(true); + m_ui.firstTB->setVisible(true); + m_ui.lastTB->setVisible(true); + } + if (m_lastPageInformation.currentPage > 1) { + m_ui.firstTB->setEnabled(true); + m_ui.previousTB->setText(QString::number(m_lastPageInformation.currentPage - 1)); + m_ui.previousTB->setVisible(true); + } else { + m_ui.firstTB->setEnabled(false); + m_ui.previousTB->setVisible(false); + } + if (m_lastPageInformation.currentPage < m_lastPageInformation.totalPages) { + m_ui.lastTB->setEnabled(true); + m_ui.nextTB->setText(QString::number(m_lastPageInformation.currentPage + 1)); + m_ui.nextTB->setVisible(true); + } else { + m_ui.lastTB->setEnabled(false); + m_ui.nextTB->setVisible(false); + } +} + +void GitLabDialog::queryFirstPage() +{ + QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return); + QTC_ASSERT(m_lastPageInformation.currentPage != -1, return); + m_lastTreeViewQuery.setPageParameter(1); + fetchProjects(); +} + +void GitLabDialog::queryPreviousPage() +{ + QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return); + QTC_ASSERT(m_lastPageInformation.currentPage != -1, return); + m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.currentPage - 1); + fetchProjects(); +} + +void GitLabDialog::queryNextPage() +{ + QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return); + QTC_ASSERT(m_lastPageInformation.currentPage != -1, return); + m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.currentPage + 1); + fetchProjects(); +} + +void GitLabDialog::queryLastPage() +{ + QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return); + QTC_ASSERT(m_lastPageInformation.currentPage != -1, return); + m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.totalPages); + fetchProjects(); +} + +void GitLabDialog::querySearch() +{ + QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return); + m_lastTreeViewQuery.setPageParameter(-1); + m_lastTreeViewQuery.setAdditionalParameters({"search=" + m_ui.searchLE->text()}); + fetchProjects(); +} + +void GitLabDialog::handleUser(const User &user) +{ + m_lastPageInformation = {}; + m_currentUserId = user.id; + + if (!user.error.message.isEmpty()) { + // TODO + if (user.error.code == 1) { + m_ui.mainLabel->setText(tr("Not logged in.")); + m_ui.detailsLabel->setText(tr("Insufficient access token.")); + m_ui.detailsLabel->setToolTip(user.error.message + QLatin1Char('\n') + + tr("Permission scope read_api or api needed.")); + updatePageButtons(); + m_ui.treeViewTitle->setText(tr("Projects (%1)").arg(0)); + return; + } + } + + if (user.id != -1) { + if (user.bot) { + m_ui.mainLabel->setText(tr("Using project access token.")); + m_ui.detailsLabel->setText({}); + } else { + m_ui.mainLabel->setText(tr("Logged in as %1").arg(user.name)); + m_ui.detailsLabel->setText(tr("Id: %1 (%2)").arg(user.id).arg(user.email)); + } + m_ui.detailsLabel->setToolTip({}); + } else { + m_ui.mainLabel->setText(tr("Not logged in.")); + m_ui.detailsLabel->setText({}); + m_ui.detailsLabel->setToolTip({}); + } + m_lastTreeViewQuery = Query(Query::Projects); + fetchProjects(); +} + +void GitLabDialog::handleProjects(const Projects &projects) +{ + Utils::ListModel *listModel = new Utils::ListModel(this); + for (const Project &project : projects.projects) + listModel->appendItem(new Project(project)); + + // TODO use a real model / delegate..? + listModel->setDataAccessor([](Project *data, int /*column*/, int role) -> QVariant { + if (role == Qt::DisplayRole) + return QString(data->displayName + " (" + data->visibility + ')'); + if (role == Qt::UserRole) + return QVariant::fromValue(*data); + return QVariant(); + }); + resetTreeView(m_ui.treeView, listModel); + int count = projects.error.message.isEmpty() ? projects.pageInfo.total : 0; + m_ui.treeViewTitle->setText(tr("Projects (%1)").arg(count)); + + m_lastPageInformation = projects.pageInfo; + updatePageButtons(); +} + +void GitLabDialog::fetchProjects() +{ + QueryRunner *runner = new QueryRunner(m_lastTreeViewQuery, m_currentServerId, this); + connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) { + handleProjects(ResultParser::parseProjects(result)); + }); + connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); }); + runner->start(); +} + +void GitLabDialog::cloneSelected() +{ + const QModelIndexList indexes = m_ui.treeView->selectionModel()->selectedIndexes(); + QTC_ASSERT(indexes.size() == 1, return); + const Project project = indexes.first().data(Qt::UserRole).value(); + QTC_ASSERT(!project.sshUrl.isEmpty() && !project.httpUrl.isEmpty(), return); + GitLabCloneDialog dialog(project, this); + if (dialog.exec() == QDialog::Accepted) + reject(); +} + +} // namespace GitLab diff --git a/src/plugins/remotelinux/abstractuploadandinstallpackageservice.h b/src/plugins/gitlab/gitlabdialog.h similarity index 50% rename from src/plugins/remotelinux/abstractuploadandinstallpackageservice.h rename to src/plugins/gitlab/gitlabdialog.h index 82d9e6cd175..907d786f474 100644 --- a/src/plugins/remotelinux/abstractuploadandinstallpackageservice.h +++ b/src/plugins/gitlab/gitlabdialog.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -22,49 +22,60 @@ ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ - #pragma once -#include "abstractremotelinuxdeployservice.h" -#include "remotelinux_export.h" +#include "ui_gitlabdialog.h" -namespace Utils { -class FilePath; -class ProcessResultData; -} +#include "queryrunner.h" +#include "resultparser.h" -namespace RemoteLinux { -class AbstractRemoteLinuxPackageInstaller; +#include +#include -namespace Internal { class AbstractUploadAndInstallPackageServicePrivate; } +#include -class REMOTELINUX_EXPORT AbstractUploadAndInstallPackageService : public AbstractRemoteLinuxDeployService +QT_BEGIN_NAMESPACE +class QPushButton; +QT_END_NAMESPACE + +namespace GitLab { + +class GitLabParameters; +class GitLabDialog : public QDialog { Q_OBJECT - public: - void setPackageFilePath(const Utils::FilePath &filePath); + explicit GitLabDialog(QWidget *parent = nullptr); + + void updateRemotes(); protected: - AbstractUploadAndInstallPackageService(); - ~AbstractUploadAndInstallPackageService() override; + void keyPressEvent(QKeyEvent *event) override; private: - void handleUploadFinished(const Utils::ProcessResultData &resultData); - void handleInstallationFinished(const QString &errorMsg); + void resetTreeView(QTreeView *treeView, QAbstractItemModel *model); + void requestMainViewUpdate(); + void updatePageButtons(); - virtual AbstractRemoteLinuxPackageInstaller *packageInstaller() const = 0; - virtual QString uploadDir() const; // Defaults to remote user's home directory. + void queryFirstPage(); + void queryPreviousPage(); + void queryNextPage(); + void queryLastPage(); + void querySearch(); + void continuePageUpdate(); - bool isDeploymentNecessary() const override; - void doDeviceSetup() override; - void stopDeviceSetup() override; - void doDeploy() override; - void stopDeployment() override; + void handleUser(const User &user); + void handleProjects(const Projects &projects); + void fetchProjects(); - void setFinished(); + void cloneSelected(); - Internal::AbstractUploadAndInstallPackageServicePrivate * const d; + Ui::GitLabDialog m_ui; + QPushButton *m_clonePB = nullptr; + Utils::Id m_currentServerId; + Query m_lastTreeViewQuery; + PageInformation m_lastPageInformation; + int m_currentUserId = -1; }; -} // namespace RemoteLinux +} // namespace GitLab diff --git a/src/plugins/gitlab/gitlabdialog.ui b/src/plugins/gitlab/gitlabdialog.ui new file mode 100644 index 00000000000..2b4377b1263 --- /dev/null +++ b/src/plugins/gitlab/gitlabdialog.ui @@ -0,0 +1,268 @@ + + + GitLab::GitLabDialog + + + + 0 + 0 + 665 + 530 + + + + GitLab + + + + + + + + + + + + Login + + + + + + + Details + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remote: + + + + + + + + 200 + 0 + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + + + + Projects + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Search + + + + + + + Search + + + + + + + + + + + false + + + true + + + false + + + false + + + false + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 200 + 20 + + + + + + + + + + |< + + + + + + + ... + + + + + + + 0 + + + + + + + ... + + + + + + + >| + + + + + + + + + Qt::Horizontal + + + + 200 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + rejected() + GitLab::GitLabDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/gitlab/gitlabplugin.cpp b/src/plugins/gitlab/gitlabplugin.cpp index 1f45fe97d1d..5df45c8e688 100644 --- a/src/plugins/gitlab/gitlabplugin.cpp +++ b/src/plugins/gitlab/gitlabplugin.cpp @@ -25,23 +25,52 @@ #include "gitlabplugin.h" +#include "gitlabdialog.h" #include "gitlaboptionspage.h" #include "gitlabparameters.h" #include "gitlabprojectsettings.h" +#include "queryrunner.h" +#include "resultparser.h" +#include +#include #include +#include #include #include +#include #include +#include + +#include +#include +#include +#include namespace GitLab { +namespace Constants { +const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView"; +} // namespace Constants -class GitLabPluginPrivate +class GitLabPluginPrivate : public QObject { public: GitLabParameters parameters; GitLabOptionsPage optionsPage{¶meters}; QHash projectSettings; + QPointer dialog; + + QTimer notificationTimer; + QString projectName; + Utils::Id serverId; + bool runningQuery = false; + + void setupNotificationTimer(); + void fetchEvents(); + void fetchUser(); + void createAndSendEventsRequest(const QDateTime timeStamp, int page = -1); + void handleUser(const User &user); + void handleEvents(const Events &events, const QDateTime &timeStamp); }; static GitLabPluginPrivate *dd = nullptr; @@ -71,9 +100,180 @@ bool GitLabPlugin::initialize(const QStringList & /*arguments*/, QString * /*err return new GitLabProjectSettingsWidget(project); }); ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory); + QAction *openViewAction = new QAction(tr("GitLab..."), this); + auto gitlabCommand = Core::ActionManager::registerAction(openViewAction, + Constants::GITLAB_OPEN_VIEW); + connect(openViewAction, &QAction::triggered, this, &GitLabPlugin::openView); + Core::ActionContainer *ac = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS); + ac->addAction(gitlabCommand); + connect(&dd->optionsPage, &GitLabOptionsPage::settingsChanged, this, [this] { + if (dd->dialog) + dd->dialog->updateRemotes(); + }); + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::startupProjectChanged, + this, &GitLabPlugin::onStartupProjectChanged); return true; } +void GitLabPlugin::openView() +{ + if (dd->dialog.isNull()) { + while (!dd->parameters.isValid()) { + QMessageBox::warning(Core::ICore::dialogParent(), tr("Error"), + tr("Invalid GitLab configuration. For a fully functional " + "configuration, you need to set up host name or address and " + "an access token. Providing the path to curl is mandatory.")); + if (!Core::ICore::showOptionsDialog("GitLab")) + return; + } + GitLabDialog *gitlabD = new GitLabDialog(Core::ICore::dialogParent()); + gitlabD->setModal(true); + Core::ICore::registerWindow(gitlabD, Core::Context("Git.GitLab")); + dd->dialog = gitlabD; + } + const Qt::WindowStates state = dd->dialog->windowState(); + if (state & Qt::WindowMinimized) + dd->dialog->setWindowState(state & ~Qt::WindowMinimized); + dd->dialog->show(); + dd->dialog->raise(); +} + +void GitLabPlugin::onStartupProjectChanged() +{ + QTC_ASSERT(dd, return); + disconnect(&dd->notificationTimer); + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (!project) { + dd->notificationTimer.stop(); + return; + } + + const GitLabProjectSettings *projSettings = projectSettings(project); + if (!projSettings->isLinked()) { + dd->notificationTimer.stop(); + return; + } + + dd->fetchEvents(); + dd->setupNotificationTimer(); +} + +void GitLabPluginPrivate::setupNotificationTimer() +{ + // make interval configurable? + notificationTimer.setInterval(15 * 60 * 1000); + QObject::connect(¬ificationTimer, &QTimer::timeout, this, &GitLabPluginPrivate::fetchEvents); + notificationTimer.start(); +} + +void GitLabPluginPrivate::fetchEvents() +{ + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + QTC_ASSERT(project, return); + + if (runningQuery) + return; + + const GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project); + projectName = projSettings->currentProject(); + serverId = projSettings->currentServer(); + + const QDateTime lastRequest = projSettings->lastRequest(); + if (!lastRequest.isValid()) { // we haven't queried events for this project yet + fetchUser(); + return; + } + createAndSendEventsRequest(lastRequest); +} + +void GitLabPluginPrivate::fetchUser() +{ + if (runningQuery) + return; + + const Query query(Query::User); + QueryRunner *runner = new QueryRunner(query, serverId, this); + QObject::connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) { + handleUser(ResultParser::parseUser(result)); + }); + QObject::connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); }); + runningQuery = true; + runner->start(); +} + +void GitLabPluginPrivate::createAndSendEventsRequest(const QDateTime timeStamp, int page) +{ + if (runningQuery) + return; + + Query query(Query::Events, {projectName}); + QStringList additional = {"sort=asc"}; + + QDateTime after = timeStamp.addDays(-1); + additional.append(QLatin1String("after=%1").arg(after.toString("yyyy-MM-dd"))); + query.setAdditionalParameters(additional); + + if (page > 1) + query.setPageParameter(page); + + QueryRunner *runner = new QueryRunner(query, serverId, this); + QObject::connect(runner, &QueryRunner::resultRetrieved, this, + [this, timeStamp](const QByteArray &result) { + handleEvents(ResultParser::parseEvents(result), timeStamp); + }); + QObject::connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); }); + runningQuery = true; + runner->start(); +} + +void GitLabPluginPrivate::handleUser(const User &user) +{ + runningQuery = false; + + QTC_ASSERT(user.error.message.isEmpty(), return); + const QDateTime timeStamp = QDateTime::fromString(user.lastLogin, Qt::ISODateWithMs); + createAndSendEventsRequest(timeStamp); +} + +void GitLabPluginPrivate::handleEvents(const Events &events, const QDateTime &timeStamp) +{ + runningQuery = false; + + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + QTC_ASSERT(project, return); + + GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project); + QTC_ASSERT(projSettings->currentProject() == projectName, return); + + if (!projSettings->isLinked()) // link state has changed meanwhile - ignore the request + return; + + if (!events.error.message.isEmpty()) { + VcsBase::VcsOutputWindow::appendError("GitLab: Error while fetching events. " + + events.error.message + '\n'); + return; + } + + QDateTime lastTimeStamp; + for (const Event &event : events.events) { + const QDateTime eventTimeStamp = QDateTime::fromString(event.timeStamp, Qt::ISODateWithMs); + if (!timeStamp.isValid() || timeStamp < eventTimeStamp) { + VcsBase::VcsOutputWindow::appendMessage("GitLab: " + event.toMessage()); + if (!lastTimeStamp.isValid() || lastTimeStamp < eventTimeStamp) + lastTimeStamp = eventTimeStamp; + } + } + if (lastTimeStamp.isValid()) { + if (auto outputWindow = VcsBase::VcsOutputWindow::instance()) + outputWindow->flash(); + projSettings->setLastRequest(lastTimeStamp); + } + + if (events.pageInfo.currentPage < events.pageInfo.totalPages) + createAndSendEventsRequest(timeStamp, events.pageInfo.currentPage + 1); +} + QList GitLabPlugin::allGitLabServers() { QTC_ASSERT(dd, return {}); @@ -107,4 +307,28 @@ GitLabOptionsPage *GitLabPlugin::optionsPage() return &dd->optionsPage; } +void GitLabPlugin::linkedStateChanged(bool enabled) +{ + QTC_ASSERT(dd, return); + + ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject(); + if (project) { + const GitLabProjectSettings *pSettings = projectSettings(project); + dd->serverId = pSettings->currentServer(); + dd->projectName = pSettings->currentProject(); + } else { + dd->serverId = Utils::Id(); + dd->projectName = QString(); + } + + if (enabled) { + dd->fetchEvents(); + dd->setupNotificationTimer(); + } else { + QObject::disconnect(&dd->notificationTimer, &QTimer::timeout, + dd, &GitLabPluginPrivate::fetchEvents); + dd->notificationTimer.stop(); + } +} + } // namespace GitLab diff --git a/src/plugins/gitlab/gitlabplugin.h b/src/plugins/gitlab/gitlabplugin.h index 7d578120f9f..f1abf1bd1ee 100644 --- a/src/plugins/gitlab/gitlabplugin.h +++ b/src/plugins/gitlab/gitlabplugin.h @@ -33,6 +33,7 @@ namespace ProjectExplorer { class Project; } namespace GitLab { +class Events; class GitLabProjectSettings; class GitLabOptionsPage; @@ -52,6 +53,11 @@ public: static GitLabParameters *globalParameters(); static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project); static GitLabOptionsPage *optionsPage(); + + static void linkedStateChanged(bool enabled); +private: + void openView(); + void onStartupProjectChanged(); }; } // namespace GitLab diff --git a/src/plugins/gitlab/gitlabprojectsettings.cpp b/src/plugins/gitlab/gitlabprojectsettings.cpp index 7088131c409..e24b4699829 100644 --- a/src/plugins/gitlab/gitlabprojectsettings.cpp +++ b/src/plugins/gitlab/gitlabprojectsettings.cpp @@ -49,6 +49,7 @@ namespace GitLab { const char PSK_LINKED_ID[] = "GitLab.LinkedId"; const char PSK_SERVER[] = "GitLab.Server"; const char PSK_PROJECT[] = "GitLab.Project"; +const char PSK_LAST_REQ[] = "GitLab.LastRequest"; static QString accessLevelString(int accessLevel) { @@ -106,6 +107,7 @@ void GitLabProjectSettings::load() m_id = Utils::Id::fromSetting(m_project->namedSettings(PSK_LINKED_ID)); m_host = m_project->namedSettings(PSK_SERVER).toString(); m_currentProject = m_project->namedSettings(PSK_PROJECT).toString(); + m_lastRequest = m_project->namedSettings(PSK_LAST_REQ).toDateTime(); // may still be wrong, but we avoid an additional request by just doing sanity check here if (!m_id.isValid() || m_host.isEmpty()) @@ -124,6 +126,7 @@ void GitLabProjectSettings::save() m_project->setNamedSettings(PSK_SERVER, QString()); } m_project->setNamedSettings(PSK_PROJECT, m_currentProject); + m_project->setNamedSettings(PSK_LAST_REQ, m_lastRequest); } GitLabProjectSettingsWidget::GitLabProjectSettingsWidget(ProjectExplorer::Project *project, @@ -184,6 +187,7 @@ void GitLabProjectSettingsWidget::unlink() m_projectSettings->setLinked(false); m_projectSettings->setCurrentProject({}); updateEnabledStates(); + GitLabPlugin::linkedStateChanged(false); } void GitLabProjectSettingsWidget::checkConnection(CheckMode mode) @@ -245,6 +249,7 @@ void GitLabProjectSettingsWidget::onConnectionChecked(const Project &project, m_projectSettings->setCurrentServerHost(remote); m_projectSettings->setLinked(true); m_projectSettings->setCurrentProject(projectName); + GitLabPlugin::linkedStateChanged(true); } updateEnabledStates(); } @@ -282,8 +287,10 @@ void GitLabProjectSettingsWidget::updateUi() m_hostCB->setCurrentIndex(m_hostCB->findData(QVariant::fromValue(serverHost))); m_linkedGitLabServer->setCurrentIndex( m_linkedGitLabServer->findData(QVariant::fromValue(server))); + GitLabPlugin::linkedStateChanged(true); } else { m_projectSettings->setLinked(false); + GitLabPlugin::linkedStateChanged(false); } } updateEnabledStates(); @@ -294,6 +301,7 @@ void GitLabProjectSettingsWidget::updateEnabledStates() const bool isGitRepository = m_hostCB->count() > 0; const bool hasGitLabServers = m_linkedGitLabServer->count(); const bool linked = m_projectSettings->isLinked(); + m_linkedGitLabServer->setEnabled(isGitRepository && !linked); m_hostCB->setEnabled(isGitRepository && !linked); m_linkWithGitLab->setEnabled(isGitRepository && !linked && hasGitLabServers); diff --git a/src/plugins/gitlab/gitlabprojectsettings.h b/src/plugins/gitlab/gitlabprojectsettings.h index de8bdb851d8..5a9081adc73 100644 --- a/src/plugins/gitlab/gitlabprojectsettings.h +++ b/src/plugins/gitlab/gitlabprojectsettings.h @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -59,9 +60,12 @@ public: QString currentProject() const { return m_currentProject; } bool isLinked() const { return m_linked; } void setLinked(bool linked); + QDateTime lastRequest() const { return m_lastRequest; } + void setLastRequest(const QDateTime &lastRequest) { m_lastRequest = lastRequest; } ProjectExplorer::Project *project() const { return m_project; } static std::tuple remotePartsFromRemote(const QString &remote); + private: void load(); void save(); @@ -69,6 +73,7 @@ private: ProjectExplorer::Project *m_project = nullptr; QString m_host; Utils::Id m_id; + QDateTime m_lastRequest; QString m_currentProject; bool m_linked = false; }; diff --git a/src/plugins/gitlab/queryrunner.cpp b/src/plugins/gitlab/queryrunner.cpp index b680374dbd1..86ad6b303ab 100644 --- a/src/plugins/gitlab/queryrunner.cpp +++ b/src/plugins/gitlab/queryrunner.cpp @@ -41,6 +41,9 @@ namespace GitLab { const char API_PREFIX[] = "/api/v4"; const char QUERY_PROJECT[] = "/projects/%1"; +const char QUERY_PROJECTS[] = "/projects?simple=true"; +const char QUERY_USER[] = "/user"; +const char QUERY_EVENTS[] = "/projects/%1/events"; Query::Query(Type type, const QStringList ¶meter) : m_type(type) @@ -48,6 +51,21 @@ Query::Query(Type type, const QStringList ¶meter) { } +void Query::setPageParameter(int page) +{ + m_pageParameter = page; +} + +void Query::setAdditionalParameters(const QStringList &additional) +{ + m_additionalParameters = additional; +} + +bool Query::hasPaginatedResults() const +{ + return m_type == Query::Projects || m_type == Query::Events; +} + QString Query::toString() const { QString query = API_PREFIX; @@ -59,6 +77,25 @@ QString Query::toString() const query += QLatin1String(QUERY_PROJECT).arg(QLatin1String( QUrl::toPercentEncoding(m_parameter.at(0)))); break; + case Query::Projects: + query += QLatin1String(QUERY_PROJECTS); + break; + case Query::User: + query += QUERY_USER; + break; + case Query::Events: + QTC_ASSERT(!m_parameter.isEmpty(), return {}); + query += QLatin1String(QUERY_EVENTS).arg(QLatin1String( + QUrl::toPercentEncoding(m_parameter.at(0)))); + break; + } + if (m_pageParameter > 0) { + query.append(m_type == Query::Projects ? '&' : '?'); + query.append("page=").append(QString::number(m_pageParameter)); + } + if (!m_additionalParameters.isEmpty()) { + query.append((m_type == Query::Projects || m_pageParameter > 0) ? '&' : '?'); + query.append(m_additionalParameters.join('&')); } return query; } @@ -69,6 +106,9 @@ QueryRunner::QueryRunner(const Query &query, const Utils::Id &id, QObject *paren const GitLabParameters *p = GitLabPlugin::globalParameters(); const auto server = p->serverForId(id); QStringList args = server.curlArguments(); + m_paginated = query.hasPaginatedResults(); + if (m_paginated) + args << "-i"; if (!server.token.isEmpty()) args << "--header" << "PRIVATE-TOKEN: " + server.token; QString url = "https://" + server.host; diff --git a/src/plugins/gitlab/queryrunner.h b/src/plugins/gitlab/queryrunner.h index c0fa801b0fd..ead892f55db 100644 --- a/src/plugins/gitlab/queryrunner.h +++ b/src/plugins/gitlab/queryrunner.h @@ -38,15 +38,24 @@ class Query public: enum Type { NoQuery, - Project + User, + Project, + Projects, + Events }; explicit Query(Type type, const QStringList ¶meters = {}); + void setPageParameter(int page); + void setAdditionalParameters(const QStringList &additional); + bool hasPaginatedResults() const; + Type type() const { return m_type; } QString toString() const; private: Type m_type = NoQuery; QStringList m_parameter; + QStringList m_additionalParameters; + int m_pageParameter = -1; }; class QueryRunner : public QObject @@ -70,6 +79,7 @@ private: Utils::QtcProcess m_process; bool m_running = false; + bool m_paginated = false; }; } // namespace GitLab diff --git a/src/plugins/gitlab/resultparser.cpp b/src/plugins/gitlab/resultparser.cpp index bd857c2e865..e9ce0d70835 100644 --- a/src/plugins/gitlab/resultparser.cpp +++ b/src/plugins/gitlab/resultparser.cpp @@ -32,8 +32,59 @@ #include namespace GitLab { + + +QString Event::toMessage() const +{ + QString message; + if (author.realname.isEmpty()) + message.append(author.name); + else + message.append(author.realname + " (" + author.name + ')'); + message.append(' '); + if (!pushData.isEmpty()) + message.append(pushData); + else if (!targetTitle.isEmpty()) + message.append(action + ' ' + targetType + " '" + targetTitle +'\''); + else + message.append(action + ' ' + targetType); + return message; +} + namespace ResultParser { +static PageInformation paginationInformation(const QByteArray &header) +{ + PageInformation result; + const QByteArrayList lines = header.split('\n'); + for (const QByteArray &line : lines) { + const QByteArray lower = line.toLower(); // depending on OS this may be capitalized + if (lower.startsWith("x-page: ")) + result.currentPage = line.mid(8).toInt(); + else if (lower.startsWith("x-per-page: ")) + result.perPage = line.mid(12).toInt(); + else if (lower.startsWith("x-total: ")) + result.total = line.mid(9).toInt(); + else if (lower.startsWith("x-total-pages: ")) + result.totalPages = line.mid(15).toInt(); + } + return result; +} + +static std::pair splitHeaderAndBody(const QByteArray &input) +{ + QByteArray header; + QByteArray json; + int emptyLine = input.indexOf("\r\n\r\n"); // we always get \r\n as line separator? + if (emptyLine != -1) { + header = input.left(emptyLine); + json = input.mid(emptyLine + 4); + } else { + json = input; + } + return std::make_pair(header, json); +} + static std::pair preHandleSingle(const QByteArray &json) { Error result; @@ -59,6 +110,53 @@ static std::pair preHandleSingle(const QByteArray &json) return std::make_pair(result, object); } +static std::pair preHandleHeaderAndBody(const QByteArray &header, + const QByteArray &json) +{ + Error result; + if (header.isEmpty()) { + result.message = "Missing Expected Header"; + return std::make_pair(result, QJsonDocument()); + } + + QJsonParseError error; + const QJsonDocument doc = QJsonDocument::fromJson(json, &error); + if (error.error != QJsonParseError::NoError) { + result.message = error.errorString(); + return std::make_pair(result, doc); + } + + if (doc.isObject()) { + const QJsonObject obj = doc.object(); + if (obj.contains("message")) { + result = parseErrorMessage(obj.value("message").toString()); + return std::make_pair(result, doc); + } else if (obj.contains("error")) { + if (obj.value("error").toString() == "insufficient_scope") + result.code = 1; + result.message = obj.value("error_description").toString(); + return std::make_pair(result, doc); + } + } + + if (!doc.isArray()) + result.message = "Not an Array"; + + return std::make_pair(result, doc); +} + +static User userFromJson(const QJsonObject &jsonObj) +{ + User user; + user.name = jsonObj.value("username").toString(); + user.realname = jsonObj.value("name").toString(); + user.id = jsonObj.value("id").toInt(-1); + user.email = jsonObj.value("email").toString(); + user.lastLogin = jsonObj.value("last_sign_in_at").toString(); + user.bot = jsonObj.value("bot").toBool(); + return user; +} + static Project projectFromJson(const QJsonObject &jsonObj) { Project project; @@ -67,6 +165,8 @@ static Project projectFromJson(const QJsonObject &jsonObj) project.pathName = jsonObj.value("path_with_namespace").toString(); project.id = jsonObj.value("id").toInt(-1); project.visibility = jsonObj.value("visibility").toString("public"); + project.httpUrl = jsonObj.value("http_url_to_repo").toString(); + project.sshUrl = jsonObj.value("ssh_url_to_repo").toString(); if (jsonObj.contains("forks_count")) project.forkCount = jsonObj.value("forks_count").toInt(); if (jsonObj.contains("star_count")) @@ -82,6 +182,42 @@ static Project projectFromJson(const QJsonObject &jsonObj) return project; } +static Event eventFromJson(const QJsonObject &jsonObj) +{ + Event event; + event.action = jsonObj.value("action_name").toString(); + const QJsonValue value = jsonObj.value("target_type"); + event.targetType = value.isNull() ? "project" : jsonObj.value("target_type").toString(); + if (event.targetType == "DiffNote") { + const QJsonObject noteObject = jsonObj.value("note").toObject(); + event.targetType = noteObject.value("noteable_type").toString(); + } + event.targetTitle = jsonObj.value("target_title").toString(); + event.author = userFromJson(jsonObj.value("author").toObject()); + event.timeStamp = jsonObj.value("created_at").toString(); + if (jsonObj.contains("push_data")) { + const QJsonObject pushDataObj = jsonObj.value("push_data").toObject(); + if (!pushDataObj.isEmpty()) { + const QString action = pushDataObj.value("action").toString(); + const QString ref = pushDataObj.value("ref").toString(); + const QString refType = pushDataObj.value("ref_type").toString(); + event.pushData = action + ' ' + refType + " '" + ref + '\''; + } + } + return event; +} + +User parseUser(const QByteArray &input) +{ + auto [error, userObj] = preHandleSingle(input); + if (!error.message.isEmpty()) { + User result; + result.error = error; + return result; + } + return userFromJson(userObj); +} + Project parseProject(const QByteArray &input) { auto [error, projectObj] = preHandleSingle(input); @@ -93,6 +229,47 @@ Project parseProject(const QByteArray &input) return projectFromJson(projectObj); } +Projects parseProjects(const QByteArray &input) +{ + auto [header, json] = splitHeaderAndBody(input); + auto [error, jsonDoc] = preHandleHeaderAndBody(header, json); + Projects result; + if (!error.message.isEmpty()) { + result.error = error; + return result; + } + result.pageInfo = paginationInformation(header); + const QJsonArray projectsArray = jsonDoc.array(); + for (const QJsonValue &value : projectsArray) { + if (!value.isObject()) + continue; + const QJsonObject projectObj = value.toObject(); + result.projects.append(projectFromJson(projectObj)); + } + return result; +} + +Events parseEvents(const QByteArray &input) +{ + auto [header, json] = splitHeaderAndBody(input); + auto [error, jsonDoc] = preHandleHeaderAndBody(header, json); + Events result; + if (!error.message.isEmpty()) { + result.error = error; + return result; + } + result.pageInfo = paginationInformation(header); + const QJsonArray eventsArray = jsonDoc.array(); + for (const QJsonValue &value : eventsArray) { + if (!value.isObject()) + continue; + const QJsonObject eventObj = value.toObject(); + result.events.append(eventFromJson(eventObj)); + } + return result; + +} + Error parseErrorMessage(const QString &message) { Error error; diff --git a/src/plugins/gitlab/resultparser.h b/src/plugins/gitlab/resultparser.h index 7a99bc14a3e..aeaeeadcc5a 100644 --- a/src/plugins/gitlab/resultparser.h +++ b/src/plugins/gitlab/resultparser.h @@ -25,6 +25,8 @@ #pragma once +#include +#include #include namespace GitLab { @@ -35,6 +37,27 @@ struct Error QString message; }; +class PageInformation +{ +public: + int currentPage = -1; + int totalPages = -1; + int perPage = -1; + int total = -1; +}; + +class User +{ +public: + QString name; + QString realname; + QString email; + QString lastLogin; + Error error; + int id = -1; + bool bot = false; +}; + class Project { public: @@ -42,18 +65,55 @@ public: QString displayName; QString pathName; QString visibility; + QString httpUrl; + QString sshUrl; Error error; int id = -1; int starCount = -1; int forkCount = -1; int issuesCount = -1; - int accessLevel = -1; // 40 maintainer, 30 developer, 20 reporter, 10 guest + int accessLevel = -1; // 50 owner, 40 maintainer, 30 developer, 20 reporter, 10 guest +}; + +class Projects +{ +public: + QList projects; + Error error; + PageInformation pageInfo; +}; + +class Event +{ +public: + QString action; + QString targetType; + QString targetTitle; + QString timeStamp; + QString pushData; + User author; + Error error; + + QString toMessage() const; +}; + +class Events +{ +public: + QList events; + Error error; + PageInformation pageInfo; }; namespace ResultParser { +User parseUser(const QByteArray &input); Project parseProject(const QByteArray &input); +Projects parseProjects(const QByteArray &input); +Events parseEvents(const QByteArray &input); Error parseErrorMessage(const QString &message); } // namespace ResultParser } // namespace GitLab + +Q_DECLARE_METATYPE(GitLab::Project) diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp index d4881346ded..aea7a28b9ca 100644 --- a/src/plugins/ios/iosrunner.cpp +++ b/src/plugins/ios/iosrunner.cpp @@ -197,8 +197,8 @@ void IosRunner::start() connect(m_toolHandler, &IosToolHandler::finished, this, &IosRunner::handleFinished); - const Runnable runnable = runControl()->runnable(); - QStringList args = ProcessArgs::splitArgs(runnable.command.arguments(), OsTypeMac); + const CommandLine command = runControl()->commandLine(); + QStringList args = ProcessArgs::splitArgs(command.arguments(), OsTypeMac); if (m_qmlServerPort.isValid()) { QUrl qmlServer; qmlServer.setPort(m_qmlServerPort.number()); diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index b77961bcf8d..e997a564c5d 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -918,6 +918,7 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document, { if (!d->m_openedDocument.contains(document) || !reachable()) return; + d->m_diagnosticManager->disableDiagnostics(document); const QString method(DidChangeTextDocumentNotification::methodName); TextDocumentSyncKind syncKind = d->m_serverCapabilities.textDocumentSyncKindHelper(); if (Utils::optional registered = d->m_dynamicCapabilities.isRegistered(method)) { diff --git a/src/plugins/languageclient/diagnosticmanager.cpp b/src/plugins/languageclient/diagnosticmanager.cpp index b80ea70b95e..4ebb7f6c304 100644 --- a/src/plugins/languageclient/diagnosticmanager.cpp +++ b/src/plugins/languageclient/diagnosticmanager.cpp @@ -90,7 +90,7 @@ void DiagnosticManager::hideDiagnostics(const Utils::FilePath &filePath) for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc)) editor->editorWidget()->setExtraSelections(m_extraSelectionsId, {}); } - qDeleteAll(m_marks.take(filePath)); + m_marks.remove(filePath); } QList DiagnosticManager::filteredDiagnostics(const QList &diagnostics) const @@ -98,6 +98,17 @@ QList DiagnosticManager::filteredDiagnostics(const QList return diagnostics; } +void DiagnosticManager::disableDiagnostics(TextEditor::TextDocument *document) +{ + + Marks &marks = m_marks[document->filePath()]; + if (!marks.enabled) + return; + for (TextEditor::TextMark *mark : marks.marks) + mark->setColor(Utils::Theme::Color::IconsDisabledColor); + marks.enabled = false; +} + void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) { const FilePath &filePath = uri.toFilePath(); @@ -106,7 +117,7 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) const VersionedDiagnostics &versionedDiagnostics = m_diagnostics.value(uri); if (versionedDiagnostics.version.value_or(version) == version && !versionedDiagnostics.diagnostics.isEmpty()) { - QList &marks = m_marks[filePath]; + Marks &marks = m_marks[filePath]; const bool isProjectFile = m_client->project() && m_client->project()->isKnownFile(filePath); for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) { @@ -115,9 +126,9 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) if (!selection.cursor.isNull()) extraSelections << selection; if (TextEditor::TextMark *mark = createTextMark(filePath, diagnostic, isProjectFile)) - marks.append(mark); + marks.marks.append(mark); } - if (!marks.isEmpty()) + if (!marks.marks.isEmpty()) emit textMarkCreated(filePath); } @@ -170,11 +181,7 @@ void DiagnosticManager::clearDiagnostics() for (const DocumentUri &uri : m_diagnostics.keys()) hideDiagnostics(uri.toFilePath()); m_diagnostics.clear(); - if (!QTC_GUARD(m_marks.isEmpty())) { - for (const QList &marks : qAsConst(m_marks)) - qDeleteAll(marks); - m_marks.clear(); - } + QTC_ASSERT(m_marks.isEmpty(), m_marks.clear()); } QList DiagnosticManager::diagnosticsAt(const DocumentUri &uri, @@ -218,4 +225,9 @@ bool DiagnosticManager::hasDiagnostics(const TextDocument *doc) const return !it->diagnostics.isEmpty(); } +DiagnosticManager::Marks::~Marks() +{ + qDeleteAll(marks); +} + } // namespace LanguageClient diff --git a/src/plugins/languageclient/diagnosticmanager.h b/src/plugins/languageclient/diagnosticmanager.h index c4972524c00..aa229b91c00 100644 --- a/src/plugins/languageclient/diagnosticmanager.h +++ b/src/plugins/languageclient/diagnosticmanager.h @@ -61,6 +61,7 @@ public: virtual QList filteredDiagnostics( const QList &diagnostics) const; + void disableDiagnostics(TextEditor::TextDocument *document); void clearDiagnostics(); QList diagnosticsAt( @@ -91,7 +92,14 @@ private: QList diagnostics; }; QMap m_diagnostics; - QMap> m_marks; + class Marks + { + public: + ~Marks(); + bool enabled = true; + QList marks; + }; + QMap m_marks; Client *m_client; Utils::Id m_extraSelectionsId; }; diff --git a/src/plugins/mesonprojectmanager/project/mesonrunconfiguration.cpp b/src/plugins/mesonprojectmanager/project/mesonrunconfiguration.cpp index cdba329df92..84f226ab6f7 100644 --- a/src/plugins/mesonprojectmanager/project/mesonrunconfiguration.cpp +++ b/src/plugins/mesonprojectmanager/project/mesonrunconfiguration.cpp @@ -46,7 +46,7 @@ MesonRunConfiguration::MesonRunConfiguration(ProjectExplorer::Target *target, Ut addAspect(target); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(); auto libAspect = addAspect(); diff --git a/src/plugins/nim/project/nimblerunconfiguration.cpp b/src/plugins/nim/project/nimblerunconfiguration.cpp index 8ff42f1aef7..e88e2c11ad6 100644 --- a/src/plugins/nim/project/nimblerunconfiguration.cpp +++ b/src/plugins/nim/project/nimblerunconfiguration.cpp @@ -54,7 +54,7 @@ public: auto envAspect = addAspect(target); addAspect(target); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(); setUpdater([this] { @@ -91,7 +91,8 @@ public: { addAspect(target)->setExecutable(Nim::nimblePathFromKit(target->kit())); addAspect(macroExpander())->setArguments("test"); - addAspect(nullptr)->setDefaultWorkingDirectory(project()->projectDirectory()); + addAspect(macroExpander(), nullptr) + ->setDefaultWorkingDirectory(project()->projectDirectory()); addAspect(); setDisplayName(tr("Nimble Test")); diff --git a/src/plugins/nim/project/nimrunconfiguration.cpp b/src/plugins/nim/project/nimrunconfiguration.cpp index 1e5e519548e..925fac47736 100644 --- a/src/plugins/nim/project/nimrunconfiguration.cpp +++ b/src/plugins/nim/project/nimrunconfiguration.cpp @@ -52,7 +52,7 @@ public: auto envAspect = addAspect(target); addAspect(target); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(); setDisplayName(tr("Current Build Target")); diff --git a/src/plugins/perfprofiler/perfprofilerruncontrol.cpp b/src/plugins/perfprofiler/perfprofilerruncontrol.cpp index e0bf209e274..e84836e0b8f 100644 --- a/src/plugins/perfprofiler/perfprofilerruncontrol.cpp +++ b/src/plugins/perfprofiler/perfprofilerruncontrol.cpp @@ -23,9 +23,9 @@ ** ****************************************************************************/ -#include "perfdatareader.h" -#include "perfprofilerconstants.h" #include "perfprofilerruncontrol.h" + +#include "perfdatareader.h" #include "perfprofilertool.h" #include "perfrunconfigurationaspect.h" #include "perfsettings.h" @@ -124,10 +124,6 @@ public: void start() override { m_process = new QtcProcess(this); - if (!m_process) { - reportFailure(tr("Could not start device process.")); - return; - } connect(m_process, &QtcProcess::started, this, &RunWorker::reportStarted); connect(m_process, &QtcProcess::done, this, [this] { @@ -144,12 +140,10 @@ public: reportStopped(); }); - Runnable perfRunnable = runnable(); - CommandLine cmd({device()->filePath("perf"), {"record"}}); cmd.addArgs(m_perfRecordArguments); cmd.addArgs({"-o", "-", "--"}); - cmd.addCommandLineAsArgs(perfRunnable.command, CommandLine::Raw); + cmd.addCommandLineAsArgs(runControl()->commandLine(), CommandLine::Raw); m_process->setCommand(cmd); m_process->start(); diff --git a/src/plugins/projectexplorer/CMakeLists.txt b/src/plugins/projectexplorer/CMakeLists.txt index 94e26e39432..23f8e926372 100644 --- a/src/plugins/projectexplorer/CMakeLists.txt +++ b/src/plugins/projectexplorer/CMakeLists.txt @@ -60,6 +60,8 @@ add_qtc_plugin(ProjectExplorer devicesupport/devicesettingswidget.cpp devicesupport/devicesettingswidget.h devicesupport/devicesettingswidget.ui devicesupport/devicetestdialog.cpp devicesupport/devicetestdialog.h devicesupport/devicetestdialog.ui devicesupport/deviceusedportsgatherer.cpp devicesupport/deviceusedportsgatherer.h + devicesupport/filetransfer.cpp devicesupport/filetransfer.h + devicesupport/filetransferinterface.h devicesupport/idevice.cpp devicesupport/idevice.h devicesupport/idevicefactory.cpp devicesupport/idevicefactory.h devicesupport/idevicefwd.h diff --git a/src/plugins/projectexplorer/appoutputpane.cpp b/src/plugins/projectexplorer/appoutputpane.cpp index 380ec9f0716..bb96e200f7f 100644 --- a/src/plugins/projectexplorer/appoutputpane.cpp +++ b/src/plugins/projectexplorer/appoutputpane.cpp @@ -44,6 +44,7 @@ #include #include + #include #include #include @@ -66,6 +67,8 @@ static Q_LOGGING_CATEGORY(appOutputLog, "qtc.projectexplorer.appoutput", QtWarningMsg); +using namespace Utils; + namespace ProjectExplorer { namespace Internal { @@ -398,19 +401,20 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) connect(rc, &RunControl::applicationProcessHandleChanged, this, &AppOutputPane::enableDefaultButtons); connect(rc, &RunControl::appendMessage, - this, [this, rc](const QString &out, Utils::OutputFormat format) { + this, [this, rc](const QString &out, OutputFormat format) { appendMessage(rc, out, format); }); // First look if we can reuse a tab - const Runnable thisRunnable = rc->runnable(); + const CommandLine thisCommand = rc->commandLine(); + const FilePath thisWorkingDirectory = rc->workingDirectory(); + const Environment thisEnvironment = rc->environment(); const int tabIndex = Utils::indexOf(m_runControlTabs, [&](const RunControlTab &tab) { if (!tab.runControl || tab.runControl->isRunning()) return false; - const Runnable otherRunnable = tab.runControl->runnable(); - return thisRunnable.command == otherRunnable.command - && thisRunnable.workingDirectory == otherRunnable.workingDirectory - && thisRunnable.environment == otherRunnable.environment; + return thisCommand == tab.runControl->commandLine() + && thisWorkingDirectory == tab.runControl->workingDirectory() + && thisEnvironment == tab.runControl->environment(); }); if (tabIndex != -1) { RunControlTab &tab = m_runControlTabs[tabIndex]; @@ -433,7 +437,7 @@ void AppOutputPane::createNewOutputWindow(RunControl *rc) } // Create new static int counter = 0; - Utils::Id contextId = Utils::Id(C_APP_OUTPUT).withSuffix(counter++); + Id contextId = Id(C_APP_OUTPUT).withSuffix(counter++); Core::Context context(contextId); Core::OutputWindow *ow = new Core::OutputWindow(context, SETTINGS_KEY, m_tabWidget); ow->setWindowTitle(tr("Application Output Window")); @@ -486,19 +490,19 @@ void AppOutputPane::updateFromSettings() } } -void AppOutputPane::appendMessage(RunControl *rc, const QString &out, Utils::OutputFormat format) +void AppOutputPane::appendMessage(RunControl *rc, const QString &out, OutputFormat format) { const int index = indexOf(rc); if (index != -1) { Core::OutputWindow *window = m_runControlTabs.at(index).window; QString stringToWrite; - if (format == Utils::NormalMessageFormat || format == Utils::ErrorMessageFormat) { + if (format == NormalMessageFormat || format == ErrorMessageFormat) { stringToWrite = QTime::currentTime().toString(); stringToWrite += ": "; } stringToWrite += out; window->appendMessage(stringToWrite, format); - if (format != Utils::NormalMessageFormat) { + if (format != NormalMessageFormat) { RunControlTab &tab = m_runControlTabs[index]; switch (tab.behaviorOnOutput) { case AppOutputPaneMode::FlashOnOutput: @@ -530,7 +534,7 @@ const bool kWrapOutputDefault = true; void AppOutputPane::storeSettings() const { - Utils::QtcSettings *const s = Core::ICore::settings(); + QtcSettings *const s = Core::ICore::settings(); s->setValueWithDefault(POP_UP_FOR_RUN_OUTPUT_KEY, int(m_settings.runOutputMode), int(kRunOutputModeDefault)); @@ -706,7 +710,7 @@ void AppOutputPane::enableButtons(const RunControl *rc) m_stopAction->setEnabled(isRunning); if (isRunning && debuggerPlugin() && rc->applicationProcessHandle().isValid()) { m_attachButton->setEnabled(true); - Utils::ProcessHandle h = rc->applicationProcessHandle(); + ProcessHandle h = rc->applicationProcessHandle(); QString tip = h.isValid() ? RunControl::tr("PID %1").arg(h.pid()) : RunControl::tr("Invalid"); m_attachButton->setToolTip(msgAttachDebuggerTooltip(tip)); diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp index 44cea723817..7116792d77c 100644 --- a/src/plugins/projectexplorer/buildmanager.cpp +++ b/src/plugins/projectexplorer/buildmanager.cpp @@ -30,6 +30,7 @@ #include "buildsystem.h" #include "compileoutputwindow.h" #include "deployconfiguration.h" +#include "devicesupport/devicemanager.h" #include "kit.h" #include "kitinformation.h" #include "project.h" @@ -46,8 +47,9 @@ #include #include #include + #include -#include + #include #include #include @@ -118,7 +120,8 @@ static int queue(const QList &projects, const QList &stepIds, return projects.contains(rc->project()); case StopBeforeBuild::SameBuildDir: return Utils::contains(projects, [rc, configSelection](Project *p) { - IDevice::ConstPtr device = rc->runnable().device; + const FilePath executable = rc->commandLine().executable(); + IDevice::ConstPtr device = DeviceManager::deviceForPath(executable); for (const Target * const t : targetsForSelection(p, configSelection)) { if (device.isNull()) device = DeviceKitAspect::device(t->kit()); @@ -126,7 +129,7 @@ static int queue(const QList &projects, const QList &stepIds, continue; for (const BuildConfiguration * const bc : buildConfigsForSelection(t, configSelection)) { - if (rc->runnable().command.executable().isChildOf(bc->buildDirectory())) + if (executable.isChildOf(bc->buildDirectory())) return true; } } diff --git a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp index 67cdaa2965a..cbd8705f6ff 100644 --- a/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp +++ b/src/plugins/projectexplorer/customexecutablerunconfiguration.cpp @@ -53,7 +53,7 @@ CustomExecutableRunConfiguration::CustomExecutableRunConfiguration(Target *targe exeAspect->setEnvironmentChange(EnvironmentChange::fromFixedEnvironment(envAspect->environment())); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(); connect(envAspect, &EnvironmentAspect::environmentChanged, this, [exeAspect, envAspect] { diff --git a/src/plugins/projectexplorer/desktoprunconfiguration.cpp b/src/plugins/projectexplorer/desktoprunconfiguration.cpp index cef277f53f3..663474afdc5 100644 --- a/src/plugins/projectexplorer/desktoprunconfiguration.cpp +++ b/src/plugins/projectexplorer/desktoprunconfiguration.cpp @@ -70,7 +70,7 @@ DesktopRunConfiguration::DesktopRunConfiguration(Target *target, Id id, Kind kin addAspect(target); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(); auto libAspect = addAspect(); diff --git a/src/plugins/projectexplorer/devicesupport/filetransfer.cpp b/src/plugins/projectexplorer/devicesupport/filetransfer.cpp new file mode 100644 index 00000000000..e16db6bc08b --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/filetransfer.cpp @@ -0,0 +1,227 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "filetransfer.h" + +#include "devicemanager.h" +#include "idevice.h" + +#include +#include + +#include + +using namespace Utils; + +namespace ProjectExplorer { + +FileTransferDirection FileToTransfer::direction() const +{ + if (m_source.needsDevice() == m_target.needsDevice()) + return FileTransferDirection::Invalid; + return m_source.needsDevice() ? FileTransferDirection::Download : FileTransferDirection::Upload; +} + +QString FileTransferSetupData::defaultRsyncFlags() +{ + return "-av"; +} + +static FileTransferDirection transferDirection(const FilesToTransfer &files) +{ + if (files.isEmpty()) + return FileTransferDirection::Invalid; + + const FileTransferDirection direction = files.first().direction(); + for (const FileToTransfer &file : files) { + if (file.direction() != direction) + return FileTransferDirection::Invalid; + } + return direction; +} + +static const FilePath &remoteFile(FileTransferDirection direction, const FileToTransfer &file) +{ + return direction == FileTransferDirection::Upload ? file.m_target : file.m_source; +} + +static bool isSameDevice(const FilePath &first, const FilePath &second) +{ + return (first.scheme() == second.scheme()) && (first.host() == second.host()); +} + +static IDeviceConstPtr matchedDevice(FileTransferDirection direction, const FilesToTransfer &files) +{ + if (files.isEmpty()) + return {}; + const FilePath &filePath = remoteFile(direction, files.first()); + for (const FileToTransfer &file : files) { + if (!isSameDevice(filePath, remoteFile(direction, file))) + return {}; + } + return DeviceManager::deviceForPath(filePath); +} + +void FileTransferInterface::startFailed(const QString &errorString) +{ + emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString}); +} + +class FileTransferPrivate : public QObject +{ + Q_OBJECT + +public: + void test(const ProjectExplorer::IDeviceConstPtr &onDevice); + void start(); + void stop(); + + FileTransferSetupData m_setup; + +signals: + void progress(const QString &progressMessage); + void done(const ProcessResultData &resultData); + +private: + void startFailed(const QString &errorString); + void run(const FileTransferSetupData &setup, const IDeviceConstPtr &device); + + std::unique_ptr m_transfer; +}; + +void FileTransferPrivate::test(const IDeviceConstPtr &onDevice) +{ + if (!onDevice) + return startFailed(tr("No device set for test transfer.")); + + run({{}, m_setup.m_method, m_setup.m_rsyncFlags}, onDevice); +} + +void FileTransferPrivate::start() +{ + if (m_setup.m_files.isEmpty()) + return startFailed(tr("No files to transfer.")); + + const FileTransferDirection direction = transferDirection(m_setup.m_files); + if (direction == FileTransferDirection::Invalid) + return startFailed(tr("Mixing different types of transfer in one go.")); + + const IDeviceConstPtr device = matchedDevice(direction, m_setup.m_files); + if (!device) + return startFailed(tr("Trying to transfer into / from not matching device.")); + + run(m_setup, device); +} + +void FileTransferPrivate::stop() +{ + if (!m_transfer) + return; + m_transfer->disconnect(); + m_transfer.release()->deleteLater(); +} + +void FileTransferPrivate::run(const FileTransferSetupData &setup, const IDeviceConstPtr &device) +{ + stop(); + + m_transfer.reset(device->createFileTransferInterface(setup)); + QTC_ASSERT(m_transfer, startFailed(tr("Missing transfer implementation.")); return); + + m_transfer->setParent(this); + connect(m_transfer.get(), &FileTransferInterface::progress, + this, &FileTransferPrivate::progress); + connect(m_transfer.get(), &FileTransferInterface::done, + this, &FileTransferPrivate::done); + m_transfer->start(); +} + +void FileTransferPrivate::startFailed(const QString &errorString) +{ + emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString}); +} + +FileTransfer::FileTransfer() + : d(new FileTransferPrivate) +{ + d->setParent(this); + connect(d, &FileTransferPrivate::progress, this, &FileTransfer::progress); + connect(d, &FileTransferPrivate::done, this, &FileTransfer::done); +} + +FileTransfer::~FileTransfer() +{ + stop(); + delete d; +} + +void FileTransfer::setFilesToTransfer(const FilesToTransfer &files) +{ + d->m_setup.m_files = files; +} + +void FileTransfer::setTransferMethod(FileTransferMethod method) +{ + d->m_setup.m_method = method; +} + +void FileTransfer::setRsyncFlags(const QString &flags) +{ + d->m_setup.m_rsyncFlags = flags; +} + +void FileTransfer::test(const ProjectExplorer::IDeviceConstPtr &onDevice) +{ + d->test(onDevice); +} + +FileTransferMethod FileTransfer::transferMethod() const +{ + return d->m_setup.m_method; +} + +void FileTransfer::start() +{ + d->start(); +} + +void FileTransfer::stop() +{ + d->stop(); +} + +QString FileTransfer::transferMethodName(FileTransferMethod method) +{ + switch (method) { + case FileTransferMethod::Sftp: return FileTransfer::tr("sftp"); + case FileTransferMethod::Rsync: return FileTransfer::tr("rsync"); + } + QTC_CHECK(false); + return {}; +} + +} // namespace ProjectExplorer + +#include "filetransfer.moc" diff --git a/src/plugins/remotelinux/filetransfer.h b/src/plugins/projectexplorer/devicesupport/filetransfer.h similarity index 70% rename from src/plugins/remotelinux/filetransfer.h rename to src/plugins/projectexplorer/devicesupport/filetransfer.h index 8560bc2a00a..a40de415ded 100644 --- a/src/plugins/remotelinux/filetransfer.h +++ b/src/plugins/projectexplorer/devicesupport/filetransfer.h @@ -25,40 +25,17 @@ #pragma once -#include "remotelinux_export.h" - -#include - -#include +#include "../projectexplorer_export.h" +#include "filetransferinterface.h" +#include "idevicefwd.h" namespace Utils { class ProcessResultData; } -namespace RemoteLinux { - -enum class TransferDirection { - Upload, - Download, - Invalid -}; - -class REMOTELINUX_EXPORT FileToTransfer -{ -public: - Utils::FilePath m_source; - Utils::FilePath m_target; - TransferDirection transferDirection() const; -}; -using FilesToTransfer = QList; - -enum class FileTransferMethod { - Sftp, - Rsync, - Default = Sftp -}; +namespace ProjectExplorer { class FileTransferPrivate; -class REMOTELINUX_EXPORT FileTransfer : public QObject +class PROJECTEXPLORER_EXPORT FileTransfer : public QObject { Q_OBJECT @@ -66,19 +43,17 @@ public: FileTransfer(); ~FileTransfer(); - void setDevice(const ProjectExplorer::IDeviceConstPtr &device); - void setTransferMethod(FileTransferMethod method); void setFilesToTransfer(const FilesToTransfer &files); + void setTransferMethod(FileTransferMethod method); void setRsyncFlags(const QString &flags); FileTransferMethod transferMethod() const; - void test(); + void test(const ProjectExplorer::IDeviceConstPtr &onDevice); void start(); void stop(); static QString transferMethodName(FileTransferMethod method); - static QString defaultRsyncFlags(); signals: void progress(const QString &progressMessage); @@ -88,6 +63,4 @@ private: FileTransferPrivate *d; }; -} // namespace RemoteLinux - -Q_DECLARE_METATYPE(RemoteLinux::FileTransferMethod) +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/filetransferinterface.h b/src/plugins/projectexplorer/devicesupport/filetransferinterface.h new file mode 100644 index 00000000000..530f2868c7d --- /dev/null +++ b/src/plugins/projectexplorer/devicesupport/filetransferinterface.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "../projectexplorer_export.h" + +#include + +namespace Utils { class ProcessResultData; } + +namespace ProjectExplorer { + +enum class FileTransferDirection { + Invalid, + Upload, + Download +}; + +enum class FileTransferMethod { + Sftp, + Rsync, + Default = Sftp +}; + +class PROJECTEXPLORER_EXPORT FileToTransfer +{ +public: + Utils::FilePath m_source; + Utils::FilePath m_target; + + FileTransferDirection direction() const; +}; + +using FilesToTransfer = QList; + +class PROJECTEXPLORER_EXPORT FileTransferSetupData +{ +public: + FilesToTransfer m_files; // When empty, do test instead of a real transfer + FileTransferMethod m_method = FileTransferMethod::Default; + QString m_rsyncFlags = defaultRsyncFlags(); + + static QString defaultRsyncFlags(); +}; + +class PROJECTEXPLORER_EXPORT FileTransferInterface : public QObject +{ + Q_OBJECT + +signals: + void progress(const QString &progressMessage); + void done(const Utils::ProcessResultData &resultData); + +protected: + FileTransferInterface(const FileTransferSetupData &setupData) + : m_setup(setupData) {} + + void startFailed(const QString &errorString); + + const FileTransferSetupData m_setup; + +private: + FileTransferInterface() = delete; + + virtual void start() = 0; + + friend class FileTransferPrivate; +}; + +} // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 2d63f6bbc55..69344b44a2c 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -434,6 +434,14 @@ ProcessInterface *IDevice::createProcessInterface() const return nullptr; } +FileTransferInterface *IDevice::createFileTransferInterface( + const FileTransferSetupData &setup) const +{ + Q_UNUSED(setup) + QTC_CHECK(false); + return nullptr; +} + Environment IDevice::systemEnvironment() const { QTC_CHECK(false); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 132e8c27194..22e125ddd9d 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -60,6 +60,8 @@ class QtcProcess; namespace ProjectExplorer { class DeviceProcessList; +class FileTransferInterface; +class FileTransferSetupData; class Kit; class SshParameters; class Task; @@ -265,6 +267,8 @@ public: virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const; virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const; virtual Utils::ProcessInterface *createProcessInterface() const; + virtual FileTransferInterface *createFileTransferInterface( + const FileTransferSetupData &setup) const; virtual Utils::Environment systemEnvironment() const; virtual qint64 fileSize(const Utils::FilePath &filePath) const; virtual qint64 bytesAvailable(const Utils::FilePath &filePath) const; diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp index 8ae238deba5..224a4c6595d 100644 --- a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp +++ b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp @@ -64,24 +64,21 @@ QStringList SshParameters::connectionOptions(const FilePath &binary) const "-o", "Port=" + QString::number(port())}; if (!userName().isEmpty()) - args.append({"-o", "User=" + userName()}); + args << "-o" << "User=" + userName(); - const bool keyOnly = authenticationType == - SshParameters::AuthenticationTypeSpecificKey; - if (keyOnly) { - args << "-o" << "IdentitiesOnly=yes"; - args << "-i" << privateKeyFile.path(); - } - if (keyOnly || SshSettings::askpassFilePath().isEmpty()) - args << "-o" << "BatchMode=yes"; + const bool keyOnly = authenticationType == SshParameters::AuthenticationTypeSpecificKey; + if (keyOnly) + args << "-o" << "IdentitiesOnly=yes" << "-i" << privateKeyFile.path(); - bool useTimeout = timeout != 0; - if (useTimeout && HostOsInfo::isWindowsHost() - && binary.toString().toLower().contains("/system32/")) { - useTimeout = false; - } + const QString batchModeEnabled = (keyOnly || SshSettings::askpassFilePath().isEmpty()) + ? QLatin1String("yes") : QLatin1String("no"); + args << "-o" << "BatchMode=" + batchModeEnabled; + + const bool isWindows = HostOsInfo::isWindowsHost() + && binary.toString().toLower().contains("/system32/"); + const bool useTimeout = (timeout != 0) && !isWindows; if (useTimeout) - args << "-o" << ("ConnectTimeout=" + QString::number(timeout)); + args << "-o" << "ConnectTimeout=" + QString::number(timeout); return args; } diff --git a/src/plugins/projectexplorer/kitinformation.cpp b/src/plugins/projectexplorer/kitinformation.cpp index 994a21465c9..a39c091dba3 100644 --- a/src/plugins/projectexplorer/kitinformation.cpp +++ b/src/plugins/projectexplorer/kitinformation.cpp @@ -1110,6 +1110,13 @@ void DeviceKitAspect::setDeviceId(Kit *k, Utils::Id id) k->setValue(DeviceKitAspect::id(), id.toSetting()); } +FilePath DeviceKitAspect::deviceFilePath(const Kit *k, const QString &pathOnDevice) +{ + if (IDevice::ConstPtr dev = device(k)) + return dev->filePath(pathOnDevice); + return FilePath::fromString(pathOnDevice); +} + void DeviceKitAspect::kitsWereLoaded() { const QList kits = KitManager::kits(); diff --git a/src/plugins/projectexplorer/kitinformation.h b/src/plugins/projectexplorer/kitinformation.h index de0939b8388..a925d84ba47 100644 --- a/src/plugins/projectexplorer/kitinformation.h +++ b/src/plugins/projectexplorer/kitinformation.h @@ -161,6 +161,7 @@ public: static Utils::Id deviceId(const Kit *k); static void setDevice(Kit *k, IDeviceConstPtr dev); static void setDeviceId(Kit *k, Utils::Id dataId); + static Utils::FilePath deviceFilePath(const Kit *k, const QString &pathOnDevice); private: QVariant defaultValue(const Kit *k) const; diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp index c4eae8b7903..bfafaa83f65 100644 --- a/src/plugins/projectexplorer/msvctoolchain.cpp +++ b/src/plugins/projectexplorer/msvctoolchain.cpp @@ -158,12 +158,23 @@ static bool hostSupportsPlatform(MsvcToolChain::Platform platform) { if (hostPrefersPlatform(platform)) return true; + + switch (HostOsInfo::hostArchitecture()) { // The x86 host toolchains are not the preferred toolchains on amd64 but they are still // supported by that host - return HostOsInfo::hostArchitecture() == HostOsInfo::HostArchitectureAMD64 - && (platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64 + case HostOsInfo::HostArchitectureAMD64: + return platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64 || platform == MsvcToolChain::x86_ia64 || platform == MsvcToolChain::x86_arm - || platform == MsvcToolChain::x86_arm64); + || platform == MsvcToolChain::x86_arm64; + // The Arm64 host can run the cross-compilers via emulation of x86 and amd64 + case HostOsInfo::HostArchitectureArm: + return platform == MsvcToolChain::x86_arm || platform == MsvcToolChain::x86_arm64 + || platform == MsvcToolChain::amd64_arm || platform == MsvcToolChain::amd64_arm64 + || platform == MsvcToolChain::x86 || platform == MsvcToolChain::x86_amd64 + || platform == MsvcToolChain::amd64 || platform == MsvcToolChain::amd64_x86; + default: + return false; + } } static QString fixRegistryPath(const QString &path) diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 0eb12650735..b857468106a 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -333,7 +333,8 @@ static bool canOpenTerminalWithRunEnv(const Project *project, const ProjectNode const RunConfiguration * const runConfig = runConfigForNode(target, node); if (!runConfig) return false; - IDevice::ConstPtr device = runConfig->runnable().device; + IDevice::ConstPtr device + = DeviceManager::deviceForPath(runConfig->runnable().command.executable()); if (!device) device = DeviceKitAspect::device(target->kit()); return device && device->canOpenTerminal(); @@ -3245,13 +3246,13 @@ void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc, dd->doUpdateRunActions(); } -QList> ProjectExplorerPlugin::runningRunControlProcesses() +QList> ProjectExplorerPlugin::runningRunControlProcesses() { - QList> processes; + QList> processes; const QList runControls = allRunControls(); for (RunControl *rc : runControls) { if (rc->isRunning()) - processes << qMakePair(rc->runnable(), rc->applicationProcessHandle()); + processes << qMakePair(rc->commandLine(), rc->applicationProcessHandle()); } return processes; } @@ -3935,7 +3936,7 @@ void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv() QTC_ASSERT(runConfig, return); const Runnable runnable = runConfig->runnable(); - IDevice::ConstPtr device = runnable.device; + IDevice::ConstPtr device = DeviceManager::deviceForPath(runnable.command.executable()); if (!device) device = DeviceKitAspect::device(target->kit()); QTC_ASSERT(device && device->canOpenTerminal(), return); diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index 0b2ad59d04b..563a380b1bb 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -164,7 +164,7 @@ public: static void runStartupProject(Utils::Id runMode, bool forceSkipDeploy = false); static void runRunConfiguration(RunConfiguration *rc, Utils::Id runMode, const bool forceSkipDeploy = false); - static QList> runningRunControlProcesses(); + static QList> runningRunControlProcesses(); static QList allRunControls(); static void addExistingFiles(FolderNode *folderNode, const Utils::FilePaths &filePaths); diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs index 4e89ad84d9b..1843cf63719 100644 --- a/src/plugins/projectexplorer/projectexplorer.qbs +++ b/src/plugins/projectexplorer/projectexplorer.qbs @@ -219,6 +219,8 @@ Project { "devicesettingswidget.cpp", "devicesettingswidget.h", "devicesettingswidget.ui", "devicetestdialog.cpp", "devicetestdialog.h", "devicetestdialog.ui", "deviceusedportsgatherer.cpp", "deviceusedportsgatherer.h", + "filetransfer.cpp", "filetransfer.h", + "filetransferinterface.h", "idevice.cpp", "idevice.h", "idevicefactory.cpp", "idevicefactory.h", "idevicefwd.h", diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp index 22a4e3a99ef..ab2e00e7440 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.cpp +++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp @@ -171,8 +171,9 @@ bool TerminalAspect::isUserSet() const working directory for running the executable. */ -WorkingDirectoryAspect::WorkingDirectoryAspect(EnvironmentAspect *envAspect) - : m_envAspect(envAspect) +WorkingDirectoryAspect::WorkingDirectoryAspect(const MacroExpander *expander, + EnvironmentAspect *envAspect) + : m_envAspect(envAspect), m_macroExpander(expander) { setDisplayName(tr("Working Directory")); setId("WorkingDirectoryAspect"); diff --git a/src/plugins/projectexplorer/runconfigurationaspects.h b/src/plugins/projectexplorer/runconfigurationaspects.h index 3c5058d40de..768d89a0430 100644 --- a/src/plugins/projectexplorer/runconfigurationaspects.h +++ b/src/plugins/projectexplorer/runconfigurationaspects.h @@ -81,7 +81,8 @@ class PROJECTEXPLORER_EXPORT WorkingDirectoryAspect : public Utils::BaseAspect Q_OBJECT public: - explicit WorkingDirectoryAspect(EnvironmentAspect *envAspect); + explicit WorkingDirectoryAspect(const Utils::MacroExpander *expander, + EnvironmentAspect *envAspect); void addToLayout(Utils::LayoutBuilder &builder) override; @@ -102,7 +103,7 @@ private: Utils::FilePath m_defaultWorkingDirectory; QPointer m_chooser; QPointer m_resetButton; - Utils::MacroExpander *m_macroExpander = nullptr; + const Utils::MacroExpander *m_macroExpander = nullptr; }; class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp index 5a1d6b6ccc2..910e8271615 100644 --- a/src/plugins/projectexplorer/runcontrol.cpp +++ b/src/plugins/projectexplorer/runcontrol.cpp @@ -28,6 +28,7 @@ #include "buildconfiguration.h" #include "customparser.h" #include "devicesupport/desktopdevice.h" +#include "devicesupport/devicemanager.h" #include "devicesupport/idevice.h" #include "devicesupport/sshsettings.h" #include "kitinformation.h" @@ -323,7 +324,6 @@ public: QMap settingsData; Utils::Id runConfigId; BuildTargetInfo buildTargetInfo; - BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown; FilePath buildDirectory; Environment buildEnvironment; Kit *kit = nullptr; // Not owned. @@ -430,7 +430,6 @@ void RunControl::setTarget(Target *target) d->buildTargetInfo = target->buildTarget(d->buildKey); if (auto bc = target->activeBuildConfiguration()) { - d->buildType = bc->buildType(); d->buildDirectory = bc->buildDirectory(); d->buildEnvironment = bc->environment(); } @@ -447,8 +446,8 @@ void RunControl::setKit(Kit *kit) d->kit = kit; d->macroExpander = kit->macroExpander(); - if (d->runnable.device) - setDevice(d->runnable.device); + if (!d->runnable.command.isEmpty()) + setDevice(DeviceManager::deviceForPath(d->runnable.command.executable())); else setDevice(DeviceKitAspect::device(kit)); } @@ -905,13 +904,50 @@ const Runnable &RunControl::runnable() const return d->runnable; } -void RunControl::setRunnable(const Runnable &runnable) +const CommandLine &RunControl::commandLine() const { - d->runnable = runnable; + return d->runnable.command; +} + +void RunControl::setCommandLine(const CommandLine &command) +{ + d->runnable.command = command; +} + +const FilePath &RunControl::workingDirectory() const +{ + return d->runnable.workingDirectory; +} + +void RunControl::setWorkingDirectory(const FilePath &workingDirectory) +{ + d->runnable.workingDirectory = workingDirectory; +} + +const Environment &RunControl::environment() const +{ + return d->runnable.environment; +} + +void RunControl::setEnvironment(const Environment &environment) +{ + d->runnable.environment = environment; +} + +const QVariantHash &RunControl::extraData() const +{ + return d->runnable.extraData; +} + +void RunControl::setExtraData(const QVariantHash &extraData) +{ + d->runnable.extraData = extraData; } QString RunControl::displayName() const { + if (d->displayName.isEmpty()) + return d->runnable.command.executable().toUserOutput(); return d->displayName; } @@ -975,11 +1011,6 @@ QString RunControl::buildKey() const return d->buildKey; } -BuildConfiguration::BuildType RunControl::buildType() const -{ - return d->buildType; -} - FilePath RunControl::buildDirectory() const { return d->buildDirectory; @@ -1242,7 +1273,10 @@ public: State m_state = Inactive; bool m_stopRequested = false; - Runnable m_runnable; + Utils::CommandLine m_command; + Utils::FilePath m_workingDirectory; + Utils::Environment m_environment; + QVariantHash m_extraData; ProcessResultData m_resultData; @@ -1398,12 +1432,12 @@ void SimpleTargetRunnerPrivate::handleStandardError() void SimpleTargetRunnerPrivate::start() { - m_isLocal = m_runnable.device.isNull() || m_runnable.device.dynamicCast(); + m_isLocal = !m_command.executable().needsDevice(); m_resultData = {}; if (m_isLocal) { - Environment env = m_runnable.environment; + Environment env = m_environment; if (m_runAsRoot) RunControl::provideAskPassEntry(env); @@ -1413,7 +1447,7 @@ void SimpleTargetRunnerPrivate::start() WinDebugInterface::startIfNeeded(); - CommandLine cmdLine = m_runnable.command; + CommandLine cmdLine = m_command; if (HostOsInfo::isMacHost()) { CommandLine disclaim(Core::ICore::libexecPath("disclaim")); @@ -1426,7 +1460,8 @@ void SimpleTargetRunnerPrivate::start() } else { QTC_ASSERT(m_state == Inactive, return); - if (!m_runnable.device) { + const IDevice::ConstPtr device = DeviceManager::deviceForPath(m_command.executable()); + if (!device) { m_resultData.m_errorString = tr("Cannot run: No device."); m_resultData.m_error = QProcess::FailedToStart; m_resultData.m_exitStatus = QProcess::CrashExit; @@ -1434,7 +1469,7 @@ void SimpleTargetRunnerPrivate::start() return; } - if (!m_runnable.device->isEmptyCommandAllowed() && m_runnable.command.isEmpty()) { + if (!device->isEmptyCommandAllowed() && m_command.isEmpty()) { m_resultData.m_errorString = tr("Cannot run: No command given."); m_resultData.m_error = QProcess::FailedToStart; m_resultData.m_exitStatus = QProcess::CrashExit; @@ -1445,12 +1480,12 @@ void SimpleTargetRunnerPrivate::start() m_state = Run; m_stopRequested = false; - m_process.setCommand(m_runnable.command); - m_process.setEnvironment(m_runnable.environment); - m_process.setExtraData(m_runnable.extraData); + m_process.setCommand(m_command); + m_process.setEnvironment(m_environment); + m_process.setExtraData(m_extraData); } - m_process.setWorkingDirectory(m_runnable.workingDirectory); + m_process.setWorkingDirectory(m_workingDirectory); if (m_isLocal) m_outputCodec = QTextCodec::codecForLocale(); @@ -1485,14 +1520,14 @@ void SimpleTargetRunnerPrivate::forwardDone() { if (m_stopReported) return; - const QString executable = m_runnable.command.executable().toUserOutput(); + const QString executable = m_command.executable().toUserOutput(); QString msg = tr("%1 exited with code %2").arg(executable).arg(m_resultData.m_exitCode); if (m_resultData.m_exitStatus == QProcess::CrashExit) msg = tr("%1 crashed.").arg(executable); else if (m_stopForced) msg = tr("The process was ended forcefully."); else if (m_resultData.m_error != QProcess::UnknownError) - msg = RunWorker::userMessageForProcessError(m_resultData.m_error, m_runnable.command.executable()); + msg = RunWorker::userMessageForProcessError(m_resultData.m_error, m_command.executable()); q->appendMessage(msg, NormalMessageFormat); m_stopReported = true; q->reportStopped(); @@ -1500,8 +1535,7 @@ void SimpleTargetRunnerPrivate::forwardDone() void SimpleTargetRunnerPrivate::forwardStarted() { - const bool isDesktop = m_runnable.device.isNull() - || m_runnable.device.dynamicCast(); + const bool isDesktop = !m_command.executable().needsDevice(); if (isDesktop) { // Console processes only know their pid after being started ProcessHandle pid{privateApplicationPID()}; @@ -1514,8 +1548,10 @@ void SimpleTargetRunnerPrivate::forwardStarted() void SimpleTargetRunner::start() { - d->m_runnable = runControl()->runnable(); - d->m_runnable.device = runControl()->device(); + d->m_command = runControl()->commandLine(); + d->m_workingDirectory = runControl()->workingDirectory(); + d->m_environment = runControl()->environment(); + d->m_extraData = runControl()->extraData(); if (d->m_startModifier) d->m_startModifier(); @@ -1534,12 +1570,11 @@ void SimpleTargetRunner::start() d->m_process.setTerminalMode(useTerminal ? Utils::TerminalMode::On : Utils::TerminalMode::Off); d->m_runAsRoot = runAsRoot; - const QString msg = RunControl::tr("Starting %1...").arg(d->m_runnable.command.toUserOutput()); - appendMessage(msg, Utils::NormalMessageFormat); + const QString msg = RunControl::tr("Starting %1...").arg(d->m_command.toUserOutput()); + appendMessage(msg, NormalMessageFormat); - const bool isDesktop = d->m_runnable.device.isNull() - || d->m_runnable.device.dynamicCast(); - if (isDesktop && d->m_runnable.command.isEmpty()) { + const bool isDesktop = !d->m_command.executable().needsDevice(); + if (isDesktop && d->m_command.isEmpty()) { reportFailure(RunControl::tr("No executable specified.")); return; } @@ -1559,31 +1594,30 @@ void SimpleTargetRunner::setStartModifier(const std::function &startMod CommandLine SimpleTargetRunner::commandLine() const { - return d->m_runnable.command; + return d->m_command; } void SimpleTargetRunner::setCommandLine(const Utils::CommandLine &commandLine) { - d->m_runnable.command = commandLine; + d->m_command = commandLine; } void SimpleTargetRunner::setEnvironment(const Environment &environment) { - d->m_runnable.environment = environment; + d->m_environment = environment; } void SimpleTargetRunner::setWorkingDirectory(const FilePath &workingDirectory) { - d->m_runnable.workingDirectory = workingDirectory; + d->m_workingDirectory = workingDirectory; } void SimpleTargetRunner::forceRunOnHost() { - d->m_runnable.device = {}; - const FilePath executable = d->m_runnable.command.executable(); + const FilePath executable = d->m_command.executable(); if (executable.needsDevice()) { QTC_CHECK(false); - d->m_runnable.command.setExecutable(FilePath::fromString(executable.path())); + d->m_command.setExecutable(FilePath::fromString(executable.path())); } } @@ -1796,11 +1830,6 @@ IDevice::ConstPtr RunWorker::device() const return d->runControl->device(); } -const Runnable &RunWorker::runnable() const -{ - return d->runControl->runnable(); -} - void RunWorker::addStartDependency(RunWorker *dependency) { d->startDependencies.append(dependency); @@ -1906,11 +1935,6 @@ void RunWorker::stop() reportStopped(); } -QString Runnable::displayName() const -{ - return command.executable().toString(); -} - // OutputFormatterFactory static QList g_outputFormatterFactories; diff --git a/src/plugins/projectexplorer/runcontrol.h b/src/plugins/projectexplorer/runcontrol.h index 12230ae8bc3..ebb0ec54a0b 100644 --- a/src/plugins/projectexplorer/runcontrol.h +++ b/src/plugins/projectexplorer/runcontrol.h @@ -25,9 +25,7 @@ #pragma once -#include "buildconfiguration.h" #include "devicesupport/idevicefwd.h" -#include "projectexplorerconstants.h" #include "runconfiguration.h" #include @@ -50,8 +48,6 @@ class OutputLineParser; } // Utils namespace ProjectExplorer { -class GlobalOrProjectAspect; -class Node; class RunConfiguration; class RunControl; class Target; @@ -71,11 +67,7 @@ public: Utils::CommandLine command; Utils::FilePath workingDirectory; Utils::Environment environment; - IDeviceConstPtr device; // Override the kit's device. Keep unset by default. QVariantHash extraData; - - // FIXME: Not necessarily a display name - QString displayName() const; }; class PROJECTEXPLORER_EXPORT RunWorker : public QObject @@ -102,7 +94,6 @@ public: // Part of read-only interface of RunControl for convenience. void appendMessage(const QString &msg, Utils::OutputFormat format, bool appendNewLine = true); IDeviceConstPtr device() const; - const Runnable &runnable() const; // States void initiateStart(); @@ -211,7 +202,7 @@ public: bool supportsReRunning() const; - virtual QString displayName() const; + QString displayName() const; void setDisplayName(const QString &displayName); bool isRunning() const; @@ -239,7 +230,6 @@ public: } QString buildKey() const; - BuildConfiguration::BuildType buildType() const; Utils::FilePath buildDirectory() const; Utils::Environment buildEnvironment() const; @@ -252,7 +242,18 @@ public: Utils::Id runMode() const; const Runnable &runnable() const; - void setRunnable(const Runnable &runnable); + + const Utils::CommandLine &commandLine() const; + void setCommandLine(const Utils::CommandLine &command); + + const Utils::FilePath &workingDirectory() const; + void setWorkingDirectory(const Utils::FilePath &workingDirectory); + + const Utils::Environment &environment() const; + void setEnvironment(const Utils::Environment &environment); + + const QVariantHash &extraData() const; + void setExtraData(const QVariantHash &extraData); static bool showPromptToStopDialog(const QString &title, const QString &text, const QString &stopButtonText = QString(), diff --git a/src/plugins/python/pysidebuildconfiguration.cpp b/src/plugins/python/pysidebuildconfiguration.cpp index c344d0d1708..1df0c4ea672 100644 --- a/src/plugins/python/pysidebuildconfiguration.cpp +++ b/src/plugins/python/pysidebuildconfiguration.cpp @@ -89,24 +89,9 @@ PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id) setWorkingDirectoryProvider([this] { return target()->project()->projectDirectory(); }); } -void PySideBuildStep::updateInterpreter(const Utils::FilePath &python) +void PySideBuildStep::updatePySideProjectPath(const Utils::FilePath &pySideProjectPath) { - Utils::FilePath pySideProjectPath; - const PipPackage pySide6Package("PySide6"); - const PipPackageInfo info = pySide6Package.info(python); - for (const FilePath &file : qAsConst(info.files)) { - if (file.fileName() == HostOsInfo::withExecutableSuffix("pyside6-project")) { - pySideProjectPath = info.location.resolvePath(file); - pySideProjectPath = pySideProjectPath.cleanPath(); - break; - } - } - - if (!pySideProjectPath.isExecutableFile()) - pySideProjectPath = Environment::systemEnvironment().searchInPath("pyside6-project"); - - if (pySideProjectPath.isExecutableFile()) - m_pysideProject->setFilePath(pySideProjectPath); + m_pysideProject->setFilePath(pySideProjectPath); } void PySideBuildStep::doRun() diff --git a/src/plugins/python/pysidebuildconfiguration.h b/src/plugins/python/pysidebuildconfiguration.h index 943c995f472..3a199f25388 100644 --- a/src/plugins/python/pysidebuildconfiguration.h +++ b/src/plugins/python/pysidebuildconfiguration.h @@ -50,7 +50,7 @@ class PySideBuildStep : public ProjectExplorer::AbstractProcessStep Q_OBJECT public: PySideBuildStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); - void updateInterpreter(const Utils::FilePath &python); + void updatePySideProjectPath(const Utils::FilePath &pySideProjectPath); private: Utils::StringAspect *m_pysideProject; diff --git a/src/plugins/python/pythonrunconfiguration.cpp b/src/plugins/python/pythonrunconfiguration.cpp index cb5cb948cb5..4c184bbdc19 100644 --- a/src/plugins/python/pythonrunconfiguration.cpp +++ b/src/plugins/python/pythonrunconfiguration.cpp @@ -140,85 +140,99 @@ private: class PythonRunConfiguration : public RunConfiguration { public: - PythonRunConfiguration(Target *target, Id id) - : RunConfiguration(target, id) - { - auto interpreterAspect = addAspect(); - interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter"); - interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID); + PythonRunConfiguration(Target *target, Id id); + void currentInterpreterChanged(); +}; - connect(interpreterAspect, &InterpreterAspect::changed, - this, &PythonRunConfiguration::currentInterpreterChanged); +PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id) + : RunConfiguration(target, id) +{ + auto interpreterAspect = addAspect(); + interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter"); + interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID); - connect(PythonSettings::instance(), &PythonSettings::interpretersChanged, - interpreterAspect, &InterpreterAspect::updateInterpreters); + connect(interpreterAspect, &InterpreterAspect::changed, + this, &PythonRunConfiguration::currentInterpreterChanged); - QList interpreters = PythonSettings::detectPythonVenvs( - project()->projectDirectory()); - interpreterAspect->updateInterpreters(PythonSettings::interpreters()); - Interpreter defaultInterpreter = interpreters.isEmpty() - ? PythonSettings::defaultInterpreter() - : interpreters.first(); - if (!defaultInterpreter.command.isExecutableFile()) - defaultInterpreter = PythonSettings::interpreters().value(0); - interpreterAspect->setDefaultInterpreter(defaultInterpreter); + connect(PythonSettings::instance(), &PythonSettings::interpretersChanged, + interpreterAspect, &InterpreterAspect::updateInterpreters); - auto bufferedAspect = addAspect(); - bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered"); - bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox); - bufferedAspect->setToolTip(tr("Enabling improves output performance, " - "but results in delayed output.")); + QList interpreters = PythonSettings::detectPythonVenvs( + project()->projectDirectory()); + interpreterAspect->updateInterpreters(PythonSettings::interpreters()); + Interpreter defaultInterpreter = interpreters.isEmpty() ? PythonSettings::defaultInterpreter() + : interpreters.first(); + if (!defaultInterpreter.command.isExecutableFile()) + defaultInterpreter = PythonSettings::interpreters().value(0); + interpreterAspect->setDefaultInterpreter(defaultInterpreter); - auto scriptAspect = addAspect(); - scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script"); - scriptAspect->setLabelText(tr("Script:")); - scriptAspect->setDisplayStyle(StringAspect::LabelDisplay); + auto bufferedAspect = addAspect(); + bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered"); + bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox); + bufferedAspect->setToolTip(tr("Enabling improves output performance, " + "but results in delayed output.")); - addAspect(target); + auto scriptAspect = addAspect(); + scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script"); + scriptAspect->setLabelText(tr("Script:")); + scriptAspect->setDisplayStyle(StringAspect::LabelDisplay); - auto argumentsAspect = addAspect(macroExpander()); + addAspect(target); - addAspect(nullptr); - addAspect(); + auto argumentsAspect = addAspect(macroExpander()); - setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] { - CommandLine cmd{interpreterAspect->currentInterpreter().command}; - if (!bufferedAspect->value()) - cmd.addArg("-u"); - cmd.addArg(scriptAspect->filePath().fileName()); - cmd.addArgs(argumentsAspect->arguments(), CommandLine::Raw); - return cmd; - }); + addAspect(macroExpander(), nullptr); + addAspect(); - setUpdater([this, scriptAspect] { - const BuildTargetInfo bti = buildTargetInfo(); - const QString script = bti.targetFilePath.toUserOutput(); - setDefaultDisplayName(tr("Run %1").arg(script)); - scriptAspect->setValue(script); - aspect()->setDefaultWorkingDirectory(bti.targetFilePath.parentDir()); - }); + setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] { + CommandLine cmd{interpreterAspect->currentInterpreter().command}; + if (!bufferedAspect->value()) + cmd.addArg("-u"); + cmd.addArg(scriptAspect->filePath().fileName()); + cmd.addArgs(argumentsAspect->arguments(), CommandLine::Raw); + return cmd; + }); - connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); + setUpdater([this, scriptAspect] { + const BuildTargetInfo bti = buildTargetInfo(); + const QString script = bti.targetFilePath.toUserOutput(); + setDefaultDisplayName(tr("Run %1").arg(script)); + scriptAspect->setValue(script); + aspect()->setDefaultWorkingDirectory(bti.targetFilePath.parentDir()); + }); + + connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); +} + +void PythonRunConfiguration::currentInterpreterChanged() +{ + const FilePath python = aspect()->currentInterpreter().command; + BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps(); + + Utils::FilePath pySideProjectPath; + const PipPackage pySide6Package("PySide6"); + const PipPackageInfo info = pySide6Package.info(python); + + for (const FilePath &file : qAsConst(info.files)) { + if (file.fileName() == HostOsInfo::withExecutableSuffix("pyside6-project")) { + pySideProjectPath = info.location.resolvePath(file); + pySideProjectPath = pySideProjectPath.cleanPath(); + break; + } } - void currentInterpreterChanged() - { - const FilePath python = aspect()->currentInterpreter().command; + if (auto pySideBuildStep = buildSteps->firstOfType()) + pySideBuildStep->updatePySideProjectPath(pySideProjectPath); - BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps(); - if (auto pySideBuildStep = buildSteps->firstOfType()) - pySideBuildStep->updateInterpreter(python); - - for (FilePath &file : project()->files(Project::AllFiles)) { - if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) { - if (document->mimeType() == Constants::C_PY_MIMETYPE) { - PyLSConfigureAssistant::openDocumentWithPython(python, document); - PySideInstaller::checkPySideInstallation(python, document); - } + for (FilePath &file : project()->files(Project::AllFiles)) { + if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) { + if (document->mimeType() == Constants::C_PY_MIMETYPE) { + PyLSConfigureAssistant::openDocumentWithPython(python, document); + PySideInstaller::checkPySideInstallation(python, document); } } } -}; +} PythonRunConfigurationFactory::PythonRunConfigurationFactory() { diff --git a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp index 7909de1981e..0fbfa346816 100644 --- a/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp +++ b/src/plugins/qbsprojectmanager/qbsprofilemanager.cpp @@ -181,7 +181,7 @@ void QbsProfileManager::addProfileFromKit(const ProjectExplorer::Kit *k) data = provider->properties(k, data); } if (const QtSupport::QtVersion * const qt = QtSupport::QtKitAspect::qtVersion(k)) - data.insert("moduleProviders.Qt.qmakeFilePaths", qt->qmakeFilePath().toString()); + data.insert("moduleProviders.Qt.qmakeFilePaths", qt->queryToolFilePath().toString()); if (QbsSettings::qbsVersion() < QVersionNumber({1, 20})) { const QString keyPrefix = "profiles." + name + "."; diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index c77f468ae23..d292f1fb5a5 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -745,6 +745,9 @@ Tasks QmakeProject::projectIssues(const Kit *k) const result.append(createProjectTask(Task::TaskType::Error, tr("Qt version is invalid."))); if (!ToolChainKitAspect::cxxToolChain(k)) result.append(createProjectTask(Task::TaskType::Error, tr("No C++ compiler set in kit."))); + if (!qtFromKit->qmakeFilePath().isExecutableFile()) + result.append(createProjectTask(Task::TaskType::Error, + tr("Qmake was not found or is not executable."))); // A project can be considered part of more than one Qt version, for instance if it is an // example shipped via the installer. diff --git a/src/plugins/qmakeprojectmanager/qmakestep.cpp b/src/plugins/qmakeprojectmanager/qmakestep.cpp index 2502297ef00..2444303cb4d 100644 --- a/src/plugins/qmakeprojectmanager/qmakestep.cpp +++ b/src/plugins/qmakeprojectmanager/qmakestep.cpp @@ -103,7 +103,8 @@ QMakeStep::QMakeStep(BuildStepList *bsl, Id id) auto updateSummary = [this] { QtVersion *qtVersion = QtKitAspect::qtVersion(target()->kit()); if (!qtVersion) - return tr("qmake: No Qt version set. Cannot run qmake."); + return tr("Query tool: No Qt version set. " + "Cannot run neither qmake nor qtpaths."); const QString program = qtVersion->qmakeFilePath().fileName(); return tr("qmake: %1 %2").arg(program, project()->projectFilePath().fileName()); }; @@ -164,7 +165,7 @@ QString QMakeStep::allArguments(const QtVersion *v, ArgumentFlags flags) const QString args = ProcessArgs::joinArgs(arguments); // User arguments ProcessArgs::addArgs(&args, userArguments()); - for (QString arg : qAsConst(m_extraArgs)) + for (const QString &arg : qAsConst(m_extraArgs)) ProcessArgs::addArgs(&args, arg); return (flags & ArgumentFlag::Expand) ? bc->macroExpander()->expand(args) : args; } @@ -214,7 +215,8 @@ bool QMakeStep::init() else workingDirectory = qmakeBc->buildDirectory(); - m_qmakeCommand = CommandLine{qtVersion->qmakeFilePath(), allArguments(qtVersion), CommandLine::Raw}; + m_qmakeCommand = + CommandLine{qtVersion->qmakeFilePath(), allArguments(qtVersion), CommandLine::Raw}; m_runMakeQmake = (qtVersion->qtVersion() >= QtVersionNumber(5, 0 ,0)); // The Makefile is used by qmake and make on the build device, from that diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 5ee0dc1888d..896ec7818c5 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -134,6 +134,8 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model) void MaterialBrowserView::selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) { + Q_UNUSED(lastSelectedNodeList) + ModelNode selectedModel; for (const ModelNode &node : selectedNodeList) { @@ -175,6 +177,8 @@ void MaterialBrowserView::modelNodePreviewPixmapChanged(const ModelNode &node, c void MaterialBrowserView::variantPropertiesChanged(const QList &propertyList, PropertyChangeFlags propertyChange) { + Q_UNUSED(propertyChange) + for (const VariantProperty &property : propertyList) { ModelNode node(property.parentModelNode()); @@ -188,6 +192,8 @@ void MaterialBrowserView::nodeReparented(const ModelNode &node, const NodeAbstractProperty &oldPropertyParent, PropertyChangeFlags propertyChange) { + Q_UNUSED(propertyChange) + if (!isMaterial(node)) return; @@ -225,6 +231,9 @@ void MaterialBrowserView::nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) { + Q_UNUSED(removedNode) + Q_UNUSED(propertyChange) + if (parentProperty.parentModelNode().id() != Constants::MATERIAL_LIB_ID) return; @@ -233,6 +242,9 @@ void MaterialBrowserView::nodeRemoved(const ModelNode &removedNode, void MaterialBrowserView::importsChanged(const QList &addedImports, const QList &removedImports) { + Q_UNUSED(addedImports) + Q_UNUSED(removedImports) + bool hasQuick3DImport = model()->hasImport("QtQuick3D"); if (hasQuick3DImport == m_hasQuick3DImport) @@ -246,6 +258,8 @@ void MaterialBrowserView::importsChanged(const QList &addedImports, cons void MaterialBrowserView::customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) { + Q_UNUSED(data) + if (view == this) return; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index 0bcefaec5fe..8257ae6857b 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -675,6 +675,8 @@ WidgetInfo MaterialEditorView::widgetInfo() void MaterialEditorView::selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) { + Q_UNUSED(lastSelectedNodeList) + m_selectedModels.clear(); for (const ModelNode &node : selectedNodeList) { @@ -730,6 +732,9 @@ void MaterialEditorView::modelNodePreviewPixmapChanged(const ModelNode &node, co void MaterialEditorView::importsChanged(const QList &addedImports, const QList &removedImports) { + Q_UNUSED(addedImports) + Q_UNUSED(removedImports) + m_hasQuick3DImport = model()->hasImport("QtQuick3D"); m_qmlBackEnd->contextObject()->setHasQuick3DImport(m_hasQuick3DImport); @@ -787,6 +792,8 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material) void MaterialEditorView::customNotification(const AbstractView *view, const QString &identifier, const QList &nodeList, const QList &data) { + Q_UNUSED(view) + if (identifier == "selected_material_changed") { m_selectedMaterial = nodeList.first(); QTimer::singleShot(0, this, &MaterialEditorView::resetView); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index 38c88a1cee1..6f511472057 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -610,7 +610,7 @@ QString PuppetCreator::qmakeCommand() const { QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(m_target->kit()); if (currentQtVersion) - return currentQtVersion->qmakeFilePath().toString(); + return currentQtVersion->queryToolFilePath().toString(); return QString(); } diff --git a/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp b/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp index 15f30b1025e..8d9b1b834dd 100644 --- a/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp +++ b/src/plugins/qmlprofiler/tests/localqmlprofilerrunner_test.cpp @@ -36,6 +36,9 @@ #include #include +using namespace ProjectExplorer; +using namespace Utils; + namespace QmlProfiler { namespace Internal { @@ -45,9 +48,8 @@ LocalQmlProfilerRunnerTest::LocalQmlProfilerRunnerTest(QObject *parent) : QObjec void LocalQmlProfilerRunnerTest::testRunner() { - QPointer runControl; + QPointer runControl; QPointer profiler; - ProjectExplorer::Runnable debuggee; QUrl serverUrl; bool running = false; @@ -56,37 +58,35 @@ void LocalQmlProfilerRunnerTest::testRunner() int runCount = 0; int stopCount = 0; - debuggee.command.setExecutable("\\-/|\\-/"); - debuggee.environment = Utils::Environment::systemEnvironment(); - // should not be used anywhere but cannot be empty serverUrl.setScheme(Utils::urlSocketScheme()); serverUrl.setPath("invalid"); - runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); - runControl->setRunnable(debuggee); + runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); + runControl->setCommandLine({"\\-/|\\-/", {}}); + profiler = new LocalQmlProfilerSupport(runControl, serverUrl); auto connectRunner = [&]() { - connect(runControl, &ProjectExplorer::RunControl::aboutToStart, this, [&]() { + connect(runControl, &RunControl::aboutToStart, this, [&] { QVERIFY(!started); QVERIFY(!running); ++startCount; started = true; }); - connect(runControl, &ProjectExplorer::RunControl::started, this, [&]() { + connect(runControl, &RunControl::started, this, [&] { QVERIFY(started); QVERIFY(!running); ++runCount; running = true; }); - connect(runControl, &ProjectExplorer::RunControl::stopped, this, [&]() { + connect(runControl, &RunControl::stopped, this, [&] { QVERIFY(started); ++stopCount; running = false; started = false; }); - connect(runControl, &ProjectExplorer::RunControl::finished, this, [&]() { + connect(runControl, &RunControl::finished, this, [&]{ running = false; started = false; }); @@ -110,10 +110,10 @@ void LocalQmlProfilerRunnerTest::testRunner() serverUrl = Utils::urlFromLocalSocket(); // comma is used to specify a test function. In this case, an invalid one. - debuggee.command = Utils::CommandLine(Utils::FilePath::fromString(QCoreApplication::applicationFilePath()), - {"-test", "QmlProfiler,"}); - runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); - runControl->setRunnable(debuggee); + runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); + + const FilePath app = FilePath::fromString(QCoreApplication::applicationFilePath()); + runControl->setCommandLine({app, {"-test", "QmlProfiler,"}}); profiler = new LocalQmlProfilerSupport(runControl, serverUrl); connectRunner(); runControl->initiateStart(); @@ -128,11 +128,10 @@ void LocalQmlProfilerRunnerTest::testRunner() QTRY_VERIFY(runControl.isNull()); QVERIFY(profiler.isNull()); - debuggee.command.setArguments({}); serverUrl.clear(); serverUrl = Utils::urlFromLocalHostAndFreePort(); - runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); - runControl->setRunnable(debuggee); + runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); + runControl->setCommandLine({app, {}}); profiler = new LocalQmlProfilerSupport(runControl, serverUrl); connectRunner(); runControl->initiateStart(); @@ -148,7 +147,6 @@ void LocalQmlProfilerRunnerTest::testRunner() QTRY_VERIFY(runControl.isNull()); QVERIFY(profiler.isNull()); - debuggee.command.setArguments("-test QmlProfiler,"); serverUrl.setScheme(Utils::urlSocketScheme()); { Utils::TemporaryFile file("file with spaces"); @@ -156,8 +154,8 @@ void LocalQmlProfilerRunnerTest::testRunner() serverUrl.setPath(file.fileName()); } - runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); - runControl->setRunnable(debuggee); + runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE); + runControl->setCommandLine({app, {"-test", "QmlProfiler,"}}); profiler = new LocalQmlProfilerSupport(runControl, serverUrl); connectRunner(); runControl->initiateStart(); diff --git a/src/plugins/qnx/qnxrunconfiguration.cpp b/src/plugins/qnx/qnxrunconfiguration.cpp index 88535ebd41d..39753f8afb4 100644 --- a/src/plugins/qnx/qnxrunconfiguration.cpp +++ b/src/plugins/qnx/qnxrunconfiguration.cpp @@ -61,7 +61,7 @@ QnxRunConfiguration::QnxRunConfiguration(Target *target, Id id) auto envAspect = addAspect(target); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); addAspect(); auto libAspect = addAspect(); diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index 1aafe1f575c..324add0e7c6 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -81,6 +81,7 @@ const char QTVERSIONAUTODETECTED[] = "isAutodetected"; const char QTVERSIONDETECTIONSOURCE[] = "autodetectionSource"; const char QTVERSION_OVERRIDE_FEATURES[] = "overrideFeatures"; const char QTVERSIONQMAKEPATH[] = "QMakePath"; +const char QTVERSIONQUERYTOOLPATH[] = "QueryToolPath"; const char QTVERSIONSOURCEPATH[] = "SourcePath"; const char QTVERSION_ABIS[] = "Abis"; @@ -187,19 +188,19 @@ public: FilePath findHostBinary(HostBinaries binary) const; void updateMkspec(); QHash versionInfo(); - static bool queryQMakeVariables(const FilePath &binary, - const Environment &env, - QHash *versionInfo, - QString *error = nullptr); + static bool queryQtPaths(const FilePath &queryTool, + const Environment &env, + QHash *versionInfo, + QString *error = nullptr); enum PropertyVariant { PropertyVariantDev, PropertyVariantGet, PropertyVariantSrc }; QString qmakeProperty(const QByteArray &name, PropertyVariant variant = PropertyVariantGet); static QString qmakeProperty(const QHash &versionInfo, const QByteArray &name, PropertyVariant variant = PropertyVariantGet); static FilePath mkspecDirectoryFromVersionInfo(const QHash &versionInfo, - const FilePath &qmakeCommand); + const FilePath &queryTool); static FilePath mkspecFromVersionInfo(const QHash &versionInfo, - const FilePath &qmakeCommand); + const FilePath &queryTool); static FilePath sourcePath(const QHash &versionInfo); void setId(int id); // used by the qtversionmanager for legacy restore // and by the qtoptionspage to replace Qt versions @@ -221,7 +222,7 @@ public: bool m_defaultConfigIsDebugAndRelease = true; bool m_frameworkBuild = false; bool m_versionInfoUpToDate = false; - bool m_qmakeIsExecutable = true; + bool m_queryToolIsExecutable = true; QString m_detectionSource; QSet m_overrideFeatures; @@ -233,6 +234,7 @@ public: QHash m_versionInfo; + FilePath m_queryTool; FilePath m_qmakeCommand; FilePath m_rccPath; @@ -346,12 +348,12 @@ QtVersion::~QtVersion() QString QtVersion::defaultUnexpandedDisplayName() const { QString location; - if (qmakeFilePath().isEmpty()) { + if (queryToolFilePath().isEmpty()) { location = QCoreApplication::translate("QtVersion", ""); } else { // Deduce a description from '/foo/qt-folder/[qtbase]/bin/qmake' -> '/foo/qt-folder'. // '/usr' indicates System Qt 4.X on Linux. - for (FilePath dir = qmakeFilePath().parentDir(); !dir.isEmpty(); dir = dir.parentDir()) { + for (FilePath dir = queryToolFilePath().parentDir(); !dir.isEmpty(); dir = dir.parentDir()) { const QString dirName = dir.fileName(); if (dirName == "usr") { // System-installed Qt. location = QCoreApplication::translate("QtVersion", "System"); @@ -725,20 +727,23 @@ void QtVersion::fromMap(const QVariantMap &map) d->m_isAutodetected = map.value(QTVERSIONAUTODETECTED).toBool(); d->m_detectionSource = map.value(QTVERSIONDETECTIONSOURCE).toString(); d->m_overrideFeatures = Utils::Id::fromStringList(map.value(QTVERSION_OVERRIDE_FEATURES).toStringList()); - d->m_qmakeCommand = FilePath::fromVariant(map.value(QTVERSIONQMAKEPATH)); + d->m_queryTool = FilePath::fromVariant(map.value(QTVERSIONQUERYTOOLPATH, + map.value(QTVERSIONQMAKEPATH))); + if (!d->m_queryTool.baseName().contains("qtpaths")) + d->m_qmakeCommand = d->m_queryTool; - FilePath qmake = d->m_qmakeCommand; + FilePath queryTool = d->m_queryTool; // FIXME: Check this is still needed or whether ProcessArgs::splitArg handles it. - QString string = d->m_qmakeCommand.path(); + QString string = d->m_queryTool.path(); if (string.startsWith('~')) string.remove(0, 1).prepend(QDir::homePath()); - qmake.setPath(string); - if (!d->m_qmakeCommand.needsDevice()) { - if (BuildableHelperLibrary::isQtChooser(qmake)) { + queryTool.setPath(string); + if (!d->m_queryTool.needsDevice()) { + if (BuildableHelperLibrary::isQtChooser(queryTool)) { // we don't want to treat qtchooser as a normal qmake // see e.g. QTCREATORBUG-9841, also this lead to users changing what // qtchooser forwards too behind our backs, which will inadvertly lead to bugs - d->m_qmakeCommand = BuildableHelperLibrary::qtChooserToQmakePath(qmake); + d->m_queryTool = BuildableHelperLibrary::qtChooserToQueryToolPath(queryTool); } } @@ -769,6 +774,7 @@ QVariantMap QtVersion::toMap() const result.insert(QTVERSION_OVERRIDE_FEATURES, Utils::Id::toStringList(d->m_overrideFeatures)); result.insert(QTVERSIONQMAKEPATH, qmakeFilePath().toVariant()); + result.insert(QTVERSIONQUERYTOOLPATH, queryToolFilePath().toVariant()); return result; } @@ -779,8 +785,8 @@ bool QtVersion::isValid() const d->updateVersionInfo(); d->updateMkspec(); - return !qmakeFilePath().isEmpty() && d->m_data.installed && !binPath().isEmpty() - && !d->m_mkspecFullPath.isEmpty() && d->m_qmakeIsExecutable; + return !queryToolFilePath().isEmpty() && d->m_data.installed && !binPath().isEmpty() + && !d->m_mkspecFullPath.isEmpty() && d->m_queryToolIsExecutable; } QtVersion::Predicate QtVersion::isValidPredicate(const QtVersion::Predicate &predicate) @@ -794,15 +800,18 @@ QString QtVersion::invalidReason() const { if (displayName().isEmpty()) return QCoreApplication::translate("QtVersion", "Qt version has no name"); - if (qmakeFilePath().isEmpty()) - return QCoreApplication::translate("QtVersion", "No qmake path set"); - if (!d->m_qmakeIsExecutable) - return QCoreApplication::translate("QtVersion", "qmake does not exist or is not executable"); + if (queryToolFilePath().isEmpty()) + return QCoreApplication::translate("QtVersion", "No Qt query tool path set"); + if (!d->m_queryToolIsExecutable) + return QCoreApplication::translate("QtVersion", "%1 does not exist or is not executable") + .arg(queryToolFilePath().baseName()); if (!d->m_data.installed) return QCoreApplication::translate("QtVersion", "Qt version is not properly installed, please run make install"); if (binPath().isEmpty()) return QCoreApplication::translate("QtVersion", - "Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?"); + "Could not determine the path to the binaries of the " + "Qt installation, maybe the %1 path is wrong?") + .arg(queryToolFilePath().baseName()); if (d->m_mkspecUpToDate && d->m_mkspecFullPath.isEmpty()) return QCoreApplication::translate("QtVersion", "The default mkspec symlink is broken."); return QString(); @@ -820,8 +829,20 @@ QStringList QtVersion::warningReason() const return ret; } +FilePath QtVersion::queryToolFilePath() const +{ + return d->m_queryTool; +} + FilePath QtVersion::qmakeFilePath() const { + if (d->m_qmakeCommand.isEmpty() && d->m_queryTool.baseName().contains("qtpaths")) { + // TODO: might need a less lazy implementation + const FilePath qmake = + FilePath::fromString(d->m_queryTool.toString().replace("qtpaths", "qmake")); + if (qmake.exists()) + d->m_qmakeCommand = qmake; + } return d->m_qmakeCommand; } @@ -855,7 +876,7 @@ bool QtVersion::hasAbi(ProjectExplorer::Abi::OS os, ProjectExplorer::Abi::OSFlav bool QtVersion::equals(QtVersion *other) { - if (d->m_qmakeCommand != other->d->m_qmakeCommand) + if (d->m_queryTool != other->d->m_queryTool) return false; if (type() != other->type()) return false; @@ -933,13 +954,15 @@ QString QtVersion::toHtml(bool verbose) const str << "" << abis.at(i).toString() << ""; } } - const OsType osType = d->m_qmakeCommand.osType(); + const OsType osType = d->m_queryTool.osType(); str << "" << QCoreApplication::translate("QtVersion", "Source:") << "" << sourcePath().toUserOutput() << ""; str << "" << QCoreApplication::translate("QtVersion", "mkspec:") << "" << QDir::toNativeSeparators(mkspec()) << ""; str << "" << QCoreApplication::translate("QtVersion", "qmake:") - << "" << d->m_qmakeCommand.toUserOutput() << ""; + << "" << qmakeFilePath().toUserOutput() << ""; + str << "" << QCoreApplication::translate("QtVersion", "Query tool:") + << "" << d->m_queryTool.toUserOutput() << ""; ensureMkSpecParsed(); if (!mkspecPath().isEmpty()) { if (d->m_defaultConfigIsDebug || d->m_defaultConfigIsDebugAndRelease) { @@ -1164,13 +1187,13 @@ void QtVersionPrivate::updateMkspec() return; m_mkspecUpToDate = true; - m_mkspecFullPath = mkspecFromVersionInfo(versionInfo(), m_qmakeCommand); + m_mkspecFullPath = mkspecFromVersionInfo(versionInfo(), m_queryTool); m_mkspec = m_mkspecFullPath; if (m_mkspecFullPath.isEmpty()) return; - FilePath baseMkspecDir = mkspecDirectoryFromVersionInfo(versionInfo(), m_qmakeCommand); + FilePath baseMkspecDir = mkspecDirectoryFromVersionInfo(versionInfo(), m_queryTool); if (m_mkspec.isChildOf(baseMkspecDir)) { m_mkspec = m_mkspec.relativeChildPath(baseMkspecDir); @@ -1197,7 +1220,7 @@ void QtVersion::ensureMkSpecParsed() const QMakeVfs vfs; QMakeGlobals option; applyProperties(&option); - Environment env = d->m_qmakeCommand.deviceEnvironment(); + Environment env = d->m_queryTool.deviceEnvironment(); setupQmakeRunEnvironment(env); option.environment = env.toProcessEnvironment(); ProMessageHandler msgHandler(true); @@ -1310,7 +1333,7 @@ QtVersionNumber QtVersion::qtVersion() const void QtVersionPrivate::updateVersionInfo() { - if (m_versionInfoUpToDate || !m_qmakeIsExecutable || m_isUpdating) + if (m_versionInfoUpToDate || !m_queryToolIsExecutable || m_isUpdating) return; m_isUpdating = true; @@ -1321,16 +1344,16 @@ void QtVersionPrivate::updateVersionInfo() m_data.hasExamples = false; m_data.hasDocumentation = false; - if (!queryQMakeVariables(m_qmakeCommand, q->qmakeRunEnvironment(), &m_versionInfo)) { - m_qmakeIsExecutable = false; + if (!queryQtPaths(m_queryTool, q->qmakeRunEnvironment(), &m_versionInfo)) { + m_queryToolIsExecutable = false; qWarning("Cannot update Qt version information: %s cannot be run.", - qPrintable(m_qmakeCommand.toString())); + qPrintable(m_queryTool.toString())); return; } - m_qmakeIsExecutable = true; + m_queryToolIsExecutable = true; auto fileProperty = [this](const QByteArray &name) { - return FilePath::fromUserInput(qmakeProperty(name)).onDevice(m_qmakeCommand); + return FilePath::fromUserInput(qmakeProperty(name)).onDevice(m_queryTool); }; m_data.prefix = fileProperty("QT_INSTALL_PREFIX"); @@ -1385,8 +1408,8 @@ QHash QtVersionPrivate::versionInfo() } QString QtVersionPrivate::qmakeProperty(const QHash &versionInfo, - const QByteArray &name, - PropertyVariant variant) + const QByteArray &name, + PropertyVariant variant) { QString val = versionInfo .value(ProKey(QString::fromLatin1( @@ -1728,7 +1751,7 @@ void QtVersion::addToEnvironment(const Kit *k, Environment &env) const Environment QtVersion::qmakeRunEnvironment() const { - Environment env = d->m_qmakeCommand.deviceEnvironment(); + Environment env = d->m_queryTool.deviceEnvironment(); setupQmakeRunEnvironment(env); return env; } @@ -1756,7 +1779,7 @@ Tasks QtVersion::reportIssuesImpl(const QString &proFile, const QString &buildDi results.append(BuildSystemTask(Task::Error, msg)); } - FilePath qmake = qmakeFilePath(); + FilePath qmake = queryToolFilePath(); if (!qmake.isExecutableFile()) { //: %1: Path to qmake executable const QString msg = QCoreApplication::translate("QmakeProjectManager::QtVersion", @@ -1816,20 +1839,21 @@ static QByteArray runQmakeQuery(const FilePath &binary, const Environment &env, return process.readAllStandardOutput(); } -bool QtVersionPrivate::queryQMakeVariables(const FilePath &binary, const Environment &env, - QHash *versionInfo, QString *error) +bool QtVersionPrivate::queryQtPaths(const FilePath &queryTool, const Environment &env, + QHash *versionInfo, QString *error) { QString tmp; if (!error) error = &tmp; - if (!binary.isExecutableFile()) { - *error = QCoreApplication::translate("QtVersion", "qmake \"%1\" is not an executable.").arg(binary.toUserOutput()); + if (!queryTool.isExecutableFile()) { + *error = QCoreApplication::translate("QtVersion", "\"%1\" is not an executable.") + .arg(queryTool.toUserOutput()); return false; } QByteArray output; - output = runQmakeQuery(binary, env, error); + output = runQmakeQuery(queryTool, env, error); if (!output.contains("QMAKE_VERSION:")) { // Some setups pass error messages via stdout, fooling the logic below. @@ -1847,14 +1871,14 @@ bool QtVersionPrivate::queryQMakeVariables(const FilePath &binary, const Environ // Try running qmake with all kinds of tool chains set up in the environment. // This is required to make non-static qmakes work on windows where every tool chain // tries to be incompatible with any other. - const Abis abiList = Abi::abisOfBinary(binary); + const Abis abiList = Abi::abisOfBinary(queryTool); const Toolchains tcList = ToolChainManager::toolchains([&abiList](const ToolChain *t) { return abiList.contains(t->targetAbi()); }); for (ToolChain *tc : tcList) { Environment realEnv = env; tc->addToEnvironment(realEnv); - output = runQmakeQuery(binary, realEnv, error); + output = runQmakeQuery(queryTool, realEnv, error); if (error->isEmpty()) break; } @@ -1876,18 +1900,18 @@ QString QtVersionPrivate::qmakeProperty(const QByteArray &name, } FilePath QtVersionPrivate::mkspecDirectoryFromVersionInfo(const QHash &versionInfo, - const FilePath &qmakeCommand) + const FilePath &queryTool) { QString dataDir = qmakeProperty(versionInfo, "QT_HOST_DATA", PropertyVariantSrc); if (dataDir.isEmpty()) return FilePath(); - return FilePath::fromUserInput(dataDir + "/mkspecs").onDevice(qmakeCommand); + return FilePath::fromUserInput(dataDir + "/mkspecs").onDevice(queryTool); } FilePath QtVersionPrivate::mkspecFromVersionInfo(const QHash &versionInfo, - const FilePath &qmakeCommand) + const FilePath &queryTool) { - FilePath baseMkspecDir = mkspecDirectoryFromVersionInfo(versionInfo, qmakeCommand); + FilePath baseMkspecDir = mkspecDirectoryFromVersionInfo(versionInfo, queryTool); if (baseMkspecDir.isEmpty()) return FilePath(); @@ -2319,14 +2343,16 @@ void QtVersion::resetCache() const static QList g_qtVersionFactories; -QtVersion *QtVersionFactory::createQtVersionFromQMakePath - (const FilePath &qmakePath, bool isAutoDetected, const QString &detectionSource, QString *error) +QtVersion *QtVersionFactory::createQtVersionFromQueryToolPath(const FilePath &queryTool, + bool isAutoDetected, + const QString &detectionSource, + QString *error) { QHash versionInfo; - const Environment env = qmakePath.deviceEnvironment(); - if (!QtVersionPrivate::queryQMakeVariables(qmakePath, env, &versionInfo, error)) + const Environment env = queryTool.deviceEnvironment(); + if (!QtVersionPrivate::queryQtPaths(queryTool, env, &versionInfo, error)) return nullptr; - FilePath mkspec = QtVersionPrivate::mkspecFromVersionInfo(versionInfo, qmakePath); + FilePath mkspec = QtVersionPrivate::mkspecFromVersionInfo(versionInfo, queryTool); QMakeVfs vfs; QMakeGlobals globals; @@ -2342,7 +2368,7 @@ QtVersion *QtVersionFactory::createQtVersionFromQMakePath return l->m_priority > r->m_priority; }); - if (!qmakePath.isExecutableFile()) + if (!queryTool.isExecutableFile()) return nullptr; QtVersionFactory::SetupData setup; @@ -2355,8 +2381,8 @@ QtVersion *QtVersionFactory::createQtVersionFromQMakePath QtVersion *ver = factory->create(); QTC_ASSERT(ver, continue); ver->d->m_id = QtVersionManager::getUniqueId(); - QTC_CHECK(ver->d->m_qmakeCommand.isEmpty()); // Should only be used once. - ver->d->m_qmakeCommand = qmakePath; + QTC_CHECK(ver->d->m_queryTool.isEmpty()); // Should only be used once. + ver->d->m_queryTool = queryTool; ver->d->m_detectionSource = detectionSource; ver->d->m_isAutodetected = isAutoDetected; ver->updateDefaultDisplayName(); @@ -2367,7 +2393,7 @@ QtVersion *QtVersionFactory::createQtVersionFromQMakePath ProFileCacheManager::instance()->decRefCount(); if (error) { *error = QCoreApplication::translate("QtSupport::QtVersionFactory", - "No factory found for qmake: \"%1\"").arg(qmakePath.toUserOutput()); + "No factory found for query tool \"%1\"").arg(queryTool.toUserOutput()); } return nullptr; } diff --git a/src/plugins/qtsupport/baseqtversion.h b/src/plugins/qtsupport/baseqtversion.h index 269a6507168..f0400bc99b5 100644 --- a/src/plugins/qtsupport/baseqtversion.h +++ b/src/plugins/qtsupport/baseqtversion.h @@ -150,6 +150,8 @@ public: bool hasDocs() const; bool hasDemos() const; + Utils::FilePath queryToolFilePath() const; + // former local functions Utils::FilePath qmakeFilePath() const; diff --git a/src/plugins/qtsupport/qtkitinformation.cpp b/src/plugins/qtsupport/qtkitinformation.cpp index 43bbc07c1c3..672ad493df4 100644 --- a/src/plugins/qtsupport/qtkitinformation.cpp +++ b/src/plugins/qtsupport/qtkitinformation.cpp @@ -324,6 +324,11 @@ void QtKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) const QtVersion *version = qtVersion(kit); return version ? version->qmakeFilePath().path() : QString(); }); + expander->registerVariable("Qt:queryToolExecutable", tr("Path to the query tool executable"), + [kit]() -> QString { + QtVersion *version = qtVersion(kit); + return version ? version->queryToolFilePath().path() : QString(); + }); } Id QtKitAspect::id() diff --git a/src/plugins/qtsupport/qtoptionspage.cpp b/src/plugins/qtsupport/qtoptionspage.cpp index c0049058abd..06a8a6bbd52 100644 --- a/src/plugins/qtsupport/qtoptionspage.cpp +++ b/src/plugins/qtsupport/qtoptionspage.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -111,7 +112,7 @@ public: if (column == 0) return m_version->displayName(); if (column == 1) - return m_version->qmakeFilePath().toUserOutput(); + return m_version->queryToolFilePath().toUserOutput(); } if (role == Qt::FontRole && m_changed) { @@ -128,7 +129,8 @@ public: "
%2
"; return QString("
" + row.arg(tr("Qt Version"), m_version->qtVersionString()) - + row.arg(tr("Location of qmake"), m_version->qmakeFilePath().toUserOutput()) + + row.arg(tr("Location of the query tool"), + m_version->queryToolFilePath().toUserOutput()) + "
"); } @@ -600,27 +602,42 @@ QtOptionsPageWidget::~QtOptionsPageWidget() delete m_configurationWidget; } +static bool isIncompatibleQtPathsTool(const FilePath &tool) +{ + if (!tool.baseName().startsWith("qtpaths")) + return false; + QtcProcess process; + process.setTimeoutS(1); + process.setCommand({tool, {"-query"}}); + process.runBlocking(); + return process.result() != ProcessResult::FinishedWithSuccess; +} + void QtOptionsPageWidget::addQtDir() { - FilePath qtVersion = FileUtils::getOpenFilePath(this, - tr("Select a qmake Executable"), - {}, - BuildableHelperLibrary::filterForQmakeFileDialog(), - 0, - QFileDialog::DontResolveSymlinks); - if (qtVersion.isEmpty()) + FilePath qtQueryTool = + FileUtils::getOpenFilePath(this, + tr("Select a qmake or qtpaths Executable"), + {}, + BuildableHelperLibrary::filterForQtQueryToolsFileDialog(), + 0, + QFileDialog::DontResolveSymlinks); + if (qtQueryTool.isEmpty()) return; - // should add all qt versions here ? - if (BuildableHelperLibrary::isQtChooser(qtVersion)) - qtVersion = BuildableHelperLibrary::qtChooserToQmakePath(qtVersion.symLinkTarget()); + if (isIncompatibleQtPathsTool(qtQueryTool)) + qtQueryTool = qtQueryTool.parentDir() / HostOsInfo::withExecutableSuffix("qmake"); - auto checkAlreadyExists = [qtVersion](TreeItem *parent) { + // should add all qt versions here ? + if (BuildableHelperLibrary::isQtChooser(qtQueryTool)) + qtQueryTool = BuildableHelperLibrary::qtChooserToQueryToolPath(qtQueryTool.symLinkTarget()); + + auto checkAlreadyExists = [qtQueryTool](TreeItem *parent) { for (int i = 0; i < parent->childCount(); ++i) { auto item = static_cast(parent->childAt(i)); - if (item->version()->qmakeFilePath() == qtVersion) { + // Compare parent dirs, since it could be either qmake or qtpaths + if (item->version()->queryToolFilePath().parentDir() == qtQueryTool.parentDir()) return std::make_pair(true, item->version()->displayName()); - } } return std::make_pair(false, QString()); }; @@ -640,7 +657,8 @@ void QtOptionsPageWidget::addQtDir() } QString error; - QtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion, false, QString(), &error); + QtVersion *version = QtVersionFactory::createQtVersionFromQueryToolPath(qtQueryTool, false, + QString(), &error); if (version) { auto item = new QtVersionItem(version); item->setIcon(version->isValid()? m_validVersionIcon : m_invalidVersionIcon); @@ -650,8 +668,9 @@ void QtOptionsPageWidget::addQtDir() m_versionUi.nameEdit->setFocus(); m_versionUi.nameEdit->selectAll(); } else { - QMessageBox::warning(this, tr("Qmake Not Executable"), - tr("The qmake executable %1 could not be added: %2").arg(qtVersion.toUserOutput()).arg(error)); + QMessageBox::warning(this, tr("Not Executable"), + tr("The executable %1 could not be added: %2").arg( + qtQueryTool.toUserOutput()).arg(error)); return; } updateCleanUpButton(); @@ -671,16 +690,16 @@ void QtOptionsPageWidget::removeQtDir() void QtOptionsPageWidget::editPath() { QtVersion *current = currentVersion(); - FilePath qtVersion = + const FilePath queryTool = FileUtils::getOpenFilePath(this, - tr("Select a qmake Executable"), - current->qmakeFilePath().absolutePath(), - BuildableHelperLibrary::filterForQmakeFileDialog(), + tr("Select a qmake or qtpaths Executable"), + current->queryToolFilePath().absolutePath(), + BuildableHelperLibrary::filterForQtQueryToolsFileDialog(), nullptr, QFileDialog::DontResolveSymlinks); - if (qtVersion.isEmpty()) + if (queryTool.isEmpty()) return; - QtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion); + QtVersion *version = QtVersionFactory::createQtVersionFromQueryToolPath(queryTool); if (!version) return; // Same type? then replace! @@ -768,7 +787,7 @@ void QtOptionsPageWidget::updateWidgets() QtVersion *version = currentVersion(); if (version) { m_versionUi.nameEdit->setText(version->unexpandedDisplayName()); - m_versionUi.qmakePath->setText(version->qmakeFilePath().toUserOutput()); + m_versionUi.queryToolPath->setText(version->queryToolFilePath().toUserOutput()); m_configurationWidget = version->createConfigurationWidget(); if (m_configurationWidget) { m_versionUi.formLayout->addRow(m_configurationWidget); @@ -778,7 +797,7 @@ void QtOptionsPageWidget::updateWidgets() } } else { m_versionUi.nameEdit->clear(); - m_versionUi.qmakePath->clear(); + m_versionUi.queryToolPath->clear(); } const bool enabled = version != nullptr; diff --git a/src/plugins/qtsupport/qtprojectimporter.cpp b/src/plugins/qtsupport/qtprojectimporter.cpp index 9014aa71f08..fc44f3b7d37 100644 --- a/src/plugins/qtsupport/qtprojectimporter.cpp +++ b/src/plugins/qtsupport/qtprojectimporter.cpp @@ -57,7 +57,7 @@ QtProjectImporter::QtVersionData QtProjectImporter::findOrCreateQtVersion(const Utils::FilePath &qmakePath) const { QtVersionData result; - result.qt = QtVersionManager::version(Utils::equal(&QtVersion::qmakeFilePath, qmakePath)); + result.qt = QtVersionManager::version(Utils::equal(&QtVersion::queryToolFilePath, qmakePath)); if (result.qt) { // Check if version is a temporary qt const int qtId = result.qt->uniqueId(); @@ -67,7 +67,7 @@ QtProjectImporter::findOrCreateQtVersion(const Utils::FilePath &qmakePath) const // Create a new version if not found: // Do not use the canonical path here... - result.qt = QtVersionFactory::createQtVersionFromQMakePath(qmakePath); + result.qt = QtVersionFactory::createQtVersionFromQueryToolPath(qmakePath); result.isTemporary = true; if (result.qt) { UpdateGuard guard(*this); @@ -281,7 +281,7 @@ static QStringList additionalFilesToCopy(const QtVersion *qt) } else if (HostOsInfo::isWindowsHost()) { const QString release = QString("bin/Qt%1Core.dll").arg(major); const QString debug = QString("bin/Qt%1Cored.dll").arg(major); - const FilePath base = qt->qmakeFilePath().parentDir().parentDir(); + const FilePath base = qt->queryToolFilePath().parentDir().parentDir(); if (base.pathAppended(release).exists()) return {release}; if (base.pathAppended(debug).exists()) @@ -289,7 +289,7 @@ static QStringList additionalFilesToCopy(const QtVersion *qt) return {release}; } else if (HostOsInfo::isLinuxHost()) { const QString core = QString("lib/libQt%1Core.so.%1").arg(major); - const QDir base(qt->qmakeFilePath().parentDir().parentDir().pathAppended("lib").toString()); + const QDir base(qt->queryToolFilePath().parentDir().parentDir().pathAppended("lib").toString()); const QStringList icuLibs = Utils::transform(base.entryList({"libicu*.so.*"}), [](const QString &lib) { return QString("lib/" + lib); }); return QStringList(core) + icuLibs; } @@ -300,7 +300,7 @@ static QStringList additionalFilesToCopy(const QtVersion *qt) static Utils::FilePath setupQmake(const QtVersion *qt, const QString &path) { // This is a hack and only works with local, "standard" installations of Qt - const FilePath qmake = qt->qmakeFilePath().canonicalPath(); + const FilePath qmake = qt->queryToolFilePath().canonicalPath(); const QString qmakeFile = "bin/" + qmake.fileName(); const FilePath source = qmake.parentDir().parentDir(); const FilePath target = FilePath::fromString(path); diff --git a/src/plugins/qtsupport/qtversionfactory.h b/src/plugins/qtsupport/qtversionfactory.h index bc01ad7601a..96bf8e2088a 100644 --- a/src/plugins/qtsupport/qtversionfactory.h +++ b/src/plugins/qtsupport/qtversionfactory.h @@ -51,7 +51,7 @@ public: /// the desktop factory claims to handle all paths int priority() const { return m_priority; } - static QtVersion *createQtVersionFromQMakePath(const Utils::FilePath &qmakePath, + static QtVersion *createQtVersionFromQueryToolPath(const Utils::FilePath &qmakePath, bool isAutoDetected = false, const QString &detectionSource = {}, QString *error = nullptr); diff --git a/src/plugins/qtsupport/qtversioninfo.ui b/src/plugins/qtsupport/qtversioninfo.ui index 467e898a20e..815be818177 100644 --- a/src/plugins/qtsupport/qtversioninfo.ui +++ b/src/plugins/qtsupport/qtversioninfo.ui @@ -39,14 +39,14 @@ - qmake path: + Query tool path: - + 0 diff --git a/src/plugins/qtsupport/qtversionmanager.cpp b/src/plugins/qtsupport/qtversionmanager.cpp index 2ca6c3a1237..c5f00e96189 100644 --- a/src/plugins/qtsupport/qtversionmanager.cpp +++ b/src/plugins/qtsupport/qtversionmanager.cpp @@ -265,7 +265,8 @@ void QtVersionManager::updateFromInstaller(bool emitSignal) if (log().isDebugEnabled()) { qCDebug(log) << "======= Existing Qt versions ======="; for (QtVersion *version : qAsConst(m_versions)) { - qCDebug(log) << version->qmakeFilePath().toUserOutput() << "id:"<uniqueId(); + qCDebug(log) << version->queryToolFilePath().toUserOutput() + << "id:" <uniqueId(); qCDebug(log) << " autodetection source:" << version->detectionSource(); qCDebug(log) << ""; } @@ -341,7 +342,8 @@ void QtVersionManager::updateFromInstaller(bool emitSignal) if (log().isDebugEnabled()) { qCDebug(log) << "======= Before removing outdated sdk versions ======="; for (QtVersion *version : qAsConst(m_versions)) { - qCDebug(log) << version->qmakeFilePath().toUserOutput() << "id:" << version->uniqueId(); + qCDebug(log) << version->queryToolFilePath().toUserOutput() + << "id:" << version->uniqueId(); qCDebug(log) << " autodetection source:" << version->detectionSource(); qCDebug(log) << ""; } @@ -360,7 +362,8 @@ void QtVersionManager::updateFromInstaller(bool emitSignal) if (log().isDebugEnabled()) { qCDebug(log)<< "======= End result ======="; for (QtVersion *version : qAsConst(m_versions)) { - qCDebug(log) << version->qmakeFilePath().toUserOutput() << "id:" << version->uniqueId(); + qCDebug(log) << version->queryToolFilePath().toUserOutput() + << "id:" << version->uniqueId(); qCDebug(log) << " autodetection source:" << version->detectionSource(); qCDebug(log) << ""; } @@ -401,14 +404,20 @@ static QList runQtChooser(const QString &qtchooser, const QStringLis } // Asks qtchooser for the qmake path of a given version +// TODO: Extend to qtpaths if qtchooser is also used for Qt 6 static QString qmakePath(const QString &qtchooser, const QString &version) { const QList outputs = runQtChooser(qtchooser, {QStringLiteral("-qt=%1").arg(version), QStringLiteral("-print-env")}); + // Exemplary output of "qtchooser -qt=qt5-x86_64-linux-gnu -print-env": + // QT_SELECT="qt5-x86_64-linux-gnu" + // QTTOOLDIR="/usr/lib/qt5/bin" + // QTLIBDIR="/usr/lib/x86_64-linux-gnu" + const QByteArray qtToolDirPrefix("QTTOOLDIR=\""); for (const QByteArray &output : outputs) { - if (output.startsWith("QTTOOLDIR=\"")) { - QByteArray withoutVarName = output.mid(11); // remove QTTOOLDIR=" + if (output.startsWith(qtToolDirPrefix)) { + QByteArray withoutVarName = output.mid(qtToolDirPrefix.size()); // remove QTTOOLDIR=" withoutVarName.chop(1); // remove trailing quote return QStandardPaths::findExecutable(QStringLiteral("qmake"), QStringList() << QString::fromLocal8Bit(withoutVarName)); @@ -424,6 +433,15 @@ static FilePaths gatherQmakePathsFromQtChooser() return FilePaths(); const QList versions = runQtChooser(qtchooser, QStringList("-l")); + // Exemplary output of "qtchooser -l": + // 4 + // 5 + // default + // qt4-x86_64-linux-gnu + // qt4 + // qt5-x86_64-linux-gnu + // qt5 + // "" QSet foundQMakes; for (const QByteArray &version : versions) { FilePath possibleQMake = FilePath::fromString( @@ -436,19 +454,20 @@ static FilePaths gatherQmakePathsFromQtChooser() static void findSystemQt() { - FilePaths systemQMakes + FilePaths systemQueryTools = BuildableHelperLibrary::findQtsInEnvironment(Environment::systemEnvironment()); - systemQMakes.append(gatherQmakePathsFromQtChooser()); - for (const FilePath &qmakePath : qAsConst(systemQMakes)) { - if (BuildableHelperLibrary::isQtChooser(qmakePath)) + systemQueryTools.append(gatherQmakePathsFromQtChooser()); + for (const FilePath &queryToolPath : qAsConst(systemQueryTools)) { + if (BuildableHelperLibrary::isQtChooser(queryToolPath)) continue; - const auto isSameQmake = [qmakePath](const QtVersion *version) { + const auto isSameQueryTool = [queryToolPath](const QtVersion *version) { return Environment::systemEnvironment(). - isSameExecutable(qmakePath.toString(), version->qmakeFilePath().toString()); + isSameExecutable(queryToolPath.toString(), + version->queryToolFilePath().toString()); }; - if (contains(m_versions, isSameQmake)) + if (contains(m_versions, isSameQueryTool)) continue; - QtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qmakePath, + QtVersion *version = QtVersionFactory::createQtVersionFromQueryToolPath(queryToolPath, false, "PATH"); if (version) diff --git a/src/plugins/remotelinux/CMakeLists.txt b/src/plugins/remotelinux/CMakeLists.txt index 9087867ccec..1572a39d54d 100644 --- a/src/plugins/remotelinux/CMakeLists.txt +++ b/src/plugins/remotelinux/CMakeLists.txt @@ -5,15 +5,14 @@ add_qtc_plugin(RemoteLinux abstractpackagingstep.cpp abstractpackagingstep.h abstractremotelinuxdeployservice.cpp abstractremotelinuxdeployservice.h abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h - abstractuploadandinstallpackageservice.cpp abstractuploadandinstallpackageservice.h deploymenttimeinfo.cpp deploymenttimeinfo.h - filetransfer.h genericdirectuploadservice.cpp genericdirectuploadservice.h genericdirectuploadstep.cpp genericdirectuploadstep.h genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h genericlinuxdeviceconfigurationwidget.ui genericlinuxdeviceconfigurationwizard.cpp genericlinuxdeviceconfigurationwizard.h genericlinuxdeviceconfigurationwizardpages.cpp genericlinuxdeviceconfigurationwizardpages.h genericlinuxdeviceconfigurationwizardsetuppage.ui + killappstep.cpp killappstep.h linuxdevice.cpp linuxdevice.h linuxdevicetester.cpp linuxdevicetester.h linuxprocessinterface.h @@ -30,8 +29,6 @@ add_qtc_plugin(RemoteLinux remotelinuxenvironmentaspect.cpp remotelinuxenvironmentaspect.h remotelinuxenvironmentaspectwidget.cpp remotelinuxenvironmentaspectwidget.h remotelinuxenvironmentreader.cpp remotelinuxenvironmentreader.h - remotelinuxkillappservice.cpp remotelinuxkillappservice.h - remotelinuxkillappstep.cpp remotelinuxkillappstep.h remotelinuxpackageinstaller.cpp remotelinuxpackageinstaller.h remotelinuxplugin.cpp remotelinuxplugin.h remotelinuxqmltoolingsupport.cpp remotelinuxqmltoolingsupport.h diff --git a/src/plugins/remotelinux/abstractremotelinuxdeployservice.cpp b/src/plugins/remotelinux/abstractremotelinuxdeployservice.cpp index cdecdaf6c63..f4ff1cef251 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeployservice.cpp +++ b/src/plugins/remotelinux/abstractremotelinuxdeployservice.cpp @@ -43,7 +43,7 @@ namespace RemoteLinux { namespace Internal { namespace { -enum State { Inactive, SettingUpDevice, Deploying }; +enum State { Inactive, Deploying }; } // anonymous namespace class AbstractRemoteLinuxDeployServicePrivate @@ -131,8 +131,8 @@ void AbstractRemoteLinuxDeployService::start() return; } - d->state = SettingUpDevice; - doDeviceSetup(); + d->state = Deploying; + doDeploy(); } void AbstractRemoteLinuxDeployService::stop() @@ -140,17 +140,9 @@ void AbstractRemoteLinuxDeployService::stop() if (d->stopRequested) return; - switch (d->state) { - case Inactive: - break; - case SettingUpDevice: - d->stopRequested = true; - stopDeviceSetup(); - break; - case Deploying: + if (d->state == Deploying) { d->stopRequested = true; stopDeployment(); - break; } } @@ -171,19 +163,6 @@ void AbstractRemoteLinuxDeployService::importDeployTimes(const QVariantMap &map) d->deployTimes.importDeployTimes(map); } -void AbstractRemoteLinuxDeployService::handleDeviceSetupDone(bool success) -{ - QTC_ASSERT(d->state == SettingUpDevice, return); - - if (!success || d->stopRequested) { - setFinished(); - return; - } - - d->state = Deploying; - doDeploy(); -} - void AbstractRemoteLinuxDeployService::handleDeploymentDone() { QTC_ASSERT(d->state == Deploying, return); diff --git a/src/plugins/remotelinux/abstractremotelinuxdeployservice.h b/src/plugins/remotelinux/abstractremotelinuxdeployservice.h index c4192d10a5e..5590d879d03 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeployservice.h +++ b/src/plugins/remotelinux/abstractremotelinuxdeployservice.h @@ -96,13 +96,8 @@ protected: bool hasRemoteFileChanged(const ProjectExplorer::DeployableFile &deployableFile, const QDateTime &remoteTimestamp) const; - void handleDeviceSetupDone(bool success); void handleDeploymentDone(); - // Should do things needed *before* connecting. Call default implementation afterwards. - virtual void doDeviceSetup() { handleDeviceSetupDone(true); } - virtual void stopDeviceSetup() { handleDeviceSetupDone(false); } - void setFinished(); private: diff --git a/src/plugins/remotelinux/abstractuploadandinstallpackageservice.cpp b/src/plugins/remotelinux/abstractuploadandinstallpackageservice.cpp deleted file mode 100644 index 1326c484301..00000000000 --- a/src/plugins/remotelinux/abstractuploadandinstallpackageservice.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#include "abstractuploadandinstallpackageservice.h" - -#include "filetransfer.h" -#include "remotelinuxpackageinstaller.h" - -#include -#include -#include -#include - -#include - -using namespace ProjectExplorer; -using namespace Utils; - -namespace RemoteLinux { -namespace Internal { -namespace { -enum State { Inactive, Uploading, Installing }; -} // anonymous namespace - -class AbstractUploadAndInstallPackageServicePrivate -{ -public: - State state = Inactive; - FileTransfer uploader; - Utils::FilePath packageFilePath; -}; - -} // namespace Internal - -using namespace Internal; - -AbstractUploadAndInstallPackageService::AbstractUploadAndInstallPackageService() - : d(new AbstractUploadAndInstallPackageServicePrivate) -{ - connect(&d->uploader, &FileTransfer::done, this, - &AbstractUploadAndInstallPackageService::handleUploadFinished); - connect(&d->uploader, &FileTransfer::progress, this, - &AbstractUploadAndInstallPackageService::progressMessage); -} - -AbstractUploadAndInstallPackageService::~AbstractUploadAndInstallPackageService() -{ - delete d; -} - -void AbstractUploadAndInstallPackageService::setPackageFilePath(const FilePath &filePath) -{ - d->packageFilePath = filePath; -} - -QString AbstractUploadAndInstallPackageService::uploadDir() const -{ - return QLatin1String("/tmp"); -} - -bool AbstractUploadAndInstallPackageService::isDeploymentNecessary() const -{ - return hasLocalFileChanged(DeployableFile(d->packageFilePath, QString())); -} - -void AbstractUploadAndInstallPackageService::doDeviceSetup() -{ - QTC_ASSERT(d->state == Inactive, return); - AbstractRemoteLinuxDeployService::doDeviceSetup(); -} - -void AbstractUploadAndInstallPackageService::stopDeviceSetup() -{ - QTC_ASSERT(d->state == Inactive, return); - AbstractRemoteLinuxDeployService::stopDeviceSetup(); -} - -void AbstractUploadAndInstallPackageService::doDeploy() -{ - QTC_ASSERT(d->state == Inactive, return); - - d->state = Uploading; - - const QString remoteFilePath = uploadDir() + QLatin1Char('/') + d->packageFilePath.fileName(); - const FilesToTransfer files {{d->packageFilePath, - deviceConfiguration()->filePath(remoteFilePath)}}; - d->uploader.setDevice(deviceConfiguration()); - d->uploader.setFilesToTransfer(files); - d->uploader.start(); -} - -void AbstractUploadAndInstallPackageService::stopDeployment() -{ - switch (d->state) { - case Inactive: - qWarning("%s: Unexpected state 'Inactive'.", Q_FUNC_INFO); - break; - case Uploading: - d->uploader.stop(); - setFinished(); - break; - case Installing: - packageInstaller()->cancelInstallation(); - setFinished(); - break; - } -} - -void AbstractUploadAndInstallPackageService::handleUploadFinished(const ProcessResultData &resultData) -{ - QTC_ASSERT(d->state == Uploading, return); - - if (resultData.m_error != QProcess::UnknownError) { - emit errorMessage(resultData.m_errorString); - setFinished(); - return; - } - - emit progressMessage(tr("Successfully uploaded package file.")); - const QString remoteFilePath = uploadDir() + '/' + d->packageFilePath.fileName(); - d->state = Installing; - emit progressMessage(tr("Installing package to device...")); - connect(packageInstaller(), &AbstractRemoteLinuxPackageInstaller::stdoutData, - this, &AbstractRemoteLinuxDeployService::stdOutData); - connect(packageInstaller(), &AbstractRemoteLinuxPackageInstaller::stderrData, - this, &AbstractRemoteLinuxDeployService::stdErrData); - connect(packageInstaller(), &AbstractRemoteLinuxPackageInstaller::finished, - this, &AbstractUploadAndInstallPackageService::handleInstallationFinished); - packageInstaller()->installPackage(deviceConfiguration(), remoteFilePath, true); -} - -void AbstractUploadAndInstallPackageService::handleInstallationFinished(const QString &errorMsg) -{ - QTC_ASSERT(d->state == Installing, return); - - if (errorMsg.isEmpty()) { - saveDeploymentTimeStamp(DeployableFile(d->packageFilePath, QString()), QDateTime()); - emit progressMessage(tr("Package installed.")); - } else { - emit errorMessage(errorMsg); - } - setFinished(); -} - -void AbstractUploadAndInstallPackageService::setFinished() -{ - d->state = Inactive; - d->uploader.stop(); - disconnect(packageInstaller(), nullptr, this, nullptr); - handleDeploymentDone(); -} - -} // namespace RemoteLinux diff --git a/src/plugins/remotelinux/filesystemaccess_test.cpp b/src/plugins/remotelinux/filesystemaccess_test.cpp index ca27238b997..c0bb80cf995 100644 --- a/src/plugins/remotelinux/filesystemaccess_test.cpp +++ b/src/plugins/remotelinux/filesystemaccess_test.cpp @@ -25,10 +25,10 @@ #include "filesystemaccess_test.h" -#include "filetransfer.h" #include "linuxdevice.h" #include +#include #include #include #include @@ -39,6 +39,8 @@ #include #include +Q_DECLARE_METATYPE(ProjectExplorer::FileTransferMethod) + using namespace ProjectExplorer; using namespace Utils; @@ -230,7 +232,6 @@ void FileSystemAccessTest::testFileTransfer() FileTransfer fileTransfer; fileTransfer.setTransferMethod(fileTransferMethod); - fileTransfer.setDevice(m_device); // Create and upload 1000 small files and one big file QTemporaryDir dirForFilesToUpload; diff --git a/src/plugins/remotelinux/genericdirectuploadservice.cpp b/src/plugins/remotelinux/genericdirectuploadservice.cpp index 076a9027012..f4aed62bfd6 100644 --- a/src/plugins/remotelinux/genericdirectuploadservice.cpp +++ b/src/plugins/remotelinux/genericdirectuploadservice.cpp @@ -25,9 +25,8 @@ #include "genericdirectuploadservice.h" -#include "filetransfer.h" - #include +#include #include #include #include @@ -129,18 +128,6 @@ bool GenericDirectUploadService::isDeploymentNecessary() const return !d->deployableFiles.isEmpty(); } -void GenericDirectUploadService::doDeviceSetup() -{ - QTC_ASSERT(d->state == Inactive, return); - AbstractRemoteLinuxDeployService::doDeviceSetup(); -} - -void GenericDirectUploadService::stopDeviceSetup() -{ - QTC_ASSERT(d->state == Inactive, return); - AbstractRemoteLinuxDeployService::stopDeviceSetup(); -} - void GenericDirectUploadService::doDeploy() { QTC_ASSERT(d->state == Inactive, setFinished(); return); @@ -149,21 +136,27 @@ void GenericDirectUploadService::doDeploy() } QDateTime GenericDirectUploadService::timestampFromStat(const DeployableFile &file, - QtcProcess *statProc, - const QString &errorMsg) + QtcProcess *statProc) { - QString errorDetails; - if (!errorMsg.isEmpty()) - errorDetails = errorMsg; - else if (statProc->exitCode() != 0) - errorDetails = QString::fromUtf8(statProc->readAllStandardError()); - if (!errorDetails.isEmpty()) { + bool succeeded = false; + QString error; + if (statProc->error() == QProcess::FailedToStart) { + error = tr("Failed to start \"stat\": %1").arg(statProc->errorString()); + } else if (statProc->exitStatus() == QProcess::CrashExit) { + error = tr("\"stat\" crashed."); + } else if (statProc->exitCode() != 0) { + error = tr("\"stat\" failed with exit code %1: %2") + .arg(statProc->exitCode()).arg(statProc->stdErr()); + } else { + succeeded = true; + } + if (!succeeded) { emit warningMessage(tr("Failed to retrieve remote timestamp for file \"%1\". " "Incremental deployment will not work. Error message was: %2") - .arg(file.remoteFilePath(), errorDetails)); + .arg(file.remoteFilePath(), error)); return QDateTime(); } - QByteArray output = statProc->readAllStandardOutput().trimmed(); + const QByteArray output = statProc->readAllStandardOutput().trimmed(); const QString warningString(tr("Unexpected stat output for remote file \"%1\": %2") .arg(file.remoteFilePath()).arg(QString::fromUtf8(output))); if (!output.startsWith(file.remoteFilePath().toUtf8())) { @@ -218,7 +211,7 @@ void GenericDirectUploadService::runStat(const DeployableFile &file) QTC_ASSERT(d->state == state, return); const DeployableFile file = d->getFileForProcess(statProc); QTC_ASSERT(file.isValid(), return); - const QDateTime timestamp = timestampFromStat(file, statProc, statProc->errorString()); + const QDateTime timestamp = timestampFromStat(file, statProc); statProc->deleteLater(); switch (state) { case PreChecking: @@ -322,7 +315,6 @@ void GenericDirectUploadService::uploadFiles() deviceConfiguration()->filePath(file.remoteFilePath())}); } - d->uploader.setDevice(deviceConfiguration()); d->uploader.setFilesToTransfer(files); d->uploader.start(); } diff --git a/src/plugins/remotelinux/genericdirectuploadservice.h b/src/plugins/remotelinux/genericdirectuploadservice.h index be7eda62610..ffbd9fc866d 100644 --- a/src/plugins/remotelinux/genericdirectuploadservice.h +++ b/src/plugins/remotelinux/genericdirectuploadservice.h @@ -55,16 +55,13 @@ public: protected: bool isDeploymentNecessary() const override; - void doDeviceSetup() override; - void stopDeviceSetup() override; - void doDeploy() override; void stopDeployment() override; private: void runStat(const ProjectExplorer::DeployableFile &file); QDateTime timestampFromStat(const ProjectExplorer::DeployableFile &file, - Utils::QtcProcess *statProc, const QString &errorMsg); + Utils::QtcProcess *statProc); void checkForStateChangeOnRemoteProcFinished(); QList collectFilesToUpload( diff --git a/src/plugins/remotelinux/killappstep.cpp b/src/plugins/remotelinux/killappstep.cpp new file mode 100644 index 00000000000..cb5bc06a79a --- /dev/null +++ b/src/plugins/remotelinux/killappstep.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "killappstep.h" + +#include "remotelinux_constants.h" + +#include +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace RemoteLinux { +namespace Internal { + +class KillAppService : public AbstractRemoteLinuxDeployService +{ + Q_OBJECT +public: + ~KillAppService() override; + + void setRemoteExecutable(const QString &filePath); + +private: + void handleStdErr(); + void handleProcessFinished(); + + bool isDeploymentNecessary() const override; + + void doDeploy() override; + void stopDeployment() override; + + void handleSignalOpFinished(const QString &errorMessage); + void cleanup(); + void finishDeployment(); + + QString m_remoteExecutable; + ProjectExplorer::DeviceProcessSignalOperation::Ptr m_signalOperation; +}; + +KillAppService::~KillAppService() +{ + cleanup(); +} + +void KillAppService::setRemoteExecutable(const QString &filePath) +{ + m_remoteExecutable = filePath; +} + +bool KillAppService::isDeploymentNecessary() const +{ + return !m_remoteExecutable.isEmpty(); +} + +void KillAppService::doDeploy() +{ + m_signalOperation = deviceConfiguration()->signalOperation(); + if (!m_signalOperation) { + handleDeploymentDone(); + return; + } + connect(m_signalOperation.data(), &ProjectExplorer::DeviceProcessSignalOperation::finished, + this, &KillAppService::handleSignalOpFinished); + emit progressMessage(tr("Trying to kill \"%1\" on remote device...").arg(m_remoteExecutable)); + m_signalOperation->killProcess(m_remoteExecutable); +} + +void KillAppService::cleanup() +{ + if (m_signalOperation) { + disconnect(m_signalOperation.data(), nullptr, this, nullptr); + m_signalOperation.clear(); + } +} + +void KillAppService::finishDeployment() +{ + cleanup(); + handleDeploymentDone(); +} + +void KillAppService::stopDeployment() +{ + finishDeployment(); +} + +void KillAppService::handleSignalOpFinished(const QString &errorMessage) +{ + if (errorMessage.isEmpty()) + emit progressMessage(tr("Remote application killed.")); + else + emit progressMessage(tr("Failed to kill remote application. Assuming it was not running.")); + finishDeployment(); +} + +} // namespace Internal + +KillAppStep::KillAppStep(BuildStepList *bsl, Id id) + : AbstractRemoteLinuxDeployStep(bsl, id) +{ + auto service = createDeployService(); + + setWidgetExpandedByDefault(false); + + setInternalInitializer([this, service] { + Target * const theTarget = target(); + QTC_ASSERT(theTarget, return CheckResult::failure()); + RunConfiguration * const rc = theTarget->activeRunConfiguration(); + const QString remoteExe = rc ? rc->runnable().command.executable().toString() : QString(); + service->setRemoteExecutable(remoteExe); + return CheckResult::success(); + }); +} + +Id KillAppStep::stepId() +{ + return Constants::KillAppStepId; +} + +QString KillAppStep::displayName() +{ + return tr("Kill current application instance"); +} + +} // namespace RemoteLinux + +#include "killappstep.moc" diff --git a/src/plugins/remotelinux/remotelinuxkillappstep.h b/src/plugins/remotelinux/killappstep.h similarity index 87% rename from src/plugins/remotelinux/remotelinuxkillappstep.h rename to src/plugins/remotelinux/killappstep.h index 506bc99d817..62fbc81643c 100644 --- a/src/plugins/remotelinux/remotelinuxkillappstep.h +++ b/src/plugins/remotelinux/killappstep.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -29,11 +29,11 @@ namespace RemoteLinux { -class REMOTELINUX_EXPORT RemoteLinuxKillAppStep : public AbstractRemoteLinuxDeployStep +class REMOTELINUX_EXPORT KillAppStep : public AbstractRemoteLinuxDeployStep { Q_OBJECT public: - explicit RemoteLinuxKillAppStep(ProjectExplorer::BuildStepList *bsl, + explicit KillAppStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id = stepId()); static Utils::Id stepId(); diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index b0881625023..93b93ca416f 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -25,7 +25,6 @@ #include "linuxdevice.h" -#include "filetransfer.h" #include "genericlinuxdeviceconfigurationwidget.h" #include "genericlinuxdeviceconfigurationwizard.h" #include "linuxdevicetester.h" @@ -39,6 +38,8 @@ #include #include +#include +#include #include #include #include @@ -101,10 +102,6 @@ public: QTC_ASSERT(m_masterSocketDir, return QString()); return m_masterSocketDir->path() + "/cs"; } - QStringList connectionOptions(const Utils::FilePath &binary) const - { - return m_sshParameters.connectionOptions(binary) << "-o" << ("ControlPath=" + socketFilePath()); - } signals: void connected(const QString &socketFilePath); @@ -117,8 +114,7 @@ private: void emitError(QProcess::ProcessError processError, const QString &errorString); void emitDisconnected(); QString fullProcessError() const; - QStringList connectionArgs(const FilePath &binary) const - { return connectionOptions(binary) << m_sshParameters.host(); } + QStringList connectionArgs(const FilePath &binary) const; const SshParameters m_sshParameters; std::unique_ptr m_masterProcess; @@ -272,6 +268,12 @@ QString SshSharedConnection::fullProcessError() const return allErrors.join('\n'); } +QStringList SshSharedConnection::connectionArgs(const FilePath &binary) const +{ + return m_sshParameters.connectionOptions(binary) << "-o" << ("ControlPath=" + socketFilePath()) + << m_sshParameters.host(); +} + // SshConnectionHandle class SshConnectionHandle : public QObject @@ -446,6 +448,11 @@ void SshProcessInterface::handleStarted(qint64 processId) emitStarted(processId); } +void SshProcessInterface::handleDone(const ProcessResultData &resultData) +{ + emit done(resultData); +} + void SshProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData) { emit readyRead(outputData, {}); @@ -578,6 +585,20 @@ void LinuxProcessInterface::handleStarted(qint64 processId) emitStarted(processId); } +void LinuxProcessInterface::handleDone(const ProcessResultData &resultData) +{ + ProcessResultData finalData = resultData; + if (!m_pidParsed) { + finalData.m_error = QProcess::FailedToStart; + if (!m_error.isEmpty()) { + if (!finalData.m_errorString.isEmpty()) + finalData.m_errorString += "\n"; + finalData.m_errorString += QString::fromLocal8Bit(m_error); + } + } + emit done(finalData); +} + void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData) { if (m_pidParsed) { @@ -688,8 +709,9 @@ void SshProcessInterfacePrivate::handleStarted() void SshProcessInterfacePrivate::handleDone() { - m_connectionHandle.reset(); - emit q->done(m_process.resultData()); + if (m_connectionHandle) // TODO: should it disconnect from signals first? + m_connectionHandle.release()->deleteLater(); + q->handleDone(m_process.resultData()); } void SshProcessInterfacePrivate::handleReadyReadStandardOutput() @@ -804,11 +826,30 @@ public: m_shell->setWriteData("echo\n"); m_shell->start(); - if (!m_shell->waitForStarted() || !m_shell->waitForReadyRead() - || m_shell->readAllStandardOutput() != "\n") { + auto failed = [this] { closeShell(); qCDebug(linuxDeviceLog) << "Failed to connect to" << m_displaylessSshParameters.host(); return false; + }; + + QDeadlineTimer timer(30000); + if (!m_shell->waitForStarted(timer.remainingTime())) + return failed(); + + while (true) { + if (!m_shell->waitForReadyRead(timer.remainingTime())) + return failed(); + + const QByteArray output = m_shell->readAllStandardOutput(); + if (output == "\n") + break; // expected output from echo + if (output.size() > 0) + return failed(); // other unidentified output + + // In case of trying to run a shell using SSH_ASKPASS, it may happen + // that we receive ready read signal but for error channel, while output + // channel still is empty. In this case we wait in loop until the user + // provides the right password, otherwise we timeout after 30 seconds. } return true; } @@ -1397,41 +1438,6 @@ bool LinuxDevice::writeFileContents(const FilePath &filePath, const QByteArray & return d->runInShell({"dd", {"of=" + filePath.path()}}, data); } -TransferDirection FileToTransfer::transferDirection() const -{ - if (m_source.needsDevice() == m_target.needsDevice()) - return TransferDirection::Invalid; - return m_source.needsDevice() ? TransferDirection::Download : TransferDirection::Upload; -} - -static TransferDirection transferDirection(const FilesToTransfer &files) -{ - if (files.isEmpty()) - return TransferDirection::Invalid; - const TransferDirection transferDirection = files.first().transferDirection(); - for (const FileToTransfer &file : files) { - if (file.transferDirection() != transferDirection) - return TransferDirection::Invalid; - } - return transferDirection; -} - -static bool isDeviceMatched(const FilePath &file, const QString &id) -{ - return (file.scheme() == "device") && (file.host() == id); -} - -static bool isDeviceMatched(const FilesToTransfer &files, const QString &id) -{ - for (const FileToTransfer &file : files) { - if (file.transferDirection() == TransferDirection::Upload && !isDeviceMatched(file.m_target, id)) - return false; - if (file.transferDirection() == TransferDirection::Download && !isDeviceMatched(file.m_source, id)) - return false; - } - return true; -} - static FilePaths dirsToCreate(const FilesToTransfer &files) { FilePaths dirs; @@ -1448,47 +1454,33 @@ static FilePaths dirsToCreate(const FilesToTransfer &files) return dirs; } -static QByteArray transferCommand(const TransferDirection transferDirection, bool link) +static QByteArray transferCommand(const FileTransferDirection direction, bool link) { - if (transferDirection == TransferDirection::Upload) + if (direction == FileTransferDirection::Upload) return link ? "ln -s" : "put"; - if (transferDirection == TransferDirection::Download) + if (direction == FileTransferDirection::Download) return "get"; return {}; } -class FileTransferInterface : public QObject +class SshTransferInterface : public FileTransferInterface { Q_OBJECT -public: - void setDevice(const ProjectExplorer::IDeviceConstPtr &device) { m_device = device; } - void setFilesToTransfer(const FilesToTransfer &files, TransferDirection direction) - { - m_files = files; - m_direction = direction; - } - - void start() { startImpl(); } - -signals: - void progress(const QString &progressMessage); - void done(const Utils::ProcessResultData &resultData); protected: - FileTransferInterface(FileTransferMethod method) - : m_method(method) + SshTransferInterface(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate) + : FileTransferInterface(setup) + , m_device(devicePrivate->q->sharedFromThis()) + , m_devicePrivate(devicePrivate) , m_process(this) { + m_direction = m_setup.m_files.isEmpty() ? FileTransferDirection::Invalid + : m_setup.m_files.first().direction(); SshParameters::setupSshEnvironment(&m_process); connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] { emit progress(QString::fromLocal8Bit(m_process.readAllStandardOutput())); }); - connect(&m_process, &QtcProcess::done, this, &FileTransferInterface::doneImpl); - } - - void startFailed(const QString &errorString) - { - emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString}); + connect(&m_process, &QtcProcess::done, this, &SshTransferInterface::doneImpl); } bool handleError() @@ -1496,10 +1488,10 @@ protected: ProcessResultData resultData = m_process.resultData(); if (resultData.m_error == QProcess::FailedToStart) { resultData.m_errorString = tr("\"%1\" failed to start: %2") - .arg(FileTransfer::transferMethodName(m_method), resultData.m_errorString); + .arg(FileTransfer::transferMethodName(m_setup.m_method), resultData.m_errorString); } else if (resultData.m_exitStatus != QProcess::NormalExit) { resultData.m_errorString = tr("\"%1\" crashed.") - .arg(FileTransfer::transferMethodName(m_method)); + .arg(FileTransfer::transferMethodName(m_setup.m_method)); } else if (resultData.m_exitCode != 0) { resultData.m_errorString = QString::fromLocal8Bit(m_process.readAllStandardError()); } else { @@ -1515,24 +1507,83 @@ protected: emit done(m_process.resultData()); } - FileTransferMethod m_method = FileTransferMethod::Default; - IDevice::ConstPtr m_device; - FilesToTransfer m_files; - QtcProcess m_process; - TransferDirection m_direction = TransferDirection::Invalid; + QStringList fullConnectionOptions() const + { + QStringList options = m_sshParameters.connectionOptions(SshSettings::sshFilePath()); + if (!m_socketFilePath.isEmpty()) + options << "-o" << ("ControlPath=" + m_socketFilePath); + return options; + } + + QString host() const { return m_sshParameters.host(); } + QString userAtHost() const { return m_sshParameters.userName() + '@' + m_sshParameters.host(); } + + QtcProcess &process() { return m_process; } + FileTransferDirection direction() const { return m_direction; } private: virtual void startImpl() = 0; virtual void doneImpl() = 0; + + void start() final + { + m_sshParameters = displayless(m_device->sshParameters()); + if (SshSettings::connectionSharingEnabled()) { + m_connecting = true; + m_connectionHandle.reset(new SshConnectionHandle(m_device)); + m_connectionHandle->setParent(this); + connect(m_connectionHandle.get(), &SshConnectionHandle::connected, + this, &SshTransferInterface::handleConnected); + connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected, + this, &SshTransferInterface::handleDisconnected); + m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters); + } else { + startImpl(); + } + } + + void handleConnected(const QString &socketFilePath) + { + m_connecting = false; + m_socketFilePath = socketFilePath; + startImpl(); + } + + void handleDisconnected(const ProcessResultData &result) + { + ProcessResultData resultData = result; + if (m_connecting) + resultData.m_error = QProcess::FailedToStart; + + m_connecting = false; + if (m_connectionHandle) // TODO: should it disconnect from signals first? + m_connectionHandle.release()->deleteLater(); + + if (resultData.m_error != QProcess::UnknownError || m_process.state() != QProcess::NotRunning) + emit done(resultData); // TODO: don't emit done() on process finished afterwards + } + + IDevice::ConstPtr m_device; + LinuxDevicePrivate *m_devicePrivate = nullptr; + SshParameters m_sshParameters; + FileTransferDirection m_direction = FileTransferDirection::Invalid; // helper + + // ssh shared connection related + std::unique_ptr m_connectionHandle; + QString m_socketFilePath; + bool m_connecting = false; + + QtcProcess m_process; }; -class SftpTransferImpl : public FileTransferInterface +class SftpTransferImpl : public SshTransferInterface { public: - SftpTransferImpl() : FileTransferInterface(FileTransferMethod::Sftp) { } + SftpTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate) + : SshTransferInterface(setup, devicePrivate) { } private: - void startImpl() + void startImpl() final { const FilePath sftpBinary = SshSettings::sftpFilePath(); if (!sftpBinary.exists()) { @@ -1546,12 +1597,12 @@ private: return; } - const FilePaths dirs = dirsToCreate(m_files); + const FilePaths dirs = dirsToCreate(m_setup.m_files); for (const FilePath &dir : dirs) { - if (m_direction == TransferDirection::Upload) { + if (direction() == FileTransferDirection::Upload) { m_batchFile->write("-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit() + '\n'); - } else if (m_direction == TransferDirection::Download) { + } else if (direction() == FileTransferDirection::Download) { if (!QDir::root().mkpath(dir.path())) { startFailed(tr("Failed to create local directory \"%1\".") .arg(QDir::toNativeSeparators(dir.path()))); @@ -1560,10 +1611,10 @@ private: } } - for (const FileToTransfer &file : m_files) { + for (const FileToTransfer &file : m_setup.m_files) { FilePath sourceFileOrLinkTarget = file.m_source; bool link = false; - if (m_direction == TransferDirection::Upload) { + if (direction() == FileTransferDirection::Upload) { const QFileInfo fi(file.m_source.toFileInfo()); if (fi.isSymLink()) { link = true; @@ -1573,18 +1624,14 @@ private: sourceFileOrLinkTarget.setPath(fi.dir().relativeFilePath(fi.symLinkTarget())); } } - m_batchFile->write(transferCommand(m_direction, link) + ' ' + m_batchFile->write(transferCommand(direction(), link) + ' ' + ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' ' + ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n'); } m_batchFile->close(); - m_process.setStandardInputFile(m_batchFile->fileName()); - - // TODO: Add support for shared ssh connection - const SshParameters params = displayless(m_device->sshParameters()); - m_process.setCommand(CommandLine(sftpBinary, - params.connectionOptions(sftpBinary) << params.host())); - m_process.start(); + process().setCommand(CommandLine(sftpBinary, fullConnectionOptions() + << "-b" << m_batchFile->fileName() << host())); + process().start(); } void doneImpl() final { handleDone(); } @@ -1592,24 +1639,23 @@ private: std::unique_ptr m_batchFile; }; -class RsyncTransferImpl : public FileTransferInterface +class RsyncTransferImpl : public SshTransferInterface { public: - RsyncTransferImpl(const QString &flags) - : FileTransferInterface(FileTransferMethod::Rsync) - , m_flags(flags) + RsyncTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate) + : SshTransferInterface(setup, devicePrivate) { } private: - void startImpl() + void startImpl() final { m_currentIndex = 0; startNextFile(); } - void doneImpl() + void doneImpl() final { - if (m_files.size() == 0 || m_currentIndex == m_files.size() - 1) + if (m_setup.m_files.size() == 0 || m_currentIndex == m_setup.m_files.size() - 1) return handleDone(); if (handleError()) @@ -1621,30 +1667,25 @@ private: void startNextFile() { - m_process.close(); + process().close(); - const SshParameters parameters = displayless(m_device->sshParameters()); - const QStringList connectionOptions // TODO: add shared connection here - = parameters.connectionOptions(SshSettings::sshFilePath()); const QString sshCmdLine = ProcessArgs::joinArgs( - QStringList{SshSettings::sshFilePath().toUserOutput()} << connectionOptions, - OsTypeLinux); - const QStringList options{"-e", sshCmdLine, m_flags}; - const QString remoteHost = parameters.userName() + '@' + parameters.host(); + QStringList{SshSettings::sshFilePath().toUserOutput()} + << fullConnectionOptions(), OsTypeLinux); + QStringList options{"-e", sshCmdLine, m_setup.m_rsyncFlags}; - QStringList args = QStringList(options); - if (!m_files.isEmpty()) { // NormalRun - const FileToTransfer file = m_files.at(m_currentIndex); + if (!m_setup.m_files.isEmpty()) { // NormalRun + const FileToTransfer file = m_setup.m_files.at(m_currentIndex); const FileToTransfer fixedFile = fixLocalFileOnWindows(file, options); - const auto fixedPaths = fixPaths(fixedFile, remoteHost); + const auto fixedPaths = fixPaths(fixedFile, userAtHost()); - args << fixedPaths.first << fixedPaths.second; + options << fixedPaths.first << fixedPaths.second; } else { // TestRun - args << "-n" << "--exclude=*" << (remoteHost + ":/tmp"); + options << "-n" << "--exclude=*" << (userAtHost() + ":/tmp"); } // TODO: Get rsync location from settings? - m_process.setCommand(CommandLine("rsync", args)); - m_process.start(); + process().setCommand(CommandLine("rsync", options)); + process().start(); } // On Windows, rsync is either from msys or cygwin. Neither work with the other's ssh.exe. @@ -1653,7 +1694,7 @@ private: if (!HostOsInfo::isWindowsHost()) return file; - QString localFilePath = m_direction == TransferDirection::Upload + QString localFilePath = direction() == FileTransferDirection::Upload ? file.m_source.path() : file.m_target.path(); localFilePath = '/' + localFilePath.at(0) + localFilePath.mid(2); if (anyOf(options, [](const QString &opt) { @@ -1662,8 +1703,8 @@ private: } FileToTransfer fixedFile = file; - (m_direction == TransferDirection::Upload) ? fixedFile.m_source.setPath(localFilePath) - : fixedFile.m_target.setPath(localFilePath); + (direction() == FileTransferDirection::Upload) ? fixedFile.m_source.setPath(localFilePath) + : fixedFile.m_target.setPath(localFilePath); return fixedFile; } @@ -1671,7 +1712,7 @@ private: { FilePath localPath; FilePath remotePath; - if (m_direction == TransferDirection::Upload) { + if (direction() == FileTransferDirection::Upload) { localPath = file.m_source; remotePath = file.m_target; } else { @@ -1682,163 +1723,24 @@ private: ? localPath.path() + '/' : localPath.path(); const QString remote = remoteHost + ':' + remotePath.path(); - return m_direction == TransferDirection::Upload ? qMakePair(local, remote) - : qMakePair(remote, local); + return direction() == FileTransferDirection::Upload ? qMakePair(local, remote) + : qMakePair(remote, local); } - QString m_flags; int m_currentIndex = 0; }; -class FileTransferPrivate : public QObject +FileTransferInterface *LinuxDevice::createFileTransferInterface( + const FileTransferSetupData &setup) const { - Q_OBJECT - -public: - void test() { run(TestRun); } - void start() { run(NormalRun); } - void stop(); - - FileTransferMethod m_method = FileTransferMethod::Default; - IDevice::ConstPtr m_device; - FilesToTransfer m_files; - QString m_rsyncFlags = FileTransfer::defaultRsyncFlags(); - -signals: - void progress(const QString &progressMessage); - void done(const Utils::ProcessResultData &resultData); - -private: - enum RunMode { - NormalRun, - TestRun - }; - - void startFailed(const QString &errorString); - void run(RunMode mode); - - std::unique_ptr m_transfer; -}; - -void FileTransferPrivate::stop() -{ - if (!m_transfer) - return; - m_transfer->disconnect(); - m_transfer.release()->deleteLater(); -} - -void FileTransferPrivate::startFailed(const QString &errorString) -{ - emit done({0, QProcess::NormalExit, QProcess::FailedToStart, errorString}); -} - -void FileTransferPrivate::run(RunMode mode) -{ - stop(); - - TransferDirection direction = TransferDirection::Invalid; - if (mode == NormalRun) { - if (m_files.isEmpty()) - return startFailed(tr("No files to transfer.")); - - direction = transferDirection(m_files); - if (direction == TransferDirection::Invalid) - return startFailed(tr("Mixing different types on transfer in one go.")); - - if (!isDeviceMatched(m_files, m_device->id().toString())) - return startFailed(tr("Trying to transfer into / from not matching device.")); - } - - switch (m_method) { - case FileTransferMethod::Sftp: - m_transfer.reset(new SftpTransferImpl()); - break; - case FileTransferMethod::Rsync: - m_transfer.reset(new RsyncTransferImpl(m_rsyncFlags)); - break; - } - QTC_ASSERT(m_transfer, startFailed(tr("Missing transfer implementation.")); return); - m_transfer->setParent(this); - m_transfer->setDevice(m_device); - if (mode == NormalRun) - m_transfer->setFilesToTransfer(m_files, direction); - connect(m_transfer.get(), &FileTransferInterface::progress, - this, &FileTransferPrivate::progress); - connect(m_transfer.get(), &FileTransferInterface::done, - this, &FileTransferPrivate::done); - m_transfer->start(); -} - -FileTransfer::FileTransfer() - : d(new FileTransferPrivate) -{ - d->setParent(this); - connect(d, &FileTransferPrivate::progress, this, &FileTransfer::progress); - connect(d, &FileTransferPrivate::done, this, &FileTransfer::done); -} - -FileTransfer::~FileTransfer() -{ - stop(); - delete d; -} - -void FileTransfer::setDevice(const ProjectExplorer::IDeviceConstPtr &device) -{ - d->m_device = device; -} - -void FileTransfer::setTransferMethod(FileTransferMethod method) -{ - d->m_method = method; -} - -void FileTransfer::setFilesToTransfer(const FilesToTransfer &files) -{ - d->m_files = files; -} - -void FileTransfer::setRsyncFlags(const QString &flags) -{ - d->m_rsyncFlags = flags; -} - -void FileTransfer::test() -{ - d->test(); -} - -FileTransferMethod FileTransfer::transferMethod() const -{ - return d->m_method; -} - -void FileTransfer::start() -{ - d->start(); -} - -void FileTransfer::stop() -{ - d->stop(); -} - -QString FileTransfer::transferMethodName(FileTransferMethod method) -{ - switch (method) { - case FileTransferMethod::Sftp: return FileTransfer::tr("sftp"); - case FileTransferMethod::Rsync: return FileTransfer::tr("rsync"); + switch (setup.m_method) { + case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, d); + case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, d); } QTC_CHECK(false); return {}; } -QString FileTransfer::defaultRsyncFlags() -{ - return "-av"; -} - namespace Internal { // Factory diff --git a/src/plugins/remotelinux/linuxdevice.h b/src/plugins/remotelinux/linuxdevice.h index 3272e46e575..1b286ac1080 100644 --- a/src/plugins/remotelinux/linuxdevice.h +++ b/src/plugins/remotelinux/linuxdevice.h @@ -81,6 +81,8 @@ public: bool writeFileContents(const Utils::FilePath &filePath, const QByteArray &data) const override; QDateTime lastModified(const Utils::FilePath &filePath) const override; Utils::ProcessInterface *createProcessInterface() const override; + ProjectExplorer::FileTransferInterface *createFileTransferInterface( + const ProjectExplorer::FileTransferSetupData &setup) const override; Utils::Environment systemEnvironment() const override; qint64 fileSize(const Utils::FilePath &filePath) const override; qint64 bytesAvailable(const Utils::FilePath &filePath) const override; @@ -92,6 +94,7 @@ protected: class LinuxDevicePrivate *d; friend class SshProcessInterface; + friend class SshTransferInterface; }; namespace Internal { diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp index fb84ebc636e..4c7ac4302b5 100644 --- a/src/plugins/remotelinux/linuxdevicetester.cpp +++ b/src/plugins/remotelinux/linuxdevicetester.cpp @@ -25,10 +25,10 @@ #include "linuxdevicetester.h" -#include "filetransfer.h" #include "remotelinux_constants.h" #include +#include #include #include #include @@ -84,7 +84,6 @@ void GenericLinuxDeviceTester::testDevice(const IDevice::Ptr &deviceConfiguratio QTC_ASSERT(d->state == Inactive, return); d->device = deviceConfiguration; - d->fileTransfer.setDevice(d->device); testEcho(); } @@ -215,7 +214,7 @@ void GenericLinuxDeviceTester::testFileTransfer(FileTransferMethod method) .arg(FileTransfer::transferMethodName(method))); d->fileTransfer.setTransferMethod(method); - d->fileTransfer.test(); + d->fileTransfer.test(d->device); } void GenericLinuxDeviceTester::handleFileTransferDone(const ProcessResultData &resultData) diff --git a/src/plugins/remotelinux/linuxdevicetester.h b/src/plugins/remotelinux/linuxdevicetester.h index 57dabb94570..2906a51b67d 100644 --- a/src/plugins/remotelinux/linuxdevicetester.h +++ b/src/plugins/remotelinux/linuxdevicetester.h @@ -27,8 +27,7 @@ #include "remotelinux_export.h" -#include "filetransfer.h" - +#include #include namespace Utils { class ProcessResultData; } @@ -59,7 +58,7 @@ private: void handlePortsGathererError(const QString &message); void handlePortsGathererDone(); - void testFileTransfer(FileTransferMethod method); + void testFileTransfer(ProjectExplorer::FileTransferMethod method); void handleFileTransferDone(const Utils::ProcessResultData &resultData); void setFinished(ProjectExplorer::DeviceTester::TestResult result); diff --git a/src/plugins/remotelinux/linuxprocessinterface.h b/src/plugins/remotelinux/linuxprocessinterface.h index 85ea19d4c01..110e6939f8c 100644 --- a/src/plugins/remotelinux/linuxprocessinterface.h +++ b/src/plugins/remotelinux/linuxprocessinterface.h @@ -44,6 +44,7 @@ private: void sendControlSignal(Utils::ControlSignal controlSignal) override; void handleStarted(qint64 processId) final; + void handleDone(const Utils::ProcessResultData &resultData) final; void handleReadyReadStandardOutput(const QByteArray &outputData) final; void handleReadyReadStandardError(const QByteArray &errorData) final; diff --git a/src/plugins/remotelinux/remotelinux.qbs b/src/plugins/remotelinux/remotelinux.qbs index d91d412ee3a..5bf01c2c49f 100644 --- a/src/plugins/remotelinux/remotelinux.qbs +++ b/src/plugins/remotelinux/remotelinux.qbs @@ -19,11 +19,8 @@ Project { "abstractremotelinuxdeployservice.h", "abstractremotelinuxdeploystep.cpp", "abstractremotelinuxdeploystep.h", - "abstractuploadandinstallpackageservice.cpp", - "abstractuploadandinstallpackageservice.h", "deploymenttimeinfo.cpp", "deploymenttimeinfo.h", - "filetransfer.h", "genericdirectuploadservice.cpp", "genericdirectuploadservice.h", "genericdirectuploadstep.cpp", @@ -36,6 +33,8 @@ Project { "genericlinuxdeviceconfigurationwizardpages.cpp", "genericlinuxdeviceconfigurationwizardpages.h", "genericlinuxdeviceconfigurationwizardsetuppage.ui", + "killappstep.cpp", + "killappstep.h", "linuxdevice.cpp", "linuxdevice.h", "linuxdevicetester.cpp", @@ -64,10 +63,6 @@ Project { "remotelinuxenvironmentaspectwidget.h", "remotelinuxenvironmentreader.cpp", "remotelinuxenvironmentreader.h", - "remotelinuxkillappservice.cpp", - "remotelinuxkillappservice.h", - "remotelinuxkillappstep.cpp", - "remotelinuxkillappstep.h", "remotelinuxpackageinstaller.cpp", "remotelinuxpackageinstaller.h", "remotelinuxplugin.cpp", diff --git a/src/plugins/remotelinux/remotelinuxcheckforfreediskspacestep.cpp b/src/plugins/remotelinux/remotelinuxcheckforfreediskspacestep.cpp index 2c3650206c6..a332c8183d8 100644 --- a/src/plugins/remotelinux/remotelinuxcheckforfreediskspacestep.cpp +++ b/src/plugins/remotelinux/remotelinuxcheckforfreediskspacestep.cpp @@ -54,14 +54,11 @@ public: void setRequiredSpaceInBytes(quint64 sizeInBytes); private: - void deployAndFinish(); bool isDeploymentNecessary() const override { return true; } CheckResult isDeploymentPossible() const override; - void doDeviceSetup() final { deployAndFinish(); } - void stopDeviceSetup() final { } - void doDeploy() final {} + void doDeploy() final; void stopDeployment() final {} QString m_pathToCheck; @@ -78,7 +75,7 @@ void RemoteLinuxCheckForFreeDiskSpaceService::setRequiredSpaceInBytes(quint64 si m_requiredSpaceInBytes = sizeInBytes; } -void RemoteLinuxCheckForFreeDiskSpaceService::deployAndFinish() +void RemoteLinuxCheckForFreeDiskSpaceService::doDeploy() { auto cleanup = qScopeGuard([this] { setFinished(); }); const FilePath path = deviceConfiguration()->filePath(m_pathToCheck); @@ -86,6 +83,7 @@ void RemoteLinuxCheckForFreeDiskSpaceService::deployAndFinish() if (freeSpace < 0) { emit errorMessage(tr("Cannot get info about free disk space for \"%1\"") .arg(path.toUserOutput())); + handleDeploymentDone(); return; } @@ -97,11 +95,13 @@ void RemoteLinuxCheckForFreeDiskSpaceService::deployAndFinish() emit errorMessage(tr("The remote file system has only %n megabytes of free space, " "but %1 megabytes are required.", nullptr, freeSpaceMB) .arg(requiredSpaceMB)); + handleDeploymentDone(); return; } emit progressMessage(tr("The remote file system has %n megabytes of free space, going ahead.", nullptr, freeSpaceMB)); + handleDeploymentDone(); } CheckResult RemoteLinuxCheckForFreeDiskSpaceService::isDeploymentPossible() const diff --git a/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp index 421a1556550..6009c502696 100644 --- a/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxcustomrunconfiguration.cpp @@ -72,15 +72,15 @@ RemoteLinuxCustomRunConfiguration::RemoteLinuxCustomRunConfiguration(Target *tar symbolsAspect->setDisplayStyle(SymbolFileAspect::PathChooserDisplay); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); if (HostOsInfo::isAnyUnixHost()) addAspect(); if (HostOsInfo::isAnyUnixHost()) - addAspect(); + addAspect(macroExpander()); setRunnableModifier([this](Runnable &r) { if (const auto * const forwardingAspect = aspect()) - r.extraData.insert("Ssh.X11ForwardToDisplay", forwardingAspect->display(macroExpander())); + r.extraData.insert("Ssh.X11ForwardToDisplay", forwardingAspect->display()); }); setDefaultDisplayName(runConfigDefaultDisplayName()); diff --git a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp index 8f311a6c424..b248d53f918 100644 --- a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp @@ -28,7 +28,7 @@ #include "genericdirectuploadstep.h" #include "makeinstallstep.h" #include "remotelinuxcheckforfreediskspacestep.h" -#include "remotelinuxkillappstep.h" +#include "killappstep.h" #include "remotelinux_constants.h" #include "rsyncdeploystep.h" @@ -77,7 +77,7 @@ RemoteLinuxDeployConfigurationFactory::RemoteLinuxDeployConfigurationFactory() addInitialStep(MakeInstallStep::stepId(), needsMakeInstall); addInitialStep(RemoteLinuxCheckForFreeDiskSpaceStep::stepId()); - addInitialStep(RemoteLinuxKillAppStep::stepId()); + addInitialStep(KillAppStep::stepId()); addInitialStep(RsyncDeployStep::stepId(), [](Target *target) { auto device = DeviceKitAspect::device(target->kit()); return device && device->extraData(Constants::SupportsRSync).toBool(); diff --git a/src/plugins/remotelinux/remotelinuxkillappservice.cpp b/src/plugins/remotelinux/remotelinuxkillappservice.cpp deleted file mode 100644 index a6c446d9c59..00000000000 --- a/src/plugins/remotelinux/remotelinuxkillappservice.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 "remotelinuxkillappservice.h" - -#include -#include - -namespace RemoteLinux { -namespace Internal { -class RemoteLinuxKillAppServicePrivate -{ -public: - QString remoteExecutable; - ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOp; -}; -} // namespace Internal - -RemoteLinuxKillAppService::RemoteLinuxKillAppService() - : d(new Internal::RemoteLinuxKillAppServicePrivate) -{ -} - -RemoteLinuxKillAppService::~RemoteLinuxKillAppService() -{ - cleanup(); - delete d; -} - -void RemoteLinuxKillAppService::setRemoteExecutable(const QString &filePath) -{ - d->remoteExecutable = filePath; -} - -bool RemoteLinuxKillAppService::isDeploymentNecessary() const -{ - return !d->remoteExecutable.isEmpty(); -} - -void RemoteLinuxKillAppService::doDeploy() -{ - d->signalOp = deviceConfiguration()->signalOperation(); - if (!d->signalOp) { - handleDeploymentDone(); - return; - } - connect(d->signalOp.data(), &ProjectExplorer::DeviceProcessSignalOperation::finished, - this, &RemoteLinuxKillAppService::handleSignalOpFinished); - emit progressMessage(tr("Trying to kill \"%1\" on remote device...").arg(d->remoteExecutable)); - d->signalOp->killProcess(d->remoteExecutable); -} - -void RemoteLinuxKillAppService::cleanup() -{ - if (d->signalOp) { - disconnect(d->signalOp.data(), nullptr, this, nullptr); - d->signalOp.clear(); - } -} - -void RemoteLinuxKillAppService::finishDeployment() -{ - cleanup(); - handleDeploymentDone(); -} - -void RemoteLinuxKillAppService::stopDeployment() -{ - finishDeployment(); -} - -void RemoteLinuxKillAppService::handleSignalOpFinished(const QString &errorMessage) -{ - if (errorMessage.isEmpty()) - emit progressMessage(tr("Remote application killed.")); - else - emit progressMessage(tr("Failed to kill remote application. Assuming it was not running.")); - finishDeployment(); -} - -} // namespace RemoteLinux diff --git a/src/plugins/remotelinux/remotelinuxkillappstep.cpp b/src/plugins/remotelinux/remotelinuxkillappstep.cpp deleted file mode 100644 index daa451d97e0..00000000000 --- a/src/plugins/remotelinux/remotelinuxkillappstep.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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 "remotelinuxkillappstep.h" - -#include "remotelinux_constants.h" -#include "remotelinuxkillappservice.h" - -#include -#include -#include - -using namespace ProjectExplorer; - -namespace RemoteLinux { - -RemoteLinuxKillAppStep::RemoteLinuxKillAppStep(BuildStepList *bsl, Utils::Id id) - : AbstractRemoteLinuxDeployStep(bsl, id) -{ - auto service = createDeployService(); - - setWidgetExpandedByDefault(false); - - setInternalInitializer([this, service] { - Target * const theTarget = target(); - QTC_ASSERT(theTarget, return CheckResult::failure()); - RunConfiguration * const rc = theTarget->activeRunConfiguration(); - const QString remoteExe = rc ? rc->runnable().command.executable().toString() : QString(); - service->setRemoteExecutable(remoteExe); - return CheckResult::success(); - }); -} - -Utils::Id RemoteLinuxKillAppStep::stepId() -{ - return Constants::KillAppStepId; -} - -QString RemoteLinuxKillAppStep::displayName() -{ - return tr("Kill current application instance"); -} - -} // namespace RemoteLinux diff --git a/src/plugins/remotelinux/remotelinuxplugin.cpp b/src/plugins/remotelinux/remotelinuxplugin.cpp index ba3286be0ba..86a55970d27 100644 --- a/src/plugins/remotelinux/remotelinuxplugin.cpp +++ b/src/plugins/remotelinux/remotelinuxplugin.cpp @@ -38,7 +38,7 @@ #include "remotelinuxcheckforfreediskspacestep.h" #include "remotelinuxdeployconfiguration.h" #include "remotelinuxcustomcommanddeploymentstep.h" -#include "remotelinuxkillappstep.h" +#include "killappstep.h" #include "rsyncdeploystep.h" #include "tarpackagecreationstep.h" #include "uploadandinstalltarpackagestep.h" @@ -83,7 +83,7 @@ public: customCommandDeploymentStepFactory; GenericDeployStepFactory checkForFreeDiskSpaceStepFactory; - GenericDeployStepFactory remoteLinuxKillAppStepFactory; + GenericDeployStepFactory killAppStepFactory; GenericDeployStepFactory makeInstallStepFactory; const QList supportedRunConfigs { diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp index ca16b4ddfc3..44d1c677281 100644 --- a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp @@ -71,11 +71,11 @@ RemoteLinuxRunConfiguration::RemoteLinuxRunConfiguration(Target *target, Id id) symbolsAspect->setDisplayStyle(SymbolFileAspect::LabelDisplay); addAspect(macroExpander()); - addAspect(envAspect); + addAspect(macroExpander(), envAspect); if (HostOsInfo::isAnyUnixHost()) addAspect(); if (HostOsInfo::isAnyUnixHost()) - addAspect(); + addAspect(macroExpander()); setUpdater([this, target, exeAspect, symbolsAspect] { BuildTargetInfo bti = buildTargetInfo(); @@ -88,7 +88,7 @@ RemoteLinuxRunConfiguration::RemoteLinuxRunConfiguration(Target *target, Id id) setRunnableModifier([this](Runnable &r) { if (const auto * const forwardingAspect = aspect()) - r.extraData.insert("Ssh.X11ForwardToDisplay", forwardingAspect->display(macroExpander())); + r.extraData.insert("Ssh.X11ForwardToDisplay", forwardingAspect->display()); }); connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); diff --git a/src/plugins/remotelinux/remotelinuxx11forwardingaspect.cpp b/src/plugins/remotelinux/remotelinuxx11forwardingaspect.cpp index 739a470321f..3fbeba70cc1 100644 --- a/src/plugins/remotelinux/remotelinuxx11forwardingaspect.cpp +++ b/src/plugins/remotelinux/remotelinuxx11forwardingaspect.cpp @@ -32,9 +32,10 @@ using namespace Utils; namespace RemoteLinux { -static QString defaultDisplay() { return QLatin1String(qgetenv("DISPLAY")); } +static QString defaultDisplay() { return qEnvironmentVariable("DISPLAY"); } -X11ForwardingAspect::X11ForwardingAspect() +X11ForwardingAspect::X11ForwardingAspect(const MacroExpander *expander) + : m_macroExpander(expander) { setLabelText(tr("X11 Forwarding:")); setDisplayStyle(LineEditDisplay); @@ -43,12 +44,14 @@ X11ForwardingAspect::X11ForwardingAspect() makeCheckable(CheckBoxPlacement::Right, tr("Forward to local display"), "RunConfiguration.UseX11Forwarding"); setValue(defaultDisplay()); + + addDataExtractor(this, &X11ForwardingAspect::display, &Data::display); } -QString X11ForwardingAspect::display(const Utils::MacroExpander *expander) const +QString X11ForwardingAspect::display() const { - QTC_ASSERT(expander, return value()); - return !isChecked() ? QString() : expander->expandProcessArgs(value()); + QTC_ASSERT(m_macroExpander, return value()); + return !isChecked() ? QString() : m_macroExpander->expandProcessArgs(value()); } } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/remotelinuxx11forwardingaspect.h b/src/plugins/remotelinux/remotelinuxx11forwardingaspect.h index b518782fbc8..5e61ec36c30 100644 --- a/src/plugins/remotelinux/remotelinuxx11forwardingaspect.h +++ b/src/plugins/remotelinux/remotelinuxx11forwardingaspect.h @@ -38,9 +38,17 @@ class REMOTELINUX_EXPORT X11ForwardingAspect : public Utils::StringAspect Q_OBJECT public: - X11ForwardingAspect(); + X11ForwardingAspect(const Utils::MacroExpander *macroExpander); - QString display(const Utils::MacroExpander *expander) const; + struct Data : StringAspect::Data + { + QString display; + }; + + QString display() const; + +private: + const Utils::MacroExpander *m_macroExpander; }; } // namespace RemoteLinux diff --git a/src/plugins/remotelinux/rsyncdeploystep.cpp b/src/plugins/remotelinux/rsyncdeploystep.cpp index 421f4998f81..b4d9929d76b 100644 --- a/src/plugins/remotelinux/rsyncdeploystep.cpp +++ b/src/plugins/remotelinux/rsyncdeploystep.cpp @@ -26,10 +26,10 @@ #include "rsyncdeploystep.h" #include "abstractremotelinuxdeployservice.h" -#include "filetransfer.h" #include "remotelinux_constants.h" #include +#include #include #include #include @@ -51,12 +51,23 @@ public: { connect(&m_mkdir, &QtcProcess::done, this, [this] { if (m_mkdir.result() != ProcessResult::FinishedWithSuccess) { - emit errorMessage(tr("Failed to create remote directories: %1").arg(m_mkdir.stdErr())); + QString finalMessage = m_mkdir.errorString(); + const QString stdErr = m_mkdir.stdErr(); + if (!stdErr.isEmpty()) { + if (!finalMessage.isEmpty()) + finalMessage += '\n'; + finalMessage += stdErr; + } + emit errorMessage(tr("Deploy via rsync: failed to create remote directories:") + + '\n' + finalMessage); setFinished(); return; } deployFiles(); }); + connect(&m_mkdir, &QtcProcess::readyReadStandardError, this, [this] { + emit stdErrData(QString::fromLocal8Bit(m_mkdir.readAllStandardError())); + }); connect(&m_fileTransfer, &FileTransfer::progress, this, &AbstractRemoteLinuxDeployService::stdOutData); connect(&m_fileTransfer, &FileTransfer::done, this, [this](const ProcessResultData &result) { @@ -136,7 +147,6 @@ void RsyncDeployService::createRemoteDirectories() void RsyncDeployService::deployFiles() { - m_fileTransfer.setDevice(deviceConfiguration()); m_fileTransfer.setTransferMethod(FileTransferMethod::Rsync); m_fileTransfer.setRsyncFlags(m_flags); m_fileTransfer.setFilesToTransfer(m_files); @@ -161,7 +171,7 @@ RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl, Utils::Id id) flags->setDisplayStyle(StringAspect::LineEditDisplay); flags->setSettingsKey("RemoteLinux.RsyncDeployStep.Flags"); flags->setLabelText(tr("Flags:")); - flags->setValue(FileTransfer::defaultRsyncFlags()); + flags->setValue(FileTransferSetupData::defaultRsyncFlags()); auto ignoreMissingFiles = addAspect(); ignoreMissingFiles->setSettingsKey("RemoteLinux.RsyncDeployStep.IgnoreMissingFiles"); diff --git a/src/plugins/remotelinux/sshprocessinterface.h b/src/plugins/remotelinux/sshprocessinterface.h index 241b4c0c87e..e60009ded2a 100644 --- a/src/plugins/remotelinux/sshprocessinterface.h +++ b/src/plugins/remotelinux/sshprocessinterface.h @@ -50,6 +50,7 @@ protected: private: virtual void handleStarted(qint64 processId); + virtual void handleDone(const Utils::ProcessResultData &resultData); virtual void handleReadyReadStandardOutput(const QByteArray &outputData); virtual void handleReadyReadStandardError(const QByteArray &errorData); diff --git a/src/plugins/remotelinux/uploadandinstalltarpackagestep.cpp b/src/plugins/remotelinux/uploadandinstalltarpackagestep.cpp index 06edf9f867c..a1bcfe58a6e 100644 --- a/src/plugins/remotelinux/uploadandinstalltarpackagestep.cpp +++ b/src/plugins/remotelinux/uploadandinstalltarpackagestep.cpp @@ -26,42 +26,153 @@ #include "uploadandinstalltarpackagestep.h" #include "remotelinux_constants.h" -#include "remotelinuxdeployconfiguration.h" #include "remotelinuxpackageinstaller.h" #include "tarpackagecreationstep.h" +#include +#include +#include + +#include + +#include + using namespace ProjectExplorer; +using namespace Utils; namespace RemoteLinux { namespace Internal { -class UploadAndInstallTarPackageServicePrivate +class UploadAndInstallTarPackageService : public AbstractRemoteLinuxDeployService { + Q_OBJECT + public: - RemoteLinuxTarPackageInstaller installer; + UploadAndInstallTarPackageService(); + void setPackageFilePath(const FilePath &filePath); + +private: + enum State { Inactive, Uploading, Installing }; + + void handleUploadFinished(const ProcessResultData &resultData); + void handleInstallationFinished(const QString &errorMsg); + + QString uploadDir() const; // Defaults to remote user's home directory. + + bool isDeploymentNecessary() const override; + void doDeploy() override; + void stopDeployment() override; + + void setFinished(); + + State m_state = Inactive; + FileTransfer m_uploader; + FilePath m_packageFilePath; + RemoteLinuxTarPackageInstaller m_installer; }; +UploadAndInstallTarPackageService::UploadAndInstallTarPackageService() +{ + connect(&m_uploader, &FileTransfer::done, this, + &UploadAndInstallTarPackageService::handleUploadFinished); + connect(&m_uploader, &FileTransfer::progress, this, + &UploadAndInstallTarPackageService::progressMessage); +} + +void UploadAndInstallTarPackageService::setPackageFilePath(const FilePath &filePath) +{ + m_packageFilePath = filePath; +} + +QString UploadAndInstallTarPackageService::uploadDir() const +{ + return QLatin1String("/tmp"); +} + +bool UploadAndInstallTarPackageService::isDeploymentNecessary() const +{ + return hasLocalFileChanged(DeployableFile(m_packageFilePath, {})); +} + +void UploadAndInstallTarPackageService::doDeploy() +{ + QTC_ASSERT(m_state == Inactive, return); + + m_state = Uploading; + + const QString remoteFilePath = uploadDir() + QLatin1Char('/') + m_packageFilePath.fileName(); + const FilesToTransfer files {{m_packageFilePath, + deviceConfiguration()->filePath(remoteFilePath)}}; + m_uploader.setFilesToTransfer(files); + m_uploader.start(); +} + +void UploadAndInstallTarPackageService::stopDeployment() +{ + switch (m_state) { + case Inactive: + qWarning("%s: Unexpected state 'Inactive'.", Q_FUNC_INFO); + break; + case Uploading: + m_uploader.stop(); + setFinished(); + break; + case Installing: + m_installer.cancelInstallation(); + setFinished(); + break; + } +} + +void UploadAndInstallTarPackageService::handleUploadFinished(const ProcessResultData &resultData) +{ + QTC_ASSERT(m_state == Uploading, return); + + if (resultData.m_error != QProcess::UnknownError) { + emit errorMessage(resultData.m_errorString); + setFinished(); + return; + } + + emit progressMessage(tr("Successfully uploaded package file.")); + const QString remoteFilePath = uploadDir() + '/' + m_packageFilePath.fileName(); + m_state = Installing; + emit progressMessage(tr("Installing package to device...")); + connect(&m_installer, &AbstractRemoteLinuxPackageInstaller::stdoutData, + this, &AbstractRemoteLinuxDeployService::stdOutData); + connect(&m_installer, &AbstractRemoteLinuxPackageInstaller::stderrData, + this, &AbstractRemoteLinuxDeployService::stdErrData); + connect(&m_installer, &AbstractRemoteLinuxPackageInstaller::finished, + this, &UploadAndInstallTarPackageService::handleInstallationFinished); + m_installer.installPackage(deviceConfiguration(), remoteFilePath, true); +} + +void UploadAndInstallTarPackageService::handleInstallationFinished(const QString &errorMsg) +{ + QTC_ASSERT(m_state == Installing, return); + + if (errorMsg.isEmpty()) { + saveDeploymentTimeStamp(DeployableFile(m_packageFilePath, {}), {}); + emit progressMessage(tr("Package installed.")); + } else { + emit errorMessage(errorMsg); + } + setFinished(); +} + +void UploadAndInstallTarPackageService::setFinished() +{ + m_state = Inactive; + m_uploader.stop(); + disconnect(&m_installer, nullptr, this, nullptr); + handleDeploymentDone(); +} + } // namespace Internal using namespace Internal; -UploadAndInstallTarPackageService::UploadAndInstallTarPackageService() - : d(new UploadAndInstallTarPackageServicePrivate) -{ -} - -UploadAndInstallTarPackageService::~UploadAndInstallTarPackageService() -{ - delete d; -} - -AbstractRemoteLinuxPackageInstaller *UploadAndInstallTarPackageService::packageInstaller() const -{ - return &d->installer; -} - - -UploadAndInstallTarPackageStep::UploadAndInstallTarPackageStep(BuildStepList *bsl, Utils::Id id) +UploadAndInstallTarPackageStep::UploadAndInstallTarPackageStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { auto service = createDeployService(); @@ -85,7 +196,7 @@ UploadAndInstallTarPackageStep::UploadAndInstallTarPackageStep(BuildStepList *bs }); } -Utils::Id UploadAndInstallTarPackageStep::stepId() +Id UploadAndInstallTarPackageStep::stepId() { return Constants::UploadAndInstallTarPackageStepId; } @@ -96,3 +207,5 @@ QString UploadAndInstallTarPackageStep::displayName() } } //namespace RemoteLinux + +#include "uploadandinstalltarpackagestep.moc" diff --git a/src/plugins/remotelinux/uploadandinstalltarpackagestep.h b/src/plugins/remotelinux/uploadandinstalltarpackagestep.h index f67e768baf4..5d120dc7271 100644 --- a/src/plugins/remotelinux/uploadandinstalltarpackagestep.h +++ b/src/plugins/remotelinux/uploadandinstalltarpackagestep.h @@ -25,29 +25,11 @@ #pragma once -#include "abstractuploadandinstallpackageservice.h" #include "abstractremotelinuxdeploystep.h" namespace RemoteLinux { class AbstractRemoteLinuxPackageInstaller; -namespace Internal { class UploadAndInstallTarPackageServicePrivate; } - -class REMOTELINUX_EXPORT UploadAndInstallTarPackageService : public AbstractUploadAndInstallPackageService -{ - Q_OBJECT - -public: - UploadAndInstallTarPackageService(); - ~UploadAndInstallTarPackageService() override; - -private: - AbstractRemoteLinuxPackageInstaller *packageInstaller() const override; - - Internal::UploadAndInstallTarPackageServicePrivate *d; -}; - - class REMOTELINUX_EXPORT UploadAndInstallTarPackageStep : public AbstractRemoteLinuxDeployStep { Q_OBJECT diff --git a/src/plugins/valgrind/CMakeLists.txt b/src/plugins/valgrind/CMakeLists.txt index 9896c93a3a8..192304e5795 100644 --- a/src/plugins/valgrind/CMakeLists.txt +++ b/src/plugins/valgrind/CMakeLists.txt @@ -4,7 +4,6 @@ add_qtc_plugin(Valgrind SOURCES callgrind/callgrindabstractmodel.h callgrind/callgrindcallmodel.cpp callgrind/callgrindcallmodel.h - callgrind/callgrindcontroller.cpp callgrind/callgrindcontroller.h callgrind/callgrindcostitem.cpp callgrind/callgrindcostitem.h callgrind/callgrindcycledetection.cpp callgrind/callgrindcycledetection.h callgrind/callgrinddatamodel.cpp callgrind/callgrinddatamodel.h diff --git a/src/plugins/valgrind/callgrind/callgrindcontroller.cpp b/src/plugins/valgrind/callgrind/callgrindcontroller.cpp deleted file mode 100644 index 3d9420f969a..00000000000 --- a/src/plugins/valgrind/callgrind/callgrindcontroller.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#include "callgrindcontroller.h" - -#include -#include - -#include - -#define CALLGRIND_CONTROL_DEBUG 0 - -using namespace ProjectExplorer; -using namespace Utils; - -namespace Valgrind { -namespace Callgrind { - -const char CALLGRIND_CONTROL_BINARY[] = "callgrind_control"; - -CallgrindController::CallgrindController() = default; - -CallgrindController::~CallgrindController() -{ - cleanupTempFile(); -} - -static QString toOptionString(CallgrindController::Option option) -{ - /* callgrind_control help from v3.9.0 - - Options: - -h --help Show this help text - --version Show version - -s --stat Show statistics - -b --back Show stack/back trace - -e [,...] Show event counters for ,... (default: all) - --dump[=] Request a dump optionally using as description - -z --zero Zero all event counters - -k --kill Kill - --instr= Switch instrumentation state on/off - */ - - switch (option) { - case CallgrindController::Dump: - return QLatin1String("--dump"); - case CallgrindController::ResetEventCounters: - return QLatin1String("--zero"); - case CallgrindController::Pause: - return QLatin1String("--instr=off"); - case CallgrindController::UnPause: - return QLatin1String("--instr=on"); - default: - return QString(); // never reached - } -} - -void CallgrindController::run(Option option) -{ - if (m_controllerProcess) { - emit statusMessage(tr("Previous command has not yet finished.")); - return; - } - - // save back current running operation - m_lastOption = option; - - m_controllerProcess.reset(new QtcProcess); - - switch (option) { - case CallgrindController::Dump: - emit statusMessage(tr("Dumping profile data...")); - break; - case CallgrindController::ResetEventCounters: - emit statusMessage(tr("Resetting event counters...")); - break; - case CallgrindController::Pause: - emit statusMessage(tr("Pausing instrumentation...")); - break; - case CallgrindController::UnPause: - emit statusMessage(tr("Unpausing instrumentation...")); - break; - default: - break; - } - -#if CALLGRIND_CONTROL_DEBUG - m_controllerProcess->setProcessChannelMode(QProcess::ForwardedChannels); -#endif - connect(m_controllerProcess.get(), &QtcProcess::finished, - this, &CallgrindController::controllerProcessDone); - - const FilePath control = - FilePath(CALLGRIND_CONTROL_BINARY).onDevice(m_valgrindRunnable.command.executable()); - m_controllerProcess->setCommand({control, {toOptionString(option), QString::number(m_pid)}}); - m_controllerProcess->setWorkingDirectory(m_valgrindRunnable.workingDirectory); - m_controllerProcess->setEnvironment(m_valgrindRunnable.environment); - m_controllerProcess->start(); -} - -void CallgrindController::setValgrindPid(qint64 pid) -{ - m_pid = pid; -} - -void CallgrindController::controllerProcessDone() -{ - const QString error = m_controllerProcess->errorString(); - const ProcessResult result = m_controllerProcess->result(); - - m_controllerProcess.release()->deleteLater(); - - if (result != ProcessResult::FinishedWithSuccess) { - emit statusMessage(tr("An error occurred while trying to run %1: %2").arg(CALLGRIND_CONTROL_BINARY).arg(error)); - qWarning() << "Controller exited abnormally:" << error; - return; - } - - // this call went fine, we might run another task after this - switch (m_lastOption) { - case ResetEventCounters: - // lets dump the new reset profiling info - run(Dump); - return; - case Pause: - break; - case Dump: - emit statusMessage(tr("Callgrind dumped profiling info")); - break; - case UnPause: - emit statusMessage(tr("Callgrind unpaused.")); - break; - default: - break; - } - - emit finished(m_lastOption); - m_lastOption = Unknown; -} - -void CallgrindController::getLocalDataFile() -{ - cleanupTempFile(); - { - TemporaryFile dataFile("callgrind.out"); - dataFile.open(); - m_hostOutputFile = FilePath::fromString(dataFile.fileName()); - } - - const auto afterCopy = [this](bool res) { - QTC_CHECK(res); - emit localParseDataAvailable(m_hostOutputFile); - }; - m_valgrindOutputFile.asyncCopyFile(afterCopy, m_hostOutputFile); -} - -void CallgrindController::cleanupTempFile() -{ - if (!m_hostOutputFile.isEmpty() && m_hostOutputFile.exists()) - m_hostOutputFile.removeFile(); - - m_hostOutputFile.clear(); -} - -void CallgrindController::setValgrindRunnable(const Runnable &runnable) -{ - m_valgrindRunnable = runnable; -} - -} // namespace Callgrind -} // namespace Valgrind diff --git a/src/plugins/valgrind/callgrind/callgrindcontroller.h b/src/plugins/valgrind/callgrind/callgrindcontroller.h deleted file mode 100644 index d5c273dc951..00000000000 --- a/src/plugins/valgrind/callgrind/callgrindcontroller.h +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -****************************************************************************/ - -#pragma once - -#include - -#include - -namespace Utils { class QtcProcess; } - -namespace Valgrind { -namespace Callgrind { - -class CallgrindController : public QObject -{ - Q_OBJECT -public: - enum Option { - Unknown, - Dump, - ResetEventCounters, - Pause, - UnPause - }; - Q_ENUM(Option) - - CallgrindController(); - ~CallgrindController() override; - - void run(Option option); - - /** - * Make data file available locally, triggers @c localParseDataAvailable. - * - * If the valgrind process was run remotely, this transparently - * downloads the data file first and returns a local path. - */ - void getLocalDataFile(); - void setValgrindPid(qint64 pid); - void setValgrindRunnable(const ProjectExplorer::Runnable &runnable); - void setValgrindOutputFile(const Utils::FilePath &output) { m_valgrindOutputFile = output; } - -signals: - void finished(Valgrind::Callgrind::CallgrindController::Option option); - void localParseDataAvailable(const Utils::FilePath &file); - void statusMessage(const QString &msg); - -private: - void cleanupTempFile(); - void controllerProcessDone(); - - std::unique_ptr m_controllerProcess; - ProjectExplorer::Runnable m_valgrindRunnable; - qint64 m_pid = 0; - - Option m_lastOption = Unknown; - - // remote callgrind support - Utils::FilePath m_valgrindOutputFile; // On the device that runs valgrind - Utils::FilePath m_hostOutputFile; // On the device that runs creator -}; - -} // namespace Callgrind -} // namespace Valgrind diff --git a/src/plugins/valgrind/callgrindengine.cpp b/src/plugins/valgrind/callgrindengine.cpp index 61ecb13a3e2..aa9b3fad546 100644 --- a/src/plugins/valgrind/callgrindengine.cpp +++ b/src/plugins/valgrind/callgrindengine.cpp @@ -25,10 +25,8 @@ #include "callgrindengine.h" -#include "callgrindtool.h" #include "valgrindsettings.h" -#include #include #include @@ -37,6 +35,11 @@ #include #include #include +#include + +#include + +#define CALLGRIND_CONTROL_DEBUG 0 using namespace ProjectExplorer; using namespace Valgrind::Callgrind; @@ -45,6 +48,8 @@ using namespace Utils; namespace Valgrind { namespace Internal { +const char CALLGRIND_CONTROL_BINARY[] = "callgrind_control"; + void setupCallgrindRunner(CallgrindToolRunner *); CallgrindToolRunner::CallgrindToolRunner(RunControl *runControl) @@ -57,29 +62,27 @@ CallgrindToolRunner::CallgrindToolRunner(RunControl *runControl) connect(&m_parser, &Callgrind::Parser::parserDataReady, this, &CallgrindToolRunner::slotFinished); - connect(&m_controller, &CallgrindController::finished, - this, &CallgrindToolRunner::controllerFinished); - connect(&m_controller, &CallgrindController::localParseDataAvailable, - this, &CallgrindToolRunner::handleLocalParseData); - connect(&m_controller, &CallgrindController::statusMessage, - this, &CallgrindToolRunner::showStatusMessage); - - connect(&m_runner, &ValgrindRunner::valgrindStarted, - &m_controller, &CallgrindController::setValgrindPid); + connect(&m_runner, &ValgrindRunner::valgrindStarted, this, [this](qint64 pid) { + m_pid = pid; + }); connect(&m_runner, &ValgrindRunner::extraProcessFinished, this, [this] { triggerParse(); }); - m_controller.setValgrindRunnable(runnable()); + m_valgrindRunnable = runControl->runnable(); static int fileCount = 100; - m_valgrindOutputFile = runnable().workingDirectory / QString("callgrind.out.f%1").arg(++fileCount); - m_controller.setValgrindOutputFile(m_valgrindOutputFile); + m_valgrindOutputFile = runControl->workingDirectory() / QString("callgrind.out.f%1").arg(++fileCount); setupCallgrindRunner(this); } +CallgrindToolRunner::~CallgrindToolRunner() +{ + cleanupTempFile(); +} + QStringList CallgrindToolRunner::toolArguments() const { QStringList arguments = {"--tool=callgrind"}; @@ -105,7 +108,7 @@ QStringList CallgrindToolRunner::toolArguments() const arguments << "--callgrind-out-file=" + m_valgrindOutputFile.path(); - arguments << Utils::ProcessArgs::splitArgs(m_settings.callgrindArguments.value()); + arguments << ProcessArgs::splitArgs(m_settings.callgrindArguments.value()); return arguments; } @@ -117,15 +120,11 @@ QString CallgrindToolRunner::progressTitle() const void CallgrindToolRunner::start() { - appendMessage(tr("Profiling %1").arg(executable().toUserOutput()), Utils::NormalMessageFormat); + const FilePath executable = runControl()->commandLine().executable(); + appendMessage(tr("Profiling %1").arg(executable.toUserOutput()), NormalMessageFormat); return ValgrindToolRunner::start(); } -void CallgrindToolRunner::dump() -{ - m_controller.run(CallgrindController::Dump); -} - void CallgrindToolRunner::setPaused(bool paused) { if (m_markAsPaused == paused) @@ -148,21 +147,6 @@ void CallgrindToolRunner::setToggleCollectFunction(const QString &toggleCollectF m_argumentForToggleCollect = "--toggle-collect=" + toggleCollectFunction; } -void CallgrindToolRunner::reset() -{ - m_controller.run(Callgrind::CallgrindController::ResetEventCounters); -} - -void CallgrindToolRunner::pause() -{ - m_controller.run(Callgrind::CallgrindController::Pause); -} - -void CallgrindToolRunner::unpause() -{ - m_controller.run(Callgrind::CallgrindController::UnPause); -} - Callgrind::ParseData *CallgrindToolRunner::takeParserData() { return m_parser.takeData(); @@ -178,34 +162,140 @@ void CallgrindToolRunner::showStatusMessage(const QString &message) Debugger::showPermanentStatusMessage(message); } +static QString toOptionString(CallgrindToolRunner::Option option) +{ + /* callgrind_control help from v3.9.0 + + Options: + -h --help Show this help text + --version Show version + -s --stat Show statistics + -b --back Show stack/back trace + -e [,...] Show event counters for ,... (default: all) + --dump[=] Request a dump optionally using as description + -z --zero Zero all event counters + -k --kill Kill + --instr= Switch instrumentation state on/off + */ + + switch (option) { + case CallgrindToolRunner::Dump: + return QLatin1String("--dump"); + case CallgrindToolRunner::ResetEventCounters: + return QLatin1String("--zero"); + case CallgrindToolRunner::Pause: + return QLatin1String("--instr=off"); + case CallgrindToolRunner::UnPause: + return QLatin1String("--instr=on"); + default: + return QString(); // never reached + } +} + +void CallgrindToolRunner::run(Option option) +{ + if (m_controllerProcess) { + showStatusMessage(tr("Previous command has not yet finished.")); + return; + } + + // save back current running operation + m_lastOption = option; + + m_controllerProcess.reset(new QtcProcess); + + switch (option) { + case CallgrindToolRunner::Dump: + showStatusMessage(tr("Dumping profile data...")); + break; + case CallgrindToolRunner::ResetEventCounters: + showStatusMessage(tr("Resetting event counters...")); + break; + case CallgrindToolRunner::Pause: + showStatusMessage(tr("Pausing instrumentation...")); + break; + case CallgrindToolRunner::UnPause: + showStatusMessage(tr("Unpausing instrumentation...")); + break; + default: + break; + } + +#if CALLGRIND_CONTROL_DEBUG + m_controllerProcess->setProcessChannelMode(QProcess::ForwardedChannels); +#endif + connect(m_controllerProcess.get(), &QtcProcess::finished, + this, &CallgrindToolRunner::controllerProcessDone); + + const FilePath control = + FilePath(CALLGRIND_CONTROL_BINARY).onDevice(m_valgrindRunnable.command.executable()); + m_controllerProcess->setCommand({control, {toOptionString(option), QString::number(m_pid)}}); + m_controllerProcess->setWorkingDirectory(m_valgrindRunnable.workingDirectory); + m_controllerProcess->setEnvironment(m_valgrindRunnable.environment); + m_controllerProcess->start(); +} + +void CallgrindToolRunner::controllerProcessDone() +{ + const QString error = m_controllerProcess->errorString(); + const ProcessResult result = m_controllerProcess->result(); + + m_controllerProcess.release()->deleteLater(); + + if (result != ProcessResult::FinishedWithSuccess) { + showStatusMessage(tr("An error occurred while trying to run %1: %2").arg(CALLGRIND_CONTROL_BINARY).arg(error)); + qWarning() << "Controller exited abnormally:" << error; + return; + } + + // this call went fine, we might run another task after this + switch (m_lastOption) { + case ResetEventCounters: + // lets dump the new reset profiling info + run(Dump); + return; + case Pause: + m_paused = true; + break; + case Dump: + showStatusMessage(tr("Callgrind dumped profiling info")); + triggerParse(); + break; + case UnPause: + m_paused = false; + showStatusMessage(tr("Callgrind unpaused.")); + break; + default: + break; + } + + m_lastOption = Unknown; +} + void CallgrindToolRunner::triggerParse() { - m_controller.getLocalDataFile(); -} - -void CallgrindToolRunner::handleLocalParseData(const FilePath &outputFile) -{ - QTC_ASSERT(outputFile.exists(), return); - showStatusMessage(tr("Parsing Profile Data...")); - m_parser.parse(outputFile); -} - -void CallgrindToolRunner::controllerFinished(CallgrindController::Option option) -{ - switch (option) + cleanupTempFile(); { - case CallgrindController::Pause: - m_paused = true; - break; - case CallgrindController::UnPause: - m_paused = false; - break; - case CallgrindController::Dump: - triggerParse(); - break; - default: - break; // do nothing + TemporaryFile dataFile("callgrind.out"); + dataFile.open(); + m_hostOutputFile = FilePath::fromString(dataFile.fileName()); } + + const auto afterCopy = [this](bool res) { + QTC_CHECK(res); + QTC_ASSERT(m_hostOutputFile.exists(), return); + showStatusMessage(tr("Parsing Profile Data...")); + m_parser.parse(m_hostOutputFile); + }; + m_valgrindOutputFile.asyncCopyFile(afterCopy, m_hostOutputFile); +} + +void CallgrindToolRunner::cleanupTempFile() +{ + if (!m_hostOutputFile.isEmpty() && m_hostOutputFile.exists()) + m_hostOutputFile.removeFile(); + + m_hostOutputFile.clear(); } } // Internal diff --git a/src/plugins/valgrind/callgrindengine.h b/src/plugins/valgrind/callgrindengine.h index dfee79d95b2..21f7abcce0a 100644 --- a/src/plugins/valgrind/callgrindengine.h +++ b/src/plugins/valgrind/callgrindengine.h @@ -26,11 +26,11 @@ #pragma once #include "valgrindengine.h" -#include "valgrindrunner.h" #include "callgrind/callgrindparsedata.h" #include "callgrind/callgrindparser.h" -#include "callgrind/callgrindcontroller.h" + +#include namespace Valgrind { namespace Internal { @@ -41,16 +41,17 @@ class CallgrindToolRunner : public ValgrindToolRunner public: explicit CallgrindToolRunner(ProjectExplorer::RunControl *runControl); + ~CallgrindToolRunner() override; void start() override; Valgrind::Callgrind::ParseData *takeParserData(); /// controller actions - void dump(); - void reset(); - void pause(); - void unpause(); + void dump() { run(Dump); } + void reset() { run(ResetEventCounters); } + void pause() { run(Pause); } + void unpause() { run(UnPause); } /// marks the callgrind process as paused /// calls pause() and unpause() if there's an active run @@ -58,6 +59,16 @@ public: void setToggleCollectFunction(const QString &toggleCollectFunction); + enum Option { + Unknown, + Dump, + ResetEventCounters, + Pause, + UnPause + }; + + Q_ENUM(Option) + protected: QStringList toolArguments() const override; QString progressTitle() const override; @@ -69,13 +80,32 @@ private: void slotFinished(); void showStatusMessage(const QString &message); + /** + * Make data file available locally, triggers @c localParseDataAvailable. + * + * If the valgrind process was run remotely, this transparently + * downloads the data file first and returns a local path. + */ void triggerParse(); - void handleLocalParseData(const Utils::FilePath &filePath); - void controllerFinished(Callgrind::CallgrindController::Option option); + void controllerFinished(Option option); + + void run(Option option); + + void cleanupTempFile(); + void controllerProcessDone(); bool m_markAsPaused = false; - Utils::FilePath m_valgrindOutputFile; - Callgrind::CallgrindController m_controller; + + std::unique_ptr m_controllerProcess; + ProjectExplorer::Runnable m_valgrindRunnable; + qint64 m_pid = 0; + + Option m_lastOption = Unknown; + + // remote callgrind support + Utils::FilePath m_valgrindOutputFile; // On the device that runs valgrind + Utils::FilePath m_hostOutputFile; // On the device that runs creator + Callgrind::Parser m_parser; bool m_paused = false; diff --git a/src/plugins/valgrind/callgrindtool.cpp b/src/plugins/valgrind/callgrindtool.cpp index f5cef8600de..03606f9a184 100644 --- a/src/plugins/valgrind/callgrindtool.cpp +++ b/src/plugins/valgrind/callgrindtool.cpp @@ -285,9 +285,8 @@ CallgrindToolPrivate::CallgrindToolPrivate() auto runControl = new RunControl(CALLGRIND_RUN_MODE); runControl->copyDataFromRunConfiguration(runConfig); runControl->createMainWorker(); - const auto runnable = dlg.runnable(); - runControl->setRunnable(runnable); - runControl->setDisplayName(runnable.command.executable().toUserOutput()); + runControl->setCommandLine(dlg.commandLine()); + runControl->setWorkingDirectory(dlg.workingDirectory()); ProjectExplorerPlugin::startRunControl(runControl); }); diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index 99cbf51d72e..1fedbcb4a1e 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -28,7 +28,6 @@ #include "memcheckerrorview.h" #include "valgrindsettings.h" -#include "valgrindplugin.h" #include "valgrindengine.h" #include "valgrindsettings.h" #include "valgrindrunner.h" @@ -38,9 +37,6 @@ #include "xmlprotocol/errorlistmodel.h" #include "xmlprotocol/frame.h" #include "xmlprotocol/stack.h" -#include "xmlprotocol/stackmodel.h" -#include "xmlprotocol/status.h" -#include "xmlprotocol/suppression.h" #include "xmlprotocol/threadedparser.h" #include @@ -51,7 +47,7 @@ #include #include -#include +#include #include #include #include @@ -711,9 +707,8 @@ MemcheckToolPrivate::MemcheckToolPrivate() RunControl *rc = new RunControl(MEMCHECK_RUN_MODE); rc->copyDataFromRunConfiguration(runConfig); rc->createMainWorker(); - const auto runnable = dlg.runnable(); - rc->setRunnable(runnable); - rc->setDisplayName(runnable.command.executable().toUserOutput()); + rc->setCommandLine(dlg.commandLine()); + rc->setWorkingDirectory(dlg.workingDirectory()); ProjectExplorerPlugin::startRunControl(rc); }); @@ -746,10 +741,9 @@ void MemcheckToolPrivate::heobAction() kit = target->kit(); if (kit) { abi = ToolChainKitAspect::targetAbi(kit); - - const Runnable runnable = rc->runnable(); - sr = runnable; - const IDevice::ConstPtr device = sr.device; + sr = rc->runnable(); + const IDevice::ConstPtr device + = DeviceManager::deviceForPath(sr.command.executable()); hasLocalRc = device && device->type() == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE; if (!hasLocalRc) hasLocalRc = DeviceTypeKitAspect::deviceTypeId(kit) == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE; @@ -1014,7 +1008,7 @@ void MemcheckToolPrivate::setupRunner(MemcheckToolRunner *runTool) m_loadExternalLogFile->setDisabled(true); const FilePath dir = runControl->project()->projectDirectory(); - const QString name = runTool->executable().fileName(); + const QString name = runControl->commandLine().executable().fileName(); m_errorView->setDefaultSuppressionFile(dir.pathAppended(name + ".supp")); @@ -1672,7 +1666,6 @@ void HeobData::processFinished() auto debugger = new DebuggerRunTool(m_runControl); debugger->setAttachPid(ProcessHandle(m_data[1])); debugger->setRunControlName(tr("Process %1").arg(m_data[1])); - debugger->setInferiorDevice(DeviceKitAspect::device(m_kit)); debugger->setStartMode(AttachToLocalProcess); debugger->setCloseMode(DetachAtClose); debugger->setContinueAfterAttach(true); diff --git a/src/plugins/valgrind/valgrind.qbs b/src/plugins/valgrind/valgrind.qbs index bee21fdf738..8830ba2b8b3 100644 --- a/src/plugins/valgrind/valgrind.qbs +++ b/src/plugins/valgrind/valgrind.qbs @@ -44,7 +44,6 @@ QtcPlugin { files: [ "callgrindabstractmodel.h", "callgrindcallmodel.cpp", "callgrindcallmodel.h", - "callgrindcontroller.cpp", "callgrindcontroller.h", "callgrindcostitem.cpp", "callgrindcostitem.h", "callgrindcycledetection.cpp", "callgrindcycledetection.h", "callgrinddatamodel.cpp", "callgrinddatamodel.h", diff --git a/src/plugins/valgrind/valgrindengine.cpp b/src/plugins/valgrind/valgrindengine.cpp index a5d77b33fad..a6a21e7dcb5 100644 --- a/src/plugins/valgrind/valgrindengine.cpp +++ b/src/plugins/valgrind/valgrindengine.cpp @@ -91,7 +91,7 @@ void ValgrindToolRunner::start() m_runner.setValgrindCommand(valgrind); m_runner.setDevice(device()); - m_runner.setDebuggee(runnable()); + m_runner.setDebuggee(runControl()->runnable()); if (auto aspect = runControl()->aspect()) m_runner.setUseTerminal(aspect->useTerminal); @@ -122,11 +122,6 @@ void ValgrindToolRunner::stop() m_runner.stop(); } -FilePath ValgrindToolRunner::executable() const -{ - return runnable().command.executable(); -} - QStringList ValgrindToolRunner::genericToolArguments() const { QString smcCheckValue; diff --git a/src/plugins/valgrind/valgrindengine.h b/src/plugins/valgrind/valgrindengine.h index 4eb3a119a4c..06e767b7629 100644 --- a/src/plugins/valgrind/valgrindengine.h +++ b/src/plugins/valgrind/valgrindengine.h @@ -47,8 +47,6 @@ public: void start() override; void stop() override; - Utils::FilePath executable() const; - protected: virtual QString progressTitle() const = 0; virtual QStringList toolArguments() const = 0; diff --git a/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp b/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp index 29a2111880a..ecf281ac908 100644 --- a/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp +++ b/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp @@ -97,7 +97,7 @@ public: typedef QPair Pair; - foreach (const Pair &conn, _connections) + for (const Pair &conn : qAsConst(_connections)) out << conn.first.constData() << " -> " << conn.second.constData() << std::endl; alignTerminals(); @@ -113,7 +113,7 @@ public: protected: void alignTerminals() { out<<"{ rank=same;" << std::endl; - foreach (const QByteArray &terminalShape, _terminalShapes) { + for (const QByteArray &terminalShape : qAsConst(_terminalShapes)) { out << " " << std::string(terminalShape.constData(), terminalShape.size()).c_str() << ";" << std::endl; } out<<"}"< parseModes, QByteArray *errors, + const QList parseModes, QByteArray *errors, bool verbose = false) { - foreach (const Document::ParseMode parseMode, parseModes) { + for (const Document::ParseMode parseMode : parseModes) { ErrorHandler *errorHandler = new ErrorHandler(parseMode, errors); // Deleted by ~Document. if (verbose) std::cout << "Parsing as " << qPrintable(parseModeToString(parseMode)) << "..."; @@ -466,7 +466,7 @@ static Document::Ptr parse(const QString &fileName, const QByteArray &source, /// Convenience function static Document::Ptr parse(const QString &fileName, const QByteArray &source, - Document::ParseMode parseMode, QByteArray *errors, + const Document::ParseMode parseMode, QByteArray *errors, bool verbose = false) { QList parseModes = QList() << parseMode; @@ -576,7 +576,7 @@ int main(int argc, char *argv[]) // Process files const QStringList files = args; - foreach (const QString &fileName, files) { + for (const QString &fileName : files) { if (! QFile::exists(fileName)) { std::cerr << "Error: File \"" << qPrintable(fileName) << "\" does not exist." << std::endl; @@ -638,7 +638,7 @@ int main(int argc, char *argv[]) QString(fileName + QLatin1String(".ast.png")))); inputOutputFiles.append(qMakePair(QString(fileName + QLatin1String(".symbols.dot")), QString(fileName + QLatin1String(".symbols.png")))); - foreach (const Pair &pair, inputOutputFiles) { + for (const Pair &pair : qAsConst(inputOutputFiles)) { createImageFromDot(pair.first, pair.second, optionVerbose); std::cout << qPrintable(QDir::toNativeSeparators(pair.second)) << std::endl; } diff --git a/src/tools/cplusplus-frontend/cplusplus-frontend.cpp b/src/tools/cplusplus-frontend/cplusplus-frontend.cpp index 3701a1759c0..5f9bef2cff8 100644 --- a/src/tools/cplusplus-frontend/cplusplus-frontend.cpp +++ b/src/tools/cplusplus-frontend/cplusplus-frontend.cpp @@ -80,7 +80,7 @@ int main(int argc, char *argv[]) // Process files const QStringList files = args; - foreach (const QString &fileName, files) { + for (const QString &fileName : files) { // Run preprocessor const QString fileNamePreprocessed = fileName + QLatin1String(".preprocessed"); CplusplusToolsUtils::SystemPreprocessor preprocessor(optionVerbose); diff --git a/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp b/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp index d834a62db0f..fe3fadd4f7d 100644 --- a/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp +++ b/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp @@ -65,7 +65,7 @@ class MkVisitor: protected SymbolVisitor bool isMiscNode(ClassOrNamespace *b) const { - foreach (ClassOrNamespace *u, b->usings()) { + for (const ClassOrNamespace *u : b->usings()) { if (oo(u->symbols().first()->name()) == QLatin1String("AST")) return true; } @@ -126,11 +126,11 @@ public: << " Semantic(TranslationUnit *unit): ASTVisitor(unit) { translationUnit(unit->ast()->asTranslationUnit()); }" << std::endl << std::endl; - foreach (ClassOrNamespace *b, interfaces) { + for (ClassOrNamespace *b : qAsConst(interfaces)) { Q_ASSERT(! b->symbols().isEmpty()); Class *klass = 0; - foreach (Symbol *s, b->symbols()) + for (Symbol *s : b->symbols()) if ((klass = s->asClass()) != 0) break; @@ -158,9 +158,9 @@ public: << std::endl; QHash > implements; - foreach (ClassOrNamespace *b, nodes) { + for (ClassOrNamespace *b : qAsConst(nodes)) { ClassOrNamespace *iface = 0; - foreach (ClassOrNamespace *u, b->usings()) { + for (ClassOrNamespace *u : b->usings()) { if (interfaces.contains(u)) { iface = u; break; @@ -170,13 +170,14 @@ public: implements[iface].append(b); } - foreach (ClassOrNamespace *iface, interfaces) { - foreach (ClassOrNamespace *b, implements.value(iface)) { + for (ClassOrNamespace *iface : qAsConst(interfaces)) { + const QList values = implements.value(iface); + for (ClassOrNamespace *b : values) { if (! isMiscNode(b)) continue; Class *klass = 0; - foreach (Symbol *s, b->symbols()) + for (Symbol *s : b->symbols()) if ((klass = s->asClass()) != 0) break; @@ -190,11 +191,12 @@ public: std::cout << std::endl; - foreach (ClassOrNamespace *iface, interfaces) { + for (ClassOrNamespace *iface : qAsConst(interfaces)) { std::cout << " // " << qPrintable(oo(iface->symbols().first()->name())) << std::endl; - foreach (ClassOrNamespace *b, implements.value(iface)) { + const QList values = implements.value(iface); + for (ClassOrNamespace *b : values) { Class *klass = 0; - foreach (Symbol *s, b->symbols()) + for (Symbol *s : b->symbols()) if ((klass = s->asClass()) != 0) break; @@ -207,11 +209,11 @@ public: } std::cout << "private:" << std::endl; - foreach (ClassOrNamespace *b, interfaces) { + for (ClassOrNamespace *b : qAsConst(interfaces)) { Q_ASSERT(! b->symbols().isEmpty()); Class *klass = 0; - foreach (Symbol *s, b->symbols()) + for (Symbol *s : b->symbols()) if ((klass = s->asClass()) != 0) break; @@ -240,11 +242,11 @@ public: // implementation - foreach (ClassOrNamespace *b, interfaces) { + for (ClassOrNamespace *b : qAsConst(interfaces)) { Q_ASSERT(! b->symbols().isEmpty()); Class *klass = 0; - foreach (Symbol *s, b->symbols()) + for (Symbol *s : b->symbols()) if ((klass = s->asClass()) != 0) break; @@ -275,11 +277,12 @@ public: << std::endl; } - foreach (ClassOrNamespace *iface, interfaces) { + for (ClassOrNamespace *iface : qAsConst(interfaces)) { std::cout << "// " << qPrintable(oo(iface->symbols().first()->name())) << std::endl; - foreach (ClassOrNamespace *b, implements.value(iface)) { + const QList values = implements.value(iface); + for (ClassOrNamespace *b : values) { Class *klass = 0; - foreach (Symbol *s, b->symbols()) + for (Symbol *s : b->symbols()) if ((klass = s->asClass()) != 0) break; @@ -383,8 +386,8 @@ protected: QList baseClasses(ClassOrNamespace *b) { QList usings = b->usings(); - foreach (ClassOrNamespace *u, usings) - usings += baseClasses(u); + for (int length = usings.size(), i = 0; i < length; ++i) + usings += baseClasses(usings[i]); return usings; } @@ -400,7 +403,7 @@ protected: if (Symbol *s = klass->find(accept0)) { if (Function *meth = s->type()->asFunctionType()) { if (! meth->isPureVirtual()) { - foreach (ClassOrNamespace *u, b->usings()) { + for (const ClassOrNamespace *u : b->usings()) { if (interfaces.contains(u)) { // qDebug() << oo(klass->name()) << "implements" << oo(u->symbols().first()->name()); } else { diff --git a/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp b/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp index df796046822..5fe66c57c1a 100644 --- a/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp +++ b/src/tools/cplusplus-update-frontend/cplusplus-update-frontend.cpp @@ -1294,7 +1294,7 @@ void generateASTMatcher_H(const Snapshot &, const QDir &cplusplusDir, " virtual ~ASTMatcher();\n" "\n"; - foreach (const QByteArray &klass, classes) { + for (const QByteArray &klass : classes) { out << " virtual bool match(" << klass << " *node, " << klass << " *pattern);\n"; } @@ -1340,7 +1340,7 @@ QStringList generateAST_H(const Snapshot &snapshot, const QDir &cplusplusDir, co Overview oo; QStringList castMethods; - foreach (ClassSpecifierAST *classAST, astNodes.deriveds) { + for (ClassSpecifierAST *classAST : qAsConst(astNodes.deriveds)) { cursors[classAST] = removeCastMethods(classAST); const QString className = oo(classAST->symbol->name()); const QString methodName = QLatin1String("as") + className.mid(0, className.length() - 3); diff --git a/src/tools/iostool/iosdevicemanager.cpp b/src/tools/iostool/iosdevicemanager.cpp index 0f24d58868f..2e6aa4ccf36 100644 --- a/src/tools/iostool/iosdevicemanager.cpp +++ b/src/tools/iostool/iosdevicemanager.cpp @@ -560,7 +560,7 @@ void IosDeviceManagerPrivate::addDevice(AMDeviceRef device) m_pendingLookups.remove(devId); devices << m_pendingLookups.values(QString()); m_pendingLookups.remove(QString()); - foreach (PendingDeviceLookup *devLookup, devices) { + for (PendingDeviceLookup *devLookup : qAsConst(devices)) { if (debugAll) qDebug() << "found pending op"; devLookup->timer.stop(); devLookup->callback(devId, device, devLookup->userData); @@ -589,8 +589,10 @@ void IosDeviceManagerPrivate::removeDevice(AMDeviceRef device) void IosDeviceManagerPrivate::checkPendingLookups() { - foreach (const QString &deviceId, m_pendingLookups.keys()) { - foreach (PendingDeviceLookup *deviceLookup, m_pendingLookups.values(deviceId)) { + const QStringList keys = m_pendingLookups.keys(); + for (const QString &deviceId : keys) { + const QList values = m_pendingLookups.values(deviceId); + for (PendingDeviceLookup *deviceLookup : values) { if (!deviceLookup->timer.isActive()) { m_pendingLookups.remove(deviceId, deviceLookup); deviceLookup->callback(deviceId, 0, deviceLookup->userData); diff --git a/src/tools/iostool/mobiledevicelib.cpp b/src/tools/iostool/mobiledevicelib.cpp index 79c4054a0d4..025248262f1 100644 --- a/src/tools/iostool/mobiledevicelib.cpp +++ b/src/tools/iostool/mobiledevicelib.cpp @@ -44,7 +44,8 @@ MobileDeviceLib::MobileDeviceLib() if (!load()) addError(QLatin1String("Error loading MobileDevice.framework")); if (!errors().isEmpty()) { - foreach (const QString &msg, errors()) + const QStringList errs = errors(); + for (const QString &msg : errs) addError(msg); } setLogLevel(5); diff --git a/src/tools/iostool/relayserver.cpp b/src/tools/iostool/relayserver.cpp index 03181a10ac4..312b3bd66d4 100644 --- a/src/tools/iostool/relayserver.cpp +++ b/src/tools/iostool/relayserver.cpp @@ -284,8 +284,7 @@ bool RelayServer::startServer() void RelayServer::stopServer() { - foreach (Relayer *connection, m_connections) - delete connection; + qDeleteAll(m_connections); if (m_ipv4Server.isListening()) m_ipv4Server.close(); if (m_ipv6Server.isListening()) diff --git a/src/tools/qtcreatorcrashhandler/crashhandler.cpp b/src/tools/qtcreatorcrashhandler/crashhandler.cpp index 621361499eb..fa0f4009266 100644 --- a/src/tools/qtcreatorcrashhandler/crashhandler.cpp +++ b/src/tools/qtcreatorcrashhandler/crashhandler.cpp @@ -70,7 +70,7 @@ class CExecList : public QVector public: CExecList(const QStringList &list) { - foreach (const QString &item, list) + for (const QString &item : list) append(qstrdup(item.toLatin1().data())); append(0); } @@ -195,7 +195,7 @@ bool CrashHandler::collectRestartAppData() return false; } commandLine.removeLast(); - foreach (const QByteArray &item, commandLine) + for (const QByteArray &item : qAsConst(commandLine)) d->restartAppCommandLine.append(QString::fromLatin1(item)); // Get environment. @@ -209,7 +209,7 @@ bool CrashHandler::collectRestartAppData() } if (environment.last().isEmpty()) environment.removeLast(); - foreach (const QByteArray &item, environment) + for (const QByteArray &item : qAsConst(environment)) d->restartAppEnvironment.append(QString::fromLatin1(item)); return true; diff --git a/src/tools/qtpromaker/main.cpp b/src/tools/qtpromaker/main.cpp index 60aba5476e9..4fddd6bdd48 100644 --- a/src/tools/qtpromaker/main.cpp +++ b/src/tools/qtpromaker/main.cpp @@ -159,7 +159,7 @@ private: void Project::setPaths(const QStringList &paths) { - foreach (const QString &path, paths) + for (const QString &path : paths) m_items.append(path); } @@ -220,7 +220,8 @@ void Project::handleBinary(const QString &item) // "}] (gdb) int first = input.indexOf(QLatin1Char('{')); input = input.mid(first, input.lastIndexOf(QLatin1Char('}')) - first); - foreach (QString item, input.split(QLatin1String("},{"))) { + const QStringList items = input.split(QLatin1String("},{")); + for (QString item : items) { //qDebug() << "ITEM: " << item; int full = item.indexOf(QLatin1String(",fullname=\"")); if (full != -1) @@ -299,17 +300,17 @@ void Project::writeProFile() if (m_subdirs.isEmpty()) { ts << "TEMPLATE = app\n"; ts << "TARGET = " << QFileInfo(m_outputFileName).baseName() << "\n"; - foreach (const FileClass &fc, m_fileClasses) + for (const FileClass &fc : qAsConst(m_fileClasses)) fc.writeProBlock(ts); ts << "\nPATHS *="; - foreach (const QDir &dir, m_items) + for (const QDir dir : qAsConst(m_items)) ts << " \\\n " << dir.path(); ts << "\n\nDEPENDPATH *= $$PATHS\n"; ts << "\nINCLUDEPATH *= $$PATHS\n"; } else { ts << "TEMPLATE = subdirs\n"; ts << "SUBDIRS = "; - foreach (const QString &subdir, m_subdirs) + for (const QString &subdir : qAsConst(m_subdirs)) ts << " \\\n " << subdir; ts << "\n"; } diff --git a/src/tools/sdktool/addcmakeoperation.cpp b/src/tools/sdktool/addcmakeoperation.cpp index 7a7304bc282..9b89433a42b 100644 --- a/src/tools/sdktool/addcmakeoperation.cpp +++ b/src/tools/sdktool/addcmakeoperation.cpp @@ -229,7 +229,7 @@ QVariantMap AddCMakeData::addCMake(const QVariantMap &map) const data << KeyValuePair({cm, AUTODETECTED_KEY}, QVariant(true)); data << KeyValuePair({cm, PATH_KEY}, Utils::FilePath::fromUserInput(m_path).toVariant()); KeyValuePairList extraList; - foreach (const KeyValuePair &pair, m_extra) + for (const KeyValuePair &pair : qAsConst(m_extra)) extraList << KeyValuePair(QStringList({cm}) << pair.key, pair.value); data.append(extraList); data << KeyValuePair(COUNT, QVariant(count + 1)); @@ -251,7 +251,7 @@ bool AddCMakeData::exists(const QVariantMap &map, const QString &id) // support old settings using QByteArray for id's valueKeys.append(FindValueOperation::findValue(map, id.toUtf8())); - foreach (const QString &k, valueKeys) { + for (const QString &k : qAsConst(valueKeys)) { if (k.endsWith(QString('/') + ID_KEY)) { return true; } diff --git a/src/tools/sdktool/adddebuggeroperation.cpp b/src/tools/sdktool/adddebuggeroperation.cpp index 9adfc6d89d2..24020003e46 100644 --- a/src/tools/sdktool/adddebuggeroperation.cpp +++ b/src/tools/sdktool/adddebuggeroperation.cpp @@ -208,9 +208,9 @@ void AddDebuggerOperation::unittest() QVariantMap AddDebuggerData::addDebugger(const QVariantMap &map) const { // Sanity check: Make sure autodetection source is not in use already: - QStringList valueKeys = FindValueOperation::findValue(map, QVariant(m_id)); + const QStringList valueKeys = FindValueOperation::findValue(map, QVariant(m_id)); bool hasId = false; - foreach (const QString &k, valueKeys) { + for (const QString &k : valueKeys) { if (k.endsWith(QString(QLatin1Char('/')) + QLatin1String(ID))) { hasId = true; break; @@ -250,7 +250,7 @@ QVariantMap AddDebuggerData::addDebugger(const QVariantMap &map) const data << KeyValuePair(QStringList() << QLatin1String(COUNT), QVariant(count + 1)); KeyValuePairList qtExtraList; - foreach (const KeyValuePair &pair, m_extra) + for (const KeyValuePair &pair : qAsConst(m_extra)) qtExtraList << KeyValuePair(QStringList() << debugger << pair.key, pair.value); data.append(qtExtraList); diff --git a/src/tools/sdktool/adddeviceoperation.cpp b/src/tools/sdktool/adddeviceoperation.cpp index 002932f7e9f..d20636fb33e 100644 --- a/src/tools/sdktool/adddeviceoperation.cpp +++ b/src/tools/sdktool/adddeviceoperation.cpp @@ -412,8 +412,8 @@ bool AddDeviceData::exists(const QVariantMap &map, const QString &id) if (id == QLatin1String(INTERNAL_DSEKTOP_DEVICE_ID)) return true; QVariantMap dmMap = map.value(QLatin1String(DEVICEMANAGER_ID)).toMap(); - QVariantList devList = dmMap.value(QLatin1String(DEVICE_LIST_ID)).toList(); - foreach (const QVariant &dev, devList) { + const QVariantList devList = dmMap.value(QLatin1String(DEVICE_LIST_ID)).toList(); + for (const QVariant &dev : devList) { QVariantMap devData = dev.toMap(); QString current = devData.value(QLatin1String(DEVICE_ID_ID)).toString(); if (current == id) diff --git a/src/tools/sdktool/addkeysoperation.cpp b/src/tools/sdktool/addkeysoperation.cpp index 5f471dfc3ed..2d100f315e4 100644 --- a/src/tools/sdktool/addkeysoperation.cpp +++ b/src/tools/sdktool/addkeysoperation.cpp @@ -229,7 +229,7 @@ QVariantMap AddKeysData::addKeys(const QVariantMap &map) const // Insert data: QVariantMap result = map; - foreach (const KeyValuePair &p, m_data) { + for (const KeyValuePair &p : m_data) { QList stack; // Set up a stack of QVariantMaps along the path we take: diff --git a/src/tools/sdktool/addkitoperation.cpp b/src/tools/sdktool/addkitoperation.cpp index 3fbed7a74a3..2befb340c4a 100644 --- a/src/tools/sdktool/addkitoperation.cpp +++ b/src/tools/sdktool/addkitoperation.cpp @@ -737,7 +737,7 @@ QVariantMap AddKitData::addKit(const QVariantMap &map, data << KeyValuePair(COUNT, QVariant(count + 1)); KeyValuePairList qtExtraList; - foreach (const KeyValuePair &pair, m_extra) + for (const KeyValuePair &pair : qAsConst(m_extra)) qtExtraList << KeyValuePair(QStringList() << kit << pair.key, pair.value); data.append(qtExtraList); diff --git a/src/tools/sdktool/addqtoperation.cpp b/src/tools/sdktool/addqtoperation.cpp index 1c4ff7d7808..9a3e8c79496 100644 --- a/src/tools/sdktool/addqtoperation.cpp +++ b/src/tools/sdktool/addqtoperation.cpp @@ -318,7 +318,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const data << KeyValuePair(QStringList() << qt << ABIS, QVariant(m_abis)); KeyValuePairList qtExtraList; - foreach (const KeyValuePair &pair, m_extra) + for (const KeyValuePair &pair : qAsConst(m_extra)) qtExtraList << KeyValuePair(QStringList() << qt << pair.key, pair.value); data.append(qtExtraList); @@ -343,8 +343,8 @@ bool AddQtData::exists(const QVariantMap &map, const QString &id) QString sdkId = extendId(id); // Sanity check: Make sure autodetection source is not in use already: - QStringList valueKeys = FindValueOperation::findValue(map, sdkId); - foreach (const QString &k, valueKeys) { + const QStringList valueKeys = FindValueOperation::findValue(map, sdkId); + for (const QString &k : valueKeys) { if (k.endsWith(QString(QLatin1Char('/')) + QLatin1String(AUTODETECTION_SOURCE))) return true; } diff --git a/src/tools/sdktool/addtoolchainoperation.cpp b/src/tools/sdktool/addtoolchainoperation.cpp index 50991259f55..f5ee2c51f49 100644 --- a/src/tools/sdktool/addtoolchainoperation.cpp +++ b/src/tools/sdktool/addtoolchainoperation.cpp @@ -308,12 +308,12 @@ QVariantMap AddToolChainData::addToolChain(const QVariantMap &map) const data << KeyValuePair({tc, PATH}, Utils::FilePath::fromUserInput(m_path).toVariant()); data << KeyValuePair({tc, TARGET_ABI}, QVariant(m_targetAbi)); QVariantList abis; - QStringList abiStrings = m_supportedAbis.split(','); - foreach (const QString &s, abiStrings) + const QStringList abiStrings = m_supportedAbis.split(','); + for (const QString &s : abiStrings) abis << QVariant(s); data << KeyValuePair({tc, SUPPORTED_ABIS}, QVariant(abis)); KeyValuePairList tcExtraList; - foreach (const KeyValuePair &pair, m_extra) + for (const KeyValuePair &pair : qAsConst(m_extra)) tcExtraList << KeyValuePair(QStringList({tc}) << pair.key, pair.value); data.append(tcExtraList); data << KeyValuePair(COUNT, QVariant(count + 1)); @@ -335,7 +335,7 @@ bool AddToolChainData::exists(const QVariantMap &map, const QString &id) // support old settings using QByteArray for id's valueKeys.append(FindValueOperation::findValue(map, id.toUtf8())); - foreach (const QString &k, valueKeys) { + for (const QString &k : qAsConst(valueKeys)) { if (k.endsWith(QString('/') + ID)) { return true; } diff --git a/src/tools/sdktool/findvalueoperation.cpp b/src/tools/sdktool/findvalueoperation.cpp index 913fc27a42c..51d1139454a 100644 --- a/src/tools/sdktool/findvalueoperation.cpp +++ b/src/tools/sdktool/findvalueoperation.cpp @@ -82,9 +82,9 @@ int FindValueOperation::execute() const Q_ASSERT(!m_values.isEmpty()); QVariantMap map = load(m_file); - foreach (const QVariant &v, m_values) { + for (const QVariant &v : qAsConst(m_values)) { const QStringList result = findValue(map, v); - foreach (const QString &r, result) + for (const QString &r : result) std::cout << qPrintable(r) << std::endl; } diff --git a/src/tools/sdktool/getoperation.cpp b/src/tools/sdktool/getoperation.cpp index 335d34529d9..5f8692f756e 100644 --- a/src/tools/sdktool/getoperation.cpp +++ b/src/tools/sdktool/getoperation.cpp @@ -91,10 +91,10 @@ static QString toString(const QVariant &variant, int indentation = 0) return res; } case QVariant::List: { - QVariantList list = variant.toList(); + const QVariantList list = variant.toList(); QString res; int counter = 0; - foreach (const QVariant &item, list) + for (const QVariant &item : list) res += indent + QString::number(counter++) + QLatin1String(":\n") + toString(item, indentation + 1); return res; } @@ -108,7 +108,7 @@ int GetOperation::execute() const Q_ASSERT(!m_keys.isEmpty()); QVariantMap map = load(m_file); - foreach (const QString &key, m_keys) { + for (const QString &key : qAsConst(m_keys)) { const QVariant result = get(map, key); if (!result.isValid()) std::cout << "" << std::endl; diff --git a/src/tools/sdktool/operation.cpp b/src/tools/sdktool/operation.cpp index 4c1eec03d96..cb6b1d66eb1 100644 --- a/src/tools/sdktool/operation.cpp +++ b/src/tools/sdktool/operation.cpp @@ -55,7 +55,7 @@ QVariant valueFromString(const QString &v) } else if (type == QLatin1String("QVariantList")) { QVariantList list; const QStringList elements = value.split(QLatin1Char(',')); - foreach (const QString &e, elements) + for (const QString &e : elements) list << QVariant(e); return QVariant(list); } diff --git a/src/tools/sdktool/rmkeysoperation.cpp b/src/tools/sdktool/rmkeysoperation.cpp index 9c7f282790e..e567b3baa68 100644 --- a/src/tools/sdktool/rmkeysoperation.cpp +++ b/src/tools/sdktool/rmkeysoperation.cpp @@ -172,7 +172,7 @@ QVariantMap RmKeysOperation::rmKeys(const QVariantMap &map, const QStringList &r { QVariantMap result = map; - foreach (const QString &r, removals) { + for (const QString &r : removals) { QList stack; const QStringList keys = r.split(QLatin1Char('/')); diff --git a/tests/auto/valgrind/callgrind/CMakeLists.txt b/tests/auto/valgrind/callgrind/CMakeLists.txt index 72df53a9d40..76859938aed 100644 --- a/tests/auto/valgrind/callgrind/CMakeLists.txt +++ b/tests/auto/valgrind/callgrind/CMakeLists.txt @@ -11,7 +11,6 @@ extend_qtc_test(tst_callgrindparsertests SOURCES_PREFIX "${PROJECT_SOURCE_DIR}/src/plugins/valgrind/" SOURCES callgrind/callgrindcallmodel.h callgrind/callgrindcallmodel.cpp - callgrind/callgrindcontroller.h callgrind/callgrindcontroller.cpp callgrind/callgrindcostitem.h callgrind/callgrindcostitem.cpp callgrind/callgrindcycledetection.h callgrind/callgrindcycledetection.cpp callgrind/callgrinddatamodel.h callgrind/callgrinddatamodel.cpp