From 15e3d26c86f1708d2bbf2972cde5697ca0792a20 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 30 Apr 2024 14:39:40 +0200 Subject: [PATCH] Android: Add licensesRecipe Change-Id: I19fcd9ac354d1ea0100126c8c3640a19256b2a9e Reviewed-by: Reviewed-by: hjk Reviewed-by: Alessandro Portale --- src/plugins/android/androidsdkmanager.cpp | 117 ++++++++++++++++++++-- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp index bbc8d2888a5..bccc0ad1454 100644 --- a/src/plugins/android/androidsdkmanager.cpp +++ b/src/plugins/android/androidsdkmanager.cpp @@ -8,6 +8,8 @@ #include +#include + #include #include #include @@ -30,6 +32,7 @@ Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager", QtWarningMsg) const char commonArgsKey[] = "Common Arguments:"; } +using namespace Tasking; using namespace Utils; using namespace std::chrono; @@ -115,10 +118,10 @@ private: OutputFormatter *m_formatter = nullptr; }; -const int sdkManagerCmdTimeoutS = 60; -const int sdkManagerOperationTimeoutS = 600; - -using SdkCmdPromise = QPromise; +static QString sdkRootArg(const AndroidConfig &config) +{ + return "--sdk_root=" + config.sdkLocation().toString(); +} static const QRegularExpression &assertionRegExp() { @@ -129,6 +132,108 @@ static const QRegularExpression &assertionRegExp() return theRegExp; } +static std::optional parseProgress(const QString &out) +{ + if (out.isEmpty()) + return {}; + + static const QRegularExpression reg("(?\\d*)%"); + static const QRegularExpression regEndOfLine("[\\n\\r]"); + const QStringList lines = out.split(regEndOfLine, Qt::SkipEmptyParts); + std::optional 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 m_dialog; +}; + +static GroupItem licensesRecipe(const Storage &dialogStorage) +{ + struct OutputData + { + QString buffer; + int current = 0; + int total = 0; + }; + + const Storage 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"(((?\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) }; +} + +const int sdkManagerCmdTimeoutS = 60; +const int sdkManagerOperationTimeoutS = 600; + +using SdkCmdPromise = QPromise; + int parseProgress(const QString &out, bool &foundAssertion) { int progress = -1; @@ -161,10 +266,6 @@ void watcherDeleter(QFutureWatcher *watcher) delete watcher; } -static QString sdkRootArg(const AndroidConfig &config) -{ - return "--sdk_root=" + config.sdkLocation().toString(); -} /*! 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.