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/algorithm.h"
#include "utils/qtcassert.h" #include "utils/qtcassert.h"
#include "utils/runextensions.h"
#include "utils/synchronousprocess.h" #include "utils/synchronousprocess.h"
#include "utils/environment.h" #include "utils/environment.h"
#include <QFutureWatcher>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSettings> #include <QSettings>
@@ -52,8 +54,10 @@ const char revisionKey[] = "Version:";
const char descriptionKey[] = "Description:"; const char descriptionKey[] = "Description:";
const int sdkManagerCmdTimeoutS = 60; const int sdkManagerCmdTimeoutS = 60;
const int sdkManagerOperationTimeoutS = 600;
using namespace Utils; using namespace Utils;
using SdkCmdFutureInterface = QFutureInterface<AndroidSdkManager::OperationOutput>;
int platformNameToApiLevel(const QString &platformName) int platformNameToApiLevel(const QString &platformName)
{ {
@@ -83,24 +87,84 @@ static bool valueForKey(QString key, const QString &line, QString *value = nullp
return false; return false;
} }
/*! int parseProgress(const QString &out)
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 int progress = -1;
blocks the calling thread. if (out.isEmpty())
*/ return progress;
static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output, QRegularExpression reg("(?<progress>\\d*)%");
int timeout = sdkManagerCmdTimeoutS) 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; SynchronousProcess proc;
proc.setTimeoutS(timeout); proc.setTimeoutS(timeout);
proc.setTimeOutMessageBoxEnabled(true); proc.setTimeOutMessageBoxEnabled(true);
SynchronousProcessResponse response = proc.run(sdkManagerToolPath, args); SynchronousProcessResponse response = proc.run(toolPath.toString(), args);
if (output) if (output)
*output = response.allOutput(); *output = response.allOutput();
return response.result == SynchronousProcessResponse::Finished; 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 class AndroidSdkManagerPrivate
{ {
@@ -114,6 +178,14 @@ public:
const AndroidSdkPackageList &allPackages(bool forceUpdate = false); const AndroidSdkPackageList &allPackages(bool forceUpdate = false);
void refreshSdkPackages(bool forceReload = 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: private:
void reloadSdkPackages(); void reloadSdkPackages();
void clearPackages(); void clearPackages();
@@ -198,7 +270,7 @@ AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config, QObject *paren
AndroidSdkManager::~AndroidSdkManager() AndroidSdkManager::~AndroidSdkManager()
{ {
cancelOperatons();
} }
SdkPlatformList AndroidSdkManager::installedSdkPlatforms() SdkPlatformList AndroidSdkManager::installedSdkPlatforms()
@@ -258,6 +330,37 @@ void AndroidSdkManager::reloadPackages(bool forceReload)
m_d->refreshSdkPackages(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) void SdkManagerOutputParser::parsePackageListing(const QString &output)
{ {
QStringList packageData; QStringList packageData;
@@ -556,6 +659,7 @@ SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QSt
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager, AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager,
const AndroidConfig &config): const AndroidConfig &config):
m_activeOperation(nullptr, watcherDeleter),
m_sdkManager(sdkManager), m_sdkManager(sdkManager),
m_config(config) m_config(config)
{ {
@@ -605,7 +709,8 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
m_allPackages = Utils::transform(toolManager.availableSdkPlatforms(), toAndroidSdkPackages); m_allPackages = Utils::transform(toolManager.availableSdkPlatforms(), toAndroidSdkPackages);
} else { } else {
QString packageListing; QString packageListing;
if (sdkManagerCommand(m_config, QStringList({"--list", "--verbose"}), &packageListing)) { if (sdkManagerCommand(m_config.sdkManagerToolPath(), QStringList({"--list", "--verbose"}),
&packageListing)) {
SdkManagerOutputParser parser(m_allPackages); SdkManagerOutputParser parser(m_allPackages);
parser.parsePackageListing(packageListing); parser.parsePackageListing(packageListing);
} }
@@ -621,6 +726,85 @@ void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload)
reloadSdkPackages(); 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() void AndroidSdkManagerPrivate::clearPackages()
{ {
for (AndroidSdkPackage *p : m_allPackages) for (AndroidSdkPackage *p : m_allPackages)

View File

@@ -28,6 +28,7 @@
#include "androidsdkpackage.h" #include "androidsdkpackage.h"
#include <QObject> #include <QObject>
#include <QFuture>
#include <memory> #include <memory>
@@ -43,6 +44,21 @@ class AndroidSdkManager : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum CommandType
{
None,
UpdateAll,
UpdatePackage
};
struct OperationOutput
{
bool success = false;
CommandType type = None;
QString stdOutput;
QString stdError;
};
AndroidSdkManager(const AndroidConfig &config, QObject *parent = nullptr); AndroidSdkManager(const AndroidConfig &config, QObject *parent = nullptr);
~AndroidSdkManager(); ~AndroidSdkManager();
@@ -58,9 +74,16 @@ public:
= AndroidSdkPackage::Installed); = AndroidSdkPackage::Installed);
void reloadPackages(bool forceReload = false); void reloadPackages(bool forceReload = false);
bool isBusy() const;
QFuture<OperationOutput> updateAll();
QFuture<OperationOutput> update(const QStringList &install, const QStringList &uninstall);
void cancelOperatons();
signals: signals:
void packageReloadBegin(); void packageReloadBegin();
void packageReloadFinished(); void packageReloadFinished();
void cancelActiveOperations();
private: private:
std::unique_ptr<AndroidSdkManagerPrivate> m_d; std::unique_ptr<AndroidSdkManagerPrivate> m_d;