1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "iostoolhandler.h"
27 #include "iosconfigurations.h"
28 #include "iosconstants.h"
29 #include "iossimulator.h"
30 #include "simulatorcontrol.h"
31 
32 #include <coreplugin/icore.h>
33 
34 #include <debugger/debuggerconstants.h>
35 
36 #include <utils/fileutils.h>
37 #include <utils/futuresynchronizer.h>
38 #include <utils/qtcassert.h>
39 #include <utils/qtcprocess.h>
40 #include <utils/runextensions.h>
41 
42 #include <QCoreApplication>
43 #include <QDir>
44 #include <QFileInfo>
45 #include <QFutureWatcher>
46 #include <QJsonArray>
47 #include <QJsonDocument>
48 #include <QJsonObject>
49 #include <QList>
50 #include <QLoggingCategory>
51 #include <QPointer>
52 #include <QProcess>
53 #include <QProcessEnvironment>
54 #include <QScopedArrayPointer>
55 #include <QSocketNotifier>
56 #include <QTemporaryFile>
57 #include <QTimer>
58 #include <QXmlStreamReader>
59 
60 #include <signal.h>
61 #include <string.h>
62 #include <errno.h>
63 
64 static Q_LOGGING_CATEGORY(toolHandlerLog, "qtc.ios.toolhandler", QtWarningMsg)
65 
66 namespace Ios {
67 
68 namespace Internal {
69 
70 using namespace std::placeholders;
71 
72 // As per the currrent behavior, any absolute path given to simctl --stdout --stderr where the
73 // directory after the root also exists on the simulator's file system will map to
74 // simulator's file system i.e. simctl translates $TMPDIR/somwhere/out.txt to
75 // your_home_dir/Library/Developer/CoreSimulator/Devices/data/$TMP_DIR/somwhere/out.txt.
76 // Because /var also exists on simulator's file system.
77 // Though the log files located at CONSOLE_PATH_TEMPLATE are deleted on
78 // app exit any leftovers shall be removed on simulator restart.
79 static QString CONSOLE_PATH_TEMPLATE = QDir::homePath() +
80         "/Library/Developer/CoreSimulator/Devices/%1/data/tmp/%2";
81 
82 class LogTailFiles : public QObject
83 {
84     Q_OBJECT
85 public:
86 
exec(QFutureInterface<void> & fi,std::shared_ptr<QTemporaryFile> stdoutFile,std::shared_ptr<QTemporaryFile> stderrFile)87     void exec(QFutureInterface<void> &fi, std::shared_ptr<QTemporaryFile> stdoutFile,
88                     std::shared_ptr<QTemporaryFile> stderrFile)
89     {
90         if (fi.isCanceled())
91             return;
92 
93         // The future is canceled when app on simulator is stoped.
94         QEventLoop loop;
95         QFutureWatcher<void> watcher;
96         connect(&watcher, &QFutureWatcher<void>::canceled, [&](){
97             loop.quit();
98         });
99         watcher.setFuture(fi.future());
100 
101         // Process to print the console output while app is running.
102         auto logProcess = [this, fi](QProcess *tailProcess, std::shared_ptr<QTemporaryFile> file) {
103             QObject::connect(tailProcess, &QProcess::readyReadStandardOutput, [=]() {
104                 if (!fi.isCanceled())
105                     emit logMessage(QString::fromLocal8Bit(tailProcess->readAll()));
106             });
107             tailProcess->start(QStringLiteral("tail"), {"-f", file->fileName()});
108         };
109 
110         auto processDeleter = [](QProcess *process) {
111             if (process->state() != QProcess::NotRunning) {
112                 process->terminate();
113                 process->waitForFinished();
114             }
115             delete process;
116         };
117 
118         std::unique_ptr<QProcess, void(*)(QProcess *)> tailStdout(new QProcess, processDeleter);
119         if (stdoutFile)
120             logProcess(tailStdout.get(), stdoutFile);
121 
122         std::unique_ptr<QProcess, void(*)(QProcess *)> tailStderr(new QProcess, processDeleter);
123         if (stderrFile)
124             logProcess(tailStderr.get(), stderrFile);
125 
126         // Blocks untill tool is deleted or toolexited is called.
127         loop.exec();
128     }
129 
130 signals:
131     void logMessage(QString message);
132 };
133 
134 struct ParserState {
135     enum Kind {
136         Msg,
137         DeviceId,
138         Key,
139         Value,
140         QueryResult,
141         AppOutput,
142         ControlChar,
143         AppStarted,
144         InferiorPid,
145         ServerPorts,
146         Item,
147         Status,
148         AppTransfer,
149         DeviceInfo,
150         Exit
151     };
152     Kind kind;
153     QString elName;
154     QString chars;
155     QString key;
156     QString value;
157     QMap<QString,QString> info;
158     int progress = 0, maxProgress = 0;
159     int gdbPort, qmlPort;
collectCharsIos::Internal::ParserState160     bool collectChars() {
161         switch (kind) {
162         case Msg:
163         case DeviceId:
164         case Key:
165         case Value:
166         case Status:
167         case InferiorPid:
168         case AppOutput:
169             return true;
170         case ServerPorts:
171         case QueryResult:
172         case ControlChar:
173         case AppStarted:
174         case AppTransfer:
175         case Item:
176         case DeviceInfo:
177         case Exit:
178             break;
179         }
180         return false;
181     }
182 
ParserStateIos::Internal::ParserState183     ParserState(Kind kind) :
184         kind(kind), gdbPort(0), qmlPort(0) { }
185 };
186 
187 class IosToolHandlerPrivate
188 {
189 public:
190     explicit IosToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
191     virtual ~IosToolHandlerPrivate();
192     virtual void requestTransferApp(const QString &bundlePath, const QString &deviceId,
193                                     int timeout = 1000) = 0;
194     virtual void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
195                                IosToolHandler::RunKind runKind,
196                                const QString &deviceId, int timeout = 1000) = 0;
197     virtual void requestDeviceInfo(const QString &deviceId, int timeout = 1000) = 0;
198     virtual bool isRunning() const = 0;
199     virtual void stop(int errorCode) = 0;
200 
201     // signals
202     void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress,
203                            int maxProgress, const QString &info);
204     void didTransferApp(const QString &bundlePath, const QString &deviceId,
205                         IosToolHandler::OpStatus status);
206     void didStartApp(const QString &bundlePath, const QString &deviceId,
207                      IosToolHandler::OpStatus status);
208     void gotServerPorts(const QString &bundlePath, const QString &deviceId, Utils::Port gdbPort,
209                         Utils::Port qmlPort);
210     void gotInferiorPid(const QString &bundlePath, const QString &deviceId, qint64 pid);
211     void deviceInfo(const QString &deviceId, const IosToolHandler::Dict &info);
212     void appOutput(const QString &output);
213     void errorMsg(const QString &msg);
214     void toolExited(int code);
215 
216 protected:
217     IosToolHandler *q;
218     QString m_deviceId;
219     QString m_bundlePath;
220     IosToolHandler::RunKind m_runKind = IosToolHandler::NormalRun;
221     IosDeviceType m_devType;
222 };
223 
224 class IosDeviceToolHandlerPrivate final : public IosToolHandlerPrivate
225 {
226     enum State {
227         NonStarted,
228         Starting,
229         StartedInferior,
230         XmlEndProcessed,
231         Stopped
232     };
233     enum Op {
234         OpNone,
235         OpAppTransfer,
236         OpDeviceInfo,
237         OpAppRun
238     };
239 public:
240     explicit IosDeviceToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
241     ~IosDeviceToolHandlerPrivate() override;
242 
243 // IosToolHandlerPrivate overrides
244 public:
245     void requestTransferApp(const QString &bundlePath, const QString &deviceId,
246                             int timeout = 1000) override;
247     void requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
248                        IosToolHandler::RunKind runKind,
249                        const QString &deviceId, int timeout = 1000) override;
250     void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override;
251     bool isRunning() const override;
252     void start(const QString &exe, const QStringList &args);
253     void stop(int errorCode) override;
254 
255 private:
256     void subprocessError(QProcess::ProcessError error);
257     void subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus);
258     void subprocessHasData();
259     void processXml();
260     void killProcess();
261 
262     QTimer killTimer;
263     std::shared_ptr<QProcess> process;
264     State state = NonStarted;
265     Op op = OpNone;
266     QXmlStreamReader outputParser;
267     QList<ParserState> stack;
268 };
269 
270 /****************************************************************************
271  * Flow to install an app on simulator:-
272  *      +------------------+
273  *      |   Transfer App   |
274  *      +--------+---------+
275  *               |
276  *               v
277  *     +---------+----------+             +--------------------------------+
278  *     |  SimulatorRunning  +---No------> +SimulatorControl::startSimulator|
279  *     +---------+----------+             +--------+-----------------------+
280  *              Yes                                |
281  *               |                                 |
282  *               v                                 |
283  * +---------+--------------------+                |
284  * | SimulatorControl::installApp | <--------------+
285  * +------------------------------+
286  *
287  *
288  *
289  * Flow to launch an app on Simulator:-
290  *            +---------+
291  *            | Run App |
292  *            +----+----+
293  *                 |
294  *                 v
295  *       +-------------------+             +----------------------------- - --+
296  *       | SimulatorRunning? +---NO------> + SimulatorControl::startSimulator |
297  *       +--------+----------+             +----------------+-----------------+
298  *                YES                                       |
299  *                 |                                        |
300  *                 v                                        |
301  * +---------+------------------------------+               |
302  * | SimulatorControl::launchAppOnSimulator | <-------------+
303  * +----------------------------------------+
304  *
305  ***************************************************************************/
306 class IosSimulatorToolHandlerPrivate : public IosToolHandlerPrivate
307 {
308 public:
309     explicit IosSimulatorToolHandlerPrivate(const IosDeviceType &devType, IosToolHandler *q);
310 
311 // IosToolHandlerPrivate overrides
312 public:
313     void requestTransferApp(const QString &appBundlePath, const QString &deviceIdentifier,
314                             int timeout = 1000) override;
315     void requestRunApp(const QString &appBundlePath, const QStringList &extraArgs,
316                        IosToolHandler::RunKind runKind,
317                        const QString &deviceIdentifier, int timeout = 1000) override;
318     void requestDeviceInfo(const QString &deviceId, int timeout = 1000) override;
319     bool isRunning() const override;
320     void stop(int errorCode) override;
321 
322 private:
323     void installAppOnSimulator();
324     void launchAppOnSimulator(const QStringList &extraArgs);
325     bool isResponseValid(const SimulatorControl::ResponseData &responseData);
326 
327 private:
328     qint64 m_pid = -1;
329     LogTailFiles outputLogger;
330     Utils::FutureSynchronizer futureSynchronizer;
331 };
332 
IosToolHandlerPrivate(const IosDeviceType & devType,Ios::IosToolHandler * q)333 IosToolHandlerPrivate::IosToolHandlerPrivate(const IosDeviceType &devType,
334                                              Ios::IosToolHandler *q) :
335     q(q),
336     m_devType(devType)
337 {
338 }
339 
340 IosToolHandlerPrivate::~IosToolHandlerPrivate() = default;
341 
342 // signals
isTransferringApp(const QString & bundlePath,const QString & deviceId,int progress,int maxProgress,const QString & info)343 void IosToolHandlerPrivate::isTransferringApp(const QString &bundlePath, const QString &deviceId,
344                                               int progress, int maxProgress, const QString &info)
345 {
346     emit q->isTransferringApp(q, bundlePath, deviceId, progress, maxProgress, info);
347 }
348 
didTransferApp(const QString & bundlePath,const QString & deviceId,Ios::IosToolHandler::OpStatus status)349 void IosToolHandlerPrivate::didTransferApp(const QString &bundlePath, const QString &deviceId,
350                     Ios::IosToolHandler::OpStatus status)
351 {
352     emit q->didTransferApp(q, bundlePath, deviceId, status);
353 }
354 
didStartApp(const QString & bundlePath,const QString & deviceId,IosToolHandler::OpStatus status)355 void IosToolHandlerPrivate::didStartApp(const QString &bundlePath, const QString &deviceId,
356                                         IosToolHandler::OpStatus status)
357 {
358     emit q->didStartApp(q, bundlePath, deviceId, status);
359 }
360 
gotServerPorts(const QString & bundlePath,const QString & deviceId,Utils::Port gdbPort,Utils::Port qmlPort)361 void IosToolHandlerPrivate::gotServerPorts(const QString &bundlePath, const QString &deviceId,
362                                            Utils::Port gdbPort, Utils::Port qmlPort)
363 {
364     emit q->gotServerPorts(q, bundlePath, deviceId, gdbPort, qmlPort);
365 }
366 
gotInferiorPid(const QString & bundlePath,const QString & deviceId,qint64 pid)367 void IosToolHandlerPrivate::gotInferiorPid(const QString &bundlePath, const QString &deviceId,
368                                            qint64 pid)
369 {
370     emit q->gotInferiorPid(q, bundlePath, deviceId, pid);
371 }
372 
deviceInfo(const QString & deviceId,const Ios::IosToolHandler::Dict & info)373 void IosToolHandlerPrivate::deviceInfo(const QString &deviceId,
374                                        const Ios::IosToolHandler::Dict &info)
375 {
376     emit q->deviceInfo(q, deviceId, info);
377 }
378 
appOutput(const QString & output)379 void IosToolHandlerPrivate::appOutput(const QString &output)
380 {
381     emit q->appOutput(q, output);
382 }
383 
errorMsg(const QString & msg)384 void IosToolHandlerPrivate::errorMsg(const QString &msg)
385 {
386     emit q->errorMsg(q, msg);
387 }
388 
toolExited(int code)389 void IosToolHandlerPrivate::toolExited(int code)
390 {
391     emit q->toolExited(q, code);
392 }
393 
subprocessError(QProcess::ProcessError error)394 void IosDeviceToolHandlerPrivate::subprocessError(QProcess::ProcessError error)
395 {
396     if (state != Stopped)
397         errorMsg(IosToolHandler::tr("iOS tool error %1").arg(error));
398     stop(-1);
399     if (error == QProcess::FailedToStart) {
400         qCDebug(toolHandlerLog) << "IosToolHandler::finished(" << this << ")";
401         emit q->finished(q);
402     }
403 }
404 
subprocessFinished(int exitCode,QProcess::ExitStatus exitStatus)405 void IosDeviceToolHandlerPrivate::subprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
406 {
407     stop((exitStatus == QProcess::NormalExit) ? exitCode : -1 );
408     qCDebug(toolHandlerLog) << "IosToolHandler::finished(" << this << ")";
409     killTimer.stop();
410     emit q->finished(q);
411 }
412 
processXml()413 void IosDeviceToolHandlerPrivate::processXml()
414 {
415     while (!outputParser.atEnd()) {
416         QXmlStreamReader::TokenType tt = outputParser.readNext();
417         //qCDebug(toolHandlerLog) << "processXml, tt=" << tt;
418         switch (tt) {
419         case QXmlStreamReader::NoToken:
420             // The reader has not yet read anything.
421             continue;
422         case QXmlStreamReader::Invalid:
423             // An error has occurred, reported in error() and errorString().
424             break;
425         case QXmlStreamReader::StartDocument:
426             // The reader reports the XML version number in documentVersion(), and the encoding
427             // as specified in the XML document in documentEncoding(). If the document is declared
428             // standalone, isStandaloneDocument() returns true; otherwise it returns false.
429             break;
430         case QXmlStreamReader::EndDocument:
431             // The reader reports the end of the document.
432             // state = XmlEndProcessed;
433             break;
434         case QXmlStreamReader::StartElement:
435             // The reader reports the start of an element with namespaceUri() and name(). Empty
436             // elements are also reported as StartElement, followed directly by EndElement.
437             // The convenience function readElementText() can be called to concatenate all content
438             // until the corresponding EndElement. Attributes are reported in attributes(),
439             // namespace declarations in namespaceDeclarations().
440         {
441             const auto elName = outputParser.name();
442             if (elName == QLatin1String("msg")) {
443                 stack.append(ParserState(ParserState::Msg));
444             } else if (elName == QLatin1String("exit")) {
445                 stack.append(ParserState(ParserState::Exit));
446                 toolExited(outputParser.attributes().value(QLatin1String("code"))
447                            .toString().toInt());
448             } else if (elName == QLatin1String("device_id")) {
449                 stack.append(ParserState(ParserState::DeviceId));
450             } else if (elName == QLatin1String("key")) {
451                 stack.append(ParserState(ParserState::Key));
452             } else if (elName == QLatin1String("value")) {
453                 stack.append(ParserState(ParserState::Value));
454             } else if (elName == QLatin1String("query_result")) {
455                 stack.append(ParserState(ParserState::QueryResult));
456             } else if (elName == QLatin1String("app_output")) {
457                 stack.append(ParserState(ParserState::AppOutput));
458             } else if (elName == QLatin1String("control_char")) {
459                 QXmlStreamAttributes attributes = outputParser.attributes();
460                 QChar c[1] = {QChar::fromLatin1(static_cast<char>(attributes.value(QLatin1String("code")).toString().toInt()))};
461                 if (stack.size() > 0 && stack.last().collectChars())
462                     stack.last().chars.append(c[0]);
463                 stack.append(ParserState(ParserState::ControlChar));
464                 break;
465             } else if (elName == QLatin1String("item")) {
466                 stack.append(ParserState(ParserState::Item));
467             } else if (elName == QLatin1String("status")) {
468                 ParserState pState(ParserState::Status);
469                 QXmlStreamAttributes attributes = outputParser.attributes();
470                 pState.progress = attributes.value(QLatin1String("progress")).toString().toInt();
471                 pState.maxProgress = attributes.value(QLatin1String("max_progress")).toString().toInt();
472                 stack.append(pState);
473             } else if (elName == QLatin1String("app_started")) {
474                 stack.append(ParserState(ParserState::AppStarted));
475                 QXmlStreamAttributes attributes = outputParser.attributes();
476                 const auto statusStr = attributes.value(QLatin1String("status"));
477                 Ios::IosToolHandler::OpStatus status = Ios::IosToolHandler::Unknown;
478                 if (statusStr.compare(QLatin1String("success"), Qt::CaseInsensitive) == 0)
479                     status = Ios::IosToolHandler::Success;
480                 else if (statusStr.compare(QLatin1String("failure"), Qt::CaseInsensitive) == 0)
481                     status = Ios::IosToolHandler::Failure;
482                 didStartApp(m_bundlePath, m_deviceId, status);
483             } else if (elName == QLatin1String("app_transfer")) {
484                 stack.append(ParserState(ParserState::AppTransfer));
485                 QXmlStreamAttributes attributes = outputParser.attributes();
486                 const auto statusStr = attributes.value(QLatin1String("status"));
487                 Ios::IosToolHandler::OpStatus status = Ios::IosToolHandler::Unknown;
488                 if (statusStr.compare(QLatin1String("success"), Qt::CaseInsensitive) == 0)
489                     status = Ios::IosToolHandler::Success;
490                 else if (statusStr.compare(QLatin1String("failure"), Qt::CaseInsensitive) == 0)
491                     status = Ios::IosToolHandler::Failure;
492                 didTransferApp(m_bundlePath, m_deviceId, status);
493             } else if (elName == QLatin1String("device_info") || elName == QLatin1String("deviceinfo")) {
494                 stack.append(ParserState(ParserState::DeviceInfo));
495             } else if (elName == QLatin1String("inferior_pid")) {
496                 stack.append(ParserState(ParserState::InferiorPid));
497             } else if (elName == QLatin1String("server_ports")) {
498                 stack.append(ParserState(ParserState::ServerPorts));
499                 QXmlStreamAttributes attributes = outputParser.attributes();
500                 Utils::Port gdbServerPort(
501                             attributes.value(QLatin1String("gdb_server")).toString().toInt());
502                 Utils::Port qmlServerPort(
503                             attributes.value(QLatin1String("qml_server")).toString().toInt());
504                 gotServerPorts(m_bundlePath, m_deviceId, gdbServerPort, qmlServerPort);
505             } else {
506                 qCWarning(toolHandlerLog) << "unexpected element " << elName;
507             }
508             break;
509         }
510         case QXmlStreamReader::EndElement:
511             // The reader reports the end of an element with namespaceUri() and name().
512         {
513             ParserState p = stack.last();
514             stack.removeLast();
515             switch (p.kind) {
516             case ParserState::Msg:
517                 errorMsg(p.chars);
518                 break;
519             case ParserState::DeviceId:
520                 if (m_deviceId.isEmpty())
521                     m_deviceId = p.chars;
522                 else
523                     QTC_CHECK(m_deviceId.compare(p.chars, Qt::CaseInsensitive) == 0);
524                 break;
525             case ParserState::Key:
526                 stack.last().key = p.chars;
527                 break;
528             case ParserState::Value:
529                 stack.last().value = p.chars;
530                 break;
531             case ParserState::Status:
532                 isTransferringApp(m_bundlePath, m_deviceId, p.progress, p.maxProgress, p.chars);
533                 break;
534             case ParserState::QueryResult:
535                 state = XmlEndProcessed;
536                 stop(0);
537                 return;
538             case ParserState::AppOutput:
539                 appOutput(p.chars);
540                 break;
541             case ParserState::ControlChar:
542                 break;
543             case ParserState::AppStarted:
544                 break;
545             case ParserState::AppTransfer:
546                 break;
547             case ParserState::Item:
548                 stack.last().info.insert(p.key, p.value);
549                 break;
550             case ParserState::DeviceInfo:
551                 deviceInfo(m_deviceId, p.info);
552                 break;
553             case ParserState::Exit:
554                 break;
555             case ParserState::InferiorPid:
556                 gotInferiorPid(m_bundlePath, m_deviceId, p.chars.toLongLong());
557                 break;
558             case ParserState::ServerPorts:
559                 break;
560             }
561             break;
562         }
563         case QXmlStreamReader::Characters:
564             // The reader reports characters in text(). If the characters are all white-space,
565             // isWhitespace() returns true. If the characters stem from a CDATA section,
566             // isCDATA() returns true.
567             if (stack.isEmpty())
568                 break;
569             if (stack.last().collectChars())
570                 stack.last().chars.append(outputParser.text());
571             break;
572         case QXmlStreamReader::Comment:
573             // The reader reports a comment in text().
574             break;
575         case QXmlStreamReader::DTD:
576             // The reader reports a DTD in text(), notation declarations in notationDeclarations(),
577             // and entity declarations in entityDeclarations(). Details of the DTD declaration are
578             // reported in in dtdName(), dtdPublicId(), and dtdSystemId().
579             break;
580         case QXmlStreamReader::EntityReference:
581             // The reader reports an entity reference that could not be resolved. The name of
582             // the reference is reported in name(), the replacement text in text().
583             break;
584         case QXmlStreamReader::ProcessingInstruction:
585             break;
586         }
587     }
588     if (outputParser.hasError()
589             && outputParser.error() != QXmlStreamReader::PrematureEndOfDocumentError) {
590         qCWarning(toolHandlerLog) << "error parsing iosTool output:" << outputParser.errorString();
591         stop(-1);
592     }
593 }
594 
killProcess()595 void IosDeviceToolHandlerPrivate::killProcess()
596 {
597     if (isRunning())
598         process->kill();
599 }
600 
subprocessHasData()601 void IosDeviceToolHandlerPrivate::subprocessHasData()
602 {
603     qCDebug(toolHandlerLog) << "subprocessHasData, state:" << state;
604     while (true) {
605         switch (state) {
606         case NonStarted:
607             qCWarning(toolHandlerLog) << "IosToolHandler unexpected state in subprocessHasData: NonStarted";
608             Q_FALLTHROUGH();
609         case Starting:
610         case StartedInferior:
611             // read some data
612         {
613             char buf[200];
614             while (isRunning()) {
615                 qint64 rRead = process->read(buf, sizeof(buf));
616                 if (rRead == -1) {
617                     stop(-1);
618                     return;
619                 }
620                 if (rRead == 0)
621                     return;
622                 qCDebug(toolHandlerLog) << "subprocessHasData read " << QByteArray(buf, rRead);
623                 outputParser.addData(QByteArray(buf, rRead));
624                 processXml();
625             }
626             break;
627         }
628         case XmlEndProcessed:
629             stop(0);
630             return;
631         case Stopped:
632             return;
633         }
634     }
635 }
636 
637 // IosDeviceToolHandlerPrivate
638 
IosDeviceToolHandlerPrivate(const IosDeviceType & devType,IosToolHandler * q)639 IosDeviceToolHandlerPrivate::IosDeviceToolHandlerPrivate(const IosDeviceType &devType,
640                                                          IosToolHandler *q)
641     : IosToolHandlerPrivate(devType, q)
642 {
643     killTimer.setSingleShot(true);
644 
645     auto deleter = [](QProcess *p) {
646         if (p->state() != QProcess::NotRunning) {
647             p->kill();
648             if (!p->waitForFinished(2000))
649                 p->terminate();
650         }
651         delete p;
652     };
653     process = std::shared_ptr<QProcess>(new QProcess, deleter);
654 
655     // Prepare & set process Environment.
656     QProcessEnvironment env(QProcessEnvironment::systemEnvironment());
657     foreach (const QString &k, env.keys())
658         if (k.startsWith(QLatin1String("DYLD_")))
659             env.remove(k);
660     QStringList frameworkPaths;
661     const Utils::FilePath libPath = IosConfigurations::developerPath().pathAppended("Platforms/iPhoneSimulator.platform/Developer/Library");
662     for (const auto framework : {"PrivateFrameworks", "OtherFrameworks", "SharedFrameworks"}) {
663         const QString frameworkPath =
664                 libPath.pathAppended(QLatin1String(framework)).toFileInfo().canonicalFilePath();
665         if (!frameworkPath.isEmpty())
666             frameworkPaths << frameworkPath;
667     }
668     frameworkPaths << "/System/Library/Frameworks" << "/System/Library/PrivateFrameworks";
669     env.insert(QLatin1String("DYLD_FALLBACK_FRAMEWORK_PATH"), frameworkPaths.join(QLatin1Char(':')));
670     qCDebug(toolHandlerLog) << "IosToolHandler runEnv:" << env.toStringList();
671     process->setProcessEnvironment(env);
672 
673     QObject::connect(process.get(), &QProcess::readyReadStandardOutput,
674                      std::bind(&IosDeviceToolHandlerPrivate::subprocessHasData,this));
675 
676     QObject::connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
677                      std::bind(&IosDeviceToolHandlerPrivate::subprocessFinished,this, _1,_2));
678 
679     QObject::connect(process.get(), &QProcess::errorOccurred,
680                      std::bind(&IosDeviceToolHandlerPrivate::subprocessError, this, _1));
681 
682     QObject::connect(&killTimer, &QTimer::timeout, std::bind(&IosDeviceToolHandlerPrivate::killProcess, this));
683 }
684 
~IosDeviceToolHandlerPrivate()685 IosDeviceToolHandlerPrivate::~IosDeviceToolHandlerPrivate()
686 {
687     if (isRunning()) {
688         // Disconnect the signals to avoid notifications while destructing.
689         // QTCREATORBUG-18147
690         process->disconnect();
691         // Quit ios-tool gracefully before kill is executed.
692         process->write("k\n\r");
693         process->closeWriteChannel();
694         // Give some time to ios-tool to finish.
695         process->waitForFinished(2000);
696     }
697 }
698 
requestTransferApp(const QString & bundlePath,const QString & deviceId,int timeout)699 void IosDeviceToolHandlerPrivate::requestTransferApp(const QString &bundlePath,
700                                                      const QString &deviceId, int timeout)
701 {
702     m_bundlePath = bundlePath;
703     m_deviceId = deviceId;
704     QStringList args;
705     args << QLatin1String("--id") << deviceId << QLatin1String("--bundle")
706          << bundlePath << QLatin1String("--timeout") << QString::number(timeout)
707          << QLatin1String("--install");
708     start(IosToolHandler::iosDeviceToolPath(), args);
709 }
710 
requestRunApp(const QString & bundlePath,const QStringList & extraArgs,IosToolHandler::RunKind runType,const QString & deviceId,int timeout)711 void IosDeviceToolHandlerPrivate::requestRunApp(const QString &bundlePath,
712                                                 const QStringList &extraArgs,
713                                                 IosToolHandler::RunKind runType,
714                                                 const QString &deviceId, int timeout)
715 {
716     m_bundlePath = bundlePath;
717     m_deviceId = deviceId;
718     m_runKind = runType;
719     QStringList args;
720     args << QLatin1String("--id") << deviceId << QLatin1String("--bundle")
721          << bundlePath << QLatin1String("--timeout") << QString::number(timeout);
722     switch (runType) {
723     case IosToolHandler::NormalRun:
724         args << QLatin1String("--run");
725         break;
726     case IosToolHandler::DebugRun:
727         args << QLatin1String("--debug");
728         break;
729     }
730     args << QLatin1String("--args") << extraArgs;
731     op = OpAppRun;
732     start(IosToolHandler::iosDeviceToolPath(), args);
733 }
734 
requestDeviceInfo(const QString & deviceId,int timeout)735 void IosDeviceToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int timeout)
736 {
737     m_deviceId = deviceId;
738     QStringList args;
739     args << QLatin1String("--id") << m_deviceId << QLatin1String("--device-info")
740          << QLatin1String("--timeout") << QString::number(timeout);
741     op = OpDeviceInfo;
742     start(IosToolHandler::iosDeviceToolPath(), args);
743 }
744 
isRunning() const745 bool IosDeviceToolHandlerPrivate::isRunning() const
746 {
747     return process && (process->state() != QProcess::NotRunning);
748 }
749 
start(const QString & exe,const QStringList & args)750 void IosDeviceToolHandlerPrivate::start(const QString &exe, const QStringList &args)
751 {
752     Q_ASSERT(process);
753     QTC_CHECK(state == NonStarted);
754     state = Starting;
755     qCDebug(toolHandlerLog) << "running " << exe << args;
756     process->start(exe, args);
757     state = StartedInferior;
758 }
759 
760 
stop(int errorCode)761 void IosDeviceToolHandlerPrivate::stop(int errorCode)
762 {
763     qCDebug(toolHandlerLog) << "IosToolHandlerPrivate::stop";
764     State oldState = state;
765     state = Stopped;
766     switch (oldState) {
767     case NonStarted:
768         qCWarning(toolHandlerLog) << "IosToolHandler::stop() when state was NonStarted";
769         Q_FALLTHROUGH();
770     case Starting:
771         switch (op){
772         case OpNone:
773             qCWarning(toolHandlerLog) << "IosToolHandler::stop() when op was OpNone";
774             break;
775         case OpAppTransfer:
776             didTransferApp(m_bundlePath, m_deviceId, IosToolHandler::Failure);
777             break;
778         case OpAppRun:
779             didStartApp(m_bundlePath, m_deviceId, IosToolHandler::Failure);
780             break;
781         case OpDeviceInfo:
782             break;
783         }
784         Q_FALLTHROUGH();
785     case StartedInferior:
786     case XmlEndProcessed:
787         toolExited(errorCode);
788         break;
789     case Stopped:
790         return;
791     }
792     if (isRunning()) {
793         process->write("k\n\r");
794         process->closeWriteChannel();
795         killTimer.start(1500);
796     }
797 }
798 
799 
800 // IosSimulatorToolHandlerPrivate
801 
IosSimulatorToolHandlerPrivate(const IosDeviceType & devType,IosToolHandler * q)802 IosSimulatorToolHandlerPrivate::IosSimulatorToolHandlerPrivate(const IosDeviceType &devType,
803                                                                IosToolHandler *q)
804     : IosToolHandlerPrivate(devType, q)
805 {
806     QObject::connect(&outputLogger, &LogTailFiles::logMessage,
807                      std::bind(&IosToolHandlerPrivate::appOutput, this, _1));
808     futureSynchronizer.setCancelOnWait(true);
809 }
810 
requestTransferApp(const QString & appBundlePath,const QString & deviceIdentifier,int timeout)811 void IosSimulatorToolHandlerPrivate::requestTransferApp(const QString &appBundlePath,
812                                                         const QString &deviceIdentifier, int timeout)
813 {
814     Q_UNUSED(timeout)
815     m_bundlePath = appBundlePath;
816     m_deviceId = deviceIdentifier;
817     isTransferringApp(m_bundlePath, m_deviceId, 0, 100, "");
818 
819     auto onSimulatorStart = [this](const SimulatorControl::ResponseData &response) {
820         if (!isResponseValid(response))
821             return;
822 
823         if (response.success) {
824             installAppOnSimulator();
825         } else {
826             errorMsg(IosToolHandler::tr("Application install on simulator failed. Simulator not running."));
827             didTransferApp(m_bundlePath, m_deviceId, IosToolHandler::Failure);
828             emit q->finished(q);
829         }
830     };
831 
832     if (SimulatorControl::isSimulatorRunning(m_deviceId))
833         installAppOnSimulator();
834     else
835         futureSynchronizer.addFuture(
836             Utils::onResultReady(SimulatorControl::startSimulator(m_deviceId), onSimulatorStart));
837 }
838 
requestRunApp(const QString & appBundlePath,const QStringList & extraArgs,IosToolHandler::RunKind runType,const QString & deviceIdentifier,int timeout)839 void IosSimulatorToolHandlerPrivate::requestRunApp(const QString &appBundlePath,
840                                                    const QStringList &extraArgs,
841                                                    IosToolHandler::RunKind runType,
842                                                    const QString &deviceIdentifier, int timeout)
843 {
844     Q_UNUSED(timeout)
845     Q_UNUSED(deviceIdentifier)
846     m_bundlePath = appBundlePath;
847     m_deviceId = m_devType.identifier;
848     m_runKind = runType;
849 
850     Utils::FilePath appBundle = Utils::FilePath::fromString(m_bundlePath);
851     if (!appBundle.exists()) {
852         errorMsg(IosToolHandler::tr("Application launch on simulator failed. Invalid bundle path %1")
853                  .arg(m_bundlePath));
854         didStartApp(m_bundlePath, m_deviceId, Ios::IosToolHandler::Failure);
855         return;
856     }
857 
858     auto onSimulatorStart = [this, extraArgs] (const SimulatorControl::ResponseData &response) {
859         if (!isResponseValid(response))
860             return;
861         if (response.success) {
862             launchAppOnSimulator(extraArgs);
863         } else {
864             errorMsg(IosToolHandler::tr("Application launch on simulator failed. Simulator not running."));
865             didStartApp(m_bundlePath, m_deviceId, Ios::IosToolHandler::Failure);
866         }
867     };
868 
869     if (SimulatorControl::isSimulatorRunning(m_deviceId))
870         launchAppOnSimulator(extraArgs);
871     else
872         futureSynchronizer.addFuture(
873             Utils::onResultReady(SimulatorControl::startSimulator(m_deviceId), onSimulatorStart));
874 }
875 
requestDeviceInfo(const QString & deviceId,int timeout)876 void IosSimulatorToolHandlerPrivate::requestDeviceInfo(const QString &deviceId, int timeout)
877 {
878     Q_UNUSED(timeout)
879     Q_UNUSED(deviceId)
880 }
881 
isRunning() const882 bool IosSimulatorToolHandlerPrivate::isRunning() const
883 {
884 #ifdef Q_OS_UNIX
885     return m_pid > 0 && (kill(m_pid, 0) == 0);
886 #else
887     return false;
888 #endif
889 }
890 
stop(int errorCode)891 void IosSimulatorToolHandlerPrivate::stop(int errorCode)
892 {
893 #ifdef Q_OS_UNIX
894     if (m_pid > 0)
895         kill(m_pid, SIGKILL);
896 #endif
897     m_pid = -1;
898     futureSynchronizer.cancelAllFutures();
899     futureSynchronizer.flushFinishedFutures();
900 
901     toolExited(errorCode);
902     emit q->finished(q);
903 }
904 
installAppOnSimulator()905 void IosSimulatorToolHandlerPrivate::installAppOnSimulator()
906 {
907     auto onResponseAppInstall = [this](const SimulatorControl::ResponseData &response) {
908         if (!isResponseValid(response))
909             return;
910 
911         if (response.success) {
912             isTransferringApp(m_bundlePath, m_deviceId, 100, 100, "");
913             didTransferApp(m_bundlePath, m_deviceId, IosToolHandler::Success);
914         } else {
915             errorMsg(IosToolHandler::tr("Application install on simulator failed. %1")
916                      .arg(response.commandOutput));
917             didTransferApp(m_bundlePath, m_deviceId, IosToolHandler::Failure);
918         }
919         emit q->finished(q);
920     };
921 
922     isTransferringApp(m_bundlePath, m_deviceId, 20, 100, "");
923     auto installFuture = SimulatorControl::installApp(m_deviceId,
924                                                       Utils::FilePath::fromString(m_bundlePath));
925     futureSynchronizer.addFuture(Utils::onResultReady(installFuture, onResponseAppInstall));
926 }
927 
launchAppOnSimulator(const QStringList & extraArgs)928 void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &extraArgs)
929 {
930     const Utils::FilePath appBundle = Utils::FilePath::fromString(m_bundlePath);
931     const QString bundleId = SimulatorControl::bundleIdentifier(appBundle);
932     const bool debugRun = m_runKind == IosToolHandler::DebugRun;
933     bool captureConsole = IosConfigurations::xcodeVersion() >= QVersionNumber(8);
934     std::shared_ptr<QTemporaryFile> stdoutFile;
935     std::shared_ptr<QTemporaryFile> stderrFile;
936 
937     if (captureConsole) {
938         const QString fileTemplate = CONSOLE_PATH_TEMPLATE.arg(m_deviceId).arg(bundleId);
939         stdoutFile.reset(new QTemporaryFile(fileTemplate + ".stdout"));
940         stderrFile.reset(new QTemporaryFile(fileTemplate + ".stderr"));
941 
942         captureConsole = stdoutFile->open() && stderrFile->open();
943         if (!captureConsole)
944             errorMsg(IosToolHandler::tr("Cannot capture console output from %1. "
945                                         "Error redirecting output to %2.*")
946                      .arg(bundleId).arg(fileTemplate));
947     } else {
948         errorMsg(IosToolHandler::tr("Cannot capture console output from %1. "
949                     "Install Xcode 8 or later.").arg(bundleId));
950     }
951 
952     auto monitorPid = [this](QFutureInterface<void> &fi, qint64 pid) {
953 #ifdef Q_OS_UNIX
954         do {
955             // Poll every 1 sec to check whether the app is running.
956             QThread::msleep(1000);
957         } while (!fi.isCanceled() && kill(pid, 0) == 0);
958 #else
959     Q_UNUSED(pid)
960 #endif
961         // Future is cancelled if the app is stopped from the qt creator.
962         if (!fi.isCanceled())
963             stop(0);
964     };
965 
966     auto onResponseAppLaunch = [=](const SimulatorControl::ResponseData &response) {
967         if (!isResponseValid(response))
968             return;
969         if (response.success) {
970             m_pid = response.pID;
971             gotInferiorPid(m_bundlePath, m_deviceId, response.pID);
972             didStartApp(m_bundlePath, m_deviceId, Ios::IosToolHandler::Success);
973             // Start monitoring app's life signs.
974             futureSynchronizer.addFuture(Utils::runAsync(monitorPid, response.pID));
975             if (captureConsole)
976                 futureSynchronizer.addFuture(Utils::runAsync(&LogTailFiles::exec, &outputLogger,
977                                                              stdoutFile, stderrFile));
978         } else {
979             m_pid = -1;
980             errorMsg(IosToolHandler::tr("Application launch on simulator failed. %1")
981                      .arg(response.commandOutput));
982             didStartApp(m_bundlePath, m_deviceId, Ios::IosToolHandler::Failure);
983             stop(-1);
984             emit q->finished(q);
985         }
986     };
987 
988     futureSynchronizer.addFuture(
989         Utils::onResultReady(SimulatorControl::launchApp(m_deviceId,
990                                                          bundleId,
991                                                          debugRun,
992                                                          extraArgs,
993                                                          captureConsole ? stdoutFile->fileName()
994                                                                         : QString(),
995                                                          captureConsole ? stderrFile->fileName()
996                                                                         : QString()),
997                              onResponseAppLaunch));
998 }
999 
isResponseValid(const SimulatorControl::ResponseData & responseData)1000 bool IosSimulatorToolHandlerPrivate::isResponseValid(const SimulatorControl::ResponseData &responseData)
1001 {
1002     if (responseData.simUdid.compare(m_deviceId) != 0) {
1003         errorMsg(IosToolHandler::tr("Invalid simulator response. Device Id mismatch. "
1004                                     "Device Id = %1 Response Id = %2")
1005                  .arg(responseData.simUdid)
1006                  .arg(m_deviceId));
1007         emit q->finished(q);
1008         return false;
1009     }
1010     return true;
1011 }
1012 
1013 } // namespace Internal
1014 
iosDeviceToolPath()1015 QString IosToolHandler::iosDeviceToolPath()
1016 {
1017     return Core::ICore::libexecPath("ios/iostool").toString();
1018 }
1019 
IosToolHandler(const Internal::IosDeviceType & devType,QObject * parent)1020 IosToolHandler::IosToolHandler(const Internal::IosDeviceType &devType, QObject *parent) :
1021     QObject(parent)
1022 {
1023     if (devType.type == Internal::IosDeviceType::IosDevice)
1024         d = new Internal::IosDeviceToolHandlerPrivate(devType, this);
1025     else
1026         d = new Internal::IosSimulatorToolHandlerPrivate(devType, this);
1027 }
1028 
~IosToolHandler()1029 IosToolHandler::~IosToolHandler()
1030 {
1031     delete d;
1032 }
1033 
stop()1034 void IosToolHandler::stop()
1035 {
1036     d->stop(-1);
1037 }
1038 
requestTransferApp(const QString & bundlePath,const QString & deviceId,int timeout)1039 void IosToolHandler::requestTransferApp(const QString &bundlePath, const QString &deviceId,
1040                                         int timeout)
1041 {
1042     d->requestTransferApp(bundlePath, deviceId, timeout);
1043 }
1044 
requestRunApp(const QString & bundlePath,const QStringList & extraArgs,RunKind runType,const QString & deviceId,int timeout)1045 void IosToolHandler::requestRunApp(const QString &bundlePath, const QStringList &extraArgs,
1046                                    RunKind runType, const QString &deviceId, int timeout)
1047 {
1048     d->requestRunApp(bundlePath, extraArgs, runType, deviceId, timeout);
1049 }
1050 
requestDeviceInfo(const QString & deviceId,int timeout)1051 void IosToolHandler::requestDeviceInfo(const QString &deviceId, int timeout)
1052 {
1053     d->requestDeviceInfo(deviceId, timeout);
1054 }
1055 
isRunning() const1056 bool IosToolHandler::isRunning() const
1057 {
1058     return d->isRunning();
1059 }
1060 
1061 } // namespace Ios
1062 
1063 #include "iostoolhandler.moc"
1064