diff options
author | Eike Ziller <[email protected]> | 2017-04-19 09:56:14 +0200 |
---|---|---|
committer | Eike Ziller <[email protected]> | 2017-04-19 09:56:14 +0200 |
commit | 88897f3a870de7b756356907801f09a90902e490 (patch) | |
tree | a947c7fdb92d9050e9ade763dcb87db192b4585e /src/plugins/android/androidrunner.cpp | |
parent | 8b6eb5aabb48fb83c3cd92be9a77401ca26a810b (diff) | |
parent | 01b2ed7904132f845819e78c84477ac9a66bd1e3 (diff) |
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
Diffstat (limited to 'src/plugins/android/androidrunner.cpp')
-rw-r--r-- | src/plugins/android/androidrunner.cpp | 291 |
1 files changed, 167 insertions, 124 deletions
diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp index 778d348b685..9208e699536 100644 --- a/src/plugins/android/androidrunner.cpp +++ b/src/plugins/android/androidrunner.cpp @@ -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) -{ - 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; -} +enum class PidStatus { + Found, + Lost +}; -void findProcessPID(QFutureInterface<qint64> &fi, const QString &adbPath, - QStringList selector, const QString &packageName) +struct PidInfo { - 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::asyncStop(const QVector<QStringList> &adbCommands) +void AndroidRunnerWorker::findProcessPids() { - if (!m_pidFinder.isFinished()) - m_pidFinder.cancel(); + static QMap<qint64, QByteArray> extractedPids; + static auto oldPids = m_processPids; - if (m_processPID != -1) { - forceStop(); + 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) +{ + 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); |