Android: Make avd output parsing testable

And add some tests

Change-Id: Ife0e0c60f55251a1ac23215055ece8fb01478d59
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Eike Ziller
2021-05-06 15:19:56 +02:00
parent e9abd60732
commit c213b93aff
16 changed files with 792 additions and 253 deletions

View File

@@ -14,6 +14,7 @@ add_qtc_plugin(Android
androiddeployqtstep.cpp androiddeployqtstep.h
androiddevice.cpp androiddevice.h
androiddevicedialog.cpp androiddevicedialog.h androiddevicedialog.ui
androiddeviceinfo.cpp androiddeviceinfo.h
androiderrormessage.cpp androiderrormessage.h
androidextralibrarylistmodel.cpp androidextralibrarylistmodel.h
androidglobal.h
@@ -43,6 +44,7 @@ add_qtc_plugin(Android
androidsignaloperation.cpp androidsignaloperation.h
androidtoolchain.cpp androidtoolchain.h
avddialog.cpp avddialog.h
avdmanageroutputparser.cpp avdmanageroutputparser.h
certificatesmodel.cpp certificatesmodel.h
createandroidmanifestwizard.cpp createandroidmanifestwizard.h
javaeditor.cpp javaeditor.h

View File

@@ -27,6 +27,7 @@ HEADERS += \
javaparser.h \
androidplugin.h \
androiddevice.h \
androiddeviceinfo.h \
androidqmltoolingsupport.h \
androidmanifesteditorfactory.h \
androidmanifesteditor.h \
@@ -40,6 +41,7 @@ HEADERS += \
javaeditor.h \
javaindenter.h \
avddialog.h \
avdmanageroutputparser.h \
android_global.h \
androidbuildapkstep.h \
androidsdkmanager.h \
@@ -74,6 +76,7 @@ SOURCES += \
javaparser.cpp \
androidplugin.cpp \
androiddevice.cpp \
androiddeviceinfo.cpp \
androidqmltoolingsupport.cpp \
androidmanifesteditorfactory.cpp \
androidmanifesteditor.cpp \
@@ -87,6 +90,7 @@ SOURCES += \
javaeditor.cpp \
javaindenter.cpp \
avddialog.cpp \
avdmanageroutputparser.cpp \
androidbuildapkstep.cpp \
androidsdkmanager.cpp \
androidavdmanager.cpp \

View File

@@ -40,6 +40,8 @@ Project {
"androiddevicedialog.ui",
"androiddevice.cpp",
"androiddevice.h",
"androiddeviceinfo.cpp",
"androiddeviceinfo.h",
"androiderrormessage.h",
"androiderrormessage.cpp",
"androidextralibrarylistmodel.cpp",
@@ -100,6 +102,8 @@ Project {
"androidtoolchain.h",
"avddialog.cpp",
"avddialog.h",
"avdmanageroutputparser.cpp",
"avdmanageroutputparser.h",
"certificatesmodel.cpp",
"certificatesmodel.h",
"createandroidmanifestwizard.h",

View File

@@ -24,6 +24,8 @@
****************************************************************************/
#include "androidavdmanager.h"
#include "avdmanageroutputparser.h"
#include <coreplugin/icore.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
@@ -51,17 +53,6 @@ namespace Internal {
using namespace std;
// 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 avdInfoSdcardKey[] = "Sdcard";
const char avdInfoTargetTypeKey[] = "Target";
const char avdInfoDeviceKey[] = "Device";
const char avdInfoSkinKey[] = "Skin";
const int avdCreateTimeoutMs = 30000;
/*!
@@ -85,21 +76,6 @@ bool AndroidAvdManager::avdManagerCommand(const AndroidConfig &config, const QSt
return false;
}
/*!
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(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;
}
static bool checkForTimeout(const chrono::steady_clock::time_point &start,
int msecs = 3000)
{
@@ -203,25 +179,8 @@ static void avdProcessFinished(int exitCode, QProcess *p)
p->deleteLater();
}
/*!
\class AvdManagerOutputParser
\brief The AvdManagerOutputParser class is a helper class to parse the output of the avdmanager
commands.
*/
class AvdManagerOutputParser
{
public:
AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config);
AndroidDeviceInfoList parseAvdList(const QString &output);
private:
bool parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd);
};
AndroidAvdManager::AndroidAvdManager(const AndroidConfig &config):
m_config(config),
m_parser(new AvdManagerOutputParser)
AndroidAvdManager::AndroidAvdManager(const AndroidConfig &config)
: m_config(config)
{
}
@@ -243,9 +202,69 @@ bool AndroidAvdManager::removeAvd(const QString &name) const
return response.result == Utils::SynchronousProcessResponse::Finished && response.exitCode == 0;
}
static void avdConfigEditManufacturerTag(const QString &avdPathStr, bool recoverMode = false)
{
const Utils::FilePath avdPath = Utils::FilePath::fromString(avdPathStr);
if (avdPath.exists()) {
const QString configFilePath = avdPath.pathAppended("config.ini").toString();
QFile configFile(configFilePath);
if (configFile.open(QIODevice::ReadWrite | QIODevice::Text)) {
QString newContent;
QTextStream textStream(&configFile);
while (!textStream.atEnd()) {
QString line = textStream.readLine();
if (!line.contains("hw.device.manufacturer"))
newContent.append(line + "\n");
else if (recoverMode)
newContent.append(line.replace("#", "") + "\n");
else
newContent.append("#" + line + "\n");
}
configFile.resize(0);
textStream << newContent;
configFile.close();
}
}
}
static AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config)
{
QString output;
AndroidDeviceInfoList avdList;
/*
Currenly avdmanager tool fails to parse some AVDs because the correct
device definitions at devices.xml does not have some of the newest devices.
Particularly, failing because of tag "hw.device.manufacturer", thus removing
it would make paring successful. However, it has to be returned afterwards,
otherwise, Android Studio would give an error during parsing also. So this fix
aim to keep support for Qt Creator and Android Studio.
*/
QStringList allAvdErrorPaths;
QStringList avdErrorPaths;
do {
if (!AndroidAvdManager::avdManagerCommand(config, {"list", "avd"}, &output)) {
qCDebug(avdManagerLog)
<< "Avd list command failed" << output << config.sdkToolsVersion();
return {};
}
avdErrorPaths.clear();
avdList = parseAvdList(output, &avdErrorPaths);
allAvdErrorPaths << avdErrorPaths;
for (const QString &avdPathStr : qAsConst(avdErrorPaths))
avdConfigEditManufacturerTag(avdPathStr); // comment out manufacturer tag
} while (!avdErrorPaths.isEmpty()); // try again
for (const QString &avdPathStr : qAsConst(allAvdErrorPaths))
avdConfigEditManufacturerTag(avdPathStr, true); // re-add manufacturer tag
return avdList;
}
QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const
{
return Utils::runAsync(&AvdManagerOutputParser::listVirtualDevices, m_parser.get(), m_config);
return Utils::runAsync(listVirtualDevices, m_config);
}
QString AndroidAvdManager::startAvd(const QString &name) const
@@ -353,147 +372,7 @@ bool AndroidAvdManager::waitForBooted(const QString &serialNumber,
return false;
}
/* Currenly avdmanager tool fails to parse some AVDs because the correct
* device definitions at devices.xml does not have some of the newest devices.
* Particularly, failing because of tag "hw.device.manufacturer", thus removing
* it would make paring successful. However, it has to be returned afterwards,
* otherwise, Android Studio would give an error during parsing also. So this fix
* aim to keep support for Qt Creator and Android Studio.
*/
static const QString avdManufacturerError = "no longer exists as a device";
static QStringList avdErrorPaths;
static void AvdConfigEditManufacturerTag(const QString &avdPathStr, bool recoverMode = false)
{
const Utils::FilePath avdPath = Utils::FilePath::fromString(avdPathStr);
if (avdPath.exists()) {
const QString configFilePath = avdPath.pathAppended("config.ini").toString();
QFile configFile(configFilePath);
if (configFile.open(QIODevice::ReadWrite | QIODevice::Text)) {
QString newContent;
QTextStream textStream(&configFile);
while (!textStream.atEnd()) {
QString line = textStream.readLine();
if (!line.contains("hw.device.manufacturer"))
newContent.append(line + "\n");
else if (recoverMode)
newContent.append(line.replace("#", "") + "\n");
else
newContent.append("#" + line + "\n");
}
configFile.resize(0);
textStream << newContent;
configFile.close();
}
}
}
AndroidDeviceInfoList AvdManagerOutputParser::listVirtualDevices(const AndroidConfig &config)
{
QString output;
AndroidDeviceInfoList avdList;
do {
if (!AndroidAvdManager::avdManagerCommand(config, {"list", "avd"}, &output)) {
qCDebug(avdManagerLog)
<< "Avd list command failed" << output << config.sdkToolsVersion();
return {};
}
avdList = parseAvdList(output);
} while (output.contains(avdManufacturerError));
for (const QString &avdPathStr : qAsConst(avdErrorPaths))
AvdConfigEditManufacturerTag(avdPathStr, true);
return avdList;
}
AndroidDeviceInfoList AvdManagerOutputParser::parseAvdList(const QString &output)
{
AndroidDeviceInfoList avdList;
QStringList avdInfo;
auto parseAvdInfo = [&avdInfo, &avdList, this] () {
AndroidDeviceInfo avd;
if (!avdInfo.filter(avdManufacturerError).isEmpty()) {
for (const QString &line : avdInfo) {
QString value;
if (valueForKey(avdInfoPathKey, line, &value)) {
avdErrorPaths.append(value);
AvdConfigEditManufacturerTag(value);
}
}
} else if (parseAvd(avdInfo, &avd)) {
// armeabi-v7a devices can also run armeabi code
if (avd.cpuAbi.contains(ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A))
avd.cpuAbi << ProjectExplorer::Constants::ANDROID_ABI_ARMEABI;
avd.state = AndroidDeviceInfo::OkState;
avd.type = AndroidDeviceInfo::Emulator;
avdList << avd;
} else {
qCDebug(avdManagerLog) << "Avd Parsing: Parsing failed: " << avdInfo;
}
avdInfo.clear();
};
for (const QString &line : output.split('\n')) {
if (line.startsWith("---------") || line.isEmpty())
parseAvdInfo();
else
avdInfo << line;
}
Utils::sort(avdList);
return avdList;
}
bool AvdManagerOutputParser::parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd)
{
QTC_ASSERT(avd, return false);
for (const QString &line : deviceInfo) {
QString value;
if (valueForKey(avdInfoErrorKey, line)) {
qCDebug(avdManagerLog) << "Avd Parsing: Skip avd device. Error key found:" << line;
return false;
} else if (valueForKey(avdInfoNameKey, line, &value)) {
avd->avdname = value;
} else if (valueForKey(avdInfoPathKey, line, &value)) {
const Utils::FilePath avdPath = Utils::FilePath::fromString(value);
if (avdPath.exists())
{
// Get ABI.
const Utils::FilePath configFile = avdPath.pathAppended("config.ini");
QSettings config(configFile.toString(), QSettings::IniFormat);
value = config.value(avdInfoAbiKey).toString();
if (!value.isEmpty())
avd->cpuAbi << value;
else
qCDebug(avdManagerLog) << "Avd Parsing: Cannot find ABI:" << configFile;
// Get Target
const QString avdInfoFileName = avd->avdname + ".ini";
const Utils::FilePath
avdInfoFile = avdPath.parentDir().pathAppended(avdInfoFileName);
QSettings avdInfo(avdInfoFile.toString(), QSettings::IniFormat);
value = avdInfo.value(avdInfoTargetKey).toString();
if (!value.isEmpty())
avd->sdk = value.section('-', -1).toInt();
else
qCDebug(avdManagerLog) << "Avd Parsing: Cannot find sdk API:" << avdInfoFile.toString();
}
} else if (valueForKey(avdInfoDeviceKey, line, &value)) {
avd->avdDevice = value.remove(0, 2);
} else if (valueForKey(avdInfoTargetTypeKey, line, &value)) {
avd->avdTarget = value.remove(0, 2);
} else if (valueForKey(avdInfoSkinKey, line, &value)) {
avd->avdSkin = value.remove(0, 2);
} else if (valueForKey(avdInfoSdcardKey, line, &value)) {
avd->avdSdcardSize = value.remove(0, 2);
}
}
return true;
}
} // namespace Internal
} // namespace Android

View File

@@ -32,8 +32,6 @@
namespace Android {
namespace Internal {
class AvdManagerOutputParser;
class AndroidAvdManager
{
Q_DECLARE_TR_FUNCTIONS(Android::Internal::AndroidAvdManager)
@@ -62,7 +60,6 @@ private:
private:
const AndroidConfig &m_config;
std::unique_ptr<AvdManagerOutputParser> m_parser;
};
} // namespace Internal

View File

@@ -1388,31 +1388,6 @@ Environment AndroidConfigurations::toolsEnvironment(const AndroidConfig &config)
return env;
}
/**
* Workaround for '????????????' serial numbers
* @return ("-d") for buggy devices, ("-s", <serial no>) for normal
*/
QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber)
{
if (serialNumber.startsWith(QLatin1String("????")))
return QStringList("-d");
return QStringList({"-s", serialNumber});
}
bool AndroidDeviceInfo::operator<(const AndroidDeviceInfo &other) const
{
if (serialNumber.contains("????") != other.serialNumber.contains("????"))
return !serialNumber.contains("????");
if (type != other.type)
return type == AndroidDeviceInfo::Hardware;
if (sdk != other.sdk)
return sdk < other.sdk;
if (avdname != other.avdname)
return avdname < other.avdname;
return serialNumber < other.serialNumber;
}
const AndroidConfig &AndroidConfigurations::currentConfig()
{
return m_instance->m_config; // ensure that m_instance is initialized
@@ -1561,13 +1536,4 @@ void AndroidConfigurations::updateAndroidDevice()
AndroidConfigurations *AndroidConfigurations::m_instance = nullptr;
QDebug &operator<<(QDebug &stream, const AndroidDeviceInfo &device)
{
stream << "Type:"<< (device.type == AndroidDeviceInfo::Emulator ? "Emulator" : "Device")
<< ", ABI:" << device.cpuAbi << ", Serial:" << device.serialNumber
<< ", Name:" << device.avdname << ", API:" << device.sdk
<< ", Authorised:" << !device.unauthorized;
return stream;
}
} // namespace Android

View File

@@ -26,8 +26,9 @@
#pragma once
#include "android_global.h"
#include "androidsdkpackage.h"
#include "androiddeviceinfo.h"
#include "androidsdkmanager.h"
#include "androidsdkpackage.h"
#include <projectexplorer/toolchain.h>
#include <qtsupport/qtversionmanager.h>
@@ -58,31 +59,6 @@ class AndroidSdkManager;
class AndroidPluginPrivate;
}
class AndroidDeviceInfo
{
public:
QString serialNumber;
QString avdname;
QStringList cpuAbi;
QString avdTarget;
QString avdDevice;
QString avdSkin;
QString avdSdcardSize;
int sdk = -1;
enum State { OkState, UnAuthorizedState, OfflineState };
State state = OfflineState;
bool unauthorized = false;
enum AndroidDeviceType { Hardware, Emulator };
AndroidDeviceType type = Emulator;
static QStringList adbSelector(const QString &serialNumber);
bool isValid() const { return !serialNumber.isEmpty() || !avdname.isEmpty(); }
bool operator<(const AndroidDeviceInfo &other) const;
};
using AndroidDeviceInfoList = QList<AndroidDeviceInfo>;
class CreateAvdInfo
{
public:
@@ -269,8 +245,4 @@ private:
bool m_force32bit;
};
QDebug &operator<<(QDebug &stream, const AndroidDeviceInfo &device);
} // namespace Android
Q_DECLARE_METATYPE(Android::AndroidDeviceInfo)

View File

@@ -0,0 +1,72 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "androiddeviceinfo.h"
namespace Android {
/**
* Workaround for '????????????' serial numbers
* @return ("-d") for buggy devices, ("-s", <serial no>) for normal
*/
QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber)
{
if (serialNumber.startsWith(QLatin1String("????")))
return QStringList("-d");
return QStringList({"-s", serialNumber});
}
bool AndroidDeviceInfo::operator<(const AndroidDeviceInfo &other) const
{
if (serialNumber.contains("????") != other.serialNumber.contains("????"))
return !serialNumber.contains("????");
if (type != other.type)
return type == AndroidDeviceInfo::Hardware;
if (sdk != other.sdk)
return sdk < other.sdk;
if (avdname != other.avdname)
return avdname < other.avdname;
return serialNumber < other.serialNumber;
}
bool AndroidDeviceInfo::operator==(const AndroidDeviceInfo &other) const
{
return serialNumber == other.serialNumber && avdname == other.avdname && cpuAbi == other.cpuAbi
&& avdTarget == other.avdTarget && avdDevice == other.avdDevice
&& avdSkin == other.avdSkin && avdSdcardSize == other.avdSdcardSize && sdk == other.sdk
&& state == other.state && unauthorized == other.unauthorized && type == other.type;
}
QDebug &operator<<(QDebug &stream, const AndroidDeviceInfo &device)
{
stream << "Type:" << (device.type == AndroidDeviceInfo::Emulator ? "Emulator" : "Device")
<< ", ABI:" << device.cpuAbi << ", Serial:" << device.serialNumber
<< ", Name:" << device.avdname << ", API:" << device.sdk
<< ", Authorised:" << !device.unauthorized;
return stream;
}
} // namespace Android

View File

@@ -0,0 +1,65 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QDebug>
#include <QMetaType>
#include <QString>
#include <QStringList>
namespace Android {
class AndroidDeviceInfo
{
public:
QString serialNumber;
QString avdname;
QStringList cpuAbi;
QString avdTarget;
QString avdDevice;
QString avdSkin;
QString avdSdcardSize;
int sdk = -1;
enum State { OkState, UnAuthorizedState, OfflineState };
State state = OfflineState;
bool unauthorized = false;
enum AndroidDeviceType { Hardware, Emulator };
AndroidDeviceType type = Emulator;
static QStringList adbSelector(const QString &serialNumber);
bool isValid() const { return !serialNumber.isEmpty() || !avdname.isEmpty(); }
bool operator<(const AndroidDeviceInfo &other) const;
bool operator==(const AndroidDeviceInfo &other) const; // should be = default with C++20
};
using AndroidDeviceInfoList = QList<AndroidDeviceInfo>;
QDebug &operator<<(QDebug &stream, const AndroidDeviceInfo &device);
} // namespace Android
Q_DECLARE_METATYPE(Android::AndroidDeviceInfo)

View File

@@ -0,0 +1,164 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "avdmanageroutputparser.h"
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <utils/variant.h>
#include <QLoggingCategory>
#include <QSettings>
namespace {
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 avdInfoSdcardKey[] = "Sdcard";
const char avdInfoTargetTypeKey[] = "Target";
const char avdInfoDeviceKey[] = "Device";
const char avdInfoSkinKey[] = "Skin";
namespace Android {
namespace Internal {
/*!
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(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;
}
static bool parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd)
{
QTC_ASSERT(avd, return false);
for (const QString &line : deviceInfo) {
QString value;
if (valueForKey(avdInfoErrorKey, line)) {
qCDebug(avdOutputParserLog) << "Avd Parsing: Skip avd device. Error key found:" << line;
return false;
} else if (valueForKey(avdInfoNameKey, line, &value)) {
avd->avdname = value;
} else if (valueForKey(avdInfoPathKey, line, &value)) {
const Utils::FilePath avdPath = Utils::FilePath::fromString(value);
if (avdPath.exists()) {
// Get ABI.
const Utils::FilePath configFile = avdPath.pathAppended("config.ini");
QSettings config(configFile.toString(), 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 Utils::FilePath avdInfoFile = avdPath.parentDir().pathAppended(
avdInfoFileName);
QSettings avdInfo(avdInfoFile.toString(), QSettings::IniFormat);
value = avdInfo.value(avdInfoTargetKey).toString();
if (!value.isEmpty())
avd->sdk = value.section('-', -1).toInt();
else
qCDebug(avdOutputParserLog)
<< "Avd Parsing: Cannot find sdk API:" << avdInfoFile.toString();
}
} else if (valueForKey(avdInfoDeviceKey, line, &value)) {
avd->avdDevice = value.remove(0, 2);
} else if (valueForKey(avdInfoTargetTypeKey, line, &value)) {
avd->avdTarget = value.remove(0, 2);
} else if (valueForKey(avdInfoSkinKey, line, &value)) {
avd->avdSkin = value.remove(0, 2);
} else if (valueForKey(avdInfoSdcardKey, line, &value)) {
avd->avdSdcardSize = value.remove(0, 2);
}
}
return true;
}
AndroidDeviceInfoList parseAvdList(const QString &output, QStringList *avdErrorPaths)
{
QTC_CHECK(avdErrorPaths);
AndroidDeviceInfoList avdList;
QStringList avdInfo;
using ErrorPath = QString;
using AvdResult = Utils::variant<std::monostate, AndroidDeviceInfo, ErrorPath>;
const auto parseAvdInfo = [](const QStringList &avdInfo) {
AndroidDeviceInfo avd;
if (!avdInfo.filter(avdManufacturerError).isEmpty()) {
for (const QString &line : avdInfo) {
QString value;
if (valueForKey(avdInfoPathKey, line, &value))
return AvdResult(value); // error path
}
} else if (parseAvd(avdInfo, &avd)) {
// armeabi-v7a devices can also run armeabi code
if (avd.cpuAbi.contains(ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A))
avd.cpuAbi << ProjectExplorer::Constants::ANDROID_ABI_ARMEABI;
avd.state = AndroidDeviceInfo::OkState;
avd.type = AndroidDeviceInfo::Emulator;
return AvdResult(avd);
} else {
qCDebug(avdOutputParserLog) << "Avd Parsing: Parsing failed: " << avdInfo;
}
return AvdResult();
};
for (const QString &line : output.split('\n')) {
if (line.startsWith("---------") || line.isEmpty()) {
const AvdResult result = parseAvdInfo(avdInfo);
if (auto info = Utils::get_if<AndroidDeviceInfo>(&result))
avdList << *info;
else if (auto errorPath = Utils::get_if<ErrorPath>(&result))
*avdErrorPaths << *errorPath;
avdInfo.clear();
} else {
avdInfo << line;
}
}
Utils::sort(avdList);
return avdList;
}
} // namespace Internal
} // namespace Android

View File

@@ -0,0 +1,36 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "androiddeviceinfo.h"
namespace Android {
namespace Internal {
const char avdManufacturerError[] = "no longer exists as a device";
AndroidDeviceInfoList parseAvdList(const QString &output, QStringList *avdErrorPaths);
} // namespace Internal
} // namespace Android

View File

@@ -1,5 +1,6 @@
add_subdirectory(aggregation)
add_subdirectory(algorithm)
add_subdirectory(android)
add_subdirectory(changeset)
add_subdirectory(cplusplus)
add_subdirectory(debugger)

View File

@@ -0,0 +1,18 @@
add_qtc_test(tst_avdmanageroutputparser
DEPENDS Utils
INCLUDES
"${PROJECT_SOURCE_DIR}/src/plugins"
"${PROJECT_SOURCE_DIR}/src/plugins/android"
SOURCES
tst_avdmanageroutputparser.cpp
"${PROJECT_SOURCE_DIR}/src/plugins/android/avdmanageroutputparser.cpp"
"${PROJECT_SOURCE_DIR}/src/plugins/android/avdmanageroutputparser.h"
"${PROJECT_SOURCE_DIR}/src/plugins/android/androiddeviceinfo.cpp"
"${PROJECT_SOURCE_DIR}/src/plugins/android/androiddeviceinfo.h"
)
qtc_add_resources(tst_avdmanageroutputparser tst_avdmanageroutputparser_rcc
FILES
Test.avd/config.ini
TestTablet.avd/config.ini
)

View File

@@ -0,0 +1,119 @@
PlayStore.enabled=no
abi.type=x86
avd.id=<build>
avd.ini.encoding=UTF-8
avd.name=<build>
disk.cachePartition=yes
disk.cachePartition.size=66MB
disk.dataPartition.path=<temp>
disk.dataPartition.size=800M
disk.systemPartition.size=0
disk.vendorPartition.size=0
fastboot.forceChosenSnapshotBoot=no
fastboot.forceColdBoot=no
fastboot.forceFastBoot=yes
hw.accelerometer=yes
hw.arc=no
hw.arc.autologin=no
hw.audioInput=yes
hw.audioOutput=yes
hw.battery=yes
hw.camera.back=emulated
hw.camera.front=none
hw.cpu.arch=x86
hw.cpu.ncore=4
hw.dPad=no
hw.device.hash2=MD5:5c288d27461585ecc73a535555e7cf61
hw.device.manufacturer=Google
hw.device.name=Galaxy Nexus
hw.display1.density=0
hw.display1.flag=0
hw.display1.height=0
hw.display1.width=0
hw.display1.xOffset=-1
hw.display1.yOffset=-1
hw.display2.density=0
hw.display2.flag=0
hw.display2.height=0
hw.display2.width=0
hw.display2.xOffset=-1
hw.display2.yOffset=-1
hw.display3.density=0
hw.display3.flag=0
hw.display3.height=0
hw.display3.width=0
hw.display3.xOffset=-1
hw.display3.yOffset=-1
hw.displayRegion.0.1.height=0
hw.displayRegion.0.1.width=0
hw.displayRegion.0.1.xOffset=-1
hw.displayRegion.0.1.yOffset=-1
hw.displayRegion.0.2.height=0
hw.displayRegion.0.2.width=0
hw.displayRegion.0.2.xOffset=-1
hw.displayRegion.0.2.yOffset=-1
hw.displayRegion.0.3.height=0
hw.displayRegion.0.3.width=0
hw.displayRegion.0.3.xOffset=-1
hw.displayRegion.0.3.yOffset=-1
hw.gltransport=pipe
hw.gltransport.asg.dataRingSize=32768
hw.gltransport.asg.writeBufferSize=1048576
hw.gltransport.asg.writeStepSize=4096
hw.gltransport.drawFlushInterval=800
hw.gps=yes
hw.gpu.enabled=no
hw.gpu.mode=auto
hw.gsmModem=yes
hw.gyroscope=yes
hw.initialOrientation=portrait
hw.keyboard=no
hw.keyboard.charmap=qwerty2
hw.keyboard.lid=yes
hw.lcd.backlight=yes
hw.lcd.density=320
hw.lcd.depth=16
hw.lcd.height=1280
hw.lcd.vsync=60
hw.lcd.width=720
hw.mainKeys=no
hw.ramSize=1024
hw.rotaryInput=no
hw.screen=multi-touch
hw.sdCard=no
hw.sensor.hinge=yes
hw.sensor.hinge.count=0
hw.sensor.hinge.fold_to_displayRegion.0.1_at_posture=1
hw.sensor.hinge.sub_type=0
hw.sensor.hinge.type=0
hw.sensor.roll=no
hw.sensor.roll.count=0
hw.sensor.roll.resize_to_displayRegion.0.1_at_posture=6
hw.sensor.roll.resize_to_displayRegion.0.2_at_posture=6
hw.sensor.roll.resize_to_displayRegion.0.3_at_posture=6
hw.sensors.gyroscope_uncalibrated=yes
hw.sensors.heart_rate=no
hw.sensors.humidity=yes
hw.sensors.light=yes
hw.sensors.magnetic_field=yes
hw.sensors.magnetic_field_uncalibrated=yes
hw.sensors.orientation=yes
hw.sensors.pressure=yes
hw.sensors.proximity=yes
hw.sensors.temperature=yes
hw.trackBall=no
hw.useext4=yes
image.sysdir.1=system-images/android-30/google_apis/x86/
kernel.newDeviceNaming=autodetect
kernel.supportsYaffs2=autodetect
runtime.network.latency=None
runtime.network.speed=Full
sdcard.size=512 MB
showDeviceFrame=yes
skin.path=_no_skin
tag.display=Google APIs
tag.id=google_apis
test.delayAdbTillBootComplete=0
test.monitorAdb=0
test.quitAfterBootTimeOut=-1
vm.heapSize=112M

View File

@@ -0,0 +1,119 @@
PlayStore.enabled=no
abi.type=x86
avd.id=<build>
avd.ini.encoding=UTF-8
avd.name=<build>
disk.cachePartition=yes
disk.cachePartition.size=66MB
disk.dataPartition.path=<temp>
disk.dataPartition.size=800M
disk.systemPartition.size=0
disk.vendorPartition.size=0
fastboot.forceChosenSnapshotBoot=no
fastboot.forceColdBoot=no
fastboot.forceFastBoot=yes
hw.accelerometer=yes
hw.arc=no
hw.arc.autologin=no
hw.audioInput=yes
hw.audioOutput=yes
hw.battery=yes
hw.camera.back=emulated
hw.camera.front=none
hw.cpu.arch=x86
hw.cpu.ncore=4
hw.dPad=no
hw.device.hash2=MD5:6f5876a1c548aef127b373f80cac4953
hw.device.manufacturer=Generic
hw.device.name=7in WSVGA (Tablet)
hw.display1.density=0
hw.display1.flag=0
hw.display1.height=0
hw.display1.width=0
hw.display1.xOffset=-1
hw.display1.yOffset=-1
hw.display2.density=0
hw.display2.flag=0
hw.display2.height=0
hw.display2.width=0
hw.display2.xOffset=-1
hw.display2.yOffset=-1
hw.display3.density=0
hw.display3.flag=0
hw.display3.height=0
hw.display3.width=0
hw.display3.xOffset=-1
hw.display3.yOffset=-1
hw.displayRegion.0.1.height=0
hw.displayRegion.0.1.width=0
hw.displayRegion.0.1.xOffset=-1
hw.displayRegion.0.1.yOffset=-1
hw.displayRegion.0.2.height=0
hw.displayRegion.0.2.width=0
hw.displayRegion.0.2.xOffset=-1
hw.displayRegion.0.2.yOffset=-1
hw.displayRegion.0.3.height=0
hw.displayRegion.0.3.width=0
hw.displayRegion.0.3.xOffset=-1
hw.displayRegion.0.3.yOffset=-1
hw.gltransport=pipe
hw.gltransport.asg.dataRingSize=32768
hw.gltransport.asg.writeBufferSize=1048576
hw.gltransport.asg.writeStepSize=4096
hw.gltransport.drawFlushInterval=800
hw.gps=yes
hw.gpu.enabled=no
hw.gpu.mode=auto
hw.gsmModem=yes
hw.gyroscope=yes
hw.initialOrientation=portrait
hw.keyboard=no
hw.keyboard.charmap=qwerty2
hw.keyboard.lid=yes
hw.lcd.backlight=yes
hw.lcd.density=160
hw.lcd.depth=16
hw.lcd.height=600
hw.lcd.vsync=60
hw.lcd.width=1024
hw.mainKeys=no
hw.ramSize=512
hw.rotaryInput=no
hw.screen=multi-touch
hw.sdCard=no
hw.sensor.hinge=yes
hw.sensor.hinge.count=0
hw.sensor.hinge.fold_to_displayRegion.0.1_at_posture=1
hw.sensor.hinge.sub_type=0
hw.sensor.hinge.type=0
hw.sensor.roll=no
hw.sensor.roll.count=0
hw.sensor.roll.resize_to_displayRegion.0.1_at_posture=6
hw.sensor.roll.resize_to_displayRegion.0.2_at_posture=6
hw.sensor.roll.resize_to_displayRegion.0.3_at_posture=6
hw.sensors.gyroscope_uncalibrated=yes
hw.sensors.heart_rate=no
hw.sensors.humidity=yes
hw.sensors.light=yes
hw.sensors.magnetic_field=yes
hw.sensors.magnetic_field_uncalibrated=yes
hw.sensors.orientation=yes
hw.sensors.pressure=yes
hw.sensors.proximity=yes
hw.sensors.temperature=yes
hw.trackBall=no
hw.useext4=yes
image.sysdir.1=system-images/android-30/google_apis/x86/
kernel.newDeviceNaming=autodetect
kernel.supportsYaffs2=autodetect
runtime.network.latency=None
runtime.network.speed=Full
sdcard.size=512 MB
showDeviceFrame=yes
skin.path=_no_skin
tag.display=Google APIs
tag.id=google_apis
test.delayAdbTillBootComplete=0
test.monitorAdb=0
test.quitAfterBootTimeOut=-1
vm.heapSize=112M

View File

@@ -0,0 +1,121 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "avdmanageroutputparser.h"
#include <QtTest>
using namespace Android;
using namespace Android::Internal;
class tst_AvdManagerOutputParser : public QObject
{
Q_OBJECT
private slots:
void parse_data();
void parse();
};
void tst_AvdManagerOutputParser::parse_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<AndroidDeviceInfoList>("output");
QTest::addColumn<QStringList>("errorPaths");
QTest::newRow("one") << "Available Android Virtual Devices:\n"
" Name: Test\n"
" Device: Galaxy Nexus (Google)\n"
" Path: :Test.avd\n"
" Target: Google APIs (Google Inc.)\n"
" Based on: Android API 30 Tag/ABI: google_apis/x86\n"
" Sdcard: 512 MB\n"
<< AndroidDeviceInfoList({{"",
"Test",
{"x86"},
"Google APIs (Google Inc.)",
"Galaxy Nexus (Google)",
"",
"512 MB",
-1,
AndroidDeviceInfo::OkState,
false,
AndroidDeviceInfo::Emulator}})
<< QStringList();
QTest::newRow("two") << "Available Android Virtual Devices:\n"
" Name: Test\n"
" Device: Galaxy Nexus (Google)\n"
" Path: :Test.avd\n"
" Target: Google APIs (Google Inc.)\n"
" Based on: Android API 30 Tag/ABI: google_apis/x86\n"
" Sdcard: 512 MB\n"
"---------\n"
" Name: TestTablet\n"
" Device: 7in WSVGA (Tablet) (Generic)\n"
" Path: :TestTablet.avd\n"
" Target: Google APIs (Google Inc.)\n"
" Based on: Android API 30 Tag/ABI: google_apis/x86\n"
" Sdcard: 256 MB\n"
<< AndroidDeviceInfoList({{"",
"Test",
{"x86"},
"Google APIs (Google Inc.)",
"Galaxy Nexus (Google)",
"",
"512 MB",
-1,
AndroidDeviceInfo::OkState,
false,
AndroidDeviceInfo::Emulator},
{"",
"TestTablet",
{"x86"},
"Google APIs (Google Inc.)",
"7in WSVGA (Tablet) (Generic)",
"",
"256 MB",
-1,
AndroidDeviceInfo::OkState,
false,
AndroidDeviceInfo::Emulator}})
<< QStringList();
}
void tst_AvdManagerOutputParser::parse()
{
QFETCH(QString, input);
QFETCH(AndroidDeviceInfoList, output);
QFETCH(QStringList, errorPaths);
QStringList avdErrorPaths;
const auto result = parseAvdList(input, &avdErrorPaths);
QCOMPARE(result, output);
QCOMPARE(avdErrorPaths, errorPaths);
}
QTEST_MAIN(tst_AvdManagerOutputParser)
#include "tst_avdmanageroutputparser.moc"