1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "qqmldebugprocess_p.h" |
30 | |
31 | #include <QtCore/qdebug.h> |
32 | #include <QtCore/qfileinfo.h> |
33 | #include <QtCore/qdir.h> |
34 | |
35 | QQmlDebugProcess::QQmlDebugProcess(const QString &executable, QObject *parent) |
36 | : QObject(parent) |
37 | , m_executable(executable) |
38 | , m_state(SessionUnknown) |
39 | , m_port(0) |
40 | , m_maximumBindErrors(0) |
41 | , m_receivedBindErrors(0) |
42 | { |
43 | m_process.setProcessChannelMode(QProcess::MergedChannels); |
44 | m_timer.setInterval(15000); |
45 | connect(sender: &m_process, signal: &QProcess::readyReadStandardOutput, |
46 | receiver: this, slot: &QQmlDebugProcess::processAppOutput); |
47 | connect(sender: &m_process, signal: &QProcess::errorOccurred, |
48 | receiver: this, slot: &QQmlDebugProcess::processError); |
49 | connect(sender: &m_process, signal: QOverload<int, QProcess::ExitStatus>::of(ptr: &QProcess::finished), |
50 | context: this, slot: [this]() { |
51 | m_timer.stop(); |
52 | m_eventLoop.quit(); |
53 | emit finished(); |
54 | }); |
55 | connect(sender: &m_timer, signal: &QTimer::timeout, |
56 | receiver: this, slot: &QQmlDebugProcess::timeout); |
57 | } |
58 | |
59 | QQmlDebugProcess::~QQmlDebugProcess() |
60 | { |
61 | stop(); |
62 | } |
63 | |
64 | QString QQmlDebugProcess::stateString() const |
65 | { |
66 | QString stateStr; |
67 | switch (m_process.state()) { |
68 | case QProcess::NotRunning: { |
69 | stateStr = "not running" ; |
70 | if (m_process.exitStatus() == QProcess::CrashExit) |
71 | stateStr += " (crashed!)" ; |
72 | else |
73 | stateStr += ", return value " + QString::number(m_process.exitCode()); |
74 | break; |
75 | } |
76 | case QProcess::Starting: |
77 | stateStr = "starting" ; |
78 | break; |
79 | case QProcess::Running: |
80 | stateStr = "running" ; |
81 | break; |
82 | } |
83 | return stateStr; |
84 | } |
85 | |
86 | void QQmlDebugProcess::start(const QStringList &arguments) |
87 | { |
88 | #ifdef Q_OS_MAC |
89 | // make sure m_executable points to the actual binary even if it's inside an app bundle |
90 | QFileInfo binFile(m_executable); |
91 | if (!binFile.isExecutable()) { |
92 | QDir bundleDir(m_executable + ".app" ); |
93 | if (bundleDir.exists()) { |
94 | m_executable = bundleDir.absoluteFilePath("Contents/MacOS/" + binFile.baseName()); |
95 | //qDebug() << Q_FUNC_INFO << "found bundled binary" << m_executable; |
96 | } |
97 | } |
98 | #endif |
99 | m_mutex.lock(); |
100 | m_port = 0; |
101 | m_process.setEnvironment(QProcess::systemEnvironment() + m_environment); |
102 | m_process.start(program: m_executable, arguments); |
103 | if (!m_process.waitForStarted()) { |
104 | qWarning() << "QML Debug Client: Could not launch app " << m_executable |
105 | << ": " << m_process.errorString(); |
106 | m_eventLoop.quit(); |
107 | } |
108 | m_mutex.unlock(); |
109 | } |
110 | |
111 | void QQmlDebugProcess::stop() |
112 | { |
113 | if (m_process.state() != QProcess::NotRunning) { |
114 | disconnect(sender: &m_process, signal: &QProcess::errorOccurred, receiver: this, slot: &QQmlDebugProcess::processError); |
115 | m_process.kill(); |
116 | m_process.waitForFinished(msecs: 5000); |
117 | } |
118 | } |
119 | |
120 | void QQmlDebugProcess::setMaximumBindErrors(int ignore) |
121 | { |
122 | m_maximumBindErrors = ignore; |
123 | } |
124 | |
125 | void QQmlDebugProcess::timeout() |
126 | { |
127 | qWarning() << "Timeout while waiting for QML debugging messages " |
128 | "in application output. Process is in state" << m_process.state() |
129 | << ", Output:" << m_output << "." ; |
130 | } |
131 | |
132 | bool QQmlDebugProcess::waitForSessionStart() |
133 | { |
134 | if (m_process.state() != QProcess::Running) { |
135 | qWarning() << "Could not start up " << m_executable; |
136 | return false; |
137 | } else if (m_state == SessionStarted) { |
138 | return true; |
139 | } else if (m_state == SessionFailed) { |
140 | return false; |
141 | } |
142 | |
143 | m_timer.start(); |
144 | m_eventLoop.exec(); |
145 | |
146 | return m_state == SessionStarted; |
147 | } |
148 | |
149 | int QQmlDebugProcess::debugPort() const |
150 | { |
151 | return m_port; |
152 | } |
153 | |
154 | bool QQmlDebugProcess::waitForFinished() |
155 | { |
156 | return m_process.waitForFinished(); |
157 | } |
158 | |
159 | QProcess::ProcessState QQmlDebugProcess::state() const |
160 | { |
161 | return m_process.state(); |
162 | } |
163 | |
164 | QProcess::ExitStatus QQmlDebugProcess::exitStatus() const |
165 | { |
166 | return m_process.exitStatus(); |
167 | } |
168 | |
169 | void QQmlDebugProcess::addEnvironment(const QString &environment) |
170 | { |
171 | m_environment.append(t: environment); |
172 | } |
173 | |
174 | QString QQmlDebugProcess::output() const |
175 | { |
176 | return m_output; |
177 | } |
178 | |
179 | void QQmlDebugProcess::processAppOutput() |
180 | { |
181 | m_mutex.lock(); |
182 | |
183 | bool outputFromAppItself = false; |
184 | |
185 | QString newOutput = m_process.readAll(); |
186 | m_output.append(s: newOutput); |
187 | m_outputBuffer.append(s: newOutput); |
188 | |
189 | while (true) { |
190 | const int nlIndex = m_outputBuffer.indexOf(c: QLatin1Char('\n')); |
191 | if (nlIndex < 0) // no further complete lines |
192 | break; |
193 | const QString line = m_outputBuffer.left(n: nlIndex); |
194 | m_outputBuffer = m_outputBuffer.right(n: m_outputBuffer.size() - nlIndex - 1); |
195 | |
196 | if (line.contains(s: "QML Debugger:" )) { |
197 | const QRegExp portRx("Waiting for connection on port (\\d+)" ); |
198 | if (portRx.indexIn(str: line) != -1) { |
199 | m_port = portRx.cap(nth: 1).toInt(); |
200 | m_timer.stop(); |
201 | m_state = SessionStarted; |
202 | m_eventLoop.quit(); |
203 | continue; |
204 | } |
205 | if (line.contains(s: "Unable to listen" )) { |
206 | if (++m_receivedBindErrors >= m_maximumBindErrors) { |
207 | if (m_maximumBindErrors == 0) |
208 | qWarning() << "App was unable to bind to port!" ; |
209 | m_timer.stop(); |
210 | m_state = SessionFailed; |
211 | m_eventLoop.quit(); |
212 | } |
213 | continue; |
214 | } |
215 | } |
216 | |
217 | // set to true if there is output not coming from the debugger or we don't understand it |
218 | outputFromAppItself = true; |
219 | } |
220 | m_mutex.unlock(); |
221 | |
222 | if (outputFromAppItself) |
223 | emit readyReadStandardOutput(); |
224 | } |
225 | |
226 | void QQmlDebugProcess::processError(QProcess::ProcessError error) |
227 | { |
228 | qDebug() << "An error occurred while waiting for debug process to become available:" ; |
229 | switch (error) { |
230 | case QProcess::FailedToStart: |
231 | qDebug() << "Process failed to start." ; |
232 | break; |
233 | case QProcess::Crashed: |
234 | qDebug() << "Process crashed." ; |
235 | break; |
236 | case QProcess::Timedout: |
237 | qDebug() << "Process timed out." ; |
238 | break; |
239 | case QProcess::WriteError: |
240 | qDebug() << "Error while writing to process." ; |
241 | break; |
242 | case QProcess::ReadError: |
243 | qDebug() << "Error while reading from process." ; |
244 | break; |
245 | case QProcess::UnknownError: |
246 | qDebug() << "Unknown process error." ; |
247 | break; |
248 | } |
249 | } |
250 | |