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:
Vikas Pachdha
2016-09-26 12:40:09 +02:00
parent 19dcb9ed96
commit 2b0daf42ce
15 changed files with 793 additions and 240 deletions

View File

@@ -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 \

View File

@@ -69,6 +69,8 @@ QtcPlugin {
"iossimulatorfactory.cpp",
"iossimulatorfactory.h",
"iostoolhandler.cpp",
"iostoolhandler.h"
"iostoolhandler.h",
"simulatorcontrol.cpp",
"simulatorcontrol.h"
]
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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())
TaskHub::addTask(Task::Error, tr("Deployment failed. No iOS device found."),
ProjectExplorer::Constants::TASK_CATEGORY_DEPLOYMENT);
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;
}

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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,
IosToolHandler::RunKind runKind,
const QString &deviceId, int timeout = 1000);
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
virtual bool expectsFileDescriptor();
// 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) 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,
IosToolHandler::RunKind runKind,
const QString &deviceId, int timeout = 1000);
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
virtual bool expectsFileDescriptor();
// 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 &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,11 +641,46 @@ 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
IosSimulatorToolHandlerPrivate::IosSimulatorToolHandlerPrivate(const IosDeviceType &devType,
@@ -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;
}
void IosSimulatorToolHandlerPrivate::addDeviceArguments(QStringList &args) const
{
if (devType.type != IosDeviceType::SimulatedDevice) {
qCWarning(toolHandlerLog) << "IosSimulatorToolHandlerPrivate device type is not SimulatedDevice";
return;
if (process) {
if (isRunning()) {
process->terminate();
if (!process->waitForFinished(1000))
process->kill();
}
process->deleteLater();
process = nullptr;
appPId = -1;
appLaunched = false;
}
args << QLatin1String("--devicetypeid") << devType.identifier;
toolExited(errorCode);
}
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

View File

@@ -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;

View 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"

View 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