forked from qt-creator/qt-creator
Merge remote-tracking branch 'origin/4.2'
Conflicts: src/plugins/android/androidbuildapkstep.cpp src/plugins/genericprojectmanager/genericproject.cpp Change-Id: I3484b668f9323ed0c05de99f8dfed07c9e65ab98
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/synchronousprocess.h>
|
||||
#include <projectexplorer/kitmanager.h>
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/devicesupport/devicemanager.h>
|
||||
@@ -48,6 +49,7 @@
|
||||
#include <qtsupport/qtversionmanager.h>
|
||||
#include <qtsupport/qtversionfactory.h>
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
@@ -62,6 +64,7 @@ using namespace Debugger;
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(kitSetupLog, "qtc.ios.kitSetup")
|
||||
Q_LOGGING_CATEGORY(iosCommonLog, "qtc.ios.common")
|
||||
}
|
||||
|
||||
using ToolChainPair = std::pair<ClangToolChain *, ClangToolChain *>;
|
||||
@@ -214,6 +217,33 @@ static void setupKit(Kit *kit, Core::Id pDeviceType, const ToolChainPair& toolCh
|
||||
SysRootKitInformation::setSysRoot(kit, sdkPath);
|
||||
}
|
||||
|
||||
static QVersionNumber findXcodeVersion()
|
||||
{
|
||||
Utils::SynchronousProcess pkgUtilProcess;
|
||||
Utils::SynchronousProcessResponse resp =
|
||||
pkgUtilProcess.runBlocking("pkgutil", QStringList("--pkg-info-plist=com.apple.pkg.Xcode"));
|
||||
if (resp.result == Utils::SynchronousProcessResponse::Finished) {
|
||||
QDomDocument xcodeVersionDoc;
|
||||
if (xcodeVersionDoc.setContent(resp.allRawOutput())) {
|
||||
QDomNodeList nodes = xcodeVersionDoc.elementsByTagName(QStringLiteral("key"));
|
||||
for (int i = 0; i < nodes.count(); ++i) {
|
||||
QDomElement elem = nodes.at(i).toElement();
|
||||
if (elem.text().compare(QStringLiteral("pkg-version")) == 0) {
|
||||
QString versionStr = elem.nextSiblingElement().text();
|
||||
return QVersionNumber::fromString(versionStr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(iosCommonLog) << "Error finding Xcode version. Cannot parse xml output from pkgutil.";
|
||||
}
|
||||
} else {
|
||||
qCDebug(iosCommonLog) << "Error finding Xcode version. pkgutil command failed.";
|
||||
}
|
||||
|
||||
qCDebug(iosCommonLog) << "Error finding Xcode version. Unknow error.";
|
||||
return QVersionNumber();
|
||||
}
|
||||
|
||||
void IosConfigurations::updateAutomaticKitList()
|
||||
{
|
||||
const QList<Platform> platforms = handledPlatforms();
|
||||
@@ -319,6 +349,11 @@ FileName IosConfigurations::developerPath()
|
||||
return m_instance->m_developerPath;
|
||||
}
|
||||
|
||||
QVersionNumber IosConfigurations::xcodeVersion()
|
||||
{
|
||||
return m_instance->m_xcodeVersion;
|
||||
}
|
||||
|
||||
void IosConfigurations::save()
|
||||
{
|
||||
QSettings *settings = Core::ICore::settings();
|
||||
@@ -365,6 +400,9 @@ void IosConfigurations::setDeveloperPath(const FileName &devPath)
|
||||
QTimer::singleShot(1000, IosDeviceManager::instance(),
|
||||
&IosDeviceManager::monitorAvailableDevices);
|
||||
m_instance->updateSimulators();
|
||||
|
||||
// Find xcode version.
|
||||
m_instance->m_xcodeVersion = findXcodeVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVersionNumber>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QSettings;
|
||||
@@ -60,6 +61,7 @@ public:
|
||||
static bool ignoreAllDevices();
|
||||
static void setIgnoreAllDevices(bool ignoreDevices);
|
||||
static Utils::FileName developerPath();
|
||||
static QVersionNumber xcodeVersion();
|
||||
static Utils::FileName lldbPath();
|
||||
static void updateAutomaticKitList();
|
||||
|
||||
@@ -71,6 +73,7 @@ private:
|
||||
static void setDeveloperPath(const Utils::FileName &devPath);
|
||||
|
||||
Utils::FileName m_developerPath;
|
||||
QVersionNumber m_xcodeVersion;
|
||||
bool m_ignoreAllDevices;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,19 +37,24 @@
|
||||
#include "utils/synchronousprocess.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QFutureWatcher>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QPointer>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QScopedArrayPointer>
|
||||
#include <QSocketNotifier>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -61,6 +66,68 @@ namespace Internal {
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
// As per the currrent behavior, any absolute path given to simctl --stdout --stderr where the
|
||||
// directory after the root also exists on the simulator's file system will map to
|
||||
// simulator's file system i.e. simctl translates $TMPDIR/somwhere/out.txt to
|
||||
// your_home_dir/Library/Developer/CoreSimulator/Devices/data/$TMP_DIR/somwhere/out.txt.
|
||||
// Because /var also exists on simulator's file system.
|
||||
// Though the log files located at CONSOLE_PATH_TEMPLATE are deleted on
|
||||
// app exit any leftovers shall be removed on simulator restart.
|
||||
static QString CONSOLE_PATH_TEMPLATE = QDir::homePath() +
|
||||
"/Library/Developer/CoreSimulator/Devices/%1/data/tmp/%2";
|
||||
|
||||
class LogTailFiles : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
void exec(QFutureInterface<void> &fi, std::shared_ptr<QTemporaryFile> stdoutFile,
|
||||
std::shared_ptr<QTemporaryFile> stderrFile)
|
||||
{
|
||||
if (fi.isCanceled())
|
||||
return;
|
||||
|
||||
// The future is canceled when app on simulator is stoped.
|
||||
QEventLoop loop;
|
||||
QFutureWatcher<void> watcher;
|
||||
connect(&watcher, &QFutureWatcher<void>::canceled, [&](){
|
||||
loop.quit();
|
||||
});
|
||||
watcher.setFuture(fi.future());
|
||||
|
||||
// Process to print the console output while app is running.
|
||||
auto logProcess = [this, fi](QProcess *tailProcess, std::shared_ptr<QTemporaryFile> file) {
|
||||
QObject::connect(tailProcess, &QProcess::readyReadStandardOutput, [=]() {
|
||||
if (!fi.isCanceled())
|
||||
emit logMessage(QString::fromLocal8Bit(tailProcess->readAll()));
|
||||
});
|
||||
tailProcess->start(QStringLiteral("tail"), QStringList() << "-f" << file->fileName());
|
||||
};
|
||||
|
||||
auto processDeleter = [](QProcess *process) {
|
||||
if (process->state() != QProcess::NotRunning) {
|
||||
process->terminate();
|
||||
process->waitForFinished();
|
||||
}
|
||||
delete process;
|
||||
};
|
||||
|
||||
std::unique_ptr<QProcess, void(*)(QProcess *)> tailStdout(new QProcess, processDeleter);
|
||||
if (stdoutFile)
|
||||
logProcess(tailStdout.get(), stdoutFile);
|
||||
|
||||
std::unique_ptr<QProcess, void(*)(QProcess *)> tailStderr(new QProcess, processDeleter);
|
||||
if (stderrFile)
|
||||
logProcess(tailStderr.get(), stderrFile);
|
||||
|
||||
// Blocks untill tool is deleted or toolexited is called.
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
signals:
|
||||
void logMessage(QString message);
|
||||
};
|
||||
|
||||
struct ParserState {
|
||||
enum Kind {
|
||||
Msg,
|
||||
@@ -256,14 +323,9 @@ private:
|
||||
void launchAppOnSimulator(const QStringList &extraArgs);
|
||||
bool isResponseValid(const SimulatorControl::ResponseData &responseData);
|
||||
|
||||
void simAppProcessError(QProcess::ProcessError error);
|
||||
void simAppProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void simAppProcessHasData();
|
||||
void simAppProcessHasErrorOutput();
|
||||
|
||||
private:
|
||||
qint64 appPId = -1;
|
||||
SimulatorControl *simCtl;
|
||||
LogTailFiles outputLogger;
|
||||
QList<QFuture<void>> futureList;
|
||||
};
|
||||
|
||||
@@ -727,6 +789,8 @@ IosSimulatorToolHandlerPrivate::IosSimulatorToolHandlerPrivate(const IosDeviceTy
|
||||
: IosToolHandlerPrivate(devType, q),
|
||||
simCtl(new SimulatorControl)
|
||||
{
|
||||
QObject::connect(&outputLogger, &LogTailFiles::logMessage,
|
||||
std::bind(&IosToolHandlerPrivate::appOutput, this, _1));
|
||||
}
|
||||
|
||||
IosSimulatorToolHandlerPrivate::~IosSimulatorToolHandlerPrivate()
|
||||
@@ -809,7 +873,6 @@ void IosSimulatorToolHandlerPrivate::requestDeviceInfo(const QString &deviceId,
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::stop(int errorCode)
|
||||
{
|
||||
appPId = -1;
|
||||
foreach (auto f, futureList) {
|
||||
if (!f.isFinished())
|
||||
f.cancel();
|
||||
@@ -843,30 +906,54 @@ void IosSimulatorToolHandlerPrivate::installAppOnSimulator()
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &extraArgs)
|
||||
{
|
||||
auto monitorPid = [this](QFutureInterface<int> &fi, qint64 pid) {
|
||||
int exitCode = 0;
|
||||
const QStringList args({QStringLiteral("-0"), QString::number(pid)});
|
||||
Utils::SynchronousProcess pKill;
|
||||
while (!fi.isCanceled() && exitCode == 0) {
|
||||
const Utils::FileName appBundle = Utils::FileName::fromString(bundlePath);
|
||||
const QString bundleId = SimulatorControl::bundleIdentifier(appBundle);
|
||||
const bool debugRun = runKind == IosToolHandler::DebugRun;
|
||||
bool captureConsole = IosConfigurations::xcodeVersion() >= QVersionNumber(8);
|
||||
std::shared_ptr<QTemporaryFile> stdoutFile;
|
||||
std::shared_ptr<QTemporaryFile> stderrFile;
|
||||
|
||||
if (captureConsole) {
|
||||
const QString fileTemplate = CONSOLE_PATH_TEMPLATE.arg(deviceId).arg(bundleId);
|
||||
stdoutFile.reset(new QTemporaryFile);
|
||||
stdoutFile->setFileTemplate(fileTemplate + QStringLiteral(".stdout"));
|
||||
|
||||
stderrFile.reset(new QTemporaryFile);
|
||||
stderrFile->setFileTemplate(fileTemplate + QStringLiteral(".stderr"));
|
||||
|
||||
captureConsole = stdoutFile->open() && stderrFile->open();
|
||||
if (!captureConsole)
|
||||
errorMsg(IosToolHandler::tr("Cannot capture console output from %1. "
|
||||
"Error redirecting output to %2.*")
|
||||
.arg(bundleId).arg(fileTemplate));
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Cannot capture console output from %1. "
|
||||
"Install Xcode 8 or later.").arg(bundleId));
|
||||
}
|
||||
|
||||
auto monitorPid = [this](QFutureInterface<void> &fi, qint64 pid) {
|
||||
#ifdef Q_OS_UNIX
|
||||
do {
|
||||
// Poll every 1 sec to check whether the app is running.
|
||||
QThread::msleep(1000);
|
||||
Utils::SynchronousProcessResponse resp = pKill.runBlocking(QStringLiteral("kill"), args);
|
||||
exitCode = resp.exitCode;
|
||||
}
|
||||
} while (!fi.isCanceled() && kill(pid, 0) == 0);
|
||||
#endif
|
||||
// Future is cancelled if the app is stopped from the qt creator.
|
||||
if (!fi.isCanceled())
|
||||
stop(0);
|
||||
};
|
||||
|
||||
auto onResponseAppLaunch = [this, monitorPid](const SimulatorControl::ResponseData &response) {
|
||||
auto onResponseAppLaunch = [=](const SimulatorControl::ResponseData &response) {
|
||||
if (!isResponseValid(response))
|
||||
return;
|
||||
if (response.success) {
|
||||
appPId = response.pID;
|
||||
gotInferiorPid(bundlePath, deviceId, appPId);
|
||||
gotInferiorPid(bundlePath, deviceId, response.pID);
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Success);
|
||||
// Start monitoring app's life signs.
|
||||
futureList << Utils::runAsync(monitorPid, appPId);
|
||||
futureList << Utils::runAsync(monitorPid, response.pID);
|
||||
if (captureConsole)
|
||||
futureList << Utils::runAsync(&LogTailFiles::exec, &outputLogger, stdoutFile,
|
||||
stderrFile);
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Application launch on Simulator failed. %1")
|
||||
.arg(QString::fromLocal8Bit(response.commandOutput)));
|
||||
@@ -875,11 +962,12 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &ext
|
||||
q->finished(q);
|
||||
}
|
||||
};
|
||||
Utils::FileName appBundle = Utils::FileName::fromString(bundlePath);
|
||||
futureList << Utils::onResultReady(simCtl->launchApp(deviceId,
|
||||
SimulatorControl::bundleIdentifier(appBundle),
|
||||
runKind == IosToolHandler::DebugRun,
|
||||
extraArgs), onResponseAppLaunch);
|
||||
|
||||
futureList << Utils::onResultReady(
|
||||
simCtl->launchApp(deviceId, bundleId, debugRun, extraArgs,
|
||||
captureConsole ? stdoutFile->fileName() : QString(),
|
||||
captureConsole ? stderrFile->fileName() : QString()),
|
||||
onResponseAppLaunch);
|
||||
}
|
||||
|
||||
bool IosSimulatorToolHandlerPrivate::isResponseValid(const SimulatorControl::ResponseData &responseData)
|
||||
@@ -895,28 +983,6 @@ bool IosSimulatorToolHandlerPrivate::isResponseValid(const SimulatorControl::Res
|
||||
return true;
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::simAppProcessError(QProcess::ProcessError error)
|
||||
{
|
||||
errorMsg(IosToolHandler::tr("Simulator application process error %1").arg(error));
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::simAppProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
stop((exitStatus == QProcess::NormalExit) ? exitCode : -1 );
|
||||
qCDebug(toolHandlerLog) << "IosToolHandler::finished(" << this << ")";
|
||||
q->finished(q);
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::simAppProcessHasData()
|
||||
{
|
||||
appOutput(QString::fromLocal8Bit(process->readAllStandardOutput()));
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::simAppProcessHasErrorOutput()
|
||||
{
|
||||
errorMsg(QString::fromLocal8Bit(process->readAllStandardError()));
|
||||
}
|
||||
|
||||
void IosToolHandlerPrivate::killProcess()
|
||||
{
|
||||
if (isRunning())
|
||||
@@ -973,3 +1039,5 @@ bool IosToolHandler::isRunning()
|
||||
}
|
||||
|
||||
} // namespace Ios
|
||||
|
||||
#include "iostoolhandler.moc"
|
||||
|
||||
@@ -53,11 +53,10 @@ Q_LOGGING_CATEGORY(simulatorLog, "qtc.ios.simulator")
|
||||
namespace Ios {
|
||||
namespace Internal {
|
||||
|
||||
static int COMMAND_TIMEOUT = 10000;
|
||||
static int SIMULATOR_START_TIMEOUT = 60000;
|
||||
static QString SIM_UDID_TAG = QStringLiteral("SimUdid");
|
||||
|
||||
static bool checkForTimeout(const chrono::high_resolution_clock::time_point &start, int msecs = COMMAND_TIMEOUT)
|
||||
static bool checkForTimeout(const chrono::high_resolution_clock::time_point &start, int msecs = 10000)
|
||||
{
|
||||
bool timedOut = false;
|
||||
auto end = chrono::high_resolution_clock::now();
|
||||
@@ -108,7 +107,8 @@ private:
|
||||
const Utils::FileName &bundlePath);
|
||||
void launchApp(QFutureInterface<SimulatorControl::ResponseData> &fi, const QString &simUdid,
|
||||
const QString &bundleIdentifier, bool waitForDebugger,
|
||||
const QStringList &extraArgs);
|
||||
const QStringList &extraArgs, const QString &stdoutPath,
|
||||
const QString &stderrPath);
|
||||
|
||||
static QList<IosDeviceType> availableDevices;
|
||||
friend class SimulatorControl;
|
||||
@@ -196,10 +196,11 @@ SimulatorControl::installApp(const QString &simUdid, const Utils::FileName &bund
|
||||
|
||||
QFuture<SimulatorControl::ResponseData>
|
||||
SimulatorControl::launchApp(const QString &simUdid, const QString &bundleIdentifier,
|
||||
bool waitForDebugger, const QStringList &extraArgs) const
|
||||
bool waitForDebugger, const QStringList &extraArgs,
|
||||
const QString &stdoutPath, const QString &stderrPath) const
|
||||
{
|
||||
return Utils::runAsync(&SimulatorControlPrivate::launchApp, d, simUdid, bundleIdentifier,
|
||||
waitForDebugger, extraArgs);
|
||||
waitForDebugger, extraArgs, stdoutPath, stderrPath);
|
||||
}
|
||||
|
||||
QList<IosDeviceType> SimulatorControlPrivate::availableDevices;
|
||||
@@ -342,12 +343,20 @@ void SimulatorControlPrivate::installApp(QFutureInterface<SimulatorControl::Resp
|
||||
|
||||
void SimulatorControlPrivate::launchApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
|
||||
const QString &simUdid, const QString &bundleIdentifier,
|
||||
bool waitForDebugger, const QStringList &extraArgs)
|
||||
bool waitForDebugger, const QStringList &extraArgs,
|
||||
const QString &stdoutPath, const QString &stderrPath)
|
||||
{
|
||||
SimulatorControl::ResponseData response(simUdid);
|
||||
if (!bundleIdentifier.isEmpty() && !fi.isCanceled()) {
|
||||
QStringList args({QStringLiteral("launch"), simUdid, bundleIdentifier});
|
||||
|
||||
// simctl usage documentation : Note: Log output is often directed to stderr, not stdout.
|
||||
if (!stdoutPath.isEmpty())
|
||||
args.insert(1, QStringLiteral("--stderr=%1").arg(stdoutPath));
|
||||
|
||||
if (!stderrPath.isEmpty())
|
||||
args.insert(1, QStringLiteral("--stdout=%1").arg(stderrPath));
|
||||
|
||||
if (waitForDebugger)
|
||||
args.insert(1, QStringLiteral("-w"));
|
||||
|
||||
|
||||
@@ -69,7 +69,9 @@ public:
|
||||
QFuture<ResponseData> startSimulator(const QString &simUdid) const;
|
||||
QFuture<ResponseData> installApp(const QString &simUdid, const Utils::FileName &bundlePath) const;
|
||||
QFuture<ResponseData> launchApp(const QString &simUdid, const QString &bundleIdentifier,
|
||||
bool waitForDebugger, const QStringList &extraArgs) const;
|
||||
bool waitForDebugger, const QStringList &extraArgs,
|
||||
const QString& stdoutPath = QString(),
|
||||
const QString& stderrPath = QString()) const;
|
||||
|
||||
private:
|
||||
SimulatorControlPrivate *d;
|
||||
|
||||
Reference in New Issue
Block a user