forked from qt-creator/qt-creator
iOS: Replaces ios_sim tool with simctl
Task-number: QTCREATORBUG-16947 Change-Id: Ia28d5e4f9f220d566bd64da73989e8c24ef3eb37 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
@@ -33,7 +33,8 @@ HEADERS += \
|
||||
iosdeploystep.h \
|
||||
iosdeploystepfactory.h \
|
||||
iosdeploystepwidget.h \
|
||||
iosanalyzesupport.h
|
||||
iosanalyzesupport.h \
|
||||
simulatorcontrol.h
|
||||
|
||||
|
||||
SOURCES += \
|
||||
@@ -61,7 +62,8 @@ SOURCES += \
|
||||
iosdeploystep.cpp \
|
||||
iosdeploystepfactory.cpp \
|
||||
iosdeploystepwidget.cpp \
|
||||
iosanalyzesupport.cpp
|
||||
iosanalyzesupport.cpp \
|
||||
simulatorcontrol.cpp
|
||||
|
||||
FORMS += \
|
||||
iossettingswidget.ui \
|
||||
|
@@ -69,6 +69,8 @@ QtcPlugin {
|
||||
"iossimulatorfactory.cpp",
|
||||
"iossimulatorfactory.h",
|
||||
"iostoolhandler.cpp",
|
||||
"iostoolhandler.h"
|
||||
"iostoolhandler.h",
|
||||
"simulatorcontrol.cpp",
|
||||
"simulatorcontrol.h"
|
||||
]
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "iosconstants.h"
|
||||
#include "iosdevice.h"
|
||||
#include "iossimulator.h"
|
||||
#include "simulatorcontrol.h"
|
||||
#include "iosprobe.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
@@ -333,7 +334,7 @@ void IosConfigurations::updateSimulators()
|
||||
dev = IDevice::ConstPtr(new IosSimulator(devId));
|
||||
devManager->addDevice(dev);
|
||||
}
|
||||
IosSimulator::updateAvailableDevices();
|
||||
SimulatorControl::updateAvailableSimulators();
|
||||
}
|
||||
|
||||
void IosConfigurations::setDeveloperPath(const FileName &devPath)
|
||||
|
@@ -169,6 +169,8 @@ IosDebugSupport::IosDebugSupport(IosRunConfiguration *runConfig,
|
||||
m_runner, &IosRunner::start);
|
||||
connect(m_runControl, &RunControl::finished,
|
||||
m_runner, &IosRunner::stop);
|
||||
connect(m_runControl, &DebuggerRunControl::stateChanged,
|
||||
m_runner, &IosRunner::debuggerStateChanged);
|
||||
|
||||
connect(m_runner, &IosRunner::gotServerPorts,
|
||||
this, &IosDebugSupport::handleServerPorts);
|
||||
|
@@ -101,7 +101,12 @@ bool IosDeployStep::init(QList<const BuildStep *> &earlierSteps)
|
||||
this->target()->activeRunConfiguration());
|
||||
QTC_ASSERT(runConfig, return false);
|
||||
m_bundlePath = runConfig->bundleDirectory().toString();
|
||||
if (m_device.isNull()) {
|
||||
|
||||
if (iosdevice()) {
|
||||
m_deviceType = IosDeviceType(IosDeviceType::IosDevice, deviceId());
|
||||
} else if (iossimulator()) {
|
||||
m_deviceType = runConfig->deviceType();
|
||||
} else {
|
||||
emit addOutput(tr("Error: no device available, deploy failed."),
|
||||
BuildStep::ErrorMessageOutput);
|
||||
return false;
|
||||
@@ -113,17 +118,15 @@ void IosDeployStep::run(QFutureInterface<bool> &fi)
|
||||
{
|
||||
m_futureInterface = fi;
|
||||
QTC_CHECK(m_transferStatus == NoTransfer);
|
||||
if (iosdevice().isNull()) {
|
||||
if (iossimulator().isNull())
|
||||
if (device().isNull()) {
|
||||
TaskHub::addTask(Task::Error, tr("Deployment failed. No iOS device found."),
|
||||
ProjectExplorer::Constants::TASK_CATEGORY_DEPLOYMENT);
|
||||
reportRunResult(m_futureInterface, !iossimulator().isNull());
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
m_toolHandler = new IosToolHandler(m_deviceType, this);
|
||||
m_transferStatus = TransferInProgress;
|
||||
QTC_CHECK(m_toolHandler == 0);
|
||||
m_toolHandler = new IosToolHandler(IosDeviceType(IosDeviceType::IosDevice), this);
|
||||
m_futureInterface.setProgressRange(0, 200);
|
||||
m_futureInterface.setProgressValueAndText(0, QLatin1String("Transferring application"));
|
||||
m_futureInterface.reportStarted();
|
||||
@@ -136,7 +139,7 @@ void IosDeployStep::run(QFutureInterface<bool> &fi)
|
||||
connect(m_toolHandler, &IosToolHandler::errorMsg,
|
||||
this, &IosDeployStep::handleErrorMsg);
|
||||
checkProvisioningProfile();
|
||||
m_toolHandler->requestTransferApp(appBundle(), deviceId());
|
||||
m_toolHandler->requestTransferApp(appBundle(), m_deviceType.identifier);
|
||||
}
|
||||
|
||||
void IosDeployStep::cancel()
|
||||
@@ -150,7 +153,7 @@ void IosDeployStep::cleanup()
|
||||
QTC_CHECK(m_transferStatus != TransferInProgress);
|
||||
m_transferStatus = NoTransfer;
|
||||
m_device.clear();
|
||||
m_toolHandler = 0;
|
||||
m_toolHandler = nullptr;
|
||||
m_expectFail = false;
|
||||
}
|
||||
|
||||
|
@@ -101,6 +101,7 @@ private:
|
||||
QFutureInterface<bool> m_futureInterface;
|
||||
ProjectExplorer::IDevice::ConstPtr m_device;
|
||||
QString m_bundlePath;
|
||||
IosDeviceType m_deviceType;
|
||||
static const Core::Id Id;
|
||||
bool m_expectFail;
|
||||
};
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "iosconstants.h"
|
||||
#include "iosmanager.h"
|
||||
#include "iosdeploystep.h"
|
||||
#include "simulatorcontrol.h"
|
||||
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/target.h>
|
||||
@@ -346,7 +347,7 @@ IosDeviceType IosRunConfiguration::deviceType() const
|
||||
{
|
||||
QList<IosDeviceType> availableSimulators;
|
||||
if (m_deviceType.type == IosDeviceType::SimulatedDevice)
|
||||
availableSimulators = IosSimulator::availableDevices();
|
||||
availableSimulators = SimulatorControl::availableSimulators();
|
||||
if (!availableSimulators.isEmpty()) {
|
||||
QList<IosDeviceType> elegibleDevices;
|
||||
QString devname = m_deviceType.identifier.split(QLatin1Char(',')).value(0);
|
||||
@@ -417,7 +418,7 @@ void IosRunConfigurationWidget::updateValues()
|
||||
m_deviceTypeLabel->setVisible(showDeviceSelector);
|
||||
m_deviceTypeComboBox->setVisible(showDeviceSelector);
|
||||
if (showDeviceSelector && m_deviceTypeModel.rowCount() == 0) {
|
||||
foreach (const IosDeviceType &dType, IosSimulator::availableDevices()) {
|
||||
foreach (const IosDeviceType &dType, SimulatorControl::availableSimulators()) {
|
||||
QStandardItem *item = new QStandardItem(dType.displayName);
|
||||
QVariant v;
|
||||
v.setValue(dType);
|
||||
|
@@ -168,6 +168,12 @@ void IosRunner::stop()
|
||||
}
|
||||
}
|
||||
|
||||
void IosRunner::debuggerStateChanged(Debugger::DebuggerState state)
|
||||
{
|
||||
if (m_toolHandler)
|
||||
m_toolHandler->debuggerStateChanged(state);
|
||||
}
|
||||
|
||||
void IosRunner::handleDidStartApp(IosToolHandler *handler, const QString &bundlePath,
|
||||
const QString &deviceId, IosToolHandler::OpStatus status)
|
||||
{
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "iostoolhandler.h"
|
||||
#include "iossimulator.h"
|
||||
|
||||
#include <debugger/debuggerconstants.h>
|
||||
#include <projectexplorer/devicesupport/idevice.h>
|
||||
#include <qmldebug/qmldebugcommandlinearguments.h>
|
||||
|
||||
@@ -64,6 +65,9 @@ public:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
public slots:
|
||||
void debuggerStateChanged(Debugger::DebuggerState state);
|
||||
|
||||
signals:
|
||||
void didStartApp(Ios::IosToolHandler::OpStatus status);
|
||||
void gotServerPorts(Utils::Port gdbPort, Utils::Port qmlPort);
|
||||
|
@@ -44,9 +44,6 @@ static const QLatin1String iosDeviceTypeDisplayNameKey = QLatin1String("displayN
|
||||
static const QLatin1String iosDeviceTypeTypeKey = QLatin1String("type");
|
||||
static const QLatin1String iosDeviceTypeIdentifierKey = QLatin1String("identifier");
|
||||
|
||||
QMutex IosSimulator::_mutex;
|
||||
QList<IosDeviceType> IosSimulator::_availableDevices;
|
||||
|
||||
IosSimulator::IosSimulator(Core::Id id)
|
||||
: IDevice(Core::Id(Constants::IOS_SIMULATOR_TYPE),
|
||||
IDevice::AutoDetected,
|
||||
@@ -119,48 +116,6 @@ IDevice::Ptr IosSimulator::clone() const
|
||||
return IDevice::Ptr(new IosSimulator(*this));
|
||||
}
|
||||
|
||||
QList<IosDeviceType> IosSimulator::availableDevices()
|
||||
{
|
||||
QMutexLocker l(&_mutex);
|
||||
return _availableDevices;
|
||||
}
|
||||
|
||||
void IosSimulator::setAvailableDevices(QList<IosDeviceType> value)
|
||||
{
|
||||
QMutexLocker l(&_mutex);
|
||||
_availableDevices = value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void handleDeviceInfo(Ios::IosToolHandler *handler, const QString &deviceId,
|
||||
const Ios::IosToolHandler::Dict &info)
|
||||
{
|
||||
Q_UNUSED(deviceId);
|
||||
QList<IosDeviceType> res;
|
||||
QMapIterator<QString, QString> i(info);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
IosDeviceType simulatorType(IosDeviceType::SimulatedDevice);
|
||||
simulatorType.displayName = i.value();
|
||||
simulatorType.identifier = i.key();
|
||||
QStringList ids = i.key().split(QLatin1Char(','));
|
||||
if (ids.length() > 1)
|
||||
simulatorType.displayName += QLatin1String(", iOS ") + ids.last().trimmed();
|
||||
res.append(simulatorType);
|
||||
}
|
||||
handler->deleteLater();
|
||||
std::stable_sort(res.begin(), res.end());
|
||||
IosSimulator::setAvailableDevices(res);
|
||||
}
|
||||
}
|
||||
|
||||
void IosSimulator::updateAvailableDevices()
|
||||
{
|
||||
IosToolHandler *toolHandler = new IosToolHandler(IosDeviceType(IosDeviceType::SimulatedDevice));
|
||||
QObject::connect(toolHandler, &IosToolHandler::deviceInfo, &handleDeviceInfo);
|
||||
toolHandler->requestDeviceInfo(QString());
|
||||
}
|
||||
|
||||
void IosSimulator::fromMap(const QVariantMap &map)
|
||||
{
|
||||
IDevice::fromMap(map);
|
||||
|
@@ -67,10 +67,6 @@ public:
|
||||
typedef QSharedPointer<IosSimulator> Ptr;
|
||||
ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override;
|
||||
|
||||
static QList<IosDeviceType> availableDevices();
|
||||
static void setAvailableDevices(QList<IosDeviceType> value);
|
||||
static void updateAvailableDevices();
|
||||
|
||||
QString displayType() const override;
|
||||
ProjectExplorer::IDeviceWidget *createWidget() override;
|
||||
QList<Core::Id> actionIds() const override;
|
||||
@@ -91,8 +87,6 @@ protected:
|
||||
IosSimulator(const IosSimulator &other);
|
||||
private:
|
||||
mutable quint16 m_lastPort;
|
||||
static QMutex _mutex;
|
||||
static QList<IosDeviceType> _availableDevices;
|
||||
};
|
||||
|
||||
namespace IosKitInformation {
|
||||
|
@@ -27,13 +27,18 @@
|
||||
#include "iosconfigurations.h"
|
||||
#include "iosconstants.h"
|
||||
#include "iossimulator.h"
|
||||
#include "simulatorcontrol.h"
|
||||
|
||||
#include "debugger/debuggerconstants.h"
|
||||
#include <coreplugin/icore.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QLoggingCategory>
|
||||
#include <QProcess>
|
||||
@@ -52,6 +57,8 @@ namespace Ios {
|
||||
|
||||
namespace Internal {
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
struct ParserState {
|
||||
enum Kind {
|
||||
Msg,
|
||||
@@ -132,7 +139,8 @@ public:
|
||||
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000) = 0;
|
||||
bool isRunning();
|
||||
void start(const QString &exe, const QStringList &args);
|
||||
void stop(int errorCode);
|
||||
virtual void stop(int errorCode) = 0;
|
||||
virtual void debuggerStateChanged(Debugger::DebuggerState state) { Q_UNUSED(state); }
|
||||
|
||||
// signals
|
||||
void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress,
|
||||
@@ -148,15 +156,12 @@ public:
|
||||
void appOutput(const QString &output);
|
||||
void errorMsg(const QString &msg);
|
||||
void toolExited(int code);
|
||||
// slots
|
||||
void subprocessError(QProcess::ProcessError error);
|
||||
void subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void subprocessHasData();
|
||||
void killProcess();
|
||||
virtual bool expectsFileDescriptor() = 0;
|
||||
protected:
|
||||
void processXml();
|
||||
|
||||
protected:
|
||||
void killProcess();
|
||||
|
||||
|
||||
protected:
|
||||
IosToolHandler *q;
|
||||
QProcess *process;
|
||||
QTimer killTimer;
|
||||
@@ -176,34 +181,56 @@ class IosDeviceToolHandlerPrivate : public IosToolHandlerPrivate
|
||||
{
|
||||
public:
|
||||
explicit IosDeviceToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
|
||||
virtual void requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
||||
int timeout = 1000);
|
||||
virtual void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
||||
|
||||
// IosToolHandlerPrivate overrides
|
||||
public:
|
||||
void requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
||||
int timeout = 1000) override;
|
||||
void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
||||
IosToolHandler::RunKind runKind,
|
||||
const QString &deviceId, int timeout = 1000);
|
||||
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
||||
virtual bool expectsFileDescriptor();
|
||||
const QString &deviceId, int timeout = 1000) override;
|
||||
void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override;
|
||||
void stop(int errorCode) override;
|
||||
|
||||
private:
|
||||
void subprocessError(QProcess::ProcessError error);
|
||||
void subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void subprocessHasData();
|
||||
void processXml();
|
||||
};
|
||||
|
||||
class IosSimulatorToolHandlerPrivate : public IosToolHandlerPrivate
|
||||
{
|
||||
public:
|
||||
explicit IosSimulatorToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
|
||||
virtual void requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
||||
int timeout = 1000);
|
||||
virtual void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
||||
|
||||
// IosToolHandlerPrivate overrides
|
||||
public:
|
||||
void requestTransferApp(const QString &bundlePath, const QString &deviceIdentifier,
|
||||
int timeout = 1000) override;
|
||||
void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
||||
IosToolHandler::RunKind runKind,
|
||||
const QString &deviceId, int timeout = 1000);
|
||||
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
||||
virtual bool expectsFileDescriptor();
|
||||
const QString &deviceIdentifier, int timeout = 1000) override;
|
||||
void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override;
|
||||
void stop(int errorCode) override;
|
||||
void debuggerStateChanged(Debugger::DebuggerState state) override;
|
||||
|
||||
private:
|
||||
void addDeviceArguments(QStringList &args) const;
|
||||
void simAppProcessError(QProcess::ProcessError error);
|
||||
void simAppProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void simAppProcessHasData();
|
||||
void simAppProcessHasErrorOutput();
|
||||
void launchAppOnSimulator();
|
||||
|
||||
private:
|
||||
qint64 appPId = -1;
|
||||
bool appLaunched = false;
|
||||
};
|
||||
|
||||
IosToolHandlerPrivate::IosToolHandlerPrivate(const IosDeviceType &devType,
|
||||
Ios::IosToolHandler *q) :
|
||||
q(q),
|
||||
process(new QProcess),
|
||||
process(nullptr),
|
||||
state(NonStarted),
|
||||
devType(devType),
|
||||
iBegin(0),
|
||||
@@ -211,36 +238,6 @@ IosToolHandlerPrivate::IosToolHandlerPrivate(const IosDeviceType &devType,
|
||||
gdbSocket(-1)
|
||||
{
|
||||
killTimer.setSingleShot(true);
|
||||
QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
|
||||
foreach (const QString &k, env.keys())
|
||||
if (k.startsWith(QLatin1String("DYLD_")))
|
||||
env.remove(k);
|
||||
QStringList frameworkPaths;
|
||||
Utils::FileName xcPath = IosConfigurations::developerPath();
|
||||
QString privateFPath = xcPath.appendPath(QLatin1String("Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks")).toFileInfo().canonicalFilePath();
|
||||
if (!privateFPath.isEmpty())
|
||||
frameworkPaths << privateFPath;
|
||||
QString otherFPath = xcPath.appendPath(QLatin1String("../OtherFrameworks")).toFileInfo().canonicalFilePath();
|
||||
if (!otherFPath.isEmpty())
|
||||
frameworkPaths << otherFPath;
|
||||
QString sharedFPath = xcPath.appendPath(QLatin1String("../SharedFrameworks")).toFileInfo().canonicalFilePath();
|
||||
if (!sharedFPath.isEmpty())
|
||||
frameworkPaths << sharedFPath;
|
||||
frameworkPaths << QLatin1String("/System/Library/Frameworks")
|
||||
<< QLatin1String("/System/Library/PrivateFrameworks");
|
||||
env.insert(QLatin1String("DYLD_FALLBACK_FRAMEWORK_PATH"), frameworkPaths.join(QLatin1Char(':')));
|
||||
qCDebug(toolHandlerLog) << "IosToolHandler runEnv:" << env.toStringList();
|
||||
process->setProcessEnvironment(env);
|
||||
QObject::connect(process, &QProcess::readyReadStandardOutput,
|
||||
q, &IosToolHandler::subprocessHasData);
|
||||
QObject::connect(process,
|
||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
q, &IosToolHandler::subprocessFinished);
|
||||
QObject::connect(process,
|
||||
static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
|
||||
q, &IosToolHandler::subprocessError);
|
||||
QObject::connect(&killTimer, &QTimer::timeout,
|
||||
q, &IosToolHandler::killProcess);
|
||||
}
|
||||
|
||||
IosToolHandlerPrivate::~IosToolHandlerPrivate()
|
||||
@@ -260,6 +257,7 @@ bool IosToolHandlerPrivate::isRunning()
|
||||
|
||||
void IosToolHandlerPrivate::start(const QString &exe, const QStringList &args)
|
||||
{
|
||||
Q_ASSERT(process);
|
||||
QTC_CHECK(state == NonStarted);
|
||||
state = Starting;
|
||||
qCDebug(toolHandlerLog) << "running " << exe << args;
|
||||
@@ -267,44 +265,6 @@ void IosToolHandlerPrivate::start(const QString &exe, const QStringList &args)
|
||||
state = StartedInferior;
|
||||
}
|
||||
|
||||
void IosToolHandlerPrivate::stop(int errorCode)
|
||||
{
|
||||
qCDebug(toolHandlerLog) << "IosToolHandlerPrivate::stop";
|
||||
State oldState = state;
|
||||
state = Stopped;
|
||||
switch (oldState) {
|
||||
case NonStarted:
|
||||
qCWarning(toolHandlerLog) << "IosToolHandler::stop() when state was NonStarted";
|
||||
// pass
|
||||
case Starting:
|
||||
switch (op){
|
||||
case OpNone:
|
||||
qCWarning(toolHandlerLog) << "IosToolHandler::stop() when op was OpNone";
|
||||
break;
|
||||
case OpAppTransfer:
|
||||
didTransferApp(bundlePath, deviceId, IosToolHandler::Failure);
|
||||
break;
|
||||
case OpAppRun:
|
||||
didStartApp(bundlePath, deviceId, IosToolHandler::Failure);
|
||||
break;
|
||||
case OpDeviceInfo:
|
||||
break;
|
||||
}
|
||||
// pass
|
||||
case StartedInferior:
|
||||
case XmlEndProcessed:
|
||||
toolExited(errorCode);
|
||||
break;
|
||||
case Stopped:
|
||||
return;
|
||||
}
|
||||
if (isRunning()) {
|
||||
process->write("k\n\r");
|
||||
process->closeWriteChannel();
|
||||
killTimer.start(1500);
|
||||
}
|
||||
}
|
||||
|
||||
// signals
|
||||
void IosToolHandlerPrivate::isTransferringApp(const QString &bundlePath, const QString &deviceId,
|
||||
int progress, int maxProgress, const QString &info)
|
||||
@@ -357,7 +317,7 @@ void IosToolHandlerPrivate::toolExited(int code)
|
||||
emit q->toolExited(q, code);
|
||||
}
|
||||
|
||||
void IosToolHandlerPrivate::subprocessError(QProcess::ProcessError error)
|
||||
void IosDeviceToolHandlerPrivate::subprocessError(QProcess::ProcessError error)
|
||||
{
|
||||
if (state != Stopped)
|
||||
errorMsg(IosToolHandler::tr("iOS tool Error %1").arg(error));
|
||||
@@ -368,7 +328,7 @@ void IosToolHandlerPrivate::subprocessError(QProcess::ProcessError error)
|
||||
}
|
||||
}
|
||||
|
||||
void IosToolHandlerPrivate::subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
void IosDeviceToolHandlerPrivate::subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
stop((exitStatus == QProcess::NormalExit) ? exitCode : -1 );
|
||||
qCDebug(toolHandlerLog) << "IosToolHandler::finished(" << this << ")";
|
||||
@@ -376,7 +336,7 @@ void IosToolHandlerPrivate::subprocessFinished(int exitCode, QProcess::ExitStatu
|
||||
emit q->finished(q);
|
||||
}
|
||||
|
||||
void IosToolHandlerPrivate::processXml()
|
||||
void IosDeviceToolHandlerPrivate::processXml()
|
||||
{
|
||||
while (!outputParser.atEnd()) {
|
||||
QXmlStreamReader::TokenType tt = outputParser.readNext();
|
||||
@@ -558,7 +518,7 @@ void IosToolHandlerPrivate::processXml()
|
||||
}
|
||||
}
|
||||
|
||||
void IosToolHandlerPrivate::subprocessHasData()
|
||||
void IosDeviceToolHandlerPrivate::subprocessHasData()
|
||||
{
|
||||
qCDebug(toolHandlerLog) << "subprocessHasData, state:" << state;
|
||||
while (true) {
|
||||
@@ -598,7 +558,42 @@ void IosToolHandlerPrivate::subprocessHasData()
|
||||
IosDeviceToolHandlerPrivate::IosDeviceToolHandlerPrivate(const IosDeviceType &devType,
|
||||
IosToolHandler *q)
|
||||
: IosToolHandlerPrivate(devType, q)
|
||||
{ }
|
||||
{
|
||||
process = new QProcess;
|
||||
|
||||
// Prepare & set process Environment.
|
||||
QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
|
||||
foreach (const QString &k, env.keys())
|
||||
if (k.startsWith(QLatin1String("DYLD_")))
|
||||
env.remove(k);
|
||||
QStringList frameworkPaths;
|
||||
Utils::FileName xcPath = IosConfigurations::developerPath();
|
||||
QString privateFPath = xcPath.appendPath(QLatin1String("Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks")).toFileInfo().canonicalFilePath();
|
||||
if (!privateFPath.isEmpty())
|
||||
frameworkPaths << privateFPath;
|
||||
QString otherFPath = xcPath.appendPath(QLatin1String("../OtherFrameworks")).toFileInfo().canonicalFilePath();
|
||||
if (!otherFPath.isEmpty())
|
||||
frameworkPaths << otherFPath;
|
||||
QString sharedFPath = xcPath.appendPath(QLatin1String("../SharedFrameworks")).toFileInfo().canonicalFilePath();
|
||||
if (!sharedFPath.isEmpty())
|
||||
frameworkPaths << sharedFPath;
|
||||
frameworkPaths << QLatin1String("/System/Library/Frameworks")
|
||||
<< QLatin1String("/System/Library/PrivateFrameworks");
|
||||
env.insert(QLatin1String("DYLD_FALLBACK_FRAMEWORK_PATH"), frameworkPaths.join(QLatin1Char(':')));
|
||||
qCDebug(toolHandlerLog) << "IosToolHandler runEnv:" << env.toStringList();
|
||||
process->setProcessEnvironment(env);
|
||||
|
||||
QObject::connect(process, &QProcess::readyReadStandardOutput,
|
||||
std::bind(&IosDeviceToolHandlerPrivate::subprocessHasData,this));
|
||||
|
||||
QObject::connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
std::bind(&IosDeviceToolHandlerPrivate::subprocessFinished,this, _1,_2));
|
||||
|
||||
QObject::connect(process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
|
||||
std::bind(&IosDeviceToolHandlerPrivate::subprocessError, this, _1));
|
||||
|
||||
QObject::connect(&killTimer, &QTimer::timeout, std::bind(&IosDeviceToolHandlerPrivate::killProcess, this));
|
||||
}
|
||||
|
||||
void IosDeviceToolHandlerPrivate::requestTransferApp(const QString &bundlePath,
|
||||
const QString &deviceId, int timeout)
|
||||
@@ -646,10 +641,45 @@ void IosDeviceToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int
|
||||
start(IosToolHandler::iosDeviceToolPath(), args);
|
||||
}
|
||||
|
||||
bool IosDeviceToolHandlerPrivate::expectsFileDescriptor()
|
||||
|
||||
void IosDeviceToolHandlerPrivate::stop(int errorCode)
|
||||
{
|
||||
return op == OpAppRun && runKind == IosToolHandler::DebugRun;
|
||||
qCDebug(toolHandlerLog) << "IosToolHandlerPrivate::stop";
|
||||
State oldState = state;
|
||||
state = Stopped;
|
||||
switch (oldState) {
|
||||
case NonStarted:
|
||||
qCWarning(toolHandlerLog) << "IosToolHandler::stop() when state was NonStarted";
|
||||
// pass
|
||||
case Starting:
|
||||
switch (op){
|
||||
case OpNone:
|
||||
qCWarning(toolHandlerLog) << "IosToolHandler::stop() when op was OpNone";
|
||||
break;
|
||||
case OpAppTransfer:
|
||||
didTransferApp(bundlePath, deviceId, IosToolHandler::Failure);
|
||||
break;
|
||||
case OpAppRun:
|
||||
didStartApp(bundlePath, deviceId, IosToolHandler::Failure);
|
||||
break;
|
||||
case OpDeviceInfo:
|
||||
break;
|
||||
}
|
||||
// pass
|
||||
case StartedInferior:
|
||||
case XmlEndProcessed:
|
||||
toolExited(errorCode);
|
||||
break;
|
||||
case Stopped:
|
||||
return;
|
||||
}
|
||||
if (isRunning()) {
|
||||
process->write("k\n\r");
|
||||
process->closeWriteChannel();
|
||||
killTimer.start(1500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IosSimulatorToolHandlerPrivate
|
||||
|
||||
@@ -659,64 +689,159 @@ IosSimulatorToolHandlerPrivate::IosSimulatorToolHandlerPrivate(const IosDeviceTy
|
||||
{ }
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::requestTransferApp(const QString &bundlePath,
|
||||
const QString &deviceId, int timeout)
|
||||
const QString &deviceIdentifier, int timeout)
|
||||
{
|
||||
Q_UNUSED(timeout);
|
||||
this->bundlePath = bundlePath;
|
||||
this->deviceId = deviceId;
|
||||
emit didTransferApp(bundlePath, deviceId, IosToolHandler::Success);
|
||||
this->deviceId = deviceIdentifier;
|
||||
isTransferringApp(bundlePath, deviceId, 0, 100, "");
|
||||
if (SimulatorControl::startSimulator(deviceId)) {
|
||||
isTransferringApp(bundlePath, deviceId, 20, 100, "");
|
||||
QByteArray cmdOutput;
|
||||
if (SimulatorControl::installApp(deviceId, Utils::FileName::fromString(bundlePath), cmdOutput)) {
|
||||
isTransferringApp(bundlePath, deviceId, 100, 100, "");
|
||||
didTransferApp(bundlePath, deviceId, IosToolHandler::Success);
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Application install on Simulator failed. %1").arg(QString::fromLocal8Bit(cmdOutput)));
|
||||
didTransferApp(bundlePath, deviceId, IosToolHandler::Failure);
|
||||
}
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Application install on Simulator failed. Simulator not running."));
|
||||
didTransferApp(bundlePath, deviceId, IosToolHandler::Failure);
|
||||
}
|
||||
emit q->finished(q);
|
||||
}
|
||||
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &bundlePath,
|
||||
const QStringList &extraArgs,
|
||||
IosToolHandler::RunKind runType,
|
||||
const QString &deviceId, int timeout)
|
||||
const QString &deviceIdentifier, int timeout)
|
||||
{
|
||||
Q_UNUSED(timeout);
|
||||
Q_UNUSED(deviceIdentifier);
|
||||
this->bundlePath = bundlePath;
|
||||
this->deviceId = deviceId;
|
||||
this->deviceId = devType.identifier;
|
||||
this->runKind = runType;
|
||||
QStringList args;
|
||||
|
||||
args << QLatin1String("launch") << bundlePath;
|
||||
Utils::FileName devPath = IosConfigurations::developerPath();
|
||||
if (!devPath.isEmpty())
|
||||
args << QLatin1String("--developer-path") << devPath.toString();
|
||||
addDeviceArguments(args);
|
||||
switch (runType) {
|
||||
case IosToolHandler::NormalRun:
|
||||
break;
|
||||
case IosToolHandler::DebugRun:
|
||||
args << QLatin1String("--wait-for-debugger");
|
||||
break;
|
||||
}
|
||||
args << QLatin1String("--args") << extraArgs;
|
||||
op = OpAppRun;
|
||||
start(IosToolHandler::iosSimulatorToolPath(), args);
|
||||
|
||||
Utils::FileName appBundle = Utils::FileName::fromString(bundlePath);
|
||||
if (!appBundle.exists()) {
|
||||
errorMsg(IosToolHandler::tr("Application launch on Simulator failed. Invalid Bundle path %1")
|
||||
.arg(bundlePath));
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SimulatorControl::startSimulator(deviceId)) {
|
||||
qint64 pId = -1;
|
||||
bool debugRun = runType == IosToolHandler::DebugRun;
|
||||
QProcess* controlProcess = SimulatorControl::spawnAppProcess(deviceId, appBundle, pId, debugRun, extraArgs);
|
||||
if (controlProcess) {
|
||||
Q_ASSERT(!process || !isRunning());
|
||||
if (process) {
|
||||
delete process;
|
||||
process = nullptr;
|
||||
}
|
||||
process = controlProcess;
|
||||
QObject::connect(process, &QProcess::readyReadStandardOutput,
|
||||
std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessHasData,this));
|
||||
QObject::connect(process, &QProcess::readyReadStandardError,
|
||||
std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessHasErrorOutput,this));
|
||||
QObject::connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessFinished,this, _1,_2));
|
||||
QObject::connect(process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
|
||||
std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessError, this, _1));
|
||||
|
||||
appPId = pId;
|
||||
gotInferiorPid(bundlePath,deviceId,pId);
|
||||
|
||||
// For debug run, wait for the debugger to attach and then launch the app.
|
||||
if (!debugRun) {
|
||||
launchAppOnSimulator();
|
||||
}
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Spawning the Application process on Simulator failed."));
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure);
|
||||
}
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Application launch on Simulator failed. Simulator not running.")
|
||||
.arg(bundlePath));
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure);
|
||||
}
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::launchAppOnSimulator()
|
||||
{
|
||||
// Wait for the app to reach a state when we can launch it on the simulator.
|
||||
if (appPId != -1 && SimulatorControl::waitForProcessSpawn(appPId)) {
|
||||
QByteArray commandOutput;
|
||||
Utils::FileName appBundle = Utils::FileName::fromString(bundlePath);
|
||||
if (SimulatorControl::launchApp(deviceId, SimulatorControl::bundleIdentifier(appBundle), &commandOutput) != -1) {
|
||||
appLaunched = true;
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Success);
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Application launch on Simulator failed. %1")
|
||||
.arg(QString::fromLocal8Bit(commandOutput)));
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure);
|
||||
}
|
||||
} else {
|
||||
errorMsg(IosToolHandler::tr("Spawning the Application process on Simulator failed. Spawning timed out."));
|
||||
didStartApp(bundlePath, deviceId, Ios::IosToolHandler::Failure);
|
||||
}
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int timeout)
|
||||
{
|
||||
Q_UNUSED(timeout);
|
||||
this->deviceId = deviceId;
|
||||
QStringList args;
|
||||
args << QLatin1String("showdevicetypes");
|
||||
op = OpDeviceInfo;
|
||||
start(IosToolHandler::iosSimulatorToolPath(), args);
|
||||
Q_UNUSED(deviceId);
|
||||
}
|
||||
|
||||
bool IosSimulatorToolHandlerPrivate::expectsFileDescriptor()
|
||||
void IosSimulatorToolHandlerPrivate::stop(int errorCode)
|
||||
{
|
||||
return false;
|
||||
if (process) {
|
||||
if (isRunning()) {
|
||||
process->terminate();
|
||||
if (!process->waitForFinished(1000))
|
||||
process->kill();
|
||||
}
|
||||
process->deleteLater();
|
||||
process = nullptr;
|
||||
appPId = -1;
|
||||
appLaunched = false;
|
||||
}
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::addDeviceArguments(QStringList &args) const
|
||||
{
|
||||
if (devType.type != IosDeviceType::SimulatedDevice) {
|
||||
qCWarning(toolHandlerLog) << "IosSimulatorToolHandlerPrivate device type is not SimulatedDevice";
|
||||
return;
|
||||
toolExited(errorCode);
|
||||
}
|
||||
args << QLatin1String("--devicetypeid") << devType.identifier;
|
||||
|
||||
void IosSimulatorToolHandlerPrivate::debuggerStateChanged(Debugger::DebuggerState state)
|
||||
{
|
||||
if (!appLaunched && state == Debugger::DebuggerState::InferiorRunOk) {
|
||||
// Debugger attached. Launch it on the simulator.
|
||||
launchAppOnSimulator();
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -733,18 +858,6 @@ QString IosToolHandler::iosDeviceToolPath()
|
||||
return res;
|
||||
}
|
||||
|
||||
QString IosToolHandler::iosSimulatorToolPath()
|
||||
{
|
||||
Utils::FileName devPath = Internal::IosConfigurations::developerPath();
|
||||
bool version182 = devPath.appendPath(QLatin1String(
|
||||
"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/iPhoneSimulatorRemoteClient.framework"))
|
||||
.exists();
|
||||
QString res = Core::ICore::libexecPath() + QLatin1String("/ios/iossim");
|
||||
if (version182)
|
||||
res = res.append(QLatin1String("_1_8_2"));
|
||||
return res;
|
||||
}
|
||||
|
||||
IosToolHandler::IosToolHandler(const Internal::IosDeviceType &devType, QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
@@ -764,6 +877,11 @@ void IosToolHandler::stop()
|
||||
d->stop(-1);
|
||||
}
|
||||
|
||||
void IosToolHandler::debuggerStateChanged(int state)
|
||||
{
|
||||
d->debuggerStateChanged((Debugger::DebuggerState)state);
|
||||
}
|
||||
|
||||
void IosToolHandler::requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
||||
int timeout)
|
||||
{
|
||||
@@ -786,24 +904,4 @@ bool IosToolHandler::isRunning()
|
||||
return d->isRunning();
|
||||
}
|
||||
|
||||
void IosToolHandler::subprocessError(QProcess::ProcessError error)
|
||||
{
|
||||
d->subprocessError(error);
|
||||
}
|
||||
|
||||
void IosToolHandler::subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||
{
|
||||
d->subprocessFinished(exitCode, exitStatus);
|
||||
}
|
||||
|
||||
void IosToolHandler::subprocessHasData()
|
||||
{
|
||||
d->subprocessHasData();
|
||||
}
|
||||
|
||||
void IosToolHandler::killProcess()
|
||||
{
|
||||
d->killProcess();
|
||||
}
|
||||
|
||||
} // namespace Ios
|
||||
|
@@ -33,7 +33,6 @@
|
||||
#include <QStringList>
|
||||
#include <QProcess>
|
||||
|
||||
|
||||
namespace Ios {
|
||||
namespace Internal {
|
||||
class IosToolHandlerPrivate;
|
||||
@@ -56,7 +55,6 @@ public:
|
||||
};
|
||||
|
||||
static QString iosDeviceToolPath();
|
||||
static QString iosSimulatorToolPath();
|
||||
|
||||
explicit IosToolHandler(const Internal::IosDeviceType &type, QObject *parent = 0);
|
||||
~IosToolHandler();
|
||||
@@ -66,6 +64,7 @@ public:
|
||||
void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
||||
bool isRunning();
|
||||
void stop();
|
||||
void debuggerStateChanged(int state);
|
||||
|
||||
signals:
|
||||
void isTransferringApp(Ios::IosToolHandler *handler, const QString &bundlePath,
|
||||
@@ -85,11 +84,10 @@ signals:
|
||||
void errorMsg(Ios::IosToolHandler *handler, const QString &msg);
|
||||
void toolExited(Ios::IosToolHandler *handler, int code);
|
||||
void finished(Ios::IosToolHandler *handler);
|
||||
private:
|
||||
void subprocessError(QProcess::ProcessError error);
|
||||
void subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void subprocessHasData();
|
||||
|
||||
protected:
|
||||
void killProcess();
|
||||
|
||||
private:
|
||||
friend class Ios::Internal::IosToolHandlerPrivate;
|
||||
Ios::Internal::IosToolHandlerPrivate *d;
|
||||
|
422
src/plugins/ios/simulatorcontrol.cpp
Normal file
422
src/plugins/ios/simulatorcontrol.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 "simulatorcontrol.h"
|
||||
#include "iossimulator.h"
|
||||
#include "iosconfigurations.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QProcess>
|
||||
#include <QReadLocker>
|
||||
#include <QReadWriteLock>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QWriteLocker>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(simulatorLog, "qtc.ios.simulator")
|
||||
}
|
||||
|
||||
namespace Ios {
|
||||
namespace Internal {
|
||||
|
||||
static int COMMAND_TIMEOUT = 10000;
|
||||
static int SIMULATOR_TIMEOUT = 60000;
|
||||
|
||||
static bool checkForTimeout(const std::chrono::time_point< std::chrono::high_resolution_clock, std::chrono::nanoseconds> &start, int msecs = COMMAND_TIMEOUT)
|
||||
{
|
||||
bool timedOut = false;
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() > msecs)
|
||||
timedOut = true;
|
||||
return timedOut;
|
||||
}
|
||||
|
||||
class SimulatorControlPrivate :QObject {
|
||||
Q_OBJECT
|
||||
private:
|
||||
struct SimDeviceInfo {
|
||||
bool isBooted() const { return state.compare(QStringLiteral("Booted")) == 0; }
|
||||
bool isAvailable() const { return !availability.contains(QStringLiteral("unavailable")); }
|
||||
QString name;
|
||||
QString udid;
|
||||
QString availability;
|
||||
QString state;
|
||||
QString sdk;
|
||||
};
|
||||
|
||||
SimulatorControlPrivate(QObject *parent = nullptr);
|
||||
~SimulatorControlPrivate();
|
||||
QByteArray runSimCtlCommand(QStringList args) const;
|
||||
SimDeviceInfo deviceInfo(const QString &simUdid) const;
|
||||
bool runCommand(QString command, const QStringList &args, QByteArray *output = nullptr);
|
||||
|
||||
QHash<QString, QProcess*> simulatorProcesses;
|
||||
QReadWriteLock processDataLock;
|
||||
QList<IosDeviceType> availableDevices;
|
||||
QReadWriteLock deviceDataLock;
|
||||
friend class SimulatorControl;
|
||||
};
|
||||
|
||||
SimulatorControlPrivate *SimulatorControl::d = new SimulatorControlPrivate;
|
||||
|
||||
SimulatorControl::SimulatorControl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QList<Ios::Internal::IosDeviceType> SimulatorControl::availableSimulators()
|
||||
{
|
||||
QReadLocker locer(&d->deviceDataLock);
|
||||
return d->availableDevices;
|
||||
}
|
||||
|
||||
void SimulatorControl::updateAvailableSimulators()
|
||||
{
|
||||
const QByteArray output = d->runSimCtlCommand({QLatin1String("list"), QLatin1String("-j"), QLatin1String("devices")});
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
if (!doc.isNull()) {
|
||||
QList<IosDeviceType> availableDevices;
|
||||
const QJsonObject buildInfo = doc.object().value("devices").toObject();
|
||||
foreach (const QString &buildVersion, buildInfo.keys()) {
|
||||
QJsonArray devices = buildInfo.value(buildVersion).toArray();
|
||||
foreach (const QJsonValue device, devices) {
|
||||
QJsonObject deviceInfo = device.toObject();
|
||||
QString deviceName = QString("%1, %2")
|
||||
.arg(deviceInfo.value("name").toString("Unknown"))
|
||||
.arg(buildVersion);
|
||||
QString deviceUdid = deviceInfo.value("udid").toString("Unknown");
|
||||
if (!deviceInfo.value("availability").toString().contains("unavailable")) {
|
||||
IosDeviceType iOSDevice(IosDeviceType::SimulatedDevice, deviceUdid, deviceName);
|
||||
availableDevices.append(iOSDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::stable_sort(availableDevices.begin(), availableDevices.end());
|
||||
|
||||
{
|
||||
QWriteLocker locker(&d->deviceDataLock);
|
||||
d->availableDevices = availableDevices;
|
||||
}
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Error parsing json output from simctl. Output:" << output;
|
||||
}
|
||||
}
|
||||
|
||||
// Blocks until simulators reaches "Booted" state.
|
||||
bool SimulatorControl::startSimulator(const QString &simUdid)
|
||||
{
|
||||
QWriteLocker locker(&d->processDataLock);
|
||||
bool simulatorRunning = isSimulatorRunning(simUdid);
|
||||
if (!simulatorRunning && d->deviceInfo(simUdid).isAvailable()) {
|
||||
// Simulator is not running but it's available. Start the simulator.
|
||||
QProcess *p = new QProcess;
|
||||
QObject::connect(p, static_cast<void(QProcess::*)(int)>(&QProcess::finished), [simUdid]() {
|
||||
QWriteLocker locker(&d->processDataLock);
|
||||
d->simulatorProcesses[simUdid]->deleteLater();
|
||||
d->simulatorProcesses.remove(simUdid);
|
||||
});
|
||||
|
||||
const QString cmd = IosConfigurations::developerPath().appendPath(QStringLiteral("/Applications/Simulator.app")).toString();
|
||||
const QStringList args({QStringLiteral("--args"), QStringLiteral("-CurrentDeviceUDID"), simUdid});
|
||||
p->start(cmd, args);
|
||||
|
||||
if (p->waitForStarted()) {
|
||||
d->simulatorProcesses[simUdid] = p;
|
||||
// At this point the sim device exists, available and was not running.
|
||||
// So the simulator is started and we'll wait for it to reach to a state
|
||||
// where we can interact with it.
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
SimulatorControlPrivate::SimDeviceInfo info;
|
||||
do {
|
||||
info = d->deviceInfo(simUdid);
|
||||
} while (!info.isBooted()
|
||||
&& p->state() == QProcess::Running
|
||||
&& !checkForTimeout(start, SIMULATOR_TIMEOUT));
|
||||
simulatorRunning = info.isBooted();
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Error starting simulator." << p->errorString();
|
||||
delete p;
|
||||
}
|
||||
}
|
||||
return simulatorRunning;
|
||||
}
|
||||
|
||||
bool SimulatorControl::isSimulatorRunning(const QString &simUdid)
|
||||
{
|
||||
if (simUdid.isEmpty())
|
||||
return false;
|
||||
return d->deviceInfo(simUdid).isBooted();
|
||||
}
|
||||
|
||||
bool SimulatorControl::installApp(const QString &simUdid, const Utils::FileName &bundlePath, QByteArray &commandOutput)
|
||||
{
|
||||
bool installed = false;
|
||||
if (isSimulatorRunning(simUdid)) {
|
||||
commandOutput = d->runSimCtlCommand(QStringList() << QStringLiteral("install") << simUdid << bundlePath.toString());
|
||||
installed = commandOutput.isEmpty();
|
||||
} else {
|
||||
commandOutput = "Simulator device not running.";
|
||||
}
|
||||
return installed;
|
||||
}
|
||||
|
||||
qint64 SimulatorControl::launchApp(const QString &simUdid, const QString &bundleIdentifier, QByteArray* commandOutput)
|
||||
{
|
||||
qint64 pId = -1;
|
||||
pId = -1;
|
||||
if (!bundleIdentifier.isEmpty() && isSimulatorRunning(simUdid)) {
|
||||
const QStringList args({QStringLiteral("launch"), simUdid , bundleIdentifier});
|
||||
const QByteArray output = d->runSimCtlCommand(args);
|
||||
const QByteArray pIdStr = output.trimmed().split(' ').last().trimmed();
|
||||
bool validInt = false;
|
||||
pId = pIdStr.toLongLong(&validInt);
|
||||
if (!validInt) {
|
||||
// Launch Failed.
|
||||
qCDebug(simulatorLog) << "Launch app failed. Process id returned is not valid. PID =" << pIdStr;
|
||||
pId = -1;
|
||||
if (commandOutput)
|
||||
*commandOutput = output;
|
||||
}
|
||||
}
|
||||
return pId;
|
||||
}
|
||||
|
||||
QString SimulatorControl::bundleIdentifier(const Utils::FileName &bundlePath)
|
||||
{
|
||||
QString bundleID;
|
||||
#ifdef Q_OS_MAC
|
||||
if (bundlePath.exists()) {
|
||||
CFStringRef cFBundlePath = bundlePath.toString().toCFString();
|
||||
CFURLRef bundle_url = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, cFBundlePath, kCFURLPOSIXPathStyle, true);
|
||||
CFRelease(cFBundlePath);
|
||||
CFBundleRef bundle = CFBundleCreate (kCFAllocatorDefault, bundle_url);
|
||||
CFRelease(bundle_url);
|
||||
CFStringRef cFBundleID = CFBundleGetIdentifier(bundle);
|
||||
bundleID = QString::fromCFString(cFBundleID).trimmed();
|
||||
CFRelease(bundle);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(bundlePath)
|
||||
#endif
|
||||
return bundleID;
|
||||
}
|
||||
|
||||
QString SimulatorControl::bundleExecutable(const Utils::FileName &bundlePath)
|
||||
{
|
||||
QString executable;
|
||||
#ifdef Q_OS_MAC
|
||||
if (bundlePath.exists()) {
|
||||
CFStringRef cFBundlePath = bundlePath.toString().toCFString();
|
||||
CFURLRef bundle_url = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, cFBundlePath, kCFURLPOSIXPathStyle, true);
|
||||
CFRelease(cFBundlePath);
|
||||
CFBundleRef bundle = CFBundleCreate (kCFAllocatorDefault, bundle_url);
|
||||
CFStringRef cFStrExecutableName = (CFStringRef)CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey);
|
||||
executable = QString::fromCFString(cFStrExecutableName).trimmed();
|
||||
CFRelease(bundle);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(bundlePath)
|
||||
#endif
|
||||
return executable;
|
||||
}
|
||||
|
||||
SimulatorControlPrivate::SimulatorControlPrivate(QObject *parent):
|
||||
QObject(parent),
|
||||
processDataLock(QReadWriteLock::Recursive)
|
||||
{
|
||||
}
|
||||
|
||||
SimulatorControlPrivate::~SimulatorControlPrivate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QByteArray SimulatorControlPrivate::runSimCtlCommand(QStringList args) const
|
||||
{
|
||||
QProcess simCtlProcess;
|
||||
args.prepend(QStringLiteral("simctl"));
|
||||
simCtlProcess.start(QStringLiteral("xcrun"), args, QProcess::ReadOnly);
|
||||
if (!simCtlProcess.waitForFinished())
|
||||
qCDebug(simulatorLog) << "simctl command failed." << simCtlProcess.errorString();
|
||||
return simCtlProcess.readAll();
|
||||
}
|
||||
|
||||
// The simctl spawns the process and returns the pId but the application process might not have started, at least in a state where you can interrupt it.
|
||||
// Use SimulatorControl::waitForProcessSpawn to be sure.
|
||||
QProcess *SimulatorControl::spawnAppProcess(const QString &simUdid, const Utils::FileName &bundlePath, qint64 &pId, bool waitForDebugger, const QStringList &extraArgs)
|
||||
{
|
||||
QProcess *simCtlProcess = nullptr;
|
||||
if (isSimulatorRunning(simUdid)) {
|
||||
QString bundleId = bundleIdentifier(bundlePath);
|
||||
QString executableName = bundleExecutable(bundlePath);
|
||||
QByteArray appPath = d->runSimCtlCommand(QStringList() << QStringLiteral("get_app_container") << simUdid << bundleId).trimmed();
|
||||
if (!appPath.isEmpty() && !executableName.isEmpty()) {
|
||||
// Spawn the app. The spawned app is started in suspended mode.
|
||||
appPath.append('/' + executableName.toLocal8Bit());
|
||||
simCtlProcess = new QProcess;
|
||||
QStringList args;
|
||||
args << QStringLiteral("simctl");
|
||||
args << QStringLiteral("spawn");
|
||||
if (waitForDebugger)
|
||||
args << QStringLiteral("-w");
|
||||
args << simUdid;
|
||||
args << QString::fromLocal8Bit(appPath);
|
||||
args << extraArgs;
|
||||
simCtlProcess->start(QStringLiteral("xcrun"), args);
|
||||
if (!simCtlProcess->waitForStarted()){
|
||||
// Spawn command failed.
|
||||
qCDebug(simulatorLog) << "Spawning the app failed." << simCtlProcess->errorString();
|
||||
delete simCtlProcess;
|
||||
simCtlProcess = nullptr;
|
||||
}
|
||||
|
||||
// Find the process id of the the app process.
|
||||
if (simCtlProcess) {
|
||||
qint64 simctlPId = simCtlProcess->processId();
|
||||
pId = -1;
|
||||
QByteArray commandOutput;
|
||||
QStringList pGrepArgs;
|
||||
pGrepArgs << QStringLiteral("-f") << QString::fromLocal8Bit(appPath);
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
// Find the pid of the spawned app.
|
||||
while (pId == -1 && d->runCommand(QStringLiteral("pgrep"), pGrepArgs, &commandOutput)) {
|
||||
foreach (auto pidStr, commandOutput.trimmed().split('\n')) {
|
||||
qint64 parsedPId = pidStr.toLongLong();
|
||||
if (parsedPId != simctlPId)
|
||||
pId = parsedPId;
|
||||
}
|
||||
if (checkForTimeout(begin)) {
|
||||
qCDebug(simulatorLog) << "Spawning the app failed. Process timed out";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pId == -1) {
|
||||
// App process id can't be found.
|
||||
qCDebug(simulatorLog) << "Spawning the app failed. PID not found.";
|
||||
delete simCtlProcess;
|
||||
simCtlProcess = nullptr;
|
||||
}
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Spawning the app failed. Check installed app." << appPath;
|
||||
}
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Spawning the app failed. Simulator not running." << simUdid;
|
||||
}
|
||||
return simCtlProcess;
|
||||
}
|
||||
|
||||
bool SimulatorControl::waitForProcessSpawn(qint64 processPId)
|
||||
{
|
||||
bool success = true;
|
||||
if (processPId != -1) {
|
||||
// Wait for app to reach intruptible sleep state.
|
||||
QByteArray wqStr;
|
||||
QStringList args;
|
||||
int wqCount = -1;
|
||||
args << QStringLiteral("-p") << QString::number(processPId) << QStringLiteral("-o") << QStringLiteral("wq=");
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
do {
|
||||
if (!d->runCommand(QStringLiteral("ps"), args, &wqStr)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
bool validInt = false;
|
||||
wqCount = wqStr.toInt(&validInt);
|
||||
if (!validInt) {
|
||||
wqCount = -1;
|
||||
}
|
||||
} while (wqCount < 0 && !checkForTimeout(begin));
|
||||
success = wqCount >= 0;
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Wait for spawned failed. Invalid Process ID." << processPId;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
SimulatorControlPrivate::SimDeviceInfo SimulatorControlPrivate::deviceInfo(const QString &simUdid) const
|
||||
{
|
||||
SimDeviceInfo info;
|
||||
bool found = false;
|
||||
if (!simUdid.isEmpty()) {
|
||||
// It might happend that the simulator is not started by SimControl.
|
||||
// Check of intances started externally.
|
||||
const QByteArray output = runSimCtlCommand({QLatin1String("list"), QLatin1String("-j"), QLatin1String("devices")});
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output);
|
||||
if (!doc.isNull()) {
|
||||
const QJsonObject buildInfo = doc.object().value(QStringLiteral("devices")).toObject();
|
||||
foreach (const QString &buildVersion, buildInfo.keys()) {
|
||||
QJsonArray devices = buildInfo.value(buildVersion).toArray();
|
||||
foreach (const QJsonValue device, devices) {
|
||||
QJsonObject deviceInfo = device.toObject();
|
||||
QString deviceUdid = deviceInfo.value(QStringLiteral("udid")).toString();
|
||||
if (deviceUdid.compare(simUdid) == 0) {
|
||||
found = true;
|
||||
info.name = deviceInfo.value(QStringLiteral("name")).toString();
|
||||
info.udid = deviceUdid;
|
||||
info.state = deviceInfo.value(QStringLiteral("state")).toString();
|
||||
info.sdk = buildVersion;
|
||||
info.availability = deviceInfo.value(QStringLiteral("availability")).toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Cannot find device info. Error parsing json output from simctl. Output:" << output;
|
||||
}
|
||||
} else {
|
||||
qCDebug(simulatorLog) << "Cannot find device info. Invalid UDID.";
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
bool SimulatorControlPrivate::runCommand(QString command, const QStringList &args, QByteArray *output)
|
||||
{
|
||||
bool success = false;
|
||||
QProcess process;
|
||||
process.start(command, args);
|
||||
success = process.waitForFinished();
|
||||
if (output)
|
||||
*output = process.readAll().trimmed();
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Ios
|
||||
#include "simulatorcontrol.moc"
|
64
src/plugins/ios/simulatorcontrol.h
Normal file
64
src/plugins/ios/simulatorcontrol.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
#ifndef SIMULATORCONTROL_H
|
||||
#define SIMULATORCONTROL_H
|
||||
|
||||
#include <QHash>
|
||||
#include "utils/fileutils.h"
|
||||
|
||||
class QProcess;
|
||||
|
||||
namespace Ios {
|
||||
namespace Internal {
|
||||
|
||||
class IosDeviceType;
|
||||
class SimulatorControlPrivate;
|
||||
|
||||
class SimulatorControl
|
||||
{
|
||||
explicit SimulatorControl();
|
||||
|
||||
public:
|
||||
static QList<IosDeviceType> availableSimulators();
|
||||
static void updateAvailableSimulators();
|
||||
|
||||
static bool startSimulator(const QString &simUdid);
|
||||
static bool isSimulatorRunning(const QString &simUdid);
|
||||
|
||||
static bool installApp(const QString &simUdid, const Utils::FileName &bundlePath, QByteArray &commandOutput);
|
||||
static QProcess* spawnAppProcess(const QString &simUdid, const Utils::FileName &bundlePath, qint64 &pId,
|
||||
bool waitForDebugger, const QStringList &extraArgs);
|
||||
|
||||
static qint64 launchApp(const QString &simUdid, const QString &bundleIdentifier, QByteArray *commandOutput = nullptr);
|
||||
static QString bundleIdentifier(const Utils::FileName &bundlePath);
|
||||
static QString bundleExecutable(const Utils::FileName &bundlePath);
|
||||
static bool waitForProcessSpawn(qint64 processPId);
|
||||
|
||||
private:
|
||||
static SimulatorControlPrivate *d;
|
||||
};
|
||||
} // namespace Internal
|
||||
} // namespace Ios
|
||||
#endif // SIMULATORCONTROL_H
|
Reference in New Issue
Block a user