Android: Do some cleanup

Cleanup after employing task tree...

Change-Id: I79ffa385886b0a635a5fdfdbc496dcf6b042aa71
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Jarek Kobus
2024-05-02 17:45:32 +02:00
parent 74994b435d
commit bb5cdfeb4c
7 changed files with 86 additions and 768 deletions

View File

@@ -15,16 +15,13 @@
#include <utils/layoutbuilder.h>
#include <utils/outputformatter.h>
#include <utils/qtcprocess.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
#include <QFutureWatcher>
#include <QDialogButtonBox>
#include <QLabel>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QProgressBar>
#include <QReadWriteLock>
#include <QRegularExpression>
#include <QTextCodec>
@@ -39,8 +36,7 @@ using namespace Utils;
using namespace std::chrono;
using namespace std::chrono_literals;
namespace Android {
namespace Internal {
namespace Android::Internal {
class QuestionProgressDialog : public QDialog
{
@@ -124,7 +120,7 @@ static QString sdkRootArg(const AndroidConfig &config)
return "--sdk_root=" + config.sdkLocation().toString();
}
static const QRegularExpression &assertionRegExp()
const QRegularExpression &assertionRegExp()
{
static const QRegularExpression theRegExp
(R"((\(\s*y\s*[\/\\]\s*n\s*\)\s*)(?<mark>[\:\?]))", // (y/N)?
@@ -329,49 +325,11 @@ static GroupItem updateRecipe(const Storage<DialogStorage> &dialogStorage)
return ProcessTask(onUpdateSetup, onDone);
}
const int sdkManagerCmdTimeoutS = 60;
const int sdkManagerOperationTimeoutS = 600;
using SdkCmdPromise = QPromise<AndroidSdkManager::OperationOutput>;
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 watcherDeleter(QFutureWatcher<void> *watcher)
{
if (!watcher->isFinished() && !watcher->isCanceled())
watcher->cancel();
if (!watcher->isFinished())
watcher->waitForFinished();
delete watcher;
}
/*!
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.
*/
static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
QString *output, int timeout = sdkManagerCmdTimeoutS)
static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output)
{
QStringList newArgs = args;
newArgs.append(sdkRootArg(config));
@@ -382,60 +340,12 @@ static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &ar
proc.setEnvironment(config.toolsEnvironment());
proc.setTimeOutMessageBoxEnabled(true);
proc.setCommand({config.sdkManagerToolPath(), newArgs});
proc.runBlocking(seconds(timeout), EventLoopMode::On);
proc.runBlocking(60s, EventLoopMode::On);
if (output)
*output = proc.allOutput();
return proc.result() == ProcessResult::FinishedWithSuccess;
}
/*!
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.
*/
static void sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
AndroidSdkManager &sdkManager, SdkCmdPromise &promise,
AndroidSdkManager::OperationOutput &output, double progressQuota,
bool interruptible = true, int timeout = sdkManagerOperationTimeoutS)
{
QStringList newArgs = args;
newArgs.append(sdkRootArg(config));
qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (async):"
<< CommandLine(config.sdkManagerToolPath(), newArgs)
.toUserOutput();
int offset = promise.future().progressValue();
Process proc;
proc.setEnvironment(config.toolsEnvironment());
bool assertionFound = false;
proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &promise](const QString &out) {
int progressPercent = parseProgress(out, assertionFound);
if (assertionFound)
proc.stop();
if (progressPercent != -1)
promise.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
});
proc.setStdErrCallback([&output](const QString &err) {
output.stdError = err;
});
if (interruptible) {
QObject::connect(&sdkManager, &AndroidSdkManager::cancelActiveOperations, &proc, [&proc] {
proc.stop();
proc.waitForFinished();
});
}
proc.setCommand({config.sdkManagerToolPath(), newArgs});
proc.runBlocking(seconds(timeout), EventLoopMode::On);
if (assertionFound) {
output.success = false;
output.stdOutput = proc.cleanedStdOut();
output.stdError = Tr::tr("The operation requires user interaction. "
"Use the \"sdkmanager\" command-line tool.");
} else {
output.success = proc.result() == ProcessResult::FinishedWithSuccess;
}
}
class AndroidSdkManagerPrivate
{
public:
@@ -453,20 +363,8 @@ public:
const AndroidSdkPackageList &allPackages();
void parseCommonArguments(QPromise<QString> &promise);
void updateInstalled(SdkCmdPromise &fi);
void updatePackages(SdkCmdPromise &fi, const InstallationChange &change);
void licenseCheck(SdkCmdPromise &fi);
void licenseWorkflow(SdkCmdPromise &fi);
void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
void setLicenseInput(bool acceptLicense);
std::unique_ptr<QFutureWatcher<void>, decltype(&watcherDeleter)> m_activeOperation;
QByteArray getUserInput() const;
void clearUserInput();
void reloadSdkPackages();
void clearPackages();
void runDialogRecipe(const Storage<DialogStorage> &dialogStorage,
const GroupItem &licenseRecipe, const GroupItem &continuationRecipe);
@@ -474,21 +372,13 @@ public:
AndroidSdkManager &m_sdkManager;
AndroidSdkPackageList m_allPackages;
FilePath lastSdkManagerPath;
QByteArray m_licenseUserInput;
mutable QReadWriteLock m_licenseInputLock;
bool m_packageListingSuccessful = false;
TaskTreeRunner m_taskTreeRunner;
};
AndroidSdkManager::AndroidSdkManager()
: m_d(new AndroidSdkManagerPrivate(*this))
{
}
AndroidSdkManager::AndroidSdkManager() : m_d(new AndroidSdkManagerPrivate(*this)) {}
AndroidSdkManager::~AndroidSdkManager()
{
cancelOperatons();
}
AndroidSdkManager::~AndroidSdkManager() = default;
SdkPlatformList AndroidSdkManager::installedSdkPlatforms()
{
@@ -537,14 +427,12 @@ SystemImageList AndroidSdkManager::installedSystemImages()
{
const AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::AnyValidState,
AndroidSdkPackage::SdkPlatformPackage);
QList<SdkPlatform *> platforms = Utils::static_container_cast<SdkPlatform *>(list);
const 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;
}
@@ -573,7 +461,6 @@ SdkPlatformList AndroidSdkManager::filteredSdkPlatforms(int minApiLevel,
{
const AndroidSdkPackageList list = m_d->filteredPackages(state,
AndroidSdkPackage::SdkPlatformPackage);
SdkPlatformList result;
for (AndroidSdkPackage *p : list) {
auto platform = static_cast<SdkPlatform *>(p);
@@ -608,11 +495,6 @@ void AndroidSdkManager::reloadPackages()
m_d->reloadSdkPackages();
}
bool AndroidSdkManager::isBusy() const
{
return m_d->m_activeOperation && !m_d->m_activeOperation->isFinished();
}
bool AndroidSdkManager::packageListingSuccessful() const
{
return m_d->m_packageListingSuccessful;
@@ -623,62 +505,13 @@ QFuture<QString> AndroidSdkManager::availableArguments() const
return Utils::asyncRun(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get());
}
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updateInstalled()
{
if (isBusy()) {
return QFuture<AndroidSdkManager::OperationOutput>();
}
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::updateInstalled, m_d.get());
m_d->addWatcher(future);
return future;
}
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updatePackages(const InstallationChange &change)
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::updatePackages, m_d.get(), change);
m_d->addWatcher(future);
return future;
}
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::licenseCheck()
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::licenseCheck, m_d.get());
m_d->addWatcher(future);
return future;
}
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::licenseWorkflow()
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::licenseWorkflow, m_d.get());
m_d->addWatcher(future);
return future;
}
void AndroidSdkManager::cancelOperatons()
{
emit cancelActiveOperations();
m_d->m_activeOperation.reset();
}
void AndroidSdkManager::acceptSdkLicense(bool accept)
{
m_d->setLicenseInput(accept);
}
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager):
m_activeOperation(nullptr, watcherDeleter),
m_sdkManager(sdkManager)
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager)
: m_sdkManager(sdkManager)
{}
AndroidSdkManagerPrivate::~AndroidSdkManagerPrivate()
{
clearPackages();
qDeleteAll(m_allPackages);
}
const AndroidSdkPackageList &AndroidSdkManagerPrivate::allPackages()
@@ -690,7 +523,8 @@ const AndroidSdkPackageList &AndroidSdkManagerPrivate::allPackages()
void AndroidSdkManagerPrivate::reloadSdkPackages()
{
emit m_sdkManager.packageReloadBegin();
clearPackages();
qDeleteAll(m_allPackages);
m_allPackages.clear();
lastSdkManagerPath = androidConfig().sdkManagerToolPath();
m_packageListingSuccessful = false;
@@ -715,194 +549,6 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
emit m_sdkManager.packageReloadFinished();
}
void AndroidSdkManagerPrivate::updateInstalled(SdkCmdPromise &promise)
{
promise.setProgressRange(0, 100);
promise.setProgressValue(0);
AndroidSdkManager::OperationOutput result;
result.type = AndroidSdkManager::UpdateInstalled;
result.stdOutput = Tr::tr("Updating installed packages.");
promise.addResult(result);
QStringList args("--update");
args << androidConfig().sdkManagerToolArgs();
if (!promise.isCanceled())
sdkManagerCommand(androidConfig(), args, m_sdkManager, promise, result, 100);
else
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
if (result.stdError.isEmpty() && !result.success)
result.stdError = Tr::tr("Failed.");
result.stdOutput = Tr::tr("Done") + "\n\n";
promise.addResult(result);
promise.setProgressValue(100);
}
void AndroidSdkManagerPrivate::updatePackages(SdkCmdPromise &fi, const InstallationChange &change)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
double progressQuota = 100.0 / change.count();
int currentProgress = 0;
QString installTag = Tr::tr("Installing");
QString uninstallTag = Tr::tr("Uninstalling");
auto doOperation = [&](const QString& packagePath, const QStringList& args,
bool isInstall) {
AndroidSdkManager::OperationOutput result;
result.type = AndroidSdkManager::UpdatePackages;
result.stdOutput = QString("%1 %2").arg(isInstall ? installTag : uninstallTag)
.arg(packagePath);
fi.addResult(result);
if (fi.isCanceled())
qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start";
else
sdkManagerCommand(androidConfig(), args, m_sdkManager, fi, result, progressQuota, isInstall);
currentProgress += progressQuota;
fi.setProgressValue(currentProgress);
if (result.stdError.isEmpty() && !result.success)
result.stdError = Tr::tr("Failed");
result.stdOutput = Tr::tr("Done") + "\n\n";
fi.addResult(result);
return fi.isCanceled();
};
// Uninstall packages
for (const QString &sdkStylePath : change.toUninstall) {
// Uninstall operations are not interptible. We don't want to leave half uninstalled.
QStringList args;
args << "--uninstall" << sdkStylePath << androidConfig().sdkManagerToolArgs();
if (doOperation(sdkStylePath, args, false))
break;
}
// Install packages
for (const QString &sdkStylePath : change.toInstall) {
QStringList args(sdkStylePath);
args << androidConfig().sdkManagerToolArgs();
if (doOperation(sdkStylePath, args, true))
break;
}
fi.setProgressValue(100);
}
void AndroidSdkManagerPrivate::licenseCheck(SdkCmdPromise &fi)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
AndroidSdkManager::OperationOutput result;
result.type = AndroidSdkManager::LicenseCheck;
if (!fi.isCanceled()) {
const int timeOutS = 4; // Short timeout as workaround for QTCREATORBUG-25667
sdkManagerCommand(androidConfig(), {"--licenses"}, m_sdkManager, fi, result, 100.0, true,
timeOutS);
} else {
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
}
fi.addResult(result);
fi.setProgressValue(100);
}
void AndroidSdkManagerPrivate::licenseWorkflow(SdkCmdPromise &fi)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
AndroidSdkManager::OperationOutput result;
result.type = AndroidSdkManager::LicenseWorkflow;
Process licenseCommand;
licenseCommand.setProcessMode(ProcessMode::Writer);
licenseCommand.setEnvironment(androidConfig().toolsEnvironment());
bool reviewingLicenses = false;
licenseCommand.setCommand(CommandLine(androidConfig().sdkManagerToolPath(),
{"--licenses", sdkRootArg(androidConfig())}));
licenseCommand.setUseCtrlCStub(true);
licenseCommand.start();
QTextCodec *codec = QTextCodec::codecForLocale();
int inputCounter = 0, steps = -1;
QString licenseTextCache;
while (!licenseCommand.waitForFinished(200ms)) {
const QString stdOut = codec->toUnicode(licenseCommand.readAllRawStandardOutput());
bool assertion = false;
if (!stdOut.isEmpty()) {
licenseTextCache.append(stdOut);
assertion = assertionRegExp().match(licenseTextCache).hasMatch();
if (assertion) {
if (reviewingLicenses) {
result.stdOutput = licenseTextCache;
fi.addResult(result);
}
licenseTextCache.clear();
}
}
if (reviewingLicenses) {
// Check user input
QByteArray userInput = getUserInput();
if (!userInput.isEmpty()) {
clearUserInput();
licenseCommand.writeRaw(userInput);
++inputCounter;
if (steps != -1)
fi.setProgressValue(qRound((inputCounter / (double)steps) * 100));
}
} else if (assertion) {
// The first assertion is to start reviewing licenses. Always accept.
reviewingLicenses = true;
static const QRegularExpression reg(R"((\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(300ms)) {
licenseCommand.kill();
licenseCommand.waitForFinished(200ms);
}
}
if (licenseCommand.state() == QProcess::NotRunning)
break;
}
result.success = licenseCommand.exitStatus() == QProcess::NormalExit;
if (!result.success)
result.stdError = Tr::tr("License command failed.") + "\n\n";
fi.addResult(result);
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();
}
void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future)
{
if (future.isFinished())
return;
m_activeOperation.reset(new QFutureWatcher<void>());
m_activeOperation->setFuture(QFuture<void>(future));
}
void AndroidSdkManagerPrivate::parseCommonArguments(QPromise<QString> &promise)
{
QString argumentDetails;
@@ -923,13 +569,6 @@ void AndroidSdkManagerPrivate::parseCommonArguments(QPromise<QString> &promise)
promise.addResult(argumentDetails);
}
void AndroidSdkManagerPrivate::clearPackages()
{
for (AndroidSdkPackage *p : std::as_const(m_allPackages))
delete p;
m_allPackages.clear();
}
void AndroidSdkManagerPrivate::runDialogRecipe(const Storage<DialogStorage> &dialogStorage,
const GroupItem &licensesRecipe,
const GroupItem &continuationRecipe)
@@ -991,7 +630,6 @@ void AndroidSdkManager::runUpdate()
m_d->runDialogRecipe(dialogStorage, licensesRecipe(dialogStorage), updateRecipe(dialogStorage));
}
} // namespace Internal
} // namespace Android
} // namespace Android::Internal
#include "androidsdkmanager.moc"