Merge remote-tracking branch 'origin/master' into 8.0

Change-Id: I0ab7200a8696e52122b2739cbc740e940336f962
This commit is contained in:
Eike Ziller
2022-06-01 12:23:29 +02:00
175 changed files with 4579 additions and 2922 deletions

View File

@@ -837,6 +837,8 @@ void Qt5InformationNodeInstanceServer::updateActiveSceneToEditView3D(bool timerC
auto helper = qobject_cast<QmlDesigner::Internal::GeneralHelper *>(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
}

View File

@@ -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
}

View File

@@ -32121,16 +32121,16 @@ Spustil jste Qemu?</translation>
<translation>Qt %1 (%2)</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>Soubor qmake neexistuje nebo není spustitelný</translation>
<source>%1 does not exist or is not executable</source>
<translation>Soubor %1 neexistuje nebo není spustitelný</translation>
</message>
<message>
<source>Qt version is not properly installed, please run make install</source>
<translation>Verze Qt není správně nainstalována. Proveďte, prosím, příkaz &quot;make install&quot;</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Cestu ke spustitelným souborům instalace Qt se nepodařilo určit. Možná je cesta k qmake chybná?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Cestu ke spustitelným souborům instalace Qt se nepodařilo určit. Možná je cesta k %1 chybná?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -35382,16 +35382,16 @@ For flere detaljer, se /etc/sysctl.d/10-ptrace.conf
<translation>Ingen qmake-sti sat</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake findes ikke eller er ikke eksekverbar</translation>
<source>%1 does not exist or is not executable</source>
<translation>%1 findes ikke eller er ikke eksekverbar</translation>
</message>
<message>
<source>Qt version is not properly installed, please run make install</source>
<translation>Qt version er ikke ordentligt installeret, kør venligst make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Kunne ikke beslutte stien til binærene af Qt installationen, måske er qmake-stien forkert?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Kunne ikke beslutte stien til binærene af Qt installationen, måske er %1-stien forkert?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -10113,8 +10113,8 @@ Dies ist unabhängig vom Wert der Eigenschaft &quot;visible&quot; in QML.</trans
<translation>Es ist keine qmake-Pfad gesetzt</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>Die qmake-Datei existiert nicht oder ist nicht ausführbar</translation>
<source>%1 does not exist or is not executable</source>
<translation>Die %1-Datei existiert nicht oder ist nicht ausführbar</translation>
</message>
<message>
<source>Qt version has no name</source>
@@ -10141,8 +10141,8 @@ Dies ist unabhängig vom Wert der Eigenschaft &quot;visible&quot; in QML.</trans
<translation>Die Qt-Version ist nicht richtig installiert, führen Sie bitte make install aus</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Der Pfad zu den ausführbaren Dateien der Qt-Installation konnte nicht bestimmt werden, möglicherweise ist der Pfad zu qmake falsch?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Der Pfad zu den ausführbaren Dateien der Qt-Installation konnte nicht bestimmt werden, möglicherweise ist der Pfad zu %1 falsch?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -33604,8 +33604,8 @@ Nous allons essayer de travailler avec cela mais vous pourrez rencontrer des pro
<translation>Chemin de qmake non spécifié</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake n&apos;existe pas ou n&apos;est pas exécutable</translation>
<source>%1 does not exist or is not executable</source>
<translation>%1 n&apos;existe pas ou n&apos;est pas exécutable</translation>
</message>
<message>
<source>Qt version has no name</source>
@@ -33633,8 +33633,8 @@ Nous allons essayer de travailler avec cela mais vous pourrez rencontrer des pro
<translation>La version de Qt n&apos;est pas correctement installée, veuillez exécuter make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Impossible de déterminer le chemin vers les programmes de Qt, peut-être que le chemin vers qmake est faux ?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Impossible de déterminer le chemin vers les programmes de Qt, peut-être que le chemin vers %1 est faux ?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -41628,7 +41628,7 @@ Saving failed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<source>%1 does not exist or is not executable</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -41636,7 +41636,7 @@ Saving failed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation type="unfinished"></translation>
</message>
<message>

View File

@@ -26108,16 +26108,16 @@ Do you want to save the data first?</source>
<translation>qmake </translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake </translation>
<source>%1 does not exist or is not executable</source>
<translation>%1 </translation>
</message>
<message>
<source>Qt version is not properly installed, please run make install</source>
<translation>Qt make install </translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Qt qmake </translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Qt %1 </translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -12382,8 +12382,8 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM
<translation>Nie ustawiono ścieżki do qmake</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>Brak qmake lub nie jest on plikiem wykonywalnym</translation>
<source>%1 does not exist or is not executable</source>
<translation>Brak %1 lub nie jest on plikiem wykonywalnym</translation>
</message>
<message>
<source>Qt version has no name</source>
@@ -12410,8 +12410,8 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM
<translation>Wersja Qt zainstalowana niepoprawnie, uruchom komendę: make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do qmake.</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Nie można określić ścieżki do plików binarnych instalacji Qt. Sprawdź ścieżkę do %1.</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -45319,8 +45319,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Путь к qmake не указан</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake отсутствует или не запускается</translation>
<source>%1 does not exist or is not executable</source>
<translation>%1 отсутствует или не запускается</translation>
</message>
<message>
<source>Qt version has no name</source>
@@ -45347,8 +45347,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Профиль Qt не установлен, пожалуйста выполните make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Не удалось определить путь к утилитам Qt. Может путь к qmake неверен?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Не удалось определить путь к утилитам Qt. Может путь к %1 неверен?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -21803,8 +21803,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi.</t
</message>
<message>
<location line="+58"/>
<source>qmake does not exist or is not executable</source>
<translation>Datoteka »qmake« ne obstaja ali pa ni izvršljiva</translation>
<source>%1 does not exist or is not executable</source>
<translation>Datoteka »%1« ne obstaja ali pa ni izvršljiva</translation>
</message>
<message>
<location line="+2"/>
@@ -21813,8 +21813,8 @@ Projekte programov QML izvede pregledovalnik QML in jih ni potrebno zgraditi.</t
</message>
<message>
<location line="+2"/>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do qmake napačna?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Ni bilo moč ugotoviti poti do programov namestitve Qt. Morda je pot do %1 napačna?</translation>
</message>
<message>
<location line="+3"/>

View File

@@ -22711,8 +22711,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Шлях до qmake не задано</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake не існує або не є виконуваним модулем</translation>
<source>%1 does not exist or is not executable</source>
<translation>%1 не існує або не є виконуваним модулем</translation>
</message>
<message>
<source>ABI detection failed: Make sure to use a matching compiler when building.</source>
@@ -22763,8 +22763,8 @@ For more details, see /etc/sysctl.d/10-ptrace.conf
<translation>Версія Qt не встановлена як слід, будь ласка, запустіть make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до qmake помилковий?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Не вдалось визначити шлях до виконуваних модулів встановлення Qt, можливо, шлях до %1 помилковий?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -27948,8 +27948,8 @@ Did you start Qemu?</source>
<translation>qmake路径</translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake不存在或者不可执</translation>
<source>%1 does not exist or is not executable</source>
<translation>%1</translation>
</message>
<message>
<source>Qt version has no name</source>
@@ -27976,8 +27976,8 @@ Did you start Qemu?</source>
<translation>Qt没有被正确安装make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation>Qt安装的二进制所在的路径qmake的路径设置出现了错?</translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation>Qt安装的二进制所在的路径%1?</translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -15636,8 +15636,8 @@ Requires &lt;b&gt;Qt 4.7.4&lt;/b&gt; or newer.</source>
<translation> qmake </translation>
</message>
<message>
<source>qmake does not exist or is not executable</source>
<translation>qmake </translation>
<source>%1 does not exist or is not executable</source>
<translation>%1 </translation>
</message>
<message>
<source>Qt version has no name</source>
@@ -15664,8 +15664,8 @@ Requires &lt;b&gt;Qt 4.7.4&lt;/b&gt; or newer.</source>
<translation>Qt make install</translation>
</message>
<message>
<source>Could not determine the path to the binaries of the Qt installation, maybe the qmake path is wrong?</source>
<translation> Qt qmake </translation>
<source>Could not determine the path to the binaries of the Qt installation, maybe the %1 path is wrong?</source>
<translation> Qt %1 </translation>
</message>
<message>
<source>The default mkspec symlink is broken.</source>

View File

@@ -629,7 +629,7 @@ ClassOrNamespace *ClassOrNamespace::parent() const
return _parent;
}
QList<ClassOrNamespace *> ClassOrNamespace::usings() const
const QList<ClassOrNamespace *> ClassOrNamespace::usings() const
{
const_cast<ClassOrNamespace *>(this)->flush();
return _usings;
@@ -641,7 +641,7 @@ QList<Enum *> ClassOrNamespace::unscopedEnums() const
return _enums;
}
QList<Symbol *> ClassOrNamespace::symbols() const
const QList<Symbol *> ClassOrNamespace::symbols() const
{
const_cast<ClassOrNamespace *>(this)->flush();
return _symbols;

View File

@@ -70,9 +70,9 @@ public:
ClassOrNamespace *instantiationOrigin() const;
ClassOrNamespace *parent() const;
QList<ClassOrNamespace *> usings() const;
const QList<ClassOrNamespace *> usings() const;
QList<Enum *> unscopedEnums() const;
QList<Symbol *> symbols() const;
const QList<Symbol *> symbols() const;
ClassOrNamespace *globalNamespace() const;

View File

@@ -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<QString> 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();
}
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();
}
QString BuildableHelperLibrary::filterForQmakeFileDialog()
{
QString filter = QLatin1String("qmake (");
const QStringList commands = possibleQMakeCommands();
for (int i = 0; i < commands.size(); ++i) {
if (i)
filter += QLatin1Char(' ');
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 *
filter += QLatin1Char('*');
filter += commands.at(i);
toolFilter += QLatin1Char('*');
toolFilter += tool;
if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
// kde bug, we need at least one wildcard character
// see QTCREATORBUG-7771
filter += QLatin1Char('*');
toolFilter += QLatin1Char('*');
toolFilters.append(toolFilter);
}
filter += QLatin1Char(')');
return filter;
}
return queryToolNames().join(", ") + " (" + toolFilters.join(" ") + ")";
}
QStringList BuildableHelperLibrary::possibleQMakeCommands()
QStringList BuildableHelperLibrary::possibleQtQueryTools(const QString &tool)
{
// On Windows it is always "qmake.exe"
// On Windows it is "<queryTool>.exe" or "<queryTool>.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 <tool>, 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

View File

@@ -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

View File

@@ -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;

View File

@@ -192,7 +192,7 @@ public:
EnvironmentChange m_environmentChange;
BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr;
QList<QAbstractButton *> m_buttons;
MacroExpander *m_macroExpander = globalMacroExpander();
const MacroExpander *m_macroExpander = globalMacroExpander();
std::function<void()> 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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -28,11 +28,11 @@
#include "androidconfigurations.h"
#include "androidconstants.h"
#include "androidmanager.h"
#include "androidrunconfiguration.h"
#include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/runconfigurationaspects.h>
@@ -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);

View File

@@ -32,6 +32,7 @@
#include "../itestframework.h"
#include "../testsettings.h"
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/project.h>

View File

@@ -53,7 +53,7 @@ public:
exeAspect->setPlaceHolderText(tr("Unknown"));
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(nullptr);
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
setUpdater([this, exeAspect] {
const BuildTargetInfo bti = buildTargetInfo();
@@ -80,7 +80,7 @@ public:
exeAspect->setExpectedKind(PathChooser::Any);
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(nullptr);
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
setDefaultDisplayName(RunConfigurationFactory::decoratedTargetName(tr("Custom Executable"), target));
}

View File

@@ -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<ArgumentsAspect>())
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;
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -102,7 +102,7 @@ QdbRunConfiguration::QdbRunConfiguration(Target *target, Id id)
auto envAspect = addAspect<RemoteLinux::RemoteLinuxEnvironmentAspect>(target);
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect);
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<FullCommandLineAspect>(this);
setUpdater([this, target, exeAspect, symbolsAspect] {

View File

@@ -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
)

View File

@@ -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 {

File diff suppressed because it is too large Load Diff

View File

@@ -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 <cppeditor/semantichighlighter.h>
#include <languageclient/semantichighlightsupport.h>
#include <languageserverprotocol/lsptypes.h>
#include <texteditor/blockrange.h>
#include <texteditor/textstyles.h>
#include <QtConcurrent>
#include <QTextDocument>
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<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc,
const QString &docContent)
{
QList<BlockRange> 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<HighlightingResult> &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<HighlightingResult> &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<HighlightingResult> &future,
const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents,
const ClangdAstNode &ast,
const QPointer<TextDocument> &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<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
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<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
// 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<ClangdAstNode> 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<ClangdAstNode>());
}
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<ClangdAstNode> children = it->children().value_or(QList<ClangdAstNode>());
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<HighlightingResult(const ExpandedSemanticToken &)> 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<HighlightingResults>(tokens, toResult);
const QList<BlockRange> 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<Range> 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<HighlightingResult>(results.cbegin(), results.cend()));
}
future.reportFinished();
}
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
QFutureInterface<HighlightingResult> &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<ClangdAstNode> children = node.children().value_or(QList<ClangdAstNode>());
// 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

View File

@@ -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 <QFutureInterface>
#include <QLoggingCategory>
#include <QPointer>
#include <QVersionNumber>
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<TextEditor::HighlightingResult> &future,
const Utils::FilePath &filePath,
const QList<LanguageClient::ExpandedSemanticToken> &tokens,
const QString &docContents,
const ClangdAstNode &ast,
const QPointer<TextEditor::TextDocument> &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

View File

@@ -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 <utils/qtcassert.h>
#include <QDateTime>
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

View File

@@ -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 <QElapsedTimer>
#include <QLoggingCategory>
#include <QString>
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

View File

@@ -51,6 +51,7 @@
#include <debugger/analyzer/analyzermanager.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorericons.h>

View File

@@ -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;

View File

@@ -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 <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <cppeditor/cppmodelmanager.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildtargettype.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/textmark.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>

View File

@@ -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."));

View File

@@ -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

View File

@@ -29,7 +29,10 @@
#include <QDialog>
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();

View File

@@ -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

View File

@@ -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

View File

@@ -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())

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -25,23 +25,21 @@
#include "loadcoredialog.h"
#include "debuggerdialogs.h"
#include "debuggerkitinformation.h"
#include "gdb/gdbengine.h"
#include <projectexplorer/devicesupport/devicefilesystemmodel.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/kitchooser.h>
#include <utils/pathchooser.h>
#include <utils/processinterface.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/temporaryfile.h>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QFutureWatcher>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
@@ -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<bool> *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<bool> &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<bool>(this);
auto future = runAsync(copyFile, m_remoteFile, m_localFile);
connect(m_watcher, &QFutureWatcher<bool>::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();
}
///////////////////////////////////////////////////////////////////////

View File

@@ -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)

View File

@@ -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

View File

@@ -27,6 +27,7 @@
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/idevicefactory.h>
#include <coreplugin/documentmanager.h>
#include <utils/aspects.h>
@@ -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

View File

@@ -25,8 +25,6 @@
#include "kitdetector.h"
#include "dockerconstants.h"
#include <cmakeprojectmanager/cmakeprojectconstants.h>
#include <extensionsystem/pluginmanager.h>
@@ -216,7 +214,7 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const
QString error;
const auto handleQmake = [this, &qtVersions, &error](const FilePath &qmake) {
if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake,
if (QtVersion *qtVersion = QtVersionFactory::createQtVersionFromQueryToolPath(qmake,
false,
m_sharedId,
&error)) {
@@ -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);

View File

@@ -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

View File

@@ -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",

View File

@@ -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 <coreplugin/documentmanager.h>
#include <coreplugin/shellcommand.h>
#include <coreplugin/vcsmanager.h>
#include <git/gitclient.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/fancylineedit.h>
#include <utils/filepath.h>
#include <utils/infolabel.h>
#include <utils/mimeutils.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QVBoxLayout>
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<int>::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

View File

@@ -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 <QCoreApplication>
#include <QDialog>
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

View File

@@ -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 <projectexplorer/session.h>
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>
#include <utils/listmodel.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QSyntaxHighlighter>
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<int>::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<GitLabServer>().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<Project *> *listModel = new Utils::ListModel<Project *>(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<Project>();
QTC_ASSERT(!project.sshUrl.isEmpty() && !project.httpUrl.isEmpty(), return);
GitLabCloneDialog dialog(project, this);
if (dialog.exec() == QDialog::Accepted)
reject();
}
} // namespace GitLab

View File

@@ -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 <utils/filepath.h>
#include <utils/id.h>
namespace Internal { class AbstractUploadAndInstallPackageServicePrivate; }
#include <QDialog>
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

View File

@@ -0,0 +1,268 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GitLab::GitLabDialog</class>
<widget class="QDialog" name="GitLab::GitLabDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>665</width>
<height>530</height>
</rect>
</property>
<property name="windowTitle">
<string>GitLab</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="mainLabel">
<property name="text">
<string>Login</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="detailsLabel">
<property name="text">
<string>Details</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_0">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Remote:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="remoteCB">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="treeViewTitle">
<property name="text">
<string>Projects</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLineEdit" name="searchLE">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>Search</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchPB">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QTreeView" name="treeView">
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QToolButton" name="firstTB">
<property name="text">
<string notr="true">|&lt;</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="previousTB">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="currentPage">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="nextTB">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="lastTB">
<property name="text">
<string notr="true">&gt;|</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>GitLab::GitLabDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -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 <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h>
#include <git/gitplugin.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectpanelfactory.h>
#include <projectexplorer/session.h>
#include <utils/qtcassert.h>
#include <vcsbase/vcsoutputwindow.h>
#include <QAction>
#include <QMessageBox>
#include <QPointer>
#include <QTimer>
namespace GitLab {
namespace Constants {
const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView";
} // namespace Constants
class GitLabPluginPrivate
class GitLabPluginPrivate : public QObject
{
public:
GitLabParameters parameters;
GitLabOptionsPage optionsPage{&parameters};
QHash<ProjectExplorer::Project *, GitLabProjectSettings *> projectSettings;
QPointer<GitLabDialog> 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(&notificationTimer, &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<GitLabServer> 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

View File

@@ -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

View File

@@ -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);

View File

@@ -28,6 +28,7 @@
#include <projectexplorer/projectsettingswidget.h>
#include <utils/id.h>
#include <QDateTime>
#include <QObject>
#include <QWidget>
@@ -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<QString, QString, int> 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;
};

View File

@@ -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 &parameter)
: m_type(type)
@@ -48,6 +51,21 @@ Query::Query(Type type, const QStringList &parameter)
{
}
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;

View File

@@ -38,15 +38,24 @@ class Query
public:
enum Type {
NoQuery,
Project
User,
Project,
Projects,
Events
};
explicit Query(Type type, const QStringList &parameters = {});
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

View File

@@ -32,8 +32,59 @@
#include <utility>
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<QByteArray, QByteArray> 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<Error, QJsonObject> preHandleSingle(const QByteArray &json)
{
Error result;
@@ -59,6 +110,53 @@ static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
return std::make_pair(result, object);
}
static std::pair<Error, QJsonDocument> 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;

View File

@@ -25,6 +25,8 @@
#pragma once
#include <QList>
#include <QMetaType>
#include <QString>
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<Project> 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<Event> 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)

View File

@@ -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());

View File

@@ -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<bool> registered = d->m_dynamicCapabilities.isRegistered(method)) {

View File

@@ -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<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic> &diagnostics) const
@@ -98,6 +98,17 @@ QList<Diagnostic> DiagnosticManager::filteredDiagnostics(const QList<Diagnostic>
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<TextEditor::TextMark *> &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<TextEditor::TextMark *> &marks : qAsConst(m_marks))
qDeleteAll(marks);
m_marks.clear();
}
QTC_ASSERT(m_marks.isEmpty(), m_marks.clear());
}
QList<Diagnostic> 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

View File

@@ -61,6 +61,7 @@ public:
virtual QList<LanguageServerProtocol::Diagnostic> filteredDiagnostics(
const QList<LanguageServerProtocol::Diagnostic> &diagnostics) const;
void disableDiagnostics(TextEditor::TextDocument *document);
void clearDiagnostics();
QList<LanguageServerProtocol::Diagnostic> diagnosticsAt(
@@ -91,7 +92,14 @@ private:
QList<LanguageServerProtocol::Diagnostic> diagnostics;
};
QMap<LanguageServerProtocol::DocumentUri, VersionedDiagnostics> m_diagnostics;
QMap<Utils::FilePath, QList<TextEditor::TextMark *>> m_marks;
class Marks
{
public:
~Marks();
bool enabled = true;
QList<TextEditor::TextMark *> marks;
};
QMap<Utils::FilePath, Marks> m_marks;
Client *m_client;
Utils::Id m_extraSelectionsId;
};

View File

@@ -46,7 +46,7 @@ MesonRunConfiguration::MesonRunConfiguration(ProjectExplorer::Target *target, Ut
addAspect<ProjectExplorer::ExecutableAspect>(target);
addAspect<ProjectExplorer::ArgumentsAspect>(macroExpander());
addAspect<ProjectExplorer::WorkingDirectoryAspect>(envAspect);
addAspect<ProjectExplorer::WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<ProjectExplorer::TerminalAspect>();
auto libAspect = addAspect<ProjectExplorer::UseLibraryPathsAspect>();

View File

@@ -54,7 +54,7 @@ public:
auto envAspect = addAspect<LocalEnvironmentAspect>(target);
addAspect<ExecutableAspect>(target);
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect);
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>();
setUpdater([this] {
@@ -91,7 +91,8 @@ public:
{
addAspect<ExecutableAspect>(target)->setExecutable(Nim::nimblePathFromKit(target->kit()));
addAspect<ArgumentsAspect>(macroExpander())->setArguments("test");
addAspect<WorkingDirectoryAspect>(nullptr)->setDefaultWorkingDirectory(project()->projectDirectory());
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr)
->setDefaultWorkingDirectory(project()->projectDirectory());
addAspect<TerminalAspect>();
setDisplayName(tr("Nimble Test"));

View File

@@ -52,7 +52,7 @@ public:
auto envAspect = addAspect<LocalEnvironmentAspect>(target);
addAspect<ExecutableAspect>(target);
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect);
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>();
setDisplayName(tr("Current Build Target"));

View File

@@ -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();

View File

@@ -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

View File

@@ -44,6 +44,7 @@
#include <extensionsystem/invoker.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/algorithm.h>
#include <utils/outputformatter.h>
#include <utils/qtcassert.h>
@@ -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));

View File

@@ -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 <coreplugin/icore.h>
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/devicesupport/idevice.h>
#include <utils/algorithm.h>
#include <utils/outputformatter.h>
#include <utils/runextensions.h>
@@ -118,7 +120,8 @@ static int queue(const QList<Project *> &projects, const QList<Id> &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<Project *> &projects, const QList<Id> &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;
}
}

View File

@@ -53,7 +53,7 @@ CustomExecutableRunConfiguration::CustomExecutableRunConfiguration(Target *targe
exeAspect->setEnvironmentChange(EnvironmentChange::fromFixedEnvironment(envAspect->environment()));
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect);
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>();
connect(envAspect, &EnvironmentAspect::environmentChanged, this, [exeAspect, envAspect] {

View File

@@ -70,7 +70,7 @@ DesktopRunConfiguration::DesktopRunConfiguration(Target *target, Id id, Kind kin
addAspect<ExecutableAspect>(target);
addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(envAspect);
addAspect<WorkingDirectoryAspect>(macroExpander(), envAspect);
addAspect<TerminalAspect>();
auto libAspect = addAspect<UseLibraryPathsAspect>();

View File

@@ -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 <utils/processinterface.h>
#include <utils/qtcassert.h>
#include <QProcess>
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<FileTransferInterface> 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"

View File

@@ -25,40 +25,17 @@
#pragma once
#include "remotelinux_export.h"
#include <projectexplorer/devicesupport/idevicefwd.h>
#include <utils/filepath.h>
#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<FileToTransfer>;
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

View File

@@ -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 <utils/filepath.h>
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<FileToTransfer>;
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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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<Kit *> kits = KitManager::kits();

View File

@@ -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;

View File

@@ -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)

View File

@@ -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<QPair<Runnable, ProcessHandle>> ProjectExplorerPlugin::runningRunControlProcesses()
QList<QPair<CommandLine, ProcessHandle>> ProjectExplorerPlugin::runningRunControlProcesses()
{
QList<QPair<Runnable, ProcessHandle>> processes;
QList<QPair<CommandLine, ProcessHandle>> processes;
const QList<RunControl *> 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);

View File

@@ -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<QPair<Runnable, Utils::ProcessHandle>> runningRunControlProcesses();
static QList<QPair<Utils::CommandLine, Utils::ProcessHandle>> runningRunControlProcesses();
static QList<RunControl *> allRunControls();
static void addExistingFiles(FolderNode *folderNode, const Utils::FilePaths &filePaths);

View File

@@ -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",

View File

@@ -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");

View File

@@ -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<Utils::PathChooser> m_chooser;
QPointer<QToolButton> m_resetButton;
Utils::MacroExpander *m_macroExpander = nullptr;
const Utils::MacroExpander *m_macroExpander = nullptr;
};
class PROJECTEXPLORER_EXPORT ArgumentsAspect : public Utils::BaseAspect

View File

@@ -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<Utils::Id, QVariantMap> 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<const DesktopDevice>();
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 DesktopDevice>();
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<const DesktopDevice>();
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<void ()> &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<OutputFormatterFactory *> g_outputFormatterFactories;

View File

@@ -25,9 +25,7 @@
#pragma once
#include "buildconfiguration.h"
#include "devicesupport/idevicefwd.h"
#include "projectexplorerconstants.h"
#include "runconfiguration.h"
#include <utils/commandline.h>
@@ -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(),

View File

@@ -89,23 +89,8 @@ 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);
}

View File

@@ -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;

View File

@@ -140,9 +140,13 @@ private:
class PythonRunConfiguration : public RunConfiguration
{
public:
PythonRunConfiguration(Target *target, Id id)
PythonRunConfiguration(Target *target, Id id);
void currentInterpreterChanged();
};
PythonRunConfiguration::PythonRunConfiguration(Target *target, Id id)
: RunConfiguration(target, id)
{
{
auto interpreterAspect = addAspect<InterpreterAspect>();
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
interpreterAspect->setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
@@ -156,8 +160,7 @@ public:
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(
project()->projectDirectory());
interpreterAspect->updateInterpreters(PythonSettings::interpreters());
Interpreter defaultInterpreter = interpreters.isEmpty()
? PythonSettings::defaultInterpreter()
Interpreter defaultInterpreter = interpreters.isEmpty() ? PythonSettings::defaultInterpreter()
: interpreters.first();
if (!defaultInterpreter.command.isExecutableFile())
defaultInterpreter = PythonSettings::interpreters().value(0);
@@ -178,7 +181,7 @@ public:
auto argumentsAspect = addAspect<ArgumentsAspect>(macroExpander());
addAspect<WorkingDirectoryAspect>(nullptr);
addAspect<WorkingDirectoryAspect>(macroExpander(), nullptr);
addAspect<TerminalAspect>();
setCommandLineGetter([bufferedAspect, interpreterAspect, argumentsAspect, scriptAspect] {
@@ -199,15 +202,27 @@ public:
});
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
}
void PythonRunConfiguration::currentInterpreterChanged()
{
const FilePath python = aspect<InterpreterAspect>()->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<InterpreterAspect>()->currentInterpreter().command;
BuildStepList *buildSteps = target()->activeBuildConfiguration()->buildSteps();
if (auto pySideBuildStep = buildSteps->firstOfType<PySideBuildStep>())
pySideBuildStep->updateInterpreter(python);
pySideBuildStep->updatePySideProjectPath(pySideProjectPath);
for (FilePath &file : project()->files(Project::AllFiles)) {
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
@@ -217,8 +232,7 @@ public:
}
}
}
}
};
}
PythonRunConfigurationFactory::PythonRunConfigurationFactory()
{

Some files were not shown because too many files have changed in this diff Show More