Android: Fix debugging on Android 8+

The new way is much reliable and now we can debug all the libs from very
first start, including static constructors, JNI_OnLoad, etc.

The downside is that the startup is a little bit slower then before.
On a Ryzen 1700X is 2 to 5 seconds slower.

Task-number: QTCREATORBUG-19081
Change-Id: Iacedf7b8aa84de5026f9c81eeca35dd377cf4640
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
Reviewed-by: Vikas Pachdha <vikas.pachdha@qt.io>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
BogDan Vatra
2018-03-17 09:31:56 +02:00
parent 6a62717271
commit 429c596395
11 changed files with 731 additions and 583 deletions

View File

@@ -18,6 +18,7 @@ HEADERS += \
androiderrormessage.h \
androidglobal.h \
androidrunner.h \
androidrunnerworker.h \
androiddebugsupport.h \
androidqtversionfactory.h \
androidqtversion.h \
@@ -66,6 +67,7 @@ SOURCES += \
androidtoolchain.cpp \
androiderrormessage.cpp \
androidrunner.cpp \
androidrunnerworker.cpp \
androiddebugsupport.cpp \
androidqtversionfactory.cpp \
androidqtversion.cpp \

View File

@@ -93,6 +93,8 @@ Project {
"androidrunnable.h",
"androidrunner.cpp",
"androidrunner.h",
"androidrunnerworker.cpp",
"androidrunnerworker.h",
"androidsdkmanager.cpp",
"androidsdkmanager.h",
"androidsdkmanagerwidget.cpp",

View File

@@ -166,6 +166,7 @@ bool AndroidDeployQtStep::init(QList<const BuildStep *> &earlierSteps)
m_filesToPull["/system/" + libDirName + "/libc.so"] = buildDir + "libc.so";
AndroidManager::setDeviceSerialNumber(target(), m_serialNumber);
AndroidManager::setDeviceApiLevel(target(), info.sdk);
QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(target()->kit());

View File

@@ -240,6 +240,16 @@ void AndroidManager::setDeviceSerialNumber(ProjectExplorer::Target *target, cons
target->setNamedSettings(AndroidDeviceSn, deviceSerialNumber);
}
int AndroidManager::deviceApiLevel(ProjectExplorer::Target *target)
{
return target->namedSettings(ApiLevelKey).toInt();
}
void AndroidManager::setDeviceApiLevel(ProjectExplorer::Target *target, int level)
{
target->setNamedSettings(ApiLevelKey, level);
}
QPair<int, int> AndroidManager::apiLevelRange()
{
return qMakePair(9, 26);

View File

@@ -56,6 +56,9 @@ public:
static QString deviceSerialNumber(ProjectExplorer::Target *target);
static void setDeviceSerialNumber(ProjectExplorer::Target *target, const QString &deviceSerialNumber);
static int deviceApiLevel(ProjectExplorer::Target *target);
static void setDeviceApiLevel(ProjectExplorer::Target *target, int level);
static QString buildTargetSDK(ProjectExplorer::Target *target);
static bool signPackage(ProjectExplorer::Target *target);

View File

@@ -40,6 +40,7 @@ struct ANDROID_EXPORT AndroidRunnable
QStringList beforeStartAdbCommands;
QStringList afterFinishAdbCommands;
QString deviceSerialNumber;
int apiLevel = -1;
QString displayName() const { return packageName; }
static void *staticTypeId;

View File

@@ -28,35 +28,17 @@
#include "androiddeployqtstep.h"
#include "androidconfigurations.h"
#include "androidglobal.h"
#include "androidrunconfiguration.h"
#include "androidmanager.h"
#include "androidavdmanager.h"
#include "androidrunnerworker.h"
#include <debugger/debuggerrunconfigurationaspect.h>
#include <coreplugin/messagemanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorersettings.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include <utils/synchronousprocess.h>
#include <utils/temporaryfile.h>
#include <utils/url.h>
#include <chrono>
#include <memory>
#include <QApplication>
#include <QDir>
#include <QRegExp>
#include <QTime>
#include <QTcpServer>
#include <QTcpSocket>
using namespace std;
using namespace std::placeholders;
using namespace ProjectExplorer;
using namespace Utils;
@@ -126,556 +108,6 @@ using namespace Utils;
namespace Android {
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 regExpLogcat = QStringLiteral("[0-9\\-]*" // date
"\\s+"
"[0-9\\-:.]*"// time
"\\s*"
"(\\d*)" // pid 1. capture
"\\s+"
"\\d*" // unknown
"\\s+"
"(\\w)" // message type 2. capture
"\\s+"
"(.*): " // source 3. capture
"(.*)" // message 4. capture
"[\\n\\r]*"
);
static int APP_START_TIMEOUT = 45000;
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
int msecs = APP_START_TIMEOUT)
{
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);
}
static void deleter(QProcess *p)
{
p->kill();
p->waitForFinished();
// Might get deleted from its own signal handler.
p->deleteLater();
}
class AndroidRunnerWorker : public QObject
{
Q_OBJECT
enum DebugHandShakeType {
PingPongFiles,
SocketHandShake
};
public:
AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable);
~AndroidRunnerWorker();
void asyncStart();
void asyncStop();
void setAndroidRunnable(const AndroidRunnable &runnable);
void handleRemoteDebuggerRunning();
signals:
void remoteProcessStarted(Utils::Port gdbServerPort, const QUrl &qmlServer, int pid);
void remoteProcessFinished(const QString &errString = QString());
void remoteOutput(const QString &output);
void remoteErrorOutput(const QString &output);
private:
void onProcessIdChanged(qint64 pid);
void logcatReadStandardError();
void logcatReadStandardOutput();
void adbKill(qint64 pid);
QStringList selector() const;
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);
// 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;
QScopedPointer<QTcpSocket> m_socket;
QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer;
QFuture<qint64> m_pidFinder;
qint64 m_processPID = -1;
bool m_useCppDebugger = false;
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
QUrl m_qmlServer;
QString m_pingFile;
QString m_pongFile;
QString m_gdbserverPath;
QString m_gdbserverSocket;
QString m_adb;
QRegExp m_logCatRegExp;
DebugHandShakeType m_handShakeMethod = SocketHandShake;
bool m_customPort = false;
AndroidRunnable m_androidRunnable;
int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
};
AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable)
: m_adbLogcatProcess(nullptr, deleter)
, m_psIsAlive(nullptr, deleter)
, m_logCatRegExp(regExpLogcat)
, m_androidRunnable(runnable)
{
auto runConfig = runControl->runConfiguration();
auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
Core::Id runMode = runControl->runMode();
const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
if (debuggingMode && aspect->useQmlDebugger())
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlProfilerServices;
else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlPreviewServices;
else
m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
m_localGdbServerPort = Utils::Port(5039);
QTC_CHECK(m_localGdbServerPort.isValid());
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
QTcpServer server;
QTC_ASSERT(server.listen(QHostAddress::LocalHost)
|| server.listen(QHostAddress::LocalHostIPv6),
qDebug() << tr("No free ports available on host for QML debugging."));
m_qmlServer.setScheme(Utils::urlTcpScheme());
m_qmlServer.setHost(server.serverAddress().toString());
m_qmlServer.setPort(server.serverPort());
}
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
QString packageDir = "/data/data/" + m_androidRunnable.packageName;
m_pingFile = packageDir + "/debug-ping";
m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_androidRunnable.packageName;
m_gdbserverSocket = packageDir + "/debug-socket";
const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion(
runConfig->target()->kit());
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0))
m_gdbserverPath = packageDir + "/lib/libgdbserver.so";
else
m_gdbserverPath = packageDir + "/lib/gdbserver";
if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0)) {
if (qEnvironmentVariableIsSet("QTC_ANDROID_USE_FILE_HANDSHAKE"))
m_handShakeMethod = PingPongFiles;
} else {
m_handShakeMethod = PingPongFiles;
}
if (qEnvironmentVariableIsSet("QTC_ANDROID_SOCKET_HANDSHAKE_PORT")) {
QByteArray envData = qgetenv("QTC_ANDROID_SOCKET_HANDSHAKE_PORT");
if (!envData.isEmpty()) {
bool ok = false;
int port = 0;
port = envData.toInt(&ok);
if (ok && port > 0 && port < 65535) {
m_socketHandShakePort = port;
m_customPort = true;
}
}
}
}
AndroidRunnerWorker::~AndroidRunnerWorker()
{
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
}
void AndroidRunnerWorker::forceStop()
{
runAdb({"shell", "am", "force-stop", m_androidRunnable.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_androidRunnable.packageName);
if (pid != -1) {
adbKill(pid);
}
}
void AndroidRunnerWorker::asyncStart()
{
forceStop();
// Start the logcat process before app starts.
std::unique_ptr<QProcess, decltype(&deleter)> logcatProcess(new QProcess, deleter);
connect(logcatProcess.get(), &QProcess::readyReadStandardOutput,
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");
QString errorMessage;
if (m_useCppDebugger)
runAdb({"shell", "rm", m_pongFile}); // Remove pong file.
for (const QString &entry: m_androidRunnable.beforeStartAdbCommands)
runAdb(entry.split(' ', QString::SkipEmptyParts));
QStringList args({"shell", "am", "start"});
args << m_androidRunnable.amStartExtraArgs;
args << "-n" << m_androidRunnable.intentName;
if (m_useCppDebugger) {
if (!runAdb({"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()})){
QTC_CHECK(false);
}
if (!runAdb({"forward", "tcp:" + m_localGdbServerPort.toString(),
"localfilesystem:" + m_gdbserverSocket}, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(errorMessage));
return;
}
const QString pingPongSocket(m_androidRunnable.packageName + ".ping_pong_socket");
args << "-e" << "debug_ping" << "true";
if (m_handShakeMethod == SocketHandShake) {
args << "-e" << "ping_socket" << pingPongSocket;
} else if (m_handShakeMethod == PingPongFiles) {
args << "-e" << "ping_file" << m_pingFile;
args << "-e" << "pong_file" << m_pongFile;
}
QString gdbserverCommand = QString::fromLatin1(adbShellAmNeedsQuotes() ? "\"%1 --multi +%2\"" : "%1 --multi +%2")
.arg(m_gdbserverPath).arg(m_gdbserverSocket);
args << "-e" << "gdbserver_command" << gdbserverCommand;
args << "-e" << "gdbserver_socket" << m_gdbserverSocket;
if (m_handShakeMethod == SocketHandShake) {
const QString port = QString("tcp:%1").arg(m_socketHandShakePort);
if (!runAdb({"forward", port, "localabstract:" + pingPongSocket}, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.")
.arg(errorMessage));
return;
}
}
}
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
// currently forward to same port on device and host
const QString port = QString("tcp:%1").arg(m_qmlServer.port());
if (!runAdb({"forward", port, port}, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
.arg(errorMessage));
return;
}
args << "-e" << "qml_debug" << "true"
<< "-e" << "qmljsdebugger"
<< QString("port:%1,block,services:%2")
.arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
}
if (!runAdb(args, &errorMessage)) {
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
.arg(errorMessage));
return;
}
if (m_useCppDebugger) {
if (m_handShakeMethod == SocketHandShake) {
//Handling socket
bool wasSuccess = false;
const int maxAttempts = 20; //20 seconds
m_socket.reset(new QTcpSocket());
for (int i = 0; i < maxAttempts; i++) {
QThread::sleep(1); // give Android time to start process
m_socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")),
m_socketHandShakePort);
if (!m_socket->waitForConnected())
continue;
if (!m_socket->waitForReadyRead()) {
m_socket->close();
continue;
}
const QByteArray pid = m_socket->readLine();
if (pid.isEmpty()) {
m_socket->close();
continue;
}
wasSuccess = true;
break;
}
if (!m_customPort) {
// increment running port to avoid clash when using multiple
// debug sessions at the same time
m_socketHandShakePort++;
// wrap ports around to avoid overflow
if (m_socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT)
m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT;
}
if (!wasSuccess) {
emit remoteProcessFinished(tr("Failed to contact debugging port."));
return;
}
} else {
// Handling ping.
for (int i = 0; ; ++i) {
Utils::TemporaryFile tmp("pingpong");
tmp.open();
tmp.close();
runAdb({"pull", m_pingFile, tmp.fileName()});
QFile res(tmp.fileName());
const bool doBreak = res.size();
res.remove();
if (doBreak)
break;
if (i == 20) {
emit remoteProcessFinished(tr("Unable to start \"%1\".")
.arg(m_androidRunnable.packageName));
return;
}
qDebug() << "WAITING FOR " << tmp.fileName();
QThread::msleep(500);
}
}
}
QTC_ASSERT(!m_adbLogcatProcess, /**/);
m_adbLogcatProcess = std::move(logcatProcess);
m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
m_androidRunnable.packageName),
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
}
bool AndroidRunnerWorker::adbShellAmNeedsQuotes()
{
// Between Android SDK Tools version 24.3.1 and 24.3.4 the quoting
// needs for the 'adb shell am start ...' parameters changed.
// Run a test to find out on what side of the fence we live.
// The command will fail with a complaint about the "--dummy"
// option on newer SDKs, and with "No intent supplied" on older ones.
// In case the test itself fails assume a new SDK.
Utils::SynchronousProcess adb;
adb.setTimeoutS(10);
Utils::SynchronousProcessResponse response
= adb.run(m_adb, selector() << "shell" << "am" << "start"
<< "-e" << "dummy" << "dummy --dummy");
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|| response.result != Utils::SynchronousProcessResponse::Finished)
return true;
const QString output = response.allOutput();
const bool oldSdk = output.contains("Error: No intent supplied");
return !oldSdk;
}
bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage, int timeoutS)
{
Utils::SynchronousProcess adb;
adb.setTimeoutS(timeoutS);
Utils::SynchronousProcessResponse response = adb.run(m_adb, selector() + args);
if (exitMessage)
*exitMessage = response.exitMessage(m_adb, timeoutS);
return response.result == Utils::SynchronousProcessResponse::Finished;
}
void AndroidRunnerWorker::handleRemoteDebuggerRunning()
{
if (m_useCppDebugger) {
if (m_handShakeMethod == SocketHandShake) {
m_socket->write("OK");
m_socket->waitForBytesWritten();
m_socket->close();
} else {
Utils::TemporaryFile tmp("pingpong");
tmp.open();
runAdb({"push", tmp.fileName(), m_pongFile});
}
QTC_CHECK(m_processPID != -1);
}
// emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort);
}
void AndroidRunnerWorker::asyncStop()
{
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
if (m_processPID != -1) {
forceStop();
}
}
void AndroidRunnerWorker::setAndroidRunnable(const AndroidRunnable &runnable)
{
m_androidRunnable = runnable;
}
void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
{
QList<QByteArray> lines = text.split('\n');
// lines always contains at least one item
lines[0].prepend(buffer);
if (!lines.last().endsWith('\n')) {
// incomplete line
buffer = lines.last();
lines.removeLast();
} else {
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);
}
}
}
void AndroidRunnerWorker::onProcessIdChanged(qint64 pid)
{
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
m_processPID = pid;
if (pid == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_androidRunnable.packageName));
// App died/killed. Reset log and monitor processes.
m_adbLogcatProcess.reset();
m_psIsAlive.reset();
// Run adb commands after application quit.
for (const QString &entry: m_androidRunnable.afterFinishAdbCommands)
runAdb(entry.split(' ', QString::SkipEmptyParts));
} else {
// In debugging cases this will be funneled to the engine to actually start
// and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
emit remoteProcessStarted(m_localGdbServerPort, m_qmlServer, m_processPID);
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)
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
}
void AndroidRunnerWorker::logcatReadStandardOutput()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
}
void AndroidRunnerWorker::adbKill(qint64 pid)
{
runAdb({"shell", "kill", "-9", QString::number(pid)});
runAdb({"shell", "run-as", m_androidRunnable.packageName, "kill", "-9", QString::number(pid)});
}
QStringList AndroidRunnerWorker::selector() const
{
return AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
}
AndroidRunner::AndroidRunner(RunControl *runControl)
: RunWorker(runControl), m_target(runControl->runConfiguration()->target())
{
@@ -693,6 +125,7 @@ AndroidRunner::AndroidRunner(RunControl *runControl)
m_androidRunnable.packageName = m_androidRunnable.intentName.left(
m_androidRunnable.intentName.indexOf(QLatin1Char('/')));
m_androidRunnable.deviceSerialNumber = AndroidManager::deviceSerialNumber(m_target);
m_androidRunnable.apiLevel = AndroidManager::deviceApiLevel(m_target);
auto androidRunConfig = qobject_cast<AndroidRunConfiguration *>(runControl->runConfiguration());
m_androidRunnable.amStartExtraArgs = androidRunConfig->amStartExtraArgs();
@@ -702,23 +135,26 @@ AndroidRunner::AndroidRunner(RunControl *runControl)
for (QString shellCmd: androidRunConfig->postFinishShellCommands())
m_androidRunnable.afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd));
if (m_androidRunnable.apiLevel > 23)
m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable));
else
m_worker.reset(new AndroidRunnerWorkerPreNougat(runControl, m_androidRunnable));
m_worker->moveToThread(&m_thread);
connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorker::asyncStart);
connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorker::asyncStop);
connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorkerBase::asyncStart);
connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorkerBase::asyncStop);
connect(this, &AndroidRunner::androidRunnableChanged,
m_worker.data(), &AndroidRunnerWorker::setAndroidRunnable);
m_worker.data(), &AndroidRunnerWorkerBase::setAndroidRunnable);
connect(this, &AndroidRunner::remoteDebuggerRunning,
m_worker.data(), &AndroidRunnerWorker::handleRemoteDebuggerRunning);
m_worker.data(), &AndroidRunnerWorkerBase::handleRemoteDebuggerRunning);
connect(m_worker.data(), &AndroidRunnerWorker::remoteProcessStarted,
connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteProcessStarted,
this, &AndroidRunner::handleRemoteProcessStarted);
connect(m_worker.data(), &AndroidRunnerWorker::remoteProcessFinished,
connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteProcessFinished,
this, &AndroidRunner::handleRemoteProcessFinished);
connect(m_worker.data(), &AndroidRunnerWorker::remoteOutput,
connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteOutput,
this, &AndroidRunner::remoteOutput);
connect(m_worker.data(), &AndroidRunnerWorker::remoteErrorOutput,
connect(m_worker.data(), &AndroidRunnerWorkerBase::remoteErrorOutput,
this, &AndroidRunner::remoteErrorOutput);
connect(&m_outputParser, &QmlDebug::QmlOutputParser::waitingForConnectionOnPort,
@@ -822,6 +258,7 @@ void AndroidRunner::launchAVD()
m_target->project(), deviceAPILevel, targetArch);
AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber);
m_androidRunnable.deviceSerialNumber = info.serialNumber;
m_androidRunnable.apiLevel = info.sdk;
emit androidRunnableChanged(m_androidRunnable);
if (info.isValid()) {
AndroidAvdManager avdManager;
@@ -854,5 +291,3 @@ void AndroidRunner::checkAVD()
} // namespace Internal
} // namespace Android
#include "androidrunner.moc"

View File

@@ -43,7 +43,7 @@
namespace Android {
namespace Internal {
class AndroidRunnerWorker;
class AndroidRunnerWorkerBase;
class AndroidRunner : public ProjectExplorer::RunWorker
{
@@ -85,7 +85,7 @@ private:
QString m_launchedAVDName;
QThread m_thread;
QTimer m_checkAVDTimer;
QScopedPointer<AndroidRunnerWorker> m_worker;
QScopedPointer<AndroidRunnerWorkerBase> m_worker;
QPointer<ProjectExplorer::Target> m_target;
Utils::Port m_gdbServerPort;
QUrl m_qmlServer;

View File

@@ -0,0 +1,558 @@
/****************************************************************************
**
** Copyright (C) 2018 BogDan Vatra <bog_dan_ro@yahoo.com>
** 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 "androidconfigurations.h"
#include "androidrunnerworker.h"
#include <QThread>
#include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/target.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/hostosinfo.h>
#include <utils/runextensions.h>
#include <utils/synchronousprocess.h>
#include <utils/temporaryfile.h>
#include <utils/url.h>
#include <QTcpServer>
#include <chrono>
using namespace std;
using namespace std::placeholders;
using namespace ProjectExplorer;
namespace Android {
namespace Internal {
static const QString pidScript = "pidof -s \"%1\"";
static const QString pidScriptPreNougat = QStringLiteral("for p in /proc/[0-9]*; "
"do cat <$p/cmdline && echo :${p##*/}; done");
static const QString pidPollingScript = QStringLiteral("while [ -d /proc/%1 ]; do sleep 1; done");
static const QString regExpLogcat = QStringLiteral("[0-9\\-]*" // date
"\\s+"
"[0-9\\-:.]*"// time
"\\s*"
"(\\d*)" // pid 1. capture
"\\s+"
"\\d*" // unknown
"\\s+"
"(\\w)" // message type 2. capture
"\\s+"
"(.*): " // source 3. capture
"(.*)" // message 4. capture
"[\\n\\r]*"
);
static int APP_START_TIMEOUT = 45000;
static bool isTimedOut(const chrono::high_resolution_clock::time_point &start,
int msecs = APP_START_TIMEOUT)
{
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 findProcessPIDPreNougat(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") << pidScriptPreNougat)
.allRawOutput();
processPID = extractPID(out, packageName);
} while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
if (!fi.isCanceled())
fi.reportResult(processPID);
}
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.arg(packageName))
.allRawOutput();
if (!out.isEmpty())
processPID = out.trimmed().toLongLong();
} while (processPID == -1 && !isTimedOut(start) && !fi.isCanceled());
if (!fi.isCanceled())
fi.reportResult(processPID);
}
AndroidRunnerWorkerBase::AndroidRunnerWorkerBase(RunControl *runControl, const AndroidRunnable &runnable)
: m_androidRunnable(runnable)
, m_adbLogcatProcess(nullptr, deleter)
, m_psIsAlive(nullptr, deleter)
, m_logCatRegExp(regExpLogcat)
, m_gdbServerProcess(nullptr, deleter)
, m_jdbProcess(nullptr, deleter)
{
auto runConfig = runControl->runConfiguration();
auto aspect = runConfig->extraAspect<Debugger::DebuggerRunConfigurationAspect>();
Core::Id runMode = runControl->runMode();
const bool debuggingMode = runMode == ProjectExplorer::Constants::DEBUG_RUN_MODE;
m_useCppDebugger = debuggingMode && aspect->useCppDebugger();
if (debuggingMode && aspect->useQmlDebugger())
m_qmlDebugServices = QmlDebug::QmlDebuggerServices;
else if (runMode == ProjectExplorer::Constants::QML_PROFILER_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlProfilerServices;
else if (runMode == ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE)
m_qmlDebugServices = QmlDebug::QmlPreviewServices;
else
m_qmlDebugServices = QmlDebug::NoQmlDebugServices;
m_localGdbServerPort = Utils::Port(5039);
QTC_CHECK(m_localGdbServerPort.isValid());
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
QTcpServer server;
QTC_ASSERT(server.listen(QHostAddress::LocalHost)
|| server.listen(QHostAddress::LocalHostIPv6),
qDebug() << tr("No free ports available on host for QML debugging."));
m_qmlServer.setScheme(Utils::urlTcpScheme());
m_qmlServer.setHost(server.serverAddress().toString());
m_qmlServer.setPort(server.serverPort());
}
m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString();
m_localJdbServerPort = Utils::Port(5038);
QTC_CHECK(m_localJdbServerPort.isValid());
}
AndroidRunnerWorkerBase::~AndroidRunnerWorkerBase()
{
if (m_processPID != -1)
forceStop();
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
}
bool AndroidRunnerWorkerBase::adbShellAmNeedsQuotes()
{
// Between Android SDK Tools version 24.3.1 and 24.3.4 the quoting
// needs for the 'adb shell am start ...' parameters changed.
// Run a test to find out on what side of the fence we live.
// The command will fail with a complaint about the "--dummy"
// option on newer SDKs, and with "No intent supplied" on older ones.
// In case the test itself fails assume a new SDK.
Utils::SynchronousProcess adb;
adb.setTimeoutS(10);
Utils::SynchronousProcessResponse response
= adb.run(m_adb, selector() << "shell" << "am" << "start"
<< "-e" << "dummy" << "dummy --dummy");
if (response.result == Utils::SynchronousProcessResponse::StartFailed
|| response.result != Utils::SynchronousProcessResponse::Finished)
return true;
const QString output = response.allOutput();
const bool oldSdk = output.contains("Error: No intent supplied");
return !oldSdk;
}
bool AndroidRunnerWorkerBase::runAdb(const QStringList &args, int timeoutS)
{
Utils::SynchronousProcess adb;
adb.setTimeoutS(timeoutS);
Utils::SynchronousProcessResponse response = adb.run(m_adb, selector() + args);
m_lastRunAdbError = response.exitMessage(m_adb, timeoutS);
m_lastRunAdbRawOutput = response.allRawOutput();
return response.result == Utils::SynchronousProcessResponse::Finished;
}
void AndroidRunnerWorkerBase::adbKill(qint64 pid)
{
runAdb({"shell", "kill", "-9", QString::number(pid)});
runAdb({"shell", "run-as", m_androidRunnable.packageName, "kill", "-9", QString::number(pid)});
}
QStringList AndroidRunnerWorkerBase::selector() const
{
return AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber);
}
void AndroidRunnerWorkerBase::forceStop()
{
runAdb({"shell", "am", "force-stop", m_androidRunnable.packageName}, 30);
// try killing it via kill -9
const QByteArray out = Utils::SynchronousProcess()
.runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScriptPreNougat)
.allRawOutput();
qint64 pid = extractPID(out.simplified(), m_androidRunnable.packageName);
if (pid != -1) {
adbKill(pid);
}
}
void AndroidRunnerWorkerBase::logcatReadStandardError()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess->readAllStandardError(), m_stderrBuffer, true);
}
void AndroidRunnerWorkerBase::logcatReadStandardOutput()
{
if (m_processPID != -1)
logcatProcess(m_adbLogcatProcess->readAllStandardOutput(), m_stdoutBuffer, false);
}
void AndroidRunnerWorkerBase::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError)
{
QList<QByteArray> lines = text.split('\n');
// lines always contains at least one item
lines[0].prepend(buffer);
if (!lines.last().endsWith('\n')) {
// incomplete line
buffer = lines.last();
lines.removeLast();
} else {
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_useCppDebugger) {
switch (m_jdbState) {
case JDBState::Idle:
if (msg.trimmed().endsWith("Sending WAIT chunk")) {
m_jdbState = JDBState::Waiting;
handleJdbWaiting();
}
break;
case JDBState::Waiting:
if (msg.indexOf("debugger has settled") > 0) {
m_jdbState = JDBState::Settled;
handleJdbSettled();
}
break;
default:
break;
}
}
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);
}
}
}
void AndroidRunnerWorkerBase::setAndroidRunnable(const AndroidRunnable &runnable)
{
m_androidRunnable = runnable;
}
void AndroidRunnerWorkerBase::asyncStart()
{
forceStop();
// Start the logcat process before app starts.
std::unique_ptr<QProcess, decltype(&deleter)> logcatProcess(new QProcess, deleter);
connect(logcatProcess.get(), &QProcess::readyReadStandardOutput,
this, &AndroidRunnerWorkerBase::logcatReadStandardOutput);
connect(logcatProcess.get(), &QProcess::readyReadStandardError,
this, &AndroidRunnerWorkerBase::logcatReadStandardError);
// Its assumed that the device or avd returned by selector() is online.
logcatProcess->start(m_adb, selector() << "logcat");
QTC_ASSERT(!m_adbLogcatProcess, /**/);
m_adbLogcatProcess = std::move(logcatProcess);
for (const QString &entry: m_androidRunnable.beforeStartAdbCommands)
runAdb(entry.split(' ', QString::SkipEmptyParts));
QStringList args({"shell", "am", "start"});
args << m_androidRunnable.amStartExtraArgs;
args << "-n" << m_androidRunnable.intentName;
if (m_useCppDebugger) {
args << "-D";
QString gdbServerSocket;
// run-as <package-name> pwd fails on API 22 so route the pwd through shell.
if (!runAdb({"shell", "run-as", m_androidRunnable.packageName, "/system/bin/sh", "-c", "pwd"})) {
emit remoteProcessFinished(tr("Failed to get process path. Reason: %1.").arg(m_lastRunAdbError));
return;
}
gdbServerSocket = QString::fromUtf8(m_lastRunAdbRawOutput.trimmed()) + "/debug-socket";
QString gdbServerExecutable;
if (!runAdb({"shell", "run-as", m_androidRunnable.packageName, "ls", "lib/"})) {
emit remoteProcessFinished(tr("Failed to get process path. Reason: %1.").arg(m_lastRunAdbError));
return;
}
for (const auto &line: m_lastRunAdbRawOutput.split('\n')) {
if (line.indexOf("gdbserver") != -1/* || line.indexOf("lldb-server") != -1*/) {
gdbServerExecutable = QString::fromUtf8(line.trimmed());
break;
}
}
if (gdbServerExecutable.isEmpty()) {
emit remoteProcessFinished(tr("Can't find C++ debugger."));
return;
}
runAdb({"shell", "run-as", m_androidRunnable.packageName, "killall", gdbServerExecutable});
runAdb({"shell", "run-as", m_androidRunnable.packageName, "rm", gdbServerSocket});
std::unique_ptr<QProcess, decltype(&deleter)> gdbServerProcess(new QProcess, deleter);
gdbServerProcess->start(m_adb, selector() << "shell" << "run-as"
<< m_androidRunnable.packageName << "lib/" + gdbServerExecutable
<< "--multi" << "+" + gdbServerSocket);
if (!gdbServerProcess->waitForStarted()) {
emit remoteProcessFinished(tr("Failed to start C++ debugger."));
return;
}
m_gdbServerProcess = std::move(gdbServerProcess);
QStringList removeForward{"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()};
runAdb(removeForward);
if (!runAdb({"forward", "tcp:" + m_localGdbServerPort.toString(),
"localfilesystem:" + gdbServerSocket})) {
emit remoteProcessFinished(tr("Failed to forward C++ debugging ports. Reason: %1.").arg(m_lastRunAdbError));
return;
}
m_androidRunnable.afterFinishAdbCommands.push_back(removeForward.join(' '));
}
if (m_qmlDebugServices != QmlDebug::NoQmlDebugServices) {
// currently forward to same port on device and host
const QString port = QString("tcp:%1").arg(m_qmlServer.port());
QStringList removeForward{{"forward", "--remove", port}};
runAdb(removeForward);
if (!runAdb({"forward", port, port})) {
emit remoteProcessFinished(tr("Failed to forward QML debugging ports. Reason: %1.")
.arg(m_lastRunAdbError));
return;
}
m_androidRunnable.afterFinishAdbCommands.push_back(removeForward.join(' '));
args << "-e" << "qml_debug" << "true"
<< "-e" << "qmljsdebugger"
<< QString("port:%1,block,services:%2")
.arg(m_qmlServer.port()).arg(QmlDebug::qmlDebugServices(m_qmlDebugServices));
}
if (!runAdb(args)) {
emit remoteProcessFinished(tr("Failed to start the activity. Reason: %1.")
.arg(m_lastRunAdbError));
return;
}
}
void AndroidRunnerWorkerBase::asyncStop()
{
if (!m_pidFinder.isFinished())
m_pidFinder.cancel();
if (m_processPID != -1)
forceStop();
m_jdbProcess.reset();
m_gdbServerProcess.reset();
}
void AndroidRunnerWorkerBase::handleRemoteDebuggerRunning()
{}
void AndroidRunnerWorkerBase::handleJdbWaiting()
{
QStringList removeForward{"forward", "--remove", "tcp:" + m_localJdbServerPort.toString()};
runAdb(removeForward);
if (!runAdb({"forward", "tcp:" + m_localJdbServerPort.toString(),
"jdwp:" + QString::number(m_processPID)})) {
emit remoteProcessFinished(tr("Failed to forward jdb debugging ports. Reason: %1.").arg(m_lastRunAdbError));
return;
}
m_androidRunnable.afterFinishAdbCommands.push_back(removeForward.join(' '));
auto jdbPath = AndroidConfigurations::currentConfig().openJDKLocation().appendPath("bin");
if (Utils::HostOsInfo::isWindowsHost())
jdbPath.appendPath("jdb.exe");
else
jdbPath.appendPath("jdb");
std::unique_ptr<QProcess, decltype(&deleter)> jdbProcess(new QProcess, deleter);
jdbProcess->setProcessChannelMode(QProcess::MergedChannels);
jdbProcess->start(jdbPath.toString(), QStringList() << "-connect" <<
QString("com.sun.jdi.SocketAttach:hostname=localhost,port=%1")
.arg(m_localJdbServerPort.toString()));
if (!jdbProcess->waitForStarted()) {
emit remoteProcessFinished(tr("Failed to start JDB"));
return;
}
m_jdbProcess = std::move(jdbProcess);
}
void AndroidRunnerWorkerBase::handleJdbSettled()
{
auto waitForCommand = [&]() {
for (int i= 0; i < 5 && m_jdbProcess->state() == QProcess::Running; ++i) {
m_jdbProcess->waitForReadyRead(500);
QByteArray lines = m_jdbProcess->readAll();
for (const auto &line: lines.split('\n')) {
auto msg = line.trimmed();
if (msg.startsWith(">"))
return true;
}
}
return false;
};
if (waitForCommand()) {
m_jdbProcess->write("cont\n");
if (m_jdbProcess->waitForBytesWritten(5000) && waitForCommand()) {
m_jdbProcess->write("exit\n");
m_jdbProcess->waitForBytesWritten(5000);
if (!m_jdbProcess->waitForFinished(5000)) {
m_jdbProcess->terminate();
if (!m_jdbProcess->waitForFinished(5000)) {
m_jdbProcess->kill();
m_jdbProcess->waitForFinished();
}
} else if (m_jdbProcess->exitStatus() == QProcess::NormalExit && m_jdbProcess->exitCode() == 0) {
return;
}
}
}
emit remoteProcessFinished(tr("Can't attach jdb to the running application").arg(m_lastRunAdbError));
}
void AndroidRunnerWorkerBase::onProcessIdChanged(qint64 pid)
{
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
m_processPID = pid;
if (pid == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.")
.arg(m_androidRunnable.packageName));
// App died/killed. Reset log, monitor, jdb & gdb processes.
m_adbLogcatProcess.reset();
m_psIsAlive.reset();
m_jdbProcess.reset();
m_gdbServerProcess.reset();
// Run adb commands after application quit.
for (const QString &entry: m_androidRunnable.afterFinishAdbCommands)
runAdb(entry.split(' ', QString::SkipEmptyParts));
} else {
// In debugging cases this will be funneled to the engine to actually start
// and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below.
emit remoteProcessStarted(m_localGdbServerPort, m_qmlServer, m_processPID);
logcatReadStandardOutput();
QTC_ASSERT(!m_psIsAlive, /**/);
m_psIsAlive.reset(new QProcess);
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
connect(m_psIsAlive.get(), static_cast<void(QProcess::*)(int)>(&QProcess::finished),
this, bind(&AndroidRunnerWorkerBase::onProcessIdChanged, this, -1));
m_psIsAlive->start(m_adb, selector() << QStringLiteral("shell")
<< pidPollingScript.arg(m_processPID));
}
}
AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable)
: AndroidRunnerWorkerBase(runControl, runnable)
{
}
void AndroidRunnerWorker::asyncStart()
{
AndroidRunnerWorkerBase::asyncStart();
m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(),
m_androidRunnable.packageName),
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
}
AndroidRunnerWorkerPreNougat::AndroidRunnerWorkerPreNougat(RunControl *runControl, const AndroidRunnable &runnable)
: AndroidRunnerWorkerBase(runControl, runnable)
{
}
void AndroidRunnerWorkerPreNougat::asyncStart()
{
AndroidRunnerWorkerBase::asyncStart();
m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPIDPreNougat, m_adb, selector(),
m_androidRunnable.packageName),
bind(&AndroidRunnerWorkerPreNougat::onProcessIdChanged, this, _1));
}
} // namespace Internal
} // namespace Android

View File

@@ -0,0 +1,134 @@
/****************************************************************************
**
** Copyright (C) 2018 BogDan Vatra <bog_dan_ro@yahoo.com>
** 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 <qmldebug/qmldebugcommandlinearguments.h>
#include <QFuture>
#include <QTcpSocket>
#include "androidrunnable.h"
namespace ProjectExplorer {
class RunControl;
}
namespace Android {
namespace Internal {
const int MIN_SOCKET_HANDSHAKE_PORT = 20001;
static void deleter(QProcess *p)
{
p->terminate();
if (!p->waitForFinished(1000)) {
p->kill();
p->waitForFinished();
}
// Might get deleted from its own signal handler.
p->deleteLater();
}
class AndroidRunnerWorkerBase : public QObject
{
Q_OBJECT
public:
AndroidRunnerWorkerBase(ProjectExplorer::RunControl *runControl, const AndroidRunnable &runnable);
~AndroidRunnerWorkerBase() override;
bool adbShellAmNeedsQuotes();
bool runAdb(const QStringList &args, int timeoutS = 10);
void adbKill(qint64 pid);
QStringList selector() const;
void forceStop();
void logcatReadStandardError();
void logcatReadStandardOutput();
void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError);
void setAndroidRunnable(const AndroidRunnable &runnable);
virtual void asyncStart();
virtual void asyncStop();
virtual void handleRemoteDebuggerRunning();
virtual void handleJdbWaiting();
virtual void handleJdbSettled();
signals:
void remoteProcessStarted(Utils::Port gdbServerPort, const QUrl &qmlServer, int pid);
void remoteProcessFinished(const QString &errString = QString());
void remoteOutput(const QString &output);
void remoteErrorOutput(const QString &output);
protected:
enum class JDBState {
Idle,
Waiting,
Settled
};
virtual void onProcessIdChanged(qint64 pid);
// Create the processes and timer in the worker thread, for correct thread affinity
AndroidRunnable m_androidRunnable;
QString m_adb;
qint64 m_processPID = -1;
std::unique_ptr<QProcess, decltype(&deleter)> m_adbLogcatProcess;
std::unique_ptr<QProcess, decltype(&deleter)> m_psIsAlive;
QByteArray m_stdoutBuffer;
QByteArray m_stderrBuffer;
QRegExp m_logCatRegExp;
QFuture<qint64> m_pidFinder;
bool m_useCppDebugger = false;
QmlDebug::QmlDebugServicesPreset m_qmlDebugServices;
Utils::Port m_localGdbServerPort; // Local end of forwarded debug socket.
QUrl m_qmlServer;
QByteArray m_lastRunAdbRawOutput;
QString m_lastRunAdbError;
JDBState m_jdbState = JDBState::Idle;
Utils::Port m_localJdbServerPort;
std::unique_ptr<QProcess, decltype(&deleter)> m_gdbServerProcess;
std::unique_ptr<QProcess, decltype(&deleter)> m_jdbProcess;
};
class AndroidRunnerWorker : public AndroidRunnerWorkerBase
{
Q_OBJECT
public:
AndroidRunnerWorker(ProjectExplorer::RunControl *runControl, const AndroidRunnable &runnable);
void asyncStart() override;
};
class AndroidRunnerWorkerPreNougat : public AndroidRunnerWorkerBase
{
Q_OBJECT
public:
AndroidRunnerWorkerPreNougat(ProjectExplorer::RunControl *runControl, const AndroidRunnable &runnable);
void asyncStart() override;
};
} // namespace Internal
} // namespace Android

View File

@@ -95,10 +95,12 @@ void QmakeAndroidRunConfiguration::updateDisplayName()
const QmakeProjectManager::QmakeProFileNode *root = project->rootProjectNode();
if (root) {
const QmakeProjectManager::QmakeProFileNode *node = root->findProFileFor(m_proFilePath);
if (node) // should always be found
if (node) { // should always be found
setDisplayName(node->displayName());
setDefaultDisplayName(node->displayName());
}
}
}
QString QmakeAndroidRunConfiguration::disabledReason() const
{