diff options
author | Artem Sokolovskii <[email protected]> | 2023-01-23 16:34:24 +0100 |
---|---|---|
committer | Artem Sokolovskii <[email protected]> | 2023-02-14 14:11:30 +0000 |
commit | b7fde29cdfbdf46d8d3f89d0c4dfc6328b43782d (patch) | |
tree | 837f532ec90684dc5f5b15397f99cf56cf365169 | |
parent | b4f665f8acc73c11063cc2cb6c829428fc1d1748 (diff) |
Android: Extract sdkmanageroutputparser to separate file
- Needed for testing and for improving readability
Change-Id: Ie7a716b204ae0a216d72fa0191d4d05b7708c16c
Reviewed-by: <[email protected]>
Reviewed-by: Alessandro Portale <[email protected]>
-rw-r--r-- | src/plugins/android/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/android/android.qbs | 4 | ||||
-rw-r--r-- | src/plugins/android/androidsdkmanager.cpp | 508 | ||||
-rw-r--r-- | src/plugins/android/sdkmanageroutputparser.cpp | 449 | ||||
-rw-r--r-- | src/plugins/android/sdkmanageroutputparser.h | 79 |
5 files changed, 538 insertions, 503 deletions
diff --git a/src/plugins/android/CMakeLists.txt b/src/plugins/android/CMakeLists.txt index e58c4698a53..cbd588fdee9 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 07fdc9bf018..843e1adf6d3 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 531bd0b273f..bdb3378c0ba 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 00000000000..2989ec35ddc --- /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 00000000000..5f0534dfbde --- /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 |