aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcus Tillmanns <[email protected]>2025-06-23 09:10:52 +0200
committerMarcus Tillmanns <[email protected]>2025-07-01 09:17:25 +0000
commitc7631c36d1cf0d912032adce4f11b9d598d25d7f (patch)
treebbf0e825d7c5b25ed5bc9e57d5d0ee4667b0870d
parent3f78b213f3ca157e2a6f2ce56561b31cbb41de96 (diff)
Utils: Add WrapperProcessInterfaceHEADmaster
Helper class to simplify implementing process interfaces that wrap the actual call inside another process call like docker or ssh. Change-Id: I78b0f403ddbb928259b31683b599bdefaf13888d Reviewed-by: hjk <[email protected]>
-rw-r--r--src/libs/utils/processinterface.cpp190
-rw-r--r--src/libs/utils/processinterface.h25
2 files changed, 215 insertions, 0 deletions
diff --git a/src/libs/utils/processinterface.cpp b/src/libs/utils/processinterface.cpp
index a2dc746905c..fabc4868238 100644
--- a/src/libs/utils/processinterface.cpp
+++ b/src/libs/utils/processinterface.cpp
@@ -4,6 +4,11 @@
#include "processinterface.h"
#include "qtcassert.h"
+#include "qtcprocess.h"
+
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(wrappedProcessInterface, "qtc.wrappedprocessinterface", QtWarningMsg)
namespace Utils {
@@ -37,4 +42,189 @@ int ProcessInterface::controlSignalToInt(ControlSignal controlSignal)
return 0;
}
+namespace Internal {
+class WrappedProcessInterfacePrivate
+{
+public:
+ Process m_process;
+ bool m_hasReceivedFirstOutput = false;
+ qint64 m_remotePID = 0;
+ QString m_unexpectedStartupOutput;
+ bool m_forwardStdout = false;
+ bool m_forwardStderr = false;
+};
+
+} // namespace Internal
+
+WrappedProcessInterface::WrappedProcessInterface()
+ : d(std::make_unique<Internal::WrappedProcessInterfacePrivate>())
+{
+ d->m_process.setParent(this);
+
+ connect(&d->m_process, &Process::started, this, [this] {
+ qCDebug(wrappedProcessInterface) << "Process started:" << d->m_process.commandLine();
+
+ if (m_setup.m_ptyData.has_value()) {
+ d->m_hasReceivedFirstOutput = true;
+ emit started(d->m_process.processId(), d->m_process.applicationMainThreadId());
+ }
+ });
+
+ connect(&d->m_process, &Process::readyReadStandardOutput, this, [this] {
+ if (d->m_hasReceivedFirstOutput) {
+ emit readyRead(d->m_process.readAllRawStandardOutput(), {});
+ return;
+ }
+
+ QByteArray output = d->m_process.readAllRawStandardOutput();
+ qsizetype idx = output.indexOf('\n');
+ QByteArrayView firstLine = output.left(idx).trimmed();
+ QByteArrayView rest = output.mid(idx + 1);
+
+ qCDebug(wrappedProcessInterface)
+ << "Process first line received:" << d->m_process.commandLine() << firstLine;
+
+ if (!firstLine.startsWith("__qtc")) {
+ d->m_unexpectedStartupOutput = QString::fromUtf8(firstLine);
+ d->m_process.kill();
+ return;
+ }
+
+ bool ok = false;
+ d->m_remotePID = firstLine.mid(5, firstLine.size() - 5 - 5).toLongLong(&ok);
+
+ if (ok)
+ emit started(d->m_remotePID);
+ else {
+ d->m_unexpectedStartupOutput = QString::fromUtf8(firstLine);
+ d->m_process.kill();
+ return;
+ }
+
+ d->m_hasReceivedFirstOutput = true;
+
+ if (d->m_forwardStdout && rest.size() > 0) {
+ fprintf(stdout, "%s", rest.constData());
+ rest = QByteArrayView();
+ }
+
+ // In case we already received some error output, send it now.
+ QByteArray stdErr = d->m_process.readAllRawStandardError();
+ if (stdErr.size() > 0 && d->m_forwardStderr) {
+ fprintf(stderr, "%s", stdErr.constData());
+ stdErr.clear();
+ }
+
+ if (rest.size() > 0 || stdErr.size() > 0)
+ emit readyRead(rest.toByteArray(), stdErr);
+ });
+
+ connect(&d->m_process, &Process::readyReadStandardError, this, [this] {
+ if (!d->m_remotePID)
+ return;
+
+ if (d->m_forwardStderr) {
+ fprintf(stderr, "%s", d->m_process.readAllRawStandardError().constData());
+ return;
+ }
+
+ emit readyRead({}, d->m_process.readAllRawStandardError());
+ });
+
+ connect(&d->m_process, &Process::done, this, [this] {
+ qCDebug(wrappedProcessInterface) << "Process exited:" << d->m_process.commandLine()
+ << "with code:" << d->m_process.resultData().m_exitCode;
+
+ ProcessResultData resultData = d->m_process.resultData();
+
+ if (d->m_remotePID == 0 && !d->m_hasReceivedFirstOutput) {
+ resultData.m_error = QProcess::FailedToStart;
+ resultData.m_errorString = d->m_unexpectedStartupOutput;
+
+ qCWarning(wrappedProcessInterface)
+ << "Process failed to start:" << d->m_process.commandLine() << ":"
+ << d->m_unexpectedStartupOutput;
+ QByteArray stdOut = d->m_process.readAllRawStandardOutput();
+ QByteArray stdErr = d->m_process.readAllRawStandardError();
+
+ if (!stdOut.isEmpty())
+ qCWarning(wrappedProcessInterface) << "stdout:" << stdOut;
+ if (!stdErr.isEmpty())
+ qCWarning(wrappedProcessInterface) << "stderr:" << stdErr;
+ }
+
+ emit done(resultData);
+ });
+}
+
+WrappedProcessInterface::~WrappedProcessInterface()
+{
+ if (d->m_process.state() == QProcess::Running)
+ sendControlSignal(ControlSignal::Kill);
+}
+
+void WrappedProcessInterface::start()
+{
+ d->m_process.setProcessMode(m_setup.m_processMode);
+ d->m_process.setTerminalMode(m_setup.m_terminalMode);
+ d->m_process.setPtyData(m_setup.m_ptyData);
+ d->m_process.setReaperTimeout(m_setup.m_reaperTimeout);
+ d->m_process.setWriteData(m_setup.m_writeData);
+ // We need separate channels so we can intercept our Process ID markers.
+ d->m_process.setProcessChannelMode(QProcess::ProcessChannelMode::SeparateChannels);
+ d->m_process.setExtraData(m_setup.m_extraData);
+ d->m_process.setStandardInputFile(m_setup.m_standardInputFile);
+ d->m_process.setAbortOnMetaChars(m_setup.m_abortOnMetaChars);
+ d->m_process.setCreateConsoleOnWindows(m_setup.m_createConsoleOnWindows);
+ if (m_setup.m_lowPriority)
+ d->m_process.setLowPriority();
+
+ d->m_forwardStdout = m_setup.m_processChannelMode == QProcess::ForwardedChannels
+ || m_setup.m_processChannelMode == QProcess::ForwardedOutputChannel;
+ d->m_forwardStderr = m_setup.m_processChannelMode == QProcess::ForwardedChannels
+ || m_setup.m_processChannelMode == QProcess::ForwardedErrorChannel;
+
+ const Result<CommandLine> fullCommandLine = wrapCommmandLine(m_setup, "__qtc%1qtc__");
+
+ if (!fullCommandLine) {
+ emit done(ProcessResultData{
+ -1,
+ QProcess::ExitStatus::CrashExit,
+ QProcess::ProcessError::FailedToStart,
+ fullCommandLine.error(),
+ });
+ return;
+ }
+
+ d->m_process.setCommand(*fullCommandLine);
+ d->m_process.start();
+}
+
+qint64 WrappedProcessInterface::write(const QByteArray &data)
+{
+ return d->m_process.writeRaw(data);
+}
+
+void WrappedProcessInterface::sendControlSignal(ControlSignal controlSignal)
+{
+ if (!m_setup.m_ptyData.has_value()) {
+ QTC_ASSERT(d->m_remotePID, return);
+ if (controlSignal == ControlSignal::CloseWriteChannel) {
+ d->m_process.closeWriteChannel();
+ return;
+ }
+ forwardControlSignal(controlSignal, d->m_remotePID);
+ } else {
+ // clang-format off
+ switch (controlSignal) {
+ case ControlSignal::Terminate: d->m_process.terminate(); break;
+ case ControlSignal::Kill: d->m_process.kill(); break;
+ case ControlSignal::Interrupt: d->m_process.interrupt(); break;
+ case ControlSignal::KickOff: d->m_process.kickoffProcess(); break;
+ case ControlSignal::CloseWriteChannel: break;
+ }
+ // clang-format on
+ }
+}
+
} // Utils
diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h
index a29e3c78dc5..ff0ed3167be 100644
--- a/src/libs/utils/processinterface.h
+++ b/src/libs/utils/processinterface.h
@@ -157,4 +157,29 @@ private:
friend class Internal::ProcessPrivate;
};
+namespace Internal {
+class WrappedProcessInterfacePrivate;
+}
+
+class QTCREATOR_UTILS_EXPORT WrappedProcessInterface : public ProcessInterface
+{
+public:
+ WrappedProcessInterface();
+ ~WrappedProcessInterface() override;
+
+public:
+ virtual Result<CommandLine> wrapCommmandLine(
+ const ProcessSetupData &setupData, const QString &markerTemplate) const
+ = 0;
+ virtual void forwardControlSignal(ControlSignal controlSignal, qint64 remotePid) const = 0;
+
+public:
+ void start() final;
+ qint64 write(const QByteArray &data) final;
+ void sendControlSignal(ControlSignal controlSignal) final;
+
+private:
+ std::unique_ptr<Internal::WrappedProcessInterfacePrivate> d;
+};
+
} // namespace Utils