forked from qt-creator/qt-creator
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:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user