forked from qt-creator/qt-creator
Docker: Implement some DockerDevice functions
Container file system accesses are primarily done through the host file system by prepending the appropriate path. This is Linux-only, and only in case the volumes are mounted to the same relative location in the container. As a fallback, provide the functionality also via 'docker exec'. This is, however, too slow to be used in the future. Change-Id: I23185ae45184cb7b1de7c723a01ab96fe9c0801e Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -32,10 +32,15 @@
|
|||||||
|
|
||||||
#include <projectexplorer/devicesupport/idevicewidget.h>
|
#include <projectexplorer/devicesupport/idevicewidget.h>
|
||||||
#include <projectexplorer/runcontrol.h>
|
#include <projectexplorer/runcontrol.h>
|
||||||
|
#include <projectexplorer/toolchain.h>
|
||||||
|
#include <projectexplorer/toolchainmanager.h>
|
||||||
|
|
||||||
|
#include <qtsupport/baseqtversion.h>
|
||||||
|
#include <qtsupport/qtversionfactory.h>
|
||||||
|
#include <qtsupport/qtversionmanager.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/basetreeview.h>
|
#include <utils/basetreeview.h>
|
||||||
#include <utils/consoleprocess.h>
|
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/layoutbuilder.h>
|
#include <utils/layoutbuilder.h>
|
||||||
@@ -43,18 +48,25 @@
|
|||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/stringutils.h>
|
#include <utils/stringutils.h>
|
||||||
|
#include <utils/temporaryfile.h>
|
||||||
#include <utils/treemodel.h>
|
#include <utils/treemodel.h>
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
#include <QFileSystemWatcher>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTextBrowser>
|
#include <QTextBrowser>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
using namespace Core;
|
using namespace Core;
|
||||||
using namespace ProjectExplorer;
|
using namespace ProjectExplorer;
|
||||||
|
using namespace QtSupport;
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
|
#define LOG(x)
|
||||||
|
//#define LOG(x) qDebug() << x
|
||||||
|
|
||||||
namespace Docker {
|
namespace Docker {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
@@ -83,8 +95,7 @@ public:
|
|||||||
qint64 write(const QByteArray &data) override { return m_process.write(data); }
|
qint64 write(const QByteArray &data) override { return m_process.write(data); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QProcess m_process;
|
QtcProcess m_process;
|
||||||
ConsoleProcess m_consoleProcess;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DockerDeviceProcess::DockerDeviceProcess(const QSharedPointer<const IDevice> &device,
|
DockerDeviceProcess::DockerDeviceProcess(const QSharedPointer<const IDevice> &device,
|
||||||
@@ -101,50 +112,27 @@ void DockerDeviceProcess::start(const Runnable &runnable)
|
|||||||
|
|
||||||
const QStringList dockerRunFlags = runnable.extraData[Constants::DOCKER_RUN_FLAGS].toStringList();
|
const QStringList dockerRunFlags = runnable.extraData[Constants::DOCKER_RUN_FLAGS].toStringList();
|
||||||
|
|
||||||
const DockerDeviceData &data = dockerDevice->data();
|
|
||||||
CommandLine cmd("docker");
|
|
||||||
cmd.addArg("run");
|
|
||||||
cmd.addArgs(dockerRunFlags);
|
|
||||||
cmd.addArg(data.imageId);
|
|
||||||
if (!runnable.executable.isEmpty())
|
|
||||||
cmd.addArgs(runnable.commandLine());
|
|
||||||
|
|
||||||
disconnect(&m_consoleProcess);
|
|
||||||
disconnect(&m_process);
|
|
||||||
|
|
||||||
if (runInTerminal()) {
|
|
||||||
connect(&m_consoleProcess, &ConsoleProcess::errorOccurred,
|
|
||||||
this, &DeviceProcess::error);
|
|
||||||
connect(&m_consoleProcess, &ConsoleProcess::processStarted,
|
|
||||||
this, &DeviceProcess::started);
|
|
||||||
connect(&m_consoleProcess, &ConsoleProcess::stubStopped,
|
|
||||||
this, &DeviceProcess::finished);
|
|
||||||
|
|
||||||
m_consoleProcess.setAbortOnMetaChars(false);
|
|
||||||
m_consoleProcess.setSettings(Core::ICore::settings());
|
|
||||||
m_consoleProcess.setEnvironment(runnable.environment);
|
|
||||||
m_consoleProcess.setCommand(cmd);
|
|
||||||
m_consoleProcess.start();
|
|
||||||
} else {
|
|
||||||
m_process.setProcessEnvironment(runnable.environment.toProcessEnvironment());
|
|
||||||
connect(&m_process, &QProcess::errorOccurred, this, &DeviceProcess::error);
|
|
||||||
connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
|
||||||
this, &DeviceProcess::finished);
|
|
||||||
connect(&m_process, &QProcess::readyReadStandardOutput,
|
|
||||||
this, &DeviceProcess::readyReadStandardOutput);
|
|
||||||
connect(&m_process, &QProcess::readyReadStandardError,
|
|
||||||
this, &DeviceProcess::readyReadStandardError);
|
|
||||||
connect(&m_process, &QProcess::started, this, &DeviceProcess::started);
|
|
||||||
m_process.setWorkingDirectory(runnable.workingDirectory);
|
|
||||||
m_process.start(cmd.executable().toString(), cmd.splitArguments());
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(this, &DeviceProcess::readyReadStandardOutput, this, [this] {
|
connect(this, &DeviceProcess::readyReadStandardOutput, this, [this] {
|
||||||
MessageManager::writeSilently(QString::fromLocal8Bit(readAllStandardError()));
|
MessageManager::writeSilently(QString::fromLocal8Bit(readAllStandardError()));
|
||||||
});
|
});
|
||||||
connect(this, &DeviceProcess::readyReadStandardError, this, [this] {
|
connect(this, &DeviceProcess::readyReadStandardError, this, [this] {
|
||||||
MessageManager::writeDisrupting(QString::fromLocal8Bit(readAllStandardError()));
|
MessageManager::writeDisrupting(QString::fromLocal8Bit(readAllStandardError()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
disconnect(&m_process);
|
||||||
|
|
||||||
|
m_process.setCommand(runnable.commandLine());
|
||||||
|
m_process.setProcessEnvironment(runnable.environment.toProcessEnvironment());
|
||||||
|
m_process.setWorkingDirectory(runnable.workingDirectory);
|
||||||
|
connect(&m_process, &QProcess::errorOccurred, this, &DeviceProcess::error);
|
||||||
|
connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||||
|
this, &DeviceProcess::finished);
|
||||||
|
connect(&m_process, &QProcess::readyReadStandardOutput,
|
||||||
|
this, &DeviceProcess::readyReadStandardOutput);
|
||||||
|
connect(&m_process, &QProcess::readyReadStandardError,
|
||||||
|
this, &DeviceProcess::readyReadStandardError);
|
||||||
|
connect(&m_process, &QProcess::started, this, &DeviceProcess::started);
|
||||||
|
dockerDevice->runProcess(m_process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DockerDeviceProcess::interrupt()
|
void DockerDeviceProcess::interrupt()
|
||||||
@@ -272,9 +260,38 @@ IDeviceWidget *DockerDevice::createWidget()
|
|||||||
return new DockerDeviceWidget(sharedFromThis());
|
return new DockerDeviceWidget(sharedFromThis());
|
||||||
}
|
}
|
||||||
|
|
||||||
DockerDevice::DockerDevice(const DockerDeviceData &data)
|
|
||||||
: m_data(data)
|
// DockerDevice
|
||||||
|
|
||||||
|
class DockerDevicePrivate : public QObject
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
DockerDevicePrivate()
|
||||||
|
{
|
||||||
|
connect(&m_mergedDirWatcher, &QFileSystemWatcher::fileChanged, this, [](const QString &path) {
|
||||||
|
LOG("Container watcher change, file: " << path);
|
||||||
|
});
|
||||||
|
connect(&m_mergedDirWatcher, &QFileSystemWatcher::directoryChanged, this, [](const QString &path) {
|
||||||
|
LOG("Container watcher change, directory: " << path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
~DockerDevicePrivate() { delete m_shell; }
|
||||||
|
|
||||||
|
DockerDeviceData m_data;
|
||||||
|
|
||||||
|
// For local file access
|
||||||
|
QPointer<QtcProcess> m_shell;
|
||||||
|
QString m_container;
|
||||||
|
QString m_mergedDir;
|
||||||
|
QFileSystemWatcher m_mergedDirWatcher;
|
||||||
|
};
|
||||||
|
|
||||||
|
DockerDevice::DockerDevice(const DockerDeviceData &data)
|
||||||
|
: d(new DockerDevicePrivate)
|
||||||
|
{
|
||||||
|
d->m_data = data;
|
||||||
|
|
||||||
setDisplayType(tr("Docker"));
|
setDisplayType(tr("Docker"));
|
||||||
setOsType(OsTypeOtherUnix);
|
setOsType(OsTypeOtherUnix);
|
||||||
setDefaultDisplayName(tr("Docker Image"));;
|
setDefaultDisplayName(tr("Docker Image"));;
|
||||||
@@ -313,9 +330,125 @@ DockerDevice::DockerDevice(const DockerDeviceData &data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DockerDevice::~DockerDevice()
|
||||||
|
{
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
const DockerDeviceData &DockerDevice::data() const
|
const DockerDeviceData &DockerDevice::data() const
|
||||||
{
|
{
|
||||||
return m_data;
|
return d->m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDevice::autoDetectQtVersion() const
|
||||||
|
{
|
||||||
|
QString error;
|
||||||
|
QString source = "docker:" + d->m_data.imageId;
|
||||||
|
const QStringList candidates = {"/usr/local/bin/qmake", "/usr/bin/qmake"};
|
||||||
|
for (const QString &candidate : candidates) {
|
||||||
|
const FilePath qmake = mapToGlobalPath(FilePath::fromString(candidate));
|
||||||
|
if (auto qtVersion = QtVersionFactory::createQtVersionFromQMakePath(qmake, false, source, &error)) {
|
||||||
|
QtVersionManager::addVersion(qtVersion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDevice::autoDetectToolChains()
|
||||||
|
{
|
||||||
|
const QList<ToolChainFactory *> factories = ToolChainFactory::allToolChainFactories();
|
||||||
|
|
||||||
|
QList<ToolChain *> toolChains;
|
||||||
|
for (ToolChainFactory *factory : factories) {
|
||||||
|
const QList<ToolChain *> newToolChains = factory->autoDetect(toolChains, sharedFromThis());
|
||||||
|
for (ToolChain *toolChain : newToolChains) {
|
||||||
|
LOG("Found ToolChain: " << toolChain->compilerCommand().toUserOutput());
|
||||||
|
ToolChainManager::registerToolChain(toolChain);
|
||||||
|
toolChains.append(toolChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDevice::tryCreateLocalFileAccess() const
|
||||||
|
{
|
||||||
|
if (!d->m_container.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString tempFileName;
|
||||||
|
|
||||||
|
{
|
||||||
|
TemporaryFile temp("qtc-docker-XXXXXX");
|
||||||
|
temp.open();
|
||||||
|
tempFileName = temp.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
d->m_shell = new QtcProcess;
|
||||||
|
// FIXME: Make mounts flexible
|
||||||
|
d->m_shell->setCommand({"docker", {"run", "-i", "--cidfile=" + tempFileName,
|
||||||
|
"-v", "/opt:/opt",
|
||||||
|
"-v", "/data:/data",
|
||||||
|
"-e", "DISPLAY=:0",
|
||||||
|
"-e", "XAUTHORITY=/.Xauthority",
|
||||||
|
"--net", "host",
|
||||||
|
d->m_data.imageId, "/bin/sh"}});
|
||||||
|
LOG("RUNNING: " << d->m_shell->commandLine().toUserOutput());
|
||||||
|
d->m_shell->start();
|
||||||
|
d->m_shell->waitForStarted();
|
||||||
|
|
||||||
|
LOG("CHECKING: " << tempFileName);
|
||||||
|
for (int i = 0; i <= 10; ++i) {
|
||||||
|
QFile file(tempFileName);
|
||||||
|
file.open(QIODevice::ReadOnly);
|
||||||
|
d->m_container = QString::fromUtf8(file.readAll()).trimmed();
|
||||||
|
if (!d->m_container.isEmpty()) {
|
||||||
|
LOG("Container: " << d->m_container);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i == 10) {
|
||||||
|
qWarning("Docker cid file empty.");
|
||||||
|
return; // No
|
||||||
|
}
|
||||||
|
QThread::msleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
QtcProcess proc;
|
||||||
|
proc.setCommand({"docker", {"inspect", "--format={{.GraphDriver.Data.MergedDir}}", d->m_container}});
|
||||||
|
//LOG(proc2.commandLine().toUserOutput());
|
||||||
|
proc.start();
|
||||||
|
proc.waitForFinished();
|
||||||
|
const QByteArray out = proc.readAllStandardOutput();
|
||||||
|
d->m_mergedDir = QString::fromUtf8(out).trimmed();
|
||||||
|
if (d->m_mergedDir.endsWith('/'))
|
||||||
|
d->m_mergedDir.chop(1);
|
||||||
|
|
||||||
|
d->m_mergedDirWatcher.addPath(d->m_mergedDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::hasLocalFileAccess() const
|
||||||
|
{
|
||||||
|
return !d->m_mergedDir.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath DockerDevice::mapToLocalAccess(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!d->m_mergedDir.isEmpty(), return {});
|
||||||
|
QString path = filePath.path();
|
||||||
|
if (path.startsWith('/'))
|
||||||
|
return FilePath::fromString(d->m_mergedDir + path);
|
||||||
|
return FilePath::fromString(d->m_mergedDir + '/' + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath DockerDevice::mapFromLocalAccess(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!filePath.needsDevice(), return {});
|
||||||
|
return mapFromLocalAccess(filePath.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath DockerDevice::mapFromLocalAccess(const QString &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!d->m_mergedDir.isEmpty(), return {});
|
||||||
|
QTC_ASSERT(filePath.startsWith(d->m_mergedDir), return FilePath::fromString(filePath));
|
||||||
|
return mapToGlobalPath(FilePath::fromString(filePath.mid(d->m_mergedDir.size())));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char DockerDeviceDataImageIdKey[] = "DockerDeviceDataImageId";
|
const char DockerDeviceDataImageIdKey[] = "DockerDeviceDataImageId";
|
||||||
@@ -326,24 +459,22 @@ const char DockerDeviceDataSizeKey[] = "DockerDeviceDataSize";
|
|||||||
void DockerDevice::fromMap(const QVariantMap &map)
|
void DockerDevice::fromMap(const QVariantMap &map)
|
||||||
{
|
{
|
||||||
ProjectExplorer::IDevice::fromMap(map);
|
ProjectExplorer::IDevice::fromMap(map);
|
||||||
m_data.imageId = map.value(DockerDeviceDataImageIdKey).toString();
|
d->m_data.imageId = map.value(DockerDeviceDataImageIdKey).toString();
|
||||||
m_data.repo = map.value(DockerDeviceDataRepoKey).toString();
|
d->m_data.repo = map.value(DockerDeviceDataRepoKey).toString();
|
||||||
m_data.tag = map.value(DockerDeviceDataTagKey).toString();
|
d->m_data.tag = map.value(DockerDeviceDataTagKey).toString();
|
||||||
m_data.size = map.value(DockerDeviceDataSizeKey).toString();
|
d->m_data.size = map.value(DockerDeviceDataSizeKey).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap DockerDevice::toMap() const
|
QVariantMap DockerDevice::toMap() const
|
||||||
{
|
{
|
||||||
QVariantMap map = ProjectExplorer::IDevice::toMap();
|
QVariantMap map = ProjectExplorer::IDevice::toMap();
|
||||||
map.insert(DockerDeviceDataImageIdKey, m_data.imageId);
|
map.insert(DockerDeviceDataImageIdKey, d->m_data.imageId);
|
||||||
map.insert(DockerDeviceDataRepoKey, m_data.repo);
|
map.insert(DockerDeviceDataRepoKey, d->m_data.repo);
|
||||||
map.insert(DockerDeviceDataTagKey, m_data.tag);
|
map.insert(DockerDeviceDataTagKey, d->m_data.tag);
|
||||||
map.insert(DockerDeviceDataSizeKey, m_data.size);
|
map.insert(DockerDeviceDataSizeKey, d->m_data.size);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
DockerDevice::~DockerDevice() = default;
|
|
||||||
|
|
||||||
DeviceProcess *DockerDevice::createProcess(QObject *parent) const
|
DeviceProcess *DockerDevice::createProcess(QObject *parent) const
|
||||||
{
|
{
|
||||||
return new DockerDeviceProcess(sharedFromThis(), parent);
|
return new DockerDeviceProcess(sharedFromThis(), parent);
|
||||||
@@ -379,6 +510,167 @@ DeviceEnvironmentFetcher::Ptr DockerDevice::environmentFetcher() const
|
|||||||
return DeviceEnvironmentFetcher::Ptr();
|
return DeviceEnvironmentFetcher::Ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FilePath DockerDevice::mapToGlobalPath(const FilePath &pathOnDevice) const
|
||||||
|
{
|
||||||
|
QUrl url = pathOnDevice.toUrl();
|
||||||
|
if (url.isValid()) {
|
||||||
|
QTC_CHECK(url.host() == d->m_data.imageId);
|
||||||
|
QTC_CHECK(url.scheme() == "docker");
|
||||||
|
return pathOnDevice;
|
||||||
|
}
|
||||||
|
url.setScheme("docker");
|
||||||
|
url.setHost(d->m_data.imageId);
|
||||||
|
url.setPath(pathOnDevice.toString());
|
||||||
|
return FilePath::fromUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::handlesFile(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
const QUrl &url = filePath.toUrl();
|
||||||
|
return url.scheme() == "docker" && url.host() == d->m_data.imageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::isExecutableFile(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return false);
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess()) {
|
||||||
|
const FilePath localAccess = mapToLocalAccess(filePath);
|
||||||
|
const bool res = localAccess.isExecutableFile();
|
||||||
|
LOG("Executable? " << filePath.toUserOutput() << localAccess.toUserOutput() << res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
const QString path = filePath.toUrl().path();
|
||||||
|
const CommandLine cmd("test", {"-x", path});
|
||||||
|
const int exitCode = runSynchronously(cmd);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::isReadableFile(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return false);
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess()) {
|
||||||
|
const FilePath localAccess = mapToLocalAccess(filePath);
|
||||||
|
const bool res = localAccess.isReadableFile();
|
||||||
|
LOG("ReadableFile? " << filePath.toUserOutput() << localAccess.toUserOutput() << res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
const QString path = filePath.toUrl().path();
|
||||||
|
const CommandLine cmd("test", {"-r", path, "-a", "-f", path});
|
||||||
|
const int exitCode = runSynchronously(cmd);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::isReadableDirectory(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return false);
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess()) {
|
||||||
|
const FilePath localAccess = mapToLocalAccess(filePath);
|
||||||
|
const bool res = localAccess.isReadableDir();
|
||||||
|
LOG("ReadableDirectory? " << filePath.toUserOutput() << localAccess.toUserOutput() << res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
const QString path = filePath.toUrl().path();
|
||||||
|
const CommandLine cmd("test", {"-x", path, "-a", "-d", path});
|
||||||
|
const int exitCode = runSynchronously(cmd);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::isWritableDirectory(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return false);
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess()) {
|
||||||
|
const FilePath localAccess = mapToLocalAccess(filePath);
|
||||||
|
const bool res = localAccess.isReadableDir();
|
||||||
|
LOG("WritableDirectory? " << filePath.toUserOutput() << localAccess.toUserOutput() << res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
const QString path = filePath.toUrl().path();
|
||||||
|
const CommandLine cmd("test", {"-x", path, "-a", "-d", path});
|
||||||
|
const int exitCode = runSynchronously(cmd);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DockerDevice::createDirectory(const FilePath &filePath) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return false);
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess()) {
|
||||||
|
const FilePath localAccess = mapToLocalAccess(filePath);
|
||||||
|
const bool res = localAccess.createDir();
|
||||||
|
LOG("CreateDirectory? " << filePath.toUserOutput() << localAccess.toUserOutput() << res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
const QString path = filePath.toUrl().path();
|
||||||
|
const CommandLine cmd("mkdir", {"-p", path});
|
||||||
|
const int exitCode = runSynchronously(cmd);
|
||||||
|
return exitCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<FilePath> DockerDevice::directoryEntries(const FilePath &filePath,
|
||||||
|
const QStringList &nameFilters,
|
||||||
|
QDir::Filters filters) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return {});
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess()) {
|
||||||
|
const FilePath localAccess = mapToLocalAccess(filePath);
|
||||||
|
const QFileInfoList entryInfoList = QDir(localAccess.toString()).entryInfoList(nameFilters, filters);
|
||||||
|
return Utils::transform(entryInfoList, [this](const QFileInfo &fi) {
|
||||||
|
return mapFromLocalAccess(fi.absoluteFilePath());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
QTC_CHECK(false); // FIXME: Implement
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray DockerDevice::fileContents(const FilePath &filePath, int limit) const
|
||||||
|
{
|
||||||
|
QTC_ASSERT(handlesFile(filePath), return {});
|
||||||
|
tryCreateLocalFileAccess();
|
||||||
|
if (hasLocalFileAccess())
|
||||||
|
return mapToLocalAccess(filePath).fileContents(limit);
|
||||||
|
|
||||||
|
QTC_CHECK(false); // FIXME: Implement
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DockerDevice::runProcess(QtcProcess &process) const
|
||||||
|
{
|
||||||
|
CommandLine dcmd{"docker", {"exec"}};
|
||||||
|
dcmd.addArgs({"-w", process.workingDirectory()});
|
||||||
|
dcmd.addArg(d->m_container);
|
||||||
|
dcmd.addArgs(process.commandLine());
|
||||||
|
|
||||||
|
LOG("Run" << dcmd.toUserOutput());
|
||||||
|
|
||||||
|
process.setCommand(dcmd);
|
||||||
|
process.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DockerDevice::runSynchronously(const CommandLine &cmd, QByteArray *out, QByteArray *err) const
|
||||||
|
{
|
||||||
|
CommandLine dcmd{"docker", {"exec", d->m_container}};
|
||||||
|
dcmd.addArgs(cmd);
|
||||||
|
|
||||||
|
QtcProcess proc;
|
||||||
|
proc.setCommand(dcmd);
|
||||||
|
proc.setWorkingDirectory("/tmp");
|
||||||
|
proc.start();
|
||||||
|
proc.waitForFinished();
|
||||||
|
|
||||||
|
if (out)
|
||||||
|
*out = proc.readAllStandardOutput();
|
||||||
|
if (err)
|
||||||
|
*err = proc.readAllStandardError();
|
||||||
|
|
||||||
|
LOG("Run sync:" << dcmd.toUserOutput() << " result: " << proc.exitCode());
|
||||||
|
return proc.exitCode();
|
||||||
|
}
|
||||||
|
|
||||||
// Factory
|
// Factory
|
||||||
|
|
||||||
DockerDeviceFactory::DockerDeviceFactory()
|
DockerDeviceFactory::DockerDeviceFactory()
|
||||||
@@ -427,13 +719,16 @@ public:
|
|||||||
: QDialog(ICore::dialogParent())
|
: QDialog(ICore::dialogParent())
|
||||||
{
|
{
|
||||||
setWindowTitle(tr("Docker Image Selection"));
|
setWindowTitle(tr("Docker Image Selection"));
|
||||||
|
resize(800, 600);
|
||||||
|
|
||||||
m_model.setHeader({"Image", "Repository", "Tag", "Size"});
|
m_model.setHeader({"Image", "Repository", "Tag", "Size"});
|
||||||
|
|
||||||
m_view = new TreeView;
|
m_view = new TreeView;
|
||||||
|
m_view->setModel(&m_model);
|
||||||
|
m_view->header()->setStretchLastSection(true);
|
||||||
|
m_view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
m_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
m_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
m_view->setModel(&m_model);
|
|
||||||
|
|
||||||
auto output = new QTextBrowser;
|
auto output = new QTextBrowser;
|
||||||
output->setEnabled(false);
|
output->setEnabled(false);
|
||||||
@@ -504,6 +799,8 @@ public:
|
|||||||
device->setupId(IDevice::ManuallyAdded, Utils::Id());
|
device->setupId(IDevice::ManuallyAdded, Utils::Id());
|
||||||
device->setType(Constants::DOCKER_DEVICE_TYPE);
|
device->setType(Constants::DOCKER_DEVICE_TYPE);
|
||||||
device->setMachineType(IDevice::Hardware);
|
device->setMachineType(IDevice::Hardware);
|
||||||
|
device->autoDetectToolChains();
|
||||||
|
device->autoDetectQtVersion();
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -52,7 +52,9 @@ public:
|
|||||||
using Ptr = QSharedPointer<DockerDevice>;
|
using Ptr = QSharedPointer<DockerDevice>;
|
||||||
using ConstPtr = QSharedPointer<const DockerDevice>;
|
using ConstPtr = QSharedPointer<const DockerDevice>;
|
||||||
|
|
||||||
|
explicit DockerDevice(const DockerDeviceData &data);
|
||||||
~DockerDevice();
|
~DockerDevice();
|
||||||
|
|
||||||
static Ptr create(const DockerDeviceData &data) { return Ptr(new DockerDevice(data)); }
|
static Ptr create(const DockerDeviceData &data) { return Ptr(new DockerDevice(data)); }
|
||||||
|
|
||||||
ProjectExplorer::IDeviceWidget *createWidget() override;
|
ProjectExplorer::IDeviceWidget *createWidget() override;
|
||||||
@@ -68,15 +70,40 @@ public:
|
|||||||
ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override;
|
ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override;
|
||||||
ProjectExplorer::DeviceEnvironmentFetcher::Ptr environmentFetcher() const override;
|
ProjectExplorer::DeviceEnvironmentFetcher::Ptr environmentFetcher() const override;
|
||||||
|
|
||||||
|
Utils::FilePath mapToGlobalPath(const Utils::FilePath &pathOnDevice) const override;
|
||||||
|
|
||||||
|
bool handlesFile(const Utils::FilePath &filePath) const override;
|
||||||
|
bool isExecutableFile(const Utils::FilePath &filePath) const override;
|
||||||
|
bool isReadableFile(const Utils::FilePath &filePath) const override;
|
||||||
|
bool isReadableDirectory(const Utils::FilePath &filePath) const override;
|
||||||
|
bool isWritableDirectory(const Utils::FilePath &filePath) const override;
|
||||||
|
bool createDirectory(const Utils::FilePath &filePath) const override;
|
||||||
|
QList<Utils::FilePath> directoryEntries(const Utils::FilePath &filePath,
|
||||||
|
const QStringList &nameFilters,
|
||||||
|
QDir::Filters filters) const override;
|
||||||
|
QByteArray fileContents(const Utils::FilePath &filePath, int limit) const override;
|
||||||
|
void runProcess(Utils::QtcProcess &process) const override;
|
||||||
|
|
||||||
|
int runSynchronously(const Utils::CommandLine &cmd,
|
||||||
|
QByteArray *out = nullptr,
|
||||||
|
QByteArray *err = nullptr) const override;
|
||||||
|
|
||||||
const DockerDeviceData &data() const;
|
const DockerDeviceData &data() const;
|
||||||
|
void autoDetectQtVersion() const;
|
||||||
|
void autoDetectToolChains();
|
||||||
|
|
||||||
|
void tryCreateLocalFileAccess() const;
|
||||||
|
bool hasLocalFileAccess() const;
|
||||||
|
|
||||||
|
Utils::FilePath mapToLocalAccess(const Utils::FilePath &filePath) const;
|
||||||
|
Utils::FilePath mapFromLocalAccess(const Utils::FilePath &filePath) const;
|
||||||
|
Utils::FilePath mapFromLocalAccess(const QString &filePath) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit DockerDevice(const DockerDeviceData &data);
|
|
||||||
|
|
||||||
void fromMap(const QVariantMap &map) final;
|
void fromMap(const QVariantMap &map) final;
|
||||||
QVariantMap toMap() const final;
|
QVariantMap toMap() const final;
|
||||||
|
|
||||||
DockerDeviceData m_data;
|
class DockerDevicePrivate *d = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DockerDeviceFactory final : public ProjectExplorer::IDeviceFactory
|
class DockerDeviceFactory final : public ProjectExplorer::IDeviceFactory
|
||||||
|
Reference in New Issue
Block a user