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 \
|
iosdeploystep.h \
|
||||||
iosdeploystepfactory.h \
|
iosdeploystepfactory.h \
|
||||||
iosdeploystepwidget.h \
|
iosdeploystepwidget.h \
|
||||||
iosanalyzesupport.h
|
iosanalyzesupport.h \
|
||||||
|
simulatorcontrol.h
|
||||||
|
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
@@ -61,7 +62,8 @@ SOURCES += \
|
|||||||
iosdeploystep.cpp \
|
iosdeploystep.cpp \
|
||||||
iosdeploystepfactory.cpp \
|
iosdeploystepfactory.cpp \
|
||||||
iosdeploystepwidget.cpp \
|
iosdeploystepwidget.cpp \
|
||||||
iosanalyzesupport.cpp
|
iosanalyzesupport.cpp \
|
||||||
|
simulatorcontrol.cpp
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
iossettingswidget.ui \
|
iossettingswidget.ui \
|
||||||
|
@@ -69,6 +69,8 @@ QtcPlugin {
|
|||||||
"iossimulatorfactory.cpp",
|
"iossimulatorfactory.cpp",
|
||||||
"iossimulatorfactory.h",
|
"iossimulatorfactory.h",
|
||||||
"iostoolhandler.cpp",
|
"iostoolhandler.cpp",
|
||||||
"iostoolhandler.h"
|
"iostoolhandler.h",
|
||||||
|
"simulatorcontrol.cpp",
|
||||||
|
"simulatorcontrol.h"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#include "iosconstants.h"
|
#include "iosconstants.h"
|
||||||
#include "iosdevice.h"
|
#include "iosdevice.h"
|
||||||
#include "iossimulator.h"
|
#include "iossimulator.h"
|
||||||
|
#include "simulatorcontrol.h"
|
||||||
#include "iosprobe.h"
|
#include "iosprobe.h"
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
@@ -333,7 +334,7 @@ void IosConfigurations::updateSimulators()
|
|||||||
dev = IDevice::ConstPtr(new IosSimulator(devId));
|
dev = IDevice::ConstPtr(new IosSimulator(devId));
|
||||||
devManager->addDevice(dev);
|
devManager->addDevice(dev);
|
||||||
}
|
}
|
||||||
IosSimulator::updateAvailableDevices();
|
SimulatorControl::updateAvailableSimulators();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosConfigurations::setDeveloperPath(const FileName &devPath)
|
void IosConfigurations::setDeveloperPath(const FileName &devPath)
|
||||||
|
@@ -169,6 +169,8 @@ IosDebugSupport::IosDebugSupport(IosRunConfiguration *runConfig,
|
|||||||
m_runner, &IosRunner::start);
|
m_runner, &IosRunner::start);
|
||||||
connect(m_runControl, &RunControl::finished,
|
connect(m_runControl, &RunControl::finished,
|
||||||
m_runner, &IosRunner::stop);
|
m_runner, &IosRunner::stop);
|
||||||
|
connect(m_runControl, &DebuggerRunControl::stateChanged,
|
||||||
|
m_runner, &IosRunner::debuggerStateChanged);
|
||||||
|
|
||||||
connect(m_runner, &IosRunner::gotServerPorts,
|
connect(m_runner, &IosRunner::gotServerPorts,
|
||||||
this, &IosDebugSupport::handleServerPorts);
|
this, &IosDebugSupport::handleServerPorts);
|
||||||
|
@@ -101,7 +101,12 @@ bool IosDeployStep::init(QList<const BuildStep *> &earlierSteps)
|
|||||||
this->target()->activeRunConfiguration());
|
this->target()->activeRunConfiguration());
|
||||||
QTC_ASSERT(runConfig, return false);
|
QTC_ASSERT(runConfig, return false);
|
||||||
m_bundlePath = runConfig->bundleDirectory().toString();
|
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."),
|
emit addOutput(tr("Error: no device available, deploy failed."),
|
||||||
BuildStep::ErrorMessageOutput);
|
BuildStep::ErrorMessageOutput);
|
||||||
return false;
|
return false;
|
||||||
@@ -113,17 +118,15 @@ void IosDeployStep::run(QFutureInterface<bool> &fi)
|
|||||||
{
|
{
|
||||||
m_futureInterface = fi;
|
m_futureInterface = fi;
|
||||||
QTC_CHECK(m_transferStatus == NoTransfer);
|
QTC_CHECK(m_transferStatus == NoTransfer);
|
||||||
if (iosdevice().isNull()) {
|
if (device().isNull()) {
|
||||||
if (iossimulator().isNull())
|
|
||||||
TaskHub::addTask(Task::Error, tr("Deployment failed. No iOS device found."),
|
TaskHub::addTask(Task::Error, tr("Deployment failed. No iOS device found."),
|
||||||
ProjectExplorer::Constants::TASK_CATEGORY_DEPLOYMENT);
|
ProjectExplorer::Constants::TASK_CATEGORY_DEPLOYMENT);
|
||||||
reportRunResult(m_futureInterface, !iossimulator().isNull());
|
reportRunResult(m_futureInterface, !iossimulator().isNull());
|
||||||
cleanup();
|
cleanup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_toolHandler = new IosToolHandler(m_deviceType, this);
|
||||||
m_transferStatus = TransferInProgress;
|
m_transferStatus = TransferInProgress;
|
||||||
QTC_CHECK(m_toolHandler == 0);
|
|
||||||
m_toolHandler = new IosToolHandler(IosDeviceType(IosDeviceType::IosDevice), this);
|
|
||||||
m_futureInterface.setProgressRange(0, 200);
|
m_futureInterface.setProgressRange(0, 200);
|
||||||
m_futureInterface.setProgressValueAndText(0, QLatin1String("Transferring application"));
|
m_futureInterface.setProgressValueAndText(0, QLatin1String("Transferring application"));
|
||||||
m_futureInterface.reportStarted();
|
m_futureInterface.reportStarted();
|
||||||
@@ -136,7 +139,7 @@ void IosDeployStep::run(QFutureInterface<bool> &fi)
|
|||||||
connect(m_toolHandler, &IosToolHandler::errorMsg,
|
connect(m_toolHandler, &IosToolHandler::errorMsg,
|
||||||
this, &IosDeployStep::handleErrorMsg);
|
this, &IosDeployStep::handleErrorMsg);
|
||||||
checkProvisioningProfile();
|
checkProvisioningProfile();
|
||||||
m_toolHandler->requestTransferApp(appBundle(), deviceId());
|
m_toolHandler->requestTransferApp(appBundle(), m_deviceType.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosDeployStep::cancel()
|
void IosDeployStep::cancel()
|
||||||
@@ -150,7 +153,7 @@ void IosDeployStep::cleanup()
|
|||||||
QTC_CHECK(m_transferStatus != TransferInProgress);
|
QTC_CHECK(m_transferStatus != TransferInProgress);
|
||||||
m_transferStatus = NoTransfer;
|
m_transferStatus = NoTransfer;
|
||||||
m_device.clear();
|
m_device.clear();
|
||||||
m_toolHandler = 0;
|
m_toolHandler = nullptr;
|
||||||
m_expectFail = false;
|
m_expectFail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -101,6 +101,7 @@ private:
|
|||||||
QFutureInterface<bool> m_futureInterface;
|
QFutureInterface<bool> m_futureInterface;
|
||||||
ProjectExplorer::IDevice::ConstPtr m_device;
|
ProjectExplorer::IDevice::ConstPtr m_device;
|
||||||
QString m_bundlePath;
|
QString m_bundlePath;
|
||||||
|
IosDeviceType m_deviceType;
|
||||||
static const Core::Id Id;
|
static const Core::Id Id;
|
||||||
bool m_expectFail;
|
bool m_expectFail;
|
||||||
};
|
};
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#include "iosconstants.h"
|
#include "iosconstants.h"
|
||||||
#include "iosmanager.h"
|
#include "iosmanager.h"
|
||||||
#include "iosdeploystep.h"
|
#include "iosdeploystep.h"
|
||||||
|
#include "simulatorcontrol.h"
|
||||||
|
|
||||||
#include <projectexplorer/kitinformation.h>
|
#include <projectexplorer/kitinformation.h>
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/target.h>
|
||||||
@@ -346,7 +347,7 @@ IosDeviceType IosRunConfiguration::deviceType() const
|
|||||||
{
|
{
|
||||||
QList<IosDeviceType> availableSimulators;
|
QList<IosDeviceType> availableSimulators;
|
||||||
if (m_deviceType.type == IosDeviceType::SimulatedDevice)
|
if (m_deviceType.type == IosDeviceType::SimulatedDevice)
|
||||||
availableSimulators = IosSimulator::availableDevices();
|
availableSimulators = SimulatorControl::availableSimulators();
|
||||||
if (!availableSimulators.isEmpty()) {
|
if (!availableSimulators.isEmpty()) {
|
||||||
QList<IosDeviceType> elegibleDevices;
|
QList<IosDeviceType> elegibleDevices;
|
||||||
QString devname = m_deviceType.identifier.split(QLatin1Char(',')).value(0);
|
QString devname = m_deviceType.identifier.split(QLatin1Char(',')).value(0);
|
||||||
@@ -417,7 +418,7 @@ void IosRunConfigurationWidget::updateValues()
|
|||||||
m_deviceTypeLabel->setVisible(showDeviceSelector);
|
m_deviceTypeLabel->setVisible(showDeviceSelector);
|
||||||
m_deviceTypeComboBox->setVisible(showDeviceSelector);
|
m_deviceTypeComboBox->setVisible(showDeviceSelector);
|
||||||
if (showDeviceSelector && m_deviceTypeModel.rowCount() == 0) {
|
if (showDeviceSelector && m_deviceTypeModel.rowCount() == 0) {
|
||||||
foreach (const IosDeviceType &dType, IosSimulator::availableDevices()) {
|
foreach (const IosDeviceType &dType, SimulatorControl::availableSimulators()) {
|
||||||
QStandardItem *item = new QStandardItem(dType.displayName);
|
QStandardItem *item = new QStandardItem(dType.displayName);
|
||||||
QVariant v;
|
QVariant v;
|
||||||
v.setValue(dType);
|
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,
|
void IosRunner::handleDidStartApp(IosToolHandler *handler, const QString &bundlePath,
|
||||||
const QString &deviceId, IosToolHandler::OpStatus status)
|
const QString &deviceId, IosToolHandler::OpStatus status)
|
||||||
{
|
{
|
||||||
|
@@ -29,6 +29,7 @@
|
|||||||
#include "iostoolhandler.h"
|
#include "iostoolhandler.h"
|
||||||
#include "iossimulator.h"
|
#include "iossimulator.h"
|
||||||
|
|
||||||
|
#include <debugger/debuggerconstants.h>
|
||||||
#include <projectexplorer/devicesupport/idevice.h>
|
#include <projectexplorer/devicesupport/idevice.h>
|
||||||
#include <qmldebug/qmldebugcommandlinearguments.h>
|
#include <qmldebug/qmldebugcommandlinearguments.h>
|
||||||
|
|
||||||
@@ -64,6 +65,9 @@ public:
|
|||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void debuggerStateChanged(Debugger::DebuggerState state);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void didStartApp(Ios::IosToolHandler::OpStatus status);
|
void didStartApp(Ios::IosToolHandler::OpStatus status);
|
||||||
void gotServerPorts(Utils::Port gdbPort, Utils::Port qmlPort);
|
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 iosDeviceTypeTypeKey = QLatin1String("type");
|
||||||
static const QLatin1String iosDeviceTypeIdentifierKey = QLatin1String("identifier");
|
static const QLatin1String iosDeviceTypeIdentifierKey = QLatin1String("identifier");
|
||||||
|
|
||||||
QMutex IosSimulator::_mutex;
|
|
||||||
QList<IosDeviceType> IosSimulator::_availableDevices;
|
|
||||||
|
|
||||||
IosSimulator::IosSimulator(Core::Id id)
|
IosSimulator::IosSimulator(Core::Id id)
|
||||||
: IDevice(Core::Id(Constants::IOS_SIMULATOR_TYPE),
|
: IDevice(Core::Id(Constants::IOS_SIMULATOR_TYPE),
|
||||||
IDevice::AutoDetected,
|
IDevice::AutoDetected,
|
||||||
@@ -119,48 +116,6 @@ IDevice::Ptr IosSimulator::clone() const
|
|||||||
return IDevice::Ptr(new IosSimulator(*this));
|
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)
|
void IosSimulator::fromMap(const QVariantMap &map)
|
||||||
{
|
{
|
||||||
IDevice::fromMap(map);
|
IDevice::fromMap(map);
|
||||||
|
@@ -67,10 +67,6 @@ public:
|
|||||||
typedef QSharedPointer<IosSimulator> Ptr;
|
typedef QSharedPointer<IosSimulator> Ptr;
|
||||||
ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override;
|
ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override;
|
||||||
|
|
||||||
static QList<IosDeviceType> availableDevices();
|
|
||||||
static void setAvailableDevices(QList<IosDeviceType> value);
|
|
||||||
static void updateAvailableDevices();
|
|
||||||
|
|
||||||
QString displayType() const override;
|
QString displayType() const override;
|
||||||
ProjectExplorer::IDeviceWidget *createWidget() override;
|
ProjectExplorer::IDeviceWidget *createWidget() override;
|
||||||
QList<Core::Id> actionIds() const override;
|
QList<Core::Id> actionIds() const override;
|
||||||
@@ -91,8 +87,6 @@ protected:
|
|||||||
IosSimulator(const IosSimulator &other);
|
IosSimulator(const IosSimulator &other);
|
||||||
private:
|
private:
|
||||||
mutable quint16 m_lastPort;
|
mutable quint16 m_lastPort;
|
||||||
static QMutex _mutex;
|
|
||||||
static QList<IosDeviceType> _availableDevices;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace IosKitInformation {
|
namespace IosKitInformation {
|
||||||
|
@@ -27,13 +27,18 @@
|
|||||||
#include "iosconfigurations.h"
|
#include "iosconfigurations.h"
|
||||||
#include "iosconstants.h"
|
#include "iosconstants.h"
|
||||||
#include "iossimulator.h"
|
#include "iossimulator.h"
|
||||||
|
#include "simulatorcontrol.h"
|
||||||
|
|
||||||
|
#include "debugger/debuggerconstants.h"
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
@@ -52,6 +57,8 @@ namespace Ios {
|
|||||||
|
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
|
using namespace std::placeholders;
|
||||||
|
|
||||||
struct ParserState {
|
struct ParserState {
|
||||||
enum Kind {
|
enum Kind {
|
||||||
Msg,
|
Msg,
|
||||||
@@ -132,7 +139,8 @@ public:
|
|||||||
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000) = 0;
|
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000) = 0;
|
||||||
bool isRunning();
|
bool isRunning();
|
||||||
void start(const QString &exe, const QStringList &args);
|
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
|
// signals
|
||||||
void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress,
|
void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress,
|
||||||
@@ -148,15 +156,12 @@ public:
|
|||||||
void appOutput(const QString &output);
|
void appOutput(const QString &output);
|
||||||
void errorMsg(const QString &msg);
|
void errorMsg(const QString &msg);
|
||||||
void toolExited(int code);
|
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;
|
IosToolHandler *q;
|
||||||
QProcess *process;
|
QProcess *process;
|
||||||
QTimer killTimer;
|
QTimer killTimer;
|
||||||
@@ -176,34 +181,56 @@ class IosDeviceToolHandlerPrivate : public IosToolHandlerPrivate
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit IosDeviceToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
|
explicit IosDeviceToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
|
||||||
virtual void requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
|
||||||
int timeout = 1000);
|
// IosToolHandlerPrivate overrides
|
||||||
virtual void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
public:
|
||||||
|
void requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
||||||
|
int timeout = 1000) override;
|
||||||
|
void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
||||||
IosToolHandler::RunKind runKind,
|
IosToolHandler::RunKind runKind,
|
||||||
const QString &deviceId, int timeout = 1000);
|
const QString &deviceId, int timeout = 1000) override;
|
||||||
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override;
|
||||||
virtual bool expectsFileDescriptor();
|
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
|
class IosSimulatorToolHandlerPrivate : public IosToolHandlerPrivate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit IosSimulatorToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
|
explicit IosSimulatorToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
|
||||||
virtual void requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
|
||||||
int timeout = 1000);
|
// IosToolHandlerPrivate overrides
|
||||||
virtual void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
public:
|
||||||
|
void requestTransferApp(const QString &bundlePath, const QString &deviceIdentifier,
|
||||||
|
int timeout = 1000) override;
|
||||||
|
void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
|
||||||
IosToolHandler::RunKind runKind,
|
IosToolHandler::RunKind runKind,
|
||||||
const QString &deviceId, int timeout = 1000);
|
const QString &deviceIdentifier, int timeout = 1000) override;
|
||||||
virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override;
|
||||||
virtual bool expectsFileDescriptor();
|
void stop(int errorCode) override;
|
||||||
|
void debuggerStateChanged(Debugger::DebuggerState state) override;
|
||||||
|
|
||||||
private:
|
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,
|
IosToolHandlerPrivate::IosToolHandlerPrivate(const IosDeviceType &devType,
|
||||||
Ios::IosToolHandler *q) :
|
Ios::IosToolHandler *q) :
|
||||||
q(q),
|
q(q),
|
||||||
process(new QProcess),
|
process(nullptr),
|
||||||
state(NonStarted),
|
state(NonStarted),
|
||||||
devType(devType),
|
devType(devType),
|
||||||
iBegin(0),
|
iBegin(0),
|
||||||
@@ -211,36 +238,6 @@ IosToolHandlerPrivate::IosToolHandlerPrivate(const IosDeviceType &devType,
|
|||||||
gdbSocket(-1)
|
gdbSocket(-1)
|
||||||
{
|
{
|
||||||
killTimer.setSingleShot(true);
|
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()
|
IosToolHandlerPrivate::~IosToolHandlerPrivate()
|
||||||
@@ -260,6 +257,7 @@ bool IosToolHandlerPrivate::isRunning()
|
|||||||
|
|
||||||
void IosToolHandlerPrivate::start(const QString &exe, const QStringList &args)
|
void IosToolHandlerPrivate::start(const QString &exe, const QStringList &args)
|
||||||
{
|
{
|
||||||
|
Q_ASSERT(process);
|
||||||
QTC_CHECK(state == NonStarted);
|
QTC_CHECK(state == NonStarted);
|
||||||
state = Starting;
|
state = Starting;
|
||||||
qCDebug(toolHandlerLog) << "running " << exe << args;
|
qCDebug(toolHandlerLog) << "running " << exe << args;
|
||||||
@@ -267,44 +265,6 @@ void IosToolHandlerPrivate::start(const QString &exe, const QStringList &args)
|
|||||||
state = StartedInferior;
|
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
|
// signals
|
||||||
void IosToolHandlerPrivate::isTransferringApp(const QString &bundlePath, const QString &deviceId,
|
void IosToolHandlerPrivate::isTransferringApp(const QString &bundlePath, const QString &deviceId,
|
||||||
int progress, int maxProgress, const QString &info)
|
int progress, int maxProgress, const QString &info)
|
||||||
@@ -357,7 +317,7 @@ void IosToolHandlerPrivate::toolExited(int code)
|
|||||||
emit q->toolExited(q, code);
|
emit q->toolExited(q, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosToolHandlerPrivate::subprocessError(QProcess::ProcessError error)
|
void IosDeviceToolHandlerPrivate::subprocessError(QProcess::ProcessError error)
|
||||||
{
|
{
|
||||||
if (state != Stopped)
|
if (state != Stopped)
|
||||||
errorMsg(IosToolHandler::tr("iOS tool Error %1").arg(error));
|
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 );
|
stop((exitStatus == QProcess::NormalExit) ? exitCode : -1 );
|
||||||
qCDebug(toolHandlerLog) << "IosToolHandler::finished(" << this << ")";
|
qCDebug(toolHandlerLog) << "IosToolHandler::finished(" << this << ")";
|
||||||
@@ -376,7 +336,7 @@ void IosToolHandlerPrivate::subprocessFinished(int exitCode, QProcess::ExitStatu
|
|||||||
emit q->finished(q);
|
emit q->finished(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosToolHandlerPrivate::processXml()
|
void IosDeviceToolHandlerPrivate::processXml()
|
||||||
{
|
{
|
||||||
while (!outputParser.atEnd()) {
|
while (!outputParser.atEnd()) {
|
||||||
QXmlStreamReader::TokenType tt = outputParser.readNext();
|
QXmlStreamReader::TokenType tt = outputParser.readNext();
|
||||||
@@ -558,7 +518,7 @@ void IosToolHandlerPrivate::processXml()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosToolHandlerPrivate::subprocessHasData()
|
void IosDeviceToolHandlerPrivate::subprocessHasData()
|
||||||
{
|
{
|
||||||
qCDebug(toolHandlerLog) << "subprocessHasData, state:" << state;
|
qCDebug(toolHandlerLog) << "subprocessHasData, state:" << state;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -598,7 +558,42 @@ void IosToolHandlerPrivate::subprocessHasData()
|
|||||||
IosDeviceToolHandlerPrivate::IosDeviceToolHandlerPrivate(const IosDeviceType &devType,
|
IosDeviceToolHandlerPrivate::IosDeviceToolHandlerPrivate(const IosDeviceType &devType,
|
||||||
IosToolHandler *q)
|
IosToolHandler *q)
|
||||||
: IosToolHandlerPrivate(devType, 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,
|
void IosDeviceToolHandlerPrivate::requestTransferApp(const QString &bundlePath,
|
||||||
const QString &deviceId, int timeout)
|
const QString &deviceId, int timeout)
|
||||||
@@ -646,10 +641,45 @@ void IosDeviceToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int
|
|||||||
start(IosToolHandler::iosDeviceToolPath(), args);
|
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
|
||||||
|
|
||||||
@@ -659,64 +689,159 @@ IosSimulatorToolHandlerPrivate::IosSimulatorToolHandlerPrivate(const IosDeviceTy
|
|||||||
{ }
|
{ }
|
||||||
|
|
||||||
void IosSimulatorToolHandlerPrivate::requestTransferApp(const QString &bundlePath,
|
void IosSimulatorToolHandlerPrivate::requestTransferApp(const QString &bundlePath,
|
||||||
const QString &deviceId, int timeout)
|
const QString &deviceIdentifier, int timeout)
|
||||||
{
|
{
|
||||||
Q_UNUSED(timeout);
|
Q_UNUSED(timeout);
|
||||||
this->bundlePath = bundlePath;
|
this->bundlePath = bundlePath;
|
||||||
this->deviceId = deviceId;
|
this->deviceId = deviceIdentifier;
|
||||||
emit didTransferApp(bundlePath, deviceId, IosToolHandler::Success);
|
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,
|
void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &bundlePath,
|
||||||
const QStringList &extraArgs,
|
const QStringList &extraArgs,
|
||||||
IosToolHandler::RunKind runType,
|
IosToolHandler::RunKind runType,
|
||||||
const QString &deviceId, int timeout)
|
const QString &deviceIdentifier, int timeout)
|
||||||
{
|
{
|
||||||
Q_UNUSED(timeout);
|
Q_UNUSED(timeout);
|
||||||
|
Q_UNUSED(deviceIdentifier);
|
||||||
this->bundlePath = bundlePath;
|
this->bundlePath = bundlePath;
|
||||||
this->deviceId = deviceId;
|
this->deviceId = devType.identifier;
|
||||||
this->runKind = runType;
|
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;
|
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)
|
void IosSimulatorToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int timeout)
|
||||||
{
|
{
|
||||||
Q_UNUSED(timeout);
|
Q_UNUSED(timeout);
|
||||||
this->deviceId = deviceId;
|
Q_UNUSED(deviceId);
|
||||||
QStringList args;
|
|
||||||
args << QLatin1String("showdevicetypes");
|
|
||||||
op = OpDeviceInfo;
|
|
||||||
start(IosToolHandler::iosSimulatorToolPath(), args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
toolExited(errorCode);
|
||||||
{
|
|
||||||
if (devType.type != IosDeviceType::SimulatedDevice) {
|
|
||||||
qCWarning(toolHandlerLog) << "IosSimulatorToolHandlerPrivate device type is not SimulatedDevice";
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
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()
|
void IosToolHandlerPrivate::killProcess()
|
||||||
@@ -733,18 +858,6 @@ QString IosToolHandler::iosDeviceToolPath()
|
|||||||
return res;
|
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) :
|
IosToolHandler::IosToolHandler(const Internal::IosDeviceType &devType, QObject *parent) :
|
||||||
QObject(parent)
|
QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -764,6 +877,11 @@ void IosToolHandler::stop()
|
|||||||
d->stop(-1);
|
d->stop(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IosToolHandler::debuggerStateChanged(int state)
|
||||||
|
{
|
||||||
|
d->debuggerStateChanged((Debugger::DebuggerState)state);
|
||||||
|
}
|
||||||
|
|
||||||
void IosToolHandler::requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
void IosToolHandler::requestTransferApp(const QString &bundlePath, const QString &deviceId,
|
||||||
int timeout)
|
int timeout)
|
||||||
{
|
{
|
||||||
@@ -786,24 +904,4 @@ bool IosToolHandler::isRunning()
|
|||||||
return d->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
|
} // namespace Ios
|
||||||
|
@@ -33,7 +33,6 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
|
||||||
|
|
||||||
namespace Ios {
|
namespace Ios {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
class IosToolHandlerPrivate;
|
class IosToolHandlerPrivate;
|
||||||
@@ -56,7 +55,6 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
static QString iosDeviceToolPath();
|
static QString iosDeviceToolPath();
|
||||||
static QString iosSimulatorToolPath();
|
|
||||||
|
|
||||||
explicit IosToolHandler(const Internal::IosDeviceType &type, QObject *parent = 0);
|
explicit IosToolHandler(const Internal::IosDeviceType &type, QObject *parent = 0);
|
||||||
~IosToolHandler();
|
~IosToolHandler();
|
||||||
@@ -66,6 +64,7 @@ public:
|
|||||||
void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
void requestDeviceInfo(const QString &deviceId, int timeout = 1000);
|
||||||
bool isRunning();
|
bool isRunning();
|
||||||
void stop();
|
void stop();
|
||||||
|
void debuggerStateChanged(int state);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void isTransferringApp(Ios::IosToolHandler *handler, const QString &bundlePath,
|
void isTransferringApp(Ios::IosToolHandler *handler, const QString &bundlePath,
|
||||||
@@ -85,11 +84,10 @@ signals:
|
|||||||
void errorMsg(Ios::IosToolHandler *handler, const QString &msg);
|
void errorMsg(Ios::IosToolHandler *handler, const QString &msg);
|
||||||
void toolExited(Ios::IosToolHandler *handler, int code);
|
void toolExited(Ios::IosToolHandler *handler, int code);
|
||||||
void finished(Ios::IosToolHandler *handler);
|
void finished(Ios::IosToolHandler *handler);
|
||||||
private:
|
|
||||||
void subprocessError(QProcess::ProcessError error);
|
protected:
|
||||||
void subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
|
||||||
void subprocessHasData();
|
|
||||||
void killProcess();
|
void killProcess();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Ios::Internal::IosToolHandlerPrivate;
|
friend class Ios::Internal::IosToolHandlerPrivate;
|
||||||
Ios::Internal::IosToolHandlerPrivate *d;
|
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