Docker: Improve error reporting

Change-Id: I1a1ad824b4084ce7203357acf9ec19ccfe91b5cd
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-09-14 15:13:43 +02:00
parent aab39532f3
commit b81026488c
7 changed files with 86 additions and 77 deletions

View File

@@ -2611,6 +2611,10 @@ void TextDisplay::addToLayout(LayoutItem &parent)
// have a QWidget parent yet when used in a LayoutBuilder.
if (!isVisible())
d->m_label->setVisible(false);
connect(this, &TextDisplay::changed, d->m_label, [this] {
d->m_label->setText(d->m_message);
});
}
parent.addItem(d->m_label.data());
}
@@ -2629,6 +2633,7 @@ void TextDisplay::setIconType(InfoLabel::InfoType t)
void TextDisplay::setText(const QString &message)
{
d->m_message = message;
emit changed();
}
/*!

View File

@@ -6,6 +6,7 @@
#include "process.h"
#include "processinterface.h"
#include "qtcassert.h"
#include "utilstr.h"
#include <QLoggingCategory>
@@ -153,23 +154,13 @@ CommandLine DeviceShell::createFallbackCommand(const CommandLine &cmd)
return cmd;
}
/*!
* \brief DeviceShell::startupFailed
*
* Override to display custom error messages
*/
void DeviceShell::startupFailed(const CommandLine &cmdLine)
{
qCWarning(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()
expected_str<void> DeviceShell::start()
{
m_shellProcess = std::make_unique<Process>();
connect(m_shellProcess.get(), &Process::done, m_shellProcess.get(),
@@ -185,19 +176,21 @@ bool DeviceShell::start()
// Moving the process into its own thread ...
m_shellProcess->moveToThread(&m_thread);
bool result = false;
expected_str<void> result;
QMetaObject::invokeMethod(
m_shellProcess.get(),
[this] {
qCDebug(deviceShellLog) << "Starting shell process:" << m_shellProcess->commandLine().toUserOutput();
[this]() -> expected_str<void> {
qCDebug(deviceShellLog)
<< "Starting shell process:" << m_shellProcess->commandLine().toUserOutput();
m_shellProcess->start();
if (!m_shellProcess->waitForStarted()) {
closeShellProcess();
return false;
return make_unexpected(Tr::tr("The process failed to start."));
}
if (installShellScript()) {
auto installResult = installShellScript();
if (installResult) {
connect(m_shellProcess.get(),
&Process::readyReadStandardOutput,
m_shellProcess.get(),
@@ -210,12 +203,6 @@ bool DeviceShell::start()
qCWarning(deviceShellLog)
<< "Received unexpected output on stderr:" << stdErr;
});
} else if (m_shellProcess->isRunning()) {
m_shellProcess->kill();
m_shellProcess.reset();
return false;
} else
return false;
connect(m_shellProcess.get(), &Process::done, m_shellProcess.get(), [this] {
if (m_shellProcess->resultData().m_exitCode != EXIT_SUCCESS
@@ -226,51 +213,54 @@ bool DeviceShell::start()
}
});
return true;
return {};
} else if (m_shellProcess->isRunning()) {
m_shellProcess->kill();
m_shellProcess.reset();
}
return make_unexpected(Tr::tr("Failed to install shell script: %1\n%2")
.arg(installResult.error())
.arg(m_shellProcess->readAllStandardError()));
},
Qt::BlockingQueuedConnection,
&result);
if (!result) {
startupFailed(cmdLine);
}
return result;
}
bool DeviceShell::checkCommand(const QByteArray &command)
expected_str<QByteArray> DeviceShell::checkCommand(const QByteArray &command)
{
const QByteArray checkCmd = "(which " + command + " || echo '<missing>')\n";
m_shellProcess->writeRaw(checkCmd);
if (!m_shellProcess->waitForReadyRead()) {
qCWarning(deviceShellLog) << "Timeout while trying to check for" << command;
return false;
return make_unexpected(
Tr::tr("Timeout while trying to check for %1.").arg(QString::fromUtf8(command)));
}
QByteArray out = m_shellProcess->readAllRawStandardOutput();
if (out.contains("<missing>")) {
m_shellScriptState = State::Failed;
qCWarning(deviceShellLog) << "Command" << command << "was not found";
m_missingFeatures.append(QString::fromUtf8(command));
return false;
return make_unexpected(Tr::tr("Command %1 was not found.").arg(QString::fromUtf8(command)));
}
return true;
return out;
}
bool DeviceShell::installShellScript()
expected_str<void> DeviceShell::installShellScript()
{
if (m_forceFailScriptInstallation) {
m_shellScriptState = State::Failed;
return false;
return make_unexpected(Tr::tr("Script installation was forced to fail."));
}
static const QList<QByteArray> requiredCommands
= {"base64", "cat", "echo", "kill", "mkfifo", "mktemp", "rm"};
for (const QByteArray &command : requiredCommands) {
if (!checkCommand(command))
return false;
auto checkResult = checkCommand(command);
if (!checkResult)
return make_unexpected(checkResult.error());
}
const static QByteArray shellScriptBase64 = FilePath(":/utils/scripts/deviceshell.sh")
@@ -287,19 +277,18 @@ bool DeviceShell::installShellScript()
while (m_shellScriptState == State::Unknown) {
if (!m_shellProcess->waitForReadyRead(5000)) {
qCWarning(deviceShellLog) << "Timeout while waiting for shell script installation";
return false;
return make_unexpected(Tr::tr("Timeout while waiting for shell script installation."));
}
QByteArray out = m_shellProcess->readAllRawStandardError();
if (out.contains("SCRIPT_INSTALLED") && !out.contains("ERROR_INSTALL_SCRIPT")) {
m_shellScriptState = State::Succeeded;
return true;
return {};
}
if (out.contains("ERROR_INSTALL_SCRIPT")) {
m_shellScriptState = State::Failed;
qCWarning(deviceShellLog) << "Failed installing device shell script";
return false;
return make_unexpected(
Tr::tr("Failed to install shell script: %1").arg(QString::fromUtf8(out)));
}
if (!out.isEmpty()) {
qCWarning(deviceShellLog)
@@ -307,7 +296,7 @@ bool DeviceShell::installShellScript()
}
}
return true;
return {};
}
void DeviceShell::closeShellProcess()

View File

@@ -3,9 +3,9 @@
#pragma once
#include "utils_global.h"
#include "expected.h"
#include "fileutils.h"
#include "utils_global.h"
#include <QHash>
#include <QMutex>
@@ -39,7 +39,7 @@ public:
DeviceShell(bool forceFailScriptInstallation = false);
virtual ~DeviceShell();
bool start();
expected_str<void> start();
RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
@@ -51,7 +51,6 @@ signals:
void done(const ProcessResultData &resultData);
protected:
virtual void startupFailed(const CommandLine &cmdLine);
RunResult run(const CommandLine &cmd, const QByteArray &stdInData = {});
void close();
@@ -60,12 +59,12 @@ private:
virtual void setupShellProcess(Process *shellProcess);
virtual CommandLine createFallbackCommand(const CommandLine &cmdLine);
bool installShellScript();
expected_str<void> installShellScript();
void closeShellProcess();
void onReadyRead();
bool checkCommand(const QByteArray &command);
expected_str<QByteArray> checkCommand(const QByteArray &command);
private:
struct CommandRun : public RunResult

View File

@@ -190,6 +190,8 @@ DockerDeviceSettings::DockerDeviceSettings()
return newValue;
});
});
containerStatus.setText(Tr::tr("stopped"));
}
// Used for "docker run"
@@ -251,8 +253,8 @@ public:
bool prepareForBuild(const Target *target);
Tasks validateMounts() const;
bool createContainer();
bool startContainer();
expected_str<QString> createContainer();
expected_str<void> startContainer();
void stopCurrentContainer();
void fetchSystemEnviroment();
@@ -733,10 +735,10 @@ bool DockerDevicePrivate::isImageAvailable() const
return false;
}
bool DockerDevicePrivate::createContainer()
expected_str<QString> DockerDevicePrivate::createContainer()
{
if (!isImageAvailable())
return false;
return make_unexpected(Tr::tr("Image \"%1\" is not available.").arg(repoAndTag()));
const QString display = HostOsInfo::isLinuxHost() ? QString(":0")
: QString("host.docker.internal:0");
@@ -773,24 +775,25 @@ bool DockerDevicePrivate::createContainer()
createProcess.runBlocking();
if (createProcess.result() != ProcessResult::FinishedWithSuccess) {
qCWarning(dockerDeviceLog) << "Failed creating docker container:";
qCWarning(dockerDeviceLog) << "Exit Code:" << createProcess.exitCode();
qCWarning(dockerDeviceLog) << createProcess.allOutput();
return false;
return make_unexpected(Tr::tr("Failed creating Docker container. Exit code: %1, output: %2")
.arg(createProcess.exitCode())
.arg(createProcess.allOutput()));
}
m_container = createProcess.cleanedStdOut().trimmed();
if (m_container.isEmpty())
return false;
return make_unexpected(
Tr::tr("Failed creating Docker container. No container ID received."));
qCDebug(dockerDeviceLog) << "ContainerId:" << m_container;
return true;
return m_container;
}
bool DockerDevicePrivate::startContainer()
expected_str<void> DockerDevicePrivate::startContainer()
{
if (!createContainer())
return false;
auto createResult = createContainer();
if (!createResult)
return make_unexpected(createResult.error());
m_shell = std::make_unique<ContainerShell>(m_container, q->rootPath());
@@ -811,13 +814,10 @@ bool DockerDevicePrivate::startContainer()
"or restart Qt Creator."));
});
QTC_ASSERT(m_shell, return false);
QTC_ASSERT(m_shell,
return make_unexpected(Tr::tr("Failed to create container shell (Out of memory).")));
if (m_shell->start())
return true;
qCWarning(dockerDeviceLog) << "Container shell failed to start";
return false;
return m_shell->start();
}
bool DockerDevicePrivate::updateContainerAccess()
@@ -831,7 +831,15 @@ bool DockerDevicePrivate::updateContainerAccess()
if (m_shell)
return true;
return startContainer();
auto result = startContainer();
if (result) {
deviceSettings->containerStatus.setText(Tr::tr("Running"));
return true;
}
qCWarning(dockerDeviceLog) << "Failed to start container:" << result.error();
deviceSettings->containerStatus.setText(result.error());
return false;
}
void DockerDevice::setMounts(const QStringList &mounts) const

View File

@@ -30,6 +30,8 @@ public:
Utils::BoolAspect keepEntryPoint{this};
Utils::BoolAspect enableLldbFlags{this};
Utils::FilePathAspect clangdExecutable{this};
Utils::TextDisplay containerStatus{this};
};
class DockerDevice : public ProjectExplorer::IDevice

View File

@@ -162,6 +162,7 @@ DockerDeviceWidget::DockerDeviceWidget(const IDevice::Ptr &device)
deviceSettings->tag, br,
deviceSettings->imageId, br,
daemonStateLabel, m_daemonReset, m_daemonState, br,
Tr::tr("Container State:"), deviceSettings->containerStatus, br,
deviceSettings->useLocalUidGid, br,
deviceSettings->keepEntryPoint, br,
deviceSettings->enableLldbFlags, br,

View File

@@ -847,7 +847,12 @@ public:
connect(m_shell.get(), &DeviceShell::done, this, [this] {
m_shell.release()->deleteLater();
});
return m_shell->start();
auto result = m_shell->start();
if (!result) {
qCWarning(linuxDeviceLog) << "Failed to start shell for:" << parameters.userAtHost()
<< ", " << result.error();
}
return result.has_value();
}
// Call me with shell mutex locked