aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArtem Sokolovskii <artem.sokolovskii@qt.io>2023-01-23 16:34:24 +0100
committerArtem Sokolovskii <artem.sokolovskii@qt.io>2023-02-14 14:11:30 +0000
commitb7fde29cdfbdf46d8d3f89d0c4dfc6328b43782d (patch)
tree837f532ec90684dc5f5b15397f99cf56cf365169
parentb4f665f8acc73c11063cc2cb6c829428fc1d1748 (diff)
Android: Extract sdkmanageroutputparser to separate file
- Needed for testing and for improving readability Change-Id: Ie7a716b204ae0a216d72fa0191d4d05b7708c16c Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
-rw-r--r--src/plugins/android/CMakeLists.txt1
-rw-r--r--src/plugins/android/android.qbs4
-rw-r--r--src/plugins/android/androidsdkmanager.cpp508
-rw-r--r--src/plugins/android/sdkmanageroutputparser.cpp449
-rw-r--r--src/plugins/android/sdkmanageroutputparser.h79
5 files changed, 538 insertions, 503 deletions
diff --git a/src/plugins/android/CMakeLists.txt b/src/plugins/android/CMakeLists.txt
index e58c4698a5..cbd588fdee 100644
--- a/src/plugins/android/CMakeLists.txt
+++ b/src/plugins/android/CMakeLists.txt
@@ -52,6 +52,7 @@ add_qtc_plugin(Android
javaparser.cpp javaparser.h
splashscreencontainerwidget.cpp splashscreencontainerwidget.h
splashscreenwidget.cpp splashscreenwidget.h
+ sdkmanageroutputparser.cpp sdkmanageroutputparser.h
)
extend_qtc_plugin(Android
diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs
index 07fdc9bf01..843e1adf6d 100644
--- a/src/plugins/android/android.qbs
+++ b/src/plugins/android/android.qbs
@@ -111,7 +111,9 @@ Project {
"splashscreencontainerwidget.cpp",
"splashscreencontainerwidget.h",
"splashscreenwidget.cpp",
- "splashscreenwidget.h"
+ "splashscreenwidget.h",
+ "sdkmanageroutputparser.cpp",
+ "sdkmanageroutputparser.h"
]
Group {
diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp
index 531bd0b273..bdb3378c0b 100644
--- a/src/plugins/android/androidsdkmanager.cpp
+++ b/src/plugins/android/androidsdkmanager.cpp
@@ -2,11 +2,10 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "androidconfigurations.h"
-#include "androidconstants.h"
-#include "androidmanager.h"
#include "androidsdkmanager.h"
#include "androidtr.h"
#include "avdmanageroutputparser.h"
+#include "sdkmanageroutputparser.h"
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
@@ -29,16 +28,13 @@
using namespace Utils;
namespace {
-static Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager", QtWarningMsg)
+Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager", QtWarningMsg)
+const char commonArgsKey[] = "Common Arguments:";
}
namespace Android {
namespace Internal {
-const char installLocationKey[] = "Installed Location:";
-const char revisionKey[] = "Version:";
-const char descriptionKey[] = "Description:";
-const char commonArgsKey[] = "Common Arguments:";
const int sdkManagerCmdTimeoutS = 60;
const int sdkManagerOperationTimeoutS = 600;
@@ -54,28 +50,14 @@ static const QRegularExpression &assertionRegExp()
return theRegExp;
}
-/*!
- Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns
- \c true if \a key is found, false otherwise. Result is copied into \a value.
- */
-static bool valueForKey(QString key, const QString &line, QString *value = nullptr)
-{
- auto trimmedInput = line.trimmed();
- if (trimmedInput.startsWith(key)) {
- if (value)
- *value = trimmedInput.section(key, 1, 1).trimmed();
- return true;
- }
- return false;
-}
-
int parseProgress(const QString &out, bool &foundAssertion)
{
int progress = -1;
if (out.isEmpty())
return progress;
- QRegularExpression reg("(?<progress>\\d*)%");
- QStringList lines = out.split(QRegularExpression("[\\n\\r]"), Qt::SkipEmptyParts);
+ 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()) {
@@ -223,87 +205,6 @@ public:
bool m_packageListingSuccessful = false;
};
-/*!
- \class SdkManagerOutputParser
- \brief The SdkManagerOutputParser class is a helper class to parse the output of the \c sdkmanager
- commands.
- */
-class SdkManagerOutputParser
-{
- class GenericPackageData
- {
- public:
- bool isValid() const { return !revision.isNull() && !description.isNull(); }
- QStringList headerParts;
- QVersionNumber revision;
- QString description;
- Utils::FilePath installedLocation;
- QMap<QString, QString> extraData;
- };
-
-public:
- enum MarkerTag
- {
- None = 0x001,
- InstalledPackagesMarker = 0x002,
- AvailablePackagesMarkers = 0x004,
- AvailableUpdatesMarker = 0x008,
- EmptyMarker = 0x010,
- PlatformMarker = 0x020,
- SystemImageMarker = 0x040,
- BuildToolsMarker = 0x080,
- SdkToolsMarker = 0x100,
- PlatformToolsMarker = 0x200,
- EmulatorToolsMarker = 0x400,
- NdkMarker = 0x800,
- ExtrasMarker = 0x1000,
- CmdlineSdkToolsMarker = 0x2000,
- GenericToolMarker = 0x4000,
- SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker
- };
-
- SdkManagerOutputParser(AndroidSdkPackageList &container) : m_packages(container) {}
- void parsePackageListing(const QString &output);
-
- AndroidSdkPackageList &m_packages;
-
-private:
- void compilePackageAssociations();
- void parsePackageData(MarkerTag packageMarker, const QStringList &data);
- bool parseAbstractData(GenericPackageData &output, const QStringList &input, int minParts,
- const QString &logStrTag,
- const QStringList &extraKeys = QStringList()) const;
- AndroidSdkPackage *parsePlatform(const QStringList &data) const;
- QPair<SystemImage *, int> parseSystemImage(const QStringList &data) const;
- BuildTools *parseBuildToolsPackage(const QStringList &data) const;
- SdkTools *parseSdkToolsPackage(const QStringList &data) const;
- PlatformTools *parsePlatformToolsPackage(const QStringList &data) const;
- EmulatorTools *parseEmulatorToolsPackage(const QStringList &data) const;
- Ndk *parseNdkPackage(const QStringList &data) const;
- ExtraTools *parseExtraToolsPackage(const QStringList &data) const;
- GenericSdkPackage *parseGenericTools(const QStringList &data) const;
- MarkerTag parseMarkers(const QString &line);
-
- MarkerTag m_currentSection = MarkerTag::None;
- QHash<AndroidSdkPackage *, int> m_systemImages;
-};
-
-using MarkerTagsType = std::map<SdkManagerOutputParser::MarkerTag, const char *>;
-Q_GLOBAL_STATIC_WITH_ARGS(MarkerTagsType, markerTags, ({
- {SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"},
- {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"},
- {SdkManagerOutputParser::MarkerTag::AvailableUpdatesMarker, "Available Updates:"},
- {SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"},
- {SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"},
- {SdkManagerOutputParser::MarkerTag::BuildToolsMarker, "build-tools"},
- {SdkManagerOutputParser::MarkerTag::SdkToolsMarker, "tools"},
- {SdkManagerOutputParser::MarkerTag::CmdlineSdkToolsMarker, Constants::cmdlineToolsName},
- {SdkManagerOutputParser::MarkerTag::PlatformToolsMarker, "platform-tools"},
- {SdkManagerOutputParser::MarkerTag::EmulatorToolsMarker, "emulator"},
- {SdkManagerOutputParser::MarkerTag::NdkMarker, Constants::ndkPackageName},
- {SdkManagerOutputParser::MarkerTag::ExtrasMarker, "extras"}
-}));
-
AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config):
m_d(new AndroidSdkManagerPrivate(*this, config))
{
@@ -464,403 +365,6 @@ void AndroidSdkManager::acceptSdkLicense(bool accept)
m_d->setLicenseInput(accept);
}
-void SdkManagerOutputParser::parsePackageListing(const QString &output)
-{
- QStringList packageData;
- bool collectingPackageData = false;
- MarkerTag currentPackageMarker = MarkerTag::None;
-
- auto processCurrentPackage = [&] {
- if (collectingPackageData) {
- collectingPackageData = false;
- parsePackageData(currentPackageMarker, packageData);
- packageData.clear();
- }
- };
-
- QRegularExpression delimiters("[\\n\\r]");
- const auto lines = output.split(delimiters);
- for (const QString &outputLine : lines) {
-
- // NOTE: we don't want to parse Dependencies part as it does not add value
- if (outputLine.startsWith(" "))
- continue;
-
- // We don't need to parse this because they would still be listed on available packages
- if (m_currentSection == AvailableUpdatesMarker)
- continue;
-
- MarkerTag marker = parseMarkers(outputLine.trimmed());
- if (marker & SectionMarkers) {
- // Section marker found. Update the current section being parsed.
- m_currentSection = marker;
- processCurrentPackage();
- continue;
- }
-
- if (m_currentSection == None)
- continue; // Continue with the verbose output until a valid section starts.
-
- if (marker == EmptyMarker) {
- // Empty marker. Occurs at the end of a package details.
- // Process the collected package data, if any.
- processCurrentPackage();
- continue;
- }
-
- if (marker == None) {
- if (collectingPackageData)
- packageData << outputLine; // Collect data until next marker.
- else
- continue;
- } else {
- // Package marker found.
- processCurrentPackage(); // New package starts. Process the collected package data, if any.
- currentPackageMarker = marker;
- collectingPackageData = true;
- packageData << outputLine;
- }
- }
- compilePackageAssociations();
-}
-
-void SdkManagerOutputParser::compilePackageAssociations()
-{
- // Return true if package p is already installed i.e. there exists a installed package having
- // same sdk style path and same revision as of p.
- auto isInstalled = [](const AndroidSdkPackageList &container, AndroidSdkPackage *p) {
- return Utils::anyOf(container, [p](AndroidSdkPackage *other) {
- return other->state() == AndroidSdkPackage::Installed &&
- other->sdkStylePath() == p->sdkStylePath() &&
- other->revision() == p->revision();
- });
- };
-
- auto deleteAlreadyInstalled = [isInstalled](AndroidSdkPackageList &packages) {
- for (auto p = packages.begin(); p != packages.end();) {
- if ((*p)->state() == AndroidSdkPackage::Available && isInstalled(packages, *p)) {
- delete *p;
- p = packages.erase(p);
- } else {
- ++p;
- }
- }
- };
-
- // Remove already installed packages.
- deleteAlreadyInstalled(m_packages);
-
- // Filter out available images that are already installed.
- AndroidSdkPackageList images = m_systemImages.keys();
- deleteAlreadyInstalled(images);
-
- // Associate the system images with sdk platforms.
- for (AndroidSdkPackage *image : std::as_const(images)) {
- int imageApi = m_systemImages[image];
- auto itr = std::find_if(m_packages.begin(), m_packages.end(),
- [imageApi](const AndroidSdkPackage *p) {
- const SdkPlatform *platform = nullptr;
- if (p->type() == AndroidSdkPackage::SdkPlatformPackage)
- platform = static_cast<const SdkPlatform*>(p);
- return platform && platform->apiLevel() == imageApi;
- });
- if (itr != m_packages.end()) {
- auto platform = static_cast<SdkPlatform*>(*itr);
- platform->addSystemImage(static_cast<SystemImage *>(image));
- }
- }
-}
-
-void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QStringList &data)
-{
- QTC_ASSERT(!data.isEmpty() && packageMarker != None, return);
-
- AndroidSdkPackage *package = nullptr;
- auto createPackage = [&](std::function<AndroidSdkPackage *(SdkManagerOutputParser *,
- const QStringList &)> creator) {
- if ((package = creator(this, data)))
- m_packages.append(package);
- };
-
- switch (packageMarker) {
- case MarkerTag::BuildToolsMarker:
- createPackage(&SdkManagerOutputParser::parseBuildToolsPackage);
- break;
-
- case MarkerTag::SdkToolsMarker:
- createPackage(&SdkManagerOutputParser::parseSdkToolsPackage);
- break;
-
- case MarkerTag::CmdlineSdkToolsMarker:
- createPackage(&SdkManagerOutputParser::parseSdkToolsPackage);
- break;
-
- case MarkerTag::PlatformToolsMarker:
- createPackage(&SdkManagerOutputParser::parsePlatformToolsPackage);
- break;
-
- case MarkerTag::EmulatorToolsMarker:
- createPackage(&SdkManagerOutputParser::parseEmulatorToolsPackage);
- break;
-
- case MarkerTag::PlatformMarker:
- createPackage(&SdkManagerOutputParser::parsePlatform);
- break;
-
- case MarkerTag::SystemImageMarker:
- {
- QPair<SystemImage *, int> result = parseSystemImage(data);
- if (result.first) {
- m_systemImages[result.first] = result.second;
- package = result.first;
- }
- }
- break;
-
- case MarkerTag::NdkMarker:
- createPackage(&SdkManagerOutputParser::parseNdkPackage);
- break;
-
- case MarkerTag::ExtrasMarker:
- createPackage(&SdkManagerOutputParser::parseExtraToolsPackage);
- break;
-
- case MarkerTag::GenericToolMarker:
- createPackage(&SdkManagerOutputParser::parseGenericTools);
- break;
-
- default:
- qCDebug(sdkManagerLog) << "Unhandled package: " << markerTags->at(packageMarker);
- break;
- }
-
- if (package) {
- switch (m_currentSection) {
- case MarkerTag::InstalledPackagesMarker:
- package->setState(AndroidSdkPackage::Installed);
- break;
- case MarkerTag::AvailablePackagesMarkers:
- case MarkerTag::AvailableUpdatesMarker:
- package->setState(AndroidSdkPackage::Available);
- break;
- default:
- qCDebug(sdkManagerLog) << "Invalid section marker: " << markerTags->at(m_currentSection);
- break;
- }
- }
-}
-
-bool SdkManagerOutputParser::parseAbstractData(SdkManagerOutputParser::GenericPackageData &output,
- const QStringList &input, int minParts,
- const QString &logStrTag,
- const QStringList &extraKeys) const
-{
- if (input.isEmpty()) {
- qCDebug(sdkManagerLog) << logStrTag + ": Empty input";
- return false;
- }
-
- output.headerParts = input.at(0).split(';');
- if (output.headerParts.count() < minParts) {
- qCDebug(sdkManagerLog) << logStrTag + "%1: Unexpected header:" << input;
- return false;
- }
-
- QStringList keys = extraKeys;
- keys << installLocationKey << revisionKey << descriptionKey;
- for (const QString &line : input) {
- QString value;
- for (const auto &key: std::as_const(keys)) {
- if (valueForKey(key, line, &value)) {
- if (key == installLocationKey)
- output.installedLocation = Utils::FilePath::fromUserInput(value);
- else if (key == revisionKey)
- output.revision = QVersionNumber::fromString(value);
- else if (key == descriptionKey)
- output.description = value;
- else
- output.extraData[key] = value;
- break;
- }
- }
- }
-
- return output.isValid();
-}
-
-AndroidSdkPackage *SdkManagerOutputParser::parsePlatform(const QStringList &data) const
-{
- SdkPlatform *platform = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 2, "Platform")) {
- const int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1));
- if (apiLevel == -1) {
- qCDebug(sdkManagerLog) << "Platform: Cannot parse api level:"<< data;
- return nullptr;
- }
- platform = new SdkPlatform(packageData.revision, data.at(0), apiLevel);
- platform->setExtension(convertNameToExtension(packageData.headerParts.at(1)));
- platform->setInstalledLocation(packageData.installedLocation);
- platform->setDescriptionText(packageData.description);
- } else {
- qCDebug(sdkManagerLog) << "Platform: Parsing failed. Minimum required data unavailable:"
- << data;
- }
- return platform;
-}
-
-QPair<SystemImage *, int> SdkManagerOutputParser::parseSystemImage(const QStringList &data) const
-{
- QPair <SystemImage *, int> result(nullptr, -1);
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 4, "System-image")) {
- const int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1));
- if (apiLevel == -1) {
- qCDebug(sdkManagerLog) << "System-image: Cannot parse api level:"<< data;
- return result;
- }
- auto image = new SystemImage(packageData.revision, data.at(0),
- packageData.headerParts.at(3));
- image->setInstalledLocation(packageData.installedLocation);
- image->setDisplayText(packageData.description);
- image->setDescriptionText(packageData.description);
- image->setApiLevel(apiLevel);
- result = {image, apiLevel};
- } else {
- qCDebug(sdkManagerLog) << "System-image: Minimum required data unavailable: "<< data;
- }
- return result;
-}
-
-BuildTools *SdkManagerOutputParser::parseBuildToolsPackage(const QStringList &data) const
-{
- BuildTools *buildTools = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 2, "Build-tools")) {
- buildTools = new BuildTools(packageData.revision, data.at(0));
- buildTools->setDescriptionText(packageData.description);
- buildTools->setDisplayText(packageData.description);
- buildTools->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "Build-tools: Parsing failed. Minimum required data unavailable:"
- << data;
- }
- return buildTools;
-}
-
-SdkTools *SdkManagerOutputParser::parseSdkToolsPackage(const QStringList &data) const
-{
- SdkTools *sdkTools = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 1, "SDK-tools")) {
- sdkTools = new SdkTools(packageData.revision, data.at(0));
- sdkTools->setDescriptionText(packageData.description);
- sdkTools->setDisplayText(packageData.description);
- sdkTools->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "SDK-tools: Parsing failed. Minimum required data unavailable:"
- << data;
- }
- return sdkTools;
-}
-
-PlatformTools *SdkManagerOutputParser::parsePlatformToolsPackage(const QStringList &data) const
-{
- PlatformTools *platformTools = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 1, "Platform-tools")) {
- platformTools = new PlatformTools(packageData.revision, data.at(0));
- platformTools->setDescriptionText(packageData.description);
- platformTools->setDisplayText(packageData.description);
- platformTools->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "Platform-tools: Parsing failed. Minimum required data "
- "unavailable:" << data;
- }
- return platformTools;
-}
-
-EmulatorTools *SdkManagerOutputParser::parseEmulatorToolsPackage(const QStringList &data) const
-{
- EmulatorTools *emulatorTools = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 1, "Emulator-tools")) {
- emulatorTools = new EmulatorTools(packageData.revision, data.at(0));
- emulatorTools->setDescriptionText(packageData.description);
- emulatorTools->setDisplayText(packageData.description);
- emulatorTools->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "Emulator-tools: Parsing failed. Minimum required data "
- "unavailable:" << data;
- }
- return emulatorTools;
-}
-
-Ndk *SdkManagerOutputParser::parseNdkPackage(const QStringList &data) const
-{
- Ndk *ndk = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 1, "NDK")) {
- ndk = new Ndk(packageData.revision, data.at(0));
- ndk->setDescriptionText(packageData.description);
- ndk->setDisplayText(packageData.description);
- ndk->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "NDK: Parsing failed. Minimum required data unavailable:"
- << data;
- }
- return ndk;
-}
-
-ExtraTools *SdkManagerOutputParser::parseExtraToolsPackage(const QStringList &data) const
-{
- ExtraTools *extraTools = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 1, "Extras")) {
- extraTools = new ExtraTools(packageData.revision, data.at(0));
- extraTools->setDescriptionText(packageData.description);
- extraTools->setDisplayText(packageData.description);
- extraTools->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "Extra-tools: Parsing failed. Minimum required data "
- "unavailable:" << data;
- }
- return extraTools;
-}
-
-GenericSdkPackage *SdkManagerOutputParser::parseGenericTools(const QStringList &data) const
-{
- GenericSdkPackage *sdkPackage = nullptr;
- GenericPackageData packageData;
- if (parseAbstractData(packageData, data, 1, "Generic")) {
- sdkPackage = new GenericSdkPackage(packageData.revision, data.at(0));
- sdkPackage->setDescriptionText(packageData.description);
- sdkPackage->setDisplayText(packageData.description);
- sdkPackage->setInstalledLocation(packageData.installedLocation);
- } else {
- qCDebug(sdkManagerLog) << "Generic: Parsing failed. Minimum required data "
- "unavailable:" << data;
- }
- return sdkPackage;
-}
-
-SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QString &line)
-{
- if (line.isEmpty())
- return EmptyMarker;
-
- for (auto pair : *markerTags) {
- if (line.startsWith(QLatin1String(pair.second)))
- return pair.first;
- }
-
- QRegularExpressionMatch match = QRegularExpression("^[a-zA-Z]+[A-Za-z0-9;._-]+").match(line);
- if (match.hasMatch() && match.captured(0) == line)
- return GenericToolMarker;
-
- return None;
-}
-
AndroidSdkManagerPrivate::AndroidSdkManagerPrivate(AndroidSdkManager &sdkManager,
const AndroidConfig &config):
m_activeOperation(nullptr, watcherDeleter),
diff --git a/src/plugins/android/sdkmanageroutputparser.cpp b/src/plugins/android/sdkmanageroutputparser.cpp
new file mode 100644
index 0000000000..2989ec35dd
--- /dev/null
+++ b/src/plugins/android/sdkmanageroutputparser.cpp
@@ -0,0 +1,449 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "sdkmanageroutputparser.h"
+
+#include "avdmanageroutputparser.h"
+#include "androidsdkpackage.h"
+
+#include <utils/algorithm.h>
+
+#include <QRegularExpression>
+#include <QLoggingCategory>
+
+namespace {
+Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager", QtWarningMsg)
+const char installLocationKey[] = "Installed Location:";
+const char revisionKey[] = "Version:";
+const char descriptionKey[] = "Description:";
+} // namespace
+
+using namespace Android;
+using namespace Android::Internal;
+
+using MarkerTagsType = std::map<SdkManagerOutputParser::MarkerTag, const char *>;
+Q_GLOBAL_STATIC_WITH_ARGS(MarkerTagsType, markerTags,
+ ({{SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"},
+ {SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"},
+ {SdkManagerOutputParser::MarkerTag::AvailableUpdatesMarker, "Available Updates:"},
+ {SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"},
+ {SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"},
+ {SdkManagerOutputParser::MarkerTag::BuildToolsMarker, "build-tools"},
+ {SdkManagerOutputParser::MarkerTag::SdkToolsMarker, "tools"},
+ {SdkManagerOutputParser::MarkerTag::CmdlineSdkToolsMarker, Constants::cmdlineToolsName},
+ {SdkManagerOutputParser::MarkerTag::PlatformToolsMarker, "platform-tools"},
+ {SdkManagerOutputParser::MarkerTag::EmulatorToolsMarker, "emulator"},
+ {SdkManagerOutputParser::MarkerTag::NdkMarker, Constants::ndkPackageName},
+ {SdkManagerOutputParser::MarkerTag::ExtrasMarker, "extras"}}));
+
+void SdkManagerOutputParser::parsePackageListing(const QString &output)
+{
+ QStringList packageData;
+ bool collectingPackageData = false;
+ MarkerTag currentPackageMarker = MarkerTag::None;
+
+ auto processCurrentPackage = [&] {
+ if (collectingPackageData) {
+ collectingPackageData = false;
+ parsePackageData(currentPackageMarker, packageData);
+ packageData.clear();
+ }
+ };
+
+ static const QRegularExpression delimiters("[\\n\\r]");
+ const auto lines = output.split(delimiters);
+ for (const QString &outputLine : lines) {
+
+ // NOTE: we don't want to parse Dependencies part as it does not add value
+ if (outputLine.startsWith(" "))
+ continue;
+
+ // We don't need to parse this because they would still be listed on available packages
+ if (m_currentSection == AvailableUpdatesMarker)
+ continue;
+
+ MarkerTag marker = parseMarkers(outputLine.trimmed());
+ if (marker & SectionMarkers) {
+ // Section marker found. Update the current section being parsed.
+ m_currentSection = marker;
+ processCurrentPackage();
+ continue;
+ }
+
+ if (m_currentSection == None)
+ continue; // Continue with the verbose output until a valid section starts.
+
+ if (marker == EmptyMarker) {
+ // Empty marker. Occurs at the end of a package details.
+ // Process the collected package data, if any.
+ processCurrentPackage();
+ continue;
+ }
+
+ if (marker == None) {
+ if (collectingPackageData)
+ packageData << outputLine; // Collect data until next marker.
+ else
+ continue;
+ } else {
+ // Package marker found.
+ processCurrentPackage(); // New package starts. Process the collected package data, if any.
+ currentPackageMarker = marker;
+ collectingPackageData = true;
+ packageData << outputLine;
+ }
+ }
+ compilePackageAssociations();
+}
+
+void SdkManagerOutputParser::compilePackageAssociations()
+{
+ // Return true if package p is already installed i.e. there exists a installed package having
+ // same sdk style path and same revision as of p.
+ auto isInstalled = [](const AndroidSdkPackageList &container, AndroidSdkPackage *p) {
+ return Utils::anyOf(container, [p](AndroidSdkPackage *other) {
+ return other->state() == AndroidSdkPackage::Installed &&
+ other->sdkStylePath() == p->sdkStylePath() &&
+ other->revision() == p->revision();
+ });
+ };
+
+ auto deleteAlreadyInstalled = [isInstalled](AndroidSdkPackageList &packages) {
+ for (auto p = packages.begin(); p != packages.end();) {
+ if ((*p)->state() == AndroidSdkPackage::Available && isInstalled(packages, *p)) {
+ delete *p;
+ p = packages.erase(p);
+ } else {
+ ++p;
+ }
+ }
+ };
+
+ // Remove already installed packages.
+ deleteAlreadyInstalled(m_packages);
+
+ // Filter out available images that are already installed.
+ AndroidSdkPackageList images = m_systemImages.keys();
+ deleteAlreadyInstalled(images);
+
+ // Associate the system images with sdk platforms.
+ for (AndroidSdkPackage *image : std::as_const(images)) {
+ int imageApi = m_systemImages[image];
+ auto itr = std::find_if(m_packages.begin(), m_packages.end(),
+ [imageApi](const AndroidSdkPackage *p) {
+ const SdkPlatform *platform = nullptr;
+ if (p->type() == AndroidSdkPackage::SdkPlatformPackage)
+ platform = static_cast<const SdkPlatform*>(p);
+ return platform && platform->apiLevel() == imageApi;
+ });
+ if (itr != m_packages.end()) {
+ auto platform = static_cast<SdkPlatform*>(*itr);
+ platform->addSystemImage(static_cast<SystemImage *>(image));
+ }
+ }
+}
+
+void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QStringList &data)
+{
+ QTC_ASSERT(!data.isEmpty() && packageMarker != None, return);
+
+ AndroidSdkPackage *package = nullptr;
+ auto createPackage = [&](std::function<AndroidSdkPackage *(SdkManagerOutputParser *,
+ const QStringList &)> creator) {
+ if ((package = creator(this, data)))
+ m_packages.append(package);
+ };
+
+ switch (packageMarker) {
+ case MarkerTag::BuildToolsMarker:
+ createPackage(&SdkManagerOutputParser::parseBuildToolsPackage);
+ break;
+
+ case MarkerTag::SdkToolsMarker:
+ createPackage(&SdkManagerOutputParser::parseSdkToolsPackage);
+ break;
+
+ case MarkerTag::CmdlineSdkToolsMarker:
+ createPackage(&SdkManagerOutputParser::parseSdkToolsPackage);
+ break;
+
+ case MarkerTag::PlatformToolsMarker:
+ createPackage(&SdkManagerOutputParser::parsePlatformToolsPackage);
+ break;
+
+ case MarkerTag::EmulatorToolsMarker:
+ createPackage(&SdkManagerOutputParser::parseEmulatorToolsPackage);
+ break;
+
+ case MarkerTag::PlatformMarker:
+ createPackage(&SdkManagerOutputParser::parsePlatform);
+ break;
+
+ case MarkerTag::SystemImageMarker:
+ {
+ QPair<SystemImage *, int> result = parseSystemImage(data);
+ if (result.first) {
+ m_systemImages[result.first] = result.second;
+ package = result.first;
+ }
+ }
+ break;
+
+ case MarkerTag::NdkMarker:
+ createPackage(&SdkManagerOutputParser::parseNdkPackage);
+ break;
+
+ case MarkerTag::ExtrasMarker:
+ createPackage(&SdkManagerOutputParser::parseExtraToolsPackage);
+ break;
+
+ case MarkerTag::GenericToolMarker:
+ createPackage(&SdkManagerOutputParser::parseGenericTools);
+ break;
+
+ default:
+ qCDebug(sdkManagerLog) << "Unhandled package: " << markerTags->at(packageMarker);
+ break;
+ }
+
+ if (package) {
+ switch (m_currentSection) {
+ case MarkerTag::InstalledPackagesMarker:
+ package->setState(AndroidSdkPackage::Installed);
+ break;
+ case MarkerTag::AvailablePackagesMarkers:
+ case MarkerTag::AvailableUpdatesMarker:
+ package->setState(AndroidSdkPackage::Available);
+ break;
+ default:
+ qCDebug(sdkManagerLog) << "Invalid section marker: " << markerTags->at(m_currentSection);
+ break;
+ }
+ }
+}
+
+/*!
+ Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns
+ \c true if \a key is found, false otherwise. Result is copied into \a value.
+ */
+static bool valueForKey(QString key, const QString &line, QString *value = nullptr)
+{
+ auto trimmedInput = line.trimmed();
+ if (trimmedInput.startsWith(key)) {
+ if (value)
+ *value = trimmedInput.section(key, 1, 1).trimmed();
+ return true;
+ }
+ return false;
+}
+
+bool SdkManagerOutputParser::parseAbstractData(SdkManagerOutputParser::GenericPackageData &output,
+ const QStringList &input, int minParts,
+ const QString &logStrTag,
+ const QStringList &extraKeys) const
+{
+ if (input.isEmpty()) {
+ qCDebug(sdkManagerLog) << logStrTag + ": Empty input";
+ return false;
+ }
+
+ output.headerParts = input.at(0).split(';');
+ if (output.headerParts.count() < minParts) {
+ qCDebug(sdkManagerLog) << logStrTag + "%1: Unexpected header:" << input;
+ return false;
+ }
+
+ QStringList keys = extraKeys;
+ keys << installLocationKey << revisionKey << descriptionKey;
+ for (const QString &line : input) {
+ QString value;
+ for (const auto &key: std::as_const(keys)) {
+ if (valueForKey(key, line, &value)) {
+ if (key == installLocationKey)
+ output.installedLocation = Utils::FilePath::fromUserInput(value);
+ else if (key == revisionKey)
+ output.revision = QVersionNumber::fromString(value);
+ else if (key == descriptionKey)
+ output.description = value;
+ else
+ output.extraData[key] = value;
+ break;
+ }
+ }
+ }
+
+ return output.isValid();
+}
+
+AndroidSdkPackage *SdkManagerOutputParser::parsePlatform(const QStringList &data) const
+{
+ SdkPlatform *platform = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 2, "Platform")) {
+ const int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1));
+ if (apiLevel == -1) {
+ qCDebug(sdkManagerLog) << "Platform: Cannot parse api level:"<< data;
+ return nullptr;
+ }
+ platform = new SdkPlatform(packageData.revision, data.at(0), apiLevel);
+ platform->setExtension(convertNameToExtension(packageData.headerParts.at(1)));
+ platform->setInstalledLocation(packageData.installedLocation);
+ platform->setDescriptionText(packageData.description);
+ } else {
+ qCDebug(sdkManagerLog) << "Platform: Parsing failed. Minimum required data unavailable:"
+ << data;
+ }
+ return platform;
+}
+
+QPair<SystemImage *, int> SdkManagerOutputParser::parseSystemImage(const QStringList &data) const
+{
+ QPair <SystemImage *, int> result(nullptr, -1);
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 4, "System-image")) {
+ const int apiLevel = platformNameToApiLevel(packageData.headerParts.at(1));
+ if (apiLevel == -1) {
+ qCDebug(sdkManagerLog) << "System-image: Cannot parse api level:"<< data;
+ return result;
+ }
+ auto image = new SystemImage(packageData.revision, data.at(0),
+ packageData.headerParts.at(3));
+ image->setInstalledLocation(packageData.installedLocation);
+ image->setDisplayText(packageData.description);
+ image->setDescriptionText(packageData.description);
+ image->setApiLevel(apiLevel);
+ result = {image, apiLevel};
+ } else {
+ qCDebug(sdkManagerLog) << "System-image: Minimum required data unavailable: "<< data;
+ }
+ return result;
+}
+
+BuildTools *SdkManagerOutputParser::parseBuildToolsPackage(const QStringList &data) const
+{
+ BuildTools *buildTools = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 2, "Build-tools")) {
+ buildTools = new BuildTools(packageData.revision, data.at(0));
+ buildTools->setDescriptionText(packageData.description);
+ buildTools->setDisplayText(packageData.description);
+ buildTools->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "Build-tools: Parsing failed. Minimum required data unavailable:"
+ << data;
+ }
+ return buildTools;
+}
+
+SdkTools *SdkManagerOutputParser::parseSdkToolsPackage(const QStringList &data) const
+{
+ SdkTools *sdkTools = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 1, "SDK-tools")) {
+ sdkTools = new SdkTools(packageData.revision, data.at(0));
+ sdkTools->setDescriptionText(packageData.description);
+ sdkTools->setDisplayText(packageData.description);
+ sdkTools->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "SDK-tools: Parsing failed. Minimum required data unavailable:"
+ << data;
+ }
+ return sdkTools;
+}
+
+PlatformTools *SdkManagerOutputParser::parsePlatformToolsPackage(const QStringList &data) const
+{
+ PlatformTools *platformTools = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 1, "Platform-tools")) {
+ platformTools = new PlatformTools(packageData.revision, data.at(0));
+ platformTools->setDescriptionText(packageData.description);
+ platformTools->setDisplayText(packageData.description);
+ platformTools->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "Platform-tools: Parsing failed. Minimum required data "
+ "unavailable:" << data;
+ }
+ return platformTools;
+}
+
+EmulatorTools *SdkManagerOutputParser::parseEmulatorToolsPackage(const QStringList &data) const
+{
+ EmulatorTools *emulatorTools = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 1, "Emulator-tools")) {
+ emulatorTools = new EmulatorTools(packageData.revision, data.at(0));
+ emulatorTools->setDescriptionText(packageData.description);
+ emulatorTools->setDisplayText(packageData.description);
+ emulatorTools->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "Emulator-tools: Parsing failed. Minimum required data "
+ "unavailable:" << data;
+ }
+ return emulatorTools;
+}
+
+Ndk *SdkManagerOutputParser::parseNdkPackage(const QStringList &data) const
+{
+ Ndk *ndk = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 1, "NDK")) {
+ ndk = new Ndk(packageData.revision, data.at(0));
+ ndk->setDescriptionText(packageData.description);
+ ndk->setDisplayText(packageData.description);
+ ndk->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "NDK: Parsing failed. Minimum required data unavailable:"
+ << data;
+ }
+ return ndk;
+}
+
+ExtraTools *SdkManagerOutputParser::parseExtraToolsPackage(const QStringList &data) const
+{
+ ExtraTools *extraTools = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 1, "Extras")) {
+ extraTools = new ExtraTools(packageData.revision, data.at(0));
+ extraTools->setDescriptionText(packageData.description);
+ extraTools->setDisplayText(packageData.description);
+ extraTools->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "Extra-tools: Parsing failed. Minimum required data "
+ "unavailable:" << data;
+ }
+ return extraTools;
+}
+
+GenericSdkPackage *SdkManagerOutputParser::parseGenericTools(const QStringList &data) const
+{
+ GenericSdkPackage *sdkPackage = nullptr;
+ GenericPackageData packageData;
+ if (parseAbstractData(packageData, data, 1, "Generic")) {
+ sdkPackage = new GenericSdkPackage(packageData.revision, data.at(0));
+ sdkPackage->setDescriptionText(packageData.description);
+ sdkPackage->setDisplayText(packageData.description);
+ sdkPackage->setInstalledLocation(packageData.installedLocation);
+ } else {
+ qCDebug(sdkManagerLog) << "Generic: Parsing failed. Minimum required data "
+ "unavailable:" << data;
+ }
+ return sdkPackage;
+}
+
+SdkManagerOutputParser::MarkerTag SdkManagerOutputParser::parseMarkers(const QString &line)
+{
+ if (line.isEmpty())
+ return EmptyMarker;
+
+ for (auto pair : *markerTags) {
+ if (line.startsWith(QLatin1String(pair.second)))
+ return pair.first;
+ }
+
+ QRegularExpressionMatch match = QRegularExpression("^[a-zA-Z]+[A-Za-z0-9;._-]+").match(line);
+ if (match.hasMatch() && match.captured(0) == line)
+ return GenericToolMarker;
+
+ return None;
+}
diff --git a/src/plugins/android/sdkmanageroutputparser.h b/src/plugins/android/sdkmanageroutputparser.h
new file mode 100644
index 0000000000..5f0534dfbd
--- /dev/null
+++ b/src/plugins/android/sdkmanageroutputparser.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+#include "androidconstants.h"
+#include "androidsdkpackage.h"
+
+#include <utils/filepath.h>
+
+#include <QVersionNumber>
+
+namespace Android {
+namespace Internal {
+/*!
+ \class SdkManagerOutputParser
+ \brief The SdkManagerOutputParser class is a helper class to parse the output of the \c sdkmanager
+ commands.
+ */
+class SdkManagerOutputParser
+{
+ class GenericPackageData
+ {
+ public:
+ bool isValid() const { return !revision.isNull() && !description.isNull(); }
+ QStringList headerParts;
+ QVersionNumber revision;
+ QString description;
+ Utils::FilePath installedLocation;
+ QMap<QString, QString> extraData;
+ };
+
+public:
+ enum MarkerTag
+ {
+ None = 0x001,
+ InstalledPackagesMarker = 0x002,
+ AvailablePackagesMarkers = 0x004,
+ AvailableUpdatesMarker = 0x008,
+ EmptyMarker = 0x010,
+ PlatformMarker = 0x020,
+ SystemImageMarker = 0x040,
+ BuildToolsMarker = 0x080,
+ SdkToolsMarker = 0x100,
+ PlatformToolsMarker = 0x200,
+ EmulatorToolsMarker = 0x400,
+ NdkMarker = 0x800,
+ ExtrasMarker = 0x1000,
+ CmdlineSdkToolsMarker = 0x2000,
+ GenericToolMarker = 0x4000,
+ SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker
+ };
+
+ SdkManagerOutputParser(AndroidSdkPackageList &container) : m_packages(container) {}
+ void parsePackageListing(const QString &output);
+
+ AndroidSdkPackageList &m_packages;
+
+private:
+ void compilePackageAssociations();
+ void parsePackageData(MarkerTag packageMarker, const QStringList &data);
+ bool parseAbstractData(GenericPackageData &output, const QStringList &input, int minParts,
+ const QString &logStrTag,
+ const QStringList &extraKeys = QStringList()) const;
+ AndroidSdkPackage *parsePlatform(const QStringList &data) const;
+ QPair<SystemImage *, int> parseSystemImage(const QStringList &data) const;
+ BuildTools *parseBuildToolsPackage(const QStringList &data) const;
+ SdkTools *parseSdkToolsPackage(const QStringList &data) const;
+ PlatformTools *parsePlatformToolsPackage(const QStringList &data) const;
+ EmulatorTools *parseEmulatorToolsPackage(const QStringList &data) const;
+ Ndk *parseNdkPackage(const QStringList &data) const;
+ ExtraTools *parseExtraToolsPackage(const QStringList &data) const;
+ GenericSdkPackage *parseGenericTools(const QStringList &data) const;
+ MarkerTag parseMarkers(const QString &line);
+
+ MarkerTag m_currentSection = MarkerTag::None;
+ QHash<AndroidSdkPackage *, int> m_systemImages;
+};
+} // namespace Internal
+} // namespace Android