diff options
| author | Semih Yavuz <semih.yavuz@qt.io> | 2026-06-02 11:37:42 +0200 |
|---|---|---|
| committer | Semih Yavuz <semih.yavuz@qt.io> | 2026-06-24 19:35:10 +0000 |
| commit | 265c9250df853df1110ca4c2545b100481b98a2e (patch) | |
| tree | b3f2ac2a7078f1c6e8b1fdcd8a39c5e445bb70b4 /tools | |
| parent | 895cb0a539472302ad22df98d4e11ddad0cf69f6 (diff) | |
qmlpreview CLI: handle hot reload failures
Enable qmlpreview to recover from hot reload failures.
- Taking inspiration from QtC qmlpreview plugin, add infrastructure for
recording and replaying user input events.
- Refactor QmlPreviewApplication signal connections for clarity, extract
process lifecycle management into startProcess, killProcess,
restartProcess
Pick-to: 6.12
Task-number: QTBUG-146122
Change-Id: Ib7237535b86ece0c5647834604170f9ba6ee0f29
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/qmlpreview/qmlpreviewapplication.cpp | 102 | ||||
| -rw-r--r-- | tools/qmlpreview/qmlpreviewapplication.h | 9 |
2 files changed, 88 insertions, 23 deletions
diff --git a/tools/qmlpreview/qmlpreviewapplication.cpp b/tools/qmlpreview/qmlpreviewapplication.cpp index 37a29d60e8..7a46716cf4 100644 --- a/tools/qmlpreview/qmlpreviewapplication.cpp +++ b/tools/qmlpreview/qmlpreviewapplication.cpp @@ -17,6 +17,18 @@ #include <QtCore/QFile> #include <QtCore/QLibraryInfo> +static QString qmlPreviewServices() +{ + return QStringLiteral("QmlPreview,CanvasFrameRate,EventReplay"); +} + +static QString makeQmlPreviewArgument(const QString &socketFile) +{ + return QString("-qmljsdebugger=file:%1,block,services:%2") + .arg(socketFile) + .arg(qmlPreviewServices()); +} + bool QmlPreviewApplication::argumentsFromCommandLineAndFile( QStringList &allArguments, const QStringList &arguments) { @@ -54,25 +66,27 @@ QmlPreviewApplication::QmlPreviewApplication(int &argc, char **argv) : { m_connection.reset(new QQmlDebugConnection); m_qmlPreviewClient.reset(new QQmlPreviewClient(m_connection.data())); - m_connectTimer.setInterval(1000); m_loadTimer.setInterval(100); m_loadTimer.setSingleShot(true); - connect(&m_loadTimer, &QTimer::timeout, this, [this]() { - m_qmlPreviewClient->triggerLoad(QUrl()); - }); + connect(&m_loadTimer, &QTimer::timeout, this, + [this]() { m_qmlPreviewClient->loadUrl(QUrl()); }); + m_connectTimer.setInterval(1000); connect(&m_connectTimer, &QTimer::timeout, this, &QmlPreviewApplication::tryToConnect); - connect(m_connection.data(), &QQmlDebugConnection::connected, &m_connectTimer, &QTimer::stop); - connect(m_connection.data(), &QQmlDebugConnection::connected, this, [this]() { - QQmlPreviewClient::Settings settings; - settings.enableInPlaceUpdates = true; - m_qmlPreviewClient->sendConfiguration(settings); - const QString status = QString::fromUtf8("Inplace updates configuration: %1") - .arg(settings.enableInPlaceUpdates ? "enabled" : "disabled"); - logStatus(status); - }); + connectConnectionSignals(); + connectQmlPreviewClientSignals(); + connectWatcherSignals(); +} + +QmlPreviewApplication::~QmlPreviewApplication() +{ + killProcess(); +} + +void QmlPreviewApplication::connectQmlPreviewClientSignals() +{ connect(m_qmlPreviewClient.data(), &QQmlPreviewClient::error, this, &QmlPreviewApplication::logError); connect(m_qmlPreviewClient.data(), &QQmlPreviewClient::request, this, @@ -84,13 +98,35 @@ QmlPreviewApplication::QmlPreviewApplication(int &argc, char **argv) : .arg(m_confirmedSettings.enableInPlaceUpdates ? "enabled" : "disabled"); logStatus(status); }); + connect(m_qmlPreviewClient.data(), &QQmlPreviewClient::hotReloadFailure, this, + [this](const QString &reason) { + logError(QString::fromUtf8("Hot reload failure: %1").arg(reason)); + restartProcess(); + }); +} + +void QmlPreviewApplication::connectConnectionSignals() +{ + connect(m_connection.data(), &QQmlDebugConnection::connected, this, [this]() { + QQmlPreviewClient::Settings settings; + settings.enableInPlaceUpdates = true; + m_qmlPreviewClient->sendConfiguration(settings); + const QString status = QString::fromUtf8("Inplace updates configuration: %1") + .arg(settings.enableInPlaceUpdates ? "enabled" : "disabled"); + logStatus(status); + m_connectTimer.stop(); + }); +} + +void QmlPreviewApplication::connectWatcherSignals() +{ connect(&m_watcher, &QmlPreviewFileSystemWatcher::fileChanged, this, &QmlPreviewApplication::sendFile); connect(&m_watcher, &QmlPreviewFileSystemWatcher::directoryChanged, this, &QmlPreviewApplication::sendDirectory); } -QmlPreviewApplication::~QmlPreviewApplication() +void QmlPreviewApplication::killProcess() { if (m_process && m_process->state() != QProcess::NotRunning) { logStatus("Terminating process ..."); @@ -103,6 +139,23 @@ QmlPreviewApplication::~QmlPreviewApplication() } } +void QmlPreviewApplication::restartProcess() +{ + logStatus(QString::fromUtf8("Restarting process ...")); + + if (m_process) { + disconnect(m_process.data(), nullptr, this, nullptr); + } + + killProcess(); + + // reset the connection + m_connection->close(); + m_connectionAttempts = 0; + m_connection->startLocalServer(m_socketFile); + startProcess(); +} + void QmlPreviewApplication::parseArguments() { setApplicationName(QLatin1String("qmlpreview")); @@ -167,22 +220,18 @@ int QmlPreviewApplication::exec() return QCoreApplication::exec(); } -void QmlPreviewApplication::run() +void QmlPreviewApplication::startProcess() { - logStatus(QString("Listening on %1 ...").arg(m_socketFile)); - m_connection->startLocalServer(m_socketFile); m_process.reset(new QProcess(this)); - QStringList arguments; - arguments << QString("-qmljsdebugger=file:%1,block,services:QmlPreview").arg(m_socketFile); - arguments << m_arguments; - m_process->setProcessChannelMode(QProcess::MergedChannels); connect(m_process.data(), &QIODevice::readyRead, this, &QmlPreviewApplication::processHasOutput); connect(m_process.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int){ processFinished(); }); - logStatus(QString("Starting '%1 %2' ...").arg(m_executablePath, arguments.join(QLatin1Char(' ')))); - m_process->start(m_executablePath, arguments); + m_arguments.append(makeQmlPreviewArgument(m_socketFile)); + logStatus(QString("Starting '%1 %2' ...") + .arg(m_executablePath, m_arguments.join(QLatin1Char(' ')))); + m_process->start(m_executablePath, m_arguments); if (!m_process->waitForStarted()) { logError(QString("Could not run '%1': %2").arg(m_executablePath, m_process->errorString())); exit(1); @@ -190,6 +239,13 @@ void QmlPreviewApplication::run() m_connectTimer.start(); } +void QmlPreviewApplication::run() +{ + logStatus(QString("Listening on %1 ...").arg(m_socketFile)); + m_connection->startLocalServer(m_socketFile); + startProcess(); +} + void QmlPreviewApplication::tryToConnect() { Q_ASSERT(!m_connection->isConnected()); diff --git a/tools/qmlpreview/qmlpreviewapplication.h b/tools/qmlpreview/qmlpreviewapplication.h index 017974bfcd..09f26a3e73 100644 --- a/tools/qmlpreview/qmlpreviewapplication.h +++ b/tools/qmlpreview/qmlpreviewapplication.h @@ -42,6 +42,14 @@ private: bool sendFile(const QString &path); void sendDirectory(const QString &path); + void connectQmlPreviewClientSignals(); + void connectConnectionSignals(); + void connectWatcherSignals(); + + void killProcess(); + void restartProcess(); + void startProcess(); + QString m_executablePath; QStringList m_arguments; QScopedPointer<QProcess> m_process; @@ -53,6 +61,7 @@ private: QHash<QString, QString> m_localToResourcePath; QScopedPointer<QQmlDebugConnection> m_connection; QScopedPointer<QQmlPreviewClient> m_qmlPreviewClient; + QScopedPointer<QQmlProfilerEventReceiver> m_eventReceiver; QmlPreviewFileSystemWatcher m_watcher; QTimer m_loadTimer; |
