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. // have a QWidget parent yet when used in a LayoutBuilder.
if (!isVisible()) if (!isVisible())
d->m_label->setVisible(false); 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()); parent.addItem(d->m_label.data());
} }
@@ -2629,6 +2633,7 @@ void TextDisplay::setIconType(InfoLabel::InfoType t)
void TextDisplay::setText(const QString &message) void TextDisplay::setText(const QString &message)
{ {
d->m_message = message; d->m_message = message;
emit changed();
} }
/*! /*!

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -847,7 +847,12 @@ public:
connect(m_shell.get(), &DeviceShell::done, this, [this] { connect(m_shell.get(), &DeviceShell::done, this, [this] {
m_shell.release()->deleteLater(); 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 // Call me with shell mutex locked