2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2020-06-17 06:35:31 +02:00
|
|
|
|
2020-06-30 09:50:28 +03:00
|
|
|
#include "androidconfigurations.h"
|
2022-10-06 17:53:35 +02:00
|
|
|
#include "androidsdkmanager.h"
|
|
|
|
|
#include "androidtr.h"
|
2022-02-02 16:32:24 +01:00
|
|
|
#include "avdmanageroutputparser.h"
|
2023-01-23 16:34:24 +01:00
|
|
|
#include "sdkmanageroutputparser.h"
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2019-01-08 16:05:57 +01:00
|
|
|
#include <utils/algorithm.h>
|
2023-03-03 23:59:37 +01:00
|
|
|
#include <utils/asynctask.h>
|
2019-01-08 16:05:57 +01:00
|
|
|
#include <utils/qtcassert.h>
|
2020-06-17 06:35:31 +02:00
|
|
|
#include <utils/qtcprocess.h>
|
|
|
|
|
#include <utils/stringutils.h>
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2017-09-12 09:34:02 +02:00
|
|
|
#include <QFutureWatcher>
|
2017-03-30 14:47:34 +02:00
|
|
|
#include <QLoggingCategory>
|
2017-10-07 23:22:29 +02:00
|
|
|
#include <QReadWriteLock>
|
2017-09-26 15:29:44 +02:00
|
|
|
#include <QRegularExpression>
|
2017-03-30 14:47:34 +02:00
|
|
|
#include <QSettings>
|
2022-01-25 13:55:54 +01:00
|
|
|
#include <QTextCodec>
|
2017-03-30 14:47:34 +02:00
|
|
|
|
|
|
|
|
namespace {
|
2023-01-23 16:34:24 +01:00
|
|
|
Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager", QtWarningMsg)
|
|
|
|
|
const char commonArgsKey[] = "Common Arguments:";
|
2017-03-30 14:47:34 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-20 15:21:00 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2017-03-30 14:47:34 +02:00
|
|
|
namespace Android {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
|
2017-07-24 15:27:19 +02:00
|
|
|
const int sdkManagerCmdTimeoutS = 60;
|
2017-09-12 09:34:02 +02:00
|
|
|
const int sdkManagerOperationTimeoutS = 600;
|
2017-07-24 15:27:19 +02:00
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
using SdkCmdPromise = QPromise<AndroidSdkManager::OperationOutput>;
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2023-02-10 16:56:42 +01:00
|
|
|
static const QRegularExpression &assertionRegExp()
|
|
|
|
|
{
|
|
|
|
|
static const QRegularExpression theRegExp
|
|
|
|
|
(R"((\(\s*y\s*[\/\\]\s*n\s*\)\s*)(?<mark>[\:\?]))", // (y/N)?
|
|
|
|
|
QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption);
|
|
|
|
|
|
|
|
|
|
return theRegExp;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-07 23:22:29 +02:00
|
|
|
int parseProgress(const QString &out, bool &foundAssertion)
|
2017-09-12 09:34:02 +02:00
|
|
|
{
|
|
|
|
|
int progress = -1;
|
|
|
|
|
if (out.isEmpty())
|
|
|
|
|
return progress;
|
2023-01-23 16:34:24 +01:00
|
|
|
static const QRegularExpression reg("(?<progress>\\d*)%");
|
|
|
|
|
static const QRegularExpression regEndOfLine("[\\n\\r]");
|
|
|
|
|
const QStringList lines = out.split(regEndOfLine, Qt::SkipEmptyParts);
|
2017-09-12 09:34:02 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2017-10-07 23:22:29 +02:00
|
|
|
if (!foundAssertion)
|
2023-02-10 16:56:42 +01:00
|
|
|
foundAssertion = assertionRegExp().match(line).hasMatch();
|
2017-09-12 09:34:02 +02:00
|
|
|
}
|
|
|
|
|
return progress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void watcherDeleter(QFutureWatcher<void> *watcher)
|
|
|
|
|
{
|
|
|
|
|
if (!watcher->isFinished() && !watcher->isCanceled())
|
|
|
|
|
watcher->cancel();
|
|
|
|
|
|
|
|
|
|
if (!watcher->isFinished())
|
|
|
|
|
watcher->waitForFinished();
|
|
|
|
|
|
|
|
|
|
delete watcher;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 11:32:03 +03:00
|
|
|
static QString sdkRootArg(const AndroidConfig &config)
|
|
|
|
|
{
|
|
|
|
|
return "--sdk_root=" + config.sdkLocation().toString();
|
|
|
|
|
}
|
2017-03-30 14:47:34 +02:00
|
|
|
/*!
|
2017-09-12 09:34:02 +02:00
|
|
|
Runs the \c sdkmanger tool 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.
|
2017-03-30 14:47:34 +02:00
|
|
|
*/
|
2017-10-10 09:58:19 +02:00
|
|
|
static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
|
2017-09-12 09:34:02 +02:00
|
|
|
QString *output, int timeout = sdkManagerCmdTimeoutS)
|
2017-03-30 14:47:34 +02:00
|
|
|
{
|
2020-06-30 11:32:03 +03:00
|
|
|
QStringList newArgs = args;
|
|
|
|
|
newArgs.append(sdkRootArg(config));
|
2022-07-06 18:02:52 +02:00
|
|
|
qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (sync):"
|
|
|
|
|
<< CommandLine(config.sdkManagerToolPath(), newArgs)
|
|
|
|
|
.toUserOutput();
|
2021-06-22 04:33:47 +02:00
|
|
|
QtcProcess proc;
|
2021-04-30 17:50:30 +02:00
|
|
|
proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config));
|
2017-07-24 15:27:19 +02:00
|
|
|
proc.setTimeoutS(timeout);
|
|
|
|
|
proc.setTimeOutMessageBoxEnabled(true);
|
2021-05-17 12:02:42 +02:00
|
|
|
proc.setCommand({config.sdkManagerToolPath(), newArgs});
|
2022-03-02 04:12:25 +01:00
|
|
|
proc.runBlocking(EventLoopMode::On);
|
2017-09-11 14:21:17 +02:00
|
|
|
if (output)
|
2021-05-12 14:25:50 +02:00
|
|
|
*output = proc.allOutput();
|
2022-03-02 04:12:25 +01:00
|
|
|
return proc.result() == ProcessResult::FinishedWithSuccess;
|
2017-03-30 14:47:34 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 09:34:02 +02:00
|
|
|
/*!
|
|
|
|
|
Runs the \c sdkmanger tool with arguments \a args. The operation command progress is updated in
|
|
|
|
|
to the future interface \a fi and \a output is populated with command output. The command listens
|
|
|
|
|
to cancel signal emmitted by \a sdkManager and kill the commands. The command is also killed
|
|
|
|
|
after the lapse of \a timeout seconds. The function blocks the calling thread.
|
|
|
|
|
*/
|
2017-10-10 09:58:19 +02:00
|
|
|
static void sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
|
2023-03-03 23:59:37 +01:00
|
|
|
AndroidSdkManager &sdkManager, SdkCmdPromise &promise,
|
2017-09-12 09:34:02 +02:00
|
|
|
AndroidSdkManager::OperationOutput &output, double progressQuota,
|
|
|
|
|
bool interruptible = true, int timeout = sdkManagerOperationTimeoutS)
|
|
|
|
|
{
|
2020-06-30 11:32:03 +03:00
|
|
|
QStringList newArgs = args;
|
|
|
|
|
newArgs.append(sdkRootArg(config));
|
2022-07-06 18:02:52 +02:00
|
|
|
qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (async):"
|
|
|
|
|
<< CommandLine(config.sdkManagerToolPath(), newArgs)
|
|
|
|
|
.toUserOutput();
|
2023-03-03 23:59:37 +01:00
|
|
|
int offset = promise.future().progressValue();
|
2021-06-22 04:33:47 +02:00
|
|
|
QtcProcess proc;
|
2021-04-30 17:50:30 +02:00
|
|
|
proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config));
|
2017-10-07 23:22:29 +02:00
|
|
|
bool assertionFound = false;
|
2017-09-12 09:34:02 +02:00
|
|
|
proc.setTimeoutS(timeout);
|
2023-03-03 23:59:37 +01:00
|
|
|
proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &promise](const QString &out) {
|
2017-10-07 23:22:29 +02:00
|
|
|
int progressPercent = parseProgress(out, assertionFound);
|
2022-06-16 10:17:33 +02:00
|
|
|
if (assertionFound) {
|
|
|
|
|
proc.stop();
|
|
|
|
|
proc.waitForFinished();
|
|
|
|
|
}
|
2017-09-12 09:34:02 +02:00
|
|
|
if (progressPercent != -1)
|
2023-03-03 23:59:37 +01:00
|
|
|
promise.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
|
2017-09-12 09:34:02 +02:00
|
|
|
});
|
2021-05-06 08:56:42 +02:00
|
|
|
proc.setStdErrCallback([&output](const QString &err) {
|
2017-09-12 09:34:02 +02:00
|
|
|
output.stdError = err;
|
|
|
|
|
});
|
|
|
|
|
if (interruptible) {
|
2022-06-16 10:17:33 +02:00
|
|
|
QObject::connect(&sdkManager, &AndroidSdkManager::cancelActiveOperations, &proc, [&proc] {
|
|
|
|
|
proc.stop();
|
|
|
|
|
proc.waitForFinished();
|
|
|
|
|
});
|
2017-09-12 09:34:02 +02:00
|
|
|
}
|
2021-05-17 12:02:42 +02:00
|
|
|
proc.setCommand({config.sdkManagerToolPath(), newArgs});
|
2022-03-02 04:12:25 +01:00
|
|
|
proc.runBlocking(EventLoopMode::On);
|
2017-10-07 23:22:29 +02:00
|
|
|
if (assertionFound) {
|
|
|
|
|
output.success = false;
|
2022-06-17 14:17:14 +02:00
|
|
|
output.stdOutput = proc.cleanedStdOut();
|
2022-10-06 17:53:35 +02:00
|
|
|
output.stdError = Tr::tr("The operation requires user interaction. "
|
|
|
|
|
"Use the \"sdkmanager\" command-line tool.");
|
2017-10-07 23:22:29 +02:00
|
|
|
} else {
|
2022-03-02 04:12:25 +01:00
|
|
|
output.success = proc.result() == ProcessResult::FinishedWithSuccess;
|
2017-10-07 23:22:29 +02:00
|
|
|
}
|
2017-09-12 09:34:02 +02:00
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
|
|
|
|
|
class AndroidSdkManagerPrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager, const AndroidConfig &config);
|
|
|
|
|
~AndroidSdkManagerPrivate();
|
|
|
|
|
|
|
|
|
|
AndroidSdkPackageList filteredPackages(AndroidSdkPackage::PackageState state,
|
|
|
|
|
AndroidSdkPackage::PackageType type,
|
|
|
|
|
bool forceUpdate = false);
|
|
|
|
|
const AndroidSdkPackageList &allPackages(bool forceUpdate = false);
|
|
|
|
|
void refreshSdkPackages(bool forceReload = false);
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void parseCommonArguments(QPromise<QString> &promise);
|
|
|
|
|
void updateInstalled(SdkCmdPromise &fi);
|
|
|
|
|
void update(SdkCmdPromise &fi, const QStringList &install,
|
2017-09-12 09:34:02 +02:00
|
|
|
const QStringList &uninstall);
|
2023-03-03 23:59:37 +01:00
|
|
|
void checkPendingLicense(SdkCmdPromise &fi);
|
|
|
|
|
void getPendingLicense(SdkCmdPromise &fi);
|
2017-09-12 09:34:02 +02:00
|
|
|
|
|
|
|
|
void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
|
2017-10-07 23:22:29 +02:00
|
|
|
void setLicenseInput(bool acceptLicense);
|
2017-09-12 09:34:02 +02:00
|
|
|
|
|
|
|
|
std::unique_ptr<QFutureWatcher<void>, decltype(&watcherDeleter)> m_activeOperation;
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
private:
|
2017-10-07 23:22:29 +02:00
|
|
|
QByteArray getUserInput() const;
|
|
|
|
|
void clearUserInput();
|
2017-08-18 08:22:34 +02:00
|
|
|
void reloadSdkPackages();
|
|
|
|
|
void clearPackages();
|
2017-10-07 23:22:29 +02:00
|
|
|
bool onLicenseStdOut(const QString &output, bool notify,
|
2023-03-03 23:59:37 +01:00
|
|
|
AndroidSdkManager::OperationOutput &result, SdkCmdPromise &fi);
|
2017-08-18 08:22:34 +02:00
|
|
|
|
|
|
|
|
AndroidSdkManager &m_sdkManager;
|
|
|
|
|
const AndroidConfig &m_config;
|
|
|
|
|
AndroidSdkPackageList m_allPackages;
|
2019-05-28 13:49:26 +02:00
|
|
|
FilePath lastSdkManagerPath;
|
2017-10-07 23:22:29 +02:00
|
|
|
QString m_licenseTextCache;
|
|
|
|
|
QByteArray m_licenseUserInput;
|
|
|
|
|
mutable QReadWriteLock m_licenseInputLock;
|
2019-08-05 14:46:01 +02:00
|
|
|
|
|
|
|
|
public:
|
2020-06-26 00:33:43 +03:00
|
|
|
bool m_packageListingSuccessful = false;
|
2017-08-18 08:22:34 +02:00
|
|
|
};
|
|
|
|
|
|
2020-05-20 09:54:50 +02:00
|
|
|
AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config):
|
2017-08-18 08:22:34 +02:00
|
|
|
m_d(new AndroidSdkManagerPrivate(*this, config))
|
2017-03-30 14:47:34 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidSdkManager::~AndroidSdkManager()
|
|
|
|
|
{
|
2017-09-12 09:34:02 +02:00
|
|
|
cancelOperatons();
|
2017-03-30 14:47:34 +02:00
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
SdkPlatformList AndroidSdkManager::installedSdkPlatforms()
|
2017-03-30 14:47:34 +02:00
|
|
|
{
|
2017-08-18 08:22:34 +02:00
|
|
|
AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed,
|
|
|
|
|
AndroidSdkPackage::SdkPlatformPackage);
|
2018-05-29 10:59:27 +02:00
|
|
|
return Utils::static_container_cast<SdkPlatform *>(list);
|
2017-08-18 08:22:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AndroidSdkPackageList &AndroidSdkManager::allSdkPackages()
|
|
|
|
|
{
|
|
|
|
|
return m_d->allPackages();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidSdkPackageList AndroidSdkManager::installedSdkPackages()
|
|
|
|
|
{
|
|
|
|
|
return m_d->filteredPackages(AndroidSdkPackage::Installed, AndroidSdkPackage::AnyValidType);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-08 14:55:16 +02:00
|
|
|
SystemImageList AndroidSdkManager::installedSystemImages()
|
|
|
|
|
{
|
|
|
|
|
AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::AnyValidState,
|
|
|
|
|
AndroidSdkPackage::SdkPlatformPackage);
|
|
|
|
|
QList<SdkPlatform *> platforms = Utils::static_container_cast<SdkPlatform *>(list);
|
|
|
|
|
|
|
|
|
|
SystemImageList result;
|
|
|
|
|
for (SdkPlatform *platform : platforms) {
|
|
|
|
|
if (platform->systemImages().size() > 0)
|
|
|
|
|
result.append(platform->systemImages());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 16:13:23 +02:00
|
|
|
NdkList AndroidSdkManager::installedNdkPackages()
|
|
|
|
|
{
|
|
|
|
|
AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed,
|
|
|
|
|
AndroidSdkPackage::NDKPackage);
|
|
|
|
|
return Utils::static_container_cast<Ndk *>(list);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
SdkPlatform *AndroidSdkManager::latestAndroidSdkPlatform(AndroidSdkPackage::PackageState state)
|
|
|
|
|
{
|
|
|
|
|
SdkPlatform *result = nullptr;
|
|
|
|
|
const AndroidSdkPackageList list = m_d->filteredPackages(state,
|
|
|
|
|
AndroidSdkPackage::SdkPlatformPackage);
|
|
|
|
|
for (AndroidSdkPackage *p : list) {
|
|
|
|
|
auto platform = static_cast<SdkPlatform *>(p);
|
|
|
|
|
if (!result || result->apiLevel() < platform->apiLevel())
|
|
|
|
|
result = platform;
|
2017-03-30 14:47:34 +02:00
|
|
|
}
|
2017-08-18 08:22:34 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
SdkPlatformList AndroidSdkManager::filteredSdkPlatforms(int minApiLevel,
|
|
|
|
|
AndroidSdkPackage::PackageState state)
|
|
|
|
|
{
|
|
|
|
|
const AndroidSdkPackageList list = m_d->filteredPackages(state,
|
|
|
|
|
AndroidSdkPackage::SdkPlatformPackage);
|
|
|
|
|
|
|
|
|
|
SdkPlatformList result;
|
|
|
|
|
for (AndroidSdkPackage *p : list) {
|
|
|
|
|
auto platform = static_cast<SdkPlatform *>(p);
|
|
|
|
|
if (platform && platform->apiLevel() >= minApiLevel)
|
|
|
|
|
result << platform;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2017-07-24 15:27:19 +02:00
|
|
|
|
2021-10-03 22:15:33 +03:00
|
|
|
BuildToolsList AndroidSdkManager::filteredBuildTools(int minApiLevel,
|
|
|
|
|
AndroidSdkPackage::PackageState state)
|
|
|
|
|
{
|
|
|
|
|
const AndroidSdkPackageList list = m_d->filteredPackages(state,
|
|
|
|
|
AndroidSdkPackage::BuildToolsPackage);
|
|
|
|
|
BuildToolsList result;
|
|
|
|
|
for (AndroidSdkPackage *p : list) {
|
|
|
|
|
auto platform = dynamic_cast<BuildTools *>(p);
|
|
|
|
|
if (platform && platform->revision().majorVersion() >= minApiLevel)
|
|
|
|
|
result << platform;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
void AndroidSdkManager::reloadPackages(bool forceReload)
|
|
|
|
|
{
|
|
|
|
|
m_d->refreshSdkPackages(forceReload);
|
2017-03-30 14:47:34 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 09:34:02 +02:00
|
|
|
bool AndroidSdkManager::isBusy() const
|
|
|
|
|
{
|
|
|
|
|
return m_d->m_activeOperation && !m_d->m_activeOperation->isFinished();
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-05 14:46:01 +02:00
|
|
|
bool AndroidSdkManager::packageListingSuccessful() const
|
|
|
|
|
{
|
|
|
|
|
return m_d->m_packageListingSuccessful;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-27 15:26:59 +02:00
|
|
|
QFuture<QString> AndroidSdkManager::availableArguments() const
|
|
|
|
|
{
|
2023-03-03 23:59:37 +01:00
|
|
|
return Utils::asyncRun(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get());
|
2017-09-27 15:26:59 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-12 09:34:02 +02:00
|
|
|
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updateAll()
|
|
|
|
|
{
|
|
|
|
|
if (isBusy()) {
|
|
|
|
|
return QFuture<AndroidSdkManager::OperationOutput>();
|
|
|
|
|
}
|
2023-03-03 23:59:37 +01:00
|
|
|
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::updateInstalled, m_d.get());
|
2017-09-12 09:34:02 +02:00
|
|
|
m_d->addWatcher(future);
|
|
|
|
|
return future;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFuture<AndroidSdkManager::OperationOutput>
|
|
|
|
|
AndroidSdkManager::update(const QStringList &install, const QStringList &uninstall)
|
|
|
|
|
{
|
|
|
|
|
if (isBusy())
|
|
|
|
|
return QFuture<AndroidSdkManager::OperationOutput>();
|
2023-03-03 23:59:37 +01:00
|
|
|
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall);
|
2017-09-12 09:34:02 +02:00
|
|
|
m_d->addWatcher(future);
|
|
|
|
|
return future;
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-07 23:22:29 +02:00
|
|
|
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::checkPendingLicenses()
|
|
|
|
|
{
|
|
|
|
|
if (isBusy())
|
|
|
|
|
return QFuture<AndroidSdkManager::OperationOutput>();
|
2023-03-03 23:59:37 +01:00
|
|
|
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get());
|
2017-10-07 23:22:29 +02:00
|
|
|
m_d->addWatcher(future);
|
|
|
|
|
return future;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::runLicenseCommand()
|
|
|
|
|
{
|
|
|
|
|
if (isBusy())
|
|
|
|
|
return QFuture<AndroidSdkManager::OperationOutput>();
|
2023-03-03 23:59:37 +01:00
|
|
|
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get());
|
2017-10-07 23:22:29 +02:00
|
|
|
m_d->addWatcher(future);
|
|
|
|
|
return future;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-12 09:34:02 +02:00
|
|
|
void AndroidSdkManager::cancelOperatons()
|
|
|
|
|
{
|
|
|
|
|
emit cancelActiveOperations();
|
|
|
|
|
m_d->m_activeOperation.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-07 23:22:29 +02:00
|
|
|
void AndroidSdkManager::acceptSdkLicense(bool accept)
|
|
|
|
|
{
|
|
|
|
|
m_d->setLicenseInput(accept);
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager,
|
|
|
|
|
const AndroidConfig &config):
|
2017-09-12 09:34:02 +02:00
|
|
|
m_activeOperation(nullptr, watcherDeleter),
|
2017-08-18 08:22:34 +02:00
|
|
|
m_sdkManager(sdkManager),
|
|
|
|
|
m_config(config)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidSdkManagerPrivate::~AndroidSdkManagerPrivate()
|
|
|
|
|
{
|
|
|
|
|
clearPackages();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidSdkPackageList
|
|
|
|
|
AndroidSdkManagerPrivate::filteredPackages(AndroidSdkPackage::PackageState state,
|
|
|
|
|
AndroidSdkPackage::PackageType type, bool forceUpdate)
|
|
|
|
|
{
|
|
|
|
|
refreshSdkPackages(forceUpdate);
|
|
|
|
|
return Utils::filtered(m_allPackages, [state, type](const AndroidSdkPackage *p) {
|
|
|
|
|
return p->state() & state && p->type() & type;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AndroidSdkPackageList &AndroidSdkManagerPrivate::allPackages(bool forceUpdate)
|
|
|
|
|
{
|
|
|
|
|
refreshSdkPackages(forceUpdate);
|
|
|
|
|
return m_allPackages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManagerPrivate::reloadSdkPackages()
|
|
|
|
|
{
|
2019-01-16 18:06:21 +01:00
|
|
|
emit m_sdkManager.packageReloadBegin();
|
2017-08-18 08:22:34 +02:00
|
|
|
clearPackages();
|
|
|
|
|
|
|
|
|
|
lastSdkManagerPath = m_config.sdkManagerToolPath();
|
2020-06-26 00:33:43 +03:00
|
|
|
m_packageListingSuccessful = false;
|
2017-08-18 08:22:34 +02:00
|
|
|
|
|
|
|
|
if (m_config.sdkToolsVersion().isNull()) {
|
|
|
|
|
// Configuration has invalid sdk path or corrupt installation.
|
2019-01-16 18:06:21 +01:00
|
|
|
emit m_sdkManager.packageReloadFinished();
|
2017-08-18 08:22:34 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-30 09:50:28 +03:00
|
|
|
QString packageListing;
|
|
|
|
|
QStringList args({"--list", "--verbose"});
|
|
|
|
|
args << m_config.sdkManagerToolArgs();
|
|
|
|
|
m_packageListingSuccessful = sdkManagerCommand(m_config, args, &packageListing);
|
|
|
|
|
if (m_packageListingSuccessful) {
|
|
|
|
|
SdkManagerOutputParser parser(m_allPackages);
|
|
|
|
|
parser.parsePackageListing(packageListing);
|
2017-08-18 08:22:34 +02:00
|
|
|
}
|
2019-01-16 18:06:21 +01:00
|
|
|
emit m_sdkManager.packageReloadFinished();
|
2017-08-18 08:22:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload)
|
|
|
|
|
{
|
|
|
|
|
// Sdk path changed. Updated packages.
|
|
|
|
|
// QTC updates the package listing only
|
|
|
|
|
if (m_config.sdkManagerToolPath() != lastSdkManagerPath || forceReload)
|
|
|
|
|
reloadSdkPackages();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void AndroidSdkManagerPrivate::updateInstalled(SdkCmdPromise &promise)
|
2017-09-12 09:34:02 +02:00
|
|
|
{
|
2023-03-03 23:59:37 +01:00
|
|
|
promise.setProgressRange(0, 100);
|
|
|
|
|
promise.setProgressValue(0);
|
2017-09-12 09:34:02 +02:00
|
|
|
AndroidSdkManager::OperationOutput result;
|
|
|
|
|
result.type = AndroidSdkManager::UpdateAll;
|
2022-10-06 17:53:35 +02:00
|
|
|
result.stdOutput = Tr::tr("Updating installed packages.");
|
2023-03-03 23:59:37 +01:00
|
|
|
promise.addResult(result);
|
2017-09-12 09:34:02 +02:00
|
|
|
QStringList args("--update");
|
2017-09-27 15:26:59 +02:00
|
|
|
args << m_config.sdkManagerToolArgs();
|
2023-03-03 23:59:37 +01:00
|
|
|
if (!promise.isCanceled())
|
|
|
|
|
sdkManagerCommand(m_config, args, m_sdkManager, promise, result, 100);
|
2017-09-12 09:34:02 +02:00
|
|
|
else
|
|
|
|
|
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
|
|
|
|
|
|
|
|
|
|
if (result.stdError.isEmpty() && !result.success)
|
2022-10-06 17:53:35 +02:00
|
|
|
result.stdError = Tr::tr("Failed.");
|
|
|
|
|
result.stdOutput = Tr::tr("Done\n\n");
|
2023-03-03 23:59:37 +01:00
|
|
|
promise.addResult(result);
|
|
|
|
|
promise.setProgressValue(100);
|
2017-09-12 09:34:02 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void AndroidSdkManagerPrivate::update(SdkCmdPromise &fi, const QStringList &install,
|
2017-09-12 09:34:02 +02:00
|
|
|
const QStringList &uninstall)
|
|
|
|
|
{
|
|
|
|
|
fi.setProgressRange(0, 100);
|
|
|
|
|
fi.setProgressValue(0);
|
|
|
|
|
double progressQuota = 100.0 / (install.count() + uninstall.count());
|
|
|
|
|
int currentProgress = 0;
|
|
|
|
|
|
2022-10-06 17:53:35 +02:00
|
|
|
QString installTag = Tr::tr("Installing");
|
|
|
|
|
QString uninstallTag = Tr::tr("Uninstalling");
|
2017-09-12 09:34:02 +02:00
|
|
|
|
|
|
|
|
auto doOperation = [&](const QString& packagePath, const QStringList& args,
|
|
|
|
|
bool isInstall) {
|
|
|
|
|
AndroidSdkManager::OperationOutput result;
|
|
|
|
|
result.type = AndroidSdkManager::UpdatePackage;
|
|
|
|
|
result.stdOutput = QString("%1 %2").arg(isInstall ? installTag : uninstallTag)
|
|
|
|
|
.arg(packagePath);
|
2023-03-03 23:59:37 +01:00
|
|
|
fi.addResult(result);
|
2017-10-10 09:58:19 +02:00
|
|
|
if (fi.isCanceled())
|
2017-09-12 09:34:02 +02:00
|
|
|
qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start";
|
2017-10-10 09:58:19 +02:00
|
|
|
else
|
|
|
|
|
sdkManagerCommand(m_config, args, m_sdkManager, fi, result, progressQuota, isInstall);
|
2017-09-12 09:34:02 +02:00
|
|
|
currentProgress += progressQuota;
|
|
|
|
|
fi.setProgressValue(currentProgress);
|
|
|
|
|
if (result.stdError.isEmpty() && !result.success)
|
2022-10-06 17:53:35 +02:00
|
|
|
result.stdError = Tr::tr("AndroidSdkManager", "Failed");
|
|
|
|
|
result.stdOutput = Tr::tr("AndroidSdkManager", "Done\n\n");
|
2023-03-03 23:59:37 +01:00
|
|
|
fi.addResult(result);
|
2017-09-12 09:34:02 +02:00
|
|
|
return fi.isCanceled();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Uninstall packages
|
|
|
|
|
for (const QString &sdkStylePath : uninstall) {
|
|
|
|
|
// Uninstall operations are not interptible. We don't want to leave half uninstalled.
|
2017-09-27 15:26:59 +02:00
|
|
|
QStringList args;
|
|
|
|
|
args << "--uninstall" << sdkStylePath << m_config.sdkManagerToolArgs();
|
|
|
|
|
if (doOperation(sdkStylePath, args, false))
|
2017-09-12 09:34:02 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Install packages
|
|
|
|
|
for (const QString &sdkStylePath : install) {
|
2017-09-27 15:26:59 +02:00
|
|
|
QStringList args(sdkStylePath);
|
|
|
|
|
args << m_config.sdkManagerToolArgs();
|
|
|
|
|
if (doOperation(sdkStylePath, args, true))
|
2017-09-12 09:34:02 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
fi.setProgressValue(100);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdPromise &fi)
|
2017-10-07 23:22:29 +02:00
|
|
|
{
|
|
|
|
|
fi.setProgressRange(0, 100);
|
|
|
|
|
fi.setProgressValue(0);
|
|
|
|
|
AndroidSdkManager::OperationOutput result;
|
|
|
|
|
result.type = AndroidSdkManager::LicenseCheck;
|
2020-06-30 11:32:03 +03:00
|
|
|
const QStringList args = {"--licenses", sdkRootArg(m_config)};
|
2021-05-10 12:08:56 +02:00
|
|
|
if (!fi.isCanceled()) {
|
|
|
|
|
const int timeOutS = 4; // Short timeout as workaround for QTCREATORBUG-25667
|
|
|
|
|
sdkManagerCommand(m_config, args, m_sdkManager, fi, result, 100.0, true, timeOutS);
|
|
|
|
|
} else {
|
2017-10-07 23:22:29 +02:00
|
|
|
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
|
2021-05-10 12:08:56 +02:00
|
|
|
}
|
2017-10-07 23:22:29 +02:00
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
fi.addResult(result);
|
2017-10-07 23:22:29 +02:00
|
|
|
fi.setProgressValue(100);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdPromise &fi)
|
2017-10-07 23:22:29 +02:00
|
|
|
{
|
|
|
|
|
fi.setProgressRange(0, 100);
|
|
|
|
|
fi.setProgressValue(0);
|
2022-02-10 19:25:03 +01:00
|
|
|
|
2017-10-07 23:22:29 +02:00
|
|
|
AndroidSdkManager::OperationOutput result;
|
|
|
|
|
result.type = AndroidSdkManager::LicenseWorkflow;
|
2022-02-10 19:25:03 +01:00
|
|
|
|
|
|
|
|
QtcProcess licenseCommand;
|
|
|
|
|
licenseCommand.setProcessMode(ProcessMode::Writer);
|
2021-04-30 17:50:30 +02:00
|
|
|
licenseCommand.setEnvironment(AndroidConfigurations::toolsEnvironment(m_config));
|
2017-10-07 23:22:29 +02:00
|
|
|
bool reviewingLicenses = false;
|
2020-06-30 11:32:03 +03:00
|
|
|
licenseCommand.setCommand(CommandLine(m_config.sdkManagerToolPath(), {"--licenses", sdkRootArg(m_config)}));
|
2021-06-03 11:12:51 +02:00
|
|
|
licenseCommand.setUseCtrlCStub(true);
|
2017-10-07 23:22:29 +02:00
|
|
|
licenseCommand.start();
|
|
|
|
|
QTextCodec *codec = QTextCodec::codecForLocale();
|
|
|
|
|
int inputCounter = 0, steps = -1;
|
|
|
|
|
while (!licenseCommand.waitForFinished(200)) {
|
2023-01-05 17:55:04 +01:00
|
|
|
QString stdOut = codec->toUnicode(licenseCommand.readAllRawStandardOutput());
|
2017-10-07 23:22:29 +02:00
|
|
|
bool assertionFound = false;
|
|
|
|
|
if (!stdOut.isEmpty())
|
|
|
|
|
assertionFound = onLicenseStdOut(stdOut, reviewingLicenses, result, fi);
|
|
|
|
|
|
|
|
|
|
if (reviewingLicenses) {
|
|
|
|
|
// Check user input
|
|
|
|
|
QByteArray userInput = getUserInput();
|
|
|
|
|
if (!userInput.isEmpty()) {
|
|
|
|
|
clearUserInput();
|
2022-04-27 14:19:49 +02:00
|
|
|
licenseCommand.writeRaw(userInput);
|
2017-10-07 23:22:29 +02:00
|
|
|
++inputCounter;
|
|
|
|
|
if (steps != -1)
|
|
|
|
|
fi.setProgressValue(qRound((inputCounter / (double)steps) * 100));
|
|
|
|
|
}
|
|
|
|
|
} else if (assertionFound) {
|
|
|
|
|
// The first assertion is to start reviewing licenses. Always accept.
|
|
|
|
|
reviewingLicenses = true;
|
|
|
|
|
QRegularExpression reg("(\\d+\\sof\\s)(?<steps>\\d+)");
|
|
|
|
|
QRegularExpressionMatch match = reg.match(stdOut);
|
|
|
|
|
if (match.hasMatch())
|
|
|
|
|
steps = match.captured("steps").toInt();
|
|
|
|
|
licenseCommand.write("Y\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fi.isCanceled()) {
|
|
|
|
|
licenseCommand.terminate();
|
|
|
|
|
if (!licenseCommand.waitForFinished(300)) {
|
|
|
|
|
licenseCommand.kill();
|
|
|
|
|
licenseCommand.waitForFinished(200);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (licenseCommand.state() == QProcess::NotRunning)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_licenseTextCache.clear();
|
2021-05-14 15:21:54 +02:00
|
|
|
result.success = licenseCommand.exitStatus() == QProcess::NormalExit;
|
2022-10-06 17:53:35 +02:00
|
|
|
if (!result.success)
|
|
|
|
|
result.stdError = Tr::tr("License command failed.\n\n");
|
2023-03-03 23:59:37 +01:00
|
|
|
fi.addResult(result);
|
2017-10-07 23:22:29 +02:00
|
|
|
fi.setProgressValue(100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManagerPrivate::setLicenseInput(bool acceptLicense)
|
|
|
|
|
{
|
|
|
|
|
QWriteLocker locker(&m_licenseInputLock);
|
|
|
|
|
m_licenseUserInput = acceptLicense ? "Y\n" : "n\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray AndroidSdkManagerPrivate::getUserInput() const
|
|
|
|
|
{
|
|
|
|
|
QReadLocker locker(&m_licenseInputLock);
|
|
|
|
|
return m_licenseUserInput;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManagerPrivate::clearUserInput()
|
|
|
|
|
{
|
|
|
|
|
QWriteLocker locker(&m_licenseInputLock);
|
|
|
|
|
m_licenseUserInput.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidSdkManagerPrivate::onLicenseStdOut(const QString &output, bool notify,
|
|
|
|
|
AndroidSdkManager::OperationOutput &result,
|
2023-03-03 23:59:37 +01:00
|
|
|
SdkCmdPromise &fi)
|
2017-10-07 23:22:29 +02:00
|
|
|
{
|
|
|
|
|
m_licenseTextCache.append(output);
|
2023-02-10 16:56:42 +01:00
|
|
|
const QRegularExpressionMatch assertionMatch = assertionRegExp().match(m_licenseTextCache);
|
2017-10-07 23:22:29 +02:00
|
|
|
if (assertionMatch.hasMatch()) {
|
|
|
|
|
if (notify) {
|
|
|
|
|
result.stdOutput = m_licenseTextCache;
|
2023-03-03 23:59:37 +01:00
|
|
|
fi.addResult(result);
|
2017-10-07 23:22:29 +02:00
|
|
|
}
|
|
|
|
|
// Clear the current contents. The found license text is dispatched. Continue collecting the
|
|
|
|
|
// next license text.
|
|
|
|
|
m_licenseTextCache.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-12 09:34:02 +02:00
|
|
|
void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future)
|
|
|
|
|
{
|
|
|
|
|
if (future.isFinished())
|
|
|
|
|
return;
|
|
|
|
|
m_activeOperation.reset(new QFutureWatcher<void>());
|
2020-11-03 12:49:20 +01:00
|
|
|
m_activeOperation->setFuture(QFuture<void>(future));
|
2017-09-12 09:34:02 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void AndroidSdkManagerPrivate::parseCommonArguments(QPromise<QString> &promise)
|
2017-09-27 15:26:59 +02:00
|
|
|
{
|
|
|
|
|
QString argumentDetails;
|
|
|
|
|
QString output;
|
2017-10-10 09:58:19 +02:00
|
|
|
sdkManagerCommand(m_config, QStringList("--help"), &output);
|
2017-09-27 15:26:59 +02:00
|
|
|
bool foundTag = false;
|
2021-05-28 18:22:44 +03:00
|
|
|
const auto lines = output.split('\n');
|
|
|
|
|
for (const QString& line : lines) {
|
2023-03-03 23:59:37 +01:00
|
|
|
if (promise.isCanceled())
|
2017-09-27 15:26:59 +02:00
|
|
|
break;
|
|
|
|
|
if (foundTag)
|
|
|
|
|
argumentDetails.append(line + "\n");
|
|
|
|
|
else if (line.startsWith(commonArgsKey))
|
|
|
|
|
foundTag = true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
if (!promise.isCanceled())
|
|
|
|
|
promise.addResult(argumentDetails);
|
2017-09-27 15:26:59 +02:00
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
void AndroidSdkManagerPrivate::clearPackages()
|
|
|
|
|
{
|
2022-10-07 14:46:06 +02:00
|
|
|
for (AndroidSdkPackage *p : std::as_const(m_allPackages))
|
2017-08-18 08:22:34 +02:00
|
|
|
delete p;
|
|
|
|
|
m_allPackages.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-30 14:47:34 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Android
|