// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "avdmanageroutputparser.h" #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace Android::Internal { static Q_LOGGING_CATEGORY(avdOutputParserLog, "qtc.android.avdOutputParser", QtWarningMsg) // Avd list keys to parse avd const char avdInfoNameKey[] = "Name:"; const char avdInfoPathKey[] = "Path:"; const char avdInfoAbiKey[] = "abi.type"; const char avdInfoTargetKey[] = "target"; const char avdInfoErrorKey[] = "Error:"; const char avdManufacturerError[] = "no longer exists as a device"; /*! Parses the \a line for a [spaces]key[spaces]value[spaces] pattern and returns \c true if the key is found, \c false otherwise. The value is copied into \a value. */ static bool valueForKey(const QString &key, const QString &line, QString *value = nullptr) { const QString trimmedInput = line.trimmed(); if (trimmedInput.startsWith(key)) { if (value) *value = trimmedInput.section(key, 1, 1).trimmed(); return true; } return false; } static std::optional parseAvd(const QStringList &deviceInfo) { AndroidDeviceInfo avd; for (const QString &line : deviceInfo) { QString value; if (valueForKey(avdInfoErrorKey, line)) { qCDebug(avdOutputParserLog) << "Avd Parsing: Skip avd device. Error key found:" << line; return {}; } else if (valueForKey(avdInfoNameKey, line, &value)) { avd.avdName = value; } else if (valueForKey(avdInfoPathKey, line, &value)) { const FilePath avdPath = FilePath::fromUserInput(value); avd.avdPath = avdPath; if (avdPath.exists()) { // Get ABI. const FilePath configFile = avdPath.pathAppended("config.ini"); QSettings config(configFile.toFSPathString(), QSettings::IniFormat); value = config.value(avdInfoAbiKey).toString(); if (!value.isEmpty()) avd.cpuAbi << value; else qCDebug(avdOutputParserLog) << "Avd Parsing: Cannot find ABI:" << configFile; // Get Target const QString avdInfoFileName = avd.avdName + ".ini"; const FilePath avdInfoFile = avdPath.parentDir().pathAppended(avdInfoFileName); QSettings avdInfo(avdInfoFile.toFSPathString(), QSettings::IniFormat); value = avdInfo.value(avdInfoTargetKey).toString(); if (!value.isEmpty()) avd.sdk = platformNameToApiLevel(value); else qCDebug(avdOutputParserLog) << "Avd Parsing: Cannot find sdk API:" << avdInfoFile.toUserOutput(); } } } if (avd != AndroidDeviceInfo()) return avd; return {}; } ParsedAvdList parseAvdList(const QString &output) { AndroidDeviceInfoList avdList; FilePaths errorPaths; QStringList avdInfo; using ErrorPath = FilePath; using AvdResult = std::variant; const auto parseAvdInfo = [](const QStringList &avdInfo) { if (!avdInfo.filter(avdManufacturerError).isEmpty()) { for (const QString &line : avdInfo) { QString value; if (valueForKey(avdInfoPathKey, line, &value)) return AvdResult(FilePath::fromString(value)); // error path } } else if (std::optional avd = parseAvd(avdInfo)) { // armeabi-v7a devices can also run armeabi code if (avd->cpuAbi.contains(Constants::ANDROID_ABI_ARMEABI_V7A)) avd->cpuAbi << Constants::ANDROID_ABI_ARMEABI; avd->state = IDevice::DeviceConnected; avd->type = IDevice::Emulator; return AvdResult(*avd); } else { qCDebug(avdOutputParserLog) << "Avd Parsing: Parsing failed: " << avdInfo; } return AvdResult(); }; const auto lines = output.split('\n'); for (const QString &line : lines) { if (line.startsWith("---------") || line.isEmpty()) { const AvdResult result = parseAvdInfo(avdInfo); if (auto info = std::get_if(&result)) avdList << *info; else if (auto errorPath = std::get_if(&result)) errorPaths << *errorPath; avdInfo.clear(); } else { avdInfo << line; } } return {Utils::sorted(std::move(avdList)), errorPaths}; } int platformNameToApiLevel(const QString &platformName) { int apiLevel = -1; static const QRegularExpression re("(android-)(?[0-9A-Z]{1,})", QRegularExpression::CaseInsensitiveOption); QRegularExpressionMatch match = re.match(platformName); if (match.hasMatch()) { QString apiLevelStr = match.captured("apiLevel"); bool isUInt; apiLevel = apiLevelStr.toUInt(&isUInt); if (!isUInt) { if (apiLevelStr == 'Q') apiLevel = 29; else if (apiLevelStr == 'R') apiLevel = 30; else if (apiLevelStr == 'S') apiLevel = 31; else if (apiLevelStr == "Tiramisu") apiLevel = 33; } } return apiLevel; } QString convertNameToExtension(const QString &name) { static const QRegularExpression rexEx(R"(-ext(\d+)$)"); const QRegularExpressionMatch match = rexEx.match(name); if (match.hasMatch()) return " Extension " + match.captured(1); return {}; } } // namespace Android::Internal