forked from qt-creator/qt-creator
Merge remote-tracking branch 'origin/4.3'
Conflicts: src/plugins/genericprojectmanager/genericproject.cpp src/plugins/genericprojectmanager/genericproject.h src/plugins/genericprojectmanager/genericprojectnodes.cpp src/plugins/genericprojectmanager/genericprojectnodes.h Change-Id: Ie0c870f68c8d200a75489b75860987655b2f6175
This commit is contained in:
@@ -47,7 +47,10 @@ HEADERS += \
|
||||
android_global.h \
|
||||
androidbuildapkstep.h \
|
||||
androidbuildapkwidget.h \
|
||||
androidrunnable.h
|
||||
androidrunnable.h \
|
||||
androidtoolmanager.h \
|
||||
androidsdkmanager.h \
|
||||
androidavdmanager.h
|
||||
|
||||
SOURCES += \
|
||||
androidconfigurations.cpp \
|
||||
@@ -88,7 +91,10 @@ SOURCES += \
|
||||
androidbuildapkstep.cpp \
|
||||
androidbuildapkwidget.cpp \
|
||||
androidqtsupport.cpp \
|
||||
androidrunnable.cpp
|
||||
androidrunnable.cpp \
|
||||
androidtoolmanager.cpp \
|
||||
androidsdkmanager.cpp \
|
||||
androidavdmanager.cpp
|
||||
|
||||
FORMS += \
|
||||
androidsettingswidget.ui \
|
||||
|
||||
@@ -22,6 +22,8 @@ Project {
|
||||
"android.qrc",
|
||||
"androidanalyzesupport.cpp",
|
||||
"androidanalyzesupport.h",
|
||||
"androidavdmanager.cpp",
|
||||
"androidavdmanager.h",
|
||||
"androidconfigurations.cpp",
|
||||
"androidconfigurations.h",
|
||||
"androidconstants.h",
|
||||
@@ -84,6 +86,8 @@ Project {
|
||||
"androidrunnable.h",
|
||||
"androidrunner.cpp",
|
||||
"androidrunner.h",
|
||||
"androidsdkmanager.cpp",
|
||||
"androidsdkmanager.h",
|
||||
"androidsettingspage.cpp",
|
||||
"androidsettingspage.h",
|
||||
"androidsettingswidget.cpp",
|
||||
@@ -93,6 +97,8 @@ Project {
|
||||
"androidsignaloperation.h",
|
||||
"androidtoolchain.cpp",
|
||||
"androidtoolchain.h",
|
||||
"androidtoolmanager.cpp",
|
||||
"androidtoolmanager.h",
|
||||
"avddialog.cpp",
|
||||
"avddialog.h",
|
||||
"certificatesmodel.cpp",
|
||||
|
||||
441
src/plugins/android/androidavdmanager.cpp
Normal file
441
src/plugins/android/androidavdmanager.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "androidavdmanager.h"
|
||||
|
||||
#include "androidtoolmanager.h"
|
||||
|
||||
#include "utils/algorithm.h"
|
||||
#include "utils/qtcassert.h"
|
||||
#include "utils/runextensions.h"
|
||||
#include "utils/synchronousprocess.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QLoggingCategory>
|
||||
#include <QSettings>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager")
|
||||
}
|
||||
|
||||
namespace Android {
|
||||
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 QVersionNumber avdManagerIntroVersion(25, 3 ,0);
|
||||
|
||||
const int avdCreateTimeoutMs = 30000;
|
||||
|
||||
/*!
|
||||
Runs the \c avdmanager tool specific to configuration \a config with arguments \a args. Returns
|
||||
\c true if the command is successfully executed. Output is copied into \a output. The function
|
||||
blocks the calling thread.
|
||||
*/
|
||||
static bool avdManagerCommand(const AndroidConfig config, const QStringList &args, QString *output)
|
||||
{
|
||||
QString avdManagerToolPath = config.avdManagerToolPath().toString();
|
||||
Utils::SynchronousProcess proc;
|
||||
Utils::SynchronousProcessResponse response = proc.runBlocking(avdManagerToolPath, args);
|
||||
if (response.result == Utils::SynchronousProcessResponse::Finished) {
|
||||
if (output)
|
||||
*output = response.allOutput();
|
||||
return true;
|
||||
}
|
||||
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)
|
||||
{
|
||||
bool timedOut = false;
|
||||
auto end = chrono::steady_clock::now();
|
||||
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
|
||||
timedOut = true;
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
static AndroidConfig::CreateAvdInfo createAvdCommand(const AndroidConfig config,
|
||||
const AndroidConfig::CreateAvdInfo &info)
|
||||
{
|
||||
AndroidConfig::CreateAvdInfo result = info;
|
||||
|
||||
if (!result.isValid()) {
|
||||
qCDebug(avdManagerLog) << "AVD Create failed. Invalid CreateAvdInfo" << result.name
|
||||
<< result.target.name << result.target.apiLevel;
|
||||
result.error = QApplication::translate("AndroidAvdManager",
|
||||
"Cannot create AVD. Invalid input.");
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList arguments({"create", "avd", "-k", result.target.package, "-n", result.name});
|
||||
|
||||
if (!result.abi.isEmpty()) {
|
||||
SystemImage image = Utils::findOrDefault(result.target.systemImages,
|
||||
Utils::equal(&SystemImage::abiName, result.abi));
|
||||
if (image.isValid()) {
|
||||
arguments << "-k" << image.package;
|
||||
} else {
|
||||
qCDebug(avdManagerLog) << "AVD Create failed. Cannot find system image for the platform"
|
||||
<< result.abi << result.target.name;
|
||||
result.error = QApplication::translate("AndroidAvdManager",
|
||||
"Cannot create AVD. Cannot find system image for "
|
||||
"the ABI %1(%2).").arg(result.abi).arg(result.target.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
} else {
|
||||
arguments << "-k" << result.target.package;
|
||||
}
|
||||
|
||||
if (result.sdcardSize > 0)
|
||||
arguments << "-c" << QString::fromLatin1("%1M").arg(result.sdcardSize);
|
||||
|
||||
QProcess proc;
|
||||
proc.start(config.avdManagerToolPath().toString(), arguments);
|
||||
if (!proc.waitForStarted()) {
|
||||
result.error = QApplication::translate("AndroidAvdManager",
|
||||
"Could not start process \"%1 %2\"")
|
||||
.arg(config.avdManagerToolPath().toString(), arguments.join(' '));
|
||||
return result;
|
||||
}
|
||||
QTC_CHECK(proc.state() == QProcess::Running);
|
||||
proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile"
|
||||
|
||||
auto start = chrono::steady_clock::now();
|
||||
QString errorOutput;
|
||||
QByteArray question;
|
||||
while (errorOutput.isEmpty()) {
|
||||
proc.waitForReadyRead(500);
|
||||
question += proc.readAllStandardOutput();
|
||||
if (question.endsWith(QByteArray("]:"))) {
|
||||
// truncate to last line
|
||||
int index = question.lastIndexOf(QByteArray("\n"));
|
||||
if (index != -1)
|
||||
question = question.mid(index);
|
||||
if (question.contains("hw.gpu.enabled"))
|
||||
proc.write(QByteArray("yes\n"));
|
||||
else
|
||||
proc.write(QByteArray("\n"));
|
||||
question.clear();
|
||||
}
|
||||
// The exit code is always 0, so we need to check stderr
|
||||
// For now assume that any output at all indicates a error
|
||||
errorOutput = QString::fromLocal8Bit(proc.readAllStandardError());
|
||||
if (proc.state() != QProcess::Running)
|
||||
break;
|
||||
|
||||
// For a sane input and command, process should finish before timeout.
|
||||
if (checkForTimeout(start, avdCreateTimeoutMs)) {
|
||||
result.error = QApplication::translate("AndroidAvdManager",
|
||||
"Cannot create AVD. Command timed out.");
|
||||
}
|
||||
}
|
||||
|
||||
// Kill the running process.
|
||||
if (proc.state() != QProcess::NotRunning) {
|
||||
proc.terminate();
|
||||
if (!proc.waitForFinished(3000))
|
||||
proc.kill();
|
||||
}
|
||||
|
||||
QTC_CHECK(proc.state() == QProcess::NotRunning);
|
||||
result.error = errorOutput;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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_androidTool(new AndroidToolManager(m_config)),
|
||||
m_parser(new AvdManagerOutputParser)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AndroidAvdManager::~AndroidAvdManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::avdManagerUiToolAvailable() const
|
||||
{
|
||||
return m_config.sdkToolsVersion() < avdManagerIntroVersion;
|
||||
}
|
||||
|
||||
void AndroidAvdManager::launchAvdManagerUiTool() const
|
||||
{
|
||||
if (avdManagerUiToolAvailable()) {
|
||||
m_androidTool->launchAvdManager();
|
||||
} else {
|
||||
qCDebug(avdManagerLog) << "AVD Ui tool launch failed. UI tool not available"
|
||||
<< m_config.sdkToolsVersion();
|
||||
}
|
||||
}
|
||||
|
||||
QFuture<AndroidConfig::CreateAvdInfo>
|
||||
AndroidAvdManager::createAvd(AndroidConfig::CreateAvdInfo info) const
|
||||
{
|
||||
if (m_config.sdkToolsVersion() < avdManagerIntroVersion)
|
||||
return m_androidTool->createAvd(info);
|
||||
|
||||
return Utils::runAsync(&createAvdCommand, m_config, info);
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::removeAvd(const QString &name) const
|
||||
{
|
||||
if (m_config.sdkToolsVersion() < avdManagerIntroVersion)
|
||||
return m_androidTool->removeAvd(name);
|
||||
|
||||
Utils::SynchronousProcess proc;
|
||||
proc.setTimeoutS(5);
|
||||
Utils::SynchronousProcessResponse response
|
||||
= proc.runBlocking(m_config.avdManagerToolPath().toString(),
|
||||
QStringList({"delete", "avd", "-n", name}));
|
||||
return response.result == Utils::SynchronousProcessResponse::Finished && response.exitCode == 0;
|
||||
}
|
||||
|
||||
QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const
|
||||
{
|
||||
if (m_config.sdkToolsVersion() < avdManagerIntroVersion)
|
||||
return m_androidTool->androidVirtualDevicesFuture();
|
||||
|
||||
return Utils::runAsync(&AvdManagerOutputParser::listVirtualDevices, m_parser.get(), m_config);
|
||||
}
|
||||
|
||||
QString AndroidAvdManager::startAvd(const QString &name) const
|
||||
{
|
||||
if (!findAvd(name).isEmpty() || startAvdAsync(name))
|
||||
return waitForAvd(name);
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::startAvdAsync(const QString &avdName) const
|
||||
{
|
||||
QProcess *avdProcess = new QProcess();
|
||||
QObject::connect(avdProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
|
||||
avdProcess, &QObject::deleteLater);
|
||||
|
||||
// start the emulator
|
||||
QStringList arguments;
|
||||
if (AndroidConfigurations::force32bitEmulator())
|
||||
arguments << "-force-32bit";
|
||||
|
||||
arguments << "-partition-size" << QString::number(m_config.partitionSize())
|
||||
<< "-avd" << avdName;
|
||||
avdProcess->start(m_config.emulatorToolPath().toString(), arguments);
|
||||
if (!avdProcess->waitForStarted(-1)) {
|
||||
delete avdProcess;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString AndroidAvdManager::findAvd(const QString &avdName) const
|
||||
{
|
||||
QVector<AndroidDeviceInfo> devices = m_config.connectedDevices();
|
||||
foreach (AndroidDeviceInfo device, devices) {
|
||||
if (device.type != AndroidDeviceInfo::Emulator)
|
||||
continue;
|
||||
if (device.avdname == avdName)
|
||||
return device.serialNumber;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString AndroidAvdManager::waitForAvd(const QString &avdName, const QFutureInterface<bool> &fi) const
|
||||
{
|
||||
// we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running
|
||||
// 60 rounds of 2s sleeping, two minutes for the avd to start
|
||||
QString serialNumber;
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (fi.isCanceled())
|
||||
return QString();
|
||||
serialNumber = findAvd(avdName);
|
||||
if (!serialNumber.isEmpty())
|
||||
return waitForBooted(serialNumber, fi) ? serialNumber : QString();
|
||||
QThread::sleep(2);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::isAvdBooted(const QString &device) const
|
||||
{
|
||||
QStringList arguments = AndroidDeviceInfo::adbSelector(device);
|
||||
arguments << "shell" << "getprop" << "init.svc.bootanim";
|
||||
|
||||
Utils::SynchronousProcess adbProc;
|
||||
adbProc.setTimeoutS(10);
|
||||
Utils::SynchronousProcessResponse response =
|
||||
adbProc.runBlocking(m_config.adbToolPath().toString(), arguments);
|
||||
if (response.result != Utils::SynchronousProcessResponse::Finished)
|
||||
return false;
|
||||
QString value = response.allOutput().trimmed();
|
||||
return value == "stopped";
|
||||
}
|
||||
|
||||
bool AndroidAvdManager::waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const
|
||||
{
|
||||
// found a serial number, now wait until it's done booting...
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (fi.isCanceled())
|
||||
return false;
|
||||
if (isAvdBooted(serialNumber)) {
|
||||
return true;
|
||||
} else {
|
||||
QThread::sleep(2);
|
||||
if (!m_config.isConnected(serialNumber)) // device was disconnected
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AndroidDeviceInfoList AvdManagerOutputParser::listVirtualDevices(const AndroidConfig &config)
|
||||
{
|
||||
QString output;
|
||||
if (!avdManagerCommand(config, QStringList({"list", "avd"}), &output)) {
|
||||
qCDebug(avdManagerLog) << "Avd list command failed" << output << config.sdkToolsVersion();
|
||||
return {};
|
||||
}
|
||||
return parseAvdList(output);
|
||||
}
|
||||
|
||||
AndroidDeviceInfoList AvdManagerOutputParser::parseAvdList(const QString &output)
|
||||
{
|
||||
AndroidDeviceInfoList avdList;
|
||||
QStringList avdInfo;
|
||||
auto parseAvdInfo = [&avdInfo, &avdList, this] () {
|
||||
AndroidDeviceInfo avd;
|
||||
if (parseAvd(avdInfo, &avd)) {
|
||||
// armeabi-v7a devices can also run armeabi code
|
||||
if (avd.cpuAbi.contains("armeabi-v7a"))
|
||||
avd.cpuAbi << "armeabi";
|
||||
avd.state = AndroidDeviceInfo::OkState;
|
||||
avd.type = AndroidDeviceInfo::Emulator;
|
||||
avdList << avd;
|
||||
} else {
|
||||
qCDebug(avdManagerLog) << "Avd Parsing: Parsing failed: " << avdInfo;
|
||||
}
|
||||
avdInfo.clear();
|
||||
};
|
||||
|
||||
foreach (QString line, output.split('\n')) {
|
||||
if (line.startsWith("---------") || line.isEmpty()) {
|
||||
parseAvdInfo();
|
||||
} else {
|
||||
avdInfo << line;
|
||||
}
|
||||
}
|
||||
|
||||
if (!avdInfo.isEmpty())
|
||||
parseAvdInfo();
|
||||
|
||||
Utils::sort(avdList);
|
||||
|
||||
return avdList;
|
||||
}
|
||||
|
||||
bool AvdManagerOutputParser::parseAvd(const QStringList &deviceInfo, AndroidDeviceInfo *avd)
|
||||
{
|
||||
QTC_ASSERT(avd, return false);
|
||||
foreach (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::FileName avdPath = Utils::FileName::fromString(value);
|
||||
if (avdPath.exists())
|
||||
{
|
||||
// Get ABI.
|
||||
Utils::FileName configFile = avdPath;
|
||||
configFile.appendPath("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
|
||||
Utils::FileName avdInfoFile = avdPath.parentDir();
|
||||
QString avdInfoFileName = avdPath.toFileInfo().baseName() + ".ini";
|
||||
avdInfoFile.appendPath(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
66
src/plugins/android/androidavdmanager.h
Normal file
66
src/plugins/android/androidavdmanager.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "androidconfigurations.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
class AndroidToolManager;
|
||||
class AvdManagerOutputParser;
|
||||
|
||||
class AndroidAvdManager
|
||||
{
|
||||
public:
|
||||
AndroidAvdManager(const AndroidConfig& config = AndroidConfigurations::currentConfig());
|
||||
~AndroidAvdManager();
|
||||
|
||||
bool avdManagerUiToolAvailable() const;
|
||||
void launchAvdManagerUiTool() const;
|
||||
QFuture<AndroidConfig::CreateAvdInfo> createAvd(AndroidConfig::CreateAvdInfo info) const;
|
||||
bool removeAvd(const QString &name) const;
|
||||
QFuture<AndroidDeviceInfoList> avdList() const;
|
||||
|
||||
QString startAvd(const QString &name) const;
|
||||
bool startAvdAsync(const QString &avdName) const;
|
||||
QString findAvd(const QString &avdName) const;
|
||||
QString waitForAvd(const QString &avdName,
|
||||
const QFutureInterface<bool> &fi = QFutureInterface<bool>()) const;
|
||||
bool isAvdBooted(const QString &device) const;
|
||||
|
||||
private:
|
||||
bool waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const;
|
||||
|
||||
private:
|
||||
const AndroidConfig &m_config;
|
||||
std::unique_ptr<AndroidToolManager> m_androidTool;
|
||||
std::unique_ptr<AvdManagerOutputParser> m_parser;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
@@ -60,6 +60,8 @@
|
||||
namespace Android {
|
||||
using namespace Internal;
|
||||
|
||||
const QVersionNumber gradleScriptRevokedSdkVersion(25, 3, 0);
|
||||
const QVersionNumber gradleScriptsContainedQtVersion(5, 9, 0);
|
||||
const QLatin1String DeployActionKey("Qt4ProjectManager.AndroidDeployQtStep.DeployQtAction");
|
||||
const QLatin1String KeystoreLocationKey("KeystoreLocation");
|
||||
const QLatin1String BuildTargetSdkKey("BuildTargetSdk");
|
||||
@@ -140,6 +142,15 @@ bool AndroidBuildApkStep::init(QList<const BuildStep *> &earlierSteps)
|
||||
if (!version)
|
||||
return false;
|
||||
|
||||
if (AndroidConfigurations::currentConfig().sdkToolsVersion() >= gradleScriptRevokedSdkVersion &&
|
||||
QVersionNumber::fromString(version->qtVersionString()) < gradleScriptsContainedQtVersion) {
|
||||
emit addOutput(tr("The installed SDK tools version (%1) does not include Gradle scripts. The "
|
||||
"minimum Qt version required for Gradle build to work is %2")
|
||||
.arg(gradleScriptRevokedSdkVersion.toString())
|
||||
.arg(gradleScriptsContainedQtVersion.toString()), OutputFormat::Stderr);
|
||||
return false;
|
||||
}
|
||||
|
||||
int minSDKForKit = AndroidManager::minimumSDK(target()->kit());
|
||||
if (AndroidManager::minimumSDK(target()) < minSDKForKit) {
|
||||
emit addOutput(tr("The API level set for the APK is less than the minimum required by the kit."
|
||||
@@ -342,6 +353,16 @@ void AndroidBuildApkStep::setUseGradle(bool b)
|
||||
}
|
||||
}
|
||||
|
||||
bool AndroidBuildApkStep::addDebugger() const
|
||||
{
|
||||
return m_addDebugger;
|
||||
}
|
||||
|
||||
void AndroidBuildApkStep::setAddDebugger(bool debug)
|
||||
{
|
||||
m_addDebugger = debug;
|
||||
}
|
||||
|
||||
bool AndroidBuildApkStep::verboseOutput() const
|
||||
{
|
||||
return m_verbose;
|
||||
|
||||
@@ -73,6 +73,9 @@ public:
|
||||
bool useGradle() const;
|
||||
void setUseGradle(bool b);
|
||||
|
||||
bool addDebugger() const;
|
||||
void setAddDebugger(bool debug);
|
||||
|
||||
QString buildTargetSdk() const;
|
||||
void setBuildTargetSdk(const QString &sdk);
|
||||
|
||||
@@ -99,9 +102,10 @@ protected:
|
||||
AndroidDeployAction m_deployAction = BundleLibrariesDeployment;
|
||||
bool m_signPackage = false;
|
||||
bool m_verbose = false;
|
||||
bool m_useGradle = false;
|
||||
bool m_useGradle = true; // Ant builds are deprecated.
|
||||
bool m_openPackageLocation = false;
|
||||
bool m_openPackageLocationForRun = false;
|
||||
bool m_addDebugger = true;
|
||||
QString m_buildTargetSdk;
|
||||
|
||||
Utils::FileName m_keystorePath;
|
||||
|
||||
@@ -54,9 +54,12 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->deprecatedInfoIconLabel->setPixmap(Utils::Icons::INFO.pixmap());
|
||||
|
||||
// Target sdk combobox
|
||||
int minApiLevel = 9;
|
||||
QStringList targets = AndroidConfig::apiLevelNamesFor(AndroidConfigurations::currentConfig().sdkTargets(minApiLevel));
|
||||
const AndroidConfig &config = AndroidConfigurations::currentConfig();
|
||||
QStringList targets = AndroidConfig::apiLevelNamesFor(config.sdkTargets(minApiLevel));
|
||||
targets.removeDuplicates();
|
||||
m_ui->targetSDKComboBox->addItems(targets);
|
||||
m_ui->targetSDKComboBox->setCurrentIndex(targets.indexOf(AndroidManager::buildTargetSDK(step->target())));
|
||||
@@ -91,9 +94,12 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
|
||||
m_ui->signingDebugDeployErrorIcon->setPixmap(Utils::Icons::CRITICAL.pixmap());
|
||||
signPackageCheckBoxToggled(m_step->signPackage());
|
||||
|
||||
m_ui->useGradleCheckBox->setChecked(m_step->useGradle());
|
||||
m_ui->useGradleCheckBox->setEnabled(config.antScriptsAvailable());
|
||||
m_ui->useGradleCheckBox->setChecked(!config.antScriptsAvailable() ||
|
||||
m_step->useGradle());
|
||||
m_ui->verboseOutputCheckBox->setChecked(m_step->verboseOutput());
|
||||
m_ui->openPackageLocationCheckBox->setChecked(m_step->openPackageLocation());
|
||||
m_ui->addDebuggerCheckBox->setChecked(m_step->addDebugger());
|
||||
|
||||
// target sdk
|
||||
connect(m_ui->targetSDKComboBox,
|
||||
@@ -120,6 +126,8 @@ AndroidBuildApkWidget::AndroidBuildApkWidget(AndroidBuildApkStep *step)
|
||||
this, &AndroidBuildApkWidget::openPackageLocationCheckBoxToggled);
|
||||
connect(m_ui->verboseOutputCheckBox, &QAbstractButton::toggled,
|
||||
this, &AndroidBuildApkWidget::verboseOutputCheckBoxToggled);
|
||||
connect(m_ui->addDebuggerCheckBox, &QAbstractButton::toggled,
|
||||
m_step, &AndroidBuildApkStep::setAddDebugger);
|
||||
|
||||
//signing
|
||||
connect(m_ui->signPackageCheckBox, &QAbstractButton::toggled,
|
||||
@@ -185,6 +193,7 @@ void AndroidBuildApkWidget::signPackageCheckBoxToggled(bool checked)
|
||||
{
|
||||
m_ui->certificatesAliasComboBox->setEnabled(checked);
|
||||
m_step->setSignPackage(checked);
|
||||
m_ui->addDebuggerCheckBox->setChecked(!checked);
|
||||
updateSigningWarning();
|
||||
if (!checked)
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>819</width>
|
||||
<height>390</height>
|
||||
<height>478</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -176,24 +176,75 @@ Deploying local Qt libraries is incompatible with Android 5.</string>
|
||||
<string>Advanced Actions</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="verboseOutputCheckBox">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="useGradleCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Verbose output</string>
|
||||
<string>Use Gradle (Ant builds are deprecated)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="deprecatedInfoIconLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Gradle builds are forced from Android SDK tools version 25.3.0 onwards as Ant scripts are no longer available.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="openPackageLocationCheckBox">
|
||||
<property name="text">
|
||||
<string>Open package location after build</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="useGradleCheckBox">
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="verboseOutputCheckBox">
|
||||
<property name="text">
|
||||
<string>Use Gradle</string>
|
||||
<string>Verbose output</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="addDebuggerCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Packages debug server with the APK to enable debugging. For the signed APK this option is unchecked by default.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add debug server</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -254,5 +305,22 @@ The APK will not be usable on any other device.</string>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>signPackageCheckBox</sender>
|
||||
<signal>clicked(bool)</signal>
|
||||
<receiver>addDebuggerCheckBox</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>113</x>
|
||||
<y>178</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>510</x>
|
||||
<y>452</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
||||
@@ -28,8 +28,11 @@
|
||||
#include "androidtoolchain.h"
|
||||
#include "androiddevice.h"
|
||||
#include "androidgdbserverkitinformation.h"
|
||||
#include "androidmanager.h"
|
||||
#include "androidqtversion.h"
|
||||
#include "androiddevicedialog.h"
|
||||
#include "androidsdkmanager.h"
|
||||
#include "androidtoolmanager.h"
|
||||
#include "avddialog.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
@@ -73,6 +76,9 @@ namespace Android {
|
||||
using namespace Internal;
|
||||
|
||||
namespace {
|
||||
|
||||
const QVersionNumber sdkToolsAntMissingVersion(25, 3, 0);
|
||||
|
||||
const QLatin1String SettingsGroup("AndroidConfigurations");
|
||||
const QLatin1String SDKLocationKey("SDKLocation");
|
||||
const QLatin1String NDKLocationKey("NDKLocation");
|
||||
@@ -107,39 +113,14 @@ namespace {
|
||||
const QLatin1String keytoolName("keytool");
|
||||
const QLatin1String changeTimeStamp("ChangeTimeStamp");
|
||||
|
||||
const QLatin1String sdkToolsVersionKey("Pkg.Revision");
|
||||
|
||||
static QString sdkSettingsFileName()
|
||||
{
|
||||
return QFileInfo(Core::ICore::settings(QSettings::SystemScope)->fileName()).absolutePath()
|
||||
+ QLatin1String("/qtcreator/android.xml");
|
||||
}
|
||||
|
||||
bool androidDevicesLessThan(const AndroidDeviceInfo &dev1, const AndroidDeviceInfo &dev2)
|
||||
{
|
||||
if (dev1.serialNumber.contains(QLatin1String("????")) != dev2.serialNumber.contains(QLatin1String("????")))
|
||||
return !dev1.serialNumber.contains(QLatin1String("????"));
|
||||
if (dev1.type != dev2.type)
|
||||
return dev1.type == AndroidDeviceInfo::Hardware;
|
||||
if (dev1.sdk != dev2.sdk)
|
||||
return dev1.sdk < dev2.sdk;
|
||||
if (dev1.avdname != dev2.avdname)
|
||||
return dev1.avdname < dev2.avdname;
|
||||
|
||||
return dev1.serialNumber < dev2.serialNumber;
|
||||
}
|
||||
|
||||
static QStringList cleanAndroidABIs(const QStringList &abis)
|
||||
{
|
||||
QStringList res;
|
||||
foreach (const QString &abi, abis) {
|
||||
int index = abi.lastIndexOf(QLatin1Char('/'));
|
||||
if (index == -1)
|
||||
res << abi;
|
||||
else
|
||||
res << abi.mid(index + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool is32BitUserSpace()
|
||||
{
|
||||
// Do the exact same check as android's emulator is doing:
|
||||
@@ -162,25 +143,6 @@ namespace {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some preview sdks use a non integer version
|
||||
int apiLevelFromAndroidList(const QString &string)
|
||||
{
|
||||
bool ok;
|
||||
int result = string.toInt(&ok);
|
||||
if (ok)
|
||||
return result;
|
||||
Utils::FileName sdkLocation = AndroidConfigurations::currentConfig().sdkLocation();
|
||||
sdkLocation.appendPath(QLatin1String("/platforms/android-") + string + QLatin1String("/source.properties"));
|
||||
result = QSettings(sdkLocation.toString(), QSettings::IniFormat).value(QLatin1String("AndroidVersion.ApiLevel")).toInt(&ok);
|
||||
if (ok)
|
||||
return result;
|
||||
if (string == QLatin1String("L"))
|
||||
return 21;
|
||||
if (string == QLatin1String("MNC"))
|
||||
return 22;
|
||||
return 23; // At least
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
@@ -359,61 +321,14 @@ void AndroidConfig::updateNdkInformation() const
|
||||
m_NdkInformationUpToDate = true;
|
||||
}
|
||||
|
||||
bool AndroidConfig::sortSdkPlatformByApiLevel(const SdkPlatform &a, const SdkPlatform &b)
|
||||
{
|
||||
if (a.apiLevel != b.apiLevel)
|
||||
return a.apiLevel > b.apiLevel;
|
||||
if (a.name != b.name)
|
||||
return a.name < b.name;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AndroidConfig::updateAvailableSdkPlatforms() const
|
||||
{
|
||||
if (m_availableSdkPlatformsUpToDate)
|
||||
return;
|
||||
|
||||
m_availableSdkPlatforms.clear();
|
||||
|
||||
SynchronousProcess proc;
|
||||
proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
|
||||
SynchronousProcessResponse response
|
||||
= proc.runBlocking(androidToolPath().toString(),
|
||||
QStringList({"list", "target"})); // list available AVDs
|
||||
if (response.result != SynchronousProcessResponse::Finished)
|
||||
return;
|
||||
|
||||
SdkPlatform platform;
|
||||
foreach (const QString &l, response.allOutput().split('\n')) {
|
||||
const QString line = l.trimmed();
|
||||
if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) {
|
||||
int index = line.indexOf(QLatin1String("\"android-"));
|
||||
if (index == -1)
|
||||
continue;
|
||||
QString androidTarget = line.mid(index + 1, line.length() - index - 2);
|
||||
const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1);
|
||||
platform.apiLevel = apiLevelFromAndroidList(tmp);
|
||||
} else if (line.startsWith(QLatin1String("Name:"))) {
|
||||
platform.name = line.mid(6);
|
||||
} else if (line.startsWith(QLatin1String("Tag/ABIs :"))) {
|
||||
platform.abis = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", ")));
|
||||
} else if (line.startsWith(QLatin1String("ABIs"))) {
|
||||
platform.abis = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", ")));
|
||||
} else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) {
|
||||
if (platform.apiLevel == -1)
|
||||
continue;
|
||||
auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(),
|
||||
platform, sortSdkPlatformByApiLevel);
|
||||
m_availableSdkPlatforms.insert(it, platform);
|
||||
platform = SdkPlatform();
|
||||
}
|
||||
}
|
||||
|
||||
if (platform.apiLevel != -1) {
|
||||
auto it = std::lower_bound(m_availableSdkPlatforms.begin(), m_availableSdkPlatforms.end(),
|
||||
platform, sortSdkPlatformByApiLevel);
|
||||
m_availableSdkPlatforms.insert(it, platform);
|
||||
}
|
||||
|
||||
AndroidSdkManager sdkManager(*this);
|
||||
m_availableSdkPlatforms = sdkManager.availableSdkPlatforms();
|
||||
m_availableSdkPlatformsUpToDate = true;
|
||||
}
|
||||
|
||||
@@ -446,18 +361,6 @@ FileName AndroidConfig::adbToolPath() const
|
||||
return path.appendPath(QLatin1String("platform-tools/adb" QTC_HOST_EXE_SUFFIX));
|
||||
}
|
||||
|
||||
Environment AndroidConfig::androidToolEnvironment() const
|
||||
{
|
||||
Environment env = Environment::systemEnvironment();
|
||||
if (!m_openJDKLocation.isEmpty()) {
|
||||
env.set(QLatin1String("JAVA_HOME"), m_openJDKLocation.toUserOutput());
|
||||
Utils::FileName binPath = m_openJDKLocation;
|
||||
binPath.appendPath(QLatin1String("bin"));
|
||||
env.prependOrSetPath(binPath.toUserOutput());
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
FileName AndroidConfig::androidToolPath() const
|
||||
{
|
||||
if (HostOsInfo::isWindowsHost()) {
|
||||
@@ -486,7 +389,10 @@ FileName AndroidConfig::antToolPath() const
|
||||
FileName AndroidConfig::emulatorToolPath() const
|
||||
{
|
||||
FileName path = m_sdkLocation;
|
||||
return path.appendPath(QLatin1String("tools/emulator" QTC_HOST_EXE_SUFFIX));
|
||||
QString relativePath = "emulator/emulator";
|
||||
if (sdkToolsVersion() < QVersionNumber(25, 3, 0))
|
||||
relativePath = "tools/emulator";
|
||||
return path.appendPath(relativePath + QTC_HOST_EXE_SUFFIX);
|
||||
}
|
||||
|
||||
FileName AndroidConfig::toolPath(const Abi &abi, const QString &ndkToolChainVersion) const
|
||||
@@ -499,6 +405,26 @@ FileName AndroidConfig::toolPath(const Abi &abi, const QString &ndkToolChainVers
|
||||
.arg(toolsPrefix(abi)));
|
||||
}
|
||||
|
||||
FileName AndroidConfig::sdkManagerToolPath() const
|
||||
{
|
||||
FileName sdkPath = m_sdkLocation;
|
||||
QString toolPath = "tools/bin/sdkmanager";
|
||||
if (HostOsInfo::isWindowsHost())
|
||||
toolPath += ANDROID_BAT_SUFFIX;
|
||||
sdkPath = sdkPath.appendPath(toolPath);
|
||||
return sdkPath;
|
||||
}
|
||||
|
||||
FileName AndroidConfig::avdManagerToolPath() const
|
||||
{
|
||||
FileName avdManagerPath = m_sdkLocation;
|
||||
QString toolPath = "tools/bin/avdmanager";
|
||||
if (HostOsInfo::isWindowsHost())
|
||||
toolPath += ANDROID_BAT_SUFFIX;
|
||||
avdManagerPath = avdManagerPath.appendPath(toolPath);
|
||||
return avdManagerPath;
|
||||
}
|
||||
|
||||
FileName AndroidConfig::gccPath(const Abi &abi, Core::Id lang,
|
||||
const QString &ndkToolChainVersion) const
|
||||
{
|
||||
@@ -583,7 +509,7 @@ QVector<AndroidDeviceInfo> AndroidConfig::connectedDevices(const QString &adbToo
|
||||
devices.push_back(dev);
|
||||
}
|
||||
|
||||
Utils::sort(devices, androidDevicesLessThan);
|
||||
Utils::sort(devices);
|
||||
if (devices.isEmpty() && error)
|
||||
*error = QApplication::translate("AndroidConfiguration",
|
||||
"No devices found in output of: %1")
|
||||
@@ -605,197 +531,6 @@ AndroidConfig::CreateAvdInfo AndroidConfig::gatherCreateAVDInfo(QWidget *parent,
|
||||
return result;
|
||||
}
|
||||
|
||||
QFuture<AndroidConfig::CreateAvdInfo> AndroidConfig::createAVD(CreateAvdInfo info) const
|
||||
{
|
||||
return Utils::runAsync(&AndroidConfig::createAVDImpl, info,
|
||||
androidToolPath(), androidToolEnvironment());
|
||||
}
|
||||
|
||||
AndroidConfig::CreateAvdInfo AndroidConfig::createAVDImpl(CreateAvdInfo info, FileName androidToolPath, Environment env)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env.toProcessEnvironment());
|
||||
QStringList arguments;
|
||||
arguments << QLatin1String("create") << QLatin1String("avd")
|
||||
<< QLatin1String("-t") << info.target
|
||||
<< QLatin1String("-n") << info.name
|
||||
<< QLatin1String("-b") << info.abi;
|
||||
if (info.sdcardSize > 0)
|
||||
arguments << QLatin1String("-c") << QString::fromLatin1("%1M").arg(info.sdcardSize);
|
||||
proc.start(androidToolPath.toString(), arguments);
|
||||
if (!proc.waitForStarted()) {
|
||||
info.error = QApplication::translate("AndroidConfig", "Could not start process \"%1 %2\"")
|
||||
.arg(androidToolPath.toString(), arguments.join(QLatin1Char(' ')));
|
||||
return info;
|
||||
}
|
||||
QTC_CHECK(proc.state() == QProcess::Running);
|
||||
proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile"
|
||||
|
||||
QByteArray question;
|
||||
while (true) {
|
||||
proc.waitForReadyRead(500);
|
||||
question += proc.readAllStandardOutput();
|
||||
if (question.endsWith(QByteArray("]:"))) {
|
||||
// truncate to last line
|
||||
int index = question.lastIndexOf(QByteArray("\n"));
|
||||
if (index != -1)
|
||||
question = question.mid(index);
|
||||
if (question.contains("hw.gpu.enabled"))
|
||||
proc.write(QByteArray("yes\n"));
|
||||
else
|
||||
proc.write(QByteArray("\n"));
|
||||
question.clear();
|
||||
}
|
||||
|
||||
if (proc.state() != QProcess::Running)
|
||||
break;
|
||||
}
|
||||
QTC_CHECK(proc.state() == QProcess::NotRunning);
|
||||
|
||||
QString errorOutput = QString::fromLocal8Bit(proc.readAllStandardError());
|
||||
// The exit code is always 0, so we need to check stderr
|
||||
// For now assume that any output at all indicates a error
|
||||
if (!errorOutput.isEmpty()) {
|
||||
info.error = errorOutput;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
bool AndroidConfig::removeAVD(const QString &name) const
|
||||
{
|
||||
SynchronousProcess proc;
|
||||
proc.setTimeoutS(5);
|
||||
proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
|
||||
SynchronousProcessResponse response
|
||||
= proc.runBlocking(androidToolPath().toString(), QStringList({"delete", "avd", "-n", name}));
|
||||
return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0;
|
||||
}
|
||||
|
||||
QFuture<QVector<AndroidDeviceInfo>> AndroidConfig::androidVirtualDevicesFuture() const
|
||||
{
|
||||
return Utils::runAsync(&AndroidConfig::androidVirtualDevices,
|
||||
androidToolPath().toString(), androidToolEnvironment());
|
||||
}
|
||||
|
||||
QVector<AndroidDeviceInfo> AndroidConfig::androidVirtualDevices(const QString &androidTool, const Environment &environment)
|
||||
{
|
||||
QVector<AndroidDeviceInfo> devices;
|
||||
SynchronousProcess proc;
|
||||
proc.setTimeoutS(20);
|
||||
proc.setProcessEnvironment(environment.toProcessEnvironment());
|
||||
SynchronousProcessResponse response = proc.run(androidTool, {"list", "avd"}); // list available AVDs
|
||||
if (response.result != SynchronousProcessResponse::Finished)
|
||||
return devices;
|
||||
|
||||
QStringList avds = response.allOutput().split('\n');
|
||||
if (avds.empty())
|
||||
return devices;
|
||||
|
||||
while (avds.first().startsWith(QLatin1String("* daemon")))
|
||||
avds.removeFirst(); // remove the daemon logs
|
||||
avds.removeFirst(); // remove "List of devices attached" header line
|
||||
|
||||
bool nextLineIsTargetLine = false;
|
||||
|
||||
AndroidDeviceInfo dev;
|
||||
for (int i = 0; i < avds.size(); i++) {
|
||||
QString line = avds.at(i);
|
||||
if (!line.contains(QLatin1String("Name:")))
|
||||
continue;
|
||||
|
||||
int index = line.indexOf(QLatin1Char(':')) + 2;
|
||||
if (index >= line.size())
|
||||
break;
|
||||
dev.avdname = line.mid(index).trimmed();
|
||||
dev.sdk = -1;
|
||||
dev.cpuAbi.clear();
|
||||
++i;
|
||||
for (; i < avds.size(); ++i) {
|
||||
line = avds.at(i);
|
||||
if (line.contains(QLatin1String("---------")))
|
||||
break;
|
||||
|
||||
if (line.contains(QLatin1String("Target:")) || nextLineIsTargetLine) {
|
||||
if (line.contains(QLatin1String("Google APIs"))) {
|
||||
nextLineIsTargetLine = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
nextLineIsTargetLine = false;
|
||||
|
||||
int lastIndex = line.lastIndexOf(QLatin1Char(' '));
|
||||
if (lastIndex == -1) // skip line
|
||||
break;
|
||||
QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed();
|
||||
dev.sdk = apiLevelFromAndroidList(tmp);
|
||||
}
|
||||
if (line.contains(QLatin1String("Tag/ABI:"))) {
|
||||
int lastIndex = line.lastIndexOf(QLatin1Char('/')) + 1;
|
||||
if (lastIndex >= line.size())
|
||||
break;
|
||||
dev.cpuAbi = QStringList(line.mid(lastIndex));
|
||||
} else if (line.contains(QLatin1String("ABI:"))) {
|
||||
int lastIndex = line.lastIndexOf(QLatin1Char(' ')) + 1;
|
||||
if (lastIndex >= line.size())
|
||||
break;
|
||||
dev.cpuAbi = QStringList(line.mid(lastIndex).trimmed());
|
||||
}
|
||||
}
|
||||
// armeabi-v7a devices can also run armeabi code
|
||||
if (dev.cpuAbi == QStringList("armeabi-v7a"))
|
||||
dev.cpuAbi << QLatin1String("armeabi");
|
||||
dev.state = AndroidDeviceInfo::OkState;
|
||||
dev.type = AndroidDeviceInfo::Emulator;
|
||||
if (dev.cpuAbi.isEmpty() || dev.sdk == -1)
|
||||
continue;
|
||||
devices.push_back(dev);
|
||||
}
|
||||
Utils::sort(devices, androidDevicesLessThan);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
QString AndroidConfig::startAVD(const QString &name) const
|
||||
{
|
||||
if (!findAvd(name).isEmpty() || startAVDAsync(name))
|
||||
return waitForAvd(name);
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AndroidConfig::startAVDAsync(const QString &avdName) const
|
||||
{
|
||||
QProcess *avdProcess = new QProcess();
|
||||
QObject::connect(avdProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
|
||||
avdProcess, &QObject::deleteLater);
|
||||
|
||||
// start the emulator
|
||||
QStringList arguments;
|
||||
if (AndroidConfigurations::force32bitEmulator())
|
||||
arguments << QLatin1String("-force-32bit");
|
||||
|
||||
arguments << QLatin1String("-partition-size") << QString::number(partitionSize())
|
||||
<< QLatin1String("-avd") << avdName;
|
||||
avdProcess->start(emulatorToolPath().toString(), arguments);
|
||||
if (!avdProcess->waitForStarted(-1)) {
|
||||
delete avdProcess;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString AndroidConfig::findAvd(const QString &avdName) const
|
||||
{
|
||||
QVector<AndroidDeviceInfo> devices = connectedDevices();
|
||||
foreach (AndroidDeviceInfo device, devices) {
|
||||
if (device.type != AndroidDeviceInfo::Emulator)
|
||||
continue;
|
||||
if (device.avdname == avdName)
|
||||
return device.serialNumber;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AndroidConfig::isConnected(const QString &serialNumber) const
|
||||
{
|
||||
QVector<AndroidDeviceInfo> devices = connectedDevices();
|
||||
@@ -806,39 +541,6 @@ bool AndroidConfig::isConnected(const QString &serialNumber) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AndroidConfig::waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const
|
||||
{
|
||||
// found a serial number, now wait until it's done booting...
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (fi.isCanceled())
|
||||
return false;
|
||||
if (hasFinishedBooting(serialNumber)) {
|
||||
return true;
|
||||
} else {
|
||||
QThread::sleep(2);
|
||||
if (!isConnected(serialNumber)) // device was disconnected
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString AndroidConfig::waitForAvd(const QString &avdName, const QFutureInterface<bool> &fi) const
|
||||
{
|
||||
// we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running
|
||||
// 60 rounds of 2s sleeping, two minutes for the avd to start
|
||||
QString serialNumber;
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
if (fi.isCanceled())
|
||||
return QString();
|
||||
serialNumber = findAvd(avdName);
|
||||
if (!serialNumber.isEmpty())
|
||||
return waitForBooted(serialNumber, fi) ? serialNumber : QString();
|
||||
QThread::sleep(2);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool AndroidConfig::isBootToQt(const QString &device) const
|
||||
{
|
||||
return isBootToQt(adbToolPath().toString(), device);
|
||||
@@ -961,21 +663,6 @@ QString AndroidConfig::getProductModel(const QString &device) const
|
||||
return model;
|
||||
}
|
||||
|
||||
bool AndroidConfig::hasFinishedBooting(const QString &device) const
|
||||
{
|
||||
QStringList arguments = AndroidDeviceInfo::adbSelector(device);
|
||||
arguments << QLatin1String("shell") << QLatin1String("getprop")
|
||||
<< QLatin1String("init.svc.bootanim");
|
||||
|
||||
SynchronousProcess adbProc;
|
||||
adbProc.setTimeoutS(10);
|
||||
SynchronousProcessResponse response = adbProc.runBlocking(adbToolPath().toString(), arguments);
|
||||
if (response.result != SynchronousProcessResponse::Finished)
|
||||
return false;
|
||||
QString value = response.allOutput().trimmed();
|
||||
return value == QLatin1String("stopped");
|
||||
}
|
||||
|
||||
QStringList AndroidConfig::getAbis(const QString &device) const
|
||||
{
|
||||
return getAbis(adbToolPath().toString(), device);
|
||||
@@ -1053,6 +740,19 @@ void AndroidConfig::setSdkLocation(const FileName &sdkLocation)
|
||||
m_availableSdkPlatformsUpToDate = false;
|
||||
}
|
||||
|
||||
QVersionNumber AndroidConfig::sdkToolsVersion() const
|
||||
{
|
||||
QVersionNumber version;
|
||||
if (m_sdkLocation.exists()) {
|
||||
Utils::FileName sdkToolsPropertiesPath(m_sdkLocation);
|
||||
sdkToolsPropertiesPath.appendPath("tools/source.properties");
|
||||
QSettings settings(sdkToolsPropertiesPath.toString(), QSettings::IniFormat);
|
||||
auto versionStr = settings.value(sdkToolsVersionKey).toString();
|
||||
version = QVersionNumber::fromString(versionStr);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
FileName AndroidConfig::ndkLocation() const
|
||||
{
|
||||
return m_ndkLocation;
|
||||
@@ -1126,9 +826,18 @@ void AndroidConfig::setAutomaticKitCreation(bool b)
|
||||
m_automaticKitCreation = b;
|
||||
}
|
||||
|
||||
bool AndroidConfig::antScriptsAvailable() const
|
||||
{
|
||||
return sdkToolsVersion() < sdkToolsAntMissingVersion;
|
||||
}
|
||||
|
||||
bool AndroidConfig::useGrandle() const
|
||||
{
|
||||
return m_useGradle;
|
||||
if (antScriptsAvailable()) {
|
||||
return m_useGradle;
|
||||
}
|
||||
// Force gradle builds.
|
||||
return true;
|
||||
}
|
||||
|
||||
void AndroidConfig::setUseGradle(bool b)
|
||||
@@ -1353,6 +1062,20 @@ QStringList AndroidDeviceInfo::adbSelector(const QString &serialNumber)
|
||||
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
|
||||
@@ -1494,4 +1217,13 @@ void AndroidConfigurations::updateAndroidDevice()
|
||||
|
||||
AndroidConfigurations *AndroidConfigurations::m_instance = 0;
|
||||
|
||||
bool SdkPlatform::operator <(const SdkPlatform &other) const
|
||||
{
|
||||
if (apiLevel != other.apiLevel)
|
||||
return apiLevel > other.apiLevel;
|
||||
if (name != other.name)
|
||||
return name < other.name;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Android
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QFutureInterface>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
@@ -68,19 +69,36 @@ public:
|
||||
|
||||
static QStringList adbSelector(const QString &serialNumber);
|
||||
|
||||
bool isValid() { return !serialNumber.isEmpty() || !avdname.isEmpty(); }
|
||||
bool isValid() const { return !serialNumber.isEmpty() || !avdname.isEmpty(); }
|
||||
bool operator<(const AndroidDeviceInfo &other) const;
|
||||
};
|
||||
using AndroidDeviceInfoList = QList<AndroidDeviceInfo>;
|
||||
|
||||
//! Defines an Android system image.
|
||||
class SystemImage
|
||||
{
|
||||
public:
|
||||
bool isValid() const { return (apiLevel != -1) && !abiName.isEmpty(); }
|
||||
int apiLevel = -1;
|
||||
QString abiName;
|
||||
QString package;
|
||||
Utils::FileName installedLocation;
|
||||
};
|
||||
using SystemImageList = QList<SystemImage>;
|
||||
|
||||
|
||||
class SdkPlatform
|
||||
{
|
||||
public:
|
||||
SdkPlatform()
|
||||
: apiLevel(-1)
|
||||
{}
|
||||
int apiLevel;
|
||||
bool isValid() const { return !name.isEmpty() && apiLevel != -1; }
|
||||
bool operator <(const SdkPlatform &other) const;
|
||||
int apiLevel = -1;
|
||||
QString name;
|
||||
QStringList abis;
|
||||
QString package;
|
||||
Utils::FileName installedLocation;
|
||||
SystemImageList systemImages;
|
||||
};
|
||||
using SdkPlatformList = QList<SdkPlatform>;
|
||||
|
||||
class ANDROID_EXPORT AndroidConfig
|
||||
{
|
||||
@@ -94,6 +112,7 @@ public:
|
||||
|
||||
Utils::FileName sdkLocation() const;
|
||||
void setSdkLocation(const Utils::FileName &sdkLocation);
|
||||
QVersionNumber sdkToolsVersion() const;
|
||||
|
||||
Utils::FileName ndkLocation() const;
|
||||
void setNdkLocation(const Utils::FileName &ndkLocation);
|
||||
@@ -116,18 +135,21 @@ public:
|
||||
bool automaticKitCreation() const;
|
||||
void setAutomaticKitCreation(bool b);
|
||||
|
||||
bool antScriptsAvailable() const;
|
||||
|
||||
bool useGrandle() const;
|
||||
void setUseGradle(bool b);
|
||||
|
||||
Utils::FileName adbToolPath() const;
|
||||
Utils::FileName androidToolPath() const;
|
||||
Utils::Environment androidToolEnvironment() const;
|
||||
Utils::FileName antToolPath() const;
|
||||
Utils::FileName emulatorToolPath() const;
|
||||
|
||||
Utils::FileName sdkManagerToolPath() const;
|
||||
Utils::FileName avdManagerToolPath() const;
|
||||
|
||||
Utils::FileName gccPath(const ProjectExplorer::Abi &abi, Core::Id lang,
|
||||
const QString &ndkToolChainVersion) const;
|
||||
|
||||
Utils::FileName gdbPath(const ProjectExplorer::Abi &abi, const QString &ndkToolChainVersion) const;
|
||||
|
||||
Utils::FileName keytoolPath() const;
|
||||
@@ -135,7 +157,8 @@ public:
|
||||
class CreateAvdInfo
|
||||
{
|
||||
public:
|
||||
QString target;
|
||||
bool isValid() const { return target.isValid() && !name.isEmpty(); }
|
||||
SdkPlatform target;
|
||||
QString name;
|
||||
QString abi;
|
||||
int sdcardSize = 0;
|
||||
@@ -143,19 +166,10 @@ public:
|
||||
};
|
||||
|
||||
CreateAvdInfo gatherCreateAVDInfo(QWidget *parent, int minApiLevel = 0, QString targetArch = QString()) const;
|
||||
QFuture<CreateAvdInfo> createAVD(CreateAvdInfo info) const;
|
||||
bool removeAVD(const QString &name) const;
|
||||
|
||||
QVector<AndroidDeviceInfo> connectedDevices(QString *error = 0) const;
|
||||
static QVector<AndroidDeviceInfo> connectedDevices(const QString &adbToolPath, QString *error = 0);
|
||||
|
||||
QFuture<QVector<AndroidDeviceInfo> > androidVirtualDevicesFuture() const;
|
||||
static QVector<AndroidDeviceInfo> androidVirtualDevices(const QString &androidTool, const Utils::Environment &environment);
|
||||
|
||||
QString startAVD(const QString &name) const;
|
||||
bool startAVDAsync(const QString &avdName) const;
|
||||
QString findAvd(const QString &avdName) const;
|
||||
QString waitForAvd(const QString &avdName, const QFutureInterface<bool> &fi = QFutureInterface<bool>()) const;
|
||||
QString bestNdkPlatformMatch(int target) const;
|
||||
|
||||
static ProjectExplorer::Abi abiForToolChainPrefix(const QString &toolchainPrefix);
|
||||
@@ -166,13 +180,10 @@ public:
|
||||
QString getProductModel(const QString &device) const;
|
||||
enum class OpenGl { Enabled, Disabled, Unknown };
|
||||
OpenGl getOpenGLEnabled(const QString &emulator) const;
|
||||
bool hasFinishedBooting(const QString &device) const;
|
||||
bool waitForBooted(const QString &serialNumber, const QFutureInterface<bool> &fi) const;
|
||||
bool isConnected(const QString &serialNumber) const;
|
||||
|
||||
SdkPlatform highestAndroidSdk() const;
|
||||
private:
|
||||
static CreateAvdInfo createAVDImpl(CreateAvdInfo info, Utils::FileName androidToolPath, Utils::Environment env);
|
||||
static QString getDeviceProperty(const QString &adbToolPath, const QString &device, const QString &property);
|
||||
|
||||
Utils::FileName toolPath(const ProjectExplorer::Abi &abi, const QString &ndkToolChainVersion) const;
|
||||
@@ -196,12 +207,11 @@ private:
|
||||
QStringList m_makeExtraSearchDirectories;
|
||||
unsigned m_partitionSize = 1024;
|
||||
bool m_automaticKitCreation = true;
|
||||
bool m_useGradle = false;
|
||||
bool m_useGradle = true; // Ant builds are deprecated.
|
||||
|
||||
//caches
|
||||
mutable bool m_availableSdkPlatformsUpToDate = false;
|
||||
mutable QVector<SdkPlatform> m_availableSdkPlatforms;
|
||||
static bool sortSdkPlatformByApiLevel(const SdkPlatform &a, const SdkPlatform &b);
|
||||
mutable SdkPlatformList m_availableSdkPlatforms;
|
||||
|
||||
mutable bool m_NdkInformationUpToDate = false;
|
||||
mutable QString m_toolchainHost;
|
||||
@@ -247,3 +257,5 @@ private:
|
||||
};
|
||||
|
||||
} // namespace Android
|
||||
Q_DECLARE_METATYPE(Android::SdkPlatform)
|
||||
|
||||
|
||||
@@ -180,6 +180,16 @@ AndroidDebugSupport::AndroidDebugSupport(RunControl *runControl)
|
||||
[this](const QString &output) {
|
||||
this->runControl()->showMessage(output, AppOutput);
|
||||
});
|
||||
|
||||
QTC_ASSERT(runControl, return);
|
||||
auto formatter = qobject_cast<AndroidOutputFormatter*>(runControl->outputFormatter());
|
||||
QTC_ASSERT(formatter, return);
|
||||
connect(m_runner, &AndroidRunner::pidFound, formatter, &AndroidOutputFormatter::appendPid);
|
||||
connect(m_runner, &AndroidRunner::pidLost, formatter, &AndroidOutputFormatter::removePid);
|
||||
connect(m_runner, &AndroidRunner::remoteProcessFinished, formatter,
|
||||
[formatter] {
|
||||
formatter->removePid(-1);
|
||||
});
|
||||
}
|
||||
|
||||
void AndroidDebugSupport::handleRemoteProcessStarted(Utils::Port gdbServerPort, Utils::Port qmlPort)
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "androidmanager.h"
|
||||
#include "androidconstants.h"
|
||||
#include "androidglobal.h"
|
||||
#include "androidavdmanager.h"
|
||||
|
||||
#include <coreplugin/fileutils.h>
|
||||
#include <coreplugin/icore.h>
|
||||
@@ -262,8 +263,9 @@ bool AndroidDeployQtStep::init(QList<const BuildStep *> &earlierSteps)
|
||||
|
||||
m_adbPath = AndroidConfigurations::currentConfig().adbToolPath().toString();
|
||||
|
||||
if (AndroidConfigurations::currentConfig().findAvd(m_avdName).isEmpty())
|
||||
AndroidConfigurations::currentConfig().startAVDAsync(m_avdName);
|
||||
AndroidAvdManager avdManager;
|
||||
if (avdManager.findAvd(m_avdName).isEmpty())
|
||||
avdManager.startAvdAsync(m_avdName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -414,7 +416,7 @@ void AndroidDeployQtStep::slotSetSerialNumber(const QString &serialNumber)
|
||||
void AndroidDeployQtStep::run(QFutureInterface<bool> &fi)
|
||||
{
|
||||
if (!m_avdName.isEmpty()) {
|
||||
QString serialNumber = AndroidConfigurations::currentConfig().waitForAvd(m_avdName, fi);
|
||||
QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, fi);
|
||||
if (serialNumber.isEmpty()) {
|
||||
reportRunResult(fi, false);
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "androiddevicedialog.h"
|
||||
#include "androidmanager.h"
|
||||
#include "androidavdmanager.h"
|
||||
#include "ui_androiddevicedialog.h"
|
||||
|
||||
#include <utils/environment.h>
|
||||
@@ -423,7 +424,8 @@ AndroidDeviceDialog::AndroidDeviceDialog(int apiLevel, const QString &abi, Andro
|
||||
m_ui(new Ui::AndroidDeviceDialog),
|
||||
m_apiLevel(apiLevel),
|
||||
m_abi(abi),
|
||||
m_defaultDevice(serialNumber)
|
||||
m_defaultDevice(serialNumber),
|
||||
m_avdManager(new AndroidAvdManager)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->deviceView->setModel(m_model);
|
||||
@@ -515,7 +517,7 @@ void AndroidDeviceDialog::refreshDeviceList()
|
||||
m_ui->refreshDevicesButton->setEnabled(false);
|
||||
m_progressIndicator->show();
|
||||
m_connectedDevices = AndroidConfig::connectedDevices(AndroidConfigurations::currentConfig().adbToolPath().toString());
|
||||
m_futureWatcherRefreshDevices.setFuture(AndroidConfigurations::currentConfig().androidVirtualDevicesFuture());
|
||||
m_futureWatcherRefreshDevices.setFuture(m_avdManager->avdList());
|
||||
}
|
||||
|
||||
void AndroidDeviceDialog::devicesRefreshed()
|
||||
@@ -530,7 +532,7 @@ void AndroidDeviceDialog::devicesRefreshed()
|
||||
serialNumber = deviceType == AndroidDeviceInfo::Hardware ? info.serialNumber : info.avdname;
|
||||
}
|
||||
|
||||
QVector<AndroidDeviceInfo> devices = m_futureWatcherRefreshDevices.result();
|
||||
AndroidDeviceInfoList devices = m_futureWatcherRefreshDevices.result();
|
||||
QSet<QString> startedAvds = Utils::transform<QSet>(m_connectedDevices,
|
||||
[] (const AndroidDeviceInfo &info) {
|
||||
return info.avdname;
|
||||
@@ -583,12 +585,12 @@ void AndroidDeviceDialog::createAvd()
|
||||
m_ui->createAVDButton->setEnabled(false);
|
||||
AndroidConfig::CreateAvdInfo info = AndroidConfigurations::currentConfig().gatherCreateAVDInfo(this, m_apiLevel, m_abi);
|
||||
|
||||
if (info.target.isEmpty()) {
|
||||
if (!info.target.isValid()) {
|
||||
m_ui->createAVDButton->setEnabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
m_futureWatcherAddDevice.setFuture(AndroidConfigurations::currentConfig().createAVD(info));
|
||||
m_futureWatcherAddDevice.setFuture(m_avdManager->createAvd(info));
|
||||
}
|
||||
|
||||
void AndroidDeviceDialog::avdAdded()
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QTime>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QModelIndex;
|
||||
QT_END_NAMESPACE
|
||||
@@ -41,6 +43,7 @@ namespace Utils { class ProgressIndicator; }
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
class AndroidAvdManager;
|
||||
class AndroidDeviceModel;
|
||||
namespace Ui { class AndroidDeviceDialog; }
|
||||
|
||||
@@ -74,9 +77,10 @@ private:
|
||||
QString m_abi;
|
||||
QString m_avdNameFromAdd;
|
||||
QString m_defaultDevice;
|
||||
std::unique_ptr<AndroidAvdManager> m_avdManager;
|
||||
QVector<AndroidDeviceInfo> m_connectedDevices;
|
||||
QFutureWatcher<AndroidConfig::CreateAvdInfo> m_futureWatcherAddDevice;
|
||||
QFutureWatcher<QVector<AndroidDeviceInfo>> m_futureWatcherRefreshDevices;
|
||||
QFutureWatcher<AndroidDeviceInfoList> m_futureWatcherRefreshDevices;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "androidqtsupport.h"
|
||||
#include "androidqtversion.h"
|
||||
#include "androidbuildapkstep.h"
|
||||
#include "androidavdmanager.h"
|
||||
|
||||
#include <coreplugin/documentmanager.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
@@ -60,11 +61,13 @@
|
||||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <QDomDocument>
|
||||
#include <QVersionNumber>
|
||||
|
||||
namespace {
|
||||
const QLatin1String AndroidManifestName("AndroidManifest.xml");
|
||||
const QLatin1String AndroidDefaultPropertiesName("project.properties");
|
||||
const QLatin1String AndroidDeviceSn("AndroidDeviceSerialNumber");
|
||||
const QLatin1String ApiLevelKey("AndroidVersion.ApiLevel");
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -343,7 +346,7 @@ void AndroidManager::cleanLibsOnDevice(ProjectExplorer::Target *target)
|
||||
QString deviceSerialNumber = info.serialNumber;
|
||||
|
||||
if (info.type == AndroidDeviceInfo::Emulator) {
|
||||
deviceSerialNumber = AndroidConfigurations::currentConfig().startAVD(info.avdname);
|
||||
deviceSerialNumber = AndroidAvdManager().startAvd(info.avdname);
|
||||
if (deviceSerialNumber.isEmpty())
|
||||
Core::MessageManager::write(tr("Starting Android virtual device failed."));
|
||||
}
|
||||
@@ -372,7 +375,7 @@ void AndroidManager::installQASIPackage(ProjectExplorer::Target *target, const Q
|
||||
|
||||
QString deviceSerialNumber = info.serialNumber;
|
||||
if (info.type == AndroidDeviceInfo::Emulator) {
|
||||
deviceSerialNumber = AndroidConfigurations::currentConfig().startAVD(info.avdname);
|
||||
deviceSerialNumber = AndroidAvdManager().startAvd(info.avdname);
|
||||
if (deviceSerialNumber.isEmpty())
|
||||
Core::MessageManager::write(tr("Starting Android virtual device failed."));
|
||||
}
|
||||
@@ -565,18 +568,33 @@ bool AndroidManager::updateGradleProperties(ProjectExplorer::Target *target)
|
||||
gradleProperties["buildDir"] = ".build";
|
||||
gradleProperties["androidCompileSdkVersion"] = buildTargetSDK(target).split(QLatin1Char('-')).last().toLocal8Bit();
|
||||
if (gradleProperties["androidBuildToolsVersion"].isEmpty()) {
|
||||
QString maxVersion;
|
||||
QVersionNumber maxVersion;
|
||||
QDir buildToolsDir(AndroidConfigurations::currentConfig().sdkLocation().appendPath(QLatin1String("build-tools")).toString());
|
||||
foreach (const QFileInfo &file, buildToolsDir.entryList(QDir::Dirs|QDir::NoDotAndDotDot)) {
|
||||
QString ver(file.fileName());
|
||||
QVersionNumber ver = QVersionNumber::fromString(file.fileName());
|
||||
if (maxVersion < ver)
|
||||
maxVersion = ver;
|
||||
}
|
||||
if (maxVersion.isEmpty())
|
||||
if (maxVersion.isNull())
|
||||
return false;
|
||||
gradleProperties["androidBuildToolsVersion"] = maxVersion.toLocal8Bit();
|
||||
gradleProperties["androidBuildToolsVersion"] = maxVersion.toString().toLocal8Bit();
|
||||
}
|
||||
return mergeGradleProperties(gradlePropertiesPath, gradleProperties);
|
||||
}
|
||||
|
||||
int AndroidManager::findApiLevel(const Utils::FileName &platformPath)
|
||||
{
|
||||
int apiLevel = -1;
|
||||
Utils::FileName propertiesPath = platformPath;
|
||||
propertiesPath.appendPath("/source.properties");
|
||||
if (propertiesPath.exists()) {
|
||||
QSettings sdkProperties(propertiesPath.toString(), QSettings::IniFormat);
|
||||
bool validInt = false;
|
||||
apiLevel = sdkProperties.value(ApiLevelKey).toInt(&validInt);
|
||||
if (!validInt)
|
||||
apiLevel = -1;
|
||||
}
|
||||
return apiLevel;
|
||||
}
|
||||
|
||||
} // namespace Android
|
||||
|
||||
@@ -89,6 +89,7 @@ public:
|
||||
static AndroidQtSupport *androidQtSupport(ProjectExplorer::Target *target);
|
||||
static bool useGradle(ProjectExplorer::Target *target);
|
||||
static bool updateGradleProperties(ProjectExplorer::Target *target);
|
||||
static int findApiLevel(const Utils::FileName &platformPath);
|
||||
};
|
||||
|
||||
} // namespace Android
|
||||
|
||||
@@ -29,16 +29,205 @@
|
||||
#include "androidmanager.h"
|
||||
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectexplorersettings.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <qtsupport/qtoutputformatter.h>
|
||||
#include <qtsupport/qtkitinformation.h>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QRegularExpression>
|
||||
#include <QToolButton>
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
using namespace ProjectExplorer;
|
||||
|
||||
namespace Android {
|
||||
|
||||
static QRegularExpression logCatRegExp("([0-9\\-]*\\s+[0-9\\-:.]*)" // 1. time
|
||||
"\\s*"
|
||||
"([DEIVWF])" // 2. log level
|
||||
"\\/"
|
||||
"(.*)" // 3. TAG
|
||||
"\\(\\s*"
|
||||
"(\\d+)" // 4. PID
|
||||
"\\)\\:\\s"
|
||||
"(.*)"); // 5. Message
|
||||
|
||||
AndroidOutputFormatter::AndroidOutputFormatter(Project *project)
|
||||
: QtSupport::QtOutputFormatter(project)
|
||||
, m_filtersButton(new QToolButton)
|
||||
{
|
||||
auto filtersMenu = new QMenu(m_filtersButton.data());
|
||||
|
||||
m_filtersButton->setToolTip(tr("Filters"));
|
||||
m_filtersButton->setIcon(Utils::Icons::FILTER.icon());
|
||||
m_filtersButton->setProperty("noArrow", true);
|
||||
m_filtersButton->setAutoRaise(true);
|
||||
m_filtersButton->setPopupMode(QToolButton::InstantPopup);
|
||||
m_filtersButton->setMenu(filtersMenu);
|
||||
|
||||
auto logsMenu = filtersMenu->addMenu(tr("Log Level"));
|
||||
addLogAction(All, logsMenu, tr("All"));
|
||||
addLogAction(Verbose, logsMenu, tr("Verbose"));
|
||||
addLogAction(Info, logsMenu, tr("Info"));
|
||||
addLogAction(Debug, logsMenu, tr("Debug"));
|
||||
addLogAction(Warning, logsMenu, tr("Warning"));
|
||||
addLogAction(Error, logsMenu, tr("Error"));
|
||||
addLogAction(Fatal, logsMenu, tr("Fatal"));
|
||||
updateLogMenu();
|
||||
m_appsMenu = filtersMenu->addMenu(tr("Applications"));
|
||||
appendPid(-1, tr("All"));
|
||||
}
|
||||
|
||||
AndroidOutputFormatter::~AndroidOutputFormatter()
|
||||
{}
|
||||
|
||||
QList<QWidget *> AndroidOutputFormatter::toolbarWidgets() const
|
||||
{
|
||||
return QList<QWidget *>{m_filtersButton.data()};
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::appendMessage(const QString &text, Utils::OutputFormat format)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return;
|
||||
|
||||
CachedLine line;
|
||||
line.content = text;
|
||||
|
||||
if (format < Utils::StdOutFormat) {
|
||||
line.level = SkipFiltering;
|
||||
line.pid = -1;
|
||||
} else {
|
||||
QRegularExpressionMatch match = logCatRegExp.match(text);
|
||||
if (!match.hasMatch())
|
||||
return;
|
||||
line.level = None;
|
||||
|
||||
switch (match.captured(2).toLatin1()[0]) {
|
||||
case 'D': line.level = Debug; break;
|
||||
case 'I': line.level = Info; break;
|
||||
case 'V': line.level = Verbose; break;
|
||||
case 'W': line.level = Warning; break;
|
||||
case 'E': line.level = Error; break;
|
||||
case 'F': line.level = Fatal; break;
|
||||
default: return;
|
||||
}
|
||||
line.pid = match.captured(4).toLongLong();
|
||||
}
|
||||
|
||||
m_cachedLines.append(line);
|
||||
if (m_cachedLines.size() > ProjectExplorerPlugin::projectExplorerSettings().maxAppOutputLines)
|
||||
m_cachedLines.pop_front();
|
||||
|
||||
filterMessage(line);
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::clear()
|
||||
{
|
||||
m_cachedLines.clear();
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::appendPid(qint64 pid, const QString &name)
|
||||
{
|
||||
if (m_pids.contains(pid))
|
||||
return;
|
||||
|
||||
auto action = m_appsMenu->addAction(name);
|
||||
m_pids[pid] = action;
|
||||
action->setCheckable(true);
|
||||
action->setChecked(pid != -1);
|
||||
connect(action, &QAction::triggered, this, &AndroidOutputFormatter::applyFilter);
|
||||
applyFilter();
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::removePid(qint64 pid)
|
||||
{
|
||||
if (pid == -1) {
|
||||
for (auto action : m_pids)
|
||||
m_appsMenu->removeAction(action);
|
||||
m_pids.clear();
|
||||
} else {
|
||||
m_appsMenu->removeAction(m_pids[pid]);
|
||||
m_pids.remove(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::updateLogMenu(LogLevel set, LogLevel reset)
|
||||
{
|
||||
m_logLevelFlags |= set;
|
||||
m_logLevelFlags &= ~reset;
|
||||
for (const auto & pair : m_logLevels)
|
||||
pair.second->setChecked((m_logLevelFlags & pair.first) == pair.first);
|
||||
|
||||
applyFilter();
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::filterMessage(const CachedLine &line)
|
||||
{
|
||||
if (line.level == SkipFiltering || m_pids[-1]->isChecked()) {
|
||||
QtOutputFormatter::appendMessage(line.content, Utils::NormalMessageFormat);
|
||||
} else {
|
||||
// Filter Log Level
|
||||
if (!(m_logLevelFlags & line.level))
|
||||
return;
|
||||
|
||||
// Filter PIDs
|
||||
if (!m_pids[-1]->isChecked()) {
|
||||
auto it = m_pids.find(line.pid);
|
||||
if (it == m_pids.end() || !(*it)->isChecked())
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::OutputFormat format = Utils::NormalMessageFormat;
|
||||
switch (line.level) {
|
||||
case Debug:
|
||||
format = Utils::DebugFormat;
|
||||
break;
|
||||
case Info:
|
||||
case Verbose:
|
||||
format = Utils::StdOutFormat;
|
||||
break;
|
||||
|
||||
case Warning:
|
||||
case Error:
|
||||
case Fatal:
|
||||
format = Utils::StdErrFormat;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::OutputFormatter::appendMessage(line.content, format);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidOutputFormatter::applyFilter()
|
||||
{
|
||||
if (!plainTextEdit())
|
||||
return;
|
||||
|
||||
plainTextEdit()->clear();
|
||||
if (!m_pids[-1]->isChecked()) {
|
||||
bool allOn = true;
|
||||
for (auto action : m_pids) {
|
||||
if (!action->isChecked()) {
|
||||
allOn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_pids[-1]->setChecked(allOn);
|
||||
} else {
|
||||
for (auto action : m_pids)
|
||||
action->setChecked(true);
|
||||
}
|
||||
|
||||
for (const auto &line : m_cachedLines)
|
||||
filterMessage(line);
|
||||
}
|
||||
|
||||
AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id)
|
||||
: RunConfiguration(parent, id)
|
||||
{
|
||||
@@ -56,7 +245,7 @@ QWidget *AndroidRunConfiguration::createConfigurationWidget()
|
||||
|
||||
Utils::OutputFormatter *AndroidRunConfiguration::createOutputFormatter() const
|
||||
{
|
||||
return new QtSupport::QtOutputFormatter(target()->project());
|
||||
return new AndroidOutputFormatter(target()->project());
|
||||
}
|
||||
|
||||
const QString AndroidRunConfiguration::remoteChannel() const
|
||||
|
||||
@@ -28,9 +28,75 @@
|
||||
#include "android_global.h"
|
||||
|
||||
#include <projectexplorer/runconfiguration.h>
|
||||
#include <qtsupport/qtoutputformatter.h>
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QToolButton;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Android {
|
||||
|
||||
class AndroidOutputFormatter : public QtSupport::QtOutputFormatter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum LogLevel {
|
||||
None = 0,
|
||||
Verbose = 1,
|
||||
Info = 1 << 1,
|
||||
Debug = 1 << 2,
|
||||
Warning = 1 << 3,
|
||||
Error = 1 << 4,
|
||||
Fatal = 1 << 5,
|
||||
All = Verbose | Info | Debug | Warning | Error | Fatal,
|
||||
SkipFiltering = ~All
|
||||
};
|
||||
|
||||
public:
|
||||
explicit AndroidOutputFormatter(ProjectExplorer::Project *project);
|
||||
~AndroidOutputFormatter();
|
||||
|
||||
// OutputFormatter interface
|
||||
QList<QWidget*> toolbarWidgets() const override;
|
||||
void appendMessage(const QString &text, Utils::OutputFormat format) override;
|
||||
void clear() override;
|
||||
|
||||
public slots:
|
||||
void appendPid(qint64 pid, const QString &name);
|
||||
void removePid(qint64 pid);
|
||||
|
||||
private:
|
||||
struct CachedLine {
|
||||
qint64 pid;
|
||||
LogLevel level;
|
||||
QString content;
|
||||
};
|
||||
|
||||
private:
|
||||
void updateLogMenu(LogLevel set = None, LogLevel reset = None);
|
||||
void filterMessage(const CachedLine &line);
|
||||
|
||||
void applyFilter();
|
||||
void addLogAction(LogLevel level, QMenu *logsMenu, const QString &name) {
|
||||
auto action = logsMenu->addAction(name);
|
||||
m_logLevels.push_back(qMakePair(level, action));
|
||||
action->setCheckable(true);
|
||||
connect(action, &QAction::triggered, this, [level, this](bool checked) {
|
||||
updateLogMenu(checked ? level : None , checked ? None : level);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
int m_logLevelFlags = All;
|
||||
QVector<QPair<LogLevel, QAction*>> m_logLevels;
|
||||
QHash<qint64, QAction*> m_pids;
|
||||
QScopedPointer<QToolButton> m_filtersButton;
|
||||
QMenu *m_appsMenu;
|
||||
QList<CachedLine> m_cachedLines;
|
||||
};
|
||||
|
||||
class ANDROID_EXPORT AndroidRunConfiguration : public ProjectExplorer::RunConfiguration
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -62,6 +62,13 @@ void AndroidRunControl::start()
|
||||
this, &AndroidRunControl::handleRemoteOutput);
|
||||
connect(m_runner, &AndroidRunner::remoteProcessFinished,
|
||||
this, &AndroidRunControl::handleRemoteProcessFinished);
|
||||
|
||||
auto formatter = static_cast<AndroidOutputFormatter *>(outputFormatter());
|
||||
connect(m_runner, &AndroidRunner::pidFound,
|
||||
formatter, &AndroidOutputFormatter::appendPid);
|
||||
connect(m_runner, &AndroidRunner::pidLost,
|
||||
formatter, &AndroidOutputFormatter::removePid);
|
||||
|
||||
appendMessage(tr("Starting remote process."), Utils::NormalMessageFormat);
|
||||
m_runner->setRunnable(runnable().as<AndroidRunnable>());
|
||||
m_runner->start();
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "androidglobal.h"
|
||||
#include "androidrunconfiguration.h"
|
||||
#include "androidmanager.h"
|
||||
#include "androidavdmanager.h"
|
||||
|
||||
#include <debugger/debuggerrunconfigurationaspect.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
@@ -51,6 +52,7 @@
|
||||
#include <QTime>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
@@ -125,10 +127,10 @@ namespace Internal {
|
||||
|
||||
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
|
||||
const int MAX_SOCKET_HANDSHAKE_PORT = 20999;
|
||||
static const QString pidScript = QStringLiteral("for p in /proc/[0-9]*; "
|
||||
"do cat <$p/cmdline && echo :${p##*/}; done");
|
||||
static const QString pidPollingScript = QStringLiteral("while true; do sleep 1; "
|
||||
"cat /proc/%1/cmdline > /dev/null; done");
|
||||
static const QString pidScript = QStringLiteral("input keyevent KEYCODE_WAKEUP; "
|
||||
"while true; do sleep 1; echo \"=\"; "
|
||||
"for p in /proc/[0-9]*; "
|
||||
"do cat <$p/cmdline && echo :${p##*/}; done; done");
|
||||
|
||||
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
||||
"\\s+"
|
||||
@@ -146,55 +148,26 @@ static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
|
||||
);
|
||||
static int APP_START_TIMEOUT = 45000;
|
||||
|
||||
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
|
||||
int msecs = APP_START_TIMEOUT)
|
||||
enum class PidStatus {
|
||||
Found,
|
||||
Lost
|
||||
};
|
||||
|
||||
struct PidInfo
|
||||
{
|
||||
bool timedOut = false;
|
||||
auto end = chrono::high_resolution_clock::now();
|
||||
if (chrono::duration_cast<chrono::milliseconds>(end-start).count() > msecs)
|
||||
timedOut = true;
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
static qint64 extractPID(const QByteArray &output, const QString &packageName)
|
||||
{
|
||||
qint64 pid = -1;
|
||||
foreach (auto tuple, output.split('\n')) {
|
||||
tuple = tuple.simplified();
|
||||
if (!tuple.isEmpty()) {
|
||||
auto parts = tuple.split(':');
|
||||
QString commandName = QString::fromLocal8Bit(parts.first());
|
||||
if (parts.length() == 2 && commandName == packageName) {
|
||||
pid = parts.last().toLongLong();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath,
|
||||
QStringList selector, const QString &packageName)
|
||||
{
|
||||
if (packageName.isEmpty())
|
||||
return;
|
||||
|
||||
qint64 processPID = -1;
|
||||
chrono::high_resolution_clock::time_point start = chrono::high_resolution_clock::now();
|
||||
do {
|
||||
QThread::msleep(200);
|
||||
const QByteArray out = Utils::SynchronousProcess()
|
||||
.runBlocking(adbPath, selector << QStringLiteral("shell") << pidScript)
|
||||
.allRawOutput();
|
||||
processPID = extractPID(out, packageName);
|
||||
} while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
|
||||
|
||||
if (!fi.isCanceled())
|
||||
fi.reportResult(processPID);
|
||||
}
|
||||
PidInfo(qint64 pid = -1, PidStatus status = PidStatus::Lost, QString name = {})
|
||||
: pid(pid)
|
||||
, status(status)
|
||||
, name(name)
|
||||
{}
|
||||
qint64 pid;
|
||||
PidStatus status;
|
||||
QString name;
|
||||
};
|
||||
|
||||
static void deleter(QProcess *p)
|
||||
{
|
||||
p->disconnect();
|
||||
p->kill();
|
||||
p->waitForFinished();
|
||||
// Might get deleted from its own signal handler.
|
||||
@@ -228,29 +201,31 @@ signals:
|
||||
|
||||
void remoteOutput(const QString &output);
|
||||
void remoteErrorOutput(const QString &output);
|
||||
void pidFound(qint64, const QString &name);
|
||||
void pidLost(qint64);
|
||||
|
||||
private:
|
||||
void onProcessIdChanged(qint64 pid);
|
||||
void findProcessPids();
|
||||
void onProcessIdChanged(PidInfo pidInfo);
|
||||
void logcatReadStandardError();
|
||||
void logcatReadStandardOutput();
|
||||
void adbKill(qint64 pid);
|
||||
QStringList selector() const { return m_selector; }
|
||||
void forceStop();
|
||||
void findPs();
|
||||
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
|
||||
bool adbShellAmNeedsQuotes();
|
||||
bool runAdb(const QStringList &args, QString *exitMessage = nullptr, int timeoutS = 10);
|
||||
int deviceSdkVersion();
|
||||
|
||||
// Create the processes and timer in the worker thread, for correct thread affinity
|
||||
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
|
||||
std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
|
||||
std::unique_ptr<QProcess, decltype(&deleter)> m_pidsFinderProcess;
|
||||
QScopedPointer<QTcpSocket> m_socket;
|
||||
|
||||
QByteArray m_stdoutBuffer;
|
||||
QByteArray m_stderrBuffer;
|
||||
|
||||
QFuture<qint64> m_pidFinder;
|
||||
qint64 m_processPID = -1;
|
||||
QSet<qint64> m_processPids;
|
||||
bool m_useCppDebugger = false;
|
||||
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
|
||||
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
|
||||
@@ -261,20 +236,20 @@ private:
|
||||
QString m_gdbserverSocket;
|
||||
QString m_adb;
|
||||
QStringList m_selector;
|
||||
QRegExp m_logCatRegExp;
|
||||
DebugHandShakeType m_handShakeMethod = SocketHandShake;
|
||||
bool m_customPort = false;
|
||||
|
||||
QString m_packageName;
|
||||
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
|
||||
QByteArray m_pidsBuffer;
|
||||
QScopedPointer<QTimer> m_timeoutTimer;
|
||||
};
|
||||
|
||||
AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Core::Id runMode,
|
||||
const QString &packageName, const QStringList &selector)
|
||||
: m_adbLogcatProcess(nullptr, deleter)
|
||||
, m_psIsAlive(nullptr, deleter)
|
||||
, m_pidsFinderProcess(nullptr, deleter)
|
||||
, m_selector(selector)
|
||||
, m_logCatRegExp(regExpLogcat)
|
||||
, m_packageName(packageName)
|
||||
{
|
||||
Debugger::DebuggerRunConfigurationAspect *aspect
|
||||
@@ -338,23 +313,18 @@ AndroidRunnerWorker::AndroidRunnerWorker(AndroidRunConfiguration *runConfig, Cor
|
||||
|
||||
AndroidRunnerWorker::~AndroidRunnerWorker()
|
||||
{
|
||||
if (!m_pidFinder.isFinished())
|
||||
m_pidFinder.cancel();
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::forceStop()
|
||||
{
|
||||
runAdb(selector() << "shell" << "am" << "force-stop" << m_packageName, nullptr, 30);
|
||||
|
||||
// try killing it via kill -9
|
||||
const QByteArray out = Utils::SynchronousProcess()
|
||||
.runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript)
|
||||
.allRawOutput();
|
||||
|
||||
qint64 pid = extractPID(out.simplified(), m_packageName);
|
||||
if (pid != -1) {
|
||||
adbKill(pid);
|
||||
for (auto it = m_processPids.constBegin(); it != m_processPids.constEnd(); ++it) {
|
||||
emit pidLost(*it);
|
||||
adbKill(*it);
|
||||
}
|
||||
m_processPids.clear();
|
||||
m_pidsBuffer.clear();
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
||||
@@ -368,8 +338,12 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
||||
this, &AndroidRunnerWorker::logcatReadStandardOutput);
|
||||
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
|
||||
this, &AndroidRunnerWorker::logcatReadStandardError);
|
||||
|
||||
// Its assumed that the device or avd returned by selector() is online.
|
||||
logcatProcess->start(m_adb, selector() << "logcat");
|
||||
QStringList logcatArgs = selector() << "logcat" << "-v" << "time";
|
||||
if (deviceSdkVersion() > 20)
|
||||
logcatArgs << "-T" << "0";
|
||||
logcatProcess->start(m_adb, logcatArgs);
|
||||
|
||||
QString errorMessage;
|
||||
|
||||
@@ -507,9 +481,20 @@ void AndroidRunnerWorker::asyncStart(const QString &intentName,
|
||||
|
||||
QTC_ASSERT(!m_adbLogcatProcess, /**/);
|
||||
m_adbLogcatProcess = std::move(logcatProcess);
|
||||
m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
|
||||
m_packageName),
|
||||
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
|
||||
|
||||
m_timeoutTimer.reset(new QTimer);
|
||||
m_timeoutTimer->setSingleShot(true);
|
||||
connect(m_timeoutTimer.data(), &QTimer::timeout,
|
||||
this,[this] { onProcessIdChanged(PidInfo{}); });
|
||||
m_timeoutTimer->start(APP_START_TIMEOUT);
|
||||
|
||||
m_pidsFinderProcess.reset(new QProcess);
|
||||
m_pidsFinderProcess->setProcessChannelMode(QProcess::MergedChannels);
|
||||
connect(m_pidsFinderProcess.get(), &QProcess::readyRead, this, &AndroidRunnerWorker::findProcessPids);
|
||||
connect(m_pidsFinderProcess.get(),
|
||||
static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
this, [this] { onProcessIdChanged(PidInfo{}); });
|
||||
m_pidsFinderProcess->start(m_adb, selector() << "shell" << pidScript);
|
||||
}
|
||||
|
||||
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
|
||||
@@ -545,6 +530,19 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage,
|
||||
return response.result == Utils::SynchronousProcessResponse::Finished;
|
||||
}
|
||||
|
||||
int AndroidRunnerWorker::deviceSdkVersion()
|
||||
{
|
||||
Utils::SynchronousProcess adb;
|
||||
adb.setTimeoutS(10);
|
||||
Utils::SynchronousProcessResponse response
|
||||
= adb.run(m_adb, selector() << "shell" << "getprop" << "ro.build.version.sdk");
|
||||
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|
||||
|| response.result != Utils::SynchronousProcessResponse::Finished)
|
||||
return -1;
|
||||
|
||||
return response.allOutput().trimmed().toInt();
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
|
||||
{
|
||||
if (m_useCppDebugger) {
|
||||
@@ -558,21 +556,79 @@ void AndroidRunnerWorker::handleRemoteDebuggerRunning()
|
||||
|
||||
runAdb(selector() << "push" << tmp.fileName() << m_pongFile);
|
||||
}
|
||||
QTC_CHECK(m_processPID != -1);
|
||||
QTC_CHECK(!m_processPids.isEmpty());
|
||||
}
|
||||
emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::findProcessPids()
|
||||
{
|
||||
static QMap<qint64, QByteArray> extractedPids;
|
||||
static auto oldPids = m_processPids;
|
||||
|
||||
m_pidsBuffer += m_pidsFinderProcess->readAll();
|
||||
while (!m_pidsBuffer.isEmpty()) {
|
||||
const int to = m_pidsBuffer.indexOf('\n');
|
||||
if (to < 0)
|
||||
break;
|
||||
|
||||
if (to == 0) {
|
||||
m_pidsBuffer = m_pidsBuffer.mid(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// = is used to delimit ps outputs
|
||||
// is needed to know when an existins PID is killed
|
||||
if (m_pidsBuffer[0] != '=') {
|
||||
QByteArray tuple = m_pidsBuffer.left(to + 1).simplified();
|
||||
QList<QByteArray> parts = tuple.split(':');
|
||||
QByteArray commandName = parts.takeFirst();
|
||||
if (QString::fromLocal8Bit(commandName) == m_packageName) {
|
||||
auto pid = parts.last().toLongLong();
|
||||
if (!m_processPids.contains(pid)) {
|
||||
extractedPids[pid] = commandName + (parts.length() == 2
|
||||
? ":" + parts.first() : QByteArray{});
|
||||
} else {
|
||||
oldPids.remove(pid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add new PIDs
|
||||
for (auto it = extractedPids.constBegin(); it != extractedPids.constEnd(); ++it) {
|
||||
onProcessIdChanged(PidInfo(it.key(), PidStatus::Found,
|
||||
QString::fromLocal8Bit(it.value())));
|
||||
}
|
||||
extractedPids.clear();
|
||||
|
||||
// Remove the dead ones
|
||||
for (auto it = oldPids.constBegin(); it != oldPids.constEnd(); ++it)
|
||||
onProcessIdChanged(PidInfo(*it, PidStatus::Lost));
|
||||
|
||||
// Save the current non dead PIDs
|
||||
oldPids = m_processPids;
|
||||
if (m_processPids.isEmpty()) {
|
||||
extractedPids.clear();
|
||||
m_pidsBuffer.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_pidsBuffer = m_pidsBuffer.mid(to + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::asyncStop(const QVector<QStringList> &adbCommands)
|
||||
{
|
||||
if (!m_pidFinder.isFinished())
|
||||
m_pidFinder.cancel();
|
||||
|
||||
if (m_processPID != -1) {
|
||||
m_timeoutTimer.reset();
|
||||
m_pidsFinderProcess.reset();
|
||||
if (!m_processPids.isEmpty())
|
||||
forceStop();
|
||||
}
|
||||
|
||||
foreach (const QStringList &entry, adbCommands)
|
||||
runAdb(selector() << entry);
|
||||
|
||||
m_adbLogcatProcess.reset();
|
||||
emit remoteProcessFinished(QLatin1String("\n\n") +
|
||||
tr("\"%1\" terminated.").arg(m_packageName));
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector)
|
||||
@@ -594,58 +650,48 @@ void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buff
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
QString pidString = QString::number(m_processPID);
|
||||
foreach (const QByteArray &msg, lines) {
|
||||
const QString line = QString::fromUtf8(msg).trimmed() + QLatin1Char('\n');
|
||||
if (!line.contains(pidString))
|
||||
continue;
|
||||
if (m_logCatRegExp.exactMatch(line)) {
|
||||
// Android M
|
||||
if (m_logCatRegExp.cap(1) == pidString) {
|
||||
const QString &messagetype = m_logCatRegExp.cap(2);
|
||||
QString output = line.mid(m_logCatRegExp.pos(2));
|
||||
|
||||
if (onlyError
|
||||
|| messagetype == QLatin1String("F")
|
||||
|| messagetype == QLatin1String("E")
|
||||
|| messagetype == QLatin1String("W"))
|
||||
emit remoteErrorOutput(output);
|
||||
else
|
||||
emit remoteOutput(output);
|
||||
}
|
||||
} else {
|
||||
if (onlyError || line.startsWith("F/")
|
||||
|| line.startsWith("E/")
|
||||
|| line.startsWith("W/"))
|
||||
emit remoteErrorOutput(line);
|
||||
else
|
||||
emit remoteOutput(line);
|
||||
}
|
||||
const QString line = QString::fromUtf8(msg.trimmed());
|
||||
if (onlyError)
|
||||
emit remoteErrorOutput(line);
|
||||
else
|
||||
emit remoteOutput(line);
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
|
||||
void AndroidRunnerWorker::onProcessIdChanged(PidInfo pidInfo)
|
||||
{
|
||||
// Don't write to m_psProc from a different thread
|
||||
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
||||
m_processPID = pid;
|
||||
if (m_processPID == -1) {
|
||||
|
||||
auto isFirst = m_processPids.isEmpty();
|
||||
if (pidInfo.status == PidStatus::Lost) {
|
||||
m_processPids.remove(pidInfo.pid);
|
||||
emit pidLost(pidInfo.pid);
|
||||
} else {
|
||||
m_processPids.insert(pidInfo.pid);
|
||||
emit pidFound(pidInfo.pid, pidInfo.name);
|
||||
}
|
||||
|
||||
if (m_processPids.isEmpty() || pidInfo.pid == -1) {
|
||||
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
|
||||
.arg(m_packageName));
|
||||
// App died/killed. Reset log and monitor processes.
|
||||
forceStop();
|
||||
m_adbLogcatProcess.reset();
|
||||
m_psIsAlive.reset();
|
||||
} else {
|
||||
m_timeoutTimer.reset();
|
||||
} else if (isFirst) {
|
||||
m_timeoutTimer.reset();
|
||||
if (m_useCppDebugger) {
|
||||
// This will be funneled to the engine to actually start and attach
|
||||
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
||||
QByteArray serverChannel = ':' + QByteArray::number(m_localGdbServerPort.number());
|
||||
emit remoteServerRunning(serverChannel, m_processPID);
|
||||
emit remoteServerRunning(serverChannel, pidInfo.pid);
|
||||
} else if (m_qmlDebugServices == QmlDebug::QmlDebuggerServices) {
|
||||
// This will be funneled to the engine to actually start and attach
|
||||
// gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
|
||||
QByteArray serverChannel = QByteArray::number(m_qmlPort.number());
|
||||
emit remoteServerRunning(serverChannel, m_processPID);
|
||||
emit remoteServerRunning(serverChannel, pidInfo.pid);
|
||||
} else if (m_qmlDebugServices == QmlDebug::QmlProfilerServices) {
|
||||
emit remoteProcessStarted(Utils::Port(), m_qmlPort);
|
||||
} else {
|
||||
@@ -653,27 +699,18 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
|
||||
emit remoteProcessStarted(Utils::Port(), Utils::Port());
|
||||
}
|
||||
logcatReadStandardOutput();
|
||||
QTC_ASSERT(!m_psIsAlive, /**/);
|
||||
m_psIsAlive.reset(new QProcess);
|
||||
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
|
||||
connect(m_psIsAlive.get(), &QProcess::readyRead, [this](){
|
||||
if (!m_psIsAlive->readAll().simplified().isEmpty())
|
||||
onProcessIdChanged(-1);
|
||||
});
|
||||
m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
|
||||
<< pidPollingScript.arg(m_processPID));
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::logcatReadStandardError()
|
||||
{
|
||||
if (m_processPID != -1)
|
||||
if (!m_processPids.isEmpty() && m_adbLogcatProcess)
|
||||
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
|
||||
}
|
||||
|
||||
void AndroidRunnerWorker::logcatReadStandardOutput()
|
||||
{
|
||||
if (m_processPID != -1)
|
||||
if (!m_processPids.isEmpty() && m_adbLogcatProcess)
|
||||
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
|
||||
}
|
||||
|
||||
@@ -724,6 +761,10 @@ AndroidRunner::AndroidRunner(QObject *parent, RunConfiguration *runConfig, Core:
|
||||
this, &AndroidRunner::remoteOutput);
|
||||
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
|
||||
this, &AndroidRunner::remoteErrorOutput);
|
||||
connect(m_worker.data(), &AndroidRunnerWorker::pidFound,
|
||||
this, &AndroidRunner::pidFound);
|
||||
connect(m_worker.data(), &AndroidRunnerWorker::pidLost,
|
||||
this, &AndroidRunner::pidLost);
|
||||
|
||||
m_thread.start();
|
||||
}
|
||||
@@ -791,8 +832,9 @@ void AndroidRunner::launchAVD()
|
||||
emit adbParametersChanged(m_androidRunnable.packageName,
|
||||
AndroidDeviceInfo::adbSelector(info.serialNumber));
|
||||
if (info.isValid()) {
|
||||
if (AndroidConfigurations::currentConfig().findAvd(info.avdname).isEmpty()) {
|
||||
bool launched = AndroidConfigurations::currentConfig().startAVDAsync(info.avdname);
|
||||
AndroidAvdManager avdManager;
|
||||
if (avdManager.findAvd(info.avdname).isEmpty()) {
|
||||
bool launched = avdManager.startAvdAsync(info.avdname);
|
||||
m_launchedAVDName = launched ? info.avdname:"";
|
||||
} else {
|
||||
m_launchedAVDName.clear();
|
||||
@@ -803,11 +845,12 @@ void AndroidRunner::launchAVD()
|
||||
void AndroidRunner::checkAVD()
|
||||
{
|
||||
const AndroidConfig &config = AndroidConfigurations::currentConfig();
|
||||
QString serialNumber = config.findAvd(m_launchedAVDName);
|
||||
AndroidAvdManager avdManager(config);
|
||||
QString serialNumber = avdManager.findAvd(m_launchedAVDName);
|
||||
if (!serialNumber.isEmpty())
|
||||
return; // try again on next timer hit
|
||||
|
||||
if (config.hasFinishedBooting(serialNumber)) {
|
||||
if (avdManager.isAvdBooted(serialNumber)) {
|
||||
m_checkAVDTimer.stop();
|
||||
AndroidManager::setDeviceSerialNumber(m_runConfig->target(), serialNumber);
|
||||
emit asyncStart(m_androidRunnable.intentName, m_androidRunnable.beforeStartADBCommands);
|
||||
|
||||
@@ -75,6 +75,9 @@ signals:
|
||||
void adbParametersChanged(const QString &packageName, const QStringList &selector);
|
||||
void avdDetected();
|
||||
|
||||
void pidFound(qint64, const QString &name);
|
||||
void pidLost(qint64);
|
||||
|
||||
private:
|
||||
void checkAVD();
|
||||
void launchAVD();
|
||||
|
||||
337
src/plugins/android/androidsdkmanager.cpp
Normal file
337
src/plugins/android/androidsdkmanager.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "androidsdkmanager.h"
|
||||
|
||||
#include "androidmanager.h"
|
||||
#include "androidtoolmanager.h"
|
||||
|
||||
#include "utils/algorithm.h"
|
||||
#include "utils/qtcassert.h"
|
||||
#include "utils/synchronousprocess.h"
|
||||
#include "utils/environment.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QSettings>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(sdkManagerLog, "qtc.android.sdkManager")
|
||||
}
|
||||
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
// Though sdk manager is introduced in 25.2.3 but the verbose mode is avaialble in 25.3.0
|
||||
// and android tool is supported in 25.2.3
|
||||
const QVersionNumber sdkManagerIntroVersion(25, 3 ,0);
|
||||
|
||||
const char installLocationKey[] = "Installed Location:";
|
||||
const char apiLevelPropertyKey[] = "AndroidVersion.ApiLevel";
|
||||
const char abiPropertyKey[] = "SystemImage.Abi";
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
/*!
|
||||
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;
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the \c sdkmanger tool specific to configuration \a config with arguments \a args. Returns
|
||||
\c true if the command is successfully executed. Output is copied into \a output. The function
|
||||
blocks the calling thread.
|
||||
*/
|
||||
static bool sdkManagerCommand(const AndroidConfig config, const QStringList &args, QString *output)
|
||||
{
|
||||
QString sdkManagerToolPath = config.sdkManagerToolPath().toString();
|
||||
SynchronousProcess proc;
|
||||
SynchronousProcessResponse response = proc.runBlocking(sdkManagerToolPath, args);
|
||||
if (response.result == SynchronousProcessResponse::Finished) {
|
||||
if (output)
|
||||
*output = response.allOutput();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\class SdkManagerOutputParser
|
||||
\brief The SdkManagerOutputParser class is a helper class to parse the output of the \c sdkmanager
|
||||
commands.
|
||||
*/
|
||||
class SdkManagerOutputParser
|
||||
{
|
||||
public:
|
||||
enum MarkerTag
|
||||
{
|
||||
None = 0x01,
|
||||
InstalledPackagesMarker = 0x02,
|
||||
AvailablePackagesMarkers = 0x04,
|
||||
AvailableUpdatesMarker = 0x08,
|
||||
EmptyMarker = 0x10,
|
||||
PlatformMarker = 0x20,
|
||||
SystemImageMarker = 0x40,
|
||||
SectionMarkers = InstalledPackagesMarker | AvailablePackagesMarkers | AvailableUpdatesMarker
|
||||
};
|
||||
|
||||
void parsePackageListing(const QString &output);
|
||||
|
||||
SdkPlatformList m_installedPlatforms;
|
||||
|
||||
private:
|
||||
void compileData();
|
||||
void parsePackageData(MarkerTag packageMarker, const QStringList &data);
|
||||
bool parsePlatform(const QStringList &data, SdkPlatform *platform) const;
|
||||
bool parseSystemImage(const QStringList &data, SystemImage *image);
|
||||
MarkerTag parseMarkers(const QString &line);
|
||||
|
||||
MarkerTag m_currentSection = MarkerTag::None;
|
||||
SystemImageList m_installedSystemImages;
|
||||
};
|
||||
|
||||
const std::map<SdkManagerOutputParser::MarkerTag, const char *> markerTags {
|
||||
{SdkManagerOutputParser::MarkerTag::InstalledPackagesMarker, "Installed packages:"},
|
||||
{SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Packages:"},
|
||||
{SdkManagerOutputParser::MarkerTag::AvailablePackagesMarkers, "Available Updates:"},
|
||||
{SdkManagerOutputParser::MarkerTag::PlatformMarker, "platforms"},
|
||||
{SdkManagerOutputParser::MarkerTag::SystemImageMarker, "system-images"}
|
||||
};
|
||||
|
||||
AndroidSdkManager::AndroidSdkManager(const AndroidConfig &config):
|
||||
m_config(config),
|
||||
m_parser(new SdkManagerOutputParser)
|
||||
{
|
||||
QString packageListing;
|
||||
if (sdkManagerCommand(config, QStringList({"--list", "--verbose"}), &packageListing)) {
|
||||
m_parser->parsePackageListing(packageListing);
|
||||
}
|
||||
}
|
||||
|
||||
AndroidSdkManager::~AndroidSdkManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SdkPlatformList AndroidSdkManager::availableSdkPlatforms()
|
||||
{
|
||||
if (m_config.sdkToolsVersion() < sdkManagerIntroVersion) {
|
||||
AndroidToolManager toolManager(m_config);
|
||||
return toolManager.availableSdkPlatforms();
|
||||
}
|
||||
|
||||
return m_parser->m_installedPlatforms;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
foreach (QString outputLine, output.split('\n')) {
|
||||
MarkerTag marker = parseMarkers(outputLine);
|
||||
|
||||
if (marker & SectionMarkers) {
|
||||
// Section marker found. Update the current section being parsed.
|
||||
m_currentSection = marker;
|
||||
processCurrentPackage();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_currentSection == None
|
||||
|| m_currentSection == AvailablePackagesMarkers // At this point. Not interested in
|
||||
|| m_currentSection == AvailableUpdatesMarker) { // available or update packages.
|
||||
// Let go of verbose output utill a valid section starts.
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
compileData();
|
||||
Utils::sort(m_installedPlatforms);
|
||||
}
|
||||
|
||||
void SdkManagerOutputParser::compileData()
|
||||
{
|
||||
// Associate the system images with sdk platforms.
|
||||
for (auto &image : m_installedSystemImages) {
|
||||
auto findPlatfom = [image](const SdkPlatform &platform) {
|
||||
return platform.apiLevel == image.apiLevel;
|
||||
};
|
||||
auto itr = std::find_if(m_installedPlatforms.begin(), m_installedPlatforms.end(), findPlatfom);
|
||||
if (itr != m_installedPlatforms.end())
|
||||
itr->systemImages.append(image);
|
||||
}
|
||||
}
|
||||
|
||||
void SdkManagerOutputParser::parsePackageData(MarkerTag packageMarker, const QStringList &data)
|
||||
{
|
||||
QTC_ASSERT(!data.isEmpty() && packageMarker != None, return);
|
||||
|
||||
if (m_currentSection != MarkerTag::InstalledPackagesMarker)
|
||||
return; // For now, only interested in installed packages.
|
||||
|
||||
switch (packageMarker) {
|
||||
case MarkerTag::PlatformMarker:
|
||||
{
|
||||
SdkPlatform platform;
|
||||
if (parsePlatform(data, &platform))
|
||||
m_installedPlatforms.append(platform);
|
||||
else
|
||||
qCDebug(sdkManagerLog) << "Platform: Parsing failed: " << data;
|
||||
}
|
||||
break;
|
||||
|
||||
case MarkerTag::SystemImageMarker:
|
||||
{
|
||||
SystemImage image;
|
||||
if (parseSystemImage(data, &image))
|
||||
m_installedSystemImages.append(image);
|
||||
else
|
||||
qCDebug(sdkManagerLog) << "System Image: Parsing failed: " << data;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
qCDebug(sdkManagerLog) << "Unhandled package: " << markerTags.at(packageMarker);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool SdkManagerOutputParser::parsePlatform(const QStringList &data, SdkPlatform *platform) const
|
||||
{
|
||||
QTC_ASSERT(platform && !data.isEmpty(), return false);
|
||||
|
||||
QStringList parts = data.at(0).split(';');
|
||||
if (parts.count() < 2) {
|
||||
qCDebug(sdkManagerLog) << "Platform: Unexpected header: "<< data;
|
||||
return false;
|
||||
}
|
||||
platform->name = parts[1];
|
||||
platform->package = data.at(0);
|
||||
|
||||
foreach (QString line, data) {
|
||||
QString value;
|
||||
if (valueForKey(installLocationKey, line, &value))
|
||||
platform->installedLocation = Utils::FileName::fromString(value);
|
||||
}
|
||||
|
||||
int apiLevel = AndroidManager::findApiLevel(platform->installedLocation);
|
||||
if (apiLevel != -1)
|
||||
platform->apiLevel = apiLevel;
|
||||
else
|
||||
qCDebug(sdkManagerLog) << "Platform: Can not parse api level: "<< data;
|
||||
|
||||
return apiLevel != -1;
|
||||
}
|
||||
|
||||
bool SdkManagerOutputParser::parseSystemImage(const QStringList &data, SystemImage *image)
|
||||
{
|
||||
QTC_ASSERT(image && !data.isEmpty(), return false);
|
||||
|
||||
QStringList parts = data.at(0).split(';');
|
||||
QTC_ASSERT(!data.isEmpty() && parts.count() >= 4,
|
||||
qCDebug(sdkManagerLog) << "System Image: Unexpected header: " << data);
|
||||
|
||||
image->package = data.at(0);
|
||||
foreach (QString line, data) {
|
||||
QString value;
|
||||
if (valueForKey(installLocationKey, line, &value))
|
||||
image->installedLocation = Utils::FileName::fromString(value);
|
||||
}
|
||||
|
||||
Utils::FileName propertiesPath = image->installedLocation;
|
||||
propertiesPath.appendPath("/source.properties");
|
||||
if (propertiesPath.exists()) {
|
||||
// Installed System Image.
|
||||
QSettings imageProperties(propertiesPath.toString(), QSettings::IniFormat);
|
||||
bool validApiLevel = false;
|
||||
image->apiLevel = imageProperties.value(apiLevelPropertyKey).toInt(&validApiLevel);
|
||||
if (!validApiLevel) {
|
||||
qCDebug(sdkManagerLog) << "System Image: Can not parse api level: "<< data;
|
||||
return false;
|
||||
}
|
||||
image->abiName = imageProperties.value(abiPropertyKey).toString();
|
||||
} else if (parts.count() >= 4){
|
||||
image->apiLevel = parts[1].section('-', 1, 1).toInt();
|
||||
image->abiName = parts[3];
|
||||
} else {
|
||||
qCDebug(sdkManagerLog) << "System Image: Can not parse: "<< data;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
51
src/plugins/android/androidsdkmanager.h
Normal file
51
src/plugins/android/androidsdkmanager.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "utils/fileutils.h"
|
||||
#include "androidconfigurations.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
class SdkManagerOutputParser;
|
||||
|
||||
class AndroidSdkManager
|
||||
{
|
||||
public:
|
||||
AndroidSdkManager(const AndroidConfig &config);
|
||||
~AndroidSdkManager();
|
||||
|
||||
SdkPlatformList availableSdkPlatforms();
|
||||
|
||||
private:
|
||||
const AndroidConfig &m_config;
|
||||
std::unique_ptr<SdkManagerOutputParser> m_parser;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "androidconfigurations.h"
|
||||
#include "androidconstants.h"
|
||||
#include "androidtoolchain.h"
|
||||
#include "androidavdmanager.h"
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
@@ -58,7 +59,7 @@
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
void AvdModel::setAvdList(const QVector<AndroidDeviceInfo> &list)
|
||||
void AvdModel::setAvdList(const AndroidDeviceInfoList &list)
|
||||
{
|
||||
beginResetModel();
|
||||
m_list = list;
|
||||
@@ -128,10 +129,13 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent)
|
||||
m_ndkState(NotSet),
|
||||
m_javaState(NotSet),
|
||||
m_ui(new Ui_AndroidSettingsWidget),
|
||||
m_androidConfig(AndroidConfigurations::currentConfig())
|
||||
m_androidConfig(AndroidConfigurations::currentConfig()),
|
||||
m_avdManager(new AndroidAvdManager(m_androidConfig))
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->deprecatedInfoIconLabel->setPixmap(Utils::Icons::INFO.pixmap());
|
||||
|
||||
connect(&m_checkGdbWatcher, &QFutureWatcherBase::finished,
|
||||
this, &AndroidSettingsWidget::checkGdbFinished);
|
||||
|
||||
@@ -158,7 +162,8 @@ AndroidSettingsWidget::AndroidSettingsWidget(QWidget *parent)
|
||||
m_ui->AntLocationPathChooser->setPromptDialogTitle(tr("Select ant Script"));
|
||||
m_ui->AntLocationPathChooser->setInitialBrowsePathBackup(dir);
|
||||
m_ui->AntLocationPathChooser->setPromptDialogFilter(filter);
|
||||
m_ui->UseGradleCheckBox->setChecked(m_androidConfig.useGrandle());
|
||||
|
||||
updateGradleBuildUi();
|
||||
|
||||
m_ui->OpenJDKLocationPathChooser->setFileName(m_androidConfig.openJDKLocation());
|
||||
m_ui->OpenJDKLocationPathChooser->setPromptDialogTitle(tr("Select JDK Path"));
|
||||
@@ -463,7 +468,7 @@ void AndroidSettingsWidget::enableAvdControls()
|
||||
void AndroidSettingsWidget::startUpdateAvd()
|
||||
{
|
||||
disableAvdControls();
|
||||
m_virtualDevicesWatcher.setFuture(m_androidConfig.androidVirtualDevicesFuture());
|
||||
m_virtualDevicesWatcher.setFuture(m_avdManager->avdList());
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::updateAvds()
|
||||
@@ -476,6 +481,13 @@ void AndroidSettingsWidget::updateAvds()
|
||||
enableAvdControls();
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::updateGradleBuildUi()
|
||||
{
|
||||
m_ui->UseGradleCheckBox->setEnabled(m_androidConfig.antScriptsAvailable());
|
||||
m_ui->UseGradleCheckBox->setChecked(!m_androidConfig.antScriptsAvailable() ||
|
||||
m_androidConfig.useGrandle());
|
||||
}
|
||||
|
||||
bool AndroidSettingsWidget::sdkLocationIsValid() const
|
||||
{
|
||||
Utils::FileName androidExe = m_androidConfig.sdkLocation();
|
||||
@@ -505,6 +517,7 @@ void AndroidSettingsWidget::saveSettings()
|
||||
void AndroidSettingsWidget::sdkLocationEditingFinished()
|
||||
{
|
||||
m_androidConfig.setSdkLocation(Utils::FileName::fromUserInput(m_ui->SDKLocationPathChooser->rawPath()));
|
||||
updateGradleBuildUi();
|
||||
|
||||
check(Sdk);
|
||||
|
||||
@@ -587,12 +600,12 @@ void AndroidSettingsWidget::addAVD()
|
||||
disableAvdControls();
|
||||
AndroidConfig::CreateAvdInfo info = m_androidConfig.gatherCreateAVDInfo(this);
|
||||
|
||||
if (info.target.isEmpty()) {
|
||||
if (!info.target.isValid()) {
|
||||
enableAvdControls();
|
||||
return;
|
||||
}
|
||||
|
||||
m_futureWatcher.setFuture(m_androidConfig.createAVD(info));
|
||||
m_futureWatcher.setFuture(m_avdManager->createAvd(info));
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::avdAdded()
|
||||
@@ -620,13 +633,13 @@ void AndroidSettingsWidget::removeAVD()
|
||||
return;
|
||||
}
|
||||
|
||||
m_androidConfig.removeAVD(avdName);
|
||||
m_avdManager->removeAvd(avdName);
|
||||
startUpdateAvd();
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::startAVD()
|
||||
{
|
||||
m_androidConfig.startAVDAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex()));
|
||||
m_avdManager->startAvdAsync(m_AVDModel.avdName(m_ui->AVDTableView->currentIndex()));
|
||||
}
|
||||
|
||||
void AndroidSettingsWidget::avdActivated(const QModelIndex &index)
|
||||
@@ -671,16 +684,15 @@ void AndroidSettingsWidget::showGdbWarningDialog()
|
||||
|
||||
void AndroidSettingsWidget::manageAVD()
|
||||
{
|
||||
QProcess *avdProcess = new QProcess();
|
||||
connect(this, &QObject::destroyed, avdProcess, &QObject::deleteLater);
|
||||
connect(avdProcess, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
|
||||
avdProcess, &QObject::deleteLater);
|
||||
|
||||
avdProcess->setProcessEnvironment(m_androidConfig.androidToolEnvironment().toProcessEnvironment());
|
||||
QString executable = m_androidConfig.androidToolPath().toString();
|
||||
QStringList arguments = QStringList("avd");
|
||||
|
||||
avdProcess->start(executable, arguments);
|
||||
if (m_avdManager->avdManagerUiToolAvailable()) {
|
||||
m_avdManager->launchAvdManagerUiTool();
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("AVD Manager Not Available"),
|
||||
tr("AVD manager UI tool is not available in the installed SDK tools"
|
||||
"(version %1). Use the command line tool \"avdmanager\" for "
|
||||
"advanced AVD management.")
|
||||
.arg(m_androidConfig.sdkToolsVersion().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include <QAbstractTableModel>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include <memory>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class Ui_AndroidSettingsWidget;
|
||||
QT_END_NAMESPACE
|
||||
@@ -40,11 +42,13 @@ QT_END_NAMESPACE
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
class AndroidAvdManager;
|
||||
|
||||
class AvdModel: public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void setAvdList(const QVector<AndroidDeviceInfo> &list);
|
||||
void setAvdList(const AndroidDeviceInfoList &list);
|
||||
QString avdName(const QModelIndex &index) const;
|
||||
QModelIndex indexForAvdName(const QString &avdName) const;
|
||||
|
||||
@@ -55,7 +59,7 @@ protected:
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
|
||||
private:
|
||||
QVector<AndroidDeviceInfo> m_list;
|
||||
AndroidDeviceInfoList m_list;
|
||||
};
|
||||
|
||||
class AndroidSettingsWidget : public QWidget
|
||||
@@ -91,6 +95,7 @@ private:
|
||||
void checkGdbFinished();
|
||||
void showGdbWarningDialog();
|
||||
void updateAvds();
|
||||
void updateGradleBuildUi();
|
||||
|
||||
private:
|
||||
enum Mode { Sdk = 1, Ndk = 2, Java = 4, All = Sdk | Ndk | Java };
|
||||
@@ -117,8 +122,9 @@ private:
|
||||
QFutureWatcher<QPair<QStringList, bool>> m_checkGdbWatcher;
|
||||
QStringList m_gdbCheckPaths;
|
||||
|
||||
QFutureWatcher<QVector<AndroidDeviceInfo>> m_virtualDevicesWatcher;
|
||||
QFutureWatcher<AndroidDeviceInfoList> m_virtualDevicesWatcher;
|
||||
QString m_lastAddedAvd;
|
||||
std::unique_ptr<AndroidAvdManager> m_avdManager;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -237,20 +237,30 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="CreateKitCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatically create kits for Android tool chains</string>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="CreateKitCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Automatically create kits for Android tool chains</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
@@ -289,17 +299,59 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QCheckBox" name="UseGradleCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Gradle instead of Ant</string>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="UseGradleCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Gradle instead of Ant (Ant builds are deprecated)</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="deprecatedInfoIconLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Gradle builds are forced from Android SDK tools version 25.3.0 onwards as Ant scripts are no longer available.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="AntLocationLabel">
|
||||
|
||||
346
src/plugins/android/androidtoolmanager.cpp
Normal file
346
src/plugins/android/androidtoolmanager.cpp
Normal file
@@ -0,0 +1,346 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "androidtoolmanager.h"
|
||||
|
||||
#include "androidmanager.h"
|
||||
|
||||
#include "utils/algorithm.h"
|
||||
#include "utils/environment.h"
|
||||
#include "utils/qtcassert.h"
|
||||
#include "utils/runextensions.h"
|
||||
#include "utils/synchronousprocess.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(androidToolLog, "qtc.android.sdkManager")
|
||||
}
|
||||
|
||||
namespace Android {
|
||||
namespace Internal {
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
class AndroidToolOutputParser
|
||||
{
|
||||
public:
|
||||
void parseTargetListing(const QString &output, const FileName &sdkLocation,
|
||||
SdkPlatformList *platformList);
|
||||
|
||||
QList<SdkPlatform> m_installedPlatforms;
|
||||
};
|
||||
|
||||
/*!
|
||||
Runs the \c android tool located at \a toolPath with arguments \a args and environment \a
|
||||
environment. Returns \c true for successful execution. Command's output is copied to \a
|
||||
output.
|
||||
*/
|
||||
static bool androidToolCommand(Utils::FileName toolPath, const QStringList &args,
|
||||
const Environment &environment, QString *output)
|
||||
{
|
||||
QString androidToolPath = toolPath.toString();
|
||||
SynchronousProcess proc;
|
||||
proc.setProcessEnvironment(environment.toProcessEnvironment());
|
||||
SynchronousProcessResponse response = proc.runBlocking(androidToolPath, args);
|
||||
if (response.result == SynchronousProcessResponse::Finished) {
|
||||
if (output)
|
||||
*output = response.allOutput();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static QStringList cleanAndroidABIs(const QStringList &abis)
|
||||
{
|
||||
QStringList res;
|
||||
foreach (const QString &abi, abis) {
|
||||
int index = abi.lastIndexOf(QLatin1Char('/'));
|
||||
if (index == -1)
|
||||
res << abi;
|
||||
else
|
||||
res << abi.mid(index + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
AndroidToolManager::AndroidToolManager(const AndroidConfig &config) :
|
||||
m_config(config),
|
||||
m_parser(new AndroidToolOutputParser)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
AndroidToolManager::~AndroidToolManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SdkPlatformList AndroidToolManager::availableSdkPlatforms() const
|
||||
{
|
||||
SdkPlatformList list;
|
||||
QString targetListing;
|
||||
if (androidToolCommand(m_config.androidToolPath(), QStringList({"list", "target"}),
|
||||
androidToolEnvironment(), &targetListing)) {
|
||||
m_parser->parseTargetListing(targetListing, m_config.sdkLocation(), &list);
|
||||
} else {
|
||||
qCDebug(androidToolLog) << "Android tool target listing failed";
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void AndroidToolManager::launchAvdManager() const
|
||||
{
|
||||
QProcess::startDetached(m_config.androidToolPath().toString(), QStringList("avd"));
|
||||
}
|
||||
|
||||
QFuture<AndroidConfig::CreateAvdInfo>
|
||||
AndroidToolManager::createAvd(AndroidConfig::CreateAvdInfo info) const
|
||||
{
|
||||
return Utils::runAsync(&AndroidToolManager::createAvdImpl, info,
|
||||
m_config.androidToolPath(), androidToolEnvironment());
|
||||
}
|
||||
|
||||
bool AndroidToolManager::removeAvd(const QString &name) const
|
||||
{
|
||||
SynchronousProcess proc;
|
||||
proc.setTimeoutS(5);
|
||||
proc.setProcessEnvironment(androidToolEnvironment().toProcessEnvironment());
|
||||
SynchronousProcessResponse response
|
||||
= proc.runBlocking(m_config.androidToolPath().toString(),
|
||||
QStringList({"delete", "avd", "-n", name}));
|
||||
return response.result == SynchronousProcessResponse::Finished && response.exitCode == 0;
|
||||
}
|
||||
|
||||
QFuture<AndroidDeviceInfoList> AndroidToolManager::androidVirtualDevicesFuture() const
|
||||
{
|
||||
return Utils::runAsync(&AndroidToolManager::androidVirtualDevices,
|
||||
m_config.androidToolPath(), m_config.sdkLocation(),
|
||||
androidToolEnvironment());
|
||||
}
|
||||
|
||||
Environment AndroidToolManager::androidToolEnvironment() const
|
||||
{
|
||||
Environment env = Environment::systemEnvironment();
|
||||
Utils::FileName jdkLocation = m_config.openJDKLocation();
|
||||
if (!jdkLocation.isEmpty()) {
|
||||
env.set(QLatin1String("JAVA_HOME"), jdkLocation.toUserOutput());
|
||||
Utils::FileName binPath = jdkLocation;
|
||||
binPath.appendPath(QLatin1String("bin"));
|
||||
env.prependOrSetPath(binPath.toUserOutput());
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
AndroidConfig::CreateAvdInfo AndroidToolManager::createAvdImpl(AndroidConfig::CreateAvdInfo info,
|
||||
FileName androidToolPath,
|
||||
Environment env)
|
||||
{
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env.toProcessEnvironment());
|
||||
QStringList arguments;
|
||||
arguments << QLatin1String("create") << QLatin1String("avd")
|
||||
<< QLatin1String("-t") << info.target.name
|
||||
<< QLatin1String("-n") << info.name
|
||||
<< QLatin1String("-b") << info.abi;
|
||||
if (info.sdcardSize > 0)
|
||||
arguments << QLatin1String("-c") << QString::fromLatin1("%1M").arg(info.sdcardSize);
|
||||
proc.start(androidToolPath.toString(), arguments);
|
||||
if (!proc.waitForStarted()) {
|
||||
info.error = tr("Could not start process \"%1 %2\"")
|
||||
.arg(androidToolPath.toString(), arguments.join(QLatin1Char(' ')));
|
||||
return info;
|
||||
}
|
||||
QTC_CHECK(proc.state() == QProcess::Running);
|
||||
proc.write(QByteArray("yes\n")); // yes to "Do you wish to create a custom hardware profile"
|
||||
|
||||
QByteArray question;
|
||||
while (true) {
|
||||
proc.waitForReadyRead(500);
|
||||
question += proc.readAllStandardOutput();
|
||||
if (question.endsWith(QByteArray("]:"))) {
|
||||
// truncate to last line
|
||||
int index = question.lastIndexOf(QByteArray("\n"));
|
||||
if (index != -1)
|
||||
question = question.mid(index);
|
||||
if (question.contains("hw.gpu.enabled"))
|
||||
proc.write(QByteArray("yes\n"));
|
||||
else
|
||||
proc.write(QByteArray("\n"));
|
||||
question.clear();
|
||||
}
|
||||
|
||||
if (proc.state() != QProcess::Running)
|
||||
break;
|
||||
}
|
||||
QTC_CHECK(proc.state() == QProcess::NotRunning);
|
||||
|
||||
QString errorOutput = QString::fromLocal8Bit(proc.readAllStandardError());
|
||||
// The exit code is always 0, so we need to check stderr
|
||||
// For now assume that any output at all indicates a error
|
||||
if (!errorOutput.isEmpty()) {
|
||||
info.error = errorOutput;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
AndroidDeviceInfoList AndroidToolManager::androidVirtualDevices(const Utils::FileName &androidTool,
|
||||
const FileName &sdkLocationPath,
|
||||
const Environment &environment)
|
||||
{
|
||||
AndroidDeviceInfoList devices;
|
||||
QString output;
|
||||
if (!androidToolCommand(androidTool, QStringList({"list", "avd"}), environment, &output))
|
||||
return devices;
|
||||
|
||||
QStringList avds = output.split('\n');
|
||||
if (avds.empty())
|
||||
return devices;
|
||||
|
||||
while (avds.first().startsWith(QLatin1String("* daemon")))
|
||||
avds.removeFirst(); // remove the daemon logs
|
||||
avds.removeFirst(); // remove "List of devices attached" header line
|
||||
|
||||
bool nextLineIsTargetLine = false;
|
||||
|
||||
AndroidDeviceInfo dev;
|
||||
for (int i = 0; i < avds.size(); i++) {
|
||||
QString line = avds.at(i);
|
||||
if (!line.contains(QLatin1String("Name:")))
|
||||
continue;
|
||||
|
||||
int index = line.indexOf(QLatin1Char(':')) + 2;
|
||||
if (index >= line.size())
|
||||
break;
|
||||
dev.avdname = line.mid(index).trimmed();
|
||||
dev.sdk = -1;
|
||||
dev.cpuAbi.clear();
|
||||
++i;
|
||||
for (; i < avds.size(); ++i) {
|
||||
line = avds.at(i);
|
||||
if (line.contains(QLatin1String("---------")))
|
||||
break;
|
||||
|
||||
if (line.contains(QLatin1String("Target:")) || nextLineIsTargetLine) {
|
||||
if (line.contains(QLatin1String("Google APIs"))) {
|
||||
nextLineIsTargetLine = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
nextLineIsTargetLine = false;
|
||||
|
||||
int lastIndex = line.lastIndexOf(QLatin1Char(' '));
|
||||
if (lastIndex == -1) // skip line
|
||||
break;
|
||||
QString tmp = line.mid(lastIndex).remove(QLatin1Char(')')).trimmed();
|
||||
Utils::FileName platformPath = sdkLocationPath;
|
||||
platformPath.appendPath(QString("/platforms/android-%1").arg(tmp));
|
||||
dev.sdk = AndroidManager::findApiLevel(platformPath);
|
||||
}
|
||||
if (line.contains(QLatin1String("Tag/ABI:"))) {
|
||||
int lastIndex = line.lastIndexOf(QLatin1Char('/')) + 1;
|
||||
if (lastIndex >= line.size())
|
||||
break;
|
||||
dev.cpuAbi = QStringList(line.mid(lastIndex));
|
||||
} else if (line.contains(QLatin1String("ABI:"))) {
|
||||
int lastIndex = line.lastIndexOf(QLatin1Char(' ')) + 1;
|
||||
if (lastIndex >= line.size())
|
||||
break;
|
||||
dev.cpuAbi = QStringList(line.mid(lastIndex).trimmed());
|
||||
}
|
||||
}
|
||||
// armeabi-v7a devices can also run armeabi code
|
||||
if (dev.cpuAbi == QStringList("armeabi-v7a"))
|
||||
dev.cpuAbi << QLatin1String("armeabi");
|
||||
dev.state = AndroidDeviceInfo::OkState;
|
||||
dev.type = AndroidDeviceInfo::Emulator;
|
||||
if (dev.cpuAbi.isEmpty() || dev.sdk == -1)
|
||||
continue;
|
||||
devices.push_back(dev);
|
||||
}
|
||||
Utils::sort(devices);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
void AndroidToolOutputParser::parseTargetListing(const QString &output,
|
||||
const Utils::FileName &sdkLocation,
|
||||
SdkPlatformList *platformList)
|
||||
{
|
||||
if (!platformList)
|
||||
return;
|
||||
|
||||
auto addSystemImage = [](const QStringList& abiList, SdkPlatform &platform) {
|
||||
foreach (auto imageAbi, abiList) {
|
||||
SystemImage image;
|
||||
image.abiName = imageAbi;
|
||||
image.apiLevel = platform.apiLevel;
|
||||
platform.systemImages.append(image);
|
||||
}
|
||||
};
|
||||
|
||||
SdkPlatform platform;
|
||||
QStringList abiList;
|
||||
foreach (const QString &l, output.split('\n')) {
|
||||
const QString line = l.trimmed();
|
||||
if (line.startsWith(QLatin1String("id:")) && line.contains(QLatin1String("android-"))) {
|
||||
int index = line.indexOf(QLatin1String("\"android-"));
|
||||
if (index == -1)
|
||||
continue;
|
||||
QString androidTarget = line.mid(index + 1, line.length() - index - 2);
|
||||
const QString tmp = androidTarget.mid(androidTarget.lastIndexOf(QLatin1Char('-')) + 1);
|
||||
Utils::FileName platformPath = sdkLocation;
|
||||
platformPath.appendPath(QString("/platforms/android-%1").arg(tmp));
|
||||
platform.installedLocation = platformPath;
|
||||
platform.apiLevel = AndroidManager::findApiLevel(platformPath);
|
||||
} else if (line.startsWith(QLatin1String("Name:"))) {
|
||||
platform.name = line.mid(6);
|
||||
} else if (line.startsWith(QLatin1String("Tag/ABIs :"))) {
|
||||
abiList = cleanAndroidABIs(line.mid(10).trimmed().split(QLatin1String(", ")));
|
||||
} else if (line.startsWith(QLatin1String("ABIs"))) {
|
||||
abiList = cleanAndroidABIs(line.mid(6).trimmed().split(QLatin1String(", ")));
|
||||
} else if (line.startsWith(QLatin1String("---")) || line.startsWith(QLatin1String("==="))) {
|
||||
if (platform.apiLevel == -1)
|
||||
continue;
|
||||
|
||||
addSystemImage(abiList, platform);
|
||||
*platformList << platform;
|
||||
|
||||
platform = SdkPlatform();
|
||||
abiList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// The last parsed Platform.
|
||||
if (platform.apiLevel != -1) {
|
||||
addSystemImage(abiList, platform);
|
||||
*platformList << platform;
|
||||
}
|
||||
|
||||
Utils::sort(*platformList);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
72
src/plugins/android/androidtoolmanager.h
Normal file
72
src/plugins/android/androidtoolmanager.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 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 "utils/fileutils.h"
|
||||
#include "androidconfigurations.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Android {
|
||||
class AndroidConfig;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
class AndroidToolOutputParser;
|
||||
/*!
|
||||
Wraps the \c android tool's usage. The tool itself is deprecated since SDK tools version 25.3.0.
|
||||
*/
|
||||
class AndroidToolManager
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(AndroidToolManager)
|
||||
|
||||
public:
|
||||
AndroidToolManager(const AndroidConfig &config);
|
||||
~AndroidToolManager();
|
||||
|
||||
SdkPlatformList availableSdkPlatforms() const;
|
||||
void launchAvdManager() const;
|
||||
|
||||
QFuture<AndroidConfig::CreateAvdInfo> createAvd(AndroidConfig::CreateAvdInfo info) const;
|
||||
bool removeAvd(const QString &name) const;
|
||||
QFuture<AndroidDeviceInfoList> androidVirtualDevicesFuture() const;
|
||||
|
||||
// Helper methods
|
||||
private:
|
||||
Utils::Environment androidToolEnvironment() const;
|
||||
static AndroidConfig::CreateAvdInfo createAvdImpl(AndroidConfig::CreateAvdInfo info,
|
||||
Utils::FileName androidToolPath, Utils::Environment env);
|
||||
static AndroidDeviceInfoList androidVirtualDevices(const Utils::FileName &androidTool,
|
||||
const Utils::FileName &sdkLlocationPath,
|
||||
const Utils::Environment &environment);
|
||||
private:
|
||||
const AndroidConfig &m_config;
|
||||
std::unique_ptr<AndroidToolOutputParser> m_parser;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Android
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "avddialog.h"
|
||||
#include "androidconfigurations.h"
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/tooltip/tooltip.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
@@ -67,12 +68,12 @@ AvdDialog::AvdDialog(int minApiLevel, const QString &targetArch, const AndroidCo
|
||||
|
||||
bool AvdDialog::isValid() const
|
||||
{
|
||||
return !name().isEmpty() && !target().isEmpty() && !abi().isEmpty();
|
||||
return !name().isEmpty() && target().isValid() && !abi().isEmpty();
|
||||
}
|
||||
|
||||
QString AvdDialog::target() const
|
||||
SdkPlatform AvdDialog::target() const
|
||||
{
|
||||
return m_avdDialog.targetComboBox->currentText();
|
||||
return m_avdDialog.targetComboBox->currentData().value<SdkPlatform>();
|
||||
}
|
||||
|
||||
QString AvdDialog::name() const
|
||||
@@ -92,15 +93,23 @@ int AvdDialog::sdcardSize() const
|
||||
|
||||
void AvdDialog::updateApiLevelComboBox()
|
||||
{
|
||||
QList<SdkPlatform> filteredList;
|
||||
QList<SdkPlatform> platforms = m_config->sdkTargets(m_minApiLevel);
|
||||
foreach (const SdkPlatform &platform, platforms) {
|
||||
if (platform.abis.contains(abi()))
|
||||
filteredList << platform;
|
||||
}
|
||||
SdkPlatformList filteredList;
|
||||
SdkPlatformList platforms = m_config->sdkTargets(m_minApiLevel);
|
||||
|
||||
QString selectedAbi = abi();
|
||||
auto hasAbi = [selectedAbi](const SystemImage &image) {
|
||||
return image.isValid() && (image.abiName == selectedAbi);
|
||||
};
|
||||
|
||||
filteredList = Utils::filtered(platforms, [hasAbi](const SdkPlatform &platform) {
|
||||
return Utils::anyOf(platform.systemImages,hasAbi);
|
||||
});
|
||||
|
||||
m_avdDialog.targetComboBox->clear();
|
||||
m_avdDialog.targetComboBox->addItems(AndroidConfig::apiLevelNamesFor(filteredList));
|
||||
foreach (const SdkPlatform &platform, filteredList) {
|
||||
m_avdDialog.targetComboBox->addItem(AndroidConfig::apiLevelNameFor(platform),
|
||||
QVariant::fromValue<SdkPlatform>(platform));
|
||||
}
|
||||
|
||||
if (platforms.isEmpty()) {
|
||||
m_avdDialog.warningIcon->setVisible(true);
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
namespace Android {
|
||||
class AndroidConfig;
|
||||
class SdkPlatform;
|
||||
|
||||
namespace Internal {
|
||||
|
||||
@@ -42,7 +43,7 @@ public:
|
||||
explicit AvdDialog(int minApiLevel, const QString &targetArch,
|
||||
const AndroidConfig *config, QWidget *parent = 0);
|
||||
|
||||
QString target() const;
|
||||
Android::SdkPlatform target() const;
|
||||
QString name() const;
|
||||
QString abi() const;
|
||||
int sdcardSize() const;
|
||||
|
||||
Reference in New Issue
Block a user