forked from qt-creator/qt-creator
Utils: Add deviceshell generic class
Change-Id: Ibfb16a0f13f9fe119d27055db5897213127dd104 Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
@@ -31,6 +31,7 @@ add_qtc_library(Utils
|
|||||||
delegates.cpp delegates.h
|
delegates.cpp delegates.h
|
||||||
detailsbutton.cpp detailsbutton.h
|
detailsbutton.cpp detailsbutton.h
|
||||||
detailswidget.cpp detailswidget.h
|
detailswidget.cpp detailswidget.h
|
||||||
|
deviceshell.cpp deviceshell.h
|
||||||
differ.cpp differ.h
|
differ.cpp differ.h
|
||||||
displayname.cpp displayname.h
|
displayname.cpp displayname.h
|
||||||
dropsupport.cpp dropsupport.h
|
dropsupport.cpp dropsupport.h
|
||||||
|
285
src/libs/utils/deviceshell.cpp
Normal file
285
src/libs/utils/deviceshell.cpp
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 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 "deviceshell.h"
|
||||||
|
#include "processinterface.h"
|
||||||
|
|
||||||
|
#include <qtcassert.h>
|
||||||
|
#include <qtcprocess.h>
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(deviceShellLog, "qtc.utils.deviceshell", QtWarningMsg)
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
DeviceShell::DeviceShell()
|
||||||
|
{
|
||||||
|
m_thread.setObjectName("Shell Thread");
|
||||||
|
m_thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceShell::~DeviceShell()
|
||||||
|
{
|
||||||
|
if (m_thread.isRunning()) {
|
||||||
|
m_thread.quit();
|
||||||
|
m_thread.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceShell::waitForStarted()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_shellProcess, return false);
|
||||||
|
Q_ASSERT(QThread::currentThread() != &m_thread);
|
||||||
|
|
||||||
|
bool result;
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
m_shellProcess,
|
||||||
|
[this] { return m_shellProcess->waitForStarted(); },
|
||||||
|
Qt::BlockingQueuedConnection,
|
||||||
|
&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief DeviceShell::runInShell
|
||||||
|
* \param cmd The command to run
|
||||||
|
* \param stdInData Data to send to the stdin of the command
|
||||||
|
* \return true if the command finished with EXIT_SUCCESS(0)
|
||||||
|
*
|
||||||
|
* Runs the cmd inside the internal shell process and return whether it exited with EXIT_SUCCESS
|
||||||
|
*
|
||||||
|
* Will automatically defer to the internal thread
|
||||||
|
*/
|
||||||
|
bool DeviceShell::runInShell(const CommandLine &cmd, const QByteArray &stdInData)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_shellProcess, return false);
|
||||||
|
Q_ASSERT(QThread::currentThread() != &m_thread);
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
m_shellProcess,
|
||||||
|
[this, &cmd, &stdInData] { return runInShellImpl(cmd, stdInData); },
|
||||||
|
Qt::BlockingQueuedConnection,
|
||||||
|
&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceShell::runInShellImpl(const CommandLine &cmd, const QByteArray &stdInData)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(QThread::currentThread() == &m_thread, return false);
|
||||||
|
|
||||||
|
QTC_ASSERT(m_shellProcess->isRunning(), return false);
|
||||||
|
QTC_ASSERT(m_shellProcess, return false);
|
||||||
|
QTC_CHECK(m_shellProcess->readAllStandardOutput().isNull()); // clean possible left-overs
|
||||||
|
QTC_CHECK(m_shellProcess->readAllStandardError().isNull()); // clean possible left-overs
|
||||||
|
auto cleanup = qScopeGuard(
|
||||||
|
[this] { m_shellProcess->readAllStandardOutput(); }); // clean on assert
|
||||||
|
|
||||||
|
QString prefix;
|
||||||
|
if (!stdInData.isEmpty())
|
||||||
|
prefix = "echo '" + QString::fromUtf8(stdInData.toBase64()) + "' | base64 -d | ";
|
||||||
|
|
||||||
|
const QString suffix = " > /dev/null 2>&1\necho $?\n";
|
||||||
|
const QString command = prefix + cmd.toUserOutput() + suffix;
|
||||||
|
|
||||||
|
qCDebug(deviceShellLog) << "Running:" << command;
|
||||||
|
|
||||||
|
m_shellProcess->write(command);
|
||||||
|
m_shellProcess->waitForReadyRead();
|
||||||
|
|
||||||
|
const QByteArray output = m_shellProcess->readAllStandardOutput();
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
const int result = output.toInt(&ok);
|
||||||
|
|
||||||
|
qCInfo(deviceShellLog) << "Run command in shell:" << cmd.toUserOutput() << "result: " << output
|
||||||
|
<< " ==>" << result;
|
||||||
|
QTC_ASSERT(ok, return false);
|
||||||
|
|
||||||
|
return result == EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief DeviceShell::outputForRunInShell
|
||||||
|
* \param cmd The command to run
|
||||||
|
* \param stdInData Data to send to the stdin of the command
|
||||||
|
* \return The stdout of the command
|
||||||
|
*
|
||||||
|
* Runs a command inside the running shell and returns the stdout that was generated by it.
|
||||||
|
*
|
||||||
|
* Will automatically defer to the internal thread
|
||||||
|
*/
|
||||||
|
DeviceShell::RunResult DeviceShell::outputForRunInShell(const CommandLine &cmd,
|
||||||
|
const QByteArray &stdInData)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_shellProcess, return {});
|
||||||
|
Q_ASSERT(QThread::currentThread() != &m_thread);
|
||||||
|
|
||||||
|
RunResult result;
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
m_shellProcess,
|
||||||
|
[this, &cmd, &stdInData] { return outputForRunInShellImpl(cmd, stdInData); },
|
||||||
|
Qt::BlockingQueuedConnection,
|
||||||
|
&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceShell::RunResult DeviceShell::outputForRunInShellImpl(const CommandLine &cmd,
|
||||||
|
const QByteArray &stdInData)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(QThread::currentThread() == &m_thread, return {});
|
||||||
|
|
||||||
|
QTC_ASSERT(m_shellProcess, return {});
|
||||||
|
QTC_CHECK(m_shellProcess->readAllStandardOutput().isNull()); // clean possible left-overs
|
||||||
|
QTC_CHECK(m_shellProcess->readAllStandardError().isNull()); // clean possible left-overs
|
||||||
|
auto cleanup = qScopeGuard(
|
||||||
|
[this] { m_shellProcess->readAllStandardOutput(); }); // clean on assert
|
||||||
|
|
||||||
|
QString prefix;
|
||||||
|
if (!stdInData.isEmpty())
|
||||||
|
prefix = "echo '" + QString::fromUtf8(stdInData.toBase64()) + "' | base64 -d | ";
|
||||||
|
|
||||||
|
const QString markerCmd = "echo __qtc$?qtc__ 1>&2\n";
|
||||||
|
const QString suffix = "\n" + markerCmd;
|
||||||
|
const QString command = prefix + cmd.toUserOutput() + suffix;
|
||||||
|
|
||||||
|
qCDebug(deviceShellLog) << "Running:" << command;
|
||||||
|
m_shellProcess->write(command);
|
||||||
|
|
||||||
|
RunResult result;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
m_shellProcess->waitForReadyRead();
|
||||||
|
QByteArray stdErr = m_shellProcess->readAllStandardError();
|
||||||
|
if (stdErr.endsWith("qtc__\n")) {
|
||||||
|
QByteArray marker = stdErr.right(stdErr.length() - stdErr.lastIndexOf("__qtc"));
|
||||||
|
QByteArray exitCodeStr = marker.mid(5, marker.length() - 11);
|
||||||
|
bool ok = false;
|
||||||
|
const int exitCode = exitCodeStr.toInt(&ok);
|
||||||
|
|
||||||
|
result.stdOutput = m_shellProcess->readAllStandardOutput();
|
||||||
|
result.exitCode = ok ? exitCode : -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//const QByteArray output = m_shellProcess->readAllStandardOutput();
|
||||||
|
qCDebug(deviceShellLog) << "Received output:" << result.stdOutput;
|
||||||
|
qCInfo(deviceShellLog) << "Run command in shell:" << cmd.toUserOutput()
|
||||||
|
<< "output size:" << result.stdOutput.size()
|
||||||
|
<< "exit code:" << result.exitCode;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceShell::close()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(QThread::currentThread() == thread(), return );
|
||||||
|
QTC_ASSERT(m_thread.isRunning(), return );
|
||||||
|
|
||||||
|
m_thread.quit();
|
||||||
|
m_thread.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief DeviceShell::setupShellProcess
|
||||||
|
*
|
||||||
|
* Override this function to setup the shell process.
|
||||||
|
* The default implementation just sets the command line to "bash"
|
||||||
|
*/
|
||||||
|
void DeviceShell::setupShellProcess(QtcProcess* shellProcess)
|
||||||
|
{
|
||||||
|
shellProcess->setCommand(CommandLine{"bash"});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief DeviceShell::startupFailed
|
||||||
|
*
|
||||||
|
* Override to display custom error messages
|
||||||
|
*/
|
||||||
|
void DeviceShell::startupFailed(const CommandLine &cmdLine)
|
||||||
|
{
|
||||||
|
qCDebug(deviceShellLog) << "Failed to start shell via:" << cmdLine.toUserOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief DeviceShell::start
|
||||||
|
* \return Returns true if starting the Shell process succeeded
|
||||||
|
*
|
||||||
|
* \note You have to call this function when deriving from DeviceShell. Current implementations call the function from their constructor.
|
||||||
|
*/
|
||||||
|
bool DeviceShell::start()
|
||||||
|
{
|
||||||
|
m_shellProcess = new QtcProcess();
|
||||||
|
connect(m_shellProcess, &QtcProcess::done, this, [this] { emit done(); });
|
||||||
|
connect(m_shellProcess, &QtcProcess::errorOccurred, this, &DeviceShell::errorOccurred);
|
||||||
|
connect(m_shellProcess, &QObject::destroyed, this, [this] { m_shellProcess = nullptr; });
|
||||||
|
connect(&m_thread, &QThread::finished, m_shellProcess, [this] { closeShellProcess(); });
|
||||||
|
|
||||||
|
setupShellProcess(m_shellProcess);
|
||||||
|
|
||||||
|
m_shellProcess->setProcessMode(ProcessMode::Writer);
|
||||||
|
m_shellProcess->setWriteData("echo\n");
|
||||||
|
|
||||||
|
// Moving the process into its own thread ...
|
||||||
|
m_shellProcess->moveToThread(&m_thread);
|
||||||
|
|
||||||
|
bool result = false;
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
m_shellProcess,
|
||||||
|
[this] {
|
||||||
|
m_shellProcess->start();
|
||||||
|
|
||||||
|
if (!m_shellProcess->waitForStarted() || !m_shellProcess->waitForReadyRead()
|
||||||
|
|| m_shellProcess->readAllStandardOutput() != "\n") {
|
||||||
|
closeShellProcess();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO: Check if necessary tools are available ( e.g. base64, /bin/sh etc. )
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Qt::BlockingQueuedConnection,
|
||||||
|
&result);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
startupFailed(m_shellProcess->commandLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceShell::closeShellProcess()
|
||||||
|
{
|
||||||
|
if (m_shellProcess) {
|
||||||
|
if (m_shellProcess->isRunning()) {
|
||||||
|
m_shellProcess->write("exit\n");
|
||||||
|
if (!m_shellProcess->waitForFinished(2000))
|
||||||
|
m_shellProcess->terminate();
|
||||||
|
}
|
||||||
|
m_shellProcess->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Utils
|
81
src/libs/utils/deviceshell.h
Normal file
81
src/libs/utils/deviceshell.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 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 "utils_global.h"
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
class CommandLine;
|
||||||
|
class QtcProcess;
|
||||||
|
|
||||||
|
class DeviceShellImpl;
|
||||||
|
|
||||||
|
class QTCREATOR_UTILS_EXPORT DeviceShell : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct RunResult
|
||||||
|
{
|
||||||
|
int exitCode;
|
||||||
|
QByteArray stdOutput;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceShell();
|
||||||
|
virtual ~DeviceShell();
|
||||||
|
|
||||||
|
bool runInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
|
||||||
|
RunResult outputForRunInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
|
||||||
|
|
||||||
|
bool waitForStarted();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void done();
|
||||||
|
void errorOccurred(QProcess::ProcessError error);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool runInShellImpl(const CommandLine &cmd, const QByteArray &stdInData = {});
|
||||||
|
RunResult outputForRunInShellImpl(const CommandLine &cmd, const QByteArray &stdInData = {});
|
||||||
|
|
||||||
|
bool start();
|
||||||
|
void close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void setupShellProcess(QtcProcess* shellProcess);
|
||||||
|
virtual void startupFailed(const CommandLine &cmdLine);
|
||||||
|
|
||||||
|
void closeShellProcess();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QtcProcess *m_shellProcess;
|
||||||
|
QThread m_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Utils
|
@@ -80,6 +80,8 @@ Project {
|
|||||||
"detailsbutton.h",
|
"detailsbutton.h",
|
||||||
"detailswidget.cpp",
|
"detailswidget.cpp",
|
||||||
"detailswidget.h",
|
"detailswidget.h",
|
||||||
|
"deviceshell.cpp",
|
||||||
|
"deviceshell.h",
|
||||||
"differ.cpp",
|
"differ.cpp",
|
||||||
"differ.h",
|
"differ.h",
|
||||||
"displayname.cpp",
|
"displayname.cpp",
|
||||||
|
Reference in New Issue
Block a user