forked from qt-creator/qt-creator
Merge "Merge remote-tracking branch 'origin/14.0' into qds/dev" into qds/dev
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
add_subdirectory(coreplugin)
|
||||
|
||||
# Level 1: (only depends of Level 0)
|
||||
add_subdirectory(lua)
|
||||
add_subdirectory(texteditor)
|
||||
add_subdirectory(serialterminal)
|
||||
add_subdirectory(extensionmanager)
|
||||
@@ -47,7 +48,6 @@ add_subdirectory(autotoolsprojectmanager)
|
||||
add_subdirectory(bazaar)
|
||||
add_subdirectory(beautifier)
|
||||
add_subdirectory(clearcase)
|
||||
add_subdirectory(cmakeprojectmanager)
|
||||
add_subdirectory(cvs)
|
||||
add_subdirectory(designer)
|
||||
add_subdirectory(docker)
|
||||
@@ -79,6 +79,7 @@ if (WITH_QMLDESIGNER)
|
||||
endif()
|
||||
add_subdirectory(python)
|
||||
add_subdirectory(clangformat)
|
||||
add_subdirectory(cmakeprojectmanager)
|
||||
|
||||
# Level 7:
|
||||
add_subdirectory(android)
|
||||
@@ -98,6 +99,7 @@ add_subdirectory(perfprofiler)
|
||||
add_subdirectory(qbsprojectmanager)
|
||||
add_subdirectory(ctfvisualizer)
|
||||
add_subdirectory(squish)
|
||||
add_subdirectory(appstatisticsmonitor)
|
||||
|
||||
# Level 8:
|
||||
add_subdirectory(boot2qt)
|
||||
@@ -117,4 +119,3 @@ endif()
|
||||
add_subdirectory(qnx)
|
||||
add_subdirectory(mcusupport)
|
||||
add_subdirectory(qtapplicationmanager)
|
||||
|
||||
|
||||
@@ -13,8 +13,14 @@
|
||||
"Alternatively, this plugin 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 plugin. 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."
|
||||
],
|
||||
"Category" : "Device Support",
|
||||
"Description" : "Support for deployment to and execution on Android Devices.",
|
||||
"Url" : "http://necessitas.kde.org",
|
||||
"Description" : "Develop applications for Android devices.",
|
||||
"LongDescription" : [
|
||||
"Connect devices with USB to run, debug, and analyze applications built for them.",
|
||||
"You also need:",
|
||||
"- Qt for Android",
|
||||
"- Android development tools"
|
||||
],
|
||||
"Url" : "https://www.qt.io",
|
||||
${IDE_PLUGIN_DEPENDENCIES},
|
||||
|
||||
"Mimetypes" : [
|
||||
|
||||
@@ -34,7 +34,7 @@ add_qtc_plugin(Android
|
||||
androidrunnerworker.cpp androidrunnerworker.h
|
||||
androidsdkdownloader.cpp androidsdkdownloader.h
|
||||
androidsdkmanager.cpp androidsdkmanager.h
|
||||
androidsdkmanagerwidget.cpp androidsdkmanagerwidget.h
|
||||
androidsdkmanagerdialog.cpp androidsdkmanagerdialog.h
|
||||
androidsdkmodel.cpp androidsdkmodel.h
|
||||
androidsdkpackage.cpp androidsdkpackage.h
|
||||
androidsettingswidget.cpp androidsettingswidget.h
|
||||
|
||||
@@ -73,8 +73,8 @@ QtcPlugin {
|
||||
"androidsdkdownloader.h",
|
||||
"androidsdkmanager.cpp",
|
||||
"androidsdkmanager.h",
|
||||
"androidsdkmanagerwidget.cpp",
|
||||
"androidsdkmanagerwidget.h",
|
||||
"androidsdkmanagerdialog.cpp",
|
||||
"androidsdkmanagerdialog.h",
|
||||
"androidsdkmodel.cpp",
|
||||
"androidsdkmodel.h",
|
||||
"androidsdkpackage.cpp",
|
||||
|
||||
@@ -2,212 +2,28 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "androidavdmanager.h"
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidtr.h"
|
||||
#include "avdmanageroutputparser.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/async.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace Utils;
|
||||
using namespace std;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
const int avdCreateTimeoutMs = 30000;
|
||||
namespace Android::Internal::AndroidAvdManager {
|
||||
|
||||
static Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg)
|
||||
|
||||
/*!
|
||||
Runs the \c avdmanager tool specific to configuration \a config with arguments \a args. Returns
|
||||
\c true if the command is successfully executed. Output is copied into \a output. The function
|
||||
blocks the calling thread.
|
||||
*/
|
||||
bool AndroidAvdManager::avdManagerCommand(const QStringList &args, QString *output)
|
||||
{
|
||||
CommandLine cmd(androidConfig().avdManagerToolPath(), args);
|
||||
Process proc;
|
||||
proc.setEnvironment(androidConfig().toolsEnvironment());
|
||||
qCDebug(avdManagerLog).noquote() << "Running AVD Manager command:" << cmd.toUserOutput();
|
||||
proc.setCommand(cmd);
|
||||
proc.runBlocking();
|
||||
if (proc.result() == ProcessResult::FinishedWithSuccess) {
|
||||
if (output)
|
||||
*output = proc.allOutput();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool checkForTimeout(const chrono::steady_clock::time_point &start,
|
||||
int msecs = 3000)
|
||||
{
|
||||
bool timedOut = false;
|
||||
auto end = chrono::steady_clock::now();
|
||||
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
|
||||
timedOut = true;
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
static CreateAvdInfo createAvdCommand(const CreateAvdInfo &info)
|
||||
{
|
||||
CreateAvdInfo result = info;
|
||||
|
||||
if (!result.isValid()) {
|
||||
qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name
|
||||
<< result.systemImage->displayText() << result.systemImage->apiLevel();
|
||||
result.error = Tr::tr("Cannot create AVD. Invalid input.");
|
||||
return result;
|
||||
}
|
||||
|
||||
CommandLine avdManager(androidConfig().avdManagerToolPath(), {"create", "avd", "-n", result.name});
|
||||
avdManager.addArgs({"-k", result.systemImage->sdkStylePath()});
|
||||
|
||||
if (result.sdcardSize > 0)
|
||||
avdManager.addArgs({"-c", QString("%1M").arg(result.sdcardSize)});
|
||||
|
||||
if (!result.deviceDefinition.isEmpty() && result.deviceDefinition != "Custom")
|
||||
avdManager.addArgs({"-d", QString("%1").arg(result.deviceDefinition)});
|
||||
|
||||
if (result.overwrite)
|
||||
avdManager.addArg("-f");
|
||||
|
||||
qCDebug(avdManagerLog).noquote() << "Running AVD Manager command:" << avdManager.toUserOutput();
|
||||
Process proc;
|
||||
proc.setProcessMode(ProcessMode::Writer);
|
||||
proc.setEnvironment(androidConfig().toolsEnvironment());
|
||||
proc.setCommand(avdManager);
|
||||
proc.start();
|
||||
if (!proc.waitForStarted()) {
|
||||
result.error = Tr::tr("Could not start process \"%1\".").arg(avdManager.toUserOutput());
|
||||
return result;
|
||||
}
|
||||
QTC_CHECK(proc.isRunning());
|
||||
proc.write("yes\n"); // yes to "Do you wish to create a custom hardware profile"
|
||||
|
||||
auto start = chrono::steady_clock::now();
|
||||
QString errorOutput;
|
||||
QByteArray question;
|
||||
while (errorOutput.isEmpty()) {
|
||||
proc.waitForReadyRead(500ms);
|
||||
question += proc.readAllRawStandardOutput();
|
||||
if (question.endsWith(QByteArray("]:"))) {
|
||||
// truncate to last line
|
||||
int index = question.lastIndexOf(QByteArray("\n"));
|
||||
if (index != -1)
|
||||
question = question.mid(index);
|
||||
if (question.contains("hw.gpu.enabled"))
|
||||
proc.write("yes\n");
|
||||
else
|
||||
proc.write("\n");
|
||||
question.clear();
|
||||
}
|
||||
// The exit code is always 0, so we need to check stderr
|
||||
// For now assume that any output at all indicates a error
|
||||
errorOutput = QString::fromLocal8Bit(proc.readAllRawStandardError());
|
||||
if (!proc.isRunning())
|
||||
break;
|
||||
|
||||
// For a sane input and command, process should finish before timeout.
|
||||
if (checkForTimeout(start, avdCreateTimeoutMs))
|
||||
result.error = Tr::tr("Cannot create AVD. Command timed out.");
|
||||
}
|
||||
|
||||
result.error = errorOutput;
|
||||
return result;
|
||||
}
|
||||
|
||||
AndroidAvdManager::AndroidAvdManager() = default;
|
||||
|
||||
AndroidAvdManager::~AndroidAvdManager() = default;
|
||||
|
||||
QFuture<CreateAvdInfo> AndroidAvdManager::createAvd(CreateAvdInfo info) const
|
||||
{
|
||||
return Utils::asyncRun(&createAvdCommand, info);
|
||||
}
|
||||
|
||||
static void avdConfigEditManufacturerTag(const FilePath &avdPath, bool recoverMode = false)
|
||||
{
|
||||
if (!avdPath.exists())
|
||||
return;
|
||||
|
||||
const FilePath configFilePath = avdPath / "config.ini";
|
||||
FileReader reader;
|
||||
if (!reader.fetch(configFilePath, QIODevice::ReadOnly | QIODevice::Text))
|
||||
return;
|
||||
|
||||
FileSaver saver(configFilePath);
|
||||
QTextStream textStream(reader.data());
|
||||
while (!textStream.atEnd()) {
|
||||
QString line = textStream.readLine();
|
||||
if (line.contains("hw.device.manufacturer")) {
|
||||
if (recoverMode)
|
||||
line.replace("#", "");
|
||||
else
|
||||
line.prepend("#");
|
||||
}
|
||||
line.append("\n");
|
||||
saver.write(line.toUtf8());
|
||||
}
|
||||
saver.finalize();
|
||||
}
|
||||
|
||||
static AndroidDeviceInfoList listVirtualDevices()
|
||||
{
|
||||
QString output;
|
||||
AndroidDeviceInfoList avdList;
|
||||
/*
|
||||
Currenly avdmanager tool fails to parse some AVDs because the correct
|
||||
device definitions at devices.xml does not have some of the newest devices.
|
||||
Particularly, failing because of tag "hw.device.manufacturer", thus removing
|
||||
it would make paring successful. However, it has to be returned afterwards,
|
||||
otherwise, Android Studio would give an error during parsing also. So this fix
|
||||
aim to keep support for Qt Creator and Android Studio.
|
||||
*/
|
||||
FilePaths allAvdErrorPaths;
|
||||
FilePaths avdErrorPaths;
|
||||
|
||||
do {
|
||||
if (!AndroidAvdManager::avdManagerCommand({"list", "avd"}, &output)) {
|
||||
qCDebug(avdManagerLog)
|
||||
<< "Avd list command failed" << output << androidConfig().sdkToolsVersion();
|
||||
return {};
|
||||
}
|
||||
|
||||
avdErrorPaths.clear();
|
||||
avdList = parseAvdList(output, &avdErrorPaths);
|
||||
allAvdErrorPaths << avdErrorPaths;
|
||||
for (const FilePath &avdPath : std::as_const(avdErrorPaths))
|
||||
avdConfigEditManufacturerTag(avdPath); // comment out manufacturer tag
|
||||
} while (!avdErrorPaths.isEmpty()); // try again
|
||||
|
||||
for (const FilePath &avdPath : std::as_const(allAvdErrorPaths))
|
||||
avdConfigEditManufacturerTag(avdPath, true); // re-add manufacturer tag
|
||||
|
||||
return avdList;
|
||||
}
|
||||
|
||||
QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const
|
||||
{
|
||||
return Utils::asyncRun(listVirtualDevices);
|
||||
}
|
||||
|
||||
QString AndroidAvdManager::startAvd(const QString &name) const
|
||||
QString startAvd(const QString &name, const std::optional<QFuture<void>> &future)
|
||||
{
|
||||
if (!findAvd(name).isEmpty() || startAvdAsync(name))
|
||||
return waitForAvd(name);
|
||||
return waitForAvd(name, future);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -227,64 +43,70 @@ static bool is32BitUserSpace()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::startAvdAsync(const QString &avdName) const
|
||||
bool startAvdAsync(const QString &avdName)
|
||||
{
|
||||
const FilePath emulator = androidConfig().emulatorToolPath();
|
||||
if (!emulator.exists()) {
|
||||
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [emulator] {
|
||||
const FilePath emulatorPath = AndroidConfig::emulatorToolPath();
|
||||
if (!emulatorPath.exists()) {
|
||||
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [emulatorPath] {
|
||||
QMessageBox::critical(Core::ICore::dialogParent(),
|
||||
Tr::tr("Emulator Tool Is Missing"),
|
||||
Tr::tr("Install the missing emulator tool (%1) to the"
|
||||
" installed Android SDK.")
|
||||
.arg(emulator.displayName()));
|
||||
.arg(emulatorPath.displayName()));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Here we are potentially leaking Process instance in case when shutdown happens
|
||||
// after the avdProcess has started and before it has finished. Giving a parent object here
|
||||
// should solve the issue. However, AndroidAvdManager is not a QObject, so no clue what parent
|
||||
// would be the most appropriate. Preferably some object taken form android plugin...
|
||||
Process *avdProcess = new Process;
|
||||
avdProcess->setProcessChannelMode(QProcess::MergedChannels);
|
||||
QObject::connect(avdProcess, &Process::done, avdProcess, [avdProcess] {
|
||||
if (avdProcess->exitCode()) {
|
||||
const QString errorOutput = QString::fromLatin1(avdProcess->rawStdOut());
|
||||
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [errorOutput] {
|
||||
const QString title = Tr::tr("AVD Start Error");
|
||||
QMessageBox::critical(Core::ICore::dialogParent(), title, errorOutput);
|
||||
});
|
||||
}
|
||||
avdProcess->deleteLater();
|
||||
});
|
||||
|
||||
// start the emulator
|
||||
CommandLine cmd(androidConfig().emulatorToolPath());
|
||||
CommandLine cmd(emulatorPath);
|
||||
if (is32BitUserSpace())
|
||||
cmd.addArg("-force-32bit");
|
||||
|
||||
cmd.addArgs(androidConfig().emulatorArgs(), CommandLine::Raw);
|
||||
cmd.addArgs(AndroidConfig::emulatorArgs(), CommandLine::Raw);
|
||||
cmd.addArgs({"-avd", avdName});
|
||||
qCDebug(avdManagerLog).noquote() << "Running command (startAvdAsync):" << cmd.toUserOutput();
|
||||
avdProcess->setCommand(cmd);
|
||||
avdProcess->start();
|
||||
return avdProcess->waitForStarted(QDeadlineTimer::Forever);
|
||||
if (Process::startDetached(cmd, {}, DetachedChannelMode::Discard))
|
||||
return true;
|
||||
|
||||
QMetaObject::invokeMethod(Core::ICore::mainWindow(), [avdName] {
|
||||
QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("AVD Start Error"),
|
||||
Tr::tr("Failed to start AVD emulator for \"%1\" device.").arg(avdName));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
QString AndroidAvdManager::findAvd(const QString &avdName) const
|
||||
QString findAvd(const QString &avdName)
|
||||
{
|
||||
const QVector<AndroidDeviceInfo> devices = androidConfig().connectedDevices();
|
||||
for (const AndroidDeviceInfo &device : devices) {
|
||||
if (device.type != ProjectExplorer::IDevice::Emulator)
|
||||
const QStringList lines = AndroidConfig::devicesCommandOutput();
|
||||
for (const QString &line : lines) {
|
||||
// skip the daemon logs
|
||||
if (line.startsWith("* daemon"))
|
||||
continue;
|
||||
if (device.avdName == avdName)
|
||||
return device.serialNumber;
|
||||
|
||||
const QString serialNumber = line.left(line.indexOf('\t')).trimmed();
|
||||
if (!serialNumber.startsWith("emulator"))
|
||||
continue;
|
||||
|
||||
if (AndroidConfig::getAvdName(serialNumber) == avdName)
|
||||
return serialNumber;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString AndroidAvdManager::waitForAvd(const QString &avdName,
|
||||
const std::optional<QFuture<void>> &future) const
|
||||
static bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future)
|
||||
{
|
||||
// found a serial number, now wait until it's done booting...
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (future && future->isCanceled())
|
||||
return false;
|
||||
if (isAvdBooted(serialNumber))
|
||||
return true;
|
||||
QThread::sleep(2);
|
||||
if (!AndroidConfig::isConnected(serialNumber)) // device was disconnected
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future)
|
||||
{
|
||||
// we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running
|
||||
// 60 rounds of 2s sleeping, two minutes for the avd to start
|
||||
@@ -300,36 +122,17 @@ QString AndroidAvdManager::waitForAvd(const QString &avdName,
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::isAvdBooted(const QString &device) const
|
||||
bool isAvdBooted(const QString &device)
|
||||
{
|
||||
QStringList arguments = AndroidDeviceInfo::adbSelector(device);
|
||||
arguments << "shell" << "getprop" << "init.svc.bootanim";
|
||||
|
||||
const CommandLine command({androidConfig().adbToolPath(), arguments});
|
||||
qCDebug(avdManagerLog).noquote() << "Running command (isAvdBooted):" << command.toUserOutput();
|
||||
const CommandLine cmd{AndroidConfig::adbToolPath(), {AndroidDeviceInfo::adbSelector(device),
|
||||
"shell", "getprop", "init.svc.bootanim"}};
|
||||
qCDebug(avdManagerLog).noquote() << "Running command (isAvdBooted):" << cmd.toUserOutput();
|
||||
Process adbProc;
|
||||
adbProc.setCommand(command);
|
||||
adbProc.setCommand(cmd);
|
||||
adbProc.runBlocking();
|
||||
if (adbProc.result() != ProcessResult::FinishedWithSuccess)
|
||||
return false;
|
||||
QString value = adbProc.allOutput().trimmed();
|
||||
return value == "stopped";
|
||||
return adbProc.allOutput().trimmed() == "stopped";
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::waitForBooted(const QString &serialNumber,
|
||||
const std::optional<QFuture<void>> &future) const
|
||||
{
|
||||
// found a serial number, now wait until it's done booting...
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (future && future->isCanceled())
|
||||
return false;
|
||||
if (isAvdBooted(serialNumber))
|
||||
return true;
|
||||
QThread::sleep(2);
|
||||
if (!androidConfig().isConnected(serialNumber)) // device was disconnected
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // Android::Internal
|
||||
} // namespace Android::Internal::AndroidAvdManager
|
||||
|
||||
@@ -2,32 +2,16 @@
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "androidconfigurations.h"
|
||||
|
||||
#include <QFuture>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace Android::Internal {
|
||||
namespace Android::Internal::AndroidAvdManager {
|
||||
|
||||
class AndroidAvdManager
|
||||
{
|
||||
public:
|
||||
AndroidAvdManager();
|
||||
~AndroidAvdManager();
|
||||
QString startAvd(const QString &name, const std::optional<QFuture<void>> &future = {});
|
||||
bool startAvdAsync(const QString &avdName);
|
||||
QString findAvd(const QString &avdName);
|
||||
QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {});
|
||||
bool isAvdBooted(const QString &device);
|
||||
|
||||
QFuture<CreateAvdInfo> createAvd(CreateAvdInfo info) const;
|
||||
QFuture<AndroidDeviceInfoList> avdList() const;
|
||||
|
||||
QString startAvd(const QString &name) const;
|
||||
bool startAvdAsync(const QString &avdName) const;
|
||||
QString findAvd(const QString &avdName) const;
|
||||
QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {}) const;
|
||||
bool isAvdBooted(const QString &device) const;
|
||||
static bool avdManagerCommand(const QStringList &args, QString *output);
|
||||
|
||||
private:
|
||||
bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future = {}) const;
|
||||
};
|
||||
|
||||
} // Android::Internal
|
||||
} // namespace Android::Internal::AndroidAvdManager
|
||||
|
||||
@@ -140,7 +140,7 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
|
||||
keystoreLocationChooser->setPromptDialogFilter(Tr::tr("Keystore files (*.keystore *.jks)"));
|
||||
keystoreLocationChooser->setPromptDialogTitle(Tr::tr("Select Keystore File"));
|
||||
connect(keystoreLocationChooser, &PathChooser::textChanged, this, [this, keystoreLocationChooser] {
|
||||
const FilePath file = keystoreLocationChooser->rawFilePath();
|
||||
const FilePath file = keystoreLocationChooser->unexpandedFilePath();
|
||||
m_step->setKeystorePath(file);
|
||||
m_signPackageCheckBox->setChecked(!file.isEmpty());
|
||||
if (!file.isEmpty())
|
||||
@@ -415,7 +415,7 @@ bool AndroidBuildApkWidget::isOpenSslLibsIncluded()
|
||||
|
||||
QString AndroidBuildApkWidget::openSslIncludeFileContent(const FilePath &projectPath)
|
||||
{
|
||||
QString openSslPath = androidConfig().openSslLocation().toString();
|
||||
QString openSslPath = AndroidConfig::openSslLocation().toString();
|
||||
if (projectPath.endsWith(".pro"))
|
||||
return "android: include(" + openSslPath + "/openssl.pri)";
|
||||
if (projectPath.endsWith("CMakeLists.txt"))
|
||||
@@ -495,7 +495,7 @@ bool AndroidBuildApkStep::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version->qtVersion() < QVersionNumber(5, 4, 0)) {
|
||||
if (version->qtVersion() < AndroidManager::firstQtWithAndroidDeployQt) {
|
||||
const QString error = Tr::tr("The minimum Qt version required for Gradle build to work is %1. "
|
||||
"It is recommended to install the latest Qt version.")
|
||||
.arg("5.4.0");
|
||||
@@ -541,7 +541,7 @@ bool AndroidBuildApkStep::init()
|
||||
QStringList arguments = {"--input", m_inputFile.path(),
|
||||
"--output", outputDir.path(),
|
||||
"--android-platform", m_buildTargetSdk,
|
||||
"--jdk", androidConfig().openJDKLocation().path()};
|
||||
"--jdk", AndroidConfig::openJDKLocation().path()};
|
||||
|
||||
if (verboseOutput())
|
||||
arguments << "--verbose";
|
||||
@@ -929,17 +929,16 @@ QVariant AndroidBuildApkStep::data(Utils::Id id) const
|
||||
{
|
||||
if (id == Constants::AndroidNdkPlatform) {
|
||||
if (auto qtVersion = QtKitAspect::qtVersion(kit()))
|
||||
return androidConfig()
|
||||
.bestNdkPlatformMatch(AndroidManager::minimumSDK(target()), qtVersion);
|
||||
return AndroidConfig::bestNdkPlatformMatch(AndroidManager::minimumSDK(target()), qtVersion);
|
||||
return {};
|
||||
}
|
||||
if (id == Constants::NdkLocation) {
|
||||
if (auto qtVersion = QtKitAspect::qtVersion(kit()))
|
||||
return QVariant::fromValue(androidConfig().ndkLocation(qtVersion));
|
||||
return QVariant::fromValue(AndroidConfig::ndkLocation(qtVersion));
|
||||
return {};
|
||||
}
|
||||
if (id == Constants::SdkLocation)
|
||||
return QVariant::fromValue(androidConfig().sdkLocation());
|
||||
return QVariant::fromValue(AndroidConfig::sdkLocation());
|
||||
|
||||
if (id == Constants::AndroidMkSpecAbis)
|
||||
return AndroidManager::applicationAbis(target());
|
||||
@@ -1000,7 +999,7 @@ QAbstractItemModel *AndroidBuildApkStep::keystoreCertificates()
|
||||
"-storepass", m_keystorePasswd, "-J-Duser.language=en"};
|
||||
|
||||
Process keytoolProc;
|
||||
keytoolProc.setCommand({androidConfig().keytoolPath(), params});
|
||||
keytoolProc.setCommand({AndroidConfig::keytoolPath(), params});
|
||||
using namespace std::chrono_literals;
|
||||
keytoolProc.runBlocking(30s);
|
||||
if (keytoolProc.result() > ProcessResult::FinishedWithError)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,14 @@
|
||||
#include "androidsdkpackage.h"
|
||||
|
||||
#include <projectexplorer/toolchain.h>
|
||||
|
||||
#include <qtsupport/qtversionmanager.h>
|
||||
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QStringList>
|
||||
#include <QVersionNumber>
|
||||
|
||||
namespace ProjectExplorer { class Abi; }
|
||||
@@ -28,154 +28,96 @@ namespace Internal { class AndroidSdkManager; }
|
||||
class CreateAvdInfo
|
||||
{
|
||||
public:
|
||||
bool isValid() const { return systemImage && systemImage->isValid() && !name.isEmpty(); }
|
||||
const SystemImage *systemImage = nullptr;
|
||||
QString sdkStylePath;
|
||||
int apiLevel = -1;
|
||||
QString name;
|
||||
QString abi;
|
||||
QString deviceDefinition;
|
||||
int sdcardSize = 0;
|
||||
QString error; // only used in the return value of createAVD
|
||||
bool overwrite = false;
|
||||
bool cancelled = false;
|
||||
};
|
||||
|
||||
struct SdkForQtVersions
|
||||
{
|
||||
QList<QVersionNumber> versions;
|
||||
QStringList essentialPackages;
|
||||
namespace AndroidConfig {
|
||||
|
||||
public:
|
||||
bool containsVersion(const QVersionNumber &qtVersion) const;
|
||||
};
|
||||
QString getAvdName(const QString &serialnumber);
|
||||
QStringList apiLevelNamesFor(const SdkPlatformList &platforms);
|
||||
QString apiLevelNameFor(const SdkPlatform *platform);
|
||||
|
||||
class AndroidConfig
|
||||
{
|
||||
public:
|
||||
void load(const Utils::QtcSettings &settings);
|
||||
void save(Utils::QtcSettings &settings) const;
|
||||
Utils::FilePath sdkLocation();
|
||||
void setSdkLocation(const Utils::FilePath &sdkLocation);
|
||||
QVersionNumber sdkToolsVersion();
|
||||
QVersionNumber buildToolsVersion();
|
||||
QStringList sdkManagerToolArgs();
|
||||
void setSdkManagerToolArgs(const QStringList &args);
|
||||
|
||||
static QStringList apiLevelNamesFor(const SdkPlatformList &platforms);
|
||||
static QString apiLevelNameFor(const SdkPlatform *platform);
|
||||
Utils::FilePath ndkLocation(const QtSupport::QtVersion *qtVersion);
|
||||
QVersionNumber ndkVersion(const QtSupport::QtVersion *qtVersion);
|
||||
QVersionNumber ndkVersion(const Utils::FilePath &ndkPath);
|
||||
|
||||
Utils::FilePath sdkLocation() const;
|
||||
void setSdkLocation(const Utils::FilePath &sdkLocation);
|
||||
QVersionNumber sdkToolsVersion() const;
|
||||
Utils::FilePath sdkToolsVersionPath() const;
|
||||
QVersionNumber buildToolsVersion() const;
|
||||
QStringList sdkManagerToolArgs() const;
|
||||
void setSdkManagerToolArgs(const QStringList &args);
|
||||
QUrl sdkToolsUrl();
|
||||
QByteArray getSdkToolsSha256();
|
||||
|
||||
Utils::FilePath ndkLocation(const QtSupport::QtVersion *qtVersion) const;
|
||||
QVersionNumber ndkVersion(const QtSupport::QtVersion *qtVersion) const;
|
||||
static QVersionNumber ndkVersion(const Utils::FilePath &ndkPath);
|
||||
QStringList allEssentials();
|
||||
bool allEssentialsInstalled(Internal::AndroidSdkManager *sdkManager);
|
||||
bool sdkToolsOk();
|
||||
|
||||
QUrl sdkToolsUrl() const { return m_sdkToolsUrl; }
|
||||
QByteArray getSdkToolsSha256() const { return m_sdkToolsSha256; }
|
||||
Utils::FilePath ndkSubPathFromQtVersion(const QtSupport::QtVersion &version) const; // relative!
|
||||
Utils::FilePath openJDKLocation();
|
||||
void setOpenJDKLocation(const Utils::FilePath &openJDKLocation);
|
||||
|
||||
QStringList defaultEssentials() const;
|
||||
QStringList essentialsFromQtVersion(const QtSupport::QtVersion &version) const;
|
||||
QStringList allEssentials() const;
|
||||
bool allEssentialsInstalled(Internal::AndroidSdkManager *sdkManager);
|
||||
bool sdkToolsOk() const;
|
||||
QString toolchainHost(const QtSupport::QtVersion *qtVersion);
|
||||
QString toolchainHostFromNdk(const Utils::FilePath &ndkPath);
|
||||
|
||||
Utils::FilePath openJDKLocation() const;
|
||||
void setOpenJDKLocation(const Utils::FilePath &openJDKLocation);
|
||||
QString emulatorArgs();
|
||||
void setEmulatorArgs(const QString &args);
|
||||
|
||||
Utils::FilePath keystoreLocation() const;
|
||||
bool automaticKitCreation();
|
||||
void setAutomaticKitCreation(bool b);
|
||||
|
||||
QString toolchainHost(const QtSupport::QtVersion *qtVersion) const;
|
||||
static QString toolchainHostFromNdk(const Utils::FilePath &ndkPath);
|
||||
Utils::FilePath defaultSdkPath();
|
||||
Utils::FilePath adbToolPath();
|
||||
Utils::FilePath emulatorToolPath();
|
||||
Utils::FilePath sdkManagerToolPath();
|
||||
Utils::FilePath avdManagerToolPath();
|
||||
|
||||
QString emulatorArgs() const;
|
||||
void setEmulatorArgs(const QString &args);
|
||||
void setTemporarySdkToolsPath(const Utils::FilePath &path);
|
||||
|
||||
bool automaticKitCreation() const;
|
||||
void setAutomaticKitCreation(bool b);
|
||||
Utils::FilePath toolchainPath(const QtSupport::QtVersion *qtVersion);
|
||||
Utils::FilePath toolchainPathFromNdk(
|
||||
const Utils::FilePath &ndkLocation, Utils::OsType hostOs = Utils::HostOsInfo::hostOs());
|
||||
Utils::FilePath clangPathFromNdk(const Utils::FilePath &ndkLocation);
|
||||
|
||||
static Utils::FilePath defaultSdkPath();
|
||||
Utils::FilePath adbToolPath() const;
|
||||
Utils::FilePath emulatorToolPath() const;
|
||||
Utils::FilePath sdkManagerToolPath() const;
|
||||
Utils::FilePath avdManagerToolPath() const;
|
||||
Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation);
|
||||
|
||||
void setTemporarySdkToolsPath(const Utils::FilePath &path);
|
||||
Utils::FilePath keytoolPath();
|
||||
|
||||
Utils::FilePath toolchainPath(const QtSupport::QtVersion *qtVersion) const;
|
||||
static Utils::FilePath toolchainPathFromNdk(const Utils::FilePath &ndkLocation,
|
||||
Utils::OsType hostOs = Utils::HostOsInfo::hostOs());
|
||||
static Utils::FilePath clangPathFromNdk(const Utils::FilePath &ndkLocation);
|
||||
QStringList devicesCommandOutput();
|
||||
|
||||
Utils::FilePath gdbPath(const ProjectExplorer::Abi &abi, const QtSupport::QtVersion *qtVersion) const;
|
||||
static Utils::FilePath gdbPathFromNdk(const ProjectExplorer::Abi &abi,
|
||||
const Utils::FilePath &ndkLocation);
|
||||
static Utils::FilePath lldbPathFromNdk(const Utils::FilePath &ndkLocation);
|
||||
static Utils::FilePath makePathFromNdk(const Utils::FilePath &ndkLocation);
|
||||
QString bestNdkPlatformMatch(int target, const QtSupport::QtVersion *qtVersion);
|
||||
|
||||
Utils::FilePath keytoolPath() const;
|
||||
QLatin1String displayName(const ProjectExplorer::Abi &abi);
|
||||
|
||||
QVector<AndroidDeviceInfo> connectedDevices(QString *error = nullptr) const;
|
||||
QString getProductModel(const QString &device);
|
||||
bool isConnected(const QString &serialNumber);
|
||||
|
||||
QString bestNdkPlatformMatch(int target, const QtSupport::QtVersion *qtVersion) const;
|
||||
bool sdkFullyConfigured();
|
||||
void setSdkFullyConfigured(bool allEssentialsInstalled);
|
||||
|
||||
static QLatin1String toolchainPrefix(const ProjectExplorer::Abi &abi);
|
||||
static QLatin1String toolsPrefix(const ProjectExplorer::Abi &abi);
|
||||
static QLatin1String displayName(const ProjectExplorer::Abi &abi);
|
||||
bool isValidNdk(const QString &ndkLocation);
|
||||
QStringList getCustomNdkList();
|
||||
void addCustomNdk(const QString &customNdk);
|
||||
void removeCustomNdk(const QString &customNdk);
|
||||
void setDefaultNdk(const Utils::FilePath &defaultNdk);
|
||||
Utils::FilePath defaultNdk();
|
||||
|
||||
QString getProductModel(const QString &device) const;
|
||||
bool isConnected(const QString &serialNumber) const;
|
||||
Utils::FilePath openSslLocation();
|
||||
void setOpenSslLocation(const Utils::FilePath &openSslLocation);
|
||||
|
||||
bool sdkFullyConfigured() const { return m_sdkFullyConfigured; }
|
||||
void setSdkFullyConfigured(bool allEssentialsInstalled) { m_sdkFullyConfigured = allEssentialsInstalled; }
|
||||
Utils::FilePath getJdkPath();
|
||||
QStringList getAbis(const QString &device);
|
||||
int getSDKVersion(const QString &device);
|
||||
|
||||
bool isValidNdk(const QString &ndkLocation) const;
|
||||
QStringList getCustomNdkList() const;
|
||||
void addCustomNdk(const QString &customNdk);
|
||||
void removeCustomNdk(const QString &customNdk);
|
||||
void setDefaultNdk(const Utils::FilePath &defaultNdk);
|
||||
Utils::FilePath defaultNdk() const;
|
||||
Utils::Environment toolsEnvironment();
|
||||
|
||||
Utils::FilePath openSslLocation() const;
|
||||
void setOpenSslLocation(const Utils::FilePath &openSslLocation);
|
||||
|
||||
static Utils::FilePath getJdkPath();
|
||||
static QStringList getAbis(const QString &device);
|
||||
static int getSDKVersion(const QString &device);
|
||||
|
||||
Utils::Environment toolsEnvironment() const;
|
||||
|
||||
private:
|
||||
static QString getDeviceProperty(const QString &device, const QString &property);
|
||||
|
||||
Utils::FilePath openJDKBinPath() const;
|
||||
static QString getAvdName(const QString &serialnumber);
|
||||
|
||||
void parseDependenciesJson();
|
||||
|
||||
QList<int> availableNdkPlatforms(const QtSupport::QtVersion *qtVersion) const;
|
||||
|
||||
Utils::FilePath m_sdkLocation;
|
||||
Utils::FilePath m_temporarySdkToolsPath;
|
||||
QStringList m_sdkManagerToolArgs;
|
||||
Utils::FilePath m_openJDKLocation;
|
||||
Utils::FilePath m_keystoreLocation;
|
||||
Utils::FilePath m_openSslLocation;
|
||||
QString m_emulatorArgs;
|
||||
bool m_automaticKitCreation = true;
|
||||
QUrl m_sdkToolsUrl;
|
||||
QByteArray m_sdkToolsSha256;
|
||||
QStringList m_commonEssentialPkgs;
|
||||
SdkForQtVersions m_defaultSdkDepends;
|
||||
QList<SdkForQtVersions> m_specificQtVersions;
|
||||
QStringList m_customNdkList;
|
||||
Utils::FilePath m_defaultNdk;
|
||||
bool m_sdkFullyConfigured = false;
|
||||
|
||||
//caches
|
||||
mutable QHash<QString, QString> m_serialNumberToDeviceName;
|
||||
};
|
||||
|
||||
AndroidConfig &androidConfig();
|
||||
} // namespace AndroidConfig
|
||||
|
||||
class AndroidConfigurations : public QObject
|
||||
{
|
||||
@@ -183,7 +125,7 @@ class AndroidConfigurations : public QObject
|
||||
|
||||
public:
|
||||
static Internal::AndroidSdkManager *sdkManager();
|
||||
static void setConfig(const AndroidConfig &config);
|
||||
static void applyConfig();
|
||||
static AndroidConfigurations *instance();
|
||||
|
||||
static void registerNewToolchains();
|
||||
|
||||
@@ -262,8 +262,8 @@ void AndroidCreateKeystoreCertificate::buttonBoxAccepted()
|
||||
if (!m_stateNameLineEdit->text().isEmpty())
|
||||
distinguishedNames += QLatin1String(", S=") + m_stateNameLineEdit->text().replace(',', QLatin1String("\\,"));
|
||||
|
||||
const CommandLine command(androidConfig().keytoolPath(),
|
||||
{ "-genkey", "-keyalg", "RSA",
|
||||
const CommandLine command(AndroidConfig::keytoolPath(),
|
||||
{"-genkey", "-keyalg", "RSA",
|
||||
"-keystore", m_keystoreFilePath.toString(),
|
||||
"-storepass", keystorePassword(),
|
||||
"-alias", certificateAlias(),
|
||||
|
||||
@@ -110,8 +110,7 @@ void AndroidDebugSupport::start()
|
||||
|
||||
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(kit);
|
||||
if (!HostOsInfo::isWindowsHost()
|
||||
&& (qtVersion
|
||||
&& androidConfig().ndkVersion(qtVersion) >= QVersionNumber(11, 0, 0))) {
|
||||
&& (qtVersion && AndroidConfig::ndkVersion(qtVersion) >= QVersionNumber(11, 0, 0))) {
|
||||
qCDebug(androidDebugSupportLog) << "UseTargetAsync: " << true;
|
||||
setUseTargetAsync(true);
|
||||
}
|
||||
@@ -165,7 +164,7 @@ void AndroidDebugSupport::start()
|
||||
|
||||
int sdkVersion = qMax(AndroidManager::minimumSDK(kit), minimumNdk);
|
||||
if (qtVersion) {
|
||||
const FilePath ndkLocation = androidConfig().ndkLocation(qtVersion);
|
||||
const FilePath ndkLocation = AndroidConfig::ndkLocation(qtVersion);
|
||||
FilePath sysRoot = ndkLocation
|
||||
/ "platforms"
|
||||
/ QString("android-%1").arg(sdkVersion)
|
||||
|
||||
@@ -66,27 +66,45 @@ const QLatin1String InstallFailedUpdateIncompatible("INSTALL_FAILED_UPDATE_INCOM
|
||||
const QLatin1String InstallFailedPermissionModelDowngrade("INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE");
|
||||
const QLatin1String InstallFailedVersionDowngrade("INSTALL_FAILED_VERSION_DOWNGRADE");
|
||||
|
||||
enum DeployErrorFlag
|
||||
{
|
||||
NoError = 0,
|
||||
InconsistentCertificates = 0x0001,
|
||||
UpdateIncompatible = 0x0002,
|
||||
PermissionModelDowngrade = 0x0004,
|
||||
VersionDowngrade = 0x0008,
|
||||
Failure = 0x0010
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(DeployErrorFlags, DeployErrorFlag)
|
||||
|
||||
static DeployErrorFlags parseDeployErrors(const QString &deployOutputLine)
|
||||
{
|
||||
DeployErrorFlags errorCode = NoError;
|
||||
|
||||
if (deployOutputLine.contains(InstallFailedInconsistentCertificatesString))
|
||||
errorCode |= InconsistentCertificates;
|
||||
if (deployOutputLine.contains(InstallFailedUpdateIncompatible))
|
||||
errorCode |= UpdateIncompatible;
|
||||
if (deployOutputLine.contains(InstallFailedPermissionModelDowngrade))
|
||||
errorCode |= PermissionModelDowngrade;
|
||||
if (deployOutputLine.contains(InstallFailedVersionDowngrade))
|
||||
errorCode |= VersionDowngrade;
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
// AndroidDeployQtStep
|
||||
|
||||
class AndroidDeployQtStep : public BuildStep
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum DeployErrorCode
|
||||
{
|
||||
NoError = 0,
|
||||
InconsistentCertificates = 0x0001,
|
||||
UpdateIncompatible = 0x0002,
|
||||
PermissionModelDowngrade = 0x0004,
|
||||
VersionDowngrade = 0x0008,
|
||||
Failure = 0x0010
|
||||
};
|
||||
|
||||
public:
|
||||
AndroidDeployQtStep(BuildStepList *bc, Id id);
|
||||
|
||||
signals:
|
||||
void askForUninstall(DeployErrorCode errorCode);
|
||||
void askForUninstall(DeployErrorFlags errorCode);
|
||||
|
||||
private:
|
||||
void runCommand(const CommandLine &command);
|
||||
@@ -94,26 +112,17 @@ private:
|
||||
bool init() override;
|
||||
Tasking::GroupItem runRecipe() final;
|
||||
void gatherFilesToPull();
|
||||
DeployErrorCode runDeploy(QPromise<void> &promise);
|
||||
void slotAskForUninstall(DeployErrorCode errorCode);
|
||||
DeployErrorFlags runDeploy(QPromise<void> &promise);
|
||||
void slotAskForUninstall(DeployErrorFlags errorFlags);
|
||||
|
||||
void runImpl(QPromise<void> &promise);
|
||||
|
||||
QWidget *createConfigWidget() override;
|
||||
|
||||
void processReadyReadStdOutput(DeployErrorCode &errorCode);
|
||||
void processReadyReadStdOutput(DeployErrorFlags &errorCode);
|
||||
void stdOutput(const QString &line);
|
||||
void processReadyReadStdError(DeployErrorCode &errorCode);
|
||||
void processReadyReadStdError(DeployErrorFlags &errorCode);
|
||||
void stdError(const QString &line);
|
||||
DeployErrorCode parseDeployErrors(const QString &deployOutputLine) const;
|
||||
|
||||
friend void operator|=(DeployErrorCode &e1, const DeployErrorCode &e2) {
|
||||
e1 = static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
|
||||
}
|
||||
|
||||
friend DeployErrorCode operator|(const DeployErrorCode &e1, const DeployErrorCode &e2) {
|
||||
return static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
|
||||
}
|
||||
|
||||
void reportWarningOrError(const QString &message, Task::TaskType type);
|
||||
|
||||
@@ -149,7 +158,7 @@ AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Id id)
|
||||
m_uninstallPreviousPackage.setValue(false);
|
||||
|
||||
const QtSupport::QtVersion * const qt = QtSupport::QtKitAspect::qtVersion(kit());
|
||||
const bool forced = qt && qt->qtVersion() < QVersionNumber(5, 4, 0);
|
||||
const bool forced = qt && qt->qtVersion() < AndroidManager::firstQtWithAndroidDeployQt;
|
||||
if (forced) {
|
||||
m_uninstallPreviousPackage.setValue(true);
|
||||
m_uninstallPreviousPackage.setEnabled(false);
|
||||
@@ -169,7 +178,7 @@ bool AndroidDeployQtStep::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_androiddeployqtArgs = CommandLine();
|
||||
m_androiddeployqtArgs = {};
|
||||
|
||||
m_androidABIs = AndroidManager::applicationAbis(target());
|
||||
if (m_androidABIs.isEmpty()) {
|
||||
@@ -277,7 +286,7 @@ bool AndroidDeployQtStep::init()
|
||||
if (m_uninstallPreviousPackageRun)
|
||||
m_manifestName = AndroidManager::manifestPath(target());
|
||||
|
||||
m_useAndroiddeployqt = version->qtVersion() >= QVersionNumber(5, 4, 0);
|
||||
m_useAndroiddeployqt = version->qtVersion() >= AndroidManager::firstQtWithAndroidDeployQt;
|
||||
if (m_useAndroiddeployqt) {
|
||||
const QString buildKey = target()->activeBuildKey();
|
||||
const ProjectNode *node = target()->project()->findNodeForBuildKey(buildKey);
|
||||
@@ -288,7 +297,7 @@ bool AndroidDeployQtStep::init()
|
||||
m_apkPath = FilePath::fromString(node->data(Constants::AndroidApk).toString());
|
||||
if (!m_apkPath.isEmpty()) {
|
||||
m_manifestName = FilePath::fromString(node->data(Constants::AndroidManifest).toString());
|
||||
m_command = androidConfig().adbToolPath();
|
||||
m_command = AndroidConfig::adbToolPath();
|
||||
AndroidManager::setManifestPath(target(), m_manifestName);
|
||||
} else {
|
||||
QString jsonFile = AndroidQtVersion::androidDeploymentSettings(target()).toString();
|
||||
@@ -326,22 +335,21 @@ bool AndroidDeployQtStep::init()
|
||||
}
|
||||
} else {
|
||||
m_uninstallPreviousPackageRun = true;
|
||||
m_command = androidConfig().adbToolPath();
|
||||
m_command = AndroidConfig::adbToolPath();
|
||||
m_apkPath = AndroidManager::packagePath(target());
|
||||
m_workingDirectory = bc ? AndroidManager::buildDirectory(target()): FilePath();
|
||||
}
|
||||
m_environment = bc ? bc->environment() : Environment();
|
||||
|
||||
m_adbPath = androidConfig().adbToolPath();
|
||||
m_adbPath = AndroidConfig::adbToolPath();
|
||||
|
||||
AndroidAvdManager avdManager;
|
||||
// Start the AVD if not running.
|
||||
if (!m_avdName.isEmpty() && avdManager.findAvd(m_avdName).isEmpty())
|
||||
avdManager.startAvdAsync(m_avdName);
|
||||
if (!m_avdName.isEmpty() && AndroidAvdManager::findAvd(m_avdName).isEmpty())
|
||||
AndroidAvdManager::startAvdAsync(m_avdName);
|
||||
return true;
|
||||
}
|
||||
|
||||
AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<void> &promise)
|
||||
DeployErrorFlags AndroidDeployQtStep::runDeploy(QPromise<void> &promise)
|
||||
{
|
||||
CommandLine cmd(m_command);
|
||||
if (m_useAndroiddeployqt && m_apkPath.isEmpty()) {
|
||||
@@ -356,23 +364,24 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<voi
|
||||
|
||||
} else {
|
||||
RunConfiguration *rc = target()->activeRunConfiguration();
|
||||
QTC_ASSERT(rc, return DeployErrorCode::Failure);
|
||||
QTC_ASSERT(rc, return Failure);
|
||||
QString packageName;
|
||||
|
||||
if (m_uninstallPreviousPackageRun) {
|
||||
packageName = AndroidManager::packageName(m_manifestName);
|
||||
packageName = AndroidManager::packageName(target());
|
||||
if (packageName.isEmpty()) {
|
||||
reportWarningOrError(Tr::tr("Cannot find the package name from the Android Manifest "
|
||||
"file \"%1\".").arg(m_manifestName.toUserOutput()),
|
||||
Task::Error);
|
||||
reportWarningOrError(
|
||||
Tr::tr("Cannot find the package name from AndroidManifest.xml nor "
|
||||
"build.gradle files at \"%1\".")
|
||||
.arg(AndroidManager::androidBuildDirectory(target()).toUserOutput()),
|
||||
Task::Error);
|
||||
return Failure;
|
||||
}
|
||||
const QString msg = Tr::tr("Uninstalling the previous package \"%1\".").arg(packageName);
|
||||
qCDebug(deployStepLog) << msg;
|
||||
emit addOutput(msg, OutputFormat::NormalMessage);
|
||||
runCommand({m_adbPath,
|
||||
AndroidDeviceInfo::adbSelector(m_serialNumber)
|
||||
<< "uninstall" << packageName});
|
||||
{AndroidDeviceInfo::adbSelector(m_serialNumber), "uninstall", packageName}});
|
||||
}
|
||||
|
||||
cmd.addArgs(AndroidDeviceInfo::adbSelector(m_serialNumber));
|
||||
@@ -385,7 +394,7 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<voi
|
||||
process.setEnvironment(m_environment);
|
||||
process.setUseCtrlCStub(true);
|
||||
|
||||
DeployErrorCode deployError = NoError;
|
||||
DeployErrorFlags deployError = NoError;
|
||||
|
||||
process.setStdOutLineCallback([this, &deployError](const QString &line) {
|
||||
deployError |= parseDeployErrors(line);
|
||||
@@ -442,46 +451,32 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::runDeploy(QPromise<voi
|
||||
return deployError;
|
||||
}
|
||||
|
||||
void AndroidDeployQtStep::slotAskForUninstall(DeployErrorCode errorCode)
|
||||
void AndroidDeployQtStep::slotAskForUninstall(DeployErrorFlags errorFlags)
|
||||
{
|
||||
Q_ASSERT(errorCode > 0);
|
||||
Q_ASSERT(errorFlags > 0);
|
||||
|
||||
QString uninstallMsg = Tr::tr("Deployment failed with the following errors:\n\n");
|
||||
uint errorCodeFlags = errorCode;
|
||||
uint mask = 1;
|
||||
while (errorCodeFlags) {
|
||||
switch (errorCodeFlags & mask) {
|
||||
case DeployErrorCode::PermissionModelDowngrade:
|
||||
uninstallMsg += InstallFailedPermissionModelDowngrade+"\n";
|
||||
break;
|
||||
case InconsistentCertificates:
|
||||
uninstallMsg += InstallFailedInconsistentCertificatesString+"\n";
|
||||
break;
|
||||
case UpdateIncompatible:
|
||||
uninstallMsg += InstallFailedUpdateIncompatible+"\n";
|
||||
break;
|
||||
case VersionDowngrade:
|
||||
uninstallMsg += InstallFailedVersionDowngrade+"\n";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
errorCodeFlags &= ~mask;
|
||||
mask <<= 1;
|
||||
}
|
||||
QString uninstallMsg = Tr::tr("Deployment failed with the following errors:") + "\n\n";
|
||||
if (errorFlags & InconsistentCertificates)
|
||||
uninstallMsg += InstallFailedInconsistentCertificatesString + '\n';
|
||||
if (errorFlags & UpdateIncompatible)
|
||||
uninstallMsg += InstallFailedUpdateIncompatible + '\n';
|
||||
if (errorFlags & PermissionModelDowngrade)
|
||||
uninstallMsg += InstallFailedPermissionModelDowngrade + '\n';
|
||||
if (errorFlags & VersionDowngrade)
|
||||
uninstallMsg += InstallFailedVersionDowngrade + '\n';
|
||||
uninstallMsg += '\n';
|
||||
uninstallMsg.append(Tr::tr("Uninstalling the installed package may solve the issue.") + '\n');
|
||||
uninstallMsg.append(Tr::tr("Do you want to uninstall the existing package?"));
|
||||
|
||||
uninstallMsg.append(Tr::tr("\nUninstalling the installed package may solve the issue.\n"
|
||||
"Do you want to uninstall the existing package?"));
|
||||
int button = QMessageBox::critical(nullptr, Tr::tr("Install failed"), uninstallMsg,
|
||||
QMessageBox::Yes, QMessageBox::No);
|
||||
m_askForUninstall = button == QMessageBox::Yes;
|
||||
m_askForUninstall = QMessageBox::critical(nullptr, Tr::tr("Install failed"), uninstallMsg,
|
||||
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes;
|
||||
}
|
||||
|
||||
// TODO: This implementation is not thread safe.
|
||||
void AndroidDeployQtStep::runImpl(QPromise<void> &promise)
|
||||
{
|
||||
if (!m_avdName.isEmpty()) {
|
||||
const QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, promise.future());
|
||||
const QString serialNumber = AndroidAvdManager::waitForAvd(m_avdName, promise.future());
|
||||
qCDebug(deployStepLog) << "Deploying to AVD:" << m_avdName << serialNumber;
|
||||
if (serialNumber.isEmpty()) {
|
||||
reportWarningOrError(Tr::tr("The deployment AVD \"%1\" cannot be started.")
|
||||
@@ -494,8 +489,8 @@ void AndroidDeployQtStep::runImpl(QPromise<void> &promise)
|
||||
AndroidManager::setDeviceSerialNumber(target(), serialNumber);
|
||||
}
|
||||
|
||||
DeployErrorCode returnValue = runDeploy(promise);
|
||||
if (returnValue > DeployErrorCode::NoError && returnValue < DeployErrorCode::Failure) {
|
||||
DeployErrorFlags returnValue = runDeploy(promise);
|
||||
if (returnValue > NoError && returnValue < Failure) {
|
||||
emit askForUninstall(returnValue);
|
||||
if (m_askForUninstall) {
|
||||
m_uninstallPreviousPackageRun = true;
|
||||
@@ -519,9 +514,8 @@ void AndroidDeployQtStep::runImpl(QPromise<void> &promise)
|
||||
reportWarningOrError(error, Task::Error);
|
||||
}
|
||||
|
||||
runCommand({m_adbPath,
|
||||
AndroidDeviceInfo::adbSelector(m_serialNumber)
|
||||
<< "pull" << itr.key() << itr.value().nativePath()});
|
||||
runCommand({m_adbPath, {AndroidDeviceInfo::adbSelector(m_serialNumber), "pull", itr.key(),
|
||||
itr.value().nativePath()}});
|
||||
if (!itr.value().exists()) {
|
||||
const QString error = Tr::tr("Package deploy: Failed to pull \"%1\" to \"%2\".")
|
||||
.arg(itr.key())
|
||||
@@ -635,22 +629,6 @@ void AndroidDeployQtStep::stdError(const QString &line)
|
||||
}
|
||||
}
|
||||
|
||||
AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::parseDeployErrors(
|
||||
const QString &deployOutputLine) const
|
||||
{
|
||||
DeployErrorCode errorCode = NoError;
|
||||
|
||||
if (deployOutputLine.contains(InstallFailedInconsistentCertificatesString))
|
||||
errorCode |= InconsistentCertificates;
|
||||
if (deployOutputLine.contains(InstallFailedUpdateIncompatible))
|
||||
errorCode |= UpdateIncompatible;
|
||||
if (deployOutputLine.contains(InstallFailedPermissionModelDowngrade))
|
||||
errorCode |= PermissionModelDowngrade;
|
||||
if (deployOutputLine.contains(InstallFailedVersionDowngrade))
|
||||
errorCode |= VersionDowngrade;
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
void AndroidDeployQtStep::reportWarningOrError(const QString &message, Task::TaskType type)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "androidavdmanager.h"
|
||||
#include "androidconfigurations.h"
|
||||
#include "androiddeviceinfo.h"
|
||||
|
||||
#include <projectexplorer/devicesupport/idevice.h>
|
||||
#include <projectexplorer/devicesupport/idevicefactory.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <utils/guard.h>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QSettings>
|
||||
|
||||
@@ -27,7 +29,6 @@ public:
|
||||
static IDevice::Ptr create();
|
||||
static AndroidDeviceInfo androidDeviceInfoFromIDevice(const IDevice *dev);
|
||||
|
||||
static QString displayNameFromInfo(const AndroidDeviceInfo &info);
|
||||
static Utils::Id idFromDeviceInfo(const AndroidDeviceInfo &info);
|
||||
static Utils::Id idFromAvdInfo(const CreateAvdInfo &info);
|
||||
|
||||
@@ -69,40 +70,13 @@ private:
|
||||
std::unique_ptr<QSettings> m_avdSettings;
|
||||
};
|
||||
|
||||
class AndroidDeviceManager : public QObject
|
||||
{
|
||||
public:
|
||||
static AndroidDeviceManager *instance();
|
||||
void setupDevicesWatcher();
|
||||
void updateAvdsList();
|
||||
IDevice::DeviceState getDeviceState(const QString &serial, IDevice::MachineType type) const;
|
||||
void updateDeviceState(const ProjectExplorer::IDevice::ConstPtr &device);
|
||||
namespace AndroidDeviceManager {
|
||||
|
||||
void startAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr);
|
||||
void eraseAvd(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr);
|
||||
void setupWifiForDevice(const ProjectExplorer::IDevice::Ptr &device, QWidget *parent = nullptr);
|
||||
void setupDevicesWatcher();
|
||||
void updateAvdList();
|
||||
Utils::expected_str<void> createAvd(const CreateAvdInfo &info, bool force);
|
||||
|
||||
void setEmulatorArguments(QWidget *parent = nullptr);
|
||||
|
||||
QString getRunningAvdsSerialNumber(const QString &name) const;
|
||||
|
||||
private:
|
||||
explicit AndroidDeviceManager(QObject *parent);
|
||||
~AndroidDeviceManager();
|
||||
|
||||
void HandleDevicesListChange(const QString &serialNumber);
|
||||
void HandleAvdsListChange();
|
||||
|
||||
QString emulatorName(const QString &serialNumber) const;
|
||||
|
||||
QFutureWatcher<AndroidDeviceInfoList> m_avdsFutureWatcher;
|
||||
std::unique_ptr<Utils::Process> m_removeAvdProcess;
|
||||
QFileSystemWatcher m_avdFileSystemWatcher;
|
||||
std::unique_ptr<Utils::Process> m_adbDeviceWatcherProcess;
|
||||
AndroidAvdManager m_avdManager;
|
||||
|
||||
friend void setupAndroidDeviceManager(QObject *guard);
|
||||
};
|
||||
} // namespace AndroidDeviceManager
|
||||
|
||||
void setupAndroidDevice();
|
||||
void setupAndroidDeviceManager(QObject *guard);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <QVersionNumber>
|
||||
|
||||
using namespace Android::Internal;
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
@@ -50,19 +51,32 @@ const char qtcSignature[] = "This file is generated by QtCreator to be read by "
|
||||
|
||||
static Q_LOGGING_CATEGORY(androidManagerLog, "qtc.android.androidManager", QtWarningMsg)
|
||||
|
||||
class Library
|
||||
static std::optional<QDomElement> documentElement(const FilePath &fileName)
|
||||
{
|
||||
public:
|
||||
int level = -1;
|
||||
QStringList dependencies;
|
||||
QString name;
|
||||
};
|
||||
QFile file(fileName.toString());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
MessageManager::writeDisrupting(Tr::tr("Cannot open \"%1\".").arg(fileName.toUserOutput()));
|
||||
return {};
|
||||
}
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(file.readAll())) {
|
||||
MessageManager::writeDisrupting(Tr::tr("Cannot parse \"%1\".").arg(fileName.toUserOutput()));
|
||||
return {};
|
||||
}
|
||||
return doc.documentElement();
|
||||
}
|
||||
|
||||
using LibrariesMap = QMap<QString, Library>;
|
||||
|
||||
static bool openXmlFile(QDomDocument &doc, const FilePath &fileName);
|
||||
static bool openManifest(const Target *target, QDomDocument &doc);
|
||||
static int parseMinSdk(const QDomElement &manifestElem);
|
||||
static int parseMinSdk(const QDomElement &manifestElem)
|
||||
{
|
||||
const QDomElement usesSdk = manifestElem.firstChildElement("uses-sdk");
|
||||
if (!usesSdk.isNull() && usesSdk.hasAttribute("android:minSdkVersion")) {
|
||||
bool ok;
|
||||
int tmp = usesSdk.attribute("android:minSdkVersion").toInt(&ok);
|
||||
if (ok)
|
||||
return tmp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const ProjectNode *currentProjectNode(const Target *target)
|
||||
{
|
||||
@@ -71,30 +85,62 @@ static const ProjectNode *currentProjectNode(const Target *target)
|
||||
|
||||
QString packageName(const Target *target)
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!openManifest(target, doc))
|
||||
return {};
|
||||
QDomElement manifestElem = doc.documentElement();
|
||||
return manifestElem.attribute(QLatin1String("package"));
|
||||
}
|
||||
QString packageName;
|
||||
|
||||
QString packageName(const FilePath &manifestFile)
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!openXmlFile(doc, manifestFile))
|
||||
return {};
|
||||
QDomElement manifestElem = doc.documentElement();
|
||||
return manifestElem.attribute(QLatin1String("package"));
|
||||
// Check build.gradle
|
||||
auto isComment = [](const QByteArray &trimmed) {
|
||||
return trimmed.startsWith("//") || trimmed.startsWith('*') || trimmed.startsWith("/*");
|
||||
};
|
||||
|
||||
const FilePath androidBuildDir = androidBuildDirectory(target);
|
||||
const expected_str<QByteArray> gradleContents = androidBuildDir.pathAppended("build.gradle")
|
||||
.fileContents();
|
||||
if (gradleContents) {
|
||||
const auto lines = gradleContents->split('\n');
|
||||
for (const auto &line : lines) {
|
||||
const QByteArray trimmed = line.trimmed();
|
||||
if (isComment(trimmed) || !trimmed.contains("namespace"))
|
||||
continue;
|
||||
|
||||
int idx = trimmed.indexOf('=');
|
||||
if (idx == -1)
|
||||
idx = trimmed.indexOf(' ');
|
||||
if (idx > -1) {
|
||||
packageName = QString::fromUtf8(trimmed.mid(idx + 1).trimmed());
|
||||
if (packageName == "androidPackageName") {
|
||||
// Check gradle.properties
|
||||
const QSettings gradleProperties = QSettings(
|
||||
androidBuildDir.pathAppended("gradle.properties").toFSPathString(),
|
||||
QSettings::IniFormat);
|
||||
packageName = gradleProperties.value("androidPackageName").toString();
|
||||
} else {
|
||||
// Remove quotes
|
||||
if (packageName.size() > 2)
|
||||
packageName = packageName.remove(0, 1).chopped(1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packageName.isEmpty()) {
|
||||
// Check AndroidManifest.xml
|
||||
const auto element = documentElement(AndroidManager::manifestPath(target));
|
||||
if (element)
|
||||
packageName = element->attribute("package");
|
||||
}
|
||||
|
||||
return packageName;
|
||||
}
|
||||
|
||||
QString activityName(const Target *target)
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!openManifest(target, doc))
|
||||
const auto element = documentElement(AndroidManager::manifestPath(target));
|
||||
if (!element)
|
||||
return {};
|
||||
QDomElement activityElem = doc.documentElement().firstChildElement(
|
||||
QLatin1String("application")).firstChildElement(QLatin1String("activity"));
|
||||
return activityElem.attribute(QLatin1String("android:name"));
|
||||
return element->firstChildElement("application").firstChildElement("activity")
|
||||
.attribute("android:name");
|
||||
}
|
||||
|
||||
static FilePath manifestSourcePath(const Target *target)
|
||||
@@ -118,10 +164,11 @@ static FilePath manifestSourcePath(const Target *target)
|
||||
*/
|
||||
int minimumSDK(const Target *target)
|
||||
{
|
||||
QDomDocument doc;
|
||||
if (!openXmlFile(doc, manifestSourcePath(target)))
|
||||
const auto element = documentElement(manifestSourcePath(target));
|
||||
if (!element)
|
||||
return minimumSDK(target->kit());
|
||||
const int minSdkVersion = parseMinSdk(doc.documentElement());
|
||||
|
||||
const int minSdkVersion = parseMinSdk(*element);
|
||||
if (minSdkVersion == 0)
|
||||
return AndroidManager::defaultMinimumSDK(QtSupport::QtKitAspect::qtVersion(target->kit()));
|
||||
return minSdkVersion;
|
||||
@@ -136,12 +183,12 @@ int minimumSDK(const Kit *kit)
|
||||
int minSdkVersion = -1;
|
||||
QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(kit);
|
||||
if (version && version->targetDeviceTypes().contains(Constants::ANDROID_DEVICE_TYPE)) {
|
||||
FilePath stockManifestFilePath = FilePath::fromUserInput(
|
||||
const FilePath stockManifestFilePath = FilePath::fromUserInput(
|
||||
version->prefix().toString() + "/src/android/templates/AndroidManifest.xml");
|
||||
QDomDocument doc;
|
||||
if (openXmlFile(doc, stockManifestFilePath)) {
|
||||
minSdkVersion = parseMinSdk(doc.documentElement());
|
||||
}
|
||||
|
||||
const auto element = documentElement(stockManifestFilePath);
|
||||
if (element)
|
||||
minSdkVersion = parseMinSdk(*element);
|
||||
}
|
||||
if (minSdkVersion == 0)
|
||||
return AndroidManager::defaultMinimumSDK(version);
|
||||
@@ -190,25 +237,23 @@ QJsonObject deploymentSettings(const Target *target)
|
||||
QJsonObject settings;
|
||||
settings["_description"] = qtcSignature;
|
||||
settings["qt"] = qt->prefix().toString();
|
||||
settings["ndk"] = androidConfig().ndkLocation(qt).toString();
|
||||
settings["sdk"] = androidConfig().sdkLocation().toString();
|
||||
settings["ndk"] = AndroidConfig::ndkLocation(qt).toString();
|
||||
settings["sdk"] = AndroidConfig::sdkLocation().toString();
|
||||
if (!qt->supportsMultipleQtAbis()) {
|
||||
const QStringList abis = applicationAbis(target);
|
||||
QTC_ASSERT(abis.size() == 1, return {});
|
||||
settings["stdcpp-path"] = (androidConfig().toolchainPath(qt)
|
||||
settings["stdcpp-path"] = (AndroidConfig::toolchainPath(qt)
|
||||
/ "sysroot/usr/lib"
|
||||
/ archTriplet(abis.first())
|
||||
/ "libc++_shared.so").toString();
|
||||
} else {
|
||||
settings["stdcpp-path"] = androidConfig()
|
||||
.toolchainPath(qt)
|
||||
.pathAppended("sysroot/usr/lib")
|
||||
.toString();
|
||||
settings["stdcpp-path"]
|
||||
= AndroidConfig::toolchainPath(qt).pathAppended("sysroot/usr/lib").toString();
|
||||
}
|
||||
settings["toolchain-prefix"] = "llvm";
|
||||
settings["tool-prefix"] = "llvm";
|
||||
settings["useLLVM"] = true;
|
||||
settings["ndk-host"] = androidConfig().toolchainHost(qt);
|
||||
settings["ndk-host"] = AndroidConfig::toolchainHost(qt);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -240,7 +285,7 @@ bool isQt5CmakeProject(const ProjectExplorer::Target *target)
|
||||
{
|
||||
const QtSupport::QtVersion *qt = QtSupport::QtKitAspect::qtVersion(target->kit());
|
||||
const bool isQt5 = qt && qt->qtVersion() < QVersionNumber(6, 0, 0);
|
||||
const Core::Context cmakeCtx = Core::Context(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
|
||||
const Context cmakeCtx(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
|
||||
const bool isCmakeProject = (target->project()->projectContext() == cmakeCtx);
|
||||
return isQt5 && isCmakeProject;
|
||||
}
|
||||
@@ -371,7 +416,7 @@ bool skipInstallationAndPackageSteps(const Target *target)
|
||||
|
||||
const Project *p = target->project();
|
||||
|
||||
const Core::Context cmakeCtx = Core::Context(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
|
||||
const Context cmakeCtx(CMakeProjectManager::Constants::CMAKE_PROJECT_ID);
|
||||
const bool isCmakeProject = p->projectContext() == cmakeCtx;
|
||||
if (isCmakeProject)
|
||||
return false; // CMake reports ProductType::Other for Android Apps
|
||||
@@ -538,43 +583,6 @@ QString androidNameForApiLevel(int x)
|
||||
}
|
||||
}
|
||||
|
||||
static void raiseError(const QString &reason)
|
||||
{
|
||||
QMessageBox::critical(nullptr, Tr::tr("Error creating Android templates."), reason);
|
||||
}
|
||||
|
||||
static bool openXmlFile(QDomDocument &doc, const FilePath &fileName)
|
||||
{
|
||||
QFile f(fileName.toString());
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
if (!doc.setContent(f.readAll())) {
|
||||
raiseError(Tr::tr("Cannot parse \"%1\".").arg(fileName.toUserOutput()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool openManifest(const Target *target, QDomDocument &doc)
|
||||
{
|
||||
return openXmlFile(doc, AndroidManager::manifestPath(target));
|
||||
}
|
||||
|
||||
static int parseMinSdk(const QDomElement &manifestElem)
|
||||
{
|
||||
QDomElement usesSdk = manifestElem.firstChildElement(QLatin1String("uses-sdk"));
|
||||
if (usesSdk.isNull())
|
||||
return 0;
|
||||
if (usesSdk.hasAttribute(QLatin1String("android:minSdkVersion"))) {
|
||||
bool ok;
|
||||
int tmp = usesSdk.attribute(QLatin1String("android:minSdkVersion")).toInt(&ok);
|
||||
if (ok)
|
||||
return tmp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void installQASIPackage(Target *target, const FilePath &packagePath)
|
||||
{
|
||||
const QStringList appAbis = AndroidManager::applicationAbis(target);
|
||||
@@ -587,9 +595,9 @@ void installQASIPackage(Target *target, const FilePath &packagePath)
|
||||
|
||||
QString deviceSerialNumber = info.serialNumber;
|
||||
if (info.type == IDevice::Emulator) {
|
||||
deviceSerialNumber = AndroidAvdManager().startAvd(info.avdName);
|
||||
deviceSerialNumber = AndroidAvdManager::startAvd(info.avdName);
|
||||
if (deviceSerialNumber.isEmpty())
|
||||
Core::MessageManager::writeDisrupting(Tr::tr("Starting Android virtual device failed."));
|
||||
MessageManager::writeDisrupting(Tr::tr("Starting Android virtual device failed."));
|
||||
}
|
||||
|
||||
QStringList arguments = AndroidDeviceInfo::adbSelector(deviceSerialNumber);
|
||||
@@ -600,7 +608,7 @@ void installQASIPackage(Target *target, const FilePath &packagePath)
|
||||
// TODO: Potential leak when the process is still running on Creator shutdown.
|
||||
QObject::connect(process, &Process::done, process, &QObject::deleteLater);
|
||||
} else {
|
||||
Core::MessageManager::writeDisrupting(
|
||||
MessageManager::writeDisrupting(
|
||||
Tr::tr("Android package installation failed.\n%1").arg(error));
|
||||
}
|
||||
}
|
||||
@@ -609,7 +617,7 @@ bool checkKeystorePassword(const FilePath &keystorePath, const QString &keystore
|
||||
{
|
||||
if (keystorePasswd.isEmpty())
|
||||
return false;
|
||||
const CommandLine cmd(androidConfig().keytoolPath(),
|
||||
const CommandLine cmd(AndroidConfig::keytoolPath(),
|
||||
{"-list", "-keystore", keystorePath.toUserOutput(),
|
||||
"--storepass", keystorePasswd});
|
||||
Process proc;
|
||||
@@ -630,7 +638,7 @@ bool checkCertificatePassword(const FilePath &keystorePath, const QString &keyst
|
||||
arguments << certificatePasswd;
|
||||
|
||||
Process proc;
|
||||
proc.setCommand({androidConfig().keytoolPath(), arguments});
|
||||
proc.setCommand({AndroidConfig::keytoolPath(), arguments});
|
||||
proc.runBlocking(10s);
|
||||
return proc.result() == ProcessResult::FinishedWithSuccess;
|
||||
}
|
||||
@@ -639,11 +647,11 @@ bool checkCertificateExists(const FilePath &keystorePath, const QString &keystor
|
||||
const QString &alias)
|
||||
{
|
||||
// assumes that the keystore password is correct
|
||||
QStringList arguments = { "-list", "-keystore", keystorePath.toUserOutput(),
|
||||
"--storepass", keystorePasswd, "-alias", alias };
|
||||
const QStringList arguments = {"-list", "-keystore", keystorePath.toUserOutput(),
|
||||
"--storepass", keystorePasswd, "-alias", alias};
|
||||
|
||||
Process proc;
|
||||
proc.setCommand({androidConfig().keytoolPath(), arguments});
|
||||
proc.setCommand({AndroidConfig::keytoolPath(), arguments});
|
||||
proc.runBlocking(10s);
|
||||
return proc.result() == ProcessResult::FinishedWithSuccess;
|
||||
}
|
||||
@@ -651,7 +659,7 @@ bool checkCertificateExists(const FilePath &keystorePath, const QString &keystor
|
||||
Process *startAdbProcess(const QStringList &args, QString *err)
|
||||
{
|
||||
std::unique_ptr<Process> process(new Process);
|
||||
const FilePath adb = androidConfig().adbToolPath();
|
||||
const FilePath adb = AndroidConfig::adbToolPath();
|
||||
const CommandLine command{adb, args};
|
||||
qCDebug(androidManagerLog).noquote() << "Running command (async):" << command.toUserOutput();
|
||||
process->setCommand(command);
|
||||
@@ -689,7 +697,7 @@ static SdkToolResult runCommand(const CommandLine &command, const QByteArray &wr
|
||||
|
||||
SdkToolResult runAdbCommand(const QStringList &args, const QByteArray &writeData, int timeoutS)
|
||||
{
|
||||
return runCommand({androidConfig().adbToolPath(), args}, writeData, timeoutS);
|
||||
return runCommand({AndroidConfig::adbToolPath(), args}, writeData, timeoutS);
|
||||
}
|
||||
|
||||
} // namespace Android::AndroidManager
|
||||
|
||||
@@ -35,8 +35,10 @@ public:
|
||||
namespace AndroidManager
|
||||
{
|
||||
|
||||
// With Qt 5.4, a couple of android tooling features moved over from Qt Creator to Qt.
|
||||
constexpr auto firstQtWithAndroidDeployQt = {5, 4, 0};
|
||||
|
||||
QString packageName(const ProjectExplorer::Target *target);
|
||||
QString packageName(const Utils::FilePath &manifestFile);
|
||||
QString activityName(const ProjectExplorer::Target *target);
|
||||
|
||||
QString deviceSerialNumber(const ProjectExplorer::Target *target);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <coreplugin/editormanager/ieditorfactory.h>
|
||||
|
||||
#include <texteditor/texteditoractionhandler.h>
|
||||
#include <texteditor/texteditorsettings.h>
|
||||
|
||||
namespace Android::Internal {
|
||||
@@ -18,10 +17,6 @@ class AndroidManifestEditorFactory final : public Core::IEditorFactory
|
||||
{
|
||||
public:
|
||||
AndroidManifestEditorFactory()
|
||||
: m_actionHandler(Constants::ANDROID_MANIFEST_EDITOR_ID,
|
||||
Constants::ANDROID_MANIFEST_EDITOR_CONTEXT,
|
||||
TextEditor::TextEditorActionHandler::UnCommentSelection,
|
||||
[](Core::IEditor *editor) { return static_cast<AndroidManifestEditor *>(editor)->textEditor(); })
|
||||
{
|
||||
setId(Constants::ANDROID_MANIFEST_EDITOR_ID);
|
||||
setDisplayName(Tr::tr("Android Manifest editor"));
|
||||
@@ -31,9 +26,6 @@ public:
|
||||
return androidManifestEditorWidget->editor();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
TextEditor::TextEditorActionHandler m_actionHandler;
|
||||
};
|
||||
|
||||
void setupAndroidManifestEditor()
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QWidget>
|
||||
|
||||
#include <QCoreApplication>
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
void loadIcons();
|
||||
bool hasIcons() const;
|
||||
private:
|
||||
QVector<AndroidManifestEditorIconWidget *> m_iconButtons;
|
||||
QList<AndroidManifestEditorIconWidget *> m_iconButtons;
|
||||
QString m_iconFileName = QLatin1String("icon");
|
||||
bool m_hasIcons = false;
|
||||
signals:
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <projectexplorer/projectwindow.h>
|
||||
#include <projectexplorer/target.h>
|
||||
|
||||
#include <texteditor/texteditoractionhandler.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
@@ -114,6 +113,7 @@ private:
|
||||
AndroidManifestEditorWidget::AndroidManifestEditorWidget()
|
||||
{
|
||||
m_textEditorWidget = new AndroidManifestTextEditorWidget(this);
|
||||
m_textEditorWidget->setOptionalActions(TextEditor::OptionalActions::UnCommentSelection);
|
||||
|
||||
initializePage();
|
||||
|
||||
@@ -592,7 +592,7 @@ void AndroidManifestEditorWidget::postSave()
|
||||
const FilePath docPath = m_textEditorWidget->textDocument()->filePath();
|
||||
if (Target *target = androidTarget(docPath)) {
|
||||
if (BuildConfiguration *bc = target->activeBuildConfiguration()) {
|
||||
QString androidNdkPlatform = androidConfig().bestNdkPlatformMatch(
|
||||
QString androidNdkPlatform = AndroidConfig::bestNdkPlatformMatch(
|
||||
AndroidManager::minimumSDK(target),
|
||||
QtSupport::QtKitAspect::qtVersion(
|
||||
androidTarget(m_textEditorWidget->textDocument()->filePath())->kit()));
|
||||
@@ -1406,7 +1406,7 @@ AndroidManifestTextEditorWidget::AndroidManifestTextEditorWidget(AndroidManifest
|
||||
setupGenericHighlighter();
|
||||
setMarksVisible(false);
|
||||
|
||||
// this context is used by the TextEditorActionHandler registered for the text editor in
|
||||
// this context is used by the OptionalActions registered for the text editor in
|
||||
// the AndroidManifestEditorFactory
|
||||
m_context = new Core::IContext(this);
|
||||
m_context->setWidget(this);
|
||||
|
||||
@@ -112,8 +112,8 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
|
||||
setupJavaEditor();
|
||||
setupAndroidManifestEditor();
|
||||
|
||||
connect(KitManager::instance(), &KitManager::kitsLoaded,
|
||||
this, &AndroidPlugin::kitsRestored);
|
||||
connect(KitManager::instance(), &KitManager::kitsLoaded, this, &AndroidPlugin::kitsRestored,
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
LanguageClient::LanguageClientSettings::registerClientType(
|
||||
{Android::Constants::JLS_SETTINGS_ID,
|
||||
@@ -135,7 +135,7 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
|
||||
return v->targetDeviceTypes().contains(Android::Constants::ANDROID_DEVICE_TYPE);
|
||||
}).isEmpty();
|
||||
|
||||
if (!androidConfig().sdkFullyConfigured() && qtForAndroidInstalled)
|
||||
if (!AndroidConfig::sdkFullyConfigured() && qtForAndroidInstalled)
|
||||
askUserAboutAndroidSetup();
|
||||
|
||||
AndroidConfigurations::registerNewToolchains();
|
||||
@@ -145,8 +145,6 @@ class AndroidPlugin final : public ExtensionSystem::IPlugin
|
||||
AndroidConfigurations::registerNewToolchains();
|
||||
AndroidConfigurations::updateAutomaticKitList();
|
||||
});
|
||||
disconnect(KitManager::instance(), &KitManager::kitsLoaded,
|
||||
this, &AndroidPlugin::kitsRestored);
|
||||
}
|
||||
|
||||
void askUserAboutAndroidSetup()
|
||||
|
||||
@@ -14,30 +14,27 @@
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <projectexplorer/buildsystem.h>
|
||||
#include <projectexplorer/devicesupport/devicemanager.h>
|
||||
#include <projectexplorer/environmentaspect.h>
|
||||
#include <projectexplorer/kit.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/runcontrol.h>
|
||||
#include <projectexplorer/target.h>
|
||||
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <qmlprojectmanager/qmlprojectconstants.h>
|
||||
|
||||
#include <qtsupport/baseqtversion.h>
|
||||
#include <qtsupport/qtkitaspect.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDeadlineTimer>
|
||||
#include <QFutureWatcher>
|
||||
#include <QThread>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Tasking;
|
||||
using namespace Utils;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
#define APP_ID "io.qt.qtdesignviewer"
|
||||
@@ -45,7 +42,17 @@ namespace Android::Internal {
|
||||
class ApkInfo
|
||||
{
|
||||
public:
|
||||
ApkInfo();
|
||||
ApkInfo()
|
||||
: abis{ProjectExplorer::Constants::ANDROID_ABI_X86,
|
||||
ProjectExplorer::Constants::ANDROID_ABI_X86_64,
|
||||
ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
|
||||
ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A}
|
||||
, appId(APP_ID)
|
||||
, uploadDir("/data/local/tmp/" APP_ID "/")
|
||||
// TODO Add possibility to run Qt5 built version of Qt Design Viewer
|
||||
, activityId(APP_ID "/org.qtproject.qt.android.bindings.QtActivity")
|
||||
, name("Qt Design Viewer")
|
||||
{}
|
||||
const QStringList abis;
|
||||
const QString appId;
|
||||
const QString uploadDir;
|
||||
@@ -53,18 +60,6 @@ public:
|
||||
const QString name;
|
||||
};
|
||||
|
||||
ApkInfo::ApkInfo() :
|
||||
abis({ProjectExplorer::Constants::ANDROID_ABI_X86,
|
||||
ProjectExplorer::Constants::ANDROID_ABI_X86_64,
|
||||
ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A,
|
||||
ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A}),
|
||||
appId(APP_ID),
|
||||
uploadDir("/data/local/tmp/" APP_ID "/"),
|
||||
// TODO Add possibility to run Qt5 built version of Qt Design Viewer
|
||||
activityId(APP_ID "/org.qtproject.qt.android.bindings.QtActivity"),
|
||||
name("Qt Design Viewer")
|
||||
{
|
||||
}
|
||||
|
||||
Q_GLOBAL_STATIC(ApkInfo, apkInfo)
|
||||
|
||||
@@ -82,7 +77,6 @@ class AndroidQmlPreviewWorker : public RunWorker
|
||||
Q_OBJECT
|
||||
public:
|
||||
AndroidQmlPreviewWorker(RunControl *runControl);
|
||||
~AndroidQmlPreviewWorker();
|
||||
|
||||
signals:
|
||||
void previewPidChanged();
|
||||
@@ -96,10 +90,9 @@ private:
|
||||
bool preparePreviewArtefacts();
|
||||
bool uploadPreviewArtefacts();
|
||||
|
||||
CommandLine adbCommand(const QStringList &arguments) const;
|
||||
SdkToolResult runAdbCommand(const QStringList &arguments) const;
|
||||
SdkToolResult runAdbShellCommand(const QStringList &arguments) const;
|
||||
int pidofPreview() const;
|
||||
bool isPreviewRunning(int lastKnownPid = -1) const;
|
||||
|
||||
void startPidWatcher();
|
||||
void startLogcat();
|
||||
@@ -108,15 +101,15 @@ private:
|
||||
bool startPreviewApp();
|
||||
bool stopPreviewApp();
|
||||
|
||||
Utils::FilePath designViewerApkPath(const QString &abi) const;
|
||||
Utils::FilePath createQmlrcFile(const Utils::FilePath &workFolder, const QString &basename);
|
||||
FilePath designViewerApkPath(const QString &abi) const;
|
||||
FilePath createQmlrcFile(const FilePath &workFolder, const QString &basename);
|
||||
|
||||
RunControl *m_rc = nullptr;
|
||||
QString m_serialNumber;
|
||||
QStringList m_avdAbis;
|
||||
int m_viewerPid = -1;
|
||||
QFutureWatcher<void> m_pidFutureWatcher;
|
||||
Utils::Process m_logcatProcess;
|
||||
TaskTreeRunner m_pidRunner;
|
||||
Process m_logcatProcess;
|
||||
QString m_logcatStartTimeStamp;
|
||||
UploadInfo m_uploadInfo;
|
||||
};
|
||||
@@ -133,6 +126,16 @@ FilePath AndroidQmlPreviewWorker::designViewerApkPath(const QString &abi) const
|
||||
return {};
|
||||
}
|
||||
|
||||
CommandLine AndroidQmlPreviewWorker::adbCommand(const QStringList &arguments) const
|
||||
{
|
||||
CommandLine cmd{AndroidConfig::adbToolPath()};
|
||||
if (!m_serialNumber.isEmpty())
|
||||
cmd.addArgs(AndroidDeviceInfo::adbSelector(m_serialNumber));
|
||||
cmd.addArg("shell");
|
||||
cmd.addArgs(arguments);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
SdkToolResult AndroidQmlPreviewWorker::runAdbCommand(const QStringList &arguments) const
|
||||
{
|
||||
QStringList args;
|
||||
@@ -147,44 +150,49 @@ SdkToolResult AndroidQmlPreviewWorker::runAdbShellCommand(const QStringList &arg
|
||||
return runAdbCommand(QStringList() << "shell" << arguments);
|
||||
}
|
||||
|
||||
int AndroidQmlPreviewWorker::pidofPreview() const
|
||||
{
|
||||
const QStringList command{"pidof", apkInfo()->appId};
|
||||
const SdkToolResult res = runAdbShellCommand(command);
|
||||
return res.success() ? res.stdOut().toInt() : -1;
|
||||
}
|
||||
|
||||
bool AndroidQmlPreviewWorker::isPreviewRunning(int lastKnownPid) const
|
||||
{
|
||||
const int pid = pidofPreview();
|
||||
return (lastKnownPid > 1) ? lastKnownPid == pid : pid > 1;
|
||||
}
|
||||
|
||||
void AndroidQmlPreviewWorker::startPidWatcher()
|
||||
{
|
||||
m_pidFutureWatcher.setFuture(Utils::asyncRun([this] {
|
||||
// wait for started
|
||||
const int sleepTimeMs = 2000;
|
||||
QDeadlineTimer deadline(20000);
|
||||
while (!m_pidFutureWatcher.isCanceled() && !deadline.hasExpired()) {
|
||||
if (m_viewerPid == -1) {
|
||||
m_viewerPid = pidofPreview();
|
||||
if (m_viewerPid > 0) {
|
||||
emit previewPidChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
QThread::msleep(sleepTimeMs);
|
||||
}
|
||||
const LoopUntil pidIterator([this](int) { return m_viewerPid <= 0; });
|
||||
const LoopUntil alivePidIterator([this](int) { return m_viewerPid > 0; });
|
||||
|
||||
while (!m_pidFutureWatcher.isCanceled()) {
|
||||
if (!isPreviewRunning(m_viewerPid)) {
|
||||
stop();
|
||||
break;
|
||||
}
|
||||
QThread::msleep(sleepTimeMs);
|
||||
const auto onPidSetup = [this](Process &process) {
|
||||
process.setCommand(adbCommand({"pidof", apkInfo()->appId}));
|
||||
};
|
||||
const auto onPidDone = [this](const Process &process) {
|
||||
bool ok = false;
|
||||
const int pid = process.cleanedStdOut().trimmed().toInt(&ok);
|
||||
if (ok && pid > 0) {
|
||||
m_viewerPid = pid;
|
||||
// TODO: make a continuation task (logcat)
|
||||
emit previewPidChanged();
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const auto onAlivePidDone = [this](const Process &process) {
|
||||
bool ok = false;
|
||||
const int pid = process.cleanedStdOut().trimmed().toInt(&ok);
|
||||
if (!ok || pid != m_viewerPid) {
|
||||
m_viewerPid = -1;
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const TimeoutTask timeout([](std::chrono::milliseconds &timeout) { timeout = 2s; });
|
||||
|
||||
const Group root {
|
||||
Group {
|
||||
pidIterator,
|
||||
ProcessTask(onPidSetup, onPidDone, CallDoneIf::Success),
|
||||
timeout
|
||||
}.withTimeout(20s),
|
||||
Group {
|
||||
alivePidIterator,
|
||||
ProcessTask(onPidSetup, onAlivePidDone),
|
||||
timeout
|
||||
}
|
||||
};
|
||||
|
||||
m_pidRunner.start(root);
|
||||
}
|
||||
|
||||
void AndroidQmlPreviewWorker::startLogcat()
|
||||
@@ -192,7 +200,7 @@ void AndroidQmlPreviewWorker::startLogcat()
|
||||
QString args = QString("logcat --pid=%1").arg(m_viewerPid);
|
||||
if (!m_logcatStartTimeStamp.isEmpty())
|
||||
args += QString(" -T '%1'").arg(m_logcatStartTimeStamp);
|
||||
CommandLine cmd(androidConfig().adbToolPath());
|
||||
CommandLine cmd(AndroidConfig::adbToolPath());
|
||||
cmd.setArguments(args);
|
||||
m_logcatProcess.setCommand(cmd);
|
||||
m_logcatProcess.setUseCtrlCStub(true);
|
||||
@@ -220,7 +228,7 @@ AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(RunControl *runControl)
|
||||
m_rc(runControl)
|
||||
{
|
||||
connect(this, &RunWorker::started, this, &AndroidQmlPreviewWorker::startPidWatcher);
|
||||
connect(this, &RunWorker::stopped, &m_pidFutureWatcher, &QFutureWatcher<void>::cancel);
|
||||
connect(this, &RunWorker::stopped, &m_pidRunner, &TaskTreeRunner::reset);
|
||||
connect(this, &AndroidQmlPreviewWorker::previewPidChanged,
|
||||
this, &AndroidQmlPreviewWorker::startLogcat);
|
||||
|
||||
@@ -230,12 +238,6 @@ AndroidQmlPreviewWorker::AndroidQmlPreviewWorker(RunControl *runControl)
|
||||
});
|
||||
}
|
||||
|
||||
AndroidQmlPreviewWorker::~AndroidQmlPreviewWorker()
|
||||
{
|
||||
m_pidFutureWatcher.cancel();
|
||||
m_pidFutureWatcher.waitForFinished();
|
||||
}
|
||||
|
||||
void AndroidQmlPreviewWorker::start()
|
||||
{
|
||||
const SdkToolResult dateResult = runAdbCommand({"shell", "date", "+%s"});
|
||||
@@ -254,7 +256,7 @@ void AndroidQmlPreviewWorker::start()
|
||||
|
||||
void AndroidQmlPreviewWorker::stop()
|
||||
{
|
||||
if (!isPreviewRunning(m_viewerPid) || stopPreviewApp())
|
||||
if (m_viewerPid <= 0 || stopPreviewApp())
|
||||
appendMessage(Tr::tr("%1 has been stopped.").arg(apkInfo()->name), NormalMessageFormat);
|
||||
m_viewerPid = -1;
|
||||
reportStopped();
|
||||
@@ -262,13 +264,12 @@ void AndroidQmlPreviewWorker::stop()
|
||||
|
||||
bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
|
||||
{
|
||||
AndroidAvdManager avdMananager;
|
||||
QString devSN = AndroidManager::deviceSerialNumber(m_rc->target());
|
||||
|
||||
if (devSN.isEmpty())
|
||||
devSN = m_serialNumber;
|
||||
|
||||
if (!avdMananager.isAvdBooted(devSN)) {
|
||||
if (!AndroidAvdManager::isAvdBooted(devSN)) {
|
||||
const IDevice *dev = DeviceKitAspect::device(m_rc->target()->kit()).get();
|
||||
if (!dev) {
|
||||
appendMessage(Tr::tr("Selected device is invalid."), ErrorMessageFormat);
|
||||
@@ -283,13 +284,13 @@ bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
|
||||
if (devInfoLocal.isValid()) {
|
||||
if (dev->machineType() == IDevice::Emulator) {
|
||||
appendMessage(Tr::tr("Launching AVD."), NormalMessageFormat);
|
||||
devInfoLocal.serialNumber = avdMananager.startAvd(devInfoLocal.avdName);
|
||||
devInfoLocal.serialNumber = AndroidAvdManager::startAvd(devInfoLocal.avdName);
|
||||
}
|
||||
if (devInfoLocal.serialNumber.isEmpty()) {
|
||||
appendMessage(Tr::tr("Could not start AVD."), ErrorMessageFormat);
|
||||
} else {
|
||||
m_serialNumber = devInfoLocal.serialNumber;
|
||||
m_avdAbis = androidConfig().getAbis(m_serialNumber);
|
||||
m_avdAbis = AndroidConfig::getAbis(m_serialNumber);
|
||||
}
|
||||
return !devInfoLocal.serialNumber.isEmpty();
|
||||
} else {
|
||||
@@ -297,7 +298,7 @@ bool AndroidQmlPreviewWorker::ensureAvdIsRunning()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
m_avdAbis = androidConfig().getAbis(m_serialNumber);
|
||||
m_avdAbis = AndroidConfig::getAbis(m_serialNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -459,7 +460,7 @@ bool AndroidQmlPreviewWorker::startPreviewApp()
|
||||
const QDir destDir(apkInfo()->uploadDir);
|
||||
const QString qmlrcPath = destDir.filePath(m_uploadInfo.uploadPackage.baseName()
|
||||
+ packageSuffix);
|
||||
const QStringList envVars = m_rc->aspect<EnvironmentAspect>()->environment.toStringList();
|
||||
const QStringList envVars = m_rc->aspectData<EnvironmentAspect>()->environment.toStringList();
|
||||
|
||||
const QStringList command {
|
||||
"am", "start",
|
||||
|
||||
@@ -61,9 +61,9 @@ QString AndroidQtVersion::invalidReason() const
|
||||
{
|
||||
QString tmp = QtVersion::invalidReason();
|
||||
if (tmp.isEmpty()) {
|
||||
if (androidConfig().ndkLocation(this).isEmpty())
|
||||
if (AndroidConfig::ndkLocation(this).isEmpty())
|
||||
return Tr::tr("NDK is not configured in Devices > Android.");
|
||||
if (androidConfig().sdkLocation().isEmpty())
|
||||
if (AndroidConfig::sdkLocation().isEmpty())
|
||||
return Tr::tr("SDK is not configured in Devices > Android.");
|
||||
if (qtAbis().isEmpty())
|
||||
return Tr::tr("Failed to detect the ABIs used by the Qt version. Check the settings in "
|
||||
@@ -79,7 +79,7 @@ bool AndroidQtVersion::supportsMultipleQtAbis() const
|
||||
|
||||
Abis AndroidQtVersion::detectQtAbis() const
|
||||
{
|
||||
const bool conf = androidConfig().sdkFullyConfigured();
|
||||
const bool conf = AndroidConfig::sdkFullyConfigured();
|
||||
return conf ? Utils::transform<Abis>(androidAbis(), &AndroidManager::androidAbi2Abi) : Abis();
|
||||
}
|
||||
|
||||
@@ -87,18 +87,17 @@ void AndroidQtVersion::addToEnvironment(const Kit *k, Utils::Environment &env) c
|
||||
{
|
||||
QtVersion::addToEnvironment(k, env);
|
||||
|
||||
const AndroidConfig &config = androidConfig();
|
||||
// this env vars are used by qmake mkspecs to generate makefiles (check QTDIR/mkspecs/android-g++/qmake.conf for more info)
|
||||
env.set(QLatin1String("ANDROID_NDK_HOST"), config.toolchainHost(this));
|
||||
env.set(QLatin1String("ANDROID_NDK_ROOT"), config.ndkLocation(this).toUserOutput());
|
||||
env.set(QLatin1String("ANDROID_NDK_HOST"), AndroidConfig::toolchainHost(this));
|
||||
env.set(QLatin1String("ANDROID_NDK_ROOT"), AndroidConfig::ndkLocation(this).toUserOutput());
|
||||
env.set(QLatin1String("ANDROID_NDK_PLATFORM"),
|
||||
config.bestNdkPlatformMatch(qMax(minimumNDK(), AndroidManager::minimumSDK(k)), this));
|
||||
AndroidConfig::bestNdkPlatformMatch(qMax(minimumNDK(), AndroidManager::minimumSDK(k)), this));
|
||||
}
|
||||
|
||||
void AndroidQtVersion::setupQmakeRunEnvironment(Utils::Environment &env) const
|
||||
{
|
||||
env.set(QLatin1String("ANDROID_NDK_ROOT"),
|
||||
androidConfig().ndkLocation(this).toUserOutput());
|
||||
AndroidConfig::ndkLocation(this).toUserOutput());
|
||||
}
|
||||
|
||||
QString AndroidQtVersion::description() const
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "androidtr.h"
|
||||
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectexplorersettings.h>
|
||||
#include <projectexplorer/runconfigurationaspects.h>
|
||||
#include <projectexplorer/target.h>
|
||||
@@ -36,7 +35,7 @@ AndroidRunner::AndroidRunner(RunControl *runControl, const QString &intentName)
|
||||
{
|
||||
setId("AndroidRunner");
|
||||
static const int metaTypes[] = {
|
||||
qRegisterMetaType<QVector<QStringList> >("QVector<QStringList>"),
|
||||
qRegisterMetaType<QList<QStringList>>("QList<QStringList>"),
|
||||
qRegisterMetaType<Utils::Port>("Utils::Port"),
|
||||
qRegisterMetaType<AndroidDeviceInfo>("Android::AndroidDeviceInfo")
|
||||
};
|
||||
@@ -88,7 +87,7 @@ AndroidRunner::~AndroidRunner()
|
||||
|
||||
void AndroidRunner::start()
|
||||
{
|
||||
if (!ProjectExplorerPlugin::projectExplorerSettings().deployBeforeRun) {
|
||||
if (!projectExplorerSettings().deployBeforeRun) {
|
||||
qCDebug(androidRunnerLog) << "Run without deployment";
|
||||
launchAVD();
|
||||
if (!m_launchedAVDName.isEmpty()) {
|
||||
@@ -168,28 +167,24 @@ void AndroidRunner::launchAVD()
|
||||
AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber);
|
||||
emit androidDeviceInfoChanged(info);
|
||||
if (info.isValid()) {
|
||||
AndroidAvdManager avdManager;
|
||||
if (!info.avdName.isEmpty() && avdManager.findAvd(info.avdName).isEmpty()) {
|
||||
bool launched = avdManager.startAvdAsync(info.avdName);
|
||||
m_launchedAVDName = launched ? info.avdName:"";
|
||||
} else {
|
||||
if (!info.avdName.isEmpty() && AndroidAvdManager::findAvd(info.avdName).isEmpty())
|
||||
m_launchedAVDName = AndroidAvdManager::startAvdAsync(info.avdName) ? info.avdName : "";
|
||||
else
|
||||
m_launchedAVDName.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidRunner::checkAVD()
|
||||
{
|
||||
AndroidAvdManager avdManager;
|
||||
QString serialNumber = avdManager.findAvd(m_launchedAVDName);
|
||||
const QString serialNumber = AndroidAvdManager::findAvd(m_launchedAVDName);
|
||||
if (!serialNumber.isEmpty())
|
||||
return; // try again on next timer hit
|
||||
|
||||
if (avdManager.isAvdBooted(serialNumber)) {
|
||||
if (AndroidAvdManager::isAvdBooted(serialNumber)) {
|
||||
m_checkAVDTimer.stop();
|
||||
AndroidManager::setDeviceSerialNumber(m_target, serialNumber);
|
||||
emit asyncStart();
|
||||
} else if (!androidConfig().isConnected(serialNumber)) {
|
||||
} else if (!AndroidConfig::isConnected(serialNumber)) {
|
||||
// device was disconnected
|
||||
m_checkAVDTimer.stop();
|
||||
}
|
||||
|
||||
@@ -20,20 +20,15 @@
|
||||
#include <qtsupport/baseqtversion.h>
|
||||
#include <qtsupport/qtkitaspect.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/stringutils.h>
|
||||
#include <utils/temporaryfile.h>
|
||||
#include <utils/url.h>
|
||||
|
||||
#include <QDate>
|
||||
#include <QLoggingCategory>
|
||||
#include <QScopeGuard>
|
||||
#include <QRegularExpression>
|
||||
#include <QScopeGuard>
|
||||
#include <QTcpServer>
|
||||
#include <QThread>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -57,17 +52,6 @@ static const QRegularExpression userIdPattern("u(\\d+)_a");
|
||||
|
||||
static const std::chrono::milliseconds s_jdbTimeout = 5s;
|
||||
|
||||
static int APP_START_TIMEOUT = 45000;
|
||||
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
|
||||
int msecs = APP_START_TIMEOUT)
|
||||
{
|
||||
bool timedOut = false;
|
||||
auto end = chrono::high_resolution_clock::now();
|
||||
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
|
||||
timedOut = true;
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
static qint64 extractPID(const QString &output, const QString &packageName)
|
||||
{
|
||||
qint64 pid = -1;
|
||||
@@ -82,67 +66,6 @@ static qint64 extractPID(const QString &output, const QString &packageName)
|
||||
return pid;
|
||||
}
|
||||
|
||||
static void findProcessPIDAndUser(QPromise<PidUserPair> &promise,
|
||||
QStringList selector,
|
||||
const QString &packageName,
|
||||
bool preNougat)
|
||||
{
|
||||
if (packageName.isEmpty())
|
||||
return;
|
||||
|
||||
static const QString pidScript = "pidof -s '%1'";
|
||||
static const QString pidScriptPreNougat = QStringLiteral("for p in /proc/[0-9]*; "
|
||||
"do cat <$p/cmdline && echo :${p##*/}; done");
|
||||
QStringList args = {selector};
|
||||
FilePath adbPath = androidConfig().adbToolPath();
|
||||
args.append("shell");
|
||||
args.append(preNougat ? pidScriptPreNougat : pidScript.arg(packageName));
|
||||
|
||||
qint64 processPID = -1;
|
||||
chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
|
||||
do {
|
||||
QThread::msleep(200);
|
||||
Process proc;
|
||||
proc.setCommand({adbPath, args});
|
||||
proc.runBlocking();
|
||||
const QString out = proc.allOutput();
|
||||
if (preNougat) {
|
||||
processPID = extractPID(out, packageName);
|
||||
} else {
|
||||
if (!out.isEmpty())
|
||||
processPID = out.trimmed().toLongLong();
|
||||
}
|
||||
} while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !promise.isCanceled());
|
||||
|
||||
qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat;
|
||||
|
||||
qint64 processUser = 0;
|
||||
if (processPID > 0 && !promise.isCanceled()) {
|
||||
args = {selector};
|
||||
args.append({"shell", "ps", "-o", "user", "-p"});
|
||||
args.append(QString::number(processPID));
|
||||
Process proc;
|
||||
proc.setCommand({adbPath, args});
|
||||
proc.runBlocking();
|
||||
const QString out = proc.allOutput();
|
||||
if (!out.isEmpty()) {
|
||||
QRegularExpressionMatch match;
|
||||
qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
|
||||
if (matchPos >= 0 && match.capturedLength(1) > 0) {
|
||||
bool ok = false;
|
||||
processUser = match.captured(1).toInt(&ok);
|
||||
if (!ok)
|
||||
processUser = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(androidRunWorkerLog) << "USER found:" << processUser;
|
||||
|
||||
if (!promise.isCanceled())
|
||||
promise.addResult(PidUserPair(processPID, processUser));
|
||||
}
|
||||
|
||||
static QString gdbServerArch(const QString &androidAbi)
|
||||
{
|
||||
if (androidAbi == ProjectExplorer::Constants::ANDROID_ABI_ARM64_V8A)
|
||||
@@ -170,11 +93,9 @@ static FilePath debugServer(bool useLldb, const Target *target)
|
||||
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
|
||||
QString preferredAbi = AndroidManager::apkDevicePreferredAbi(target);
|
||||
|
||||
const AndroidConfig &config = androidConfig();
|
||||
|
||||
if (useLldb) {
|
||||
// Search suitable lldb-server binary.
|
||||
const FilePath prebuilt = config.ndkLocation(qtVersion) / "toolchains/llvm/prebuilt";
|
||||
const FilePath prebuilt = AndroidConfig::ndkLocation(qtVersion) / "toolchains/llvm/prebuilt";
|
||||
const QString abiNeedle = lldbServerArch2(preferredAbi);
|
||||
|
||||
// The new, built-in LLDB.
|
||||
@@ -194,7 +115,7 @@ static FilePath debugServer(bool useLldb, const Target *target)
|
||||
return lldbServer;
|
||||
} else {
|
||||
// Search suitable gdbserver binary.
|
||||
const FilePath path = config.ndkLocation(qtVersion)
|
||||
const FilePath path = AndroidConfig::ndkLocation(qtVersion)
|
||||
.pathAppended(QString("prebuilt/android-%1/gdbserver/gdbserver")
|
||||
.arg(gdbServerArch(preferredAbi)));
|
||||
if (path.exists())
|
||||
@@ -210,7 +131,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
|
||||
auto runControl = runner->runControl();
|
||||
m_useLldb = Debugger::DebuggerKitAspect::engineType(runControl->kit())
|
||||
== Debugger::LldbEngineType;
|
||||
auto aspect = runControl->aspect<Debugger::DebuggerRunConfigurationAspect>();
|
||||
auto aspect = runControl->aspectData<Debugger::DebuggerRunConfigurationAspect>();
|
||||
Utils::Id runMode = runControl->runMode();
|
||||
const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
|
||||
m_useCppDebugger = debuggingMode && aspect->useCppDebugger;
|
||||
@@ -242,7 +163,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
|
||||
m_deviceSerialNumber = AndroidManager::deviceSerialNumber(target);
|
||||
m_apiLevel = AndroidManager::deviceApiLevel(target);
|
||||
|
||||
m_extraEnvVars = runControl->aspect<EnvironmentAspect>()->environment;
|
||||
m_extraEnvVars = runControl->aspectData<EnvironmentAspect>()->environment;
|
||||
qCDebug(androidRunWorkerLog).noquote() << "Environment variables for the app"
|
||||
<< m_extraEnvVars.toStringList();
|
||||
|
||||
@@ -251,14 +172,14 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
|
||||
|
||||
if (const Store sd = runControl->settingsData(Constants::ANDROID_AM_START_ARGS);
|
||||
!sd.values().isEmpty()) {
|
||||
QTC_CHECK(sd.first().type() == QVariant::String);
|
||||
QTC_CHECK(sd.first().typeId() == QMetaType::QString);
|
||||
const QString startArgs = sd.first().toString();
|
||||
m_amStartExtraArgs = ProcessArgs::splitArgs(startArgs, OsTypeOtherUnix);
|
||||
}
|
||||
|
||||
if (const Store sd = runControl->settingsData(Constants::ANDROID_PRESTARTSHELLCMDLIST);
|
||||
!sd.values().isEmpty()) {
|
||||
QTC_CHECK(sd.first().type() == QVariant::String);
|
||||
QTC_CHECK(sd.first().typeId() == QMetaType::QString);
|
||||
const QStringList commands = sd.first().toString().split('\n', Qt::SkipEmptyParts);
|
||||
for (const QString &shellCmd : commands)
|
||||
m_beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd));
|
||||
@@ -266,7 +187,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa
|
||||
|
||||
if (const Store sd = runControl->settingsData(Constants::ANDROID_POSTFINISHSHELLCMDLIST);
|
||||
!sd.values().isEmpty()) {
|
||||
QTC_CHECK(sd.first().type() == QVariant::String);
|
||||
QTC_CHECK(sd.first().typeId() == QMetaType::QString);
|
||||
const QStringList commands = sd.first().toString().split('\n', Qt::SkipEmptyParts);
|
||||
for (const QString &shellCmd : commands)
|
||||
m_afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
|
||||
@@ -288,9 +209,6 @@ AndroidRunnerWorker::~AndroidRunnerWorker()
|
||||
{
|
||||
if (m_processPID != -1)
|
||||
forceStop();
|
||||
|
||||
if (!m_pidFinder.isFinished())
|
||||
m_pidFinder.cancel();
|
||||
}
|
||||
|
||||
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *stdOut,
|
||||
@@ -367,12 +285,6 @@ bool AndroidRunnerWorker::packageFileExists(const QString &filePath)
|
||||
return success && !output.trimmed().isEmpty();
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::adbKill(qint64 pid)
|
||||
{
|
||||
if (!runAdb({"shell", "run-as", m_packageName, "kill", "-9", QString::number(pid)}))
|
||||
runAdb({"shell", "kill", "-9", QString::number(pid)});
|
||||
}
|
||||
|
||||
QStringList AndroidRunnerWorker::selector() const
|
||||
{
|
||||
return AndroidDeviceInfo::adbSelector(m_deviceSerialNumber);
|
||||
@@ -385,8 +297,11 @@ void AndroidRunnerWorker::forceStop()
|
||||
// try killing it via kill -9
|
||||
QString output;
|
||||
runAdb({"shell", "pidof", m_packageName}, &output);
|
||||
if (m_processPID != -1 && output == QString::number(m_processPID))
|
||||
adbKill(m_processPID);
|
||||
const QString pidString = QString::number(m_processPID);
|
||||
if (m_processPID != -1 && output == pidString
|
||||
&& !runAdb({"shell", "run-as", m_packageName, "kill", "-9", pidString})) {
|
||||
runAdb({"shell", "kill", "-9", pidString});
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::logcatReadStandardError()
|
||||
@@ -512,7 +427,7 @@ void Android::Internal::AndroidRunnerWorker::asyncStartLogcat()
|
||||
}
|
||||
|
||||
const QStringList logcatArgs = selector() << "logcat" << timeArg;
|
||||
const FilePath adb = androidConfig().adbToolPath();
|
||||
const FilePath adb = AndroidConfig::adbToolPath();
|
||||
qCDebug(androidRunWorkerLog).noquote() << "Running logcat command (async):"
|
||||
<< CommandLine(adb, logcatArgs).toUserOutput();
|
||||
m_adbLogcatProcess->setCommand({adb, logcatArgs});
|
||||
@@ -710,19 +625,69 @@ void AndroidRunnerWorker::asyncStart()
|
||||
{
|
||||
asyncStartHelper();
|
||||
|
||||
m_pidFinder = Utils::onResultReady(Utils::asyncRun(findProcessPIDAndUser,
|
||||
selector(),
|
||||
m_packageName,
|
||||
m_isPreNougat),
|
||||
this,
|
||||
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
|
||||
using namespace Tasking;
|
||||
|
||||
const Storage<PidUserPair> pidStorage;
|
||||
|
||||
const FilePath adbPath = AndroidConfig::adbToolPath();
|
||||
const QStringList args = selector();
|
||||
const QString pidScript = m_isPreNougat
|
||||
? QString("for p in /proc/[0-9]*; do cat <$p/cmdline && echo :${p##*/}; done")
|
||||
: QString("pidof -s '%1'").arg(m_packageName);
|
||||
|
||||
const auto onPidSetup = [adbPath, args, pidScript](Process &process) {
|
||||
process.setCommand({adbPath, {args, "shell", pidScript}});
|
||||
};
|
||||
const auto onPidDone = [pidStorage, packageName = m_packageName,
|
||||
isPreNougat = m_isPreNougat](const Process &process) {
|
||||
const QString out = process.allOutput();
|
||||
if (isPreNougat)
|
||||
pidStorage->first = extractPID(out, packageName);
|
||||
else if (!out.isEmpty())
|
||||
pidStorage->first = out.trimmed().toLongLong();
|
||||
};
|
||||
|
||||
const auto onUserSetup = [pidStorage, adbPath, args](Process &process) {
|
||||
process.setCommand({adbPath, {args, "shell", "ps", "-o", "user", "-p",
|
||||
QString::number(pidStorage->first)}});
|
||||
};
|
||||
const auto onUserDone = [pidStorage](const Process &process) {
|
||||
const QString out = process.allOutput();
|
||||
if (out.isEmpty())
|
||||
return DoneResult::Error;
|
||||
|
||||
QRegularExpressionMatch match;
|
||||
qsizetype matchPos = out.indexOf(userIdPattern, 0, &match);
|
||||
if (matchPos >= 0 && match.capturedLength(1) > 0) {
|
||||
bool ok = false;
|
||||
const qint64 processUser = match.captured(1).toInt(&ok);
|
||||
if (ok) {
|
||||
pidStorage->second = processUser;
|
||||
return DoneResult::Success;
|
||||
}
|
||||
}
|
||||
return DoneResult::Error;
|
||||
};
|
||||
|
||||
const Group root {
|
||||
pidStorage,
|
||||
onGroupSetup([pidStorage] { *pidStorage = {-1, 0}; }),
|
||||
Forever {
|
||||
stopOnSuccess,
|
||||
ProcessTask(onPidSetup, onPidDone, CallDoneIf::Success),
|
||||
TimeoutTask([](std::chrono::milliseconds &timeout) { timeout = 200ms; },
|
||||
DoneResult::Error)
|
||||
}.withTimeout(45s),
|
||||
ProcessTask(onUserSetup, onUserDone, CallDoneIf::Success),
|
||||
onGroupDone([pidStorage, this] { onProcessIdChanged(*pidStorage); })
|
||||
};
|
||||
|
||||
m_pidRunner.start(root);
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::asyncStop()
|
||||
{
|
||||
if (!m_pidFinder.isFinished())
|
||||
m_pidFinder.cancel();
|
||||
|
||||
m_pidRunner.reset();
|
||||
if (m_processPID != -1)
|
||||
forceStop();
|
||||
|
||||
@@ -742,7 +707,7 @@ void AndroidRunnerWorker::handleJdbWaiting()
|
||||
}
|
||||
m_afterFinishAdbCommands.push_back(removeForward.join(' '));
|
||||
|
||||
const FilePath jdbPath = androidConfig().openJDKLocation()
|
||||
const FilePath jdbPath = AndroidConfig::openJDKLocation()
|
||||
.pathAppended("bin/jdb").withExecutableSuffix();
|
||||
|
||||
QStringList jdbArgs("-connect");
|
||||
@@ -821,10 +786,8 @@ void AndroidRunnerWorker::removeForwardPort(const QString &port)
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
|
||||
void AndroidRunnerWorker::onProcessIdChanged(const PidUserPair &pidUser)
|
||||
{
|
||||
// Don't write to m_psProc from a different thread
|
||||
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
||||
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
|
||||
<< "to:" << pidUser.first;
|
||||
m_processPID = pidUser.first;
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
|
||||
#include <qmldebug/qmldebugcommandlinearguments.h>
|
||||
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <utils/port.h>
|
||||
|
||||
#include <QFuture>
|
||||
#include <utility>
|
||||
|
||||
namespace Utils {
|
||||
class FilePath;
|
||||
class Process;
|
||||
@@ -35,24 +34,11 @@ public:
|
||||
AndroidRunnerWorker(ProjectExplorer::RunWorker *runner, const QString &packageName);
|
||||
~AndroidRunnerWorker() override;
|
||||
|
||||
bool runAdb(const QStringList &args, QString *stdOut = nullptr, QString *stdErr = nullptr,
|
||||
const QByteArray &writeData = {});
|
||||
void adbKill(qint64 pid);
|
||||
QStringList selector() const;
|
||||
void forceStop();
|
||||
void logcatReadStandardError();
|
||||
void logcatReadStandardOutput();
|
||||
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
|
||||
void setAndroidDeviceInfo(const AndroidDeviceInfo &info);
|
||||
void setIsPreNougat(bool isPreNougat) { m_isPreNougat = isPreNougat; }
|
||||
void setIntentName(const QString &intentName) { m_intentName = intentName; }
|
||||
|
||||
void asyncStart();
|
||||
void asyncStop();
|
||||
void handleJdbWaiting();
|
||||
void handleJdbSettled();
|
||||
|
||||
void removeForwardPort(const QString &port);
|
||||
void setIsPreNougat(bool isPreNougat) { m_isPreNougat = isPreNougat; }
|
||||
void setIntentName(const QString &intentName) { m_intentName = intentName; }
|
||||
|
||||
signals:
|
||||
void remoteProcessStarted(Utils::Port debugServerPort, const QUrl &qmlServer, qint64 pid);
|
||||
@@ -62,6 +48,19 @@ signals:
|
||||
void remoteErrorOutput(const QString &output);
|
||||
|
||||
private:
|
||||
bool runAdb(const QStringList &args, QString *stdOut = nullptr, QString *stdErr = nullptr,
|
||||
const QByteArray &writeData = {});
|
||||
QStringList selector() const;
|
||||
void forceStop();
|
||||
void logcatReadStandardError();
|
||||
void logcatReadStandardOutput();
|
||||
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
|
||||
|
||||
void handleJdbWaiting();
|
||||
void handleJdbSettled();
|
||||
|
||||
void removeForwardPort(const QString &port);
|
||||
|
||||
void asyncStartHelper();
|
||||
void startNativeDebugging();
|
||||
bool startDebuggerServer(const QString &packageDir, const QString &debugServerFile, QString *errorStr = nullptr);
|
||||
@@ -75,7 +74,7 @@ private:
|
||||
Waiting,
|
||||
Settled
|
||||
};
|
||||
void onProcessIdChanged(PidUserPair pidUser);
|
||||
void onProcessIdChanged(const PidUserPair &pidUser);
|
||||
|
||||
// Create the processes and timer in the worker thread, for correct thread affinity
|
||||
bool m_isPreNougat = false;
|
||||
@@ -90,7 +89,7 @@ private:
|
||||
std::unique_ptr<Utils::Process> m_psIsAlive;
|
||||
QByteArray m_stdoutBuffer;
|
||||
QByteArray m_stderrBuffer;
|
||||
QFuture<PidUserPair> m_pidFinder;
|
||||
Tasking::TaskTreeRunner m_pidRunner;
|
||||
bool m_useCppDebugger = false;
|
||||
bool m_useLldb = false; // FIXME: Un-implemented currently.
|
||||
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidconstants.h"
|
||||
#include "androidsdkdownloader.h"
|
||||
#include "androidtr.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <solutions/tasking/barrier.h>
|
||||
#include <solutions/tasking/networkquery.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/networkaccessmanager.h>
|
||||
#include <utils/unarchiver.h>
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QStandardPaths>
|
||||
|
||||
@@ -24,16 +28,12 @@ using namespace Utils;
|
||||
namespace { Q_LOGGING_CATEGORY(sdkDownloaderLog, "qtc.android.sdkDownloader", QtWarningMsg) }
|
||||
|
||||
namespace Android::Internal {
|
||||
/**
|
||||
* @class SdkDownloader
|
||||
* @brief Download Android SDK tools package from within Qt Creator.
|
||||
*/
|
||||
AndroidSdkDownloader::AndroidSdkDownloader()
|
||||
{
|
||||
connect(&m_taskTreeRunner, &TaskTreeRunner::done, this, [this] { m_progressDialog.reset(); });
|
||||
}
|
||||
|
||||
AndroidSdkDownloader::~AndroidSdkDownloader() = default;
|
||||
static void logError(const QString &error)
|
||||
{
|
||||
qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data());
|
||||
QMessageBox::warning(Core::ICore::dialogParent(), dialogTitle(), error);
|
||||
}
|
||||
|
||||
static bool isHttpRedirect(QNetworkReply *reply)
|
||||
{
|
||||
@@ -74,56 +74,62 @@ static std::optional<QString> saveToDisk(const FilePath &filename, QIODevice *da
|
||||
return {};
|
||||
}
|
||||
|
||||
// TODO: Make it a separate async task in a chain?
|
||||
static bool verifyFileIntegrity(const FilePath fileName, const QByteArray &sha256)
|
||||
static void validateFileIntegrity(QPromise<void> &promise, const FilePath &fileName,
|
||||
const QByteArray &sha256)
|
||||
{
|
||||
QFile file(fileName.toString());
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha256);
|
||||
if (hash.addData(&file))
|
||||
return hash.result() == sha256;
|
||||
if (hash.addData(&file) && hash.result() == sha256)
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
promise.future().cancel();
|
||||
}
|
||||
|
||||
void AndroidSdkDownloader::downloadAndExtractSdk()
|
||||
GroupItem downloadSdkRecipe()
|
||||
{
|
||||
if (androidConfig().sdkToolsUrl().isEmpty()) {
|
||||
logError(Tr::tr("The SDK Tools download URL is empty."));
|
||||
return;
|
||||
}
|
||||
struct StorageStruct
|
||||
{
|
||||
StorageStruct() {
|
||||
progressDialog.reset(new QProgressDialog(Tr::tr("Downloading SDK Tools package..."),
|
||||
Tr::tr("Cancel"), 0, 100, Core::ICore::dialogParent()));
|
||||
progressDialog->setWindowModality(Qt::ApplicationModal);
|
||||
progressDialog->setWindowTitle(dialogTitle());
|
||||
progressDialog->setFixedSize(progressDialog->sizeHint());
|
||||
progressDialog->setAutoClose(false);
|
||||
progressDialog->show(); // TODO: Should not be needed. Investigate possible QT_BUG
|
||||
}
|
||||
std::unique_ptr<QProgressDialog> progressDialog;
|
||||
std::optional<FilePath> sdkFileName;
|
||||
};
|
||||
|
||||
m_progressDialog.reset(new QProgressDialog(Tr::tr("Downloading SDK Tools package..."),
|
||||
Tr::tr("Cancel"), 0, 100, Core::ICore::dialogParent()));
|
||||
m_progressDialog->setWindowModality(Qt::ApplicationModal);
|
||||
m_progressDialog->setWindowTitle(dialogTitle());
|
||||
m_progressDialog->setFixedSize(m_progressDialog->sizeHint());
|
||||
m_progressDialog->setAutoClose(false);
|
||||
connect(m_progressDialog.get(), &QProgressDialog::canceled, this, [this] {
|
||||
m_taskTreeRunner.reset();
|
||||
m_progressDialog.release()->deleteLater();
|
||||
});
|
||||
Storage<StorageStruct> storage;
|
||||
|
||||
Storage<std::optional<FilePath>> storage;
|
||||
const auto onSetup = [] {
|
||||
if (AndroidConfig::sdkToolsUrl().isEmpty()) {
|
||||
logError(Tr::tr("The SDK Tools download URL is empty."));
|
||||
return SetupResult::StopWithError;
|
||||
}
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const auto onQuerySetup = [this](NetworkQuery &query) {
|
||||
query.setRequest(QNetworkRequest(androidConfig().sdkToolsUrl()));
|
||||
const auto onQuerySetup = [storage](NetworkQuery &query) {
|
||||
query.setRequest(QNetworkRequest(AndroidConfig::sdkToolsUrl()));
|
||||
query.setNetworkAccessManager(NetworkAccessManager::instance());
|
||||
NetworkQuery *queryPtr = &query;
|
||||
connect(queryPtr, &NetworkQuery::started, this, [this, queryPtr] {
|
||||
QProgressDialog *progressDialog = storage->progressDialog.get();
|
||||
QObject::connect(queryPtr, &NetworkQuery::started, progressDialog, [queryPtr, progressDialog] {
|
||||
QNetworkReply *reply = queryPtr->reply();
|
||||
if (!reply)
|
||||
return;
|
||||
connect(reply, &QNetworkReply::downloadProgress,
|
||||
this, [this](qint64 received, qint64 max) {
|
||||
if (!m_progressDialog)
|
||||
return;
|
||||
m_progressDialog->setRange(0, max);
|
||||
m_progressDialog->setValue(received);
|
||||
QObject::connect(reply, &QNetworkReply::downloadProgress,
|
||||
progressDialog, [progressDialog](qint64 received, qint64 max) {
|
||||
progressDialog->setRange(0, max);
|
||||
progressDialog->setValue(received);
|
||||
});
|
||||
#if QT_CONFIG(ssl)
|
||||
connect(reply, &QNetworkReply::sslErrors,
|
||||
this, [this, reply](const QList<QSslError> &sslErrors) {
|
||||
QObject::connect(reply, &QNetworkReply::sslErrors,
|
||||
reply, [reply](const QList<QSslError> &sslErrors) {
|
||||
for (const QSslError &error : sslErrors)
|
||||
qCDebug(sdkDownloaderLog, "SSL error: %s\n", qPrintable(error.errorString()));
|
||||
logError(Tr::tr("Encountered SSL errors, download is aborted."));
|
||||
@@ -132,7 +138,10 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
|
||||
#endif
|
||||
});
|
||||
};
|
||||
const auto onQueryDone = [this, storage](const NetworkQuery &query, DoneWith result) {
|
||||
const auto onQueryDone = [storage](const NetworkQuery &query, DoneWith result) {
|
||||
if (result == DoneWith::Cancel)
|
||||
return;
|
||||
|
||||
QNetworkReply *reply = query.reply();
|
||||
QTC_ASSERT(reply, return);
|
||||
const QUrl url = reply->url();
|
||||
@@ -151,19 +160,26 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
|
||||
logError(*saveResult);
|
||||
return;
|
||||
}
|
||||
*storage = sdkFileName;
|
||||
storage->sdkFileName = sdkFileName;
|
||||
};
|
||||
|
||||
const auto onUnarchiveSetup = [this, storage](Unarchiver &unarchiver) {
|
||||
m_progressDialog->setRange(0, 0);
|
||||
m_progressDialog->setLabelText(Tr::tr("Unarchiving SDK Tools package..."));
|
||||
if (!*storage)
|
||||
const auto onValidationSetup = [storage](Async<void> &async) {
|
||||
if (!storage->sdkFileName)
|
||||
return SetupResult::StopWithError;
|
||||
const FilePath sdkFileName = **storage;
|
||||
if (!verifyFileIntegrity(sdkFileName, androidConfig().getSdkToolsSha256())) {
|
||||
logError(Tr::tr("Verifying the integrity of the downloaded file has failed."));
|
||||
return SetupResult::StopWithError;
|
||||
}
|
||||
async.setConcurrentCallData(validateFileIntegrity, *storage->sdkFileName,
|
||||
AndroidConfig::getSdkToolsSha256());
|
||||
storage->progressDialog->setRange(0, 0);
|
||||
storage->progressDialog->setLabelText(Tr::tr("Verifying package integrity..."));
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onValidationDone = [](DoneWith result) {
|
||||
if (result != DoneWith::Error)
|
||||
return;
|
||||
logError(Tr::tr("Verifying the integrity of the downloaded file has failed."));
|
||||
};
|
||||
const auto onUnarchiveSetup = [storage](Unarchiver &unarchiver) {
|
||||
storage->progressDialog->setRange(0, 0);
|
||||
storage->progressDialog->setLabelText(Tr::tr("Unarchiving SDK Tools package..."));
|
||||
const FilePath sdkFileName = *storage->sdkFileName;
|
||||
const auto sourceAndCommand = Unarchiver::sourceAndCommand(sdkFileName);
|
||||
if (!sourceAndCommand) {
|
||||
logError(sourceAndCommand.error());
|
||||
@@ -173,35 +189,31 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
|
||||
unarchiver.setDestDir(sdkFileName.parentDir());
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onUnarchiverDone = [this, storage](DoneWith result) {
|
||||
const auto onUnarchiverDone = [storage](DoneWith result) {
|
||||
if (result == DoneWith::Cancel)
|
||||
return;
|
||||
|
||||
if (result != DoneWith::Success) {
|
||||
logError(Tr::tr("Unarchiving error."));
|
||||
return;
|
||||
}
|
||||
androidConfig().setTemporarySdkToolsPath(
|
||||
(*storage)->parentDir().pathAppended(Constants::cmdlineToolsName));
|
||||
QMetaObject::invokeMethod(this, [this] { emit sdkExtracted(); }, Qt::QueuedConnection);
|
||||
AndroidConfig::setTemporarySdkToolsPath(
|
||||
storage->sdkFileName->parentDir().pathAppended(Constants::cmdlineToolsName));
|
||||
};
|
||||
const auto onCancelSetup = [storage] { return std::make_pair(storage->progressDialog.get(),
|
||||
&QProgressDialog::canceled); };
|
||||
|
||||
const Group root {
|
||||
return Group {
|
||||
storage,
|
||||
NetworkQueryTask(onQuerySetup, onQueryDone),
|
||||
UnarchiverTask(onUnarchiveSetup, onUnarchiverDone)
|
||||
Group {
|
||||
onGroupSetup(onSetup),
|
||||
NetworkQueryTask(onQuerySetup, onQueryDone),
|
||||
AsyncTask<void>(onValidationSetup, onValidationDone),
|
||||
UnarchiverTask(onUnarchiveSetup, onUnarchiverDone)
|
||||
}.withCancel(onCancelSetup)
|
||||
};
|
||||
|
||||
m_taskTreeRunner.start(root);
|
||||
}
|
||||
|
||||
QString AndroidSdkDownloader::dialogTitle()
|
||||
{
|
||||
return Tr::tr("Download SDK Tools");
|
||||
}
|
||||
|
||||
void AndroidSdkDownloader::logError(const QString &error)
|
||||
{
|
||||
qCDebug(sdkDownloaderLog, "%s", error.toUtf8().data());
|
||||
QMetaObject::invokeMethod(this, [this, error] { emit sdkDownloaderError(error); },
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
QString dialogTitle() { return Tr::tr("Download SDK Tools"); }
|
||||
|
||||
} // namespace Android::Internal
|
||||
|
||||
@@ -3,36 +3,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "androidconfigurations.h"
|
||||
#include <QString>
|
||||
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QProgressDialog;
|
||||
QT_END_NAMESPACE
|
||||
namespace Tasking { class GroupItem; }
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
class AndroidSdkDownloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AndroidSdkDownloader();
|
||||
~AndroidSdkDownloader();
|
||||
|
||||
void downloadAndExtractSdk();
|
||||
static QString dialogTitle();
|
||||
|
||||
signals:
|
||||
void sdkExtracted();
|
||||
void sdkDownloaderError(const QString &error);
|
||||
|
||||
private:
|
||||
void logError(const QString &error);
|
||||
|
||||
std::unique_ptr<QProgressDialog> m_progressDialog;
|
||||
Tasking::TaskTreeRunner m_taskTreeRunner;
|
||||
};
|
||||
Tasking::GroupItem downloadSdkRecipe();
|
||||
QString dialogTitle();
|
||||
|
||||
} // namespace Android::Internal
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,47 +4,39 @@
|
||||
|
||||
#include "androidsdkpackage.h"
|
||||
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/filepath.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QFuture>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Android {
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QRegularExpression;
|
||||
QT_END_MOC_NAMESPACE
|
||||
|
||||
class AndroidConfig;
|
||||
|
||||
namespace Internal {
|
||||
namespace Android::Internal {
|
||||
|
||||
class AndroidSdkManagerPrivate;
|
||||
|
||||
struct InstallationChange
|
||||
{
|
||||
QStringList toInstall;
|
||||
QStringList toUninstall = {};
|
||||
int count() const { return toInstall.count() + toUninstall.count(); }
|
||||
};
|
||||
|
||||
class AndroidSdkManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum CommandType
|
||||
{
|
||||
None,
|
||||
UpdateAll,
|
||||
UpdatePackage,
|
||||
LicenseCheck,
|
||||
LicenseWorkflow
|
||||
};
|
||||
|
||||
struct OperationOutput
|
||||
{
|
||||
bool success = false;
|
||||
CommandType type = None;
|
||||
QString stdOutput;
|
||||
QString stdError;
|
||||
};
|
||||
|
||||
AndroidSdkManager();
|
||||
~AndroidSdkManager() override;
|
||||
~AndroidSdkManager();
|
||||
|
||||
SdkPlatformList installedSdkPlatforms();
|
||||
const AndroidSdkPackageList &allSdkPackages();
|
||||
QStringList notFoundEssentialSdkPackages();
|
||||
QStringList missingEssentialSdkPackages();
|
||||
AndroidSdkPackageList installedSdkPackages();
|
||||
SystemImageList installedSystemImages();
|
||||
NdkList installedNdkPackages();
|
||||
@@ -57,31 +49,23 @@ public:
|
||||
BuildToolsList filteredBuildTools(int minApiLevel,
|
||||
AndroidSdkPackage::PackageState state
|
||||
= AndroidSdkPackage::Installed);
|
||||
void reloadPackages(bool forceReload = false);
|
||||
bool isBusy() const;
|
||||
void refreshPackages();
|
||||
void reloadPackages();
|
||||
|
||||
bool packageListingSuccessful() const;
|
||||
|
||||
QFuture<QString> availableArguments() const;
|
||||
QFuture<OperationOutput> updateAll();
|
||||
QFuture<OperationOutput> update(const QStringList &install, const QStringList &uninstall);
|
||||
QFuture<OperationOutput> checkPendingLicenses();
|
||||
QFuture<OperationOutput> runLicenseCommand();
|
||||
|
||||
void cancelOperatons();
|
||||
void acceptSdkLicense(bool accept);
|
||||
void runInstallationChange(const InstallationChange &change, const QString &extraMessage = {});
|
||||
void runUpdate();
|
||||
|
||||
signals:
|
||||
void packageReloadBegin();
|
||||
void packageReloadFinished();
|
||||
void cancelActiveOperations();
|
||||
|
||||
private:
|
||||
friend class AndroidSdkManagerPrivate;
|
||||
std::unique_ptr<AndroidSdkManagerPrivate> m_d;
|
||||
};
|
||||
|
||||
const QRegularExpression &assertionRegExp();
|
||||
|
||||
int parseProgress(const QString &out, bool &foundAssertion);
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
} // namespace Android::Internal
|
||||
|
||||
@@ -51,6 +51,27 @@ void AndroidSdkManagerTest::testAndroidSdkManagerProgressParser_data()
|
||||
<< true;
|
||||
}
|
||||
|
||||
static int parseProgress(const QString &out, bool &foundAssertion)
|
||||
{
|
||||
int progress = -1;
|
||||
if (out.isEmpty())
|
||||
return progress;
|
||||
static const QRegularExpression reg("(?<progress>\\d*)%");
|
||||
static const QRegularExpression regEndOfLine("[\\n\\r]");
|
||||
const QStringList lines = out.split(regEndOfLine, Qt::SkipEmptyParts);
|
||||
for (const QString &line : lines) {
|
||||
QRegularExpressionMatch match = reg.match(line);
|
||||
if (match.hasMatch()) {
|
||||
progress = match.captured("progress").toInt();
|
||||
if (progress < 0 || progress > 100)
|
||||
progress = -1;
|
||||
}
|
||||
if (!foundAssertion)
|
||||
foundAssertion = assertionRegExp().match(line).hasMatch();
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
void AndroidSdkManagerTest::testAndroidSdkManagerProgressParser()
|
||||
{
|
||||
QFETCH(QString, output);
|
||||
|
||||
342
src/plugins/android/androidsdkmanagerdialog.cpp
Normal file
342
src/plugins/android/androidsdkmanagerdialog.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidsdkmanager.h"
|
||||
#include "androidsdkmanagerdialog.h"
|
||||
#include "androidsdkmodel.h"
|
||||
#include "androidtr.h"
|
||||
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTreeView>
|
||||
|
||||
using namespace Utils;
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
class OptionsDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
OptionsDialog(AndroidSdkManager *sdkManager, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
QTC_CHECK(sdkManager);
|
||||
resize(800, 480);
|
||||
setWindowTitle(Tr::tr("SDK Manager Arguments"));
|
||||
|
||||
m_argumentDetailsEdit = new QPlainTextEdit;
|
||||
m_argumentDetailsEdit->setReadOnly(true);
|
||||
|
||||
m_process.setEnvironment(AndroidConfig::toolsEnvironment());
|
||||
m_process.setCommand({AndroidConfig::sdkManagerToolPath(),
|
||||
{"--help", "--sdk_root=" + AndroidConfig::sdkLocation().toString()}});
|
||||
connect(&m_process, &Process::done, this, [this] {
|
||||
const QString output = m_process.allOutput();
|
||||
QString argumentDetails;
|
||||
const int tagIndex = output.indexOf("Common Arguments:");
|
||||
if (tagIndex >= 0) {
|
||||
const int detailsIndex = output.indexOf('\n', tagIndex);
|
||||
if (detailsIndex >= 0)
|
||||
argumentDetails = output.mid(detailsIndex + 1);
|
||||
}
|
||||
if (argumentDetails.isEmpty())
|
||||
argumentDetails = Tr::tr("Cannot load available arguments for \"sdkmanager\" command.");
|
||||
m_argumentDetailsEdit->setPlainText(argumentDetails);
|
||||
});
|
||||
m_process.start();
|
||||
|
||||
auto dialogButtons = new QDialogButtonBox;
|
||||
dialogButtons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
connect(dialogButtons, &QDialogButtonBox::accepted, this, &OptionsDialog::accept);
|
||||
connect(dialogButtons, &QDialogButtonBox::rejected, this, &OptionsDialog::reject);
|
||||
|
||||
m_argumentsEdit = new QLineEdit;
|
||||
m_argumentsEdit->setText(AndroidConfig::sdkManagerToolArgs().join(" "));
|
||||
|
||||
using namespace Layouting;
|
||||
|
||||
Column {
|
||||
Form { Tr::tr("SDK manager arguments:"), m_argumentsEdit, br },
|
||||
Tr::tr("Available arguments:"),
|
||||
m_argumentDetailsEdit,
|
||||
dialogButtons,
|
||||
}.attachTo(this);
|
||||
}
|
||||
|
||||
QStringList sdkManagerArguments() const
|
||||
{
|
||||
const QString userInput = m_argumentsEdit->text().simplified();
|
||||
return userInput.isEmpty() ? QStringList() : userInput.split(' ');
|
||||
}
|
||||
|
||||
private:
|
||||
QPlainTextEdit *m_argumentDetailsEdit = nullptr;
|
||||
QLineEdit *m_argumentsEdit = nullptr;
|
||||
Process m_process;
|
||||
};
|
||||
|
||||
class PackageFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
PackageFilterModel(AndroidSdkModel *sdkModel);
|
||||
|
||||
void setAcceptedPackageState(AndroidSdkPackage::PackageState state);
|
||||
void setAcceptedSearchPackage(const QString &text);
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &sourceParent) const override;
|
||||
|
||||
private:
|
||||
AndroidSdkPackage::PackageState m_packageState = AndroidSdkPackage::AnyValidState;
|
||||
QString m_searchText;
|
||||
};
|
||||
|
||||
class AndroidSdkManagerDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
AndroidSdkManagerDialog(AndroidSdkManager *sdkManager, QWidget *parent);
|
||||
|
||||
private:
|
||||
AndroidSdkManager *m_sdkManager = nullptr;
|
||||
AndroidSdkModel *m_sdkModel = nullptr;
|
||||
};
|
||||
|
||||
AndroidSdkManagerDialog::AndroidSdkManagerDialog(AndroidSdkManager *sdkManager, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_sdkManager(sdkManager)
|
||||
, m_sdkModel(new AndroidSdkModel(m_sdkManager, this))
|
||||
{
|
||||
QTC_CHECK(sdkManager);
|
||||
|
||||
setWindowTitle(Tr::tr("Android SDK Manager"));
|
||||
resize(664, 396);
|
||||
setModal(true);
|
||||
|
||||
auto packagesView = new QTreeView;
|
||||
packagesView->setIndentation(20);
|
||||
packagesView->header()->setCascadingSectionResizes(false);
|
||||
|
||||
auto updateInstalledButton = new QPushButton(Tr::tr("Update Installed"));
|
||||
|
||||
auto channelCheckbox = new QComboBox;
|
||||
channelCheckbox->addItem(Tr::tr("Default"));
|
||||
channelCheckbox->addItem(Tr::tr("Stable"));
|
||||
channelCheckbox->addItem(Tr::tr("Beta"));
|
||||
channelCheckbox->addItem(Tr::tr("Dev"));
|
||||
channelCheckbox->addItem(Tr::tr("Canary"));
|
||||
|
||||
auto obsoleteCheckBox = new QCheckBox(Tr::tr("Include obsolete"));
|
||||
|
||||
auto showAvailableRadio = new QRadioButton(Tr::tr("Available"));
|
||||
auto showInstalledRadio = new QRadioButton(Tr::tr("Installed"));
|
||||
auto showAllRadio = new QRadioButton(Tr::tr("All"));
|
||||
showAllRadio->setChecked(true);
|
||||
|
||||
auto optionsButton = new QPushButton(Tr::tr("Advanced Options..."));
|
||||
|
||||
auto searchField = new FancyLineEdit;
|
||||
searchField->setPlaceholderText("Filter");
|
||||
|
||||
auto expandCheck = new QCheckBox(Tr::tr("Expand All"));
|
||||
|
||||
auto buttonBox = new QDialogButtonBox;
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
|
||||
buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
|
||||
|
||||
auto proxyModel = new PackageFilterModel(m_sdkModel);
|
||||
packagesView->setModel(proxyModel);
|
||||
packagesView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
packagesView->header()->setSectionResizeMode(AndroidSdkModel::packageNameColumn,
|
||||
QHeaderView::Stretch);
|
||||
packagesView->header()->setStretchLastSection(false);
|
||||
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
Grid {
|
||||
Row {searchField, expandCheck}, br,
|
||||
packagesView,
|
||||
Column {
|
||||
updateInstalledButton,
|
||||
st,
|
||||
Group {
|
||||
title(Tr::tr("Show Packages")),
|
||||
Column {
|
||||
Row { Tr::tr("Channel:"), channelCheckbox },
|
||||
obsoleteCheckBox,
|
||||
hr,
|
||||
showAvailableRadio,
|
||||
showInstalledRadio,
|
||||
showAllRadio,
|
||||
}
|
||||
},
|
||||
optionsButton,
|
||||
}, br,
|
||||
},
|
||||
buttonBox,
|
||||
}.attachTo(this);
|
||||
|
||||
connect(m_sdkModel, &AndroidSdkModel::dataChanged, this, [this, buttonBox] {
|
||||
buttonBox->button(QDialogButtonBox::Apply)
|
||||
->setEnabled(m_sdkModel->installationChange().count());
|
||||
});
|
||||
|
||||
connect(expandCheck, &QCheckBox::stateChanged, this, [packagesView](int state) {
|
||||
if (state == Qt::Checked)
|
||||
packagesView->expandAll();
|
||||
else
|
||||
packagesView->collapseAll();
|
||||
});
|
||||
connect(updateInstalledButton, &QPushButton::clicked,
|
||||
m_sdkManager, &AndroidSdkManager::runUpdate);
|
||||
connect(showAllRadio, &QRadioButton::toggled, this, [this, proxyModel](bool checked) {
|
||||
if (checked) {
|
||||
proxyModel->setAcceptedPackageState(AndroidSdkPackage::AnyValidState);
|
||||
m_sdkModel->resetSelection();
|
||||
}
|
||||
});
|
||||
connect(showInstalledRadio, &QRadioButton::toggled, this, [this, proxyModel](bool checked) {
|
||||
if (checked) {
|
||||
proxyModel->setAcceptedPackageState(AndroidSdkPackage::Installed);
|
||||
m_sdkModel->resetSelection();
|
||||
}
|
||||
});
|
||||
connect(showAvailableRadio, &QRadioButton::toggled, this, [this, proxyModel](bool checked) {
|
||||
if (checked) {
|
||||
proxyModel->setAcceptedPackageState(AndroidSdkPackage::Available);
|
||||
m_sdkModel->resetSelection();
|
||||
}
|
||||
});
|
||||
|
||||
connect(searchField, &QLineEdit::textChanged,
|
||||
this, [this, proxyModel, expandCheck](const QString &text) {
|
||||
proxyModel->setAcceptedSearchPackage(text);
|
||||
m_sdkModel->resetSelection();
|
||||
// It is more convenient to expand the view with the results
|
||||
expandCheck->setChecked(!text.isEmpty());
|
||||
});
|
||||
|
||||
connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, [this] {
|
||||
m_sdkManager->runInstallationChange(m_sdkModel->installationChange());
|
||||
});
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &AndroidSdkManagerDialog::reject);
|
||||
|
||||
connect(optionsButton, &QPushButton::clicked, this, [this] {
|
||||
OptionsDialog dlg(m_sdkManager, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
QStringList arguments = dlg.sdkManagerArguments();
|
||||
if (arguments != AndroidConfig::sdkManagerToolArgs()) {
|
||||
AndroidConfig::setSdkManagerToolArgs(arguments);
|
||||
m_sdkManager->reloadPackages();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(obsoleteCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
|
||||
const QString obsoleteArg = "--include_obsolete";
|
||||
QStringList args = AndroidConfig::sdkManagerToolArgs();
|
||||
if (state == Qt::Checked && !args.contains(obsoleteArg)) {
|
||||
args.append(obsoleteArg);
|
||||
AndroidConfig::setSdkManagerToolArgs(args);
|
||||
} else if (state == Qt::Unchecked && args.contains(obsoleteArg)) {
|
||||
args.removeAll(obsoleteArg);
|
||||
AndroidConfig::setSdkManagerToolArgs(args);
|
||||
}
|
||||
m_sdkManager->reloadPackages();
|
||||
});
|
||||
|
||||
connect(channelCheckbox, &QComboBox::currentIndexChanged, this, [this](int index) {
|
||||
QStringList args = AndroidConfig::sdkManagerToolArgs();
|
||||
QString existingArg;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const QString arg = "--channel=" + QString::number(i);
|
||||
if (args.contains(arg)) {
|
||||
existingArg = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == 0 && !existingArg.isEmpty()) {
|
||||
args.removeAll(existingArg);
|
||||
AndroidConfig::setSdkManagerToolArgs(args);
|
||||
} else if (index > 0) {
|
||||
// Add 1 to account for Stable (second item) being channel 0
|
||||
const QString channelArg = "--channel=" + QString::number(index - 1);
|
||||
if (existingArg != channelArg) {
|
||||
if (!existingArg.isEmpty()) {
|
||||
args.removeAll(existingArg);
|
||||
AndroidConfig::setSdkManagerToolArgs(args);
|
||||
}
|
||||
args.append(channelArg);
|
||||
AndroidConfig::setSdkManagerToolArgs(args);
|
||||
}
|
||||
}
|
||||
m_sdkManager->reloadPackages();
|
||||
});
|
||||
}
|
||||
|
||||
PackageFilterModel::PackageFilterModel(AndroidSdkModel *sdkModel) :
|
||||
QSortFilterProxyModel(sdkModel)
|
||||
{
|
||||
setSourceModel(sdkModel);
|
||||
}
|
||||
|
||||
void PackageFilterModel::setAcceptedPackageState(AndroidSdkPackage::PackageState state)
|
||||
{
|
||||
m_packageState = state;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void PackageFilterModel::setAcceptedSearchPackage(const QString &name)
|
||||
{
|
||||
m_searchText = name;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool PackageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
QModelIndex srcIndex = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
if (!srcIndex.isValid())
|
||||
return false;
|
||||
|
||||
auto packageState = [](const QModelIndex& i) {
|
||||
return (AndroidSdkPackage::PackageState)i.data(AndroidSdkModel::PackageStateRole).toInt();
|
||||
};
|
||||
|
||||
auto packageFound = [this](const QModelIndex& i) {
|
||||
return i.data(AndroidSdkModel::packageNameColumn).toString()
|
||||
.contains(m_searchText, Qt::CaseInsensitive);
|
||||
};
|
||||
|
||||
bool showTopLevel = false;
|
||||
if (!sourceParent.isValid()) {
|
||||
// Top Level items
|
||||
for (int row = 0; row < sourceModel()->rowCount(srcIndex); ++row) {
|
||||
QModelIndex childIndex = sourceModel()->index(row, 0, srcIndex);
|
||||
if ((m_packageState & packageState(childIndex) && packageFound(childIndex))) {
|
||||
showTopLevel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return showTopLevel || ((packageState(srcIndex) & m_packageState) && packageFound(srcIndex));
|
||||
}
|
||||
|
||||
void executeAndroidSdkManagerDialog(AndroidSdkManager *sdkManager, QWidget *parent)
|
||||
{
|
||||
AndroidSdkManagerDialog dialog(sdkManager, parent);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
} // Android::Internal
|
||||
13
src/plugins/android/androidsdkmanagerdialog.h
Normal file
13
src/plugins/android/androidsdkmanagerdialog.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
class AndroidSdkManager;
|
||||
|
||||
void executeAndroidSdkManagerDialog(AndroidSdkManager *sdkManager, QWidget *parent);
|
||||
|
||||
} // Android::Internal
|
||||
@@ -1,682 +0,0 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidsdkmanager.h"
|
||||
#include "androidsdkmanagerwidget.h"
|
||||
#include "androidsdkmodel.h"
|
||||
#include "androidtr.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/outputformatter.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGuiApplication>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMessageBox>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
using namespace Utils;
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
static Q_LOGGING_CATEGORY(androidSdkMgrUiLog, "qtc.android.sdkManagerUi", QtWarningMsg)
|
||||
|
||||
class PackageFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
PackageFilterModel(AndroidSdkModel *sdkModel);
|
||||
|
||||
void setAcceptedPackageState(AndroidSdkPackage::PackageState state);
|
||||
void setAcceptedSearchPackage(const QString &text);
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &sourceParent) const override;
|
||||
|
||||
private:
|
||||
AndroidSdkPackage::PackageState m_packageState = AndroidSdkPackage::AnyValidState;
|
||||
QString m_searchText;
|
||||
};
|
||||
|
||||
AndroidSdkManagerWidget::AndroidSdkManagerWidget(AndroidSdkManager *sdkManager, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_sdkManager(sdkManager),
|
||||
m_sdkModel(new AndroidSdkModel(m_sdkManager, this))
|
||||
{
|
||||
QTC_CHECK(sdkManager);
|
||||
|
||||
setWindowTitle(Tr::tr("Android SDK Manager"));
|
||||
resize(664, 396);
|
||||
setModal(true);
|
||||
|
||||
m_packagesStack = new QWidget;
|
||||
|
||||
auto packagesView = new QTreeView(m_packagesStack);
|
||||
packagesView->setIndentation(20);
|
||||
packagesView->header()->setCascadingSectionResizes(false);
|
||||
|
||||
auto updateInstalledButton = new QPushButton(Tr::tr("Update Installed"));
|
||||
|
||||
auto channelCheckbox = new QComboBox;
|
||||
channelCheckbox->addItem(Tr::tr("Default"));
|
||||
channelCheckbox->addItem(Tr::tr("Stable"));
|
||||
channelCheckbox->addItem(Tr::tr("Beta"));
|
||||
channelCheckbox->addItem(Tr::tr("Dev"));
|
||||
channelCheckbox->addItem(Tr::tr("Canary"));
|
||||
|
||||
auto obsoleteCheckBox = new QCheckBox(Tr::tr("Include obsolete"));
|
||||
|
||||
auto showAvailableRadio = new QRadioButton(Tr::tr("Available"));
|
||||
auto showInstalledRadio = new QRadioButton(Tr::tr("Installed"));
|
||||
auto showAllRadio = new QRadioButton(Tr::tr("All"));
|
||||
showAllRadio->setChecked(true);
|
||||
|
||||
auto optionsButton = new QPushButton(Tr::tr("Advanced Options..."));
|
||||
|
||||
auto searchField = new FancyLineEdit(m_packagesStack);
|
||||
searchField->setPlaceholderText("Filter");
|
||||
|
||||
auto expandCheck = new QCheckBox(Tr::tr("Expand All"));
|
||||
|
||||
m_outputStack = new QWidget;
|
||||
m_operationProgress = new QProgressBar(m_outputStack);
|
||||
|
||||
m_outputEdit = new QPlainTextEdit(m_outputStack);
|
||||
m_outputEdit->setReadOnly(true);
|
||||
|
||||
m_sdkLicenseLabel = new QLabel(Tr::tr("Do you want to accept the Android SDK license?"));
|
||||
m_sdkLicenseLabel->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
|
||||
m_sdkLicenseLabel->hide();
|
||||
|
||||
m_sdkLicenseButtonBox = new QDialogButtonBox(m_outputStack);
|
||||
m_sdkLicenseButtonBox->setEnabled(false);
|
||||
m_sdkLicenseButtonBox->setStandardButtons(QDialogButtonBox::No|QDialogButtonBox::Yes);
|
||||
m_sdkLicenseButtonBox->hide();
|
||||
|
||||
m_buttonBox = new QDialogButtonBox(this);
|
||||
m_buttonBox->setStandardButtons(QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
|
||||
m_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
|
||||
|
||||
m_viewStack = new QStackedWidget(this);
|
||||
m_viewStack->addWidget(m_packagesStack);
|
||||
m_viewStack->addWidget(m_outputStack);
|
||||
m_viewStack->setCurrentWidget(m_packagesStack);
|
||||
|
||||
m_formatter = new OutputFormatter;
|
||||
m_formatter->setPlainTextEdit(m_outputEdit);
|
||||
|
||||
auto proxyModel = new PackageFilterModel(m_sdkModel);
|
||||
packagesView->setModel(proxyModel);
|
||||
packagesView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
packagesView->header()->setSectionResizeMode(AndroidSdkModel::packageNameColumn,
|
||||
QHeaderView::Stretch);
|
||||
packagesView->header()->setStretchLastSection(false);
|
||||
|
||||
using namespace Layouting;
|
||||
Grid {
|
||||
searchField, expandCheck, br,
|
||||
|
||||
Span(2, packagesView),
|
||||
Column {
|
||||
updateInstalledButton,
|
||||
st,
|
||||
Group {
|
||||
title(Tr::tr("Show Packages")),
|
||||
Column {
|
||||
Row { Tr::tr("Channel:"), channelCheckbox },
|
||||
obsoleteCheckBox,
|
||||
hr,
|
||||
showAvailableRadio,
|
||||
showInstalledRadio,
|
||||
showAllRadio,
|
||||
}
|
||||
},
|
||||
optionsButton
|
||||
},
|
||||
noMargin
|
||||
}.attachTo(m_packagesStack);
|
||||
|
||||
Column {
|
||||
m_outputEdit,
|
||||
Row { m_sdkLicenseLabel, m_sdkLicenseButtonBox },
|
||||
m_operationProgress,
|
||||
noMargin
|
||||
}.attachTo(m_outputStack);
|
||||
|
||||
Column {
|
||||
m_viewStack,
|
||||
m_buttonBox
|
||||
}.attachTo(this);
|
||||
|
||||
connect(m_sdkModel, &AndroidSdkModel::dataChanged, this, [this] {
|
||||
if (m_viewStack->currentWidget() == m_packagesStack)
|
||||
m_buttonBox->button(QDialogButtonBox::Apply)
|
||||
->setEnabled(!m_sdkModel->userSelection().isEmpty());
|
||||
});
|
||||
|
||||
connect(m_sdkModel, &AndroidSdkModel::modelAboutToBeReset, this,
|
||||
[this, expandCheck] {
|
||||
m_buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
|
||||
expandCheck->setChecked(false);
|
||||
cancelPendingOperations();
|
||||
switchView(PackageListing);
|
||||
});
|
||||
|
||||
connect(expandCheck, &QCheckBox::stateChanged, this, [packagesView](int state) {
|
||||
if (state == Qt::Checked)
|
||||
packagesView->expandAll();
|
||||
else
|
||||
packagesView->collapseAll();
|
||||
});
|
||||
connect(updateInstalledButton, &QPushButton::clicked,
|
||||
this, &AndroidSdkManagerWidget::onUpdatePackages);
|
||||
connect(showAllRadio, &QRadioButton::toggled, this, [this, proxyModel](bool checked) {
|
||||
if (checked) {
|
||||
proxyModel->setAcceptedPackageState(AndroidSdkPackage::AnyValidState);
|
||||
m_sdkModel->resetSelection();
|
||||
}
|
||||
});
|
||||
connect(showInstalledRadio, &QRadioButton::toggled,
|
||||
this, [this, proxyModel](bool checked) {
|
||||
if (checked) {
|
||||
proxyModel->setAcceptedPackageState(AndroidSdkPackage::Installed);
|
||||
m_sdkModel->resetSelection();
|
||||
}
|
||||
});
|
||||
connect(showAvailableRadio, &QRadioButton::toggled, this, [this, proxyModel](bool checked) {
|
||||
if (checked) {
|
||||
proxyModel->setAcceptedPackageState(AndroidSdkPackage::Available);
|
||||
m_sdkModel->resetSelection();
|
||||
}
|
||||
});
|
||||
|
||||
connect(searchField, &QLineEdit::textChanged,
|
||||
this, [this, proxyModel, expandCheck](const QString &text) {
|
||||
proxyModel->setAcceptedSearchPackage(text);
|
||||
m_sdkModel->resetSelection();
|
||||
// It is more convenient to expand the view with the results
|
||||
expandCheck->setChecked(!text.isEmpty());
|
||||
});
|
||||
|
||||
connect(m_buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, [this] {
|
||||
onApplyButton();
|
||||
});
|
||||
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &AndroidSdkManagerWidget::onCancel);
|
||||
|
||||
connect(optionsButton, &QPushButton::clicked,
|
||||
this, &AndroidSdkManagerWidget::onSdkManagerOptions);
|
||||
connect(m_sdkLicenseButtonBox, &QDialogButtonBox::accepted, this, [this] {
|
||||
m_sdkManager->acceptSdkLicense(true);
|
||||
m_sdkLicenseButtonBox->setEnabled(false); // Wait for next license to enable controls
|
||||
});
|
||||
connect(m_sdkLicenseButtonBox, &QDialogButtonBox::rejected, this, [this] {
|
||||
m_sdkManager->acceptSdkLicense(false);
|
||||
m_sdkLicenseButtonBox->setEnabled(false); // Wait for next license to enable controls
|
||||
});
|
||||
|
||||
connect(obsoleteCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
|
||||
const QString obsoleteArg = "--include_obsolete";
|
||||
QStringList args = androidConfig().sdkManagerToolArgs();
|
||||
if (state == Qt::Checked && !args.contains(obsoleteArg)) {
|
||||
args.append(obsoleteArg);
|
||||
androidConfig().setSdkManagerToolArgs(args);
|
||||
} else if (state == Qt::Unchecked && args.contains(obsoleteArg)) {
|
||||
args.removeAll(obsoleteArg);
|
||||
androidConfig().setSdkManagerToolArgs(args);
|
||||
}
|
||||
m_sdkManager->reloadPackages(true);
|
||||
});
|
||||
|
||||
connect(channelCheckbox, &QComboBox::currentIndexChanged, this, [this](int index) {
|
||||
QStringList args = androidConfig().sdkManagerToolArgs();
|
||||
QString existingArg;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const QString arg = "--channel=" + QString::number(i);
|
||||
if (args.contains(arg)) {
|
||||
existingArg = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index == 0 && !existingArg.isEmpty()) {
|
||||
args.removeAll(existingArg);
|
||||
androidConfig().setSdkManagerToolArgs(args);
|
||||
} else if (index > 0) {
|
||||
// Add 1 to account for Stable (second item) being channel 0
|
||||
const QString channelArg = "--channel=" + QString::number(index - 1);
|
||||
if (existingArg != channelArg) {
|
||||
if (!existingArg.isEmpty()) {
|
||||
args.removeAll(existingArg);
|
||||
androidConfig().setSdkManagerToolArgs(args);
|
||||
}
|
||||
args.append(channelArg);
|
||||
androidConfig().setSdkManagerToolArgs(args);
|
||||
}
|
||||
}
|
||||
m_sdkManager->reloadPackages(true);
|
||||
});
|
||||
}
|
||||
|
||||
AndroidSdkManagerWidget::~AndroidSdkManagerWidget()
|
||||
{
|
||||
if (m_currentOperation)
|
||||
delete m_currentOperation;
|
||||
cancelPendingOperations();
|
||||
delete m_formatter;
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::installEssentials()
|
||||
{
|
||||
m_sdkModel->selectMissingEssentials();
|
||||
if (!m_sdkModel->missingEssentials().isEmpty()) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Android SDK Changes"),
|
||||
Tr::tr("%1 cannot find the following essential packages: \"%2\".\n"
|
||||
"Install them manually after the current operation is done.\n")
|
||||
.arg(QGuiApplication::applicationDisplayName(),
|
||||
m_sdkModel->missingEssentials().join("\", \"")));
|
||||
}
|
||||
onApplyButton(Tr::tr("Android SDK installation is missing necessary packages. "
|
||||
"Do you want to install the missing packages?"));
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::beginLicenseCheck()
|
||||
{
|
||||
m_formatter->appendMessage(Tr::tr("Checking pending licenses...") + "\n", NormalMessageFormat);
|
||||
m_formatter->appendMessage(Tr::tr("The installation of Android SDK packages may fail if the "
|
||||
"respective licenses are not accepted.")
|
||||
+ "\n",
|
||||
LogMessageFormat);
|
||||
addPackageFuture(m_sdkManager->checkPendingLicenses());
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::onApplyButton(const QString &extraMessage)
|
||||
{
|
||||
QTC_ASSERT(m_currentView == PackageListing, return);
|
||||
|
||||
if (m_sdkManager->isBusy()) {
|
||||
m_formatter->appendMessage("\n" + Tr::tr("SDK Manager is busy."), StdErrFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<const AndroidSdkPackage *> packagesToUpdate = m_sdkModel->userSelection();
|
||||
if (packagesToUpdate.isEmpty())
|
||||
return;
|
||||
|
||||
QStringList installPackages, uninstallPackages;
|
||||
for (auto package : packagesToUpdate) {
|
||||
QString str = QString(" %1").arg(package->descriptionText());
|
||||
if (package->state() == AndroidSdkPackage::Installed)
|
||||
uninstallPackages << str;
|
||||
else
|
||||
installPackages << str;
|
||||
}
|
||||
|
||||
QString message = Tr::tr("%n Android SDK packages shall be updated.", "", packagesToUpdate.count());
|
||||
if (!extraMessage.isEmpty())
|
||||
message.prepend(extraMessage + "\n\n");
|
||||
QMessageBox messageDlg(QMessageBox::Information,
|
||||
Tr::tr("Android SDK Changes"),
|
||||
message,
|
||||
QMessageBox::Ok | QMessageBox::Cancel,
|
||||
Core::ICore::dialogParent());
|
||||
|
||||
QString details;
|
||||
if (!uninstallPackages.isEmpty())
|
||||
details = Tr::tr("[Packages to be uninstalled:]\n").append(uninstallPackages.join("\n"));
|
||||
|
||||
if (!installPackages.isEmpty()) {
|
||||
if (!uninstallPackages.isEmpty())
|
||||
details.append("\n\n");
|
||||
details.append("[Packages to be installed:]\n").append(installPackages.join("\n"));
|
||||
}
|
||||
messageDlg.setDetailedText(details);
|
||||
if (messageDlg.exec() == QMessageBox::Cancel)
|
||||
return;
|
||||
|
||||
// Open the SDK Manager dialog after accepting to continue with the installation
|
||||
show();
|
||||
|
||||
switchView(Operations);
|
||||
m_pendingCommand = AndroidSdkManager::UpdatePackage;
|
||||
// User agreed with the selection. Check for licenses.
|
||||
if (!installPackages.isEmpty()) {
|
||||
// Pending license affects installtion only.
|
||||
beginLicenseCheck();
|
||||
} else {
|
||||
// Uninstall only. Go Ahead.
|
||||
beginExecution();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::onUpdatePackages()
|
||||
{
|
||||
if (m_sdkManager->isBusy()) {
|
||||
m_formatter->appendMessage("\n" + Tr::tr("SDK Manager is busy."), StdErrFormat);
|
||||
return;
|
||||
}
|
||||
switchView(Operations);
|
||||
m_pendingCommand = AndroidSdkManager::UpdateAll;
|
||||
beginLicenseCheck();
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::onCancel()
|
||||
{
|
||||
cancelPendingOperations();
|
||||
close();
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::onOperationResult(int index)
|
||||
{
|
||||
QTC_ASSERT(m_currentOperation, return);
|
||||
AndroidSdkManager::OperationOutput result = m_currentOperation->resultAt(index);
|
||||
if (result.type == AndroidSdkManager::LicenseWorkflow) {
|
||||
// Show license controls and enable to user input.
|
||||
m_sdkLicenseLabel->setVisible(true);
|
||||
m_sdkLicenseButtonBox->setVisible(true);
|
||||
m_sdkLicenseButtonBox->setEnabled(true);
|
||||
m_sdkLicenseButtonBox->button(QDialogButtonBox::No)->setDefault(true);
|
||||
}
|
||||
auto breakLine = [](const QString &line) { return line.endsWith("\n") ? line : line + "\n";};
|
||||
if (!result.stdError.isEmpty() && result.type != AndroidSdkManager::LicenseCheck)
|
||||
m_formatter->appendMessage(breakLine(result.stdError), StdErrFormat);
|
||||
if (!result.stdOutput.isEmpty() && result.type != AndroidSdkManager::LicenseCheck)
|
||||
m_formatter->appendMessage(breakLine(result.stdOutput), StdOutFormat);
|
||||
m_outputEdit->ensureCursorVisible();
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::onLicenseCheckResult(const AndroidSdkManager::OperationOutput& output)
|
||||
{
|
||||
if (output.success) {
|
||||
// No assertion was found. Looks like all license are accepted. Go Ahead.
|
||||
runPendingCommand();
|
||||
} else {
|
||||
// Run license workflow.
|
||||
beginLicenseWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput>
|
||||
&future)
|
||||
{
|
||||
QTC_ASSERT(!m_currentOperation, return);
|
||||
if (!future.isFinished() || !future.isCanceled()) {
|
||||
m_currentOperation = new QFutureWatcher<AndroidSdkManager::OperationOutput>;
|
||||
connect(m_currentOperation, &QFutureWatcherBase::resultReadyAt,
|
||||
this, &AndroidSdkManagerWidget::onOperationResult);
|
||||
connect(m_currentOperation, &QFutureWatcherBase::finished,
|
||||
this, &AndroidSdkManagerWidget::packageFutureFinished);
|
||||
connect(m_currentOperation, &QFutureWatcherBase::progressValueChanged,
|
||||
this, [this](int value) {
|
||||
m_operationProgress->setValue(value);
|
||||
});
|
||||
m_currentOperation->setFuture(future);
|
||||
} else {
|
||||
qCDebug(androidSdkMgrUiLog) << "Operation canceled/finished before adding to the queue";
|
||||
if (m_sdkManager->isBusy()) {
|
||||
m_formatter->appendMessage(Tr::tr("SDK Manager is busy. Operation cancelled."),
|
||||
StdErrFormat);
|
||||
}
|
||||
notifyOperationFinished();
|
||||
switchView(PackageListing);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::beginExecution()
|
||||
{
|
||||
const QList<const AndroidSdkPackage *> packagesToUpdate = m_sdkModel->userSelection();
|
||||
if (packagesToUpdate.isEmpty()) {
|
||||
switchView(PackageListing);
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList installSdkPaths, uninstallSdkPaths;
|
||||
for (auto package : packagesToUpdate) {
|
||||
if (package->state() == AndroidSdkPackage::Installed)
|
||||
uninstallSdkPaths << package->sdkStylePath();
|
||||
else
|
||||
installSdkPaths << package->sdkStylePath();
|
||||
}
|
||||
m_formatter->appendMessage(Tr::tr("Installing/Uninstalling selected packages...\n"),
|
||||
NormalMessageFormat);
|
||||
m_formatter->appendMessage(Tr::tr("Closing the %1 dialog will cancel the running and scheduled SDK "
|
||||
"operations.\n").arg(HostOsInfo::isMacHost() ?
|
||||
Tr::tr("preferences") : Tr::tr("options")),
|
||||
LogMessageFormat);
|
||||
|
||||
addPackageFuture(m_sdkManager->update(installSdkPaths, uninstallSdkPaths));
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::beginUpdate()
|
||||
{
|
||||
m_formatter->appendMessage(Tr::tr("Updating installed packages...\n"), NormalMessageFormat);
|
||||
m_formatter->appendMessage(Tr::tr("Closing the %1 dialog will cancel the running and scheduled SDK "
|
||||
"operations.\n").arg(HostOsInfo::isMacHost() ?
|
||||
Tr::tr("preferences") : Tr::tr("options")),
|
||||
LogMessageFormat);
|
||||
addPackageFuture(m_sdkManager->updateAll());
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::beginLicenseWorkflow()
|
||||
{
|
||||
switchView(LicenseWorkflow);
|
||||
addPackageFuture(m_sdkManager->runLicenseCommand());
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::notifyOperationFinished()
|
||||
{
|
||||
if (!m_currentOperation || m_currentOperation->isFinished()) {
|
||||
QMessageBox::information(this, Tr::tr("Android SDK Changes"),
|
||||
Tr::tr("Android SDK operations finished."), QMessageBox::Ok);
|
||||
m_operationProgress->setValue(0);
|
||||
// Once the update/install is done, let's hide the dialog.
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::packageFutureFinished()
|
||||
{
|
||||
QTC_ASSERT (m_currentOperation, return);
|
||||
|
||||
bool continueWorkflow = true;
|
||||
if (m_currentOperation->isCanceled()) {
|
||||
m_formatter->appendMessage(Tr::tr("Operation cancelled.\n"), StdErrFormat);
|
||||
continueWorkflow = false;
|
||||
}
|
||||
m_operationProgress->setValue(100);
|
||||
int resultCount = m_currentOperation->future().resultCount();
|
||||
if (continueWorkflow && resultCount > 0) {
|
||||
AndroidSdkManager::OperationOutput output = m_currentOperation->resultAt(resultCount -1);
|
||||
AndroidSdkManager::CommandType type = output.type;
|
||||
m_currentOperation->deleteLater();
|
||||
m_currentOperation = nullptr;
|
||||
switch (type) {
|
||||
case AndroidSdkManager::LicenseCheck:
|
||||
onLicenseCheckResult(output);
|
||||
break;
|
||||
case AndroidSdkManager::LicenseWorkflow:
|
||||
m_sdkLicenseButtonBox->hide();
|
||||
m_sdkLicenseLabel->hide();
|
||||
runPendingCommand();
|
||||
break;
|
||||
case AndroidSdkManager::UpdateAll:
|
||||
case AndroidSdkManager::UpdatePackage:
|
||||
notifyOperationFinished();
|
||||
switchView(PackageListing);
|
||||
m_sdkManager->reloadPackages(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
m_currentOperation->deleteLater();
|
||||
m_currentOperation = nullptr;
|
||||
switchView(PackageListing);
|
||||
m_sdkManager->reloadPackages(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::cancelPendingOperations()
|
||||
{
|
||||
if (!m_sdkManager->isBusy()) {
|
||||
m_formatter->appendMessage(Tr::tr("\nNo pending operations to cancel...\n"),
|
||||
NormalMessageFormat);
|
||||
switchView(PackageListing);
|
||||
return;
|
||||
}
|
||||
m_formatter->appendMessage(Tr::tr("\nCancelling pending operations...\n"),
|
||||
NormalMessageFormat);
|
||||
m_sdkManager->cancelOperatons();
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::switchView(AndroidSdkManagerWidget::View view)
|
||||
{
|
||||
if (m_currentView == PackageListing)
|
||||
m_formatter->clear();
|
||||
m_currentView = view;
|
||||
if (m_currentView == PackageListing) {
|
||||
// We need the buttonBox only in the main listing view, as the license and update
|
||||
// views already have a cancel button.
|
||||
m_buttonBox->button(QDialogButtonBox::Apply)->setVisible(true);
|
||||
emit updatingSdkFinished();
|
||||
} else {
|
||||
m_buttonBox->button(QDialogButtonBox::Apply)->setVisible(false);
|
||||
emit updatingSdk();
|
||||
}
|
||||
|
||||
if (m_currentView == LicenseWorkflow)
|
||||
emit licenseWorkflowStarted();
|
||||
|
||||
m_operationProgress->setValue(0);
|
||||
m_viewStack->setCurrentWidget(m_currentView == PackageListing ?
|
||||
m_packagesStack : m_outputStack);
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::runPendingCommand()
|
||||
{
|
||||
if (m_pendingCommand == AndroidSdkManager::UpdatePackage)
|
||||
beginExecution(); // License workflow can only start when updating packages.
|
||||
else if (m_pendingCommand == AndroidSdkManager::UpdateAll)
|
||||
beginUpdate();
|
||||
else
|
||||
QTC_ASSERT(false, qCDebug(androidSdkMgrUiLog) << "Unexpected state: No pending command.");
|
||||
}
|
||||
|
||||
void AndroidSdkManagerWidget::onSdkManagerOptions()
|
||||
{
|
||||
OptionsDialog dlg(m_sdkManager, androidConfig().sdkManagerToolArgs(), this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
QStringList arguments = dlg.sdkManagerArguments();
|
||||
if (arguments != androidConfig().sdkManagerToolArgs()) {
|
||||
androidConfig().setSdkManagerToolArgs(arguments);
|
||||
m_sdkManager->reloadPackages(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PackageFilterModel::PackageFilterModel(AndroidSdkModel *sdkModel) :
|
||||
QSortFilterProxyModel(sdkModel)
|
||||
{
|
||||
setSourceModel(sdkModel);
|
||||
}
|
||||
|
||||
void PackageFilterModel::setAcceptedPackageState(AndroidSdkPackage::PackageState state)
|
||||
{
|
||||
m_packageState = state;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void PackageFilterModel::setAcceptedSearchPackage(const QString &name)
|
||||
{
|
||||
m_searchText = name;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool PackageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
QModelIndex srcIndex = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
if (!srcIndex.isValid())
|
||||
return false;
|
||||
|
||||
auto packageState = [](const QModelIndex& i) {
|
||||
return (AndroidSdkPackage::PackageState)i.data(AndroidSdkModel::PackageStateRole).toInt();
|
||||
};
|
||||
|
||||
auto packageFound = [this](const QModelIndex& i) {
|
||||
return i.data(AndroidSdkModel::packageNameColumn).toString()
|
||||
.contains(m_searchText, Qt::CaseInsensitive);
|
||||
};
|
||||
|
||||
bool showTopLevel = false;
|
||||
if (!sourceParent.isValid()) {
|
||||
// Top Level items
|
||||
for (int row = 0; row < sourceModel()->rowCount(srcIndex); ++row) {
|
||||
QModelIndex childIndex = sourceModel()->index(row, 0, srcIndex);
|
||||
if ((m_packageState & packageState(childIndex) && packageFound(childIndex))) {
|
||||
showTopLevel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return showTopLevel || ((packageState(srcIndex) & m_packageState) && packageFound(srcIndex));
|
||||
}
|
||||
|
||||
OptionsDialog::OptionsDialog(AndroidSdkManager *sdkManager, const QStringList &args,
|
||||
QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
QTC_CHECK(sdkManager);
|
||||
resize(800, 480);
|
||||
setWindowTitle(Tr::tr("SDK Manager Arguments"));
|
||||
|
||||
m_argumentDetailsEdit = new QPlainTextEdit(this);
|
||||
m_argumentDetailsEdit->setReadOnly(true);
|
||||
|
||||
auto populateOptions = [this](const QString& options) {
|
||||
if (options.isEmpty()) {
|
||||
m_argumentDetailsEdit->setPlainText(Tr::tr("Cannot load available arguments for "
|
||||
"\"sdkmanager\" command."));
|
||||
} else {
|
||||
m_argumentDetailsEdit->setPlainText(options);
|
||||
}
|
||||
};
|
||||
m_optionsFuture = sdkManager->availableArguments();
|
||||
Utils::onResultReady(m_optionsFuture, this, populateOptions);
|
||||
|
||||
auto dialogButtons = new QDialogButtonBox(this);
|
||||
dialogButtons->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
|
||||
connect(dialogButtons, &QDialogButtonBox::accepted, this, &OptionsDialog::accept);
|
||||
connect(dialogButtons, &QDialogButtonBox::rejected, this, &OptionsDialog::reject);
|
||||
|
||||
m_argumentsEdit = new QLineEdit(this);
|
||||
m_argumentsEdit->setText(args.join(" "));
|
||||
|
||||
auto gridLayout = new QGridLayout(this);
|
||||
gridLayout->addWidget(new QLabel(Tr::tr("SDK manager arguments:"), this), 0, 0, 1, 1);
|
||||
gridLayout->addWidget(m_argumentsEdit, 0, 1, 1, 1);
|
||||
gridLayout->addWidget(new QLabel(Tr::tr("Available arguments:"), this), 1, 0, 1, 2);
|
||||
gridLayout->addWidget(m_argumentDetailsEdit, 2, 0, 1, 2);
|
||||
gridLayout->addWidget(dialogButtons, 3, 0, 1, 2);
|
||||
}
|
||||
|
||||
OptionsDialog::~OptionsDialog()
|
||||
{
|
||||
m_optionsFuture.cancel();
|
||||
m_optionsFuture.waitForFinished();
|
||||
}
|
||||
|
||||
QStringList OptionsDialog::sdkManagerArguments() const
|
||||
{
|
||||
QString userInput = m_argumentsEdit->text().simplified();
|
||||
return userInput.isEmpty() ? QStringList() : userInput.split(' ');
|
||||
}
|
||||
|
||||
} // Android::Internal
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright (C) 2017 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidsdkmanager.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLineEdit;
|
||||
class QPlainTextEdit;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QAbstractButton>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QCheckBox>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QDialogButtonBox>
|
||||
#include <QtWidgets/QFrame>
|
||||
#include <QtWidgets/QGroupBox>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QPlainTextEdit>
|
||||
#include <QtWidgets/QProgressBar>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QRadioButton>
|
||||
#include <QtWidgets/QSpacerItem>
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QTreeView>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <utils/fancylineedit.h>
|
||||
|
||||
namespace Utils { class OutputFormatter; }
|
||||
|
||||
namespace Android::Internal {
|
||||
|
||||
class AndroidSdkManager;
|
||||
class AndroidSdkModel;
|
||||
|
||||
class OptionsDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
OptionsDialog(AndroidSdkManager *sdkManager, const QStringList &args,
|
||||
QWidget *parent = nullptr);
|
||||
~OptionsDialog() override;
|
||||
|
||||
QStringList sdkManagerArguments() const;
|
||||
|
||||
private:
|
||||
QPlainTextEdit *m_argumentDetailsEdit;
|
||||
QLineEdit *m_argumentsEdit;
|
||||
QFuture<QString> m_optionsFuture;
|
||||
};
|
||||
|
||||
class AndroidSdkManagerWidget : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum View {
|
||||
PackageListing,
|
||||
Operations,
|
||||
LicenseWorkflow
|
||||
};
|
||||
|
||||
public:
|
||||
AndroidSdkManagerWidget(AndroidSdkManager *sdkManager, QWidget *parent = nullptr);
|
||||
~AndroidSdkManagerWidget() override;
|
||||
|
||||
void installEssentials();
|
||||
|
||||
signals:
|
||||
void updatingSdk();
|
||||
void updatingSdkFinished();
|
||||
void licenseWorkflowStarted();
|
||||
|
||||
private:
|
||||
void onApplyButton(const QString &extraMessage = {});
|
||||
void onUpdatePackages();
|
||||
void onCancel();
|
||||
void onOperationResult(int index);
|
||||
void onLicenseCheckResult(const AndroidSdkManager::OperationOutput &output);
|
||||
void onSdkManagerOptions();
|
||||
void addPackageFuture(const QFuture<AndroidSdkManager::OperationOutput> &future);
|
||||
void beginLicenseCheck();
|
||||
void beginExecution();
|
||||
void beginUpdate();
|
||||
void beginLicenseWorkflow();
|
||||
void notifyOperationFinished();
|
||||
void packageFutureFinished();
|
||||
void cancelPendingOperations();
|
||||
void switchView(View view);
|
||||
void runPendingCommand();
|
||||
|
||||
AndroidSdkManager::CommandType m_pendingCommand = AndroidSdkManager::None;
|
||||
View m_currentView = PackageListing;
|
||||
AndroidSdkManager *m_sdkManager = nullptr;
|
||||
AndroidSdkModel *m_sdkModel = nullptr;
|
||||
Utils::OutputFormatter *m_formatter = nullptr;
|
||||
QFutureWatcher<AndroidSdkManager::OperationOutput> *m_currentOperation = nullptr;
|
||||
|
||||
QStackedWidget *m_viewStack;
|
||||
QWidget *m_packagesStack;
|
||||
QWidget *m_outputStack;
|
||||
QProgressBar *m_operationProgress;
|
||||
QPlainTextEdit *m_outputEdit;
|
||||
QLabel *m_sdkLicenseLabel;
|
||||
QDialogButtonBox *m_sdkLicenseButtonBox;
|
||||
QDialogButtonBox *m_buttonBox;
|
||||
};
|
||||
|
||||
} // Android::Internal
|
||||
@@ -11,11 +11,6 @@
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QIcon>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace {
|
||||
static Q_LOGGING_CATEGORY(androidSdkModelLog, "qtc.android.sdkmodel", QtWarningMsg)
|
||||
}
|
||||
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
@@ -35,6 +30,9 @@ AndroidSdkModel::AndroidSdkModel(AndroidSdkManager *sdkManager, QObject *parent)
|
||||
refreshData();
|
||||
endResetModel();
|
||||
});
|
||||
beginResetModel();
|
||||
refreshData();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant AndroidSdkModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
@@ -258,51 +256,19 @@ bool AndroidSdkModel::setData(const QModelIndex &index, const QVariant &value, i
|
||||
return false;
|
||||
}
|
||||
|
||||
void AndroidSdkModel::selectMissingEssentials()
|
||||
InstallationChange AndroidSdkModel::installationChange() const
|
||||
{
|
||||
resetSelection();
|
||||
QStringList pendingPkgs(androidConfig().allEssentials());
|
||||
auto addTool = [this](QList<const AndroidSdkPackage *>::const_iterator itr) {
|
||||
if ((*itr)->installedLocation().isEmpty()) {
|
||||
m_changeState << *itr;
|
||||
auto i = index(std::distance(m_tools.cbegin(), itr), 0, index(0, 0));
|
||||
emit dataChanged(i, i, {Qt::CheckStateRole});
|
||||
}
|
||||
};
|
||||
for (auto tool = m_tools.cbegin(); tool != m_tools.cend(); ++tool) {
|
||||
if (!pendingPkgs.contains((*tool)->sdkStylePath()))
|
||||
continue;
|
||||
if (m_changeState.isEmpty())
|
||||
return {};
|
||||
|
||||
addTool(tool);
|
||||
pendingPkgs.removeOne((*tool)->sdkStylePath());
|
||||
|
||||
if (pendingPkgs.isEmpty())
|
||||
break;
|
||||
InstallationChange change;
|
||||
for (const AndroidSdkPackage *package : m_changeState) {
|
||||
if (package->state() == AndroidSdkPackage::Installed)
|
||||
change.toUninstall << package->sdkStylePath();
|
||||
else
|
||||
change.toInstall << package->sdkStylePath();
|
||||
}
|
||||
|
||||
// Select SDK platform
|
||||
for (const SdkPlatform *platform : std::as_const(m_sdkPlatforms)) {
|
||||
if (!platform->installedLocation().isEmpty()) {
|
||||
pendingPkgs.removeOne(platform->sdkStylePath());
|
||||
} else if (pendingPkgs.contains(platform->sdkStylePath()) &&
|
||||
platform->installedLocation().isEmpty()) {
|
||||
auto i = index(0, 0, index(1, 0));
|
||||
m_changeState << platform;
|
||||
emit dataChanged(i, i, {Qt::CheckStateRole});
|
||||
pendingPkgs.removeOne(platform->sdkStylePath());
|
||||
}
|
||||
if (pendingPkgs.isEmpty())
|
||||
break;
|
||||
}
|
||||
|
||||
m_missingEssentials = pendingPkgs;
|
||||
if (!m_missingEssentials.isEmpty())
|
||||
qCDebug(androidSdkModelLog) << "Couldn't find some essential packages:" << m_missingEssentials;
|
||||
}
|
||||
|
||||
QList<const AndroidSdkPackage *> AndroidSdkModel::userSelection() const
|
||||
{
|
||||
return Utils::toList(m_changeState);
|
||||
return change;
|
||||
}
|
||||
|
||||
void AndroidSdkModel::resetSelection()
|
||||
@@ -329,18 +295,14 @@ void AndroidSdkModel::refreshData()
|
||||
m_tools << p;
|
||||
}
|
||||
Utils::sort(m_sdkPlatforms, [](const SdkPlatform *p1, const SdkPlatform *p2) {
|
||||
return p1->apiLevel() > p2->apiLevel();
|
||||
return p1->apiLevel() > p2->apiLevel();
|
||||
});
|
||||
|
||||
Utils::sort(m_tools, [](const AndroidSdkPackage *p1, const AndroidSdkPackage *p2) {
|
||||
if (p1->state() == p2->state()) {
|
||||
if (p1->type() == p2->type())
|
||||
return p1->revision() > p2->revision();
|
||||
else
|
||||
return p1->type() > p2->type();
|
||||
} else {
|
||||
return p1->state() < p2->state();
|
||||
}
|
||||
if (p1->state() == p2->state())
|
||||
return p1->type() == p2->type() ? p1->revision() > p2->revision() : p1->type() > p2->type();
|
||||
else
|
||||
return p1->state() < p2->state();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,8 @@ public:
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
|
||||
void selectMissingEssentials();
|
||||
QList<const AndroidSdkPackage *> userSelection() const;
|
||||
void resetSelection();
|
||||
|
||||
QStringList missingEssentials() const { return m_missingEssentials; }
|
||||
InstallationChange installationChange() const;
|
||||
|
||||
private:
|
||||
void clearContainers();
|
||||
@@ -55,7 +52,6 @@ private:
|
||||
QList<const SdkPlatform *> m_sdkPlatforms;
|
||||
QList<const AndroidSdkPackage *> m_tools;
|
||||
QSet<const AndroidSdkPackage *> m_changeState;
|
||||
QStringList m_missingEssentials;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -5,15 +5,18 @@
|
||||
#include "androidconstants.h"
|
||||
#include "androidsdkdownloader.h"
|
||||
#include "androidsdkmanager.h"
|
||||
#include "androidsdkmanagerwidget.h"
|
||||
#include "androidsdkmanagerdialog.h"
|
||||
#include "androidsettingswidget.h"
|
||||
#include "androidtr.h"
|
||||
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
@@ -23,6 +26,7 @@
|
||||
#include <utils/progressindicator.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
@@ -30,6 +34,7 @@
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QGuiApplication>
|
||||
#include <QList>
|
||||
#include <QListWidget>
|
||||
#include <QLoggingCategory>
|
||||
@@ -157,7 +162,6 @@ class AndroidSettingsWidget final : public Core::IOptionsPageWidget
|
||||
public:
|
||||
// Todo: This would be so much simpler if it just used Utils::PathChooser!!!
|
||||
AndroidSettingsWidget();
|
||||
~AndroidSettingsWidget() final;
|
||||
|
||||
private:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
@@ -179,10 +183,8 @@ private:
|
||||
bool isDefaultNdkSelected() const;
|
||||
void validateOpenSsl();
|
||||
|
||||
AndroidSdkManagerWidget *m_sdkManagerWidget = nullptr;
|
||||
|
||||
AndroidSdkManager m_sdkManager;
|
||||
AndroidSdkDownloader m_sdkDownloader;
|
||||
Tasking::TaskTreeRunner m_sdkDownloader;
|
||||
bool m_isInitialReloadDone = false;
|
||||
|
||||
SummaryWidget *m_androidSummary = nullptr;
|
||||
@@ -312,7 +314,7 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
"and extracted to the selected path.\n"
|
||||
"After the SDK Tools are properly set up, you are prompted to install any essential\n"
|
||||
"packages required for Qt to build for Android.")
|
||||
.arg(androidConfig().sdkToolsUrl().toString()));
|
||||
.arg(AndroidConfig::sdkToolsUrl().toString()));
|
||||
|
||||
auto sdkManagerToolButton = new QPushButton(Tr::tr("SDK Manager"));
|
||||
|
||||
@@ -336,9 +338,6 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
"If the automatic download fails, Qt Creator proposes to open the download URL\n"
|
||||
"in the system's browser for manual download."));
|
||||
|
||||
|
||||
m_sdkManagerWidget = new AndroidSdkManagerWidget(&m_sdkManager, this);
|
||||
|
||||
const QMap<int, QString> androidValidationPoints = {
|
||||
{ JavaPathExistsAndWritableRow, Tr::tr("JDK path exists and is writable.") },
|
||||
{ SdkPathExistsAndWritableRow, Tr::tr("Android SDK path exists and is writable.") },
|
||||
@@ -381,22 +380,22 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
|
||||
connect(m_openJdkLocationPathChooser, &PathChooser::rawPathChanged,
|
||||
this, &AndroidSettingsWidget::validateJdk);
|
||||
if (androidConfig().openJDKLocation().isEmpty())
|
||||
androidConfig().setOpenJDKLocation(AndroidConfig::getJdkPath());
|
||||
m_openJdkLocationPathChooser->setFilePath(androidConfig().openJDKLocation());
|
||||
if (AndroidConfig::openJDKLocation().isEmpty())
|
||||
AndroidConfig::setOpenJDKLocation(AndroidConfig::getJdkPath());
|
||||
m_openJdkLocationPathChooser->setFilePath(AndroidConfig::openJDKLocation());
|
||||
m_openJdkLocationPathChooser->setPromptDialogTitle(Tr::tr("Select JDK Path"));
|
||||
|
||||
if (androidConfig().sdkLocation().isEmpty())
|
||||
androidConfig().setSdkLocation(AndroidConfig::defaultSdkPath());
|
||||
m_sdkLocationPathChooser->setFilePath(androidConfig().sdkLocation());
|
||||
if (AndroidConfig::sdkLocation().isEmpty())
|
||||
AndroidConfig::setSdkLocation(AndroidConfig::defaultSdkPath());
|
||||
m_sdkLocationPathChooser->setFilePath(AndroidConfig::sdkLocation());
|
||||
m_sdkLocationPathChooser->setPromptDialogTitle(Tr::tr("Select Android SDK Folder"));
|
||||
|
||||
m_openSslPathChooser->setPromptDialogTitle(Tr::tr("Select OpenSSL Include Project File"));
|
||||
if (androidConfig().openSslLocation().isEmpty())
|
||||
androidConfig().setOpenSslLocation(androidConfig().sdkLocation() / ("android_openssl"));
|
||||
m_openSslPathChooser->setFilePath(androidConfig().openSslLocation());
|
||||
if (AndroidConfig::openSslLocation().isEmpty())
|
||||
AndroidConfig::setOpenSslLocation(AndroidConfig::sdkLocation() / ("android_openssl"));
|
||||
m_openSslPathChooser->setFilePath(AndroidConfig::openSslLocation());
|
||||
|
||||
m_createKitCheckBox->setChecked(androidConfig().automaticKitCreation());
|
||||
m_createKitCheckBox->setChecked(AndroidConfig::automaticKitCreation());
|
||||
|
||||
downloadNdkToolButton->setIcon(downloadIcon);
|
||||
|
||||
@@ -436,7 +435,7 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
}
|
||||
},
|
||||
Group {
|
||||
title(Tr::tr("Android OpenSSL settings (Optional)")),
|
||||
title(Tr::tr("Android OpenSSL Settings (Optional)")),
|
||||
Grid {
|
||||
Tr::tr("OpenSSL binaries location:"),
|
||||
m_openSslPathChooser,
|
||||
@@ -455,21 +454,21 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
connect(m_ndkListWidget, &QListWidget::currentTextChanged,
|
||||
this, [this, removeCustomNdkButton](const QString &ndk) {
|
||||
updateUI();
|
||||
removeCustomNdkButton->setEnabled(androidConfig().getCustomNdkList().contains(ndk));
|
||||
removeCustomNdkButton->setEnabled(AndroidConfig::getCustomNdkList().contains(ndk));
|
||||
});
|
||||
connect(addCustomNdkButton, &QPushButton::clicked, this,
|
||||
&AndroidSettingsWidget::addCustomNdkItem);
|
||||
connect(removeCustomNdkButton, &QPushButton::clicked, this, [this] {
|
||||
if (isDefaultNdkSelected())
|
||||
androidConfig().setDefaultNdk({});
|
||||
androidConfig().removeCustomNdk(m_ndkListWidget->currentItem()->text());
|
||||
AndroidConfig::setDefaultNdk({});
|
||||
AndroidConfig::removeCustomNdk(m_ndkListWidget->currentItem()->text());
|
||||
m_ndkListWidget->takeItem(m_ndkListWidget->currentRow());
|
||||
});
|
||||
connect(m_makeDefaultNdkButton, &QPushButton::clicked, this, [this] {
|
||||
const FilePath defaultNdk = isDefaultNdkSelected()
|
||||
? FilePath()
|
||||
: FilePath::fromUserInput(m_ndkListWidget->currentItem()->text());
|
||||
androidConfig().setDefaultNdk(defaultNdk);
|
||||
AndroidConfig::setDefaultNdk(defaultNdk);
|
||||
updateUI();
|
||||
});
|
||||
|
||||
@@ -495,41 +494,31 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
m_androidSummary->setInProgressText("Retrieving packages information");
|
||||
m_androidProgress->show();
|
||||
});
|
||||
connect(sdkManagerToolButton, &QAbstractButton::clicked,
|
||||
this, [this] { m_sdkManagerWidget->exec(); });
|
||||
connect(sdkManagerToolButton, &QAbstractButton::clicked, this, [this] {
|
||||
executeAndroidSdkManagerDialog(&m_sdkManager, this);
|
||||
});
|
||||
connect(sdkToolsAutoDownloadButton, &QAbstractButton::clicked,
|
||||
this, &AndroidSettingsWidget::downloadSdk);
|
||||
connect(&m_sdkDownloader, &AndroidSdkDownloader::sdkDownloaderError, this, [this](const QString &error) {
|
||||
QMessageBox::warning(this, AndroidSdkDownloader::dialogTitle(), error);
|
||||
});
|
||||
connect(&m_sdkDownloader, &AndroidSdkDownloader::sdkExtracted, this, [this] {
|
||||
connect(&m_sdkDownloader, &Tasking::TaskTreeRunner::done, this, [this](Tasking::DoneWith result) {
|
||||
if (result != Tasking::DoneWith::Success)
|
||||
return;
|
||||
// Make sure the sdk path is created before installing packages
|
||||
const FilePath sdkPath = androidConfig().sdkLocation();
|
||||
const FilePath sdkPath = AndroidConfig::sdkLocation();
|
||||
if (!sdkPath.createDir()) {
|
||||
QMessageBox::warning(this, AndroidSdkDownloader::dialogTitle(),
|
||||
QMessageBox::warning(this, Android::Internal::dialogTitle(),
|
||||
Tr::tr("Failed to create the SDK Tools path %1.")
|
||||
.arg("\n\"" + sdkPath.toUserOutput() + "\""));
|
||||
}
|
||||
m_sdkManager.reloadPackages(true);
|
||||
m_sdkManager.reloadPackages();
|
||||
updateUI();
|
||||
apply();
|
||||
|
||||
QMetaObject::Connection *const openSslOneShot = new QMetaObject::Connection;
|
||||
*openSslOneShot = connect(&m_sdkManager, &AndroidSdkManager::packageReloadFinished,
|
||||
this, [this, openSslOneShot] {
|
||||
QObject::disconnect(*openSslOneShot);
|
||||
downloadOpenSslRepo(true);
|
||||
delete openSslOneShot;
|
||||
});
|
||||
connect(&m_sdkManager, &AndroidSdkManager::packageReloadFinished, this, [this] {
|
||||
downloadOpenSslRepo(true);
|
||||
}, Qt::SingleShotConnection);
|
||||
});
|
||||
|
||||
setOnApply([] { AndroidConfigurations::setConfig(androidConfig()); });
|
||||
}
|
||||
|
||||
AndroidSettingsWidget::~AndroidSettingsWidget()
|
||||
{
|
||||
// Deleting m_sdkManagerWidget will cancel all ongoing and pending sdkmanager operations.
|
||||
delete m_sdkManagerWidget;
|
||||
setOnApply([] { AndroidConfigurations::applyConfig(); });
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::showEvent(QShowEvent *event)
|
||||
@@ -539,8 +528,7 @@ void AndroidSettingsWidget::showEvent(QShowEvent *event)
|
||||
validateJdk();
|
||||
// Reloading SDK packages (force) is still synchronous. Use zero timer
|
||||
// to let settings dialog open first.
|
||||
QTimer::singleShot(0, &m_sdkManager, std::bind(&AndroidSdkManager::reloadPackages,
|
||||
&m_sdkManager, false));
|
||||
QTimer::singleShot(0, &m_sdkManager, &AndroidSdkManager::refreshPackages);
|
||||
validateOpenSsl();
|
||||
m_isInitialReloadDone = true;
|
||||
}
|
||||
@@ -555,12 +543,12 @@ void AndroidSettingsWidget::updateNdkList()
|
||||
ndk->installedLocation().toUserOutput()));
|
||||
}
|
||||
|
||||
const auto customNdks = androidConfig().getCustomNdkList();
|
||||
const auto customNdks = AndroidConfig::getCustomNdkList();
|
||||
for (const QString &ndk : customNdks) {
|
||||
if (androidConfig().isValidNdk(ndk)) {
|
||||
if (AndroidConfig::isValidNdk(ndk)) {
|
||||
m_ndkListWidget->addItem(new QListWidgetItem(Icons::UNLOCKED.icon(), ndk));
|
||||
} else {
|
||||
androidConfig().removeCustomNdk(ndk);
|
||||
AndroidConfig::removeCustomNdk(ndk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,8 +563,8 @@ void AndroidSettingsWidget::addCustomNdkItem()
|
||||
.constFirst();
|
||||
const QString ndkPath = QFileDialog::getExistingDirectory(this, Tr::tr("Select an NDK"), homePath);
|
||||
|
||||
if (androidConfig().isValidNdk(ndkPath)) {
|
||||
androidConfig().addCustomNdk(ndkPath);
|
||||
if (AndroidConfig::isValidNdk(ndkPath)) {
|
||||
AndroidConfig::addCustomNdk(ndkPath);
|
||||
if (m_ndkListWidget->findItems(ndkPath, Qt::MatchExactly).size() == 0) {
|
||||
m_ndkListWidget->addItem(new QListWidgetItem(Icons::UNLOCKED.icon(), ndkPath));
|
||||
}
|
||||
@@ -594,9 +582,9 @@ void AndroidSettingsWidget::addCustomNdkItem()
|
||||
|
||||
bool AndroidSettingsWidget::isDefaultNdkSelected() const
|
||||
{
|
||||
if (!androidConfig().defaultNdk().isEmpty()) {
|
||||
if (!AndroidConfig::defaultNdk().isEmpty()) {
|
||||
if (const QListWidgetItem *item = m_ndkListWidget->currentItem()) {
|
||||
return FilePath::fromUserInput(item->text()) == androidConfig().defaultNdk();
|
||||
return FilePath::fromUserInput(item->text()) == AndroidConfig::defaultNdk();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -604,27 +592,27 @@ bool AndroidSettingsWidget::isDefaultNdkSelected() const
|
||||
|
||||
void AndroidSettingsWidget::validateJdk()
|
||||
{
|
||||
androidConfig().setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
|
||||
expected_str<void> test = testJavaC(androidConfig().openJDKLocation());
|
||||
AndroidConfig::setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
|
||||
expected_str<void> test = testJavaC(AndroidConfig::openJDKLocation());
|
||||
|
||||
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, test);
|
||||
|
||||
updateUI();
|
||||
|
||||
if (m_isInitialReloadDone)
|
||||
m_sdkManager.reloadPackages(true);
|
||||
m_sdkManager.reloadPackages();
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::validateOpenSsl()
|
||||
{
|
||||
androidConfig().setOpenSslLocation(m_openSslPathChooser->filePath());
|
||||
AndroidConfig::setOpenSslLocation(m_openSslPathChooser->filePath());
|
||||
|
||||
m_openSslSummary->setPointValid(OpenSslPathExistsRow, androidConfig().openSslLocation().exists());
|
||||
m_openSslSummary->setPointValid(OpenSslPathExistsRow, AndroidConfig::openSslLocation().exists());
|
||||
|
||||
const bool priFileExists = androidConfig().openSslLocation().pathAppended("openssl.pri").exists();
|
||||
const bool priFileExists = AndroidConfig::openSslLocation().pathAppended("openssl.pri").exists();
|
||||
m_openSslSummary->setPointValid(OpenSslPriPathExists, priFileExists);
|
||||
const bool cmakeListsExists
|
||||
= androidConfig().openSslLocation().pathAppended("CMakeLists.txt").exists();
|
||||
= AndroidConfig::openSslLocation().pathAppended("CMakeLists.txt").exists();
|
||||
m_openSslSummary->setPointValid(OpenSslCmakeListsPathExists, cmakeListsExists);
|
||||
|
||||
updateUI();
|
||||
@@ -633,36 +621,36 @@ void AndroidSettingsWidget::validateOpenSsl()
|
||||
void AndroidSettingsWidget::onSdkPathChanged()
|
||||
{
|
||||
const FilePath sdkPath = m_sdkLocationPathChooser->filePath().cleanPath();
|
||||
androidConfig().setSdkLocation(sdkPath);
|
||||
FilePath currentOpenSslPath = androidConfig().openSslLocation();
|
||||
AndroidConfig::setSdkLocation(sdkPath);
|
||||
FilePath currentOpenSslPath = AndroidConfig::openSslLocation();
|
||||
if (currentOpenSslPath.isEmpty() || !currentOpenSslPath.exists())
|
||||
currentOpenSslPath = sdkPath.pathAppended("android_openssl");
|
||||
m_openSslPathChooser->setFilePath(currentOpenSslPath);
|
||||
// Package reload will trigger validateSdk.
|
||||
m_sdkManager.reloadPackages();
|
||||
m_sdkManager.refreshPackages();
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::validateSdk()
|
||||
{
|
||||
const FilePath sdkPath = m_sdkLocationPathChooser->filePath().cleanPath();
|
||||
androidConfig().setSdkLocation(sdkPath);
|
||||
AndroidConfig::setSdkLocation(sdkPath);
|
||||
|
||||
const FilePath path = androidConfig().sdkLocation();
|
||||
const FilePath path = AndroidConfig::sdkLocation();
|
||||
m_androidSummary->setPointValid(SdkPathExistsAndWritableRow,
|
||||
path.exists() && path.isWritableDir());
|
||||
m_androidSummary->setPointValid(SdkToolsInstalledRow,
|
||||
!androidConfig().sdkToolsVersion().isNull());
|
||||
!AndroidConfig::sdkToolsVersion().isNull());
|
||||
m_androidSummary->setPointValid(PlatformToolsInstalledRow,
|
||||
androidConfig().adbToolPath().exists());
|
||||
AndroidConfig::adbToolPath().exists());
|
||||
m_androidSummary->setPointValid(BuildToolsInstalledRow,
|
||||
!androidConfig().buildToolsVersion().isNull());
|
||||
!AndroidConfig::buildToolsVersion().isNull());
|
||||
m_androidSummary->setPointValid(SdkManagerSuccessfulRow, m_sdkManager.packageListingSuccessful());
|
||||
// installedSdkPlatforms should not trigger a package reload as validate SDK is only called
|
||||
// after AndroidSdkManager::packageReloadFinished.
|
||||
m_androidSummary->setPointValid(PlatformSdkInstalledRow,
|
||||
!m_sdkManager.installedSdkPlatforms().isEmpty());
|
||||
m_androidSummary->setPointValid(AllEssentialsInstalledRow,
|
||||
androidConfig().allEssentialsInstalled(&m_sdkManager));
|
||||
AndroidConfig::allEssentialsInstalled(&m_sdkManager));
|
||||
|
||||
const bool sdkToolsOk = m_androidSummary->rowsOk({SdkPathExistsAndWritableRow,
|
||||
SdkToolsInstalledRow,
|
||||
@@ -671,9 +659,21 @@ void AndroidSettingsWidget::validateSdk()
|
||||
BuildToolsInstalledRow,
|
||||
PlatformSdkInstalledRow,
|
||||
AllEssentialsInstalledRow});
|
||||
androidConfig().setSdkFullyConfigured(sdkToolsOk && componentsOk);
|
||||
if (sdkToolsOk && !componentsOk)
|
||||
m_sdkManagerWidget->installEssentials();
|
||||
AndroidConfig::setSdkFullyConfigured(sdkToolsOk && componentsOk);
|
||||
if (sdkToolsOk && !componentsOk) {
|
||||
const QStringList notFoundEssentials = m_sdkManager.notFoundEssentialSdkPackages();
|
||||
if (!notFoundEssentials.isEmpty()) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Android SDK Changes"),
|
||||
Tr::tr("%1 cannot find the following essential packages: \"%2\".\n"
|
||||
"Install them manually after the current operation is done.\n")
|
||||
.arg(QGuiApplication::applicationDisplayName(),
|
||||
notFoundEssentials.join("\", \"")));
|
||||
}
|
||||
m_sdkManager.runInstallationChange({m_sdkManager.missingEssentialSdkPackages()},
|
||||
Tr::tr("Android SDK installation is missing necessary packages. "
|
||||
"Do you want to install the missing packages?"));
|
||||
}
|
||||
|
||||
updateNdkList();
|
||||
updateUI();
|
||||
@@ -733,7 +733,8 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent)
|
||||
|
||||
const QString openSslRepo("https://github.com/KDAB/android_openssl.git");
|
||||
Process *gitCloner = new Process(this);
|
||||
CommandLine gitCloneCommand("git", {"clone", "--depth=1", openSslRepo, openSslPath.toString()});
|
||||
const CommandLine gitCloneCommand("git", {"clone", "--depth=1", openSslRepo,
|
||||
openSslPath.toString()});
|
||||
gitCloner->setCommand(gitCloneCommand);
|
||||
|
||||
qCDebug(androidsettingswidget) << "Cloning OpenSSL repo: " << gitCloneCommand.toUserOutput();
|
||||
@@ -782,7 +783,7 @@ void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent)
|
||||
|
||||
void AndroidSettingsWidget::createKitToggled()
|
||||
{
|
||||
androidConfig().setAutomaticKitCreation(m_createKitCheckBox->isChecked());
|
||||
AndroidConfig::setAutomaticKitCreation(m_createKitCheckBox->isChecked());
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::updateUI()
|
||||
@@ -793,8 +794,8 @@ void AndroidSettingsWidget::updateUI()
|
||||
const QListWidgetItem *currentItem = m_ndkListWidget->currentItem();
|
||||
const FilePath currentNdk = FilePath::fromUserInput(currentItem ? currentItem->text() : "");
|
||||
const QString infoText = Tr::tr("(SDK Version: %1, NDK Version: %2)")
|
||||
.arg(androidConfig().sdkToolsVersion().toString())
|
||||
.arg(currentNdk.isEmpty() ? "" : androidConfig().ndkVersion(currentNdk).toString());
|
||||
.arg(AndroidConfig::sdkToolsVersion().toString())
|
||||
.arg(currentNdk.isEmpty() ? "" : AndroidConfig::ndkVersion(currentNdk).toString());
|
||||
m_androidSummary->setInfoText(androidSetupOk ? infoText : "");
|
||||
|
||||
m_androidSummary->setSetupOk(androidSetupOk);
|
||||
@@ -808,7 +809,7 @@ void AndroidSettingsWidget::updateUI()
|
||||
for (int row = 0; row < m_ndkListWidget->count(); ++row) {
|
||||
QListWidgetItem *item = m_ndkListWidget->item(row);
|
||||
const bool isDefaultNdk =
|
||||
FilePath::fromUserInput(item->text()) == androidConfig().defaultNdk();
|
||||
FilePath::fromUserInput(item->text()) == AndroidConfig::defaultNdk();
|
||||
item->setFont(isDefaultNdk ? markedFont : font);
|
||||
}
|
||||
}
|
||||
@@ -820,8 +821,8 @@ void AndroidSettingsWidget::updateUI()
|
||||
|
||||
void AndroidSettingsWidget::downloadSdk()
|
||||
{
|
||||
if (androidConfig().sdkToolsOk()) {
|
||||
QMessageBox::warning(this, AndroidSdkDownloader::dialogTitle(),
|
||||
if (AndroidConfig::sdkToolsOk()) {
|
||||
QMessageBox::warning(this, Android::Internal::dialogTitle(),
|
||||
Tr::tr("The selected path already has a valid SDK Tools package."));
|
||||
validateSdk();
|
||||
return;
|
||||
@@ -830,10 +831,10 @@ void AndroidSettingsWidget::downloadSdk()
|
||||
const QString message = Tr::tr("Download and install Android SDK Tools to %1?")
|
||||
.arg("\n\"" + m_sdkLocationPathChooser->filePath().cleanPath().toUserOutput()
|
||||
+ "\"");
|
||||
auto userInput = QMessageBox::information(this, AndroidSdkDownloader::dialogTitle(),
|
||||
auto userInput = QMessageBox::information(this, Android::Internal::dialogTitle(),
|
||||
message, QMessageBox::Yes | QMessageBox::No);
|
||||
if (userInput == QMessageBox::Yes)
|
||||
m_sdkDownloader.downloadAndExtractSdk();
|
||||
m_sdkDownloader.start({Android::Internal::downloadSdkRecipe()});
|
||||
}
|
||||
|
||||
// AndroidSettingsPage
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
AndroidSignalOperation::AndroidSignalOperation()
|
||||
: m_adbPath(androidConfig().adbToolPath())
|
||||
: m_adbPath(AndroidConfig::adbToolPath())
|
||||
, m_timeout(new QTimer(this))
|
||||
{
|
||||
m_timeout->setInterval(5000);
|
||||
|
||||
@@ -77,7 +77,7 @@ bool AndroidToolchain::isValid() const
|
||||
}
|
||||
|
||||
const bool isChildofNdk = compilerCommand().isChildOf(m_ndkLocation);
|
||||
const bool isChildofSdk = compilerCommand().isChildOf(androidConfig().sdkLocation());
|
||||
const bool isChildofSdk = compilerCommand().isChildOf(AndroidConfig::sdkLocation());
|
||||
|
||||
return GccToolchain::isValid() && typeId() == Constants::ANDROID_TOOLCHAIN_TYPEID
|
||||
&& targetAbi().isValid() && (isChildofNdk || isChildofSdk)
|
||||
@@ -86,9 +86,8 @@ bool AndroidToolchain::isValid() const
|
||||
|
||||
void AndroidToolchain::addToEnvironment(Environment &env) const
|
||||
{
|
||||
const AndroidConfig &config = androidConfig();
|
||||
env.set(QLatin1String("ANDROID_NDK_HOST"), config.toolchainHostFromNdk(m_ndkLocation));
|
||||
const FilePath javaHome = config.openJDKLocation();
|
||||
env.set(QLatin1String("ANDROID_NDK_HOST"), AndroidConfig::toolchainHostFromNdk(m_ndkLocation));
|
||||
const FilePath javaHome = AndroidConfig::openJDKLocation();
|
||||
if (javaHome.exists()) {
|
||||
env.set(Constants::JAVA_HOME_ENV_VAR, javaHome.toUserOutput());
|
||||
const FilePath javaBin = javaHome.pathAppended("bin");
|
||||
@@ -97,8 +96,8 @@ void AndroidToolchain::addToEnvironment(Environment &env) const
|
||||
if (!currentJavaFilePath.isChildOf(javaBin))
|
||||
env.prependOrSetPath(javaBin);
|
||||
}
|
||||
env.set(QLatin1String("ANDROID_HOME"), config.sdkLocation().toUserOutput());
|
||||
env.set(QLatin1String("ANDROID_SDK_ROOT"), config.sdkLocation().toUserOutput());
|
||||
env.set(QLatin1String("ANDROID_HOME"), AndroidConfig::sdkLocation().toUserOutput());
|
||||
env.set(QLatin1String("ANDROID_SDK_ROOT"), AndroidConfig::sdkLocation().toUserOutput());
|
||||
}
|
||||
|
||||
void AndroidToolchain::fromMap(const Store &data)
|
||||
@@ -118,7 +117,7 @@ QStringList AndroidToolchain::suggestedMkspecList() const
|
||||
FilePath AndroidToolchain::makeCommand(const Environment &env) const
|
||||
{
|
||||
Q_UNUSED(env)
|
||||
FilePath makePath = androidConfig().makePathFromNdk(m_ndkLocation);
|
||||
FilePath makePath = AndroidConfig::makePathFromNdk(m_ndkLocation);
|
||||
return makePath.exists() ? makePath : FilePath("make");
|
||||
}
|
||||
|
||||
@@ -147,7 +146,7 @@ static FilePaths uniqueNdksForCurrentQtVersions()
|
||||
|
||||
FilePaths uniqueNdks;
|
||||
for (const QtSupport::QtVersion *version : androidQtVersions) {
|
||||
FilePath ndk = androidConfig().ndkLocation(version);
|
||||
FilePath ndk = AndroidConfig::ndkLocation(version);
|
||||
if (!uniqueNdks.contains(ndk))
|
||||
uniqueNdks.append(ndk);
|
||||
}
|
||||
@@ -161,15 +160,13 @@ ToolchainList autodetectToolchainsFromNdks(
|
||||
const bool isCustom)
|
||||
{
|
||||
QList<Toolchain *> result;
|
||||
const AndroidConfig config = androidConfig();
|
||||
|
||||
const Id LanguageIds[] {
|
||||
ProjectExplorer::Constants::CXX_LANGUAGE_ID,
|
||||
ProjectExplorer::Constants::C_LANGUAGE_ID
|
||||
};
|
||||
|
||||
for (const FilePath &ndkLocation : ndkLocations) {
|
||||
FilePath clangPath = config.clangPathFromNdk(ndkLocation);
|
||||
const FilePath clangPath = AndroidConfig::clangPathFromNdk(ndkLocation);
|
||||
if (!clangPath.exists()) {
|
||||
qCDebug(androidTCLog) << "Clang toolchains detection fails. Can not find Clang"
|
||||
<< clangPath;
|
||||
@@ -193,11 +190,11 @@ ToolchainList autodetectToolchainsFromNdks(
|
||||
const QString target = targetItr.key();
|
||||
Toolchain *tc = findToolchain(compilerCommand, lang, target, alreadyKnown);
|
||||
|
||||
QLatin1String customStr = isCustom ? QLatin1String("Custom ") : QLatin1String();
|
||||
const QString customStr = isCustom ? "Custom " : QString();
|
||||
const QString displayName(customStr + QString("Android Clang (%1, %2, NDK %3)")
|
||||
.arg(ToolchainManager::displayNameOfLanguageId(lang),
|
||||
AndroidConfig::displayName(abi),
|
||||
config.ndkVersion(ndkLocation).toString()));
|
||||
.arg(ToolchainManager::displayNameOfLanguageId(lang),
|
||||
AndroidConfig::displayName(abi),
|
||||
AndroidConfig::ndkVersion(ndkLocation).toString()));
|
||||
if (tc) {
|
||||
// make sure to update the toolchain with current name format
|
||||
if (tc->displayName() != displayName)
|
||||
|
||||
@@ -3,31 +3,34 @@
|
||||
|
||||
#include "avddialog.h"
|
||||
#include "androidtr.h"
|
||||
#include "androidavdmanager.h"
|
||||
#include "androidconstants.h"
|
||||
#include "androidconfigurations.h"
|
||||
#include "androiddevice.h"
|
||||
#include "androidsdkmanager.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/infolabel.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/tooltip/tooltip.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFutureWatcher>
|
||||
#include <QKeyEvent>
|
||||
#include <QLineEdit>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QToolTip>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
namespace Android::Internal {
|
||||
@@ -35,8 +38,8 @@ namespace Android::Internal {
|
||||
static Q_LOGGING_CATEGORY(avdDialogLog, "qtc.android.avdDialog", QtWarningMsg)
|
||||
|
||||
AvdDialog::AvdDialog(QWidget *parent)
|
||||
: QDialog(parent),
|
||||
m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*"))
|
||||
: QDialog(parent)
|
||||
, m_allowedNameChars(QLatin1String("[a-z|A-Z|0-9|._-]*"))
|
||||
{
|
||||
resize(800, 0);
|
||||
setWindowTitle(Tr::tr("Create new AVD"));
|
||||
@@ -106,6 +109,7 @@ AvdDialog::AvdDialog(QWidget *parent)
|
||||
m_deviceTypeToStringMap.insert(AvdDialog::Automotive, "Automotive");
|
||||
m_deviceTypeToStringMap.insert(AvdDialog::TV, "TV");
|
||||
m_deviceTypeToStringMap.insert(AvdDialog::Wear, "Wear");
|
||||
m_deviceTypeToStringMap.insert(AvdDialog::Desktop, "Desktop");
|
||||
|
||||
parseDeviceDefinitionsList();
|
||||
for (const QString &type : m_deviceTypeToStringMap)
|
||||
@@ -118,32 +122,25 @@ int AvdDialog::exec()
|
||||
{
|
||||
const int execResult = QDialog::exec();
|
||||
if (execResult == QDialog::Accepted) {
|
||||
CreateAvdInfo result;
|
||||
result.systemImage = systemImage();
|
||||
result.name = name();
|
||||
result.abi = abi();
|
||||
result.deviceDefinition = deviceDefinition();
|
||||
result.sdcardSize = sdcardSize();
|
||||
result.overwrite = m_overwriteCheckBox->isChecked();
|
||||
|
||||
const AndroidAvdManager avdManager;
|
||||
QFutureWatcher<CreateAvdInfo> createAvdFutureWatcher;
|
||||
|
||||
QEventLoop loop;
|
||||
QObject::connect(&createAvdFutureWatcher, &QFutureWatcher<CreateAvdInfo>::finished,
|
||||
&loop, &QEventLoop::quit);
|
||||
QObject::connect(&createAvdFutureWatcher, &QFutureWatcher<CreateAvdInfo>::canceled,
|
||||
&loop, &QEventLoop::quit);
|
||||
createAvdFutureWatcher.setFuture(avdManager.createAvd(result));
|
||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
|
||||
const QFuture<CreateAvdInfo> future = createAvdFutureWatcher.future();
|
||||
if (future.isResultReadyAt(0)) {
|
||||
m_createdAvdInfo = future.result();
|
||||
AndroidDeviceManager::instance()->updateAvdsList();
|
||||
const SystemImage *si = systemImage();
|
||||
if (!si || !si->isValid() || name().isEmpty()) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(),
|
||||
Tr::tr("Create new AVD"), Tr::tr("Cannot create AVD. Invalid input."));
|
||||
return QDialog::Rejected;
|
||||
}
|
||||
}
|
||||
|
||||
const CreateAvdInfo avdInfo{si->sdkStylePath(), si->apiLevel(), name(), abi(),
|
||||
deviceDefinition(), sdcardSize()};
|
||||
const auto result = AndroidDeviceManager::createAvd(avdInfo,
|
||||
m_overwriteCheckBox->isChecked());
|
||||
if (!result) {
|
||||
QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Create new AVD"),
|
||||
result.error());
|
||||
return QDialog::Rejected;
|
||||
}
|
||||
m_createdAvdInfo = avdInfo;
|
||||
AndroidDeviceManager::updateAvdList();
|
||||
}
|
||||
return execResult;
|
||||
}
|
||||
|
||||
@@ -152,23 +149,9 @@ bool AvdDialog::isValid() const
|
||||
return !name().isEmpty() && systemImage() && systemImage()->isValid() && !abi().isEmpty();
|
||||
}
|
||||
|
||||
ProjectExplorer::IDevice::Ptr AvdDialog::device() const
|
||||
CreateAvdInfo AvdDialog::avdInfo() const
|
||||
{
|
||||
if (!m_createdAvdInfo.systemImage) {
|
||||
qCWarning(avdDialogLog) << "System image of the created AVD is nullptr";
|
||||
return IDevice::Ptr();
|
||||
}
|
||||
AndroidDevice *dev = new AndroidDevice();
|
||||
const Utils::Id deviceId = AndroidDevice::idFromAvdInfo(m_createdAvdInfo);
|
||||
using namespace ProjectExplorer;
|
||||
dev->setupId(IDevice::AutoDetected, deviceId);
|
||||
dev->setMachineType(IDevice::Emulator);
|
||||
dev->settings()->displayName.setValue(m_createdAvdInfo.name);
|
||||
dev->setDeviceState(IDevice::DeviceConnected);
|
||||
dev->setExtraData(Constants::AndroidAvdName, m_createdAvdInfo.name);
|
||||
dev->setExtraData(Constants::AndroidCpuAbi, {m_createdAvdInfo.abi});
|
||||
dev->setExtraData(Constants::AndroidSdk, m_createdAvdInfo.systemImage->apiLevel());
|
||||
return IDevice::Ptr(dev);
|
||||
return m_createdAvdInfo;
|
||||
}
|
||||
|
||||
AvdDialog::DeviceType AvdDialog::tagToDeviceType(const QString &type_tag)
|
||||
@@ -179,20 +162,60 @@ AvdDialog::DeviceType AvdDialog::tagToDeviceType(const QString &type_tag)
|
||||
return AvdDialog::TV;
|
||||
else if (type_tag.contains("android-automotive"))
|
||||
return AvdDialog::Automotive;
|
||||
else
|
||||
return AvdDialog::PhoneOrTablet;
|
||||
else if (type_tag.contains("android-desktop"))
|
||||
return AvdDialog::Desktop;
|
||||
return AvdDialog::PhoneOrTablet;
|
||||
}
|
||||
|
||||
static bool avdManagerCommand(const QStringList &args, QString *output)
|
||||
{
|
||||
CommandLine cmd(AndroidConfig::avdManagerToolPath(), args);
|
||||
Process proc;
|
||||
proc.setEnvironment(AndroidConfig::toolsEnvironment());
|
||||
qCDebug(avdDialogLog).noquote() << "Running AVD Manager command:" << cmd.toUserOutput();
|
||||
proc.setCommand(cmd);
|
||||
proc.runBlocking();
|
||||
if (proc.result() == ProcessResult::FinishedWithSuccess) {
|
||||
if (output)
|
||||
*output = proc.allOutput();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AvdDialog::parseDeviceDefinitionsList()
|
||||
{
|
||||
QString output;
|
||||
|
||||
if (!AndroidAvdManager::avdManagerCommand({"list", "device"}, &output)) {
|
||||
if (!avdManagerCommand({"list", "device"}, &output)) {
|
||||
qCDebug(avdDialogLog) << "Avd list command failed" << output
|
||||
<< androidConfig().sdkToolsVersion();
|
||||
<< AndroidConfig::sdkToolsVersion();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Example output:
|
||||
Available devices definitions:
|
||||
id: 0 or "automotive_1024p_landscape"
|
||||
Name: Automotive (1024p landscape)
|
||||
OEM : Google
|
||||
Tag : android-automotive-playstore
|
||||
---------
|
||||
id: 1 or "automotive_1080p_landscape"
|
||||
Name: Automotive (1080p landscape)
|
||||
OEM : Google
|
||||
Tag : android-automotive
|
||||
---------
|
||||
id: 2 or "Galaxy Nexus"
|
||||
Name: Galaxy Nexus
|
||||
OEM : Google
|
||||
---------
|
||||
id: 3 or "desktop_large"
|
||||
Name: Large Desktop
|
||||
OEM : Google
|
||||
Tag : android-desktop
|
||||
...
|
||||
*/
|
||||
|
||||
QStringList avdDeviceInfo;
|
||||
|
||||
const auto lines = output.split('\n');
|
||||
|
||||
@@ -20,7 +20,6 @@ QT_END_NAMESPACE
|
||||
namespace Utils { class InfoLabel; }
|
||||
|
||||
namespace Android {
|
||||
class AndroidConfig;
|
||||
class SdkPlatform;
|
||||
|
||||
namespace Internal {
|
||||
@@ -33,9 +32,9 @@ public:
|
||||
explicit AvdDialog(QWidget *parent = nullptr);
|
||||
int exec() override;
|
||||
|
||||
enum DeviceType { Phone, Tablet, Automotive, TV, Wear, PhoneOrTablet };
|
||||
enum DeviceType { Phone, Tablet, Automotive, TV, Wear, Desktop, PhoneOrTablet };
|
||||
|
||||
ProjectExplorer::IDevice::Ptr device() const;
|
||||
CreateAvdInfo avdInfo() const;
|
||||
|
||||
const SystemImage *systemImage() const;
|
||||
QString name() const;
|
||||
|
||||
@@ -86,10 +86,10 @@ static std::optional<AndroidDeviceInfo> parseAvd(const QStringList &deviceInfo)
|
||||
return {};
|
||||
}
|
||||
|
||||
AndroidDeviceInfoList parseAvdList(const QString &output, Utils::FilePaths *avdErrorPaths)
|
||||
ParsedAvdList parseAvdList(const QString &output)
|
||||
{
|
||||
QTC_CHECK(avdErrorPaths);
|
||||
AndroidDeviceInfoList avdList;
|
||||
Utils::FilePaths errorPaths;
|
||||
QStringList avdInfo;
|
||||
using ErrorPath = Utils::FilePath;
|
||||
using AvdResult = std::variant<std::monostate, AndroidDeviceInfo, ErrorPath>;
|
||||
@@ -120,14 +120,14 @@ AndroidDeviceInfoList parseAvdList(const QString &output, Utils::FilePaths *avdE
|
||||
if (auto info = std::get_if<AndroidDeviceInfo>(&result))
|
||||
avdList << *info;
|
||||
else if (auto errorPath = std::get_if<ErrorPath>(&result))
|
||||
*avdErrorPaths << *errorPath;
|
||||
errorPaths << *errorPath;
|
||||
avdInfo.clear();
|
||||
} else {
|
||||
avdInfo << line;
|
||||
}
|
||||
}
|
||||
|
||||
return Utils::sorted(std::move(avdList));
|
||||
return {Utils::sorted(std::move(avdList)), errorPaths};
|
||||
}
|
||||
|
||||
int platformNameToApiLevel(const QString &platformName)
|
||||
@@ -156,8 +156,10 @@ int platformNameToApiLevel(const QString &platformName)
|
||||
|
||||
QString convertNameToExtension(const QString &name)
|
||||
{
|
||||
if (name.endsWith("ext4"))
|
||||
return " Extension 4";
|
||||
static const QRegularExpression rexEx(R"(-ext(\d+)$)");
|
||||
const QRegularExpressionMatch match = rexEx.match(name);
|
||||
if (match.hasMatch())
|
||||
return " Extension " + match.captured(1);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@ namespace Internal {
|
||||
|
||||
const char avdManufacturerError[] = "no longer exists as a device";
|
||||
|
||||
AndroidDeviceInfoList parseAvdList(const QString &output, Utils::FilePaths *avdErrorPaths);
|
||||
struct ParsedAvdList
|
||||
{
|
||||
AndroidDeviceInfoList avdList;
|
||||
Utils::FilePaths errorPaths;
|
||||
};
|
||||
|
||||
ParsedAvdList parseAvdList(const QString &output);
|
||||
int platformNameToApiLevel(const QString &platformName);
|
||||
QString convertNameToExtension(const QString &name);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
|
||||
namespace Android {
|
||||
@@ -17,12 +17,12 @@ public:
|
||||
CertificatesModel(const QString &rowCertificates, QObject *parent);
|
||||
|
||||
protected:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
private:
|
||||
QVector<QPair<QString, QString> > m_certs;
|
||||
QList<QPair<QString, QString>> m_certs;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ ChooseDirectoryPage::ChooseDirectoryPage(CreateAndroidManifestWizard *wizard)
|
||||
m_layout->addRow(m_sourceDirectoryWarning);
|
||||
|
||||
connect(m_androidPackageSourceDir, &PathChooser::textChanged, m_wizard, [this] {
|
||||
m_wizard->setDirectory(m_androidPackageSourceDir->rawFilePath());
|
||||
m_wizard->setDirectory(m_androidPackageSourceDir->unexpandedFilePath());
|
||||
});
|
||||
|
||||
if (wizard->copyGradle()) {
|
||||
@@ -228,7 +228,7 @@ CreateAndroidManifestWizard::CreateAndroidManifestWizard(BuildSystem *buildSyste
|
||||
|
||||
const QList<BuildTargetInfo> buildTargets = buildSystem->applicationTargets();
|
||||
QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(buildSystem->kit());
|
||||
m_copyGradle = version && version->qtVersion() >= QVersionNumber(5, 4, 0);
|
||||
m_copyGradle = version && version->qtVersion() >= AndroidManager::firstQtWithAndroidDeployQt;
|
||||
|
||||
if (buildTargets.isEmpty()) {
|
||||
// oh uhm can't create anything
|
||||
@@ -277,21 +277,21 @@ void CreateAndroidManifestWizard::createAndroidTemplateFiles()
|
||||
QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit());
|
||||
if (!version)
|
||||
return;
|
||||
if (version->qtVersion() < QVersionNumber(5, 4, 0)) {
|
||||
if (version->qtVersion() < AndroidManager::firstQtWithAndroidDeployQt) {
|
||||
FileUtils::copyRecursively(version->prefix() / "src/android/java/AndroidManifest.xml",
|
||||
m_directory / "AndroidManifest.xml",
|
||||
nullptr,
|
||||
copy);
|
||||
copy());
|
||||
} else {
|
||||
FileUtils::copyRecursively(version->prefix() / "src/android/templates",
|
||||
m_directory,
|
||||
nullptr,
|
||||
copy);
|
||||
copy());
|
||||
|
||||
if (m_copyGradle) {
|
||||
FilePath gradlePath = version->prefix() / "src/3rdparty/gradle";
|
||||
QTC_ASSERT(gradlePath.exists(), return);
|
||||
FileUtils::copyRecursively(gradlePath, m_directory, nullptr, copy);
|
||||
FileUtils::copyRecursively(gradlePath, m_directory, nullptr, copy());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <texteditor/codeassist/keywordscompletionassist.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditoractionhandler.h>
|
||||
#include <texteditor/texteditorconstants.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
@@ -48,7 +47,7 @@ public:
|
||||
setDocumentCreator(createJavaDocument);
|
||||
setUseGenericHighlighter(true);
|
||||
setCommentDefinition(Utils::CommentDefinition::CppStyle);
|
||||
setEditorActionHandlers(TextEditor::TextEditorActionHandler::UnCommentSelection);
|
||||
setOptionalActionMask(TextEditor::OptionalActions::UnCommentSelection);
|
||||
setCompletionAssistProvider(new TextEditor::KeywordsCompletionAssistProvider(keywords));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
#include <qtsupport/qtkitaspect.h>
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/mimeconstants.h>
|
||||
#include <utils/pathchooser.h>
|
||||
#include <utils/temporarydirectory.h>
|
||||
#include <utils/variablechooser.h>
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
@@ -57,26 +57,23 @@ JLSSettingsWidget::JLSSettingsWidget(const JLSSettings *settings, QWidget *paren
|
||||
, m_java(new PathChooser(this))
|
||||
, m_ls(new PathChooser(this))
|
||||
{
|
||||
int row = 0;
|
||||
auto *mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(new QLabel(Tr::tr("Name:")), row, 0);
|
||||
mainLayout->addWidget(m_name, row, 1);
|
||||
auto chooser = new VariableChooser(this);
|
||||
chooser->addSupportedWidget(m_name);
|
||||
|
||||
mainLayout->addWidget(new QLabel(Tr::tr("Java:")), ++row, 0);
|
||||
m_java->setExpectedKind(PathChooser::ExistingCommand);
|
||||
m_java->setFilePath(settings->m_executable);
|
||||
mainLayout->addWidget(m_java, row, 1);
|
||||
|
||||
mainLayout->addWidget(new QLabel(Tr::tr("Java Language Server:")), ++row, 0);
|
||||
m_ls->setExpectedKind(PathChooser::File);
|
||||
m_ls->lineEdit()->setPlaceholderText(Tr::tr("Path to equinox launcher jar"));
|
||||
m_ls->setPromptDialogFilter("org.eclipse.equinox.launcher_*.jar");
|
||||
m_ls->setFilePath(settings->m_languageServer);
|
||||
mainLayout->addWidget(m_ls, row, 1);
|
||||
|
||||
setLayout(mainLayout);
|
||||
using namespace Layouting;
|
||||
Form {
|
||||
Tr::tr("Name:"), m_name, br,
|
||||
Tr::tr("Java:"), m_java, br,
|
||||
Tr::tr("Java Language Server:"), m_ls, br,
|
||||
}.attachTo(this);
|
||||
}
|
||||
|
||||
JLSSettings::JLSSettings()
|
||||
@@ -308,7 +305,7 @@ void JLSClient::updateProjectFiles()
|
||||
|
||||
const QStringList classPaths = node->data(Constants::AndroidClassPaths).toStringList();
|
||||
|
||||
const FilePath &sdkLocation = androidConfig().sdkLocation();
|
||||
const FilePath &sdkLocation = AndroidConfig::sdkLocation();
|
||||
const QString &targetSDK = AndroidManager::buildTargetSDK(m_currentTarget);
|
||||
const FilePath androidJar = sdkLocation / QString("platforms/%2/android.jar")
|
||||
.arg(targetSDK);
|
||||
|
||||
@@ -62,7 +62,7 @@ OutputLineParser::Result JavaParser::handleLine(const QString &line, OutputForma
|
||||
absoluteFilePath(file),
|
||||
lineno);
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, match, 2);
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, task.file, task.line, task.column, match, 2);
|
||||
scheduleTask(task, 1);
|
||||
return {Status::Done, linkSpecs};
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ static SplashScreenWidget *addWidgetToPage(QWidget *page,
|
||||
const QString &splashScreenPath,
|
||||
int scalingRatio, int maxScalingRatio,
|
||||
QHBoxLayout *pageLayout,
|
||||
QVector<SplashScreenWidget *> &widgetContainer)
|
||||
QList<SplashScreenWidget *> &widgetContainer)
|
||||
{
|
||||
auto splashScreenWidget = new SplashScreenWidget(page,
|
||||
size,
|
||||
@@ -90,9 +90,9 @@ static SplashScreenWidget *addWidgetToPage(QWidget *page,
|
||||
}
|
||||
|
||||
static QWidget *createPage(TextEditor::TextEditorWidget *textEditorWidget,
|
||||
QVector<SplashScreenWidget *> &widgetContainer,
|
||||
QVector<SplashScreenWidget *> &portraitWidgetContainer,
|
||||
QVector<SplashScreenWidget *> &landscapeWidgetContainer,
|
||||
QList<SplashScreenWidget *> &widgetContainer,
|
||||
QList<SplashScreenWidget *> &portraitWidgetContainer,
|
||||
QList<SplashScreenWidget *> &landscapeWidgetContainer,
|
||||
int scalingRatio,
|
||||
const QSize &size,
|
||||
const QSize &portraitSize,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
#include <QStackedWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
@@ -48,9 +48,9 @@ private:
|
||||
void clearAll();
|
||||
|
||||
TextEditor::TextEditorWidget *m_textEditorWidget = nullptr;
|
||||
QVector<SplashScreenWidget *> m_imageWidgets;
|
||||
QVector<SplashScreenWidget *> m_portraitImageWidgets;
|
||||
QVector<SplashScreenWidget *> m_landscapeImageWidgets;
|
||||
QList<SplashScreenWidget *> m_imageWidgets;
|
||||
QList<SplashScreenWidget *> m_portraitImageWidgets;
|
||||
QList<SplashScreenWidget *> m_landscapeImageWidgets;
|
||||
bool m_splashScreenSticky = false;
|
||||
QColor m_splashScreenBackgroundColor;
|
||||
QCheckBox *m_stickyCheck = nullptr;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"Name" : "AppStatisticsMonitor",
|
||||
"Version" : "${IDE_VERSION}",
|
||||
"CompatVersion" : "${IDE_VERSION_COMPAT}",
|
||||
"Experimental" : true,
|
||||
"DisabledByDefault" : ${APPSTATISTICSMONITOR_DISABLEDBYDEFAULT},
|
||||
"Vendor" : "The Qt Company Ltd",
|
||||
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",
|
||||
"License" : [ "Commercial Usage",
|
||||
"",
|
||||
"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.",
|
||||
"",
|
||||
"GNU General Public License Usage",
|
||||
"",
|
||||
"Alternatively, this plugin 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 plugin. 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."
|
||||
],
|
||||
"Description" : "Visualize CPU and Memory consumption for running applications.",
|
||||
"LongDescription" : [
|
||||
"Select an application to monitor its performance in real-time."
|
||||
],
|
||||
"Url" : "https://www.qt.io",
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
42
src/plugins/appstatisticsmonitor/CMakeLists.txt
Normal file
42
src/plugins/appstatisticsmonitor/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake")
|
||||
|
||||
project(AppStatisticMonitor)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
find_package(Qt6 COMPONENTS Charts QUIET)
|
||||
|
||||
set(APPSTATISTICSMONITOR_DISABLEDBYDEFAULT "true")
|
||||
|
||||
if (NOT QT_CREATOR_API_DEFINED)
|
||||
# standalone build
|
||||
set(DESTINATION DESTINATION .)
|
||||
include(QtCreatorIDEBranding)
|
||||
include(QtCreatorAPI)
|
||||
qtc_handle_compiler_cache_support()
|
||||
|
||||
find_package(QtCreator COMPONENTS Core ProjectExplorer Utils REQUIRED)
|
||||
|
||||
set(APPSTATISTICSMONITOR_DISABLEDBYDEFAULT "false")
|
||||
endif()
|
||||
|
||||
|
||||
add_qtc_plugin(AppStatisticsMonitor
|
||||
CONDITION TARGET Qt6::Charts
|
||||
SKIP_TRANSLATION
|
||||
DEPENDS Qt6::Charts QtCreator::Utils
|
||||
PLUGIN_DEPENDS QtCreator::Core QtCreator::ProjectExplorer
|
||||
SOURCES
|
||||
appstatisticsmonitorplugin.cpp
|
||||
chart.cpp chart.h
|
||||
manager.cpp manager.h
|
||||
idataprovider.h idataprovider.cpp
|
||||
)
|
||||
|
||||
20
src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs
Normal file
20
src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs
Normal file
@@ -0,0 +1,20 @@
|
||||
QtcPlugin {
|
||||
name: "AppStatisticsMonitor"
|
||||
|
||||
Depends { name: "Core" }
|
||||
Depends { name: "ProjectExplorer" }
|
||||
Depends { name: "Qt.charts"; required: false }
|
||||
|
||||
condition: Qt.charts.present
|
||||
|
||||
files: [
|
||||
"appstatisticsmonitorplugin.cpp",
|
||||
"appstatisticsmonitortr.h",
|
||||
"chart.h",
|
||||
"chart.cpp",
|
||||
"idataprovider.h",
|
||||
"idataprovider.cpp",
|
||||
"manager.h",
|
||||
"manager.cpp",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
class AppStatisticsMonitorPlugin final : public ExtensionSystem::IPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "AppStatisticsMonitor.json")
|
||||
|
||||
private:
|
||||
void initialize() final;
|
||||
std::unique_ptr<AppStatisticsMonitorManager> m_appstatisticsmonitorManager;
|
||||
std::unique_ptr<AppStatisticsMonitorViewFactory> m_appstatisticsmonitorViewFactory;
|
||||
};
|
||||
|
||||
void AppStatisticsMonitorPlugin::initialize()
|
||||
{
|
||||
m_appstatisticsmonitorManager = std::make_unique<AppStatisticsMonitorManager>();
|
||||
m_appstatisticsmonitorViewFactory = std::make_unique<AppStatisticsMonitorViewFactory>(
|
||||
m_appstatisticsmonitorManager.get());
|
||||
}
|
||||
|
||||
} // namespace AppStatisticsMonitor::Internal
|
||||
|
||||
#include "appstatisticsmonitorplugin.moc"
|
||||
15
src/plugins/appstatisticsmonitor/appstatisticsmonitortr.h
Normal file
15
src/plugins/appstatisticsmonitor/appstatisticsmonitortr.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace AppStatisticsMonitor {
|
||||
|
||||
struct Tr
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(AppStatisticsMonitor)
|
||||
};
|
||||
|
||||
} // namespace AppStatisticsMonitor
|
||||
267
src/plugins/appstatisticsmonitor/chart.cpp
Normal file
267
src/plugins/appstatisticsmonitor/chart.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "chart.h"
|
||||
|
||||
#include <utils/theme/theme.h>
|
||||
|
||||
#include <QAbstractAxis>
|
||||
#include <QChartView>
|
||||
#include <QDebug>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QPointF>
|
||||
#include <QRandomGenerator>
|
||||
#include <QSplineSeries>
|
||||
#include <QString>
|
||||
#include <QValueAxis>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
static const int padding = 40;
|
||||
static const int numPadding = 10;
|
||||
static const QRectF dataRangeDefault = QRectF(0, 0, 5, 1);
|
||||
|
||||
AppStatisticsMonitorChart::AppStatisticsMonitorChart(
|
||||
const QString &name, QGraphicsItem *parent, Qt::WindowFlags wFlags)
|
||||
: QChart(QChart::ChartTypeCartesian, parent, wFlags)
|
||||
, m_series(new QLineSeries(this))
|
||||
, m_axisX(new QValueAxis())
|
||||
, m_axisY(new QValueAxis())
|
||||
, m_point(0, 0)
|
||||
, m_chartView(new QChartView(this))
|
||||
, m_name(name)
|
||||
{
|
||||
m_chartView->setMinimumHeight(200);
|
||||
m_chartView->setMinimumWidth(400);
|
||||
const QBrush brushTitle(creatorColor(Theme::Token_Text_Muted));
|
||||
// const QBrush brush(creatorColor(Theme::Token_Background_Default)); left for the future
|
||||
const QBrush brush(creatorColor(Theme::BackgroundColorNormal));
|
||||
const QPen penBack(creatorColor(Theme::Token_Text_Muted));
|
||||
const QPen penAxis(creatorColor(Theme::Token_Text_Muted));
|
||||
|
||||
setTitleBrush(brushTitle);
|
||||
setBackgroundBrush(brush);
|
||||
setBackgroundPen(penBack);
|
||||
m_axisX->setLinePen(penAxis);
|
||||
m_axisY->setLinePen(penAxis);
|
||||
m_axisX->setLabelsColor(creatorColor(Theme::Token_Text_Muted));
|
||||
m_axisY->setLabelsColor(creatorColor(Theme::Token_Text_Muted));
|
||||
QPen pen(creatorColor(Theme::Token_Accent_Default));
|
||||
pen.setWidth(2);
|
||||
m_series->setPen(pen);
|
||||
|
||||
setTitle(m_name + " " + QString::number(m_point.y(), 'g', 4) + "%");
|
||||
m_chartView->setRenderHint(QPainter::Antialiasing);
|
||||
m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
addSeries(m_series);
|
||||
|
||||
addAxis(m_axisX, Qt::AlignBottom);
|
||||
addAxis(m_axisY, Qt::AlignLeft);
|
||||
m_series->attachAxis(m_axisX);
|
||||
m_series->attachAxis(m_axisY);
|
||||
m_axisX->applyNiceNumbers();
|
||||
m_axisY->applyNiceNumbers();
|
||||
legend()->hide();
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
QChartView *AppStatisticsMonitorChart::chartView()
|
||||
{
|
||||
return m_chartView;
|
||||
}
|
||||
|
||||
void AppStatisticsMonitorChart::addNewPoint(const QPointF &point)
|
||||
{
|
||||
m_point = point;
|
||||
if (m_axisY->max() < m_point.y())
|
||||
m_axisY->setRange(0, qRound(m_point.y()) + 1);
|
||||
m_axisX->setRange(0, qRound(m_point.x()) + 1);
|
||||
|
||||
setTitle(m_name + " " + QString::number(m_point.y(), 'g', 4) + "%");
|
||||
m_series->append(m_point);
|
||||
}
|
||||
|
||||
void AppStatisticsMonitorChart::loadNewProcessData(const QList<double> &data)
|
||||
{
|
||||
clear();
|
||||
QList<QPointF> points{{0, 0}};
|
||||
int i = 0;
|
||||
double max_y = 0;
|
||||
|
||||
for (double e : qAsConst(data)) {
|
||||
points.push_back({double(++i), e});
|
||||
max_y = qMax(max_y, e);
|
||||
}
|
||||
|
||||
m_axisY->setRange(0, qRound(max_y) + 1);
|
||||
m_axisX->setRange(0, data.size() + 1);
|
||||
|
||||
m_series->clear();
|
||||
m_series->append(points);
|
||||
}
|
||||
|
||||
void AppStatisticsMonitorChart::clear()
|
||||
{
|
||||
m_axisX->setRange(0, 5);
|
||||
m_axisY->setRange(0, 1);
|
||||
m_series->clear();
|
||||
m_series->append(0, 0);
|
||||
}
|
||||
|
||||
double AppStatisticsMonitorChart::lastPointX() const
|
||||
{
|
||||
return m_point.x();
|
||||
}
|
||||
|
||||
//---------------------- Chart -----------------------
|
||||
|
||||
Chart::Chart(const QString &name, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_name(name)
|
||||
{
|
||||
setMinimumHeight(200);
|
||||
setMinimumWidth(400);
|
||||
}
|
||||
|
||||
void Chart::addNewPoint(const QPointF &point)
|
||||
{
|
||||
m_points.append(point);
|
||||
update();
|
||||
}
|
||||
|
||||
void Chart::loadNewProcessData(const QList<double> &data)
|
||||
{
|
||||
clear();
|
||||
for (long i = 0; i < data.size(); ++i) {
|
||||
m_points.append(QPointF(i + 1, data[i]));
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
double Chart::lastPointX() const
|
||||
{
|
||||
if (m_points.isEmpty())
|
||||
return 0;
|
||||
return m_points.last().x();
|
||||
}
|
||||
|
||||
void Chart::clear()
|
||||
{
|
||||
m_points.clear();
|
||||
addNewPoint({0, 0});
|
||||
update();
|
||||
}
|
||||
|
||||
void Chart::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
QPainter painter(this);
|
||||
// painter.fillRect(rect(), creatorColor(Theme::Token_Background_Default)); left for the future
|
||||
painter.fillRect(rect(), creatorColor(Theme::BackgroundColorNormal));
|
||||
|
||||
// add the name of the chart in the middle of the widget width and on the top
|
||||
painter.drawText(
|
||||
rect(),
|
||||
Qt::AlignHCenter | Qt::AlignTop,
|
||||
m_name + QString::number(m_points.last().y(), 'g', 4) + "%");
|
||||
|
||||
const QRectF dataRange = calculateDataRange();
|
||||
updateScalingFactors(dataRange);
|
||||
|
||||
for (double x = dataRange.left(); x <= dataRange.right(); x += m_xGridStep) {
|
||||
double xPos = padding + (x - dataRange.left()) * m_xScale;
|
||||
if (xPos < padding || xPos > width() - padding)
|
||||
continue;
|
||||
painter.setPen(creatorColor(Theme::Token_Foreground_Default));
|
||||
painter.drawLine(xPos, padding, xPos, height() - padding);
|
||||
|
||||
painter.setPen(creatorColor(Theme::Token_Text_Muted));
|
||||
painter.drawText(xPos, height() - numPadding, QString::number(x));
|
||||
}
|
||||
|
||||
for (double y = dataRange.top(); y <= dataRange.bottom(); y += m_yGridStep) {
|
||||
double yPos = height() - padding - (y - (int) dataRange.top()) * m_yScale;
|
||||
if (yPos < padding || yPos > height() - padding)
|
||||
continue;
|
||||
|
||||
painter.setPen(creatorColor(Theme::Token_Foreground_Default));
|
||||
painter.drawLine(padding, yPos, width() - padding, yPos);
|
||||
|
||||
painter.setPen(creatorColor(Theme::Token_Text_Muted));
|
||||
painter.drawText(numPadding, yPos, QString::number(y));
|
||||
}
|
||||
|
||||
painter.setPen(creatorColor(Theme::Token_Foreground_Default));
|
||||
painter.drawLine(padding, height() - padding, width() - padding, height() - padding); // X axis
|
||||
painter.drawLine(padding, height() - padding, padding, padding); // Y axis
|
||||
|
||||
QPen pen(creatorColor(Theme::Token_Accent_Default));
|
||||
pen.setWidth(2);
|
||||
painter.setPen(pen);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
for (int i = 1; i < m_points.size(); ++i) {
|
||||
QPointF startPoint(
|
||||
padding + (m_points[i - 1].x() - dataRange.left()) * m_xScale,
|
||||
height() - padding - (m_points[i - 1].y() - dataRange.top()) * m_yScale);
|
||||
QPointF endPoint(
|
||||
padding + (m_points[i].x() - dataRange.left()) * m_xScale,
|
||||
height() - padding - (m_points[i].y() - dataRange.top()) * m_yScale);
|
||||
painter.drawLine(startPoint, endPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void Chart::addPoint()
|
||||
{
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
double x = m_points.size();
|
||||
double y = sin(x) + 10 * cos(x / 10) + 10;
|
||||
m_points.append(QPointF(x, y));
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF Chart::calculateDataRange() const
|
||||
{
|
||||
QRectF dataRange = QRectF(0, 0, 0, 0);
|
||||
|
||||
if (m_points.isEmpty())
|
||||
return dataRange;
|
||||
|
||||
for (const QPointF &point : m_points) {
|
||||
dataRange.setLeft(qMin(dataRange.left(), point.x()));
|
||||
dataRange.setRight(qMax(dataRange.right(), point.x()));
|
||||
|
||||
dataRange.setBottom(qMin(dataRange.bottom(), point.y()));
|
||||
dataRange.setTop(qMax(dataRange.top(), point.y()));
|
||||
}
|
||||
dataRange.setRight(round(dataRange.right()) + 1);
|
||||
dataRange.setTop(round(dataRange.top()) + 1);
|
||||
|
||||
dataRange = dataRange.united(dataRangeDefault);
|
||||
return dataRange;
|
||||
}
|
||||
|
||||
void Chart::updateScalingFactors(const QRectF &dataRange)
|
||||
{
|
||||
const double xRange = dataRange.right() - dataRange.left();
|
||||
double yRange = dataRange.bottom() - dataRange.top();
|
||||
yRange = yRange == 0 ? dataRange.top() : yRange;
|
||||
|
||||
m_xGridStep = qRound(xRange / 10);
|
||||
m_xGridStep = m_xGridStep == 0 ? 1 : m_xGridStep;
|
||||
|
||||
m_yGridStep = yRange / 5;
|
||||
m_yGridStep = qRound(m_yGridStep * 10.0) / 10.0;
|
||||
if (yRange > 10)
|
||||
m_yGridStep = qRound(m_yGridStep);
|
||||
m_yGridStep = qMax(m_yGridStep, 0.1);
|
||||
|
||||
m_xScale = (width() - 2 * padding) / xRange;
|
||||
m_yScale = (height() - 2 * padding) / yRange;
|
||||
}
|
||||
} // namespace AppStatisticsMonitor::Internal
|
||||
70
src/plugins/appstatisticsmonitor/chart.h
Normal file
70
src/plugins/appstatisticsmonitor/chart.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include <QChart>
|
||||
#include <QList>
|
||||
#include <QPaintEvent>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QChartView;
|
||||
class QLineSeries;
|
||||
class QValueAxis;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
class AppStatisticsMonitorChart : public QChart
|
||||
{
|
||||
public:
|
||||
AppStatisticsMonitorChart(
|
||||
const QString &name, QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {});
|
||||
|
||||
void addNewPoint(const QPointF &point);
|
||||
void loadNewProcessData(const QList<double> &data);
|
||||
double lastPointX() const;
|
||||
void clear();
|
||||
QChartView *chartView();
|
||||
|
||||
private:
|
||||
QLineSeries *m_series;
|
||||
QStringList m_titles;
|
||||
QValueAxis *m_axisX;
|
||||
QValueAxis *m_axisY;
|
||||
QPointF m_point;
|
||||
|
||||
QChartView *m_chartView;
|
||||
QString m_name;
|
||||
};
|
||||
|
||||
class Chart : public QWidget
|
||||
{
|
||||
public:
|
||||
Chart(const QString &name, QWidget *parent = nullptr);
|
||||
|
||||
void addNewPoint(const QPointF &point);
|
||||
void loadNewProcessData(const QList<double> &data);
|
||||
double lastPointX() const;
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void addPoint();
|
||||
QRectF calculateDataRange() const;
|
||||
void updateScalingFactors(const QRectF &dataRange);
|
||||
|
||||
private:
|
||||
QList<QPointF> m_points;
|
||||
QString m_name;
|
||||
|
||||
double m_xScale = 1;
|
||||
double m_yScale = 1;
|
||||
double m_xGridStep = 1;
|
||||
double m_yGridStep = 1;
|
||||
};
|
||||
|
||||
} // namespace AppStatisticsMonitor::Internal
|
||||
343
src/plugins/appstatisticsmonitor/idataprovider.cpp
Normal file
343
src/plugins/appstatisticsmonitor/idataprovider.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "idataprovider.h"
|
||||
|
||||
#include <utils/expected.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include <sys/types.h>
|
||||
#include <sys/proc_info.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <unistd.h>
|
||||
#include <libproc.h>
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
IDataProvider::IDataProvider(qint64 pid, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_pid(pid)
|
||||
{
|
||||
m_timer.setInterval(1000);
|
||||
connect(&m_timer, &QTimer::timeout, this, [this] { handleTimeout(); });
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void IDataProvider::handleTimeout()
|
||||
{
|
||||
m_memoryConsumption.append(getMemoryConsumption());
|
||||
m_cpuConsumption.append(getCpuConsumption());
|
||||
emit newDataAvailable();
|
||||
}
|
||||
|
||||
QList<double> IDataProvider::memoryConsumptionHistory() const
|
||||
{
|
||||
return m_memoryConsumption;
|
||||
}
|
||||
|
||||
QList<double> IDataProvider::cpuConsumptionHistory() const
|
||||
{
|
||||
return m_cpuConsumption;
|
||||
}
|
||||
|
||||
double IDataProvider::memoryConsumptionLast() const
|
||||
{
|
||||
return m_memoryConsumption.isEmpty() ? 0 : m_memoryConsumption.last();
|
||||
}
|
||||
|
||||
double IDataProvider::cpuConsumptionLast() const
|
||||
{
|
||||
return m_cpuConsumption.isEmpty() ? 0 : m_cpuConsumption.last();
|
||||
}
|
||||
|
||||
// ------------------------- LinuxDataProvider --------------------------------
|
||||
#ifdef Q_OS_LINUX
|
||||
class LinuxDataProvider : public IDataProvider
|
||||
{
|
||||
public:
|
||||
LinuxDataProvider(qint64 pid, QObject *parent = nullptr)
|
||||
: IDataProvider(pid, parent)
|
||||
{}
|
||||
|
||||
double getMemoryConsumption()
|
||||
{
|
||||
const FilePath statusMemory = FilePath::fromString(
|
||||
QStringLiteral("/proc/%1/status").arg(m_pid));
|
||||
const expected_str<QByteArray> statusMemoryContent = statusMemory.fileContents();
|
||||
|
||||
if (!statusMemoryContent)
|
||||
return 0;
|
||||
|
||||
int vmPeak = 0;
|
||||
const static QRegularExpression numberRX(QLatin1String("[^0-9]+"));
|
||||
for (const QByteArray &element : statusMemoryContent.value().split('\n')) {
|
||||
if (element.startsWith("VmHWM")) {
|
||||
const QString p = QString::fromUtf8(element);
|
||||
vmPeak = p.split(numberRX, Qt::SkipEmptyParts)[0].toLong();
|
||||
}
|
||||
}
|
||||
|
||||
const FilePath meminfoFile("/proc/meminfo");
|
||||
const expected_str<QByteArray> meminfoContent = meminfoFile.fileContents();
|
||||
if (!meminfoContent)
|
||||
return 0;
|
||||
|
||||
const auto meminfo = meminfoContent.value().split('\n');
|
||||
if (meminfo.isEmpty())
|
||||
return 0;
|
||||
|
||||
const auto parts = QString::fromUtf8(meminfo.front()).split(numberRX, Qt::SkipEmptyParts);
|
||||
if (parts.isEmpty())
|
||||
return 0;
|
||||
|
||||
return double(vmPeak) / parts[0].toDouble() * 100;
|
||||
}
|
||||
|
||||
// Provides the CPU usage from the last request
|
||||
double getCpuConsumption()
|
||||
{
|
||||
const FilePath status = FilePath::fromString(QStringLiteral("/proc/%1/stat").arg(m_pid));
|
||||
const FilePath uptimeFile = FilePath::fromString(QStringLiteral("/proc/uptime"));
|
||||
const double clkTck = static_cast<double>(sysconf(_SC_CLK_TCK));
|
||||
const expected_str<QByteArray> statusFileContent = status.fileContents();
|
||||
const expected_str<QByteArray> uptimeFileContent = uptimeFile.fileContents();
|
||||
|
||||
if (!statusFileContent.has_value() || !uptimeFileContent.has_value() || clkTck == 0)
|
||||
return 0;
|
||||
|
||||
const QList<QByteArray> processStatus = statusFileContent.value().split(' ');
|
||||
if (processStatus.isEmpty() || processStatus.size() < 22)
|
||||
return 0;
|
||||
|
||||
const double uptime = uptimeFileContent.value().split(' ')[0].toDouble();
|
||||
|
||||
const double utime = processStatus[13].toDouble() / clkTck;
|
||||
const double stime = processStatus[14].toDouble() / clkTck;
|
||||
const double cutime = processStatus[15].toDouble() / clkTck;
|
||||
const double cstime = processStatus[16].toDouble() / clkTck;
|
||||
const double starttime = processStatus[21].toDouble() / clkTck;
|
||||
|
||||
// Calculate CPU usage for last request
|
||||
const double currentTotalTime = utime + stime + cutime + cstime;
|
||||
|
||||
const double elapsed = uptime - starttime;
|
||||
const double clicks = (currentTotalTime - m_lastTotalTime) * clkTck;
|
||||
const double timeClicks = (elapsed - m_lastRequestStartTime) * clkTck;
|
||||
|
||||
m_lastTotalTime = currentTotalTime;
|
||||
m_lastRequestStartTime = elapsed;
|
||||
|
||||
return timeClicks > 0 ? 100 * (clicks / timeClicks) : 0;
|
||||
}
|
||||
|
||||
// Provides the usage all over the process lifetime
|
||||
// Can be used in the future for the process lifetime statistics
|
||||
|
||||
// double LinuxDataProvider::getCpuConsumption()
|
||||
// {
|
||||
// const FilePath status = FilePath::fromString(
|
||||
// QStringLiteral("/proc/%1/stat").arg(m_pid));
|
||||
// const FilePath uptimeFile = FilePath::fromString(QStringLiteral("/proc/uptime"));
|
||||
// const double clkTck = static_cast<double>(sysconf(_SC_CLK_TCK));
|
||||
|
||||
// if (!status.fileContents().has_value() || !uptimeFile.fileContents().has_value() || clkTck == 0)
|
||||
// return 0;
|
||||
|
||||
// const QVector<QByteArray> processStatus = status.fileContents().value().split(' ').toVector();
|
||||
// const double uptime = uptimeFile.fileContents().value().split(' ')[0].toDouble();
|
||||
|
||||
// const double utime = processStatus[13].toDouble() / clkTck;
|
||||
// const double stime = processStatus[14].toDouble() / clkTck;
|
||||
// const double cutime = processStatus[15].toDouble() / clkTck;
|
||||
// const double cstime = processStatus[16].toDouble() / clkTck;
|
||||
// const double starttime = processStatus[21].toDouble() / clkTck;
|
||||
|
||||
// const double elapsed = uptime - starttime;
|
||||
// const double usage_sec = utime + stime + cutime + cstime;
|
||||
// const double usage = 100 * usage_sec / elapsed;
|
||||
|
||||
// return usage;
|
||||
// }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// ------------------------- WindowsDataProvider --------------------------------
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
class WindowsDataProvider : public IDataProvider
|
||||
{
|
||||
public:
|
||||
WindowsDataProvider(qint64 pid, QObject *parent = nullptr)
|
||||
: IDataProvider(pid, parent)
|
||||
{
|
||||
MEMORYSTATUSEX memoryStatus;
|
||||
memoryStatus.dwLength = sizeof(memoryStatus);
|
||||
GlobalMemoryStatusEx(&memoryStatus);
|
||||
|
||||
m_totalMemory = memoryStatus.ullTotalPhys;
|
||||
}
|
||||
|
||||
double getMemoryConsumption()
|
||||
{
|
||||
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid);
|
||||
|
||||
PROCESS_MEMORY_COUNTERS pmc;
|
||||
SIZE_T memoryUsed = 0;
|
||||
if (GetProcessMemoryInfo(process, &pmc, sizeof(pmc))) {
|
||||
memoryUsed = pmc.WorkingSetSize;
|
||||
// Can be used in the future for the process lifetime statistics
|
||||
//double memoryUsedMB = static_cast<double>(memoryUsed) / (1024.0 * 1024.0);
|
||||
}
|
||||
|
||||
CloseHandle(process);
|
||||
return static_cast<double>(memoryUsed) / static_cast<double>(m_totalMemory) * 100.0;
|
||||
}
|
||||
|
||||
double getCpuConsumption()
|
||||
{
|
||||
ULARGE_INTEGER sysKernel, sysUser, procKernel, procUser;
|
||||
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid);
|
||||
|
||||
FILETIME creationTime, exitTime, kernelTime, userTime;
|
||||
GetProcessTimes(process, &creationTime, &exitTime, &kernelTime, &userTime);
|
||||
procKernel.LowPart = kernelTime.dwLowDateTime;
|
||||
procKernel.HighPart = kernelTime.dwHighDateTime;
|
||||
procUser.LowPart = userTime.dwLowDateTime;
|
||||
procUser.HighPart = userTime.dwHighDateTime;
|
||||
|
||||
SYSTEMTIME sysTime;
|
||||
GetSystemTime(&sysTime);
|
||||
SystemTimeToFileTime(&sysTime, &kernelTime);
|
||||
SystemTimeToFileTime(&sysTime, &userTime);
|
||||
sysKernel.LowPart = kernelTime.dwLowDateTime;
|
||||
sysKernel.HighPart = kernelTime.dwHighDateTime;
|
||||
sysUser.LowPart = userTime.dwLowDateTime;
|
||||
sysUser.HighPart = userTime.dwHighDateTime;
|
||||
|
||||
const double sysElapsedTime = sysKernel.QuadPart + sysUser.QuadPart
|
||||
- m_lastSysKernel.QuadPart - m_lastSysUser.QuadPart;
|
||||
const double procElapsedTime = procKernel.QuadPart + procUser.QuadPart
|
||||
- m_lastProcKernel.QuadPart - m_lastProcUser.QuadPart;
|
||||
const double cpuUsagePercent = (procElapsedTime / sysElapsedTime) * 100.0;
|
||||
|
||||
m_lastProcKernel = procKernel;
|
||||
m_lastProcUser = procUser;
|
||||
m_lastSysKernel = sysKernel;
|
||||
m_lastSysUser = sysUser;
|
||||
|
||||
CloseHandle(process);
|
||||
return cpuUsagePercent;
|
||||
}
|
||||
|
||||
private:
|
||||
ULARGE_INTEGER m_lastSysKernel = {{0, 0}}, m_lastSysUser = {{0, 0}},
|
||||
m_lastProcKernel = {{0, 0}}, m_lastProcUser = {{0, 0}};
|
||||
|
||||
DWORDLONG m_totalMemory = 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
// ------------------------- MacDataProvider --------------------------------
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
class MacDataProvider : public IDataProvider
|
||||
{
|
||||
public:
|
||||
MacDataProvider(qint64 pid, QObject *parent = nullptr)
|
||||
: IDataProvider(pid, parent)
|
||||
{}
|
||||
|
||||
double getCpuConsumption()
|
||||
{
|
||||
proc_taskallinfo taskAllInfo = {};
|
||||
|
||||
const int result
|
||||
= proc_pidinfo(m_pid, PROC_PIDTASKALLINFO, 0, &taskAllInfo, sizeof(taskAllInfo));
|
||||
if (result == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
mach_timebase_info_data_t sTimebase;
|
||||
mach_timebase_info(&sTimebase);
|
||||
double timebase_to_ns = (double) sTimebase.numer / (double) sTimebase.denom;
|
||||
|
||||
const double currentTotalCpuTime = ((double) taskAllInfo.ptinfo.pti_total_user
|
||||
+ (double) taskAllInfo.ptinfo.pti_total_system)
|
||||
* timebase_to_ns / 1e9;
|
||||
|
||||
const double cpuUsageDelta = currentTotalCpuTime - m_prevCpuUsage;
|
||||
|
||||
const auto elapsedTime = std::chrono::steady_clock::now() - m_prevTime;
|
||||
const double elapsedTimeSeconds
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTime).count() / 1000.0;
|
||||
|
||||
m_prevCpuUsage = currentTotalCpuTime;
|
||||
m_prevTime = std::chrono::steady_clock::now();
|
||||
|
||||
return (cpuUsageDelta / elapsedTimeSeconds) * 100.0;
|
||||
}
|
||||
|
||||
double getTotalPhysicalMemory()
|
||||
{
|
||||
int mib[2];
|
||||
size_t length;
|
||||
long long physicalMemory;
|
||||
|
||||
mib[0] = CTL_HW;
|
||||
mib[1] = HW_MEMSIZE;
|
||||
length = sizeof(physicalMemory);
|
||||
sysctl(mib, 2, &physicalMemory, &length, NULL, 0);
|
||||
|
||||
return physicalMemory;
|
||||
}
|
||||
|
||||
double getMemoryConsumption()
|
||||
{
|
||||
proc_taskinfo taskInfo;
|
||||
int result = proc_pidinfo(m_pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo));
|
||||
if (result == -1)
|
||||
return 0;
|
||||
|
||||
return (taskInfo.pti_resident_size / getTotalPhysicalMemory()) * 100.0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::steady_clock::time_point m_prevTime = std::chrono::steady_clock::now();
|
||||
double m_prevCpuUsage = 0;
|
||||
};
|
||||
#endif
|
||||
|
||||
IDataProvider *createDataProvider(qint64 pid)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return new WindowsDataProvider(pid);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return new MacDataProvider(pid);
|
||||
#else // Q_OS_LINUX
|
||||
return new LinuxDataProvider(pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace AppStatisticsMonitor::Internal
|
||||
44
src/plugins/appstatisticsmonitor/idataprovider.h
Normal file
44
src/plugins/appstatisticsmonitor/idataprovider.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
class IDataProvider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
IDataProvider(qint64 pid, QObject *parent = nullptr);
|
||||
|
||||
QList<double> memoryConsumptionHistory() const;
|
||||
QList<double> cpuConsumptionHistory() const;
|
||||
|
||||
double memoryConsumptionLast() const;
|
||||
double cpuConsumptionLast() const;
|
||||
|
||||
protected:
|
||||
virtual double getMemoryConsumption() = 0;
|
||||
virtual double getCpuConsumption() = 0;
|
||||
|
||||
QList<double> m_memoryConsumption;
|
||||
QList<double> m_cpuConsumption;
|
||||
qint64 m_pid;
|
||||
double m_lastTotalTime;
|
||||
double m_lastRequestStartTime;
|
||||
|
||||
signals:
|
||||
void newDataAvailable();
|
||||
|
||||
private:
|
||||
void handleTimeout();
|
||||
|
||||
QTimer m_timer;
|
||||
};
|
||||
|
||||
IDataProvider *createDataProvider(qint64 pid);
|
||||
|
||||
} // AppStatisticsMonitor::Internal
|
||||
220
src/plugins/appstatisticsmonitor/manager.cpp
Normal file
220
src/plugins/appstatisticsmonitor/manager.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
#include "chart.h"
|
||||
#include "idataprovider.h"
|
||||
|
||||
#include <coreplugin/session.h>
|
||||
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/runcontrol.h>
|
||||
|
||||
#include <QChartView>
|
||||
#include <QFormLayout>
|
||||
#include <QGraphicsItem>
|
||||
#include <QHash>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
class AppStatisticsMonitorView : public QWidget
|
||||
{
|
||||
public:
|
||||
explicit AppStatisticsMonitorView(
|
||||
AppStatisticsMonitorManager *appStatisticManager);
|
||||
|
||||
~AppStatisticsMonitorView() override;
|
||||
|
||||
private:
|
||||
QComboBox *m_comboBox;
|
||||
|
||||
std::unique_ptr<AppStatisticsMonitorChart> m_chartMem;
|
||||
std::unique_ptr<AppStatisticsMonitorChart> m_chartCpu;
|
||||
|
||||
AppStatisticsMonitorManager *m_manager;
|
||||
};
|
||||
|
||||
AppStatisticsMonitorManager::AppStatisticsMonitorManager()
|
||||
{
|
||||
connect(
|
||||
ProjectExplorer::ProjectExplorerPlugin::instance(),
|
||||
&ProjectExplorer::ProjectExplorerPlugin::runControlStarted,
|
||||
this,
|
||||
[this](RunControl *runControl) {
|
||||
qint64 pid = runControl->applicationProcessHandle().pid();
|
||||
|
||||
m_pidNameMap[pid] = runControl->displayName();
|
||||
m_rcPidMap[runControl] = pid;
|
||||
|
||||
m_currentDataProvider = createDataProvider(pid);
|
||||
m_pidDataProviders.insert(pid, m_currentDataProvider);
|
||||
|
||||
emit appStarted(runControl->displayName(), pid);
|
||||
});
|
||||
|
||||
connect(
|
||||
ProjectExplorer::ProjectExplorerPlugin::instance(),
|
||||
&ProjectExplorer::ProjectExplorerPlugin::runControlStoped,
|
||||
this,
|
||||
[this](RunControl *runControl) {
|
||||
const auto pidIt = m_rcPidMap.constFind(runControl);
|
||||
if (pidIt == m_rcPidMap.constEnd())
|
||||
return;
|
||||
|
||||
const qint64 pid = pidIt.value();
|
||||
m_rcPidMap.erase(pidIt);
|
||||
|
||||
m_pidNameMap.remove(pid);
|
||||
delete m_pidDataProviders[pid];
|
||||
m_pidDataProviders.remove(pid);
|
||||
if (m_pidDataProviders.isEmpty())
|
||||
setCurrentDataProvider(-1);
|
||||
else
|
||||
setCurrentDataProvider(m_pidDataProviders.keys().last());
|
||||
|
||||
emit appStoped(pid);
|
||||
});
|
||||
}
|
||||
|
||||
QString AppStatisticsMonitorManager::nameForPid(qint64 pid) const
|
||||
{
|
||||
const auto pidIt = m_pidNameMap.constFind(pid);
|
||||
if (pidIt == m_pidNameMap.constEnd())
|
||||
return {};
|
||||
|
||||
return pidIt.value();
|
||||
}
|
||||
|
||||
IDataProvider *AppStatisticsMonitorManager::currentDataProvider() const
|
||||
{
|
||||
return m_currentDataProvider;
|
||||
}
|
||||
|
||||
void AppStatisticsMonitorManager::setCurrentDataProvider(qint64 pid)
|
||||
{
|
||||
m_currentDataProvider = nullptr;
|
||||
const auto pidIt = m_pidDataProviders.constFind(pid);
|
||||
if (pidIt == m_pidDataProviders.constEnd())
|
||||
return;
|
||||
|
||||
m_currentDataProvider = pidIt.value();
|
||||
connect(
|
||||
m_currentDataProvider,
|
||||
&IDataProvider::newDataAvailable,
|
||||
this,
|
||||
&AppStatisticsMonitorManager::newDataAvailable);
|
||||
}
|
||||
|
||||
QHash<qint64, QString> AppStatisticsMonitorManager::pidNameMap() const
|
||||
{
|
||||
return m_pidNameMap;
|
||||
}
|
||||
|
||||
// AppStatisticsMonitorView
|
||||
AppStatisticsMonitorView::AppStatisticsMonitorView(AppStatisticsMonitorManager *appManager)
|
||||
: m_manager(appManager)
|
||||
{
|
||||
auto layout = new QVBoxLayout;
|
||||
auto form = new QFormLayout;
|
||||
setLayout(layout);
|
||||
|
||||
m_comboBox = new QComboBox(this);
|
||||
form->addRow(m_comboBox);
|
||||
|
||||
m_chartMem = std::make_unique<AppStatisticsMonitorChart>(tr("Memory consumption"));
|
||||
m_chartCpu = std::make_unique<AppStatisticsMonitorChart>(tr("CPU consumption"));
|
||||
|
||||
form->addRow(m_chartMem->chartView());
|
||||
form->addRow(m_chartCpu->chartView());
|
||||
|
||||
layout->addLayout(form);
|
||||
|
||||
for (auto pidName : m_manager->pidNameMap().asKeyValueRange()) {
|
||||
qint64 pid = pidName.first;
|
||||
m_comboBox->addItem(pidName.second + " : " + QString::number(pid), pid);
|
||||
}
|
||||
m_comboBox->setCurrentIndex(m_comboBox->count() - 1);
|
||||
|
||||
m_chartCpu->clear();
|
||||
m_chartMem->clear();
|
||||
|
||||
auto updateCharts = [this](int index) {
|
||||
m_manager->setCurrentDataProvider(m_comboBox->itemData(index).toLongLong());
|
||||
if (m_manager->currentDataProvider() != nullptr) {
|
||||
const QList<double> &memConsumptionHistory
|
||||
= m_manager->currentDataProvider()->memoryConsumptionHistory();
|
||||
const QList<double> &cpuConsumptionHistory
|
||||
= m_manager->currentDataProvider()->cpuConsumptionHistory();
|
||||
|
||||
m_chartMem->loadNewProcessData(memConsumptionHistory);
|
||||
m_chartCpu->loadNewProcessData(cpuConsumptionHistory);
|
||||
}
|
||||
};
|
||||
|
||||
if (m_comboBox->count() != 0)
|
||||
updateCharts(m_comboBox->currentIndex());
|
||||
|
||||
connect(m_comboBox, &QComboBox::currentIndexChanged, this, [updateCharts](int index) {
|
||||
updateCharts(index);
|
||||
});
|
||||
|
||||
connect(
|
||||
m_manager,
|
||||
&AppStatisticsMonitorManager::appStarted,
|
||||
this,
|
||||
[this](const QString &name, qint64 pid) {
|
||||
if (pid != m_comboBox->currentData()) {
|
||||
m_comboBox->addItem(name + " : " + QString::number(pid), pid);
|
||||
|
||||
m_chartMem->clear();
|
||||
m_chartCpu->clear();
|
||||
|
||||
m_comboBox->setCurrentIndex(m_comboBox->count() - 1);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_manager, &AppStatisticsMonitorManager::appStoped, this, [this](qint64 pid) {
|
||||
m_chartMem->addNewPoint({m_chartMem->lastPointX() + 1, 0});
|
||||
m_chartCpu->addNewPoint({m_chartCpu->lastPointX() + 1, 0});
|
||||
|
||||
const int indx = m_comboBox->findData(pid);
|
||||
if (indx != -1)
|
||||
m_comboBox->removeItem(indx);
|
||||
});
|
||||
|
||||
connect(m_manager, &AppStatisticsMonitorManager::newDataAvailable, this, [this] {
|
||||
const IDataProvider *currentDataProvider = m_manager->currentDataProvider();
|
||||
if (currentDataProvider != nullptr) {
|
||||
m_chartMem->addNewPoint(
|
||||
{(double) currentDataProvider->memoryConsumptionHistory().size(),
|
||||
currentDataProvider->memoryConsumptionLast()});
|
||||
m_chartCpu->addNewPoint(
|
||||
{(double) currentDataProvider->cpuConsumptionHistory().size(),
|
||||
currentDataProvider->cpuConsumptionLast()});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
AppStatisticsMonitorView::~AppStatisticsMonitorView() = default;
|
||||
|
||||
// AppStatisticsMonitorViewFactory
|
||||
AppStatisticsMonitorViewFactory::AppStatisticsMonitorViewFactory(
|
||||
AppStatisticsMonitorManager *appStatisticManager)
|
||||
: m_manager(appStatisticManager)
|
||||
{
|
||||
setDisplayName(("AppStatisticsMonitor"));
|
||||
setPriority(300);
|
||||
setId("AppStatisticsMonitor");
|
||||
setActivationSequence(QKeySequence("Alt+S"));
|
||||
}
|
||||
|
||||
Core::NavigationView AppStatisticsMonitorViewFactory::createWidget()
|
||||
{
|
||||
return {new AppStatisticsMonitorView(m_manager), {}};
|
||||
}
|
||||
|
||||
} // namespace AppStatisticsMonitor::Internal
|
||||
60
src/plugins/appstatisticsmonitor/manager.h
Normal file
60
src/plugins/appstatisticsmonitor/manager.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "chart.h"
|
||||
|
||||
#include "idataprovider.h"
|
||||
|
||||
#include <coreplugin/inavigationwidgetfactory.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
|
||||
namespace Core { class IContext; }
|
||||
namespace ProjectExplorer { class RunControl; }
|
||||
|
||||
namespace AppStatisticsMonitor::Internal {
|
||||
|
||||
class AppStatisticsMonitorManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AppStatisticsMonitorManager();
|
||||
|
||||
QString nameForPid(qint64 pid) const;
|
||||
QHash<qint64, QString> pidNameMap() const;
|
||||
|
||||
double memoryConsumption(qint64 pid) const;
|
||||
double cpuConsumption(qint64 pid) const;
|
||||
|
||||
IDataProvider *currentDataProvider() const;
|
||||
void setCurrentDataProvider(qint64 pid);
|
||||
|
||||
signals:
|
||||
void newDataAvailable();
|
||||
void appStarted(const QString &name, qint64 pid);
|
||||
void appStoped(qint64 pid);
|
||||
|
||||
private:
|
||||
QHash<qint64, QString> m_pidNameMap;
|
||||
QHash<ProjectExplorer::RunControl *, int> m_rcPidMap;
|
||||
|
||||
QMap<qint64, IDataProvider *> m_pidDataProviders;
|
||||
IDataProvider *m_currentDataProvider;
|
||||
};
|
||||
|
||||
class AppStatisticsMonitorViewFactory : public Core::INavigationWidgetFactory
|
||||
{
|
||||
public:
|
||||
AppStatisticsMonitorViewFactory(AppStatisticsMonitorManager *appStatisticManager);
|
||||
|
||||
private:
|
||||
Core::NavigationView createWidget() override;
|
||||
|
||||
AppStatisticsMonitorManager *m_manager;
|
||||
};
|
||||
|
||||
} // namespace AppStatisticsMonitor::Internal
|
||||
@@ -12,7 +12,11 @@
|
||||
"",
|
||||
"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."
|
||||
],
|
||||
"Description" : "Auto Test plugin.",
|
||||
"Url" : "http://www.qt.io",
|
||||
"Description" : "Create code based tests and build system based tests.",
|
||||
"LongDescription" : [
|
||||
"Code based testing offers special handling for particular testing frameworks that strongly ties to the underlying code models or specialized parsers.",
|
||||
"Build system based testing is independent from any testing frameworks. It retrieves information directly from the underlying build system and uses it or even the build system as such to execute the respective tests."
|
||||
],
|
||||
"Url" : "https://www.qt.io",
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
|
||||
@@ -472,6 +472,21 @@ void popupResultsPane()
|
||||
dd->m_resultsPane->popup(Core::IOutputPane::NoModeSwitch);
|
||||
}
|
||||
|
||||
QString wildcardPatternFromString(const QString &original)
|
||||
{
|
||||
QString pattern = original;
|
||||
pattern.replace('\\', "\\\\");
|
||||
pattern.replace('.', "\\.");
|
||||
pattern.replace('^', "\\^").replace('$', "\\$");
|
||||
pattern.replace('(', "\\(").replace(')', "\\)");
|
||||
pattern.replace('[', "\\[").replace(']', "\\]");
|
||||
pattern.replace('{', "\\{").replace('}', "\\}");
|
||||
pattern.replace('+', "\\+");
|
||||
pattern.replace('*', ".*");
|
||||
pattern.replace('?', '.');
|
||||
return pattern;
|
||||
}
|
||||
|
||||
bool ChoicePair::matches(const ProjectExplorer::RunConfiguration *rc) const
|
||||
{
|
||||
return rc && rc->displayName() == displayName && rc->runnable().command.executable() == executable;
|
||||
|
||||
@@ -33,6 +33,6 @@ void cacheRunConfigChoice(const QString &buildTargetKey, const ChoicePair &choic
|
||||
ChoicePair cachedChoiceFor(const QString &buildTargetKey);
|
||||
void clearChoiceCache();
|
||||
void popupResultsPane();
|
||||
|
||||
QString wildcardPatternFromString(const QString &original);
|
||||
|
||||
} // Autotest::Internal
|
||||
|
||||
@@ -34,6 +34,7 @@ CTestTool::CTestTool()
|
||||
setId("AutoTest.Framework.CTest");
|
||||
setDisplayName(Tr::tr("CTest"));
|
||||
|
||||
// clang-format off
|
||||
setLayouter([this] {
|
||||
return Row { Form {
|
||||
outputOnFail, br,
|
||||
@@ -41,20 +42,21 @@ CTestTool::CTestTool()
|
||||
stopOnFailure, br,
|
||||
outputMode, br,
|
||||
Group {
|
||||
title(Tr::tr("Repeat tests")),
|
||||
repeat.groupChecker(),
|
||||
title(Tr::tr("Repeat Tests")),
|
||||
groupChecker(repeat.groupChecker()),
|
||||
Row { repetitionMode, repetitionCount},
|
||||
}, br,
|
||||
Group {
|
||||
title(Tr::tr("Run in parallel")),
|
||||
parallel.groupChecker(),
|
||||
title(Tr::tr("Run in Parallel")),
|
||||
groupChecker(parallel.groupChecker()),
|
||||
Column {
|
||||
Row { jobs }, br,
|
||||
Row { testLoad, threshold}
|
||||
Row { testLoad, threshold }
|
||||
}
|
||||
}
|
||||
}, st };
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
outputOnFail.setSettingsKey("OutputOnFail");
|
||||
outputOnFail.setLabelText(Tr::tr("Output on failure"));
|
||||
|
||||
@@ -87,9 +87,13 @@ QList<ITestConfiguration *> CTestTreeItem::testConfigurationsFor(const QStringLi
|
||||
return {};
|
||||
|
||||
const ProjectExplorer::BuildSystem *buildSystem = target->buildSystem();
|
||||
QStringList options{"--timeout", QString::number(testSettings().timeout() / 1000)};
|
||||
QStringList options;
|
||||
if (testSettings().useTimeout()) {
|
||||
options << "--timeout"
|
||||
<< QString::number(testSettings().timeout() / 1000);
|
||||
}
|
||||
options << theCTestTool().activeSettingsAsOptions();
|
||||
CommandLine command = buildSystem->commandLineForTests(selected, options);
|
||||
const CommandLine command = buildSystem->commandLineForTests(selected, options);
|
||||
if (command.executable().isEmpty())
|
||||
return {};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "gtestframework.h"
|
||||
#include "gtestparser.h"
|
||||
#include "../autotesttr.h"
|
||||
#include "../autotestplugin.h"
|
||||
|
||||
#include <cppeditor/cppmodelmanager.h>
|
||||
|
||||
@@ -56,20 +57,6 @@ TestTreeItem *GTestTreeItem::copyWithoutChildren()
|
||||
return copied;
|
||||
}
|
||||
|
||||
static QString wildCardPattern(const QString &original)
|
||||
{
|
||||
QString pattern = original;
|
||||
pattern.replace('.', "\\.");
|
||||
pattern.replace('$', "\\$");
|
||||
pattern.replace('(', "\\(").replace(')', "\\)");
|
||||
pattern.replace('[', "\\[").replace(']', "\\]");
|
||||
pattern.replace('{', "\\{").replace('}', "\\}");
|
||||
pattern.replace('+', "\\+");
|
||||
pattern.replace('*', ".*");
|
||||
pattern.replace('?', '.');
|
||||
return pattern;
|
||||
}
|
||||
|
||||
static bool matchesFilter(const QString &filter, const QString &fullTestName)
|
||||
{
|
||||
QStringList positive;
|
||||
@@ -87,12 +74,12 @@ static bool matchesFilter(const QString &filter, const QString &fullTestName)
|
||||
testName.append('.');
|
||||
|
||||
for (const QString &curr : negative) {
|
||||
QRegularExpression regex(wildCardPattern(curr));
|
||||
QRegularExpression regex(wildcardPatternFromString(curr));
|
||||
if (regex.match(testName).hasMatch())
|
||||
return false;
|
||||
}
|
||||
for (const QString &curr : positive) {
|
||||
QRegularExpression regex(wildCardPattern(curr));
|
||||
QRegularExpression regex(wildcardPatternFromString(curr));
|
||||
if (regex.match(testName).hasMatch())
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "autotestconstants.h"
|
||||
#include "autotestplugin.h"
|
||||
#include "autotesttr.h"
|
||||
#include "testcodeparser.h"
|
||||
#include "testprojectsettings.h"
|
||||
#include "testtreemodel.h"
|
||||
|
||||
@@ -13,10 +14,12 @@
|
||||
#include <projectexplorer/projectsettingswidget.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/aspects.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QTreeWidget>
|
||||
|
||||
@@ -37,10 +40,13 @@ public:
|
||||
private:
|
||||
void populateFrameworks(const QHash<Autotest::ITestFramework *, bool> &frameworks,
|
||||
const QHash<Autotest::ITestTool *, bool> &testTools);
|
||||
void populatePathFilters(const QStringList &filters);
|
||||
void onActiveFrameworkChanged(QTreeWidgetItem *item, int column);
|
||||
TestProjectSettings *m_projectSettings;
|
||||
QTreeWidget *m_activeFrameworks = nullptr;
|
||||
QComboBox *m_runAfterBuild = nullptr;
|
||||
Utils::BoolAspect m_applyFilter;
|
||||
QTreeWidget *m_pathFilters = nullptr;
|
||||
QTimer m_syncTimer;
|
||||
int m_syncType = 0;
|
||||
};
|
||||
@@ -59,7 +65,16 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
|
||||
m_runAfterBuild->addItem(Tr::tr("All"));
|
||||
m_runAfterBuild->addItem(Tr::tr("Selected"));
|
||||
m_runAfterBuild->setCurrentIndex(int(m_projectSettings->runAfterBuild()));
|
||||
m_applyFilter.setToolTip(Tr::tr("Apply path filters before scanning for tests."));
|
||||
m_pathFilters = new QTreeWidget;
|
||||
m_pathFilters->setHeaderHidden(true);
|
||||
m_pathFilters->setRootIsDecorated(false);
|
||||
QLabel *filterLabel = new QLabel(Tr::tr("Wildcard expressions for filtering:"), this);
|
||||
QPushButton *addFilter = new QPushButton(Tr::tr("Add"), this);
|
||||
QPushButton *removeFilter = new QPushButton(Tr::tr("Remove"), this);
|
||||
removeFilter->setEnabled(false);
|
||||
|
||||
// clang-format off
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
Widget {
|
||||
@@ -67,7 +82,7 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
|
||||
Column {
|
||||
Row {
|
||||
Group {
|
||||
title(Tr::tr("Active frameworks:")),
|
||||
title(Tr::tr("Active Test Frameworks")),
|
||||
Column { m_activeFrameworks },
|
||||
},
|
||||
st,
|
||||
@@ -77,18 +92,34 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
|
||||
m_runAfterBuild,
|
||||
st,
|
||||
},
|
||||
noMargin(),
|
||||
noMargin,
|
||||
},
|
||||
},
|
||||
noMargin(),
|
||||
Row { // explicitly outside of the global settings
|
||||
Group {
|
||||
title(Tr::tr("Limit Files to Path Patterns")),
|
||||
groupChecker(m_applyFilter.groupChecker()),
|
||||
Column {
|
||||
filterLabel,
|
||||
Row {
|
||||
Column { m_pathFilters },
|
||||
Column { addFilter, removeFilter, st },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
noMargin,
|
||||
}.attachTo(this);
|
||||
// clang-format on
|
||||
|
||||
generalWidget->setDisabled(m_projectSettings->useGlobalSettings());
|
||||
|
||||
populateFrameworks(m_projectSettings->activeFrameworks(),
|
||||
m_projectSettings->activeTestTools());
|
||||
|
||||
populatePathFilters(m_projectSettings->pathFilters());
|
||||
setUseGlobalSettings(m_projectSettings->useGlobalSettings());
|
||||
m_applyFilter.setValue(m_projectSettings->limitToFilters());
|
||||
connect(this, &ProjectSettingsWidget::useGlobalSettingsChanged,
|
||||
this, [this, generalWidget](bool useGlobalSettings) {
|
||||
generalWidget->setEnabled(!useGlobalSettings);
|
||||
@@ -102,6 +133,56 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
|
||||
connect(m_runAfterBuild, &QComboBox::currentIndexChanged, this, [this](int index) {
|
||||
m_projectSettings->setRunAfterBuild(RunAfterBuildMode(index));
|
||||
});
|
||||
|
||||
auto itemsToStringList = [this] {
|
||||
QStringList items;
|
||||
const QTreeWidgetItem *rootItem = m_pathFilters->invisibleRootItem();
|
||||
for (int i = 0, count = rootItem->childCount(); i < count; ++i) {
|
||||
auto expr = rootItem->child(i)->data(0, Qt::DisplayRole).toString();
|
||||
items.append(expr);
|
||||
}
|
||||
return items;
|
||||
};
|
||||
auto triggerRescan = [] {
|
||||
TestCodeParser *parser = TestTreeModel::instance()->parser();
|
||||
parser->emitUpdateTestTree();
|
||||
};
|
||||
|
||||
connect(&m_applyFilter, &Utils::BoolAspect::changed,
|
||||
this, [this, triggerRescan] {
|
||||
m_projectSettings->setLimitToFilter(m_applyFilter.value());
|
||||
triggerRescan();
|
||||
});
|
||||
connect(m_pathFilters, &QTreeWidget::itemSelectionChanged,
|
||||
this, [this, removeFilter] {
|
||||
removeFilter->setEnabled(!m_pathFilters->selectedItems().isEmpty());
|
||||
});
|
||||
connect(m_pathFilters->model(), &QAbstractItemModel::dataChanged,
|
||||
this, [this, itemsToStringList, triggerRescan]
|
||||
(const QModelIndex &tl, const QModelIndex &br, const QList<int> &roles) {
|
||||
if (!roles.contains(Qt::DisplayRole))
|
||||
return;
|
||||
if (tl != br)
|
||||
return;
|
||||
m_projectSettings->setPathFilters(itemsToStringList());
|
||||
triggerRescan();
|
||||
});
|
||||
connect(addFilter, &QPushButton::clicked, this, [this] {
|
||||
m_projectSettings->addPathFilter("*");
|
||||
populatePathFilters(m_projectSettings->pathFilters());
|
||||
const QTreeWidgetItem *root = m_pathFilters->invisibleRootItem();
|
||||
QTreeWidgetItem *lastChild = root->child(root->childCount() - 1);
|
||||
const QModelIndex index = m_pathFilters->indexFromItem(lastChild, 0);
|
||||
m_pathFilters->edit(index);
|
||||
});
|
||||
connect(removeFilter, &QPushButton::clicked, this, [this, itemsToStringList, triggerRescan] {
|
||||
const QList<QTreeWidgetItem *> selected = m_pathFilters->selectedItems();
|
||||
QTC_ASSERT(selected.size() == 1, return);
|
||||
m_pathFilters->invisibleRootItem()->removeChild(selected.first());
|
||||
delete selected.first();
|
||||
m_projectSettings->setPathFilters(itemsToStringList());
|
||||
triggerRescan();
|
||||
});
|
||||
m_syncTimer.setSingleShot(true);
|
||||
connect(&m_syncTimer, &QTimer::timeout, this, [this] {
|
||||
auto testTreeModel = TestTreeModel::instance();
|
||||
@@ -136,6 +217,16 @@ void ProjectTestSettingsWidget::populateFrameworks(const QHash<ITestFramework *,
|
||||
generateItem(it.key(), it.value());
|
||||
}
|
||||
|
||||
void ProjectTestSettingsWidget::populatePathFilters(const QStringList &filters)
|
||||
{
|
||||
m_pathFilters->clear();
|
||||
for (const QString &filter : filters) {
|
||||
auto item = new QTreeWidgetItem(m_pathFilters, {filter});
|
||||
item->setData(0, Qt::ToolTipRole, filter);
|
||||
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectTestSettingsWidget::onActiveFrameworkChanged(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
auto id = Utils::Id::fromSetting(item->data(column, BaseIdRole));
|
||||
|
||||
@@ -137,9 +137,11 @@ Environment prepareBasicEnvironment(const Environment &env)
|
||||
result.set("QT_FORCE_STDERR_LOGGING", "1");
|
||||
result.set("QT_LOGGING_TO_CONSOLE", "1");
|
||||
}
|
||||
const int timeout = testSettings().timeout();
|
||||
if (timeout > 5 * 60 * 1000) // Qt5.5 introduced hard limit, Qt5.6.1 added env var to raise this
|
||||
result.set("QTEST_FUNCTION_TIMEOUT", QString::number(timeout));
|
||||
if (testSettings().useTimeout()) {
|
||||
const int timeout = testSettings().timeout();
|
||||
if (timeout > 5 * 60 * 1000) // Qt5.5 introduced hard limit, Qt5.6.1 added env var to raise this
|
||||
result.set("QTEST_FUNCTION_TIMEOUT", QString::number(timeout));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,34 @@ ITestConfiguration *QtTestTreeItem::testConfiguration() const
|
||||
return config;
|
||||
}
|
||||
|
||||
struct FunctionLocation {
|
||||
FunctionLocation(const QString &n, const Link &l, std::optional<Link> r = std::nullopt)
|
||||
: name(n), declaration(l), registration(r) {}
|
||||
QString name;
|
||||
Link declaration;
|
||||
std::optional<Link> registration;
|
||||
};
|
||||
|
||||
static QStringList orderedTestCases(const QList<FunctionLocation> &original)
|
||||
{
|
||||
auto compare = [](const Link &lhs, const Link &rhs) {
|
||||
if (lhs.targetLine == rhs.targetLine)
|
||||
return lhs.targetColumn - rhs.targetColumn;
|
||||
return lhs.targetLine - rhs.targetLine;
|
||||
};
|
||||
|
||||
QList<FunctionLocation> locations = original;
|
||||
Utils::sort(locations, [compare](const FunctionLocation &lhs, const FunctionLocation &rhs) {
|
||||
if (lhs.declaration.targetFilePath != rhs.declaration.targetFilePath)
|
||||
return lhs.declaration.targetFilePath < rhs.declaration.targetFilePath;
|
||||
if (auto diff = compare(lhs.declaration, rhs.declaration))
|
||||
return diff < 0;
|
||||
return compare(lhs.registration.value_or(Link{{}, INT_MAX, INT_MAX}),
|
||||
rhs.registration.value_or(Link{{}, INT_MAX, INT_MAX})) < 0;
|
||||
});
|
||||
return Utils::transform(locations, [](const FunctionLocation &loc) { return loc.name; });
|
||||
}
|
||||
|
||||
static void fillTestConfigurationsFromCheckState(const TestTreeItem *item,
|
||||
QList<ITestConfiguration *> &testConfigurations)
|
||||
{
|
||||
@@ -190,21 +218,30 @@ static void fillTestConfigurationsFromCheckState(const TestTreeItem *item,
|
||||
testConfigurations << testConfig;
|
||||
return;
|
||||
case Qt::PartiallyChecked:
|
||||
QStringList testCases;
|
||||
// we need the location for keeping the order while running
|
||||
// normal test function: declaration, data tag: location of registration
|
||||
QList<FunctionLocation> testCases;
|
||||
item->forFirstLevelChildren([&testCases](ITestTreeItem *grandChild) {
|
||||
if (grandChild->checked() == Qt::Checked) {
|
||||
testCases << grandChild->name();
|
||||
TestTreeItem *funcItem = static_cast<TestTreeItem *>(grandChild);
|
||||
const Link link(funcItem->filePath(), funcItem->line(), funcItem->column());
|
||||
testCases << FunctionLocation(funcItem->name(), link);
|
||||
} else if (grandChild->checked() == Qt::PartiallyChecked) {
|
||||
const QString funcName = grandChild->name();
|
||||
grandChild->forFirstLevelChildren([&testCases, &funcName](ITestTreeItem *dataTag) {
|
||||
if (dataTag->checked() == Qt::Checked)
|
||||
testCases << funcName + ':' + dataTag->name();
|
||||
TestTreeItem *funcItem = static_cast<TestTreeItem *>(grandChild);
|
||||
grandChild->forFirstLevelChildren([&testCases, funcItem](ITestTreeItem *dataTag) {
|
||||
if (dataTag->checked() == Qt::Checked) {
|
||||
TestTreeItem *tagItem = static_cast<TestTreeItem *>(dataTag);
|
||||
const Link decl(funcItem->filePath(), funcItem->line(), funcItem->column());
|
||||
const Link reg(tagItem->filePath(), tagItem->line(), tagItem->column());
|
||||
testCases << FunctionLocation(funcItem->name() + ':' + tagItem->name(),
|
||||
decl, reg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
testConfig = new QtTestConfiguration(item->framework());
|
||||
testConfig->setTestCases(testCases);
|
||||
testConfig->setTestCases(orderedTestCases(testCases));
|
||||
testConfig->setProjectFile(item->proFile());
|
||||
testConfig->setProject(ProjectExplorer::ProjectManager::startupProject());
|
||||
testConfig->setInternalTargets(
|
||||
@@ -222,14 +259,24 @@ static void collectFailedTestInfo(TestTreeItem *item, QList<ITestConfiguration *
|
||||
return;
|
||||
}
|
||||
QTC_ASSERT(item->type() == TestTreeItem::TestCase, return);
|
||||
QStringList testCases;
|
||||
// we need the location for keeping the order while running
|
||||
// normal test function: declaration, data tag: location of registration
|
||||
QList<FunctionLocation> testCases;
|
||||
item->forFirstLevelChildren([&testCases](ITestTreeItem *func) {
|
||||
if (func->type() == TestTreeItem::TestFunction && func->data(0, FailedRole).toBool()) {
|
||||
testCases << func->name();
|
||||
TestTreeItem *funcItem = static_cast<TestTreeItem *>(func);
|
||||
const Link link(funcItem->filePath(), funcItem->line(), funcItem->column());
|
||||
testCases << FunctionLocation(func->name(), link);
|
||||
} else {
|
||||
func->forFirstLevelChildren([&testCases, func](ITestTreeItem *dataTag) {
|
||||
if (dataTag->data(0, FailedRole).toBool())
|
||||
testCases << func->name() + ':' + dataTag->name();
|
||||
TestTreeItem *funcItem = static_cast<TestTreeItem *>(func);
|
||||
func->forFirstLevelChildren([&testCases, funcItem](ITestTreeItem *dataTag) {
|
||||
if (dataTag->data(0, FailedRole).toBool()) {
|
||||
TestTreeItem *tagItem = static_cast<TestTreeItem *>(dataTag);
|
||||
const Link decl(funcItem->filePath(), funcItem->line(), funcItem->column());
|
||||
const Link reg(tagItem->filePath(), tagItem->line(), tagItem->column());
|
||||
testCases << FunctionLocation(funcItem->name() + ':' + dataTag->name(),
|
||||
decl, reg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -237,7 +284,7 @@ static void collectFailedTestInfo(TestTreeItem *item, QList<ITestConfiguration *
|
||||
return;
|
||||
|
||||
QtTestConfiguration *testConfig = new QtTestConfiguration(item->framework());
|
||||
testConfig->setTestCases(testCases);
|
||||
testConfig->setTestCases(orderedTestCases(testCases));
|
||||
testConfig->setProjectFile(item->proFile());
|
||||
testConfig->setProject(ProjectExplorer::ProjectManager::startupProject());
|
||||
testConfig->setInternalTargets(
|
||||
@@ -307,20 +354,23 @@ QList<ITestConfiguration *> QtTestTreeItem::getTestConfigurationsForFile(const F
|
||||
if (!project || type() != Root)
|
||||
return result;
|
||||
|
||||
QHash<TestTreeItem *, QStringList> testFunctions;
|
||||
QHash<TestTreeItem *, QList<FunctionLocation>> testFunctions;
|
||||
forAllChildItems([&testFunctions, &fileName](TestTreeItem *node) {
|
||||
if (node->type() == Type::TestFunction && node->filePath() == fileName) {
|
||||
QTC_ASSERT(node->parentItem(), return);
|
||||
TestTreeItem *testCase = node->parentItem();
|
||||
QTC_ASSERT(testCase->type() == Type::TestCase, return);
|
||||
testFunctions[testCase] << node->name();
|
||||
|
||||
// we need the declaration location for keeping the order while running
|
||||
const Link link(node->filePath(), node->line(), node->column());
|
||||
testFunctions[testCase] << FunctionLocation(node->name(), link);
|
||||
}
|
||||
});
|
||||
|
||||
for (auto it = testFunctions.cbegin(), end = testFunctions.cend(); it != end; ++it) {
|
||||
TestConfiguration *tc = static_cast<TestConfiguration *>(it.key()->testConfiguration());
|
||||
QTC_ASSERT(tc, continue);
|
||||
tc->setTestCases(it.value());
|
||||
tc->setTestCases(orderedTestCases(it.value()));
|
||||
result << tc;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "testcodeparser.h"
|
||||
|
||||
#include "autotestconstants.h"
|
||||
#include "autotestplugin.h"
|
||||
#include "autotesttr.h"
|
||||
#include "testprojectsettings.h"
|
||||
#include "testsettings.h"
|
||||
#include "testtreemodel.h"
|
||||
|
||||
@@ -336,6 +338,38 @@ void TestCodeParser::scanForTests(const QSet<FilePath> &filePaths,
|
||||
emit requestRemoval(files);
|
||||
}
|
||||
|
||||
const TestProjectSettings *settings = projectSettings(project);
|
||||
if (settings->limitToFilters()) {
|
||||
qCDebug(LOG) << "Applying project path filters - currently" << files.size() << "files";
|
||||
const QStringList filters = settings->pathFilters();
|
||||
if (!filters.isEmpty()) {
|
||||
// we cannot rely on QRegularExpression::fromWildcard() as we want handle paths
|
||||
const QList<QRegularExpression> regexes
|
||||
= Utils::transform(filters, [] (const QString &filter) {
|
||||
return QRegularExpression(wildcardPatternFromString(filter));
|
||||
});
|
||||
|
||||
files = Utils::filtered(files, [®exes](const FilePath &fn) {
|
||||
for (const QRegularExpression ®ex : regexes) {
|
||||
if (!regex.isValid()) {
|
||||
qCDebug(LOG) << "Skipping invalid pattern? Pattern:" << regex.pattern();
|
||||
continue;
|
||||
}
|
||||
if (regex.match(fn.path()).hasMatch())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
qCDebug(LOG) << "After applying filters" << files.size() << "files";
|
||||
|
||||
if (files.isEmpty()) {
|
||||
qCDebug(LOG) << "No filter matched a file - canceling scan immediately";
|
||||
onFinished(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QTC_ASSERT(!(isFullParse && files.isEmpty()), onFinished(true); return);
|
||||
|
||||
// use only a single parser or all current active?
|
||||
|
||||
@@ -88,8 +88,8 @@ TestNavigationWidget::TestNavigationWidget()
|
||||
m_view->setItemDelegate(new TestTreeItemDelegate(this));
|
||||
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Window, creatorTheme()->color(Theme::InfoBarBackground));
|
||||
pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::InfoBarText));
|
||||
pal.setColor(QPalette::Window, creatorColor(Theme::InfoBarBackground));
|
||||
pal.setColor(QPalette::WindowText, creatorColor(Theme::InfoBarText));
|
||||
m_missingFrameworksWidget = new QFrame;
|
||||
m_missingFrameworksWidget->setPalette(pal);
|
||||
m_missingFrameworksWidget->setAutoFillBackground(true);
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Internal {
|
||||
static const char SK_ACTIVE_FRAMEWORKS[] = "AutoTest.ActiveFrameworks";
|
||||
static const char SK_RUN_AFTER_BUILD[] = "AutoTest.RunAfterBuild";
|
||||
static const char SK_CHECK_STATES[] = "AutoTest.CheckStates";
|
||||
static const char SK_APPLY_FILTER[] = "AutoTest.ApplyFilter";
|
||||
static const char SK_PATH_FILTERS[] = "AutoTest.PathFilters";
|
||||
|
||||
static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.projectsettings", QtWarningMsg)
|
||||
|
||||
@@ -100,6 +102,8 @@ void TestProjectSettings::load()
|
||||
m_runAfterBuild = runAfterBuild.isValid() ? RunAfterBuildMode(runAfterBuild.toInt())
|
||||
: RunAfterBuildMode::None;
|
||||
m_checkStateCache.fromSettings(m_project->namedSettings(SK_CHECK_STATES).toMap());
|
||||
m_limitToFilter = m_project->namedSettings(SK_APPLY_FILTER).toBool();
|
||||
m_pathFilters = m_project->namedSettings(SK_PATH_FILTERS).toStringList();
|
||||
}
|
||||
|
||||
void TestProjectSettings::save()
|
||||
@@ -115,6 +119,8 @@ void TestProjectSettings::save()
|
||||
m_project->setNamedSettings(SK_ACTIVE_FRAMEWORKS, activeFrameworks);
|
||||
m_project->setNamedSettings(SK_RUN_AFTER_BUILD, int(m_runAfterBuild));
|
||||
m_project->setNamedSettings(SK_CHECK_STATES, m_checkStateCache.toSettings(Qt::Checked));
|
||||
m_project->setNamedSettings(SK_APPLY_FILTER, m_limitToFilter);
|
||||
m_project->setNamedSettings(SK_PATH_FILTERS, m_pathFilters);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -31,15 +31,22 @@ public:
|
||||
QHash<ITestTool *, bool> activeTestTools() const { return m_activeTestTools; }
|
||||
void activateTestTool(const Utils::Id &id, bool activate);
|
||||
Internal::ItemDataCache<Qt::CheckState> *checkStateCache() { return &m_checkStateCache; }
|
||||
bool limitToFilters() const { return m_limitToFilter; }
|
||||
void setLimitToFilter(bool enable) { m_limitToFilter = enable; }
|
||||
const QStringList pathFilters() const { return m_pathFilters; }
|
||||
void setPathFilters(const QStringList &filters) { m_pathFilters = filters; }
|
||||
void addPathFilter(const QString &filter) { m_pathFilters.append(filter); }
|
||||
private:
|
||||
void load();
|
||||
void save();
|
||||
|
||||
ProjectExplorer::Project *m_project;
|
||||
bool m_useGlobalSettings = true;
|
||||
bool m_limitToFilter = false;
|
||||
RunAfterBuildMode m_runAfterBuild = RunAfterBuildMode::None;
|
||||
QHash<ITestFramework *, bool> m_activeTestFrameworks;
|
||||
QHash<ITestTool *, bool> m_activeTestTools;
|
||||
QStringList m_pathFilters;
|
||||
Internal::ItemDataCache<Qt::CheckState> m_checkStateCache;
|
||||
};
|
||||
|
||||
|
||||
@@ -135,33 +135,32 @@ QColor TestResult::colorForType(const ResultType type)
|
||||
if (type >= ResultType::INTERNAL_MESSAGES_BEGIN && type <= ResultType::INTERNAL_MESSAGES_END)
|
||||
return QColor("transparent");
|
||||
|
||||
const Theme *theme = creatorTheme();
|
||||
switch (type) {
|
||||
case ResultType::Pass:
|
||||
return theme->color(Theme::OutputPanes_TestPassTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestPassTextColor);
|
||||
case ResultType::Fail:
|
||||
return theme->color(Theme::OutputPanes_TestFailTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestFailTextColor);
|
||||
case ResultType::ExpectedFail:
|
||||
return theme->color(Theme::OutputPanes_TestXFailTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestXFailTextColor);
|
||||
case ResultType::UnexpectedPass:
|
||||
return theme->color(Theme::OutputPanes_TestXPassTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestXPassTextColor);
|
||||
case ResultType::Skip:
|
||||
return theme->color(Theme::OutputPanes_TestSkipTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestSkipTextColor);
|
||||
case ResultType::MessageDebug:
|
||||
case ResultType::MessageInfo:
|
||||
return theme->color(Theme::OutputPanes_TestDebugTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestDebugTextColor);
|
||||
case ResultType::MessageWarn:
|
||||
return theme->color(Theme::OutputPanes_TestWarnTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestWarnTextColor);
|
||||
case ResultType::MessageFatal:
|
||||
case ResultType::MessageSystem:
|
||||
case ResultType::MessageError:
|
||||
return theme->color(Theme::OutputPanes_TestFatalTextColor);
|
||||
return creatorColor(Theme::OutputPanes_TestFatalTextColor);
|
||||
case ResultType::BlacklistedPass:
|
||||
case ResultType::BlacklistedFail:
|
||||
case ResultType::BlacklistedXPass:
|
||||
case ResultType::BlacklistedXFail:
|
||||
default:
|
||||
return theme->color(Theme::OutputPanes_StdOutTextColor);
|
||||
return creatorColor(Theme::OutputPanes_StdOutTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,13 +53,12 @@ void TestResultDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
const TestResult testResult = resultFilterModel->testResult(index);
|
||||
QTC_ASSERT(testResult.isValid(), painter->restore(); return);
|
||||
|
||||
const QWidget *widget = dynamic_cast<const QWidget*>(painter->device());
|
||||
QWindow *window = widget ? widget->window()->windowHandle() : nullptr;
|
||||
|
||||
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
|
||||
if (!icon.isNull())
|
||||
if (!icon.isNull()) {
|
||||
painter->drawPixmap(positions.left(), positions.top(),
|
||||
icon.pixmap(window, QSize(positions.iconSize(), positions.iconSize())));
|
||||
icon.pixmap(QSize(positions.iconSize(), positions.iconSize()),
|
||||
painter->device()->devicePixelRatio()));
|
||||
}
|
||||
|
||||
TestResultItem *item = resultFilterModel->itemForIndex(index);
|
||||
QTC_ASSERT(item, painter->restore(); return);
|
||||
|
||||
@@ -86,8 +86,8 @@ TestResultsPane::TestResultsPane(QObject *parent) :
|
||||
visualOutputWidget->setLayout(outputLayout);
|
||||
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Window, creatorTheme()->color(Theme::InfoBarBackground));
|
||||
pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::InfoBarText));
|
||||
pal.setColor(QPalette::Window, creatorColor(Theme::InfoBarBackground));
|
||||
pal.setColor(QPalette::WindowText, creatorColor(Theme::InfoBarText));
|
||||
m_summaryWidget = new QFrame;
|
||||
m_summaryWidget->setPalette(pal);
|
||||
m_summaryWidget->setAutoFillBackground(true);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <projectexplorer/buildmanager.h>
|
||||
#include <projectexplorer/buildsystem.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectexplorersettings.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/runconfiguration.h>
|
||||
@@ -169,11 +168,9 @@ void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &s
|
||||
|
||||
m_skipTargetsCheck = false;
|
||||
m_runMode = mode;
|
||||
const ProjectExplorerSettings projectExplorerSettings
|
||||
= ProjectExplorerPlugin::projectExplorerSettings();
|
||||
if (mode != TestRunMode::RunAfterBuild
|
||||
&& projectExplorerSettings.buildBeforeDeploy != BuildBeforeRunMode::Off
|
||||
&& !projectExplorerSettings.saveBeforeBuild) {
|
||||
&& projectExplorerSettings().buildBeforeDeploy != BuildBeforeRunMode::Off
|
||||
&& !projectExplorerSettings().saveBeforeBuild) {
|
||||
if (!ProjectExplorerPlugin::saveModifiedFiles())
|
||||
return;
|
||||
}
|
||||
@@ -203,7 +200,7 @@ void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &s
|
||||
m_targetConnect = connect(project, &Project::activeTargetChanged,
|
||||
this, [this] { cancelCurrent(KitChanged); });
|
||||
|
||||
if (projectExplorerSettings.buildBeforeDeploy == BuildBeforeRunMode::Off
|
||||
if (projectExplorerSettings().buildBeforeDeploy == BuildBeforeRunMode::Off
|
||||
|| mode == TestRunMode::DebugWithoutDeploy
|
||||
|| mode == TestRunMode::RunWithoutDeploy || mode == TestRunMode::RunAfterBuild) {
|
||||
runOrDebugTests();
|
||||
@@ -386,7 +383,7 @@ void TestRunner::runTestsHelper()
|
||||
connect(testStorage->m_outputReader.get(), &TestOutputReader::newOutputLineAvailable,
|
||||
TestResultsPane::instance(), &TestResultsPane::addOutputLine);
|
||||
|
||||
CommandLine command{config->testExecutable(), {}};
|
||||
CommandLine command{config->testExecutable()};
|
||||
if (config->testBase()->type() == ITestBase::Framework) {
|
||||
TestConfiguration *current = static_cast<TestConfiguration *>(config);
|
||||
QStringList omitted;
|
||||
@@ -415,8 +412,10 @@ void TestRunner::runTestsHelper()
|
||||
}
|
||||
process.setEnvironment(environment);
|
||||
|
||||
m_cancelTimer.setInterval(testSettings().timeout());
|
||||
m_cancelTimer.start();
|
||||
if (testSettings().useTimeout()) {
|
||||
m_cancelTimer.setInterval(testSettings().timeout());
|
||||
m_cancelTimer.start();
|
||||
}
|
||||
|
||||
qCInfo(runnerLog) << "Command:" << process.commandLine().executable();
|
||||
qCInfo(runnerLog) << "Arguments:" << process.commandLine().arguments();
|
||||
|
||||
@@ -33,13 +33,19 @@ TestSettings::TestSettings()
|
||||
scanThreadLimit.setSpecialValueText("Automatic");
|
||||
scanThreadLimit.setToolTip(Tr::tr("Number of worker threads used when scanning for tests."));
|
||||
|
||||
useTimeout.setSettingsKey("UseTimeout");
|
||||
useTimeout.setDefaultValue(false);
|
||||
useTimeout.setLabelText(Tr::tr("Timeout:"));
|
||||
useTimeout.setToolTip(Tr::tr("Use a timeout while executing test cases."));
|
||||
|
||||
timeout.setSettingsKey("Timeout");
|
||||
timeout.setDefaultValue(defaultTimeout);
|
||||
timeout.setRange(5000, 36'000'000); // 36 Mio ms = 36'000 s = 10 h
|
||||
timeout.setSuffix(Tr::tr(" s")); // we show seconds, but store milliseconds
|
||||
timeout.setDisplayScaleFactor(1000);
|
||||
timeout.setToolTip(Tr::tr("Timeout used when executing test cases. This will apply "
|
||||
"for each test case on its own, not the whole project."));
|
||||
"for each test case on its own, not the whole project. "
|
||||
"Overrides test framework or build system defaults."));
|
||||
|
||||
omitInternalMsg.setSettingsKey("OmitInternal");
|
||||
omitInternalMsg.setDefaultValue(true);
|
||||
@@ -106,6 +112,7 @@ TestSettings::TestSettings()
|
||||
|
||||
fromSettings();
|
||||
|
||||
timeout.setEnabler(&useTimeout);
|
||||
resultDescriptionMaxSize.setEnabler(&limitResultDescription);
|
||||
popupOnFail.setEnabler(&popupOnFinish);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
void fromSettings();
|
||||
|
||||
Utils::IntegerAspect scanThreadLimit{this};
|
||||
Utils::BoolAspect useTimeout{this};
|
||||
Utils::IntegerAspect timeout{this};
|
||||
Utils::BoolAspect omitInternalMsg{this};
|
||||
Utils::BoolAspect omitRunConfigWarn{this};
|
||||
|
||||
@@ -50,8 +50,6 @@ private:
|
||||
|
||||
TestSettingsWidget::TestSettingsWidget()
|
||||
{
|
||||
auto timeoutLabel = new QLabel(Tr::tr("Timeout:"));
|
||||
timeoutLabel->setToolTip(Tr::tr("Timeout used when executing each test case."));
|
||||
auto scanThreadLabel = new QLabel(Tr::tr("Scan threads:"));
|
||||
scanThreadLabel->setToolTip("Number of worker threads used when scanning for tests.");
|
||||
|
||||
@@ -77,7 +75,7 @@ TestSettingsWidget::TestSettingsWidget()
|
||||
|
||||
PushButton resetChoicesButton {
|
||||
text(Tr::tr("Reset Cached Choices")),
|
||||
tooltip(Tr::tr("Clear all cached choices of run configurations for "
|
||||
Layouting::toolTip(Tr::tr("Clear all cached choices of run configurations for "
|
||||
"tests where the executable could not be deduced.")),
|
||||
onClicked(&clearChoiceCache, this)
|
||||
};
|
||||
@@ -98,7 +96,7 @@ TestSettingsWidget::TestSettingsWidget()
|
||||
s.displayApplication,
|
||||
s.processArgs,
|
||||
Row { Tr::tr("Automatically run"), s.runAfterBuild, st },
|
||||
Row { timeoutLabel, s.timeout, st },
|
||||
Row { s.useTimeout, s.timeout, st },
|
||||
Row { resetChoicesButton, st }
|
||||
}
|
||||
};
|
||||
@@ -153,6 +151,8 @@ TestSettingsWidget::TestSettingsWidget()
|
||||
if (!changedIds.isEmpty())
|
||||
TestTreeModel::instance()->rebuild(changedIds);
|
||||
});
|
||||
|
||||
setOnCancel([] { Internal::testSettings().cancel(); });
|
||||
}
|
||||
|
||||
enum TestBaseInfo
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
"Alternatively, this plugin 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 plugin. 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."
|
||||
],
|
||||
"Category" : "Build Systems",
|
||||
"Description" : "Autotools project integration.",
|
||||
"Url" : "http://www.qt.io",
|
||||
"Description" : "Open Autotools-based projects in Qt Creator.",
|
||||
"LongDescription" : [
|
||||
"You also need:",
|
||||
"- Autotools",
|
||||
"- An Autotools-based project"
|
||||
],
|
||||
"Url" : "https://www.qt.io",
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
"Alternatively, this plugin 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 plugin. 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."
|
||||
],
|
||||
"Category" : "Code Analyzer",
|
||||
"Description" : "Integration of the axivion dashboard.",
|
||||
"Description" : "Access an Axivion dashboard server.",
|
||||
"LongDescription" : [
|
||||
"Protect software from erosion with static code analysis, architecture analysis, and code-smells-detection."
|
||||
],
|
||||
"Url" : "https://www.qt.io",
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<file>images/button-MV@2x.png</file>
|
||||
<file>images/button-SV.png</file>
|
||||
<file>images/button-SV@2x.png</file>
|
||||
<file>images/nodata.png</file>
|
||||
<file>images/nodata@2x.png</file>
|
||||
<file>images/sortAsc.png</file>
|
||||
<file>images/sortAsc@2x.png</file>
|
||||
<file>images/sortDesc.png</file>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/basetreeview.h>
|
||||
#include <utils/utilsicons.h>
|
||||
#include <utils/overlaywidget.h>
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QClipboard>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QStackedWidget>
|
||||
@@ -49,6 +51,8 @@ using namespace Utils;
|
||||
|
||||
namespace Axivion::Internal {
|
||||
|
||||
void showIssuesFromDashboard(const QString &kind); // impl at bottom
|
||||
|
||||
class DashboardWidget : public QScrollArea
|
||||
{
|
||||
public:
|
||||
@@ -145,14 +149,24 @@ void DashboardWidget::updateUi()
|
||||
}
|
||||
return prefix;
|
||||
};
|
||||
auto addValuesWidgets = [this, &toolTip](const QString &issueKind, qint64 total, qint64 added, qint64 removed, int row) {
|
||||
auto linked = [](const QString &text, const QString &href, bool link) {
|
||||
return link ? QString("<a href='%1'>%2</a>").arg(href).arg(text)
|
||||
: text;
|
||||
};
|
||||
auto addValuesWidgets = [this, &toolTip, &linked](const QString &issueKind, qint64 total,
|
||||
qint64 added, qint64 removed, int row, bool link) {
|
||||
const QString currentToolTip = toolTip(issueKind);
|
||||
QLabel *label = new QLabel(issueKind, this);
|
||||
label->setToolTip(currentToolTip);
|
||||
m_gridLayout->addWidget(label, row, 0);
|
||||
label = new QLabel(QString::number(total), this);
|
||||
label = new QLabel(linked(QString::number(total), issueKind, link), this);
|
||||
label->setToolTip(currentToolTip);
|
||||
label->setAlignment(Qt::AlignRight);
|
||||
if (link) {
|
||||
connect(label, &QLabel::linkActivated, this, [](const QString &issueKind) {
|
||||
showIssuesFromDashboard(issueKind);
|
||||
});
|
||||
}
|
||||
m_gridLayout->addWidget(label, row, 1);
|
||||
label = new QLabel(this);
|
||||
label->setPixmap(trendIcon(added, removed));
|
||||
@@ -188,12 +202,12 @@ void DashboardWidget::updateUi()
|
||||
allAdded += added;
|
||||
qint64 removed = extract_value(counts, QStringLiteral("Removed"));
|
||||
allRemoved += removed;
|
||||
addValuesWidgets(issueCount.first, total, added, removed, row);
|
||||
addValuesWidgets(issueCount.first, total, added, removed, row, true);
|
||||
++row;
|
||||
}
|
||||
}
|
||||
}
|
||||
addValuesWidgets(Tr::tr("Total:"), allTotal, allAdded, allRemoved, row);
|
||||
addValuesWidgets(Tr::tr("Total:"), allTotal, allAdded, allRemoved, row, false);
|
||||
}
|
||||
|
||||
struct LinkWithColumns
|
||||
@@ -242,7 +256,7 @@ public:
|
||||
if (!m_id.isEmpty())
|
||||
fetchIssueInfo(m_id);
|
||||
return true;
|
||||
} else if (role == BaseTreeView::ItemViewEventRole) {
|
||||
} else if (role == BaseTreeView::ItemViewEventRole && !m_id.isEmpty()) {
|
||||
ItemViewEvent ev = value.value<ItemViewEvent>();
|
||||
if (ev.as<QContextMenuEvent>())
|
||||
return issueListContextMenuEvent(ev);
|
||||
@@ -252,8 +266,8 @@ public:
|
||||
|
||||
private:
|
||||
const QString m_id;
|
||||
QStringList m_data;
|
||||
QStringList m_toolTips;
|
||||
const QStringList m_data;
|
||||
const QStringList m_toolTips;
|
||||
QList<LinkWithColumns> m_links;
|
||||
};
|
||||
|
||||
@@ -261,7 +275,7 @@ class IssuesWidget : public QScrollArea
|
||||
{
|
||||
public:
|
||||
explicit IssuesWidget(QWidget *parent = nullptr);
|
||||
void updateUi();
|
||||
void updateUi(const QString &kind);
|
||||
|
||||
const std::optional<Dto::TableInfoDto> currentTableInfo() const { return m_currentTableInfo; }
|
||||
IssueListSearch searchFromUi() const;
|
||||
@@ -274,6 +288,8 @@ private:
|
||||
void fetchTable();
|
||||
void fetchIssues(const IssueListSearch &search);
|
||||
void onFetchRequested(int startRow, int limit);
|
||||
void showNoDataOverlay();
|
||||
void hideNoDataOverlay();
|
||||
|
||||
QString m_currentPrefix;
|
||||
QString m_currentProject;
|
||||
@@ -294,6 +310,7 @@ private:
|
||||
QStringList m_userNames;
|
||||
QStringList m_versionDates;
|
||||
TaskTreeRunner m_taskTreeRunner;
|
||||
OverlayWidget *m_noDataOverlay = nullptr;
|
||||
};
|
||||
|
||||
IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
@@ -373,7 +390,7 @@ IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
setWidgetResizable(true);
|
||||
}
|
||||
|
||||
void IssuesWidget::updateUi()
|
||||
void IssuesWidget::updateUi(const QString &kind)
|
||||
{
|
||||
setFiltersEnabled(false);
|
||||
const std::optional<Dto::ProjectInfoDto> projectInfo = Internal::projectInfo();
|
||||
@@ -387,11 +404,30 @@ void IssuesWidget::updateUi()
|
||||
|
||||
setFiltersEnabled(true);
|
||||
// avoid refetching existing data
|
||||
if (!m_currentPrefix.isEmpty() || m_issuesModel->rowCount())
|
||||
if (kind.isEmpty() && (!m_currentPrefix.isEmpty() || m_issuesModel->rowCount()))
|
||||
return;
|
||||
|
||||
if (info.issueKinds.size())
|
||||
m_currentPrefix = info.issueKinds.front().prefix;
|
||||
if (!kind.isEmpty()) {
|
||||
const int index
|
||||
= Utils::indexOf( info.issueKinds, [kind](const Dto::IssueKindInfoDto &dto) {
|
||||
return dto.prefix == kind; });
|
||||
if (index != -1) {
|
||||
m_currentPrefix = kind;
|
||||
auto kindButton = m_typesButtonGroup->button(index + 1);
|
||||
if (QTC_GUARD(kindButton))
|
||||
kindButton->setChecked(true);
|
||||
// reset filters - if kind is not empty we get triggered from dashboard overview
|
||||
if (!m_userNames.isEmpty())
|
||||
m_ownerFilter->setCurrentIndex(0);
|
||||
m_pathGlobFilter->clear();
|
||||
if (m_versionDates.size() > 1) {
|
||||
m_versionStart->setCurrentIndex(m_versionDates.count() - 1);
|
||||
m_versionEnd->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_currentPrefix.isEmpty())
|
||||
m_currentPrefix = info.issueKinds.size() ? info.issueKinds.front().prefix : QString{};
|
||||
fetchTable();
|
||||
}
|
||||
|
||||
@@ -520,6 +556,8 @@ void IssuesWidget::addIssues(const Dto::IssueTableDto &dto, int startRow)
|
||||
items.append(it);
|
||||
}
|
||||
m_issuesModel->setItems(items);
|
||||
if (items.isEmpty() && m_totalRowCount == 0)
|
||||
showNoDataOverlay();
|
||||
}
|
||||
|
||||
void IssuesWidget::onSearchParameterChanged()
|
||||
@@ -677,6 +715,7 @@ void IssuesWidget::fetchTable()
|
||||
|
||||
void IssuesWidget::fetchIssues(const IssueListSearch &search)
|
||||
{
|
||||
hideNoDataOverlay();
|
||||
const auto issuesHandler = [this, startRow = search.offset](const Dto::IssueTableDto &dto) {
|
||||
addIssues(dto, startRow);
|
||||
};
|
||||
@@ -696,6 +735,35 @@ void IssuesWidget::onFetchRequested(int startRow, int limit)
|
||||
fetchIssues(search);
|
||||
}
|
||||
|
||||
void IssuesWidget::showNoDataOverlay()
|
||||
{
|
||||
if (!m_noDataOverlay) {
|
||||
QTC_ASSERT(m_issuesView, return);
|
||||
m_noDataOverlay = new OverlayWidget(this);
|
||||
m_noDataOverlay->setPaintFunction([](QWidget *that, QPainter &p, QPaintEvent *) {
|
||||
static const QIcon icon = Icon({{":/axivion/images/nodata.png",
|
||||
Theme::IconsDisabledColor}},
|
||||
Utils::Icon::Tint).icon();
|
||||
QRect iconRect(0, 0, 32, 32);
|
||||
iconRect.moveCenter(that->rect().center());
|
||||
icon.paint(&p, iconRect);
|
||||
p.save();
|
||||
p.setPen(Utils::creatorColor(Theme::TextColorDisabled));
|
||||
p.drawText(iconRect.bottomRight() + QPoint{10, p.fontMetrics().height() / 2 - 16},
|
||||
Tr::tr("No Data"));
|
||||
p.restore();
|
||||
});
|
||||
m_noDataOverlay->attachToWidget(m_issuesView);
|
||||
}
|
||||
m_noDataOverlay->show();
|
||||
}
|
||||
|
||||
void IssuesWidget::hideNoDataOverlay()
|
||||
{
|
||||
if (m_noDataOverlay)
|
||||
m_noDataOverlay->hide();
|
||||
}
|
||||
|
||||
class AxivionOutputPane final : public IOutputPane
|
||||
{
|
||||
public:
|
||||
@@ -713,7 +781,7 @@ public:
|
||||
m_outputWidget->addWidget(issuesWidget);
|
||||
|
||||
QPalette pal = m_outputWidget->palette();
|
||||
pal.setColor(QPalette::Window, creatorTheme()->color(Theme::Color::BackgroundColorNormal));
|
||||
pal.setColor(QPalette::Window, creatorColor(Theme::Color::BackgroundColorNormal));
|
||||
m_outputWidget->setPalette(pal);
|
||||
|
||||
m_showDashboard = new QToolButton(m_outputWidget);
|
||||
@@ -730,12 +798,7 @@ public:
|
||||
m_showIssues->setIcon(Icons::ZOOM_TOOLBAR.icon());
|
||||
m_showIssues->setToolTip(Tr::tr("Search for issues"));
|
||||
m_showIssues->setCheckable(true);
|
||||
connect(m_showIssues, &QToolButton::clicked, this, [this] {
|
||||
QTC_ASSERT(m_outputWidget, return);
|
||||
m_outputWidget->setCurrentIndex(1);
|
||||
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
|
||||
issues->updateUi();
|
||||
});
|
||||
connect(m_showIssues, &QToolButton::clicked, this, [this] { handleShowIssues({}); });
|
||||
auto *butonGroup = new QButtonGroup(this);
|
||||
butonGroup->addButton(m_showDashboard);
|
||||
butonGroup->addButton(m_showIssues);
|
||||
@@ -777,6 +840,14 @@ public:
|
||||
void goToNext() final {}
|
||||
void goToPrev() final {}
|
||||
|
||||
void handleShowIssues(const QString &kind)
|
||||
{
|
||||
QTC_ASSERT(m_outputWidget, return);
|
||||
m_outputWidget->setCurrentIndex(1);
|
||||
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
|
||||
issues->updateUi(kind);
|
||||
}
|
||||
|
||||
void updateDashboard()
|
||||
{
|
||||
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
|
||||
@@ -803,35 +874,22 @@ public:
|
||||
|
||||
QUrl issueBaseUrl = info->source.resolved(baseUri).resolved(issue);
|
||||
QUrl dashboardUrl = info->source.resolved(baseUri);
|
||||
QUrlQuery baseQuery;
|
||||
IssueListSearch search = issues->searchFromUi();
|
||||
baseQuery.addQueryItem("kind", search.kind);
|
||||
if (!search.versionStart.isEmpty())
|
||||
baseQuery.addQueryItem("start", search.versionStart);
|
||||
if (!search.versionEnd.isEmpty())
|
||||
baseQuery.addQueryItem("end", search.versionEnd);
|
||||
issueBaseUrl.setQuery(baseQuery);
|
||||
if (!search.owner.isEmpty())
|
||||
baseQuery.addQueryItem("user", search.owner);
|
||||
if (!search.filter_path.isEmpty())
|
||||
baseQuery.addQueryItem("filter_any path", search.filter_path);
|
||||
if (!search.state.isEmpty())
|
||||
baseQuery.addQueryItem("state", search.state);
|
||||
dashboardUrl.setQuery(baseQuery);
|
||||
const IssueListSearch search = issues->searchFromUi();
|
||||
issueBaseUrl.setQuery(search.toUrlQuery(QueryMode::SimpleQuery));
|
||||
dashboardUrl.setQuery(search.toUrlQuery(QueryMode::FilterQuery));
|
||||
|
||||
QMenu *menu = new QMenu;
|
||||
// FIXME Tr::tr() in before QC14
|
||||
auto action = new QAction("Open issue in Dashboard", menu);
|
||||
auto action = new QAction(Tr::tr("Open Issue in Dashboard"), menu);
|
||||
menu->addAction(action);
|
||||
QObject::connect(action, &QAction::triggered, menu, [issueBaseUrl] {
|
||||
QDesktopServices::openUrl(issueBaseUrl);
|
||||
});
|
||||
action = new QAction("Open table in Dashboard", menu);
|
||||
action = new QAction(Tr::tr("Open Table in Dashboard"), menu);
|
||||
QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] {
|
||||
QDesktopServices::openUrl(dashboardUrl);
|
||||
});
|
||||
menu->addAction(action);
|
||||
action = new QAction("Copy Dashboard link to clipboard", menu);
|
||||
action = new QAction(Tr::tr("Copy Dashboard Link to Clipboard"), menu);
|
||||
QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] {
|
||||
if (auto clipboard = QGuiApplication::clipboard())
|
||||
clipboard->setText(dashboardUrl.toString());
|
||||
@@ -873,4 +931,10 @@ static bool issueListContextMenuEvent(const ItemViewEvent &ev)
|
||||
return theAxivionOutputPane->handleContextMenu(issue, ev);
|
||||
}
|
||||
|
||||
void showIssuesFromDashboard(const QString &kind)
|
||||
{
|
||||
QTC_ASSERT(theAxivionOutputPane, return);
|
||||
theAxivionOutputPane->handleShowIssues(kind);
|
||||
}
|
||||
|
||||
} // Axivion::Internal
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <coreplugin/navigationwidget.h>
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
|
||||
#include <projectexplorer/buildsystem.h>
|
||||
#include <projectexplorer/project.h>
|
||||
@@ -106,6 +105,9 @@ QString anyToSimpleString(const Dto::Any &any)
|
||||
value = anyMap.find("name");
|
||||
if (value != anyMap.end())
|
||||
return anyToSimpleString(value->second);
|
||||
value = anyMap.find("tag");
|
||||
if (value != anyMap.end())
|
||||
return anyToSimpleString(value->second);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -126,9 +128,9 @@ static QString escapeKey(const QString &string)
|
||||
return escaped.replace('\\', "\\\\").replace('@', "\\@");
|
||||
}
|
||||
|
||||
static QString credentialKey()
|
||||
static QString credentialKey(const AxivionServer &server)
|
||||
{
|
||||
return escapeKey(settings().server.username) + '@' + escapeKey(settings().server.dashboard);
|
||||
return escapeKey(server.username) + '@' + escapeKey(server.dashboard);
|
||||
}
|
||||
|
||||
template <typename DtoType>
|
||||
@@ -167,39 +169,36 @@ static DashboardInfo toDashboardInfo(const GetDtoStorage<Dto::DashboardInfoDto>
|
||||
return {dashboardStorage.url, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
|
||||
}
|
||||
|
||||
QString IssueListSearch::toQuery() const
|
||||
QUrlQuery IssueListSearch::toUrlQuery(QueryMode mode) const
|
||||
{
|
||||
if (kind.isEmpty())
|
||||
return {};
|
||||
QString result;
|
||||
result.append(QString("?kind=%1&offset=%2").arg(kind).arg(offset));
|
||||
if (limit)
|
||||
result.append(QString("&limit=%1").arg(limit));
|
||||
// TODO other params
|
||||
if (!versionStart.isEmpty()) {
|
||||
result.append(QString("&start=%1").arg(
|
||||
QString::fromUtf8(QUrl::toPercentEncoding(versionStart))));
|
||||
}
|
||||
if (!versionEnd.isEmpty()) {
|
||||
result.append(QString("&end=%1").arg(
|
||||
QString::fromUtf8(QUrl::toPercentEncoding(versionEnd))));
|
||||
}
|
||||
if (!owner.isEmpty()) {
|
||||
result.append(QString("&user=%1").arg(
|
||||
QString::fromUtf8((QUrl::toPercentEncoding(owner)))));
|
||||
}
|
||||
if (!filter_path.isEmpty()) {
|
||||
result.append(QString("&filter_any path=%1").arg(
|
||||
QString::fromUtf8(QUrl::toPercentEncoding(filter_path))));
|
||||
}
|
||||
QUrlQuery query;
|
||||
QTC_ASSERT(!kind.isEmpty(), return query);
|
||||
query.addQueryItem("kind", kind);
|
||||
if (!versionStart.isEmpty())
|
||||
query.addQueryItem("start", versionStart);
|
||||
if (!versionEnd.isEmpty())
|
||||
query.addQueryItem("end", versionEnd);
|
||||
if (mode == QueryMode::SimpleQuery)
|
||||
return query;
|
||||
|
||||
if (!owner.isEmpty())
|
||||
query.addQueryItem("user", owner);
|
||||
if (!filter_path.isEmpty())
|
||||
query.addQueryItem("filter_any path", filter_path);
|
||||
if (!state.isEmpty())
|
||||
result.append(QString("&state=%1").arg(state));
|
||||
query.addQueryItem("state", state);
|
||||
if (mode == QueryMode::FilterQuery)
|
||||
return query;
|
||||
|
||||
QTC_CHECK(mode == QueryMode::FullQuery);
|
||||
query.addQueryItem("offset", QString::number(offset));
|
||||
if (limit)
|
||||
query.addQueryItem("limit", QString::number(limit));
|
||||
if (computeTotalRowCount)
|
||||
result.append("&computeTotalRowCount=true");
|
||||
query.addQueryItem("computeTotalRowCount", "true");
|
||||
if (!sort.isEmpty())
|
||||
result.append(QString("&sort=%1").arg(
|
||||
QString::fromUtf8(QUrl::toPercentEncoding(sort))));
|
||||
return result;
|
||||
query.addQueryItem("sort", sort);
|
||||
return query;
|
||||
}
|
||||
|
||||
enum class ServerAccess { Unknown, NoAuthorization, WithAuthorization };
|
||||
@@ -225,6 +224,9 @@ signals:
|
||||
void issueDetailsChanged(const QString &issueDetailsHtml);
|
||||
|
||||
public:
|
||||
// active id used for any network communication, defaults to settings' default
|
||||
// set to projects settings' dashboard id on open project
|
||||
Id m_dashboardServerId;
|
||||
// TODO: Should be set to Unknown on server address change in settings.
|
||||
ServerAccess m_serverAccess = ServerAccess::Unknown;
|
||||
// TODO: Should be cleared on username change in settings.
|
||||
@@ -282,10 +284,10 @@ std::optional<Dto::ProjectInfoDto> projectInfo()
|
||||
|
||||
// FIXME: extend to give some details?
|
||||
// FIXME: move when curl is no more in use?
|
||||
bool handleCertificateIssue()
|
||||
bool handleCertificateIssue(const Utils::Id &serverId)
|
||||
{
|
||||
QTC_ASSERT(dd, return false);
|
||||
const QString serverHost = QUrl(settings().server.dashboard).host();
|
||||
const QString serverHost = QUrl(settings().serverForId(serverId).dashboard).host();
|
||||
if (QMessageBox::question(ICore::dialogParent(), Tr::tr("Certificate Error"),
|
||||
Tr::tr("Server certificate for %1 cannot be authenticated.\n"
|
||||
"Do you want to disable SSL verification for this server?\n"
|
||||
@@ -294,7 +296,7 @@ bool handleCertificateIssue()
|
||||
!= QMessageBox::Yes) {
|
||||
return false;
|
||||
}
|
||||
settings().server.validateCert = false;
|
||||
settings().disableCertificateValidation(serverId);
|
||||
settings().apply();
|
||||
|
||||
return true;
|
||||
@@ -310,6 +312,7 @@ AxivionPluginPrivate::AxivionPluginPrivate()
|
||||
|
||||
void AxivionPluginPrivate::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
|
||||
{
|
||||
QTC_ASSERT(dd, return);
|
||||
#if QT_CONFIG(ssl)
|
||||
const QList<QSslError::SslError> accepted{
|
||||
QSslError::CertificateNotYetValid, QSslError::CertificateExpired,
|
||||
@@ -318,7 +321,8 @@ void AxivionPluginPrivate::handleSslErrors(QNetworkReply *reply, const QList<QSs
|
||||
};
|
||||
if (Utils::allOf(errors,
|
||||
[&accepted](const QSslError &e) { return accepted.contains(e.error()); })) {
|
||||
if (!settings().server.validateCert || handleCertificateIssue())
|
||||
const bool shouldValidate = settings().serverForId(dd->m_dashboardServerId).validateCert;
|
||||
if (!shouldValidate || handleCertificateIssue(dd->m_dashboardServerId))
|
||||
reply->ignoreSslErrors(errors);
|
||||
}
|
||||
#else // ssl
|
||||
@@ -352,14 +356,20 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
|
||||
handleOpenedDocs();
|
||||
});
|
||||
const AxivionProjectSettings *projSettings = AxivionProjectSettings::projectSettings(m_project);
|
||||
switchActiveDashboardId(projSettings->dashboardId());
|
||||
fetchProjectInfo(projSettings->dashboardProjectName());
|
||||
}
|
||||
|
||||
static QUrl urlForProject(const QString &projectName)
|
||||
static QUrl constructUrl(const QString &projectName, const QString &subPath, const QUrlQuery &query)
|
||||
{
|
||||
if (!dd->m_dashboardInfo)
|
||||
return {};
|
||||
return dd->m_dashboardInfo->source.resolved(QString("api/projects/")).resolved(projectName);
|
||||
QUrl url = dd->m_dashboardInfo->source.resolved(QString("api/projects/" + projectName + '/'));
|
||||
if (!subPath.isEmpty() && QTC_GUARD(!subPath.startsWith('/')))
|
||||
url = url.resolved(subPath);
|
||||
if (!query.isEmpty())
|
||||
url.setQuery(query);
|
||||
return url;
|
||||
}
|
||||
|
||||
static constexpr int httpStatusCodeOk = 200;
|
||||
@@ -491,7 +501,6 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
|
||||
const auto deserialize = [](QPromise<expected_str<DtoType>> &promise, const QByteArray &input) {
|
||||
promise.addResult(DtoType::deserializeExpected(input));
|
||||
};
|
||||
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
|
||||
task.setConcurrentCallData(deserialize, **storage);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
@@ -542,16 +551,17 @@ static void handleCredentialError(const CredentialQuery &credential)
|
||||
|
||||
static Group authorizationRecipe()
|
||||
{
|
||||
const Id serverId = dd->m_dashboardServerId;
|
||||
const Storage<QUrl> serverUrlStorage;
|
||||
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
|
||||
const auto onUnauthorizedGroupSetup = [serverUrlStorage, unauthorizedDashboardStorage] {
|
||||
unauthorizedDashboardStorage->url = *serverUrlStorage;
|
||||
return isServerAccessEstablished() ? SetupResult::StopWithSuccess : SetupResult::Continue;
|
||||
};
|
||||
const auto onUnauthorizedDashboard = [unauthorizedDashboardStorage] {
|
||||
const auto onUnauthorizedDashboard = [unauthorizedDashboardStorage, serverId] {
|
||||
if (unauthorizedDashboardStorage->dtoData) {
|
||||
const Dto::DashboardInfoDto &dashboardInfo = *unauthorizedDashboardStorage->dtoData;
|
||||
const QString &username = settings().server.username;
|
||||
const QString &username = settings().serverForId(serverId).username;
|
||||
if (username.isEmpty()
|
||||
|| (dashboardInfo.username && *dashboardInfo.username == username)) {
|
||||
dd->m_serverAccess = ServerAccess::NoAuthorization;
|
||||
@@ -568,10 +578,10 @@ static Group authorizationRecipe()
|
||||
const auto onCredentialLoopCondition = [](int) {
|
||||
return dd->m_serverAccess == ServerAccess::WithAuthorization && !dd->m_apiToken;
|
||||
};
|
||||
const auto onGetCredentialSetup = [](CredentialQuery &credential) {
|
||||
const auto onGetCredentialSetup = [serverId](CredentialQuery &credential) {
|
||||
credential.setOperation(CredentialOperation::Get);
|
||||
credential.setService(s_axivionKeychainService);
|
||||
credential.setKey(credentialKey());
|
||||
credential.setKey(credentialKey(settings().serverForId(serverId)));
|
||||
};
|
||||
const auto onGetCredentialDone = [](const CredentialQuery &credential, DoneWith result) {
|
||||
if (result == DoneWith::Success)
|
||||
@@ -585,19 +595,21 @@ static Group authorizationRecipe()
|
||||
|
||||
const Storage<QString> passwordStorage;
|
||||
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
|
||||
const auto onPasswordGroupSetup = [serverUrlStorage, passwordStorage, dashboardStorage] {
|
||||
const auto onPasswordGroupSetup
|
||||
= [serverId, serverUrlStorage, passwordStorage, dashboardStorage] {
|
||||
if (dd->m_apiToken)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
bool ok = false;
|
||||
const AxivionServer server = settings().serverForId(serverId);
|
||||
const QString text(Tr::tr("Enter the password for:\nDashboard: %1\nUser: %2")
|
||||
.arg(settings().server.dashboard, settings().server.username));
|
||||
.arg(server.dashboard, server.username));
|
||||
*passwordStorage = QInputDialog::getText(ICore::mainWindow(),
|
||||
Tr::tr("Axivion Server Password"), text, QLineEdit::Password, {}, &ok);
|
||||
if (!ok)
|
||||
return SetupResult::StopWithError;
|
||||
|
||||
const QString credential = settings().server.username + ':' + *passwordStorage;
|
||||
const QString credential = server.username + ':' + *passwordStorage;
|
||||
dashboardStorage->credential = "Basic " + credential.toUtf8().toBase64();
|
||||
dashboardStorage->url = *serverUrlStorage;
|
||||
return SetupResult::Continue;
|
||||
@@ -624,14 +636,14 @@ static Group authorizationRecipe()
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const auto onSetCredentialSetup = [apiTokenStorage](CredentialQuery &credential) {
|
||||
const auto onSetCredentialSetup = [apiTokenStorage, serverId](CredentialQuery &credential) {
|
||||
if (!apiTokenStorage->dtoData || !apiTokenStorage->dtoData->token)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
dd->m_apiToken = apiTokenStorage->dtoData->token->toUtf8();
|
||||
credential.setOperation(CredentialOperation::Set);
|
||||
credential.setService(s_axivionKeychainService);
|
||||
credential.setKey(credentialKey());
|
||||
credential.setKey(credentialKey(settings().serverForId(serverId)));
|
||||
credential.setData(*dd->m_apiToken);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
@@ -651,7 +663,7 @@ static Group authorizationRecipe()
|
||||
dashboardStorage->url = *serverUrlStorage;
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onDeleteCredentialSetup = [dashboardStorage](CredentialQuery &credential) {
|
||||
const auto onDeleteCredentialSetup = [dashboardStorage, serverId](CredentialQuery &credential) {
|
||||
if (dashboardStorage->dtoData) {
|
||||
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
|
||||
return SetupResult::StopWithSuccess;
|
||||
@@ -661,13 +673,15 @@ static Group authorizationRecipe()
|
||||
.arg(Tr::tr("The stored ApiToken is not valid anymore, removing it.")));
|
||||
credential.setOperation(CredentialOperation::Delete);
|
||||
credential.setService(s_axivionKeychainService);
|
||||
credential.setKey(credentialKey());
|
||||
credential.setKey(credentialKey(settings().serverForId(serverId)));
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
return {
|
||||
serverUrlStorage,
|
||||
onGroupSetup([serverUrlStorage] { *serverUrlStorage = QUrl(settings().server.dashboard); }),
|
||||
onGroupSetup([serverUrlStorage, serverId] {
|
||||
*serverUrlStorage = QUrl(settings().serverForId(serverId).dashboard);
|
||||
}),
|
||||
Group {
|
||||
unauthorizedDashboardStorage,
|
||||
onGroupSetup(onUnauthorizedGroupSetup),
|
||||
@@ -765,12 +779,11 @@ Group issueTableRecipe(const IssueListSearch &search, const IssueTableHandler &h
|
||||
{
|
||||
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
|
||||
|
||||
const QString query = search.toQuery();
|
||||
const QUrlQuery query = search.toUrlQuery(QueryMode::FullQuery);
|
||||
if (query.isEmpty())
|
||||
return {}; // TODO: Call handler with unexpected?
|
||||
|
||||
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
||||
.resolved(QString("issues" + query));
|
||||
const QUrl url = constructUrl(dd->m_currentProjectInfo.value().name, "issues", query);
|
||||
return fetchDataRecipe<Dto::IssueTableDto>(url, handler);
|
||||
}
|
||||
|
||||
@@ -780,8 +793,8 @@ Group lineMarkerRecipe(const FilePath &filePath, const LineMarkerHandler &handle
|
||||
QTC_ASSERT(!filePath.isEmpty(), return {}); // TODO: Call handler with unexpected?
|
||||
|
||||
const QString fileName = QString::fromUtf8(QUrl::toPercentEncoding(filePath.path()));
|
||||
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
||||
.resolved(QString("files?filename=" + fileName));
|
||||
const QUrlQuery query({{"filename", fileName}});
|
||||
const QUrl url = constructUrl(dd->m_currentProjectInfo.value().name, "files", query);
|
||||
return fetchDataRecipe<Dto::FileViewDto>(url, handler);
|
||||
}
|
||||
|
||||
@@ -789,11 +802,9 @@ Group issueHtmlRecipe(const QString &issueId, const HtmlHandler &handler)
|
||||
{
|
||||
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
|
||||
|
||||
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
||||
.resolved(QString("issues/"))
|
||||
.resolved(QString(issueId + '/'))
|
||||
.resolved(QString("properties"));
|
||||
|
||||
const QUrl url = constructUrl(dd->m_currentProjectInfo.value().name,
|
||||
QString("issues/" + issueId + "/properties/"),
|
||||
{});
|
||||
return fetchHtmlRecipe(url, handler);
|
||||
}
|
||||
|
||||
@@ -843,8 +854,8 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
|
||||
|
||||
Group tableInfoRecipe(const QString &prefix, const TableInfoHandler &handler)
|
||||
{
|
||||
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
||||
.resolved(QString("issues_meta?kind=" + prefix));
|
||||
const QUrlQuery query({{"kind", prefix}});
|
||||
const QUrl url = constructUrl(dd->m_currentProjectInfo.value().name, "issues_meta", query);
|
||||
return fetchDataRecipe<Dto::TableInfoDto>(url, handler);
|
||||
}
|
||||
|
||||
@@ -993,6 +1004,10 @@ public:
|
||||
{
|
||||
QTC_ASSERT(dd, return {});
|
||||
QTextBrowser *browser = new QTextBrowser;
|
||||
const QString text = Tr::tr(
|
||||
"Search for issues inside the Axivion dashboard or request issue details for "
|
||||
"Axivion inline annotations to see them here.");
|
||||
browser->setText("<p style='text-align:center'>" + text + "</p>");
|
||||
browser->setOpenLinks(false);
|
||||
NavigationView view;
|
||||
view.widget = browser;
|
||||
@@ -1044,6 +1059,15 @@ void fetchIssueInfo(const QString &id)
|
||||
dd->fetchIssueInfo(id);
|
||||
}
|
||||
|
||||
void switchActiveDashboardId(const Id &toDashboardId)
|
||||
{
|
||||
QTC_ASSERT(dd, return);
|
||||
dd->m_dashboardServerId = toDashboardId;
|
||||
dd->m_serverAccess = ServerAccess::Unknown;
|
||||
dd->m_apiToken.reset();
|
||||
dd->m_dashboardInfo.reset();
|
||||
}
|
||||
|
||||
const std::optional<DashboardInfo> currentDashboardInfo()
|
||||
{
|
||||
QTC_ASSERT(dd, return std::nullopt);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "dashboard/dto.h"
|
||||
|
||||
#include <utils/expected.h>
|
||||
#include <utils/id.h>
|
||||
|
||||
#include <QHash>
|
||||
#include <QUrl>
|
||||
@@ -25,6 +26,12 @@ namespace Axivion::Internal {
|
||||
|
||||
constexpr int DefaultSearchLimit = 2048;
|
||||
|
||||
enum class QueryMode {
|
||||
SimpleQuery, // just kind and version start and end
|
||||
FilterQuery, // + all filters if available
|
||||
FullQuery // + offset, limit, computeTotalRowCount
|
||||
};
|
||||
|
||||
struct IssueListSearch
|
||||
{
|
||||
QString kind;
|
||||
@@ -38,7 +45,7 @@ struct IssueListSearch
|
||||
int limit = DefaultSearchLimit;
|
||||
bool computeTotalRowCount = false;
|
||||
|
||||
QString toQuery() const;
|
||||
QUrlQuery toUrlQuery(QueryMode mode) const;
|
||||
};
|
||||
|
||||
class DashboardInfo
|
||||
@@ -77,6 +84,7 @@ QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind);
|
||||
QString anyToSimpleString(const Dto::Any &any);
|
||||
void fetchIssueInfo(const QString &id);
|
||||
|
||||
void switchActiveDashboardId(const Utils::Id &toDashboardId);
|
||||
const std::optional<DashboardInfo> currentDashboardInfo();
|
||||
|
||||
Utils::FilePath findFileForIssuePath(const Utils::FilePath &issuePath);
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/infolabel.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QTreeWidget>
|
||||
#include <QVBoxLayout>
|
||||
@@ -30,6 +32,7 @@ using namespace Utils;
|
||||
namespace Axivion::Internal {
|
||||
|
||||
const char PSK_PROJECTNAME[] = "Axivion.ProjectName";
|
||||
const char PSK_DASHBOARDID[] = "Axivion.DashboardId";
|
||||
|
||||
// AxivionProjectSettingsHandler
|
||||
|
||||
@@ -82,11 +85,15 @@ void AxivionProjectSettings::destroyProjectSettings()
|
||||
void AxivionProjectSettings::load()
|
||||
{
|
||||
m_dashboardProjectName = m_project->namedSettings(PSK_PROJECTNAME).toString();
|
||||
m_dashboardId = Id::fromSetting(m_project->namedSettings(PSK_DASHBOARDID));
|
||||
if (!m_dashboardId.isValid())
|
||||
m_dashboardId = settings().defaultDashboardId();
|
||||
}
|
||||
|
||||
void AxivionProjectSettings::save()
|
||||
{
|
||||
m_project->setNamedSettings(PSK_PROJECTNAME, m_dashboardProjectName);
|
||||
m_project->setNamedSettings(PSK_DASHBOARDID, m_dashboardId.toSetting());
|
||||
}
|
||||
|
||||
// AxivionProjectSettingsWidget
|
||||
@@ -99,13 +106,16 @@ public:
|
||||
private:
|
||||
void fetchProjects();
|
||||
void onSettingsChanged();
|
||||
void onServerChanged();
|
||||
void linkProject();
|
||||
void unlinkProject();
|
||||
void updateUi();
|
||||
void updateEnabledStates();
|
||||
void updateServers();
|
||||
|
||||
AxivionProjectSettings *m_projectSettings = nullptr;
|
||||
QLabel *m_linkedProject = nullptr;
|
||||
QComboBox *m_dashboardServers = nullptr;
|
||||
QTreeWidget *m_dashboardProjects = nullptr;
|
||||
QPushButton *m_fetchProjects = nullptr;
|
||||
QPushButton *m_link = nullptr;
|
||||
@@ -123,6 +133,10 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
|
||||
|
||||
m_linkedProject = new QLabel(this);
|
||||
|
||||
m_dashboardServers = new QComboBox(this);
|
||||
m_dashboardServers->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
updateServers();
|
||||
|
||||
m_dashboardProjects = new QTreeWidget(this);
|
||||
m_dashboardProjects->setHeaderHidden(true);
|
||||
m_dashboardProjects->setRootIsDecorated(false);
|
||||
@@ -141,6 +155,7 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
noMargin,
|
||||
m_dashboardServers,
|
||||
m_linkedProject,
|
||||
Tr::tr("Dashboard projects:"),
|
||||
Core::ItemViewFind::createSearchableWrapper(m_dashboardProjects),
|
||||
@@ -148,6 +163,8 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
|
||||
Row { m_fetchProjects, m_link, m_unlink, st }
|
||||
}.attachTo(this);
|
||||
|
||||
connect(m_dashboardServers, &QComboBox::currentIndexChanged,
|
||||
this, &AxivionProjectSettingsWidget::onServerChanged);
|
||||
connect(m_dashboardProjects, &QTreeWidget::itemSelectionChanged,
|
||||
this, &AxivionProjectSettingsWidget::updateEnabledStates);
|
||||
connect(m_fetchProjects, &QPushButton::clicked,
|
||||
@@ -187,6 +204,23 @@ void AxivionProjectSettingsWidget::onSettingsChanged()
|
||||
{
|
||||
m_dashboardProjects->clear();
|
||||
m_infoLabel->setVisible(false);
|
||||
// check if serverId vanished - reset to default
|
||||
const Id serverId = settings().defaultDashboardId();
|
||||
if (m_projectSettings->dashboardId() != serverId) {
|
||||
m_projectSettings->setDashboardId(serverId);
|
||||
switchActiveDashboardId(serverId);
|
||||
}
|
||||
updateServers();
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void AxivionProjectSettingsWidget::onServerChanged()
|
||||
{
|
||||
m_dashboardProjects->clear();
|
||||
m_infoLabel->setVisible(false);
|
||||
const Id id = m_dashboardServers->currentData().value<AxivionServer>().id;
|
||||
m_projectSettings->setDashboardId(id);
|
||||
switchActiveDashboardId(id);
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@@ -222,11 +256,13 @@ void AxivionProjectSettingsWidget::updateUi()
|
||||
|
||||
void AxivionProjectSettingsWidget::updateEnabledStates()
|
||||
{
|
||||
const bool hasDashboardSettings = !settings().server.dashboard.isEmpty();
|
||||
const bool hasDashboardSettings = !settings().serverForId(m_projectSettings->dashboardId())
|
||||
.dashboard.isEmpty();
|
||||
const bool linked = !m_projectSettings->dashboardProjectName().isEmpty();
|
||||
const bool linkable = m_dashboardProjects->topLevelItemCount()
|
||||
&& !m_dashboardProjects->selectedItems().isEmpty();
|
||||
|
||||
m_dashboardServers->setEnabled(!linked);
|
||||
m_fetchProjects->setEnabled(hasDashboardSettings);
|
||||
m_link->setEnabled(!linked && linkable);
|
||||
m_unlink->setEnabled(linked);
|
||||
@@ -238,6 +274,20 @@ void AxivionProjectSettingsWidget::updateEnabledStates()
|
||||
}
|
||||
}
|
||||
|
||||
void AxivionProjectSettingsWidget::updateServers()
|
||||
{
|
||||
const QList<AxivionServer> available = settings().allAvailableServers();
|
||||
m_dashboardServers->clear();
|
||||
for (const AxivionServer &server : available)
|
||||
m_dashboardServers->addItem(server.displayString(), QVariant::fromValue(server));
|
||||
const Id id = m_projectSettings->dashboardId();
|
||||
const int index = Utils::indexOf(available, [&id](const AxivionServer &s) {
|
||||
return s.id == id;
|
||||
});
|
||||
if (index != -1)
|
||||
m_dashboardServers->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
class AxivionProjectPanelFactory : public ProjectPanelFactory
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utils/id.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace ProjectExplorer { class Project; }
|
||||
@@ -16,6 +18,8 @@ public:
|
||||
|
||||
void setDashboardProjectName(const QString &name) { m_dashboardProjectName = name; }
|
||||
QString dashboardProjectName() const { return m_dashboardProjectName; }
|
||||
void setDashboardId(const Utils::Id &dashboardId) { m_dashboardId = dashboardId; }
|
||||
Utils::Id dashboardId() const { return m_dashboardId; }
|
||||
|
||||
static AxivionProjectSettings *projectSettings(ProjectExplorer::Project *project);
|
||||
static void destroyProjectSettings();
|
||||
@@ -27,6 +31,8 @@ private:
|
||||
|
||||
ProjectExplorer::Project *m_project = nullptr;
|
||||
QString m_dashboardProjectName;
|
||||
// id of the dashboard in use for this project, or default from settings on initialization
|
||||
Utils::Id m_dashboardId;
|
||||
};
|
||||
|
||||
} // Axivion::Internal
|
||||
|
||||
@@ -8,14 +8,18 @@
|
||||
#include <coreplugin/dialogs/ioptionspage.h>
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/id.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/stringutils.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
@@ -72,16 +76,19 @@ static FilePath tokensFilePath()
|
||||
.pathAppended("qtcreator/axivion.json");
|
||||
}
|
||||
|
||||
static void writeTokenFile(const FilePath &filePath, const AxivionServer &server)
|
||||
static void writeTokenFile(const FilePath &filePath, const QList<AxivionServer> &servers)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
doc.setObject(server.toJson());
|
||||
QJsonArray serverArray;
|
||||
for (const AxivionServer &server : servers)
|
||||
serverArray.append(server.toJson());
|
||||
doc.setArray(serverArray);
|
||||
// FIXME error handling?
|
||||
filePath.writeFileContents(doc.toJson());
|
||||
filePath.setPermissions(QFile::ReadUser | QFile::WriteUser);
|
||||
}
|
||||
|
||||
static AxivionServer readTokenFile(const FilePath &filePath)
|
||||
static QList<AxivionServer> readTokenFile(const FilePath &filePath)
|
||||
{
|
||||
if (!filePath.exists())
|
||||
return {};
|
||||
@@ -89,9 +96,19 @@ static AxivionServer readTokenFile(const FilePath &filePath)
|
||||
if (!contents)
|
||||
return {};
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(*contents);
|
||||
if (!doc.isObject())
|
||||
if (doc.isObject()) // old approach
|
||||
return { AxivionServer::fromJson(doc.object()) };
|
||||
if (!doc.isArray())
|
||||
return {};
|
||||
return AxivionServer::fromJson(doc.object());
|
||||
|
||||
QList<AxivionServer> result;
|
||||
const QJsonArray serverArray = doc.array();
|
||||
for (const auto &serverValue : serverArray) {
|
||||
if (!serverValue.isObject())
|
||||
continue;
|
||||
result.append(AxivionServer::fromJson(serverValue.toObject()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// AxivionSetting
|
||||
@@ -110,17 +127,62 @@ AxivionSettings::AxivionSettings()
|
||||
highlightMarks.setLabelText(Tr::tr("Highlight marks"));
|
||||
highlightMarks.setToolTip(Tr::tr("Marks issues on the scroll bar."));
|
||||
highlightMarks.setDefaultValue(false);
|
||||
m_defaultServerId.setSettingsKey("DefaultDashboardId");
|
||||
AspectContainer::readSettings();
|
||||
|
||||
server = readTokenFile(tokensFilePath());
|
||||
m_allServers = readTokenFile(tokensFilePath());
|
||||
|
||||
if (m_allServers.size() == 1 && m_defaultServerId().isEmpty()) // handle settings transition
|
||||
m_defaultServerId.setValue(m_allServers.first().id.toString());
|
||||
}
|
||||
|
||||
void AxivionSettings::toSettings() const
|
||||
{
|
||||
writeTokenFile(tokensFilePath(), server);
|
||||
writeTokenFile(tokensFilePath(), m_allServers);
|
||||
AspectContainer::writeSettings();
|
||||
}
|
||||
|
||||
Id AxivionSettings::defaultDashboardId() const
|
||||
{
|
||||
return Id::fromString(m_defaultServerId());
|
||||
}
|
||||
|
||||
const AxivionServer AxivionSettings::defaultServer() const
|
||||
{
|
||||
return serverForId(defaultDashboardId());
|
||||
}
|
||||
|
||||
const AxivionServer AxivionSettings::serverForId(const Utils::Id &id) const
|
||||
{
|
||||
return Utils::findOrDefault(m_allServers, [&id](const AxivionServer &server) {
|
||||
return id == server.id;
|
||||
});
|
||||
}
|
||||
|
||||
void AxivionSettings::disableCertificateValidation(const Utils::Id &id)
|
||||
{
|
||||
const int index = Utils::indexOf(m_allServers, [&id](const AxivionServer &server) {
|
||||
return id == server.id;
|
||||
});
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
m_allServers[index].validateCert = false;
|
||||
}
|
||||
|
||||
void AxivionSettings::updateDashboardServers(const QList<AxivionServer> &other)
|
||||
{
|
||||
if (m_allServers == other)
|
||||
return;
|
||||
|
||||
const Id oldDefault = defaultDashboardId();
|
||||
if (!Utils::anyOf(other, [&oldDefault](const AxivionServer &s) { return s.id == oldDefault; }))
|
||||
m_defaultServerId.setValue(other.isEmpty() ? QString{} : other.first().id.toString());
|
||||
|
||||
m_allServers = other;
|
||||
emit changed(); // should we be more detailed? (id)
|
||||
}
|
||||
|
||||
// AxivionSettingsPage
|
||||
|
||||
// may allow some invalid, but does some minimal check for legality
|
||||
@@ -149,8 +211,7 @@ static bool isUrlValid(const QString &in)
|
||||
class DashboardSettingsWidget : public QWidget
|
||||
{
|
||||
public:
|
||||
enum Mode { Display, Edit };
|
||||
explicit DashboardSettingsWidget(Mode m, QWidget *parent, QPushButton *ok = nullptr);
|
||||
explicit DashboardSettingsWidget(QWidget *parent, QPushButton *ok = nullptr);
|
||||
|
||||
AxivionServer dashboardServer() const;
|
||||
void setDashboardServer(const AxivionServer &server);
|
||||
@@ -158,26 +219,23 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
private:
|
||||
Mode m_mode = Display;
|
||||
Id m_id;
|
||||
StringAspect m_dashboardUrl;
|
||||
StringAspect m_username;
|
||||
BoolAspect m_valid;
|
||||
};
|
||||
|
||||
DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPushButton *ok)
|
||||
DashboardSettingsWidget::DashboardSettingsWidget(QWidget *parent, QPushButton *ok)
|
||||
: QWidget(parent)
|
||||
, m_mode(mode)
|
||||
{
|
||||
auto labelStyle = mode == Display ? StringAspect::LabelDisplay : StringAspect::LineEditDisplay;
|
||||
m_dashboardUrl.setLabelText(Tr::tr("Dashboard URL:"));
|
||||
m_dashboardUrl.setDisplayStyle(labelStyle);
|
||||
m_dashboardUrl.setDisplayStyle(StringAspect::LineEditDisplay);
|
||||
m_dashboardUrl.setValidationFunction([](FancyLineEdit *edit, QString *) {
|
||||
return isUrlValid(edit->text());
|
||||
});
|
||||
|
||||
m_username.setLabelText(Tr::tr("Username:"));
|
||||
m_username.setDisplayStyle(labelStyle);
|
||||
m_username.setDisplayStyle(StringAspect::LineEditDisplay);
|
||||
m_username.setPlaceHolderText(Tr::tr("User name"));
|
||||
|
||||
using namespace Layouting;
|
||||
@@ -188,15 +246,13 @@ DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPu
|
||||
noMargin
|
||||
}.attachTo(this);
|
||||
|
||||
if (mode == Edit) {
|
||||
QTC_ASSERT(ok, return);
|
||||
auto checkValidity = [this, ok] {
|
||||
m_valid.setValue(isValid());
|
||||
ok->setEnabled(m_valid());
|
||||
};
|
||||
connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity);
|
||||
connect(&m_username, &BaseAspect::changed, this, checkValidity);
|
||||
}
|
||||
QTC_ASSERT(ok, return);
|
||||
auto checkValidity = [this, ok] {
|
||||
m_valid.setValue(isValid());
|
||||
ok->setEnabled(m_valid());
|
||||
};
|
||||
connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity);
|
||||
connect(&m_username, &BaseAspect::changed, this, checkValidity);
|
||||
}
|
||||
|
||||
AxivionServer DashboardSettingsWidget::dashboardServer() const
|
||||
@@ -205,7 +261,7 @@ AxivionServer DashboardSettingsWidget::dashboardServer() const
|
||||
if (m_id.isValid())
|
||||
result.id = m_id;
|
||||
else
|
||||
result.id = m_mode == Edit ? Id::fromName(QUuid::createUuid().toByteArray()) : m_id;
|
||||
result.id = Id::fromName(QUuid::createUuid().toByteArray());
|
||||
result.dashboard = fixUrl(m_dashboardUrl());
|
||||
result.username = m_username();
|
||||
return result;
|
||||
@@ -231,65 +287,124 @@ public:
|
||||
void apply() override;
|
||||
|
||||
private:
|
||||
void showEditServerDialog();
|
||||
void showServerDialog(bool add);
|
||||
void removeCurrentServerConfig();
|
||||
void updateDashboardServers();
|
||||
void updateEnabledStates();
|
||||
|
||||
DashboardSettingsWidget *m_dashboardDisplay = nullptr;
|
||||
QComboBox *m_dashboardServers = nullptr;
|
||||
QPushButton *m_edit = nullptr;
|
||||
QPushButton *m_remove = nullptr;
|
||||
};
|
||||
|
||||
AxivionSettingsWidget::AxivionSettingsWidget()
|
||||
{
|
||||
using namespace Layouting;
|
||||
|
||||
m_dashboardDisplay = new DashboardSettingsWidget(DashboardSettingsWidget::Display, this);
|
||||
m_dashboardDisplay->setDashboardServer(settings().server);
|
||||
m_edit = new QPushButton(Tr::tr("Edit..."), this);
|
||||
Column {
|
||||
Row {
|
||||
Form {
|
||||
m_dashboardDisplay, br
|
||||
}, st,
|
||||
Column { m_edit },
|
||||
},
|
||||
Space(10), br,
|
||||
Row { settings().highlightMarks }, st
|
||||
}.attachTo(this);
|
||||
m_dashboardServers = new QComboBox(this);
|
||||
m_dashboardServers->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
updateDashboardServers();
|
||||
|
||||
connect(m_edit, &QPushButton::clicked, this, &AxivionSettingsWidget::showEditServerDialog);
|
||||
auto addButton = new QPushButton(Tr::tr("Add..."), this);
|
||||
m_edit = new QPushButton(Tr::tr("Edit..."), this);
|
||||
m_remove = new QPushButton(Tr::tr("Remove"), this);
|
||||
Column{
|
||||
Row{
|
||||
Form{Tr::tr("Default dashboard server:"), m_dashboardServers, br},
|
||||
st,
|
||||
Column{addButton, m_edit, st, m_remove},
|
||||
},
|
||||
Space(10),
|
||||
br,
|
||||
Row{settings().highlightMarks},
|
||||
st}
|
||||
.attachTo(this);
|
||||
|
||||
connect(addButton, &QPushButton::clicked, this, [this] {
|
||||
// add an empty item unconditionally
|
||||
m_dashboardServers->addItem(Tr::tr("unset"), QVariant::fromValue(AxivionServer()));
|
||||
m_dashboardServers->setCurrentIndex(m_dashboardServers->count() - 1);
|
||||
showServerDialog(true);
|
||||
});
|
||||
connect(m_edit, &QPushButton::clicked, this, [this] { showServerDialog(false); });
|
||||
connect(m_remove, &QPushButton::clicked,
|
||||
this, &AxivionSettingsWidget::removeCurrentServerConfig);
|
||||
updateEnabledStates();
|
||||
}
|
||||
|
||||
void AxivionSettingsWidget::apply()
|
||||
{
|
||||
settings().server = m_dashboardDisplay->dashboardServer();
|
||||
emit settings().changed(); // ugly but needed
|
||||
QList<AxivionServer> servers;
|
||||
for (int i = 0, end = m_dashboardServers->count(); i < end; ++i)
|
||||
servers.append(m_dashboardServers->itemData(i).value<AxivionServer>());
|
||||
settings().updateDashboardServers(servers);
|
||||
settings().toSettings();
|
||||
}
|
||||
|
||||
void AxivionSettingsWidget::showEditServerDialog()
|
||||
void AxivionSettingsWidget::updateDashboardServers()
|
||||
{
|
||||
const AxivionServer old = m_dashboardDisplay->dashboardServer();
|
||||
m_dashboardServers->clear();
|
||||
for (const AxivionServer &server : settings().allAvailableServers())
|
||||
m_dashboardServers->addItem(server.displayString(), QVariant::fromValue(server));
|
||||
}
|
||||
|
||||
void AxivionSettingsWidget::updateEnabledStates()
|
||||
{
|
||||
const bool enabled = m_dashboardServers->count();
|
||||
m_edit->setEnabled(enabled);
|
||||
m_remove->setEnabled(enabled);
|
||||
}
|
||||
|
||||
void AxivionSettingsWidget::removeCurrentServerConfig()
|
||||
{
|
||||
const QString config = m_dashboardServers->currentData().value<AxivionServer>().displayString();
|
||||
if (QMessageBox::question(
|
||||
ICore::dialogParent(),
|
||||
Tr::tr("Remove Server Configuration"),
|
||||
Tr::tr("Remove the server configuration \"%1\"?").arg(config))
|
||||
!= QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
m_dashboardServers->removeItem(m_dashboardServers->currentIndex());
|
||||
updateEnabledStates();
|
||||
}
|
||||
|
||||
void AxivionSettingsWidget::showServerDialog(bool add)
|
||||
{
|
||||
const AxivionServer old = m_dashboardServers->currentData().value<AxivionServer>();
|
||||
QDialog d;
|
||||
d.setWindowTitle(Tr::tr("Edit Dashboard Configuration"));
|
||||
d.setWindowTitle(add ? Tr::tr("Add Dashboard Configuration")
|
||||
: Tr::tr("Edit Dashboard Configuration"));
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok, this);
|
||||
auto ok = buttons->button(QDialogButtonBox::Ok);
|
||||
auto dashboardWidget = new DashboardSettingsWidget(DashboardSettingsWidget::Edit, this, ok);
|
||||
auto dashboardWidget = new DashboardSettingsWidget(this, ok);
|
||||
dashboardWidget->setDashboardServer(old);
|
||||
layout->addWidget(dashboardWidget);
|
||||
ok->setEnabled(m_dashboardDisplay->isValid());
|
||||
ok->setEnabled(dashboardWidget->isValid());
|
||||
connect(buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, &d, &QDialog::reject);
|
||||
connect(ok, &QPushButton::clicked, &d, &QDialog::accept);
|
||||
layout->addWidget(buttons);
|
||||
d.setLayout(layout);
|
||||
d.resize(500, 200);
|
||||
|
||||
if (d.exec() != QDialog::Accepted)
|
||||
if (d.exec() != QDialog::Accepted) {
|
||||
if (add) { // if we canceled an add, remove the canceled item
|
||||
m_dashboardServers->removeItem(m_dashboardServers->currentIndex());
|
||||
updateEnabledStates();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (dashboardWidget->isValid()) {
|
||||
const AxivionServer server = dashboardWidget->dashboardServer();
|
||||
if (server != old)
|
||||
m_dashboardDisplay->setDashboardServer(server);
|
||||
if (server != old) {
|
||||
m_dashboardServers->setItemData(m_dashboardServers->currentIndex(),
|
||||
QVariant::fromValue(server));
|
||||
m_dashboardServers->setItemData(m_dashboardServers->currentIndex(),
|
||||
server.displayString(), Qt::DisplayRole);
|
||||
}
|
||||
}
|
||||
updateEnabledStates();
|
||||
}
|
||||
|
||||
// AxivionSettingsPage
|
||||
|
||||
@@ -17,12 +17,15 @@ namespace Axivion::Internal {
|
||||
class AxivionServer
|
||||
{
|
||||
public:
|
||||
QString displayString() const { return username + " @ " + dashboard; }
|
||||
bool operator==(const AxivionServer &other) const;
|
||||
bool operator!=(const AxivionServer &other) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static AxivionServer fromJson(const QJsonObject &json);
|
||||
|
||||
// id starts empty == invalid; set to generated uuid on first apply of valid dashboard config,
|
||||
// never changes afterwards
|
||||
Utils::Id id;
|
||||
QString dashboard;
|
||||
QString username;
|
||||
@@ -37,8 +40,17 @@ public:
|
||||
|
||||
void toSettings() const;
|
||||
|
||||
AxivionServer server; // shall we have more than one?
|
||||
Utils::Id defaultDashboardId() const;
|
||||
const AxivionServer defaultServer() const;
|
||||
const AxivionServer serverForId(const Utils::Id &id) const;
|
||||
void disableCertificateValidation(const Utils::Id &id);
|
||||
const QList<AxivionServer> allAvailableServers() const { return m_allServers; };
|
||||
void updateDashboardServers(const QList<AxivionServer> &other);
|
||||
|
||||
Utils::BoolAspect highlightMarks{this};
|
||||
private:
|
||||
Utils::StringAspect m_defaultServerId{this};
|
||||
QList<AxivionServer> m_allServers;
|
||||
};
|
||||
|
||||
AxivionSettings &settings();
|
||||
|
||||
@@ -73,7 +73,7 @@ QVariant DynamicListModel::data(const QModelIndex &index, int role) const
|
||||
if (role == Qt::DisplayRole && index.column() == 0)
|
||||
return Tr::tr("Fetching..."); // TODO improve/customize?
|
||||
if (role == Qt::ForegroundRole && index.column() == 0)
|
||||
return Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled);
|
||||
return Utils::creatorColor(Utils::Theme::TextColorDisabled);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
BIN
src/plugins/axivion/images/nodata.png
Normal file
BIN
src/plugins/axivion/images/nodata.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 572 B |
BIN
src/plugins/axivion/images/nodata@2x.png
Normal file
BIN
src/plugins/axivion/images/nodata@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 760 B |
@@ -14,6 +14,12 @@
|
||||
"Alternatively, this plugin 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 plugin. 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."
|
||||
],
|
||||
"Category" : "Device Support",
|
||||
"Description" : "This plugin adds a target for bare metal development.",
|
||||
"Description" : "Develop applications for bare metal devices.",
|
||||
"LongDescription" : [
|
||||
"Adds a target for bare metal development.",
|
||||
"Connect devices with debug server providers to run, debug, and analyze applications built for them.",
|
||||
"You also need:",
|
||||
"- A toolchain for bare metal development"
|
||||
],
|
||||
${IDE_PLUGIN_DEPENDENCIES}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ QString EBlinkGdbServerProvider::channelString() const
|
||||
|
||||
CommandLine EBlinkGdbServerProvider::command() const
|
||||
{
|
||||
CommandLine cmd{m_executableFile, {}};
|
||||
CommandLine cmd{m_executableFile};
|
||||
QStringList interFaceTypeStrings = {"swd", "jtag"};
|
||||
|
||||
// Obligatorily -I
|
||||
|
||||
@@ -138,11 +138,8 @@ bool GdbServerProvider::isValid() const
|
||||
bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMessage) const
|
||||
{
|
||||
QTC_ASSERT(runTool, return false);
|
||||
const RunControl *runControl = runTool->runControl();
|
||||
const auto exeAspect = runControl->aspect<ExecutableAspect>();
|
||||
QTC_ASSERT(exeAspect, return false);
|
||||
|
||||
const FilePath bin = FilePath::fromString(exeAspect->executable.path());
|
||||
const ProcessRunData runnable = runTool->runControl()->runnable();
|
||||
const FilePath bin = FilePath::fromString(runnable.command.executable().path());
|
||||
if (bin.isEmpty()) {
|
||||
errorMessage = Tr::tr("Cannot debug: Local executable is not set.");
|
||||
return false;
|
||||
@@ -155,8 +152,7 @@ bool GdbServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMessa
|
||||
|
||||
ProcessRunData inferior;
|
||||
inferior.command.setExecutable(bin);
|
||||
if (const auto argAspect = runControl->aspect<ArgumentsAspect>())
|
||||
inferior.command.setArguments(argAspect->arguments);
|
||||
inferior.command.setArguments(runnable.command.arguments());
|
||||
runTool->setInferior(inferior);
|
||||
runTool->setSymbolFile(bin);
|
||||
runTool->setStartMode(AttachToRemoteServer);
|
||||
|
||||
@@ -131,7 +131,7 @@ QString StLinkUtilGdbServerProvider::channelString() const
|
||||
|
||||
CommandLine StLinkUtilGdbServerProvider::command() const
|
||||
{
|
||||
CommandLine cmd{m_executableFile, {}};
|
||||
CommandLine cmd{m_executableFile};
|
||||
|
||||
if (m_extendedMode)
|
||||
cmd.addArg("--multi");
|
||||
|
||||
@@ -168,11 +168,7 @@ QString UvscServerProvider::channelString() const
|
||||
bool UvscServerProvider::aboutToRun(DebuggerRunTool *runTool, QString &errorMessage) const
|
||||
{
|
||||
QTC_ASSERT(runTool, return false);
|
||||
const RunControl *runControl = runTool->runControl();
|
||||
const auto exeAspect = runControl->aspect<ExecutableAspect>();
|
||||
QTC_ASSERT(exeAspect, return false);
|
||||
|
||||
const FilePath bin = exeAspect->executable;
|
||||
const FilePath bin = runTool->runControl()->runnable().command.executable();
|
||||
if (bin.isEmpty()) {
|
||||
errorMessage = Tr::tr("Cannot debug: Local executable is not set.");
|
||||
return false;
|
||||
@@ -210,10 +206,8 @@ ProjectExplorer::RunWorker *UvscServerProvider::targetRunner(RunControl *runCont
|
||||
{
|
||||
// Get uVision executable path.
|
||||
const ProcessRunData uv = DebuggerKitAspect::runnable(runControl->kit());
|
||||
CommandLine server(uv.command.executable());
|
||||
server.addArg("-j0");
|
||||
server.addArg(QStringLiteral("-s%1").arg(m_channel.port()));
|
||||
|
||||
const CommandLine server{uv.command.executable(),
|
||||
{"-j0", QStringLiteral("-s%1").arg(m_channel.port())}};
|
||||
ProcessRunData r;
|
||||
r.command = server;
|
||||
return new UvscServerProviderRunner(runControl, r);
|
||||
|
||||
@@ -117,8 +117,8 @@ OutputLineParser::Result IarParser::parseWarningOrErrorOrFatalErrorDetailsMessag
|
||||
m_expectSnippet = false;
|
||||
m_expectFilePath = false;
|
||||
LinkSpecs linkSpecs;
|
||||
addLinkSpecForAbsoluteFilePath(linkSpecs, m_lastTask.file, m_lastTask.line, match,
|
||||
FilePathIndex);
|
||||
addLinkSpecForAbsoluteFilePath(
|
||||
linkSpecs, m_lastTask.file, m_lastTask.line, m_lastTask.column, match, FilePathIndex);
|
||||
return {Status::InProgress, linkSpecs};
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user