diff --git a/plugins/docker/dockerplugin.cpp b/plugins/docker/dockerplugin.cpp index d872ce4d9b..02f83135e3 100644 --- a/plugins/docker/dockerplugin.cpp +++ b/plugins/docker/dockerplugin.cpp @@ -1,170 +1,170 @@ /* Copyright 2017 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dockerplugin.h" #include "dockerruntime.h" #include "dockerpreferences.h" #include "dockerpreferencessettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevDockerFactory, "kdevdocker.json", registerPlugin();) using namespace KDevelop; DockerPlugin::DockerPlugin(QObject *parent, const QVariantList & /*args*/) : KDevelop::IPlugin( QStringLiteral("kdevdocker"), parent ) , m_settings(new DockerPreferencesSettings) { runtimeChanged(ICore::self()->runtimeController()->currentRuntime()); setXMLFile( QStringLiteral("kdevdockerplugin.rc") ); connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &DockerPlugin::runtimeChanged); QProcess* process = new QProcess(this); connect(process, static_cast(&QProcess::finished), this, &DockerPlugin::imagesListFinished); process->start(QStringLiteral("docker"), {QStringLiteral("images"), QStringLiteral("--filter"), QStringLiteral("dangling=false"), QStringLiteral("--format"), QStringLiteral("{{.Repository}}:{{.Tag}}\t{{.ID}}")}, QIODevice::ReadOnly); DockerRuntime::s_settings = m_settings.data(); } DockerPlugin::~DockerPlugin() { DockerRuntime::s_settings = nullptr; } void DockerPlugin::imagesListFinished(int code) { if (code != 0) return; QProcess* process = qobject_cast(sender()); Q_ASSERT(process); QTextStream stream(process); while(!stream.atEnd()) { const QString line = stream.readLine(); const QStringList parts = line.split(QLatin1Char('\t')); const QString tag = parts[0] == QLatin1String("") ? parts[1] : parts[0]; ICore::self()->runtimeController()->addRuntimes(new DockerRuntime(tag)); } process->deleteLater(); Q_EMIT imagesListed(); } void DockerPlugin::runtimeChanged(KDevelop::IRuntime* newRuntime) { const bool isDocker = qobject_cast(newRuntime); for(auto action: actionCollection()->actions()) { action->setEnabled(isDocker); } } KDevelop::ContextMenuExtension DockerPlugin::contextMenuExtension(KDevelop::Context* context) { QList urls; if ( context->type() == KDevelop::Context::FileContext ) { KDevelop::FileContext* filectx = dynamic_cast( context ); urls = filectx->urls(); } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* projctx = dynamic_cast( context ); foreach( KDevelop::ProjectBaseItem* item, projctx->items() ) { if ( item->file() ) { - urls << item->file()->path().toUrl(); + urls << item->path().toUrl(); } } } for(auto it = urls.begin(); it != urls.end(); ) { if (it->isLocalFile() && it->fileName() == QLatin1String("Dockerfile")) { ++it; } else { it = urls.erase(it); } } if ( !urls.isEmpty() ) { KDevelop::ContextMenuExtension ext; foreach(const QUrl &url, urls) { const KDevelop::Path file(url); auto action = new QAction(QIcon::fromTheme("text-dockerfile"), i18n("docker build '%1'", file.path()), this); connect(action, &QAction::triggered, this, [this, file]() { const auto dir = file.parent(); const QString name = QInputDialog::getText( ICore::self()->uiController()->activeMainWindow(), i18n("Choose tag name..."), i18n("Tag name for '%1'", file.path()), QLineEdit::Normal, dir.lastPathSegment() ); auto process = new OutputExecuteJob; process->setExecuteOnHost(true); *process << QStringList{"docker", "build", "--tag", name, dir.toLocalFile()}; connect(process, &KJob::finished, this, [this, name] (KJob* job) { if (job->error() != 0) return; ICore::self()->runtimeController()->addRuntimes({ new DockerRuntime(name) }); }); process->start(); }); ext.addAction(KDevelop::ContextMenuExtension::RunGroup, action); } return ext; } return KDevelop::IPlugin::contextMenuExtension( context ); } int DockerPlugin::configPages() const { return 1; } KDevelop::ConfigPage* DockerPlugin::configPage(int number, QWidget* parent) { if (number == 0) { return new DockerPreferences(this, m_settings.data(), parent); } return nullptr; } #include "dockerplugin.moc" diff --git a/plugins/docker/dockerruntime.cpp b/plugins/docker/dockerruntime.cpp index e972091cde..6e23f00f2e 100644 --- a/plugins/docker/dockerruntime.cpp +++ b/plugins/docker/dockerruntime.cpp @@ -1,230 +1,234 @@ /* Copyright 2017 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dockerruntime.h" #include "dockerpreferencessettings.h" #include "debug_docker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; DockerPreferencesSettings* DockerRuntime::s_settings = nullptr; DockerRuntime::DockerRuntime(const QString &tag) : KDevelop::IRuntime() , m_tag(tag) { setObjectName(tag); inspectImage(); } void DockerRuntime::inspectImage() { QProcess* process = new QProcess(this); connect(process, static_cast(&QProcess::finished), this, [process, this](int code, QProcess::ExitStatus status){ process->deleteLater(); qCDebug(DOCKER) << "inspect upper dir" << code << status; if (code || status) { qCWarning(DOCKER) << "Could not figure out the upperDir of" << m_tag; return; } m_upperDir = Path(QFile::decodeName(process->readAll().trimmed())); qCDebug(DOCKER) << "upper dir:" << m_tag << m_upperDir; }); process->start("docker", {"image", "inspect", m_tag, "--format", "{{.GraphDriver.Data.UpperDir}}"}); QProcess* processEnvs = new QProcess(this); connect(processEnvs, static_cast(&QProcess::finished), this, [processEnvs, this](int code, QProcess::ExitStatus status){ processEnvs->deleteLater(); qCDebug(DOCKER) << "inspect envs" << code << status; if (code || status) { qCWarning(DOCKER) << "Could not figure out the environment variables of" << m_tag; return; } - while(!processEnvs->atEnd()) { - const auto line = processEnvs->readLine().split('='); - if (line.count() != 2) + const auto list = processEnvs->readAll(); + const auto data = list.mid(1, list.size()-3); + const auto entries = data.split(' '); + + for (auto entry : entries) { + const auto content = entry.split('='); + if (content.count() != 2) continue; - m_envs.insert(line[0], line[1]); + m_envs.insert(content[0], content[1]); } qCDebug(DOCKER) << "envs:" << m_tag << m_envs; }); processEnvs->start("docker", {"image", "inspect", m_tag, "--format", "{{.Config.Env}}"}); } DockerRuntime::~DockerRuntime() { } QByteArray DockerRuntime::getenv(const QByteArray& varname) const { return m_envs.value(varname); } void DockerRuntime::setEnabled(bool enable) { if (enable) { m_userUpperDir = KDevelop::Path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/docker-" + QString(m_tag).replace('/', '_')); QDir().mkpath(m_userUpperDir.toLocalFile()); const QStringList cmd = {"pkexec", "bindfs", "--map=root/"+KUser().loginName(), m_upperDir.toLocalFile(), m_userUpperDir.toLocalFile() }; QProcess p; p.start(cmd.first(), cmd.mid(1)); p.waitForFinished(); if (p.exitCode()) { qCDebug(DOCKER) << "bindfs returned" << m_upperDir << m_userUpperDir << cmd << p.exitCode() << p.readAll(); } } else { int code = QProcess::execute(QStringLiteral("pkexec"), {"umount", m_userUpperDir.toLocalFile()}); qCDebug(DOCKER) << "umount returned" << code; } } static QString ensureEndsSlash(const QString &string) { return string.endsWith('/') ? string : (string + QLatin1Char('/')); } static QStringList projectVolumes() { QStringList ret; const QString dir = ensureEndsSlash(DockerRuntime::s_settings->projectsVolume()); const QString buildDir = ensureEndsSlash(DockerRuntime::s_settings->buildDirsVolume()); for (IProject* project: ICore::self()->projectController()->projects()) { const Path path = project->path(); if (path.isLocalFile()) { ret << "--volume" << QStringLiteral("%1:%2").arg(path.toLocalFile(), dir + path.lastPathSegment()); } const auto ibsm = project->buildSystemManager(); if (ibsm) { ret << "--volume" << ibsm->buildDirectory(project->projectItem()).toLocalFile() + QLatin1Char(':') + buildDir + path.lastPathSegment(); } } return ret; } QStringList DockerRuntime::workingDirArgs(QProcess* process) const { const auto wd = process->workingDirectory(); return wd.isEmpty() ? QStringList{} : QStringList{"-w", pathInRuntime(KDevelop::Path(wd)).toLocalFile()}; } void DockerRuntime::startProcess(QProcess* process) const { const QStringList args = QStringList{"run", "--rm"} << workingDirArgs(process) << KShell::splitArgs(s_settings->extraArguments()) << projectVolumes() << m_tag << process->program() << process->arguments(); process->setProgram("docker"); process->setArguments(args); qCDebug(DOCKER) << "starting qprocess" << process->program() << process->arguments(); process->start(); } void DockerRuntime::startProcess(KProcess* process) const { process->setProgram(QStringList{ "docker", "run", "--rm" } << workingDirArgs(process) << KShell::splitArgs(s_settings->extraArguments()) << projectVolumes() << m_tag << process->program()); qCDebug(DOCKER) << "starting kprocess" << process->program().join(' '); process->start(); } static Path projectRelPath(const KDevelop::Path & projectsDir, const KDevelop::Path& runtimePath, bool sourceDir) { const auto relPath = projectsDir.relativePath(runtimePath); const int index = relPath.indexOf(QLatin1Char('/')); auto project = ICore::self()->projectController()->findProjectByName(relPath.left(index)); if (!project) { qCWarning(DOCKER) << "No project for" << relPath; } else { - const auto repPathProject = relPath.mid(index+1); + const auto repPathProject = index < 0 ? QString() : relPath.mid(index+1); const auto rootPath = sourceDir ? project->path() : project->buildSystemManager()->buildDirectory(project->projectItem()); return Path(rootPath, repPathProject); } return {}; } KDevelop::Path DockerRuntime::pathInHost(const KDevelop::Path& runtimePath) const { Path ret; const Path projectsDir(DockerRuntime::s_settings->projectsVolume()); - if (projectsDir.isParentOf(runtimePath)) { + if (runtimePath==projectsDir || projectsDir.isParentOf(runtimePath)) { ret = projectRelPath(projectsDir, runtimePath, true); } else { const Path buildDirs(DockerRuntime::s_settings->buildDirsVolume()); - if (buildDirs.isParentOf(runtimePath)) { + if (runtimePath==buildDirs || buildDirs.isParentOf(runtimePath)) { ret = projectRelPath(buildDirs, runtimePath, false); } else ret = KDevelop::Path(m_userUpperDir, KDevelop::Path(QStringLiteral("/")).relativePath(runtimePath)); } qCDebug(DOCKER) << "pathInHost" << ret << runtimePath; return ret; } KDevelop::Path DockerRuntime::pathInRuntime(const KDevelop::Path& localPath) const { - if (m_userUpperDir.isParentOf(localPath)) { - KDevelop::Path ret(m_userUpperDir.relativePath(localPath)); + if (m_userUpperDir==localPath || m_userUpperDir.isParentOf(localPath)) { + KDevelop::Path ret(KDevelop::Path("/"), m_userUpperDir.relativePath(localPath)); qCDebug(DOCKER) << "docker runtime pathInRuntime..." << ret << localPath; return ret; } else if (auto project = ICore::self()->projectController()->findProjectForUrl(localPath.toUrl())) { const Path projectsDir(DockerRuntime::s_settings->projectsVolume()); const QString relpath = project->path().relativePath(localPath); const KDevelop::Path ret(projectsDir, project->name() + QLatin1Char('/') + relpath); - qCDebug(DOCKER) << "docker user pathInRuntime..." << ret << project->path() << relpath; + qCDebug(DOCKER) << "docker user pathInRuntime..." << ret << localPath; return ret; } else { const auto projects = ICore::self()->projectController()->projects(); for (auto project : projects) { auto ibsm = project->buildSystemManager(); if (ibsm) { const auto builddir = ibsm->buildDirectory(project->projectItem()); if (builddir != localPath && !builddir.isParentOf(localPath)) continue; const Path builddirs(DockerRuntime::s_settings->buildDirsVolume()); const QString relpath = builddir.relativePath(localPath); const KDevelop::Path ret(builddirs, project->name() + QLatin1Char('/') + relpath); - qCDebug(DOCKER) << "docker build pathInRuntime..." << ret << project->path() << relpath; + qCDebug(DOCKER) << "docker build pathInRuntime..." << ret << localPath; return ret; } } qCWarning(DOCKER) << "only project files are available on the docker runtime" << localPath; } qCDebug(DOCKER) << "bypass..." << localPath; return localPath; }