diff --git a/src/plugins/ios/ios.pro b/src/plugins/ios/ios.pro index b991353a243..f54bde30e81 100644 --- a/src/plugins/ios/ios.pro +++ b/src/plugins/ios/ios.pro @@ -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 \ diff --git a/src/plugins/ios/ios.qbs b/src/plugins/ios/ios.qbs index e38618c7a4f..080d0145401 100644 --- a/src/plugins/ios/ios.qbs +++ b/src/plugins/ios/ios.qbs @@ -69,6 +69,8 @@ QtcPlugin { "iossimulatorfactory.cpp", "iossimulatorfactory.h", "iostoolhandler.cpp", - "iostoolhandler.h" + "iostoolhandler.h", + "simulatorcontrol.cpp", + "simulatorcontrol.h" ] } diff --git a/src/plugins/ios/iosconfigurations.cpp b/src/plugins/ios/iosconfigurations.cpp index e1748b93715..f9cd910c36c 100644 --- a/src/plugins/ios/iosconfigurations.cpp +++ b/src/plugins/ios/iosconfigurations.cpp @@ -27,6 +27,7 @@ #include "iosconstants.h" #include "iosdevice.h" #include "iossimulator.h" +#include "simulatorcontrol.h" #include "iosprobe.h" #include @@ -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) diff --git a/src/plugins/ios/iosdebugsupport.cpp b/src/plugins/ios/iosdebugsupport.cpp index b715ab0b29b..754061d0153 100644 --- a/src/plugins/ios/iosdebugsupport.cpp +++ b/src/plugins/ios/iosdebugsupport.cpp @@ -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); diff --git a/src/plugins/ios/iosdeploystep.cpp b/src/plugins/ios/iosdeploystep.cpp index 0cefa4179c6..420b7bf9aec 100644 --- a/src/plugins/ios/iosdeploystep.cpp +++ b/src/plugins/ios/iosdeploystep.cpp @@ -101,7 +101,12 @@ bool IosDeployStep::init(QList &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 &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 &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; } diff --git a/src/plugins/ios/iosdeploystep.h b/src/plugins/ios/iosdeploystep.h index fba0993a429..2dc7916deab 100644 --- a/src/plugins/ios/iosdeploystep.h +++ b/src/plugins/ios/iosdeploystep.h @@ -101,6 +101,7 @@ private: QFutureInterface m_futureInterface; ProjectExplorer::IDevice::ConstPtr m_device; QString m_bundlePath; + IosDeviceType m_deviceType; static const Core::Id Id; bool m_expectFail; }; diff --git a/src/plugins/ios/iosrunconfiguration.cpp b/src/plugins/ios/iosrunconfiguration.cpp index 66f4826e0c9..01fb0d92cc8 100644 --- a/src/plugins/ios/iosrunconfiguration.cpp +++ b/src/plugins/ios/iosrunconfiguration.cpp @@ -27,6 +27,7 @@ #include "iosconstants.h" #include "iosmanager.h" #include "iosdeploystep.h" +#include "simulatorcontrol.h" #include #include @@ -346,7 +347,7 @@ IosDeviceType IosRunConfiguration::deviceType() const { QList availableSimulators; if (m_deviceType.type == IosDeviceType::SimulatedDevice) - availableSimulators = IosSimulator::availableDevices(); + availableSimulators = SimulatorControl::availableSimulators(); if (!availableSimulators.isEmpty()) { QList 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); diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp index 59a2e1bec77..20fbd794be9 100644 --- a/src/plugins/ios/iosrunner.cpp +++ b/src/plugins/ios/iosrunner.cpp @@ -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) { diff --git a/src/plugins/ios/iosrunner.h b/src/plugins/ios/iosrunner.h index 78f28146a24..d798b521eb2 100644 --- a/src/plugins/ios/iosrunner.h +++ b/src/plugins/ios/iosrunner.h @@ -29,6 +29,7 @@ #include "iostoolhandler.h" #include "iossimulator.h" +#include #include #include @@ -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); diff --git a/src/plugins/ios/iossimulator.cpp b/src/plugins/ios/iossimulator.cpp index a5c0bf39658..07743dbe049 100644 --- a/src/plugins/ios/iossimulator.cpp +++ b/src/plugins/ios/iossimulator.cpp @@ -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 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 IosSimulator::availableDevices() -{ - QMutexLocker l(&_mutex); - return _availableDevices; -} - -void IosSimulator::setAvailableDevices(QList value) -{ - QMutexLocker l(&_mutex); - _availableDevices = value; -} - -namespace { -void handleDeviceInfo(Ios::IosToolHandler *handler, const QString &deviceId, - const Ios::IosToolHandler::Dict &info) -{ - Q_UNUSED(deviceId); - QList res; - QMapIterator 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); diff --git a/src/plugins/ios/iossimulator.h b/src/plugins/ios/iossimulator.h index df02e6807a1..b661821a3d0 100644 --- a/src/plugins/ios/iossimulator.h +++ b/src/plugins/ios/iossimulator.h @@ -67,10 +67,6 @@ public: typedef QSharedPointer Ptr; ProjectExplorer::IDevice::DeviceInfo deviceInformation() const override; - static QList availableDevices(); - static void setAvailableDevices(QList value); - static void updateAvailableDevices(); - QString displayType() const override; ProjectExplorer::IDeviceWidget *createWidget() override; QList actionIds() const override; @@ -91,8 +87,6 @@ protected: IosSimulator(const IosSimulator &other); private: mutable quint16 m_lastPort; - static QMutex _mutex; - static QList _availableDevices; }; namespace IosKitInformation { diff --git a/src/plugins/ios/iostoolhandler.cpp b/src/plugins/ios/iostoolhandler.cpp index 35d3d3b2ed9..c5da66c3598 100644 --- a/src/plugins/ios/iostoolhandler.cpp +++ b/src/plugins/ios/iostoolhandler.cpp @@ -27,13 +27,18 @@ #include "iosconfigurations.h" #include "iosconstants.h" #include "iossimulator.h" +#include "simulatorcontrol.h" +#include "debugger/debuggerconstants.h" #include #include #include #include #include +#include +#include +#include #include #include #include @@ -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(&QProcess::finished), - q, &IosToolHandler::subprocessFinished); - QObject::connect(process, - static_cast(&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(&QProcess::finished), + std::bind(&IosDeviceToolHandlerPrivate::subprocessFinished,this, _1,_2)); + + QObject::connect(process, static_cast(&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(&QProcess::finished), + std::bind(&IosSimulatorToolHandlerPrivate::simAppProcessFinished,this, _1,_2)); + QObject::connect(process, static_cast(&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 diff --git a/src/plugins/ios/iostoolhandler.h b/src/plugins/ios/iostoolhandler.h index 4ac3e5716e3..df1cd089cf8 100644 --- a/src/plugins/ios/iostoolhandler.h +++ b/src/plugins/ios/iostoolhandler.h @@ -33,7 +33,6 @@ #include #include - 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; diff --git a/src/plugins/ios/simulatorcontrol.cpp b/src/plugins/ios/simulatorcontrol.cpp new file mode 100644 index 00000000000..a2807996a46 --- /dev/null +++ b/src/plugins/ios/simulatorcontrol.cpp @@ -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 +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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 simulatorProcesses; + QReadWriteLock processDataLock; + QList availableDevices; + QReadWriteLock deviceDataLock; + friend class SimulatorControl; +}; + +SimulatorControlPrivate *SimulatorControl::d = new SimulatorControlPrivate; + +SimulatorControl::SimulatorControl() +{ + +} + +QList 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 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(&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" diff --git a/src/plugins/ios/simulatorcontrol.h b/src/plugins/ios/simulatorcontrol.h new file mode 100644 index 00000000000..a1bd729b79f --- /dev/null +++ b/src/plugins/ios/simulatorcontrol.h @@ -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 +#include "utils/fileutils.h" + +class QProcess; + +namespace Ios { +namespace Internal { + +class IosDeviceType; +class SimulatorControlPrivate; + +class SimulatorControl +{ + explicit SimulatorControl(); + +public: + static QList 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