Android: Add APIs to install, uninstall and update SDK packages

Task-number: QTCREATORBUG-18978
Change-Id: I3e19d665fb5b8a3a562010484735022d7ed333a1
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
This commit is contained in:
Vikas Pachdha
2017-09-12 09:34:02 +02:00
parent e70179e14f
commit 4b1429de55
2 changed files with 218 additions and 11 deletions

View File

@@ -29,9 +29,11 @@
#include "utils/algorithm.h"
#include "utils/qtcassert.h"
#include "utils/runextensions.h"
#include "utils/synchronousprocess.h"
#include "utils/environment.h"
#include <QFutureWatcher>
#include <QLoggingCategory>
#include <QRegularExpression>
#include <QSettings>
@@ -52,8 +54,10 @@ const char revisionKey[] = "Version:";
const char descriptionKey[] = "Description:";
const int sdkManagerCmdTimeoutS = 60;
const int sdkManagerOperationTimeoutS = 600;
using namespace Utils;
using SdkCmdFutureInterface = QFutureInterface<AndroidSdkManager::OperationOutput>;
int platformNameToApiLevel(const QString &platformName)
{
@@ -83,24 +87,84 @@ static bool valueForKey(QString key, const QString &line, QString *value = nullp
return false;
}
/*!
Runs the \c sdkmanger 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.
*/
static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output,
int timeout = sdkManagerCmdTimeoutS)
int parseProgress(const QString &out)
{
int progress = -1;
if (out.isEmpty())
return progress;
QRegularExpression reg("(?<progress>\\d*)%");
QStringList lines = out.split(QRegularExpression("[\\n\\r]"), QString::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;
}
}
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 Utils::FileName &toolPath, const QStringList &args,
QString *output, int timeout = sdkManagerCmdTimeoutS)
{
QString sdkManagerToolPath = config.sdkManagerToolPath().toString();
SynchronousProcess proc;
proc.setTimeoutS(timeout);
proc.setTimeOutMessageBoxEnabled(true);
SynchronousProcessResponse response = proc.run(sdkManagerToolPath, args);
SynchronousProcessResponse response = proc.run(toolPath.toString(), args);
if (output)
*output = response.allOutput();
return response.result == SynchronousProcessResponse::Finished;
}
/*!
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 Utils::FileName &toolPath, const QStringList &args,
AndroidSdkManager &sdkManager, SdkCmdFutureInterface &fi,
AndroidSdkManager::OperationOutput &output, double progressQuota,
bool interruptible = true, int timeout = sdkManagerOperationTimeoutS)
{
int offset = fi.progressValue();
SynchronousProcess proc;
proc.setStdErrBufferedSignalsEnabled(true);
proc.setStdOutBufferedSignalsEnabled(true);
proc.setTimeoutS(timeout);
QObject::connect(&proc, &SynchronousProcess::stdOutBuffered,
[offset, progressQuota, &fi](const QString &out) {
int progressPercent = parseProgress(out);
if (progressPercent != -1)
fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
});
QObject::connect(&proc, &SynchronousProcess::stdErrBuffered, [&output](const QString &err) {
output.stdError = err;
});
if (interruptible) {
QObject::connect(&sdkManager, &AndroidSdkManager::cancelActiveOperations,
&proc, &SynchronousProcess::terminate);
}
SynchronousProcessResponse response = proc.run(toolPath.toString(), args);
output.success = response.result == SynchronousProcessResponse::Finished;
}
class AndroidSdkManagerPrivate
{
@@ -114,6 +178,14 @@ public:
const AndroidSdkPackageList &allPackages(bool forceUpdate = false);
void refreshSdkPackages(bool forceReload = false);
void updateInstalled(SdkCmdFutureInterface &fi);
void update(SdkCmdFutureInterface &fi, const QStringList &install,
const QStringList &uninstall);
void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
std::unique_ptr<QFutureWatcher<void>, decltype(&watcherDeleter)> m_activeOperation;
private:
void reloadSdkPackages();
void clearPackages();
@@ -198,7 +270,7 @@ AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config, QObject *paren
AndroidSdkManager::~AndroidSdkManager()
{
cancelOperatons();
}
SdkPlatformList AndroidSdkManager::installedSdkPlatforms()
@@ -258,6 +330,37 @@ void AndroidSdkManager::reloadPackages(bool forceReload)
m_d->refreshSdkPackages(forceReload);
}
bool AndroidSdkManager::isBusy() const
{
return m_d->m_activeOperation && !m_d->m_activeOperation->isFinished();
}
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updateAll()
{
if (isBusy()) {
return QFuture<AndroidSdkManager::OperationOutput>();
}
auto future = Utils::runAsync(&AndroidSdkManagerPrivate::updateInstalled, m_d.get());
m_d->addWatcher(future);
return future;
}
QFuture<AndroidSdkManager::OperationOutput>
AndroidSdkManager::update(const QStringList &install, const QStringList &uninstall)
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
auto future = Utils::runAsync(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall);
m_d->addWatcher(future);
return future;
}
void AndroidSdkManager::cancelOperatons()
{
emit cancelActiveOperations();
m_d->m_activeOperation.reset();
}
void SdkManagerOutputParser::parsePackageListing(const QString &output)
{
QStringList packageData;
@@ -556,6 +659,7 @@ SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QSt
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager,
const AndroidConfig &config):
m_activeOperation(nullptr, watcherDeleter),
m_sdkManager(sdkManager),
m_config(config)
{
@@ -605,7 +709,8 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
m_allPackages = Utils::transform(toolManager.availableSdkPlatforms(), toAndroidSdkPackages);
} else {
QString packageListing;
if (sdkManagerCommand(m_config, QStringList({"--list", "--verbose"}), &packageListing)) {
if (sdkManagerCommand(m_config.sdkManagerToolPath(), QStringList({"--list", "--verbose"}),
&packageListing)) {
SdkManagerOutputParser parser(m_allPackages);
parser.parsePackageListing(packageListing);
}
@@ -621,6 +726,85 @@ void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload)
reloadSdkPackages();
}
void AndroidSdkManagerPrivate::updateInstalled(SdkCmdFutureInterface &fi)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
AndroidSdkManager::OperationOutput result;
result.type = AndroidSdkManager::UpdateAll;
result.stdOutput = QCoreApplication::translate("AndroidSdkManager",
"Updating installed packages.");
fi.reportResult(result);
QStringList args("--update");
if (!fi.isCanceled())
sdkManagerCommand(m_config.sdkManagerToolPath(), args, m_sdkManager, fi, result, 100);
else
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
if (result.stdError.isEmpty() && !result.success)
result.stdError = QCoreApplication::translate("AndroidSdkManager", "Failed.");
result.stdOutput = QCoreApplication::translate("AndroidSdkManager", "Done\n\n");
fi.reportResult(result);
fi.setProgressValue(100);
}
void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringList &install,
const QStringList &uninstall)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
double progressQuota = 100.0 / (install.count() + uninstall.count());
int currentProgress = 0;
QString installTag = QCoreApplication::translate("AndroidSdkManager", "Installing");
QString uninstallTag = QCoreApplication::translate("AndroidSdkManager", "Uninstalling");
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);
fi.reportResult(result);
if (fi.isCanceled()) {
qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start";
} else {
sdkManagerCommand(m_config.sdkManagerToolPath(), args, m_sdkManager, fi, result,
progressQuota, isInstall);
}
currentProgress += progressQuota;
fi.setProgressValue(currentProgress);
if (result.stdError.isEmpty() && !result.success)
result.stdError = QCoreApplication::translate("AndroidSdkManager", "Failed");
result.stdOutput = QCoreApplication::translate("AndroidSdkManager", "Done\n\n");
fi.reportResult(result);
return fi.isCanceled();
};
// Uninstall packages
for (const QString &sdkStylePath : uninstall) {
// Uninstall operations are not interptible. We don't want to leave half uninstalled.
if (doOperation(sdkStylePath, {"--uninstall", sdkStylePath}, false))
break;
}
// Install packages
for (const QString &sdkStylePath : install) {
if (doOperation(sdkStylePath, {sdkStylePath}, true))
break;
}
fi.setProgressValue(100);
}
void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future)
{
if (future.isFinished())
return;
m_activeOperation.reset(new QFutureWatcher<void>());
m_activeOperation->setFuture(future);
}
void AndroidSdkManagerPrivate::clearPackages()
{
for (AndroidSdkPackage *p : m_allPackages)