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"
|
2023-01-23 16:34:24 +01:00
|
|
|
#include "sdkmanageroutputparser.h"
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2024-04-26 14:34:25 +02:00
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
|
2024-04-30 14:39:40 +02:00
|
|
|
#include <solutions/tasking/tasktreerunner.h>
|
|
|
|
|
|
2019-01-08 16:05:57 +01:00
|
|
|
#include <utils/algorithm.h>
|
2023-05-03 15:05:47 +02:00
|
|
|
#include <utils/async.h>
|
2024-04-26 14:34:25 +02:00
|
|
|
#include <utils/layoutbuilder.h>
|
|
|
|
|
#include <utils/outputformatter.h>
|
2024-02-27 16:08:45 +01:00
|
|
|
#include <utils/qtcprocess.h>
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2024-04-26 14:34:25 +02:00
|
|
|
#include <QDialogButtonBox>
|
2024-05-02 17:45:32 +02:00
|
|
|
#include <QLabel>
|
2017-03-30 14:47:34 +02:00
|
|
|
#include <QLoggingCategory>
|
2024-05-02 14:43:08 +02:00
|
|
|
#include <QMessageBox>
|
2024-04-26 14:34:25 +02:00
|
|
|
#include <QPlainTextEdit>
|
|
|
|
|
#include <QProgressBar>
|
2017-09-26 15:29:44 +02:00
|
|
|
#include <QRegularExpression>
|
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
|
|
|
}
|
|
|
|
|
|
2024-04-30 14:39:40 +02:00
|
|
|
using namespace Tasking;
|
2023-01-20 15:21:00 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2024-01-22 17:27:24 +01:00
|
|
|
using namespace std::chrono;
|
2024-01-22 18:59:10 +01:00
|
|
|
using namespace std::chrono_literals;
|
2024-01-22 17:27:24 +01:00
|
|
|
|
2024-05-02 17:45:32 +02:00
|
|
|
namespace Android::Internal {
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2024-04-26 14:34:25 +02:00
|
|
|
class QuestionProgressDialog : public QDialog
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
QuestionProgressDialog(QWidget *parent)
|
|
|
|
|
: QDialog(parent)
|
|
|
|
|
, m_outputTextEdit(new QPlainTextEdit)
|
|
|
|
|
, m_questionLabel(new QLabel(Tr::tr("Do you want to accept the Android SDK license?")))
|
|
|
|
|
, m_answerButtonBox(new QDialogButtonBox)
|
|
|
|
|
, m_progressBar(new QProgressBar)
|
|
|
|
|
, m_dialogButtonBox(new QDialogButtonBox)
|
|
|
|
|
, m_formatter(new OutputFormatter)
|
|
|
|
|
{
|
|
|
|
|
setWindowTitle(Tr::tr("Android SDK Manager"));
|
|
|
|
|
m_outputTextEdit->setReadOnly(true);
|
|
|
|
|
m_questionLabel->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
|
|
|
|
|
m_answerButtonBox->setStandardButtons(QDialogButtonBox::No | QDialogButtonBox::Yes);
|
|
|
|
|
m_dialogButtonBox->setStandardButtons(QDialogButtonBox::Cancel);
|
|
|
|
|
m_formatter->setPlainTextEdit(m_outputTextEdit);
|
|
|
|
|
m_formatter->setParent(this);
|
|
|
|
|
|
|
|
|
|
using namespace Layouting;
|
|
|
|
|
|
|
|
|
|
Column {
|
|
|
|
|
m_outputTextEdit,
|
|
|
|
|
Row { m_questionLabel, m_answerButtonBox },
|
|
|
|
|
m_progressBar,
|
|
|
|
|
m_dialogButtonBox
|
|
|
|
|
}.attachTo(this);
|
|
|
|
|
|
|
|
|
|
setQuestionVisible(false);
|
|
|
|
|
setQuestionEnabled(false);
|
|
|
|
|
|
|
|
|
|
connect(m_answerButtonBox, &QDialogButtonBox::rejected, this, [this] {
|
|
|
|
|
emit answerClicked(false);
|
|
|
|
|
});
|
|
|
|
|
connect(m_answerButtonBox, &QDialogButtonBox::accepted, this, [this] {
|
|
|
|
|
emit answerClicked(true);
|
|
|
|
|
});
|
|
|
|
|
connect(m_dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
|
|
|
|
|
|
|
|
// GUI tuning
|
|
|
|
|
setModal(true);
|
|
|
|
|
resize(800, 600);
|
|
|
|
|
show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setQuestionEnabled(bool enable)
|
|
|
|
|
{
|
|
|
|
|
m_questionLabel->setEnabled(enable);
|
|
|
|
|
m_answerButtonBox->setEnabled(enable);
|
|
|
|
|
}
|
|
|
|
|
void setQuestionVisible(bool visible)
|
|
|
|
|
{
|
|
|
|
|
m_questionLabel->setVisible(visible);
|
|
|
|
|
m_answerButtonBox->setVisible(visible);
|
|
|
|
|
}
|
|
|
|
|
void appendMessage(const QString &text, OutputFormat format)
|
|
|
|
|
{
|
|
|
|
|
m_formatter->appendMessage(text, format);
|
|
|
|
|
m_outputTextEdit->ensureCursorVisible();
|
|
|
|
|
}
|
|
|
|
|
void setProgress(int value) { m_progressBar->setValue(value); }
|
|
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void answerClicked(bool accepted);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QPlainTextEdit *m_outputTextEdit = nullptr;
|
|
|
|
|
QLabel *m_questionLabel = nullptr;
|
|
|
|
|
QDialogButtonBox *m_answerButtonBox = nullptr;
|
|
|
|
|
QProgressBar *m_progressBar = nullptr;
|
|
|
|
|
QDialogButtonBox *m_dialogButtonBox = nullptr;
|
|
|
|
|
OutputFormatter *m_formatter = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-30 14:39:40 +02:00
|
|
|
static QString sdkRootArg(const AndroidConfig &config)
|
|
|
|
|
{
|
|
|
|
|
return "--sdk_root=" + config.sdkLocation().toString();
|
|
|
|
|
}
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2024-05-02 17:45:32 +02:00
|
|
|
const QRegularExpression &assertionRegExp()
|
2023-02-10 16:56:42 +01:00
|
|
|
{
|
|
|
|
|
static const QRegularExpression theRegExp
|
|
|
|
|
(R"((\(\s*y\s*[\/\\]\s*n\s*\)\s*)(?<mark>[\:\?]))", // (y/N)?
|
|
|
|
|
QRegularExpression::CaseInsensitiveOption | QRegularExpression::MultilineOption);
|
|
|
|
|
|
|
|
|
|
return theRegExp;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 14:39:40 +02:00
|
|
|
static std::optional<int> parseProgress(const QString &out)
|
|
|
|
|
{
|
|
|
|
|
if (out.isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
static const QRegularExpression reg("(?<progress>\\d*)%");
|
|
|
|
|
static const QRegularExpression regEndOfLine("[\\n\\r]");
|
|
|
|
|
const QStringList lines = out.split(regEndOfLine, Qt::SkipEmptyParts);
|
|
|
|
|
std::optional<int> progress;
|
|
|
|
|
for (const QString &line : lines) {
|
|
|
|
|
QRegularExpressionMatch match = reg.match(line);
|
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
|
const int parsedProgress = match.captured("progress").toInt();
|
|
|
|
|
if (parsedProgress >= 0 && parsedProgress <= 100)
|
|
|
|
|
progress = parsedProgress;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return progress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct DialogStorage
|
|
|
|
|
{
|
|
|
|
|
DialogStorage() { m_dialog.reset(new QuestionProgressDialog(Core::ICore::dialogParent())); };
|
|
|
|
|
std::unique_ptr<QuestionProgressDialog> m_dialog;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static GroupItem licensesRecipe(const Storage<DialogStorage> &dialogStorage)
|
|
|
|
|
{
|
|
|
|
|
struct OutputData
|
|
|
|
|
{
|
|
|
|
|
QString buffer;
|
|
|
|
|
int current = 0;
|
|
|
|
|
int total = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Storage<OutputData> outputStorage;
|
|
|
|
|
|
|
|
|
|
const auto onLicenseSetup = [dialogStorage, outputStorage](Process &process) {
|
|
|
|
|
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
|
|
|
|
|
dialog->setProgress(0);
|
|
|
|
|
dialog->appendMessage(Tr::tr("Checking pending licenses...") + "\n", NormalMessageFormat);
|
|
|
|
|
dialog->appendMessage(Tr::tr("The installation of Android SDK packages may fail if the "
|
|
|
|
|
"respective licenses are not accepted.") + "\n\n",
|
|
|
|
|
LogMessageFormat);
|
|
|
|
|
process.setProcessMode(ProcessMode::Writer);
|
|
|
|
|
process.setEnvironment(androidConfig().toolsEnvironment());
|
|
|
|
|
process.setCommand(CommandLine(androidConfig().sdkManagerToolPath(),
|
|
|
|
|
{"--licenses", sdkRootArg(androidConfig())}));
|
|
|
|
|
process.setUseCtrlCStub(true);
|
|
|
|
|
|
|
|
|
|
Process *processPtr = &process;
|
|
|
|
|
OutputData *outputPtr = outputStorage.activeStorage();
|
|
|
|
|
QObject::connect(processPtr, &Process::readyReadStandardOutput, dialog,
|
|
|
|
|
[processPtr, outputPtr, dialog] {
|
|
|
|
|
QTextCodec *codec = QTextCodec::codecForLocale();
|
|
|
|
|
const QString stdOut = codec->toUnicode(processPtr->readAllRawStandardOutput());
|
|
|
|
|
outputPtr->buffer += stdOut;
|
|
|
|
|
dialog->appendMessage(stdOut, StdOutFormat);
|
|
|
|
|
const auto progress = parseProgress(stdOut);
|
|
|
|
|
if (progress)
|
|
|
|
|
dialog->setProgress(*progress);
|
|
|
|
|
if (assertionRegExp().match(outputPtr->buffer).hasMatch()) {
|
|
|
|
|
dialog->setQuestionVisible(true);
|
|
|
|
|
dialog->setQuestionEnabled(true);
|
|
|
|
|
if (outputPtr->total == 0) {
|
|
|
|
|
// Example output to match:
|
|
|
|
|
// 5 of 6 SDK package licenses not accepted.
|
|
|
|
|
// Review licenses that have not been accepted (y/N)?
|
|
|
|
|
static const QRegularExpression reg(R"(((?<steps>\d+)\sof\s)\d+)");
|
|
|
|
|
const QRegularExpressionMatch match = reg.match(outputPtr->buffer);
|
|
|
|
|
if (match.hasMatch()) {
|
|
|
|
|
outputPtr->total = match.captured("steps").toInt();
|
|
|
|
|
const QByteArray reply = "y\n";
|
|
|
|
|
dialog->appendMessage(QString::fromUtf8(reply), NormalMessageFormat);
|
|
|
|
|
processPtr->writeRaw(reply);
|
|
|
|
|
dialog->setProgress(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
outputPtr->buffer.clear();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
QObject::connect(dialog, &QuestionProgressDialog::answerClicked, processPtr,
|
|
|
|
|
[processPtr, outputPtr, dialog](bool accepted) {
|
|
|
|
|
dialog->setQuestionEnabled(false);
|
|
|
|
|
const QByteArray reply = accepted ? "y\n" : "n\n";
|
|
|
|
|
dialog->appendMessage(QString::fromUtf8(reply), NormalMessageFormat);
|
|
|
|
|
processPtr->writeRaw(reply);
|
|
|
|
|
++outputPtr->current;
|
|
|
|
|
if (outputPtr->total != 0)
|
|
|
|
|
dialog->setProgress(outputPtr->current * 100.0 / outputPtr->total);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Group { outputStorage, ProcessTask(onLicenseSetup) };
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 18:27:12 +02:00
|
|
|
static void setupSdkProcess(const QStringList &args, Process *process,
|
|
|
|
|
QuestionProgressDialog *dialog, int current, int total)
|
|
|
|
|
{
|
|
|
|
|
process->setEnvironment(androidConfig().toolsEnvironment());
|
|
|
|
|
process->setCommand({androidConfig().sdkManagerToolPath(),
|
|
|
|
|
args + androidConfig().sdkManagerToolArgs()});
|
|
|
|
|
QObject::connect(process, &Process::readyReadStandardOutput, dialog,
|
|
|
|
|
[process, dialog, current, total] {
|
|
|
|
|
QTextCodec *codec = QTextCodec::codecForLocale();
|
|
|
|
|
const auto progress = parseProgress(codec->toUnicode(process->readAllRawStandardOutput()));
|
|
|
|
|
if (!progress)
|
|
|
|
|
return;
|
|
|
|
|
dialog->setProgress((current * 100.0 + *progress) / total);
|
|
|
|
|
});
|
2024-05-02 15:33:06 +02:00
|
|
|
QObject::connect(process, &Process::readyReadStandardError, dialog, [process, dialog] {
|
|
|
|
|
QTextCodec *codec = QTextCodec::codecForLocale();
|
|
|
|
|
dialog->appendMessage(codec->toUnicode(process->readAllRawStandardError()), StdErrFormat);
|
|
|
|
|
});
|
2024-04-30 18:27:12 +02:00
|
|
|
};
|
|
|
|
|
|
2024-05-02 15:33:06 +02:00
|
|
|
static void handleSdkProcess(QuestionProgressDialog *dialog, DoneWith result)
|
|
|
|
|
{
|
|
|
|
|
if (result == DoneWith::Success)
|
|
|
|
|
dialog->appendMessage(Tr::tr("Finished successfully.") + "\n\n", StdOutFormat);
|
|
|
|
|
else
|
|
|
|
|
dialog->appendMessage(Tr::tr("Failed.") + "\n\n", StdErrFormat);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 18:27:12 +02:00
|
|
|
static GroupItem installationRecipe(const Storage<DialogStorage> &dialogStorage,
|
|
|
|
|
const InstallationChange &change)
|
|
|
|
|
{
|
|
|
|
|
const auto onSetup = [dialogStorage] {
|
|
|
|
|
dialogStorage->m_dialog->appendMessage(
|
|
|
|
|
Tr::tr("Installing / Uninstalling selected packages...") + '\n', NormalMessageFormat);
|
|
|
|
|
const QString optionsMessage = HostOsInfo::isMacHost()
|
|
|
|
|
? Tr::tr("Closing the preferences dialog will cancel the running and scheduled SDK "
|
|
|
|
|
"operations.")
|
|
|
|
|
: Tr::tr("Closing the options dialog will cancel the running and scheduled SDK "
|
|
|
|
|
"operations.");
|
|
|
|
|
dialogStorage->m_dialog->appendMessage(optionsMessage + '\n', LogMessageFormat);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const int total = change.count();
|
|
|
|
|
const LoopList uninstallIterator(change.toUninstall);
|
|
|
|
|
const auto onUninstallSetup = [dialogStorage, uninstallIterator, total](Process &process) {
|
|
|
|
|
const QStringList args = {"--uninstall", *uninstallIterator, sdkRootArg(androidConfig())};
|
|
|
|
|
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
|
|
|
|
|
setupSdkProcess(args, &process, dialog, uninstallIterator.iteration(), total);
|
|
|
|
|
dialog->appendMessage(Tr::tr("Uninstalling %1...").arg(*uninstallIterator) + '\n',
|
|
|
|
|
StdOutFormat);
|
|
|
|
|
dialog->setProgress(uninstallIterator.iteration() * 100.0 / total);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const LoopList installIterator(change.toInstall);
|
|
|
|
|
const int offset = change.toUninstall.count();
|
|
|
|
|
const auto onInstallSetup = [dialogStorage, installIterator, offset, total](Process &process) {
|
|
|
|
|
const QStringList args = {*installIterator, sdkRootArg(androidConfig())};
|
|
|
|
|
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
|
|
|
|
|
setupSdkProcess(args, &process, dialog, offset + installIterator.iteration(), total);
|
|
|
|
|
dialog->appendMessage(Tr::tr("Installing %1...").arg(*installIterator) + '\n',
|
|
|
|
|
StdOutFormat);
|
|
|
|
|
dialog->setProgress((offset + installIterator.iteration()) * 100.0 / total);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto onDone = [dialogStorage](DoneWith result) {
|
2024-05-02 15:33:06 +02:00
|
|
|
handleSdkProcess(dialogStorage->m_dialog.get(), result);
|
2024-04-30 18:27:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return Group {
|
|
|
|
|
onGroupSetup(onSetup),
|
|
|
|
|
Group {
|
|
|
|
|
finishAllAndSuccess,
|
|
|
|
|
uninstallIterator,
|
|
|
|
|
ProcessTask(onUninstallSetup, onDone)
|
|
|
|
|
},
|
|
|
|
|
Group {
|
|
|
|
|
finishAllAndSuccess,
|
|
|
|
|
installIterator,
|
|
|
|
|
ProcessTask(onInstallSetup, onDone)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 15:33:06 +02:00
|
|
|
static GroupItem updateRecipe(const Storage<DialogStorage> &dialogStorage)
|
|
|
|
|
{
|
|
|
|
|
const auto onUpdateSetup = [dialogStorage](Process &process) {
|
|
|
|
|
const QStringList args = {"--update", sdkRootArg(androidConfig())};
|
|
|
|
|
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
|
|
|
|
|
setupSdkProcess(args, &process, dialog, 0, 1);
|
|
|
|
|
dialog->appendMessage(Tr::tr("Updating installed packages....") + '\n', NormalMessageFormat);
|
|
|
|
|
dialog->setProgress(0);
|
|
|
|
|
};
|
|
|
|
|
const auto onDone = [dialogStorage](DoneWith result) {
|
|
|
|
|
handleSdkProcess(dialogStorage->m_dialog.get(), result);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return ProcessTask(onUpdateSetup, onDone);
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
*/
|
2024-05-02 17:45:32 +02:00
|
|
|
static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &args, QString *output)
|
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();
|
2023-05-03 16:00:22 +02:00
|
|
|
Process proc;
|
2023-07-27 14:44:44 +02:00
|
|
|
proc.setEnvironment(config.toolsEnvironment());
|
2017-07-24 15:27:19 +02:00
|
|
|
proc.setTimeOutMessageBoxEnabled(true);
|
2021-05-17 12:02:42 +02:00
|
|
|
proc.setCommand({config.sdkManagerToolPath(), newArgs});
|
2024-05-02 17:45:32 +02:00
|
|
|
proc.runBlocking(60s, 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-08-18 08:22:34 +02:00
|
|
|
class AndroidSdkManagerPrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
2023-11-22 18:46:09 +01:00
|
|
|
AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager);
|
2017-08-18 08:22:34 +02:00
|
|
|
~AndroidSdkManagerPrivate();
|
|
|
|
|
|
|
|
|
|
AndroidSdkPackageList filteredPackages(AndroidSdkPackage::PackageState state,
|
2024-04-17 17:11:43 +02:00
|
|
|
AndroidSdkPackage::PackageType type)
|
|
|
|
|
{
|
2024-04-17 17:43:48 +02:00
|
|
|
m_sdkManager.refreshPackages();
|
2024-04-17 17:11:43 +02:00
|
|
|
return Utils::filtered(m_allPackages, [state, type](const AndroidSdkPackage *p) {
|
|
|
|
|
return p->state() & state && p->type() & type;
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-04-17 17:14:16 +02:00
|
|
|
const AndroidSdkPackageList &allPackages();
|
2017-08-18 08:22:34 +02:00
|
|
|
|
2023-03-03 23:59:37 +01:00
|
|
|
void parseCommonArguments(QPromise<QString> &promise);
|
2017-09-12 09:34:02 +02:00
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
void reloadSdkPackages();
|
|
|
|
|
|
2024-05-02 14:43:08 +02:00
|
|
|
void runDialogRecipe(const Storage<DialogStorage> &dialogStorage,
|
|
|
|
|
const GroupItem &licenseRecipe, const GroupItem &continuationRecipe);
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
AndroidSdkManager &m_sdkManager;
|
|
|
|
|
AndroidSdkPackageList m_allPackages;
|
2019-05-28 13:49:26 +02:00
|
|
|
FilePath lastSdkManagerPath;
|
2020-06-26 00:33:43 +03:00
|
|
|
bool m_packageListingSuccessful = false;
|
2024-05-02 14:43:08 +02:00
|
|
|
TaskTreeRunner m_taskTreeRunner;
|
2017-08-18 08:22:34 +02:00
|
|
|
};
|
|
|
|
|
|
2024-05-02 17:45:32 +02:00
|
|
|
AndroidSdkManager::AndroidSdkManager() : m_d(new AndroidSdkManagerPrivate(*this)) {}
|
2017-03-30 14:47:34 +02:00
|
|
|
|
2024-05-02 17:45:32 +02:00
|
|
|
AndroidSdkManager::~AndroidSdkManager() = default;
|
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
|
|
|
{
|
2024-04-17 17:11:43 +02:00
|
|
|
const 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();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-18 15:57:25 +02:00
|
|
|
QStringList AndroidSdkManager::notFoundEssentialSdkPackages()
|
|
|
|
|
{
|
|
|
|
|
QStringList essentials = androidConfig().allEssentials();
|
|
|
|
|
const AndroidSdkPackageList &packages = allSdkPackages();
|
|
|
|
|
for (AndroidSdkPackage *package : packages) {
|
|
|
|
|
essentials.removeOne(package->sdkStylePath());
|
|
|
|
|
if (essentials.isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
return essentials;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList AndroidSdkManager::missingEssentialSdkPackages()
|
|
|
|
|
{
|
|
|
|
|
const QStringList essentials = androidConfig().allEssentials();
|
|
|
|
|
const AndroidSdkPackageList &packages = allSdkPackages();
|
|
|
|
|
QStringList missingPackages;
|
|
|
|
|
for (AndroidSdkPackage *package : packages) {
|
|
|
|
|
if (essentials.contains(package->sdkStylePath())
|
|
|
|
|
&& package->state() != AndroidSdkPackage::Installed) {
|
|
|
|
|
missingPackages.append(package->sdkStylePath());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return missingPackages;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-18 08:22:34 +02:00
|
|
|
AndroidSdkPackageList AndroidSdkManager::installedSdkPackages()
|
|
|
|
|
{
|
|
|
|
|
return m_d->filteredPackages(AndroidSdkPackage::Installed, AndroidSdkPackage::AnyValidType);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-08 14:55:16 +02:00
|
|
|
SystemImageList AndroidSdkManager::installedSystemImages()
|
|
|
|
|
{
|
2024-04-17 17:11:43 +02:00
|
|
|
const AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::AnyValidState,
|
|
|
|
|
AndroidSdkPackage::SdkPlatformPackage);
|
2024-05-02 17:45:32 +02:00
|
|
|
const QList<SdkPlatform *> platforms = Utils::static_container_cast<SdkPlatform *>(list);
|
2020-01-08 14:55:16 +02:00
|
|
|
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()
|
|
|
|
|
{
|
2024-04-17 17:11:43 +02:00
|
|
|
const AndroidSdkPackageList list = m_d->filteredPackages(AndroidSdkPackage::Installed,
|
|
|
|
|
AndroidSdkPackage::NDKPackage);
|
2019-12-23 16:13:23 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-17 17:43:48 +02:00
|
|
|
void AndroidSdkManager::refreshPackages()
|
2017-08-18 08:22:34 +02:00
|
|
|
{
|
2024-04-17 17:43:48 +02:00
|
|
|
if (androidConfig().sdkManagerToolPath() != m_d->lastSdkManagerPath)
|
|
|
|
|
reloadPackages();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManager::reloadPackages()
|
|
|
|
|
{
|
|
|
|
|
m_d->reloadSdkPackages();
|
2017-03-30 14:47:34 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2024-05-02 17:45:32 +02:00
|
|
|
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager)
|
|
|
|
|
: m_sdkManager(sdkManager)
|
2023-11-22 18:46:09 +01:00
|
|
|
{}
|
2017-08-18 08:22:34 +02:00
|
|
|
|
|
|
|
|
AndroidSdkManagerPrivate::~AndroidSdkManagerPrivate()
|
|
|
|
|
{
|
2024-05-02 17:45:32 +02:00
|
|
|
qDeleteAll(m_allPackages);
|
2017-08-18 08:22:34 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-17 17:14:16 +02:00
|
|
|
const AndroidSdkPackageList &AndroidSdkManagerPrivate::allPackages()
|
2017-08-18 08:22:34 +02:00
|
|
|
{
|
2024-04-17 17:43:48 +02:00
|
|
|
m_sdkManager.refreshPackages();
|
2017-08-18 08:22:34 +02:00
|
|
|
return m_allPackages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManagerPrivate::reloadSdkPackages()
|
|
|
|
|
{
|
2019-01-16 18:06:21 +01:00
|
|
|
emit m_sdkManager.packageReloadBegin();
|
2024-05-02 17:45:32 +02:00
|
|
|
qDeleteAll(m_allPackages);
|
|
|
|
|
m_allPackages.clear();
|
2017-08-18 08:22:34 +02:00
|
|
|
|
2023-11-22 18:46:09 +01:00
|
|
|
lastSdkManagerPath = androidConfig().sdkManagerToolPath();
|
2020-06-26 00:33:43 +03:00
|
|
|
m_packageListingSuccessful = false;
|
2017-08-18 08:22:34 +02:00
|
|
|
|
2023-11-22 18:46:09 +01:00
|
|
|
if (androidConfig().sdkToolsVersion().isNull()) {
|
2017-08-18 08:22:34 +02:00
|
|
|
// 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"});
|
2023-11-22 18:46:09 +01:00
|
|
|
args << androidConfig().sdkManagerToolArgs();
|
|
|
|
|
m_packageListingSuccessful = sdkManagerCommand(androidConfig(), args, &packageListing);
|
2020-06-30 09:50:28 +03:00
|
|
|
if (m_packageListingSuccessful) {
|
|
|
|
|
SdkManagerOutputParser parser(m_allPackages);
|
|
|
|
|
parser.parsePackageListing(packageListing);
|
2024-02-27 06:52:33 +01:00
|
|
|
} else {
|
|
|
|
|
qCWarning(sdkManagerLog) << "Failed parsing packages:" << packageListing;
|
2017-08-18 08:22:34 +02:00
|
|
|
}
|
2024-02-27 06:52:33 +01:00
|
|
|
|
2019-01-16 18:06:21 +01:00
|
|
|
emit m_sdkManager.packageReloadFinished();
|
2017-08-18 08:22:34 +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;
|
2023-11-22 18:46:09 +01:00
|
|
|
sdkManagerCommand(androidConfig(), 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
|
|
|
}
|
|
|
|
|
|
2024-05-02 14:43:08 +02:00
|
|
|
void AndroidSdkManagerPrivate::runDialogRecipe(const Storage<DialogStorage> &dialogStorage,
|
|
|
|
|
const GroupItem &licensesRecipe,
|
|
|
|
|
const GroupItem &continuationRecipe)
|
|
|
|
|
{
|
|
|
|
|
const auto onCancelSetup = [dialogStorage] {
|
|
|
|
|
return std::make_pair(dialogStorage->m_dialog.get(), &QDialog::rejected);
|
|
|
|
|
};
|
|
|
|
|
const Group root {
|
|
|
|
|
dialogStorage,
|
|
|
|
|
Group {
|
|
|
|
|
licensesRecipe,
|
|
|
|
|
Sync([dialogStorage] { dialogStorage->m_dialog->setQuestionVisible(false); }),
|
|
|
|
|
continuationRecipe
|
|
|
|
|
}.withCancel(onCancelSetup)
|
|
|
|
|
};
|
|
|
|
|
m_taskTreeRunner.start(root, {}, [this](DoneWith) {
|
|
|
|
|
QMetaObject::invokeMethod(&m_sdkManager, &AndroidSdkManager::reloadPackages,
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManager::runInstallationChange(const InstallationChange &change,
|
|
|
|
|
const QString &extraMessage)
|
|
|
|
|
{
|
|
|
|
|
QString message = Tr::tr("%n Android SDK packages shall be updated.", "", change.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 (!change.toUninstall.isEmpty()) {
|
|
|
|
|
QStringList toUninstall = {Tr::tr("[Packages to be uninstalled:]")};
|
|
|
|
|
toUninstall += change.toUninstall;
|
|
|
|
|
details += toUninstall.join("\n ");
|
|
|
|
|
}
|
|
|
|
|
if (!change.toInstall.isEmpty()) {
|
|
|
|
|
if (!change.toUninstall.isEmpty())
|
|
|
|
|
details.append("\n\n");
|
|
|
|
|
QStringList toInstall = {Tr::tr("[Packages to be installed:]")};
|
|
|
|
|
toInstall += change.toInstall;
|
|
|
|
|
details += toInstall.join("\n ");
|
|
|
|
|
}
|
|
|
|
|
messageDlg.setDetailedText(details);
|
|
|
|
|
if (messageDlg.exec() == QMessageBox::Cancel)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const Storage<DialogStorage> dialogStorage;
|
|
|
|
|
m_d->runDialogRecipe(dialogStorage,
|
|
|
|
|
change.toInstall.count() ? licensesRecipe(dialogStorage) : nullItem,
|
|
|
|
|
installationRecipe(dialogStorage, change));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidSdkManager::runUpdate()
|
|
|
|
|
{
|
|
|
|
|
const Storage<DialogStorage> dialogStorage;
|
|
|
|
|
m_d->runDialogRecipe(dialogStorage, licensesRecipe(dialogStorage), updateRecipe(dialogStorage));
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 17:45:32 +02:00
|
|
|
} // namespace Android::Internal
|
2024-04-26 14:34:25 +02:00
|
|
|
|
|
|
|
|
#include "androidsdkmanager.moc"
|