flatpakplugin.cpp 8.87 KB
Newer Older
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
   Copyright 2017 Aleix Pol Gonzalez <aleixpol@kde.org>

   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 "flatpakplugin.h"
#include "flatpakruntime.h"
#include <interfaces/icore.h>
#include <interfaces/iruntimecontroller.h>
#include <interfaces/iuicontroller.h>
24 25
#include <interfaces/iprojectcontroller.h>
#include <interfaces/iruncontroller.h>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
26 27 28
#include <interfaces/context.h>
#include <interfaces/contextmenuextension.h>
#include <project/projectmodel.h>
29
#include <util/executecompositejob.h>
30 31

#include <QTextStream>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
32 33 34 35
#include <QStandardPaths>
#include <QAction>
#include <QProcess>
#include <QRegularExpression>
36
#include <QInputDialog>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
37
#include <QTemporaryDir>
38
#include <QTemporaryFile>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
39 40 41 42 43 44
#include <QFileDialog>
#include <KPluginFactory>
#include <KActionCollection>
#include <KLocalizedString>
#include <KParts/MainWindow>
#include <KJob>
45 46
#include <KSharedConfig>
#include <KConfigGroup>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
47 48 49 50 51 52 53 54 55 56 57 58

K_PLUGIN_FACTORY_WITH_JSON(KDevFlatpakFactory, "kdevflatpak.json", registerPlugin<FlatpakPlugin>();)

using namespace KDevelop;

FlatpakPlugin::FlatpakPlugin(QObject *parent, const QVariantList & /*args*/)
    : KDevelop::IPlugin( QStringLiteral("kdevflatpak"), parent )
{
    auto ac = actionCollection();

    auto action = new QAction(QIcon::fromTheme(QStringLiteral("run-build-clean")), i18n("Rebuild environment"), this);
    action->setWhatsThis(i18n("Recompiles all dependencies for a fresh environment."));
59
    ac->setDefaultShortcut(action, Qt::CTRL | Qt::META | Qt::Key_X);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
60 61 62
    connect(action, &QAction::triggered, this, &FlatpakPlugin::rebuildCurrent);
    ac->addAction(QStringLiteral("runtime_flatpak_rebuild"), action);

63
    auto exportAction = new QAction(QIcon::fromTheme(QStringLiteral("document-export")), i18n("Export flatpak bundle..."), this);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
64
    exportAction->setWhatsThis(i18n("Exports the current build into a 'bundle.flatpak' file."));
65
    ac->setDefaultShortcut(exportAction, Qt::CTRL | Qt::META | Qt::Key_E);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
66 67 68
    connect(exportAction, &QAction::triggered, this, &FlatpakPlugin::exportCurrent);
    ac->addAction(QStringLiteral("runtime_flatpak_export"), exportAction);

69 70 71 72 73
    auto remoteAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-remote-symbolic")), i18n("Send to device..."), this);
    ac->setDefaultShortcut(remoteAction, Qt::CTRL | Qt::META | Qt::Key_D);
    connect(remoteAction, &QAction::triggered, this, &FlatpakPlugin::executeOnRemoteDevice);
    ac->addAction(QStringLiteral("runtime_flatpak_remote"), remoteAction);

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
74 75 76 77 78 79 80 81 82 83 84 85
    runtimeChanged(ICore::self()->runtimeController()->currentRuntime());

    setXMLFile( QStringLiteral("kdevflatpakplugin.rc") );
    connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &FlatpakPlugin::runtimeChanged);
}

FlatpakPlugin::~FlatpakPlugin() = default;

void FlatpakPlugin::runtimeChanged(KDevelop::IRuntime* newRuntime)
{
    const bool isFlatpak = qobject_cast<FlatpakRuntime*>(newRuntime);

86 87
    const auto& actions = actionCollection()->actions();
    for (auto action: actions) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
88
        action->setEnabled(isFlatpak);
89
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
90 91 92 93 94 95
}

void FlatpakPlugin::rebuildCurrent()
{
    const auto runtime = qobject_cast<FlatpakRuntime*>(ICore::self()->runtimeController()->currentRuntime());
    Q_ASSERT(runtime);
96
    ICore::self()->runController()->registerJob(runtime->rebuild());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
97 98 99 100 101 102 103
}

void FlatpakPlugin::exportCurrent()
{
    const auto runtime = qobject_cast<FlatpakRuntime*>(ICore::self()->runtimeController()->currentRuntime());
    Q_ASSERT(runtime);

104
    const QString path = QFileDialog::getSaveFileName(ICore::self()->uiController()->activeMainWindow(), i18n("Export %1 to...", runtime->name()), {}, i18n("Flatpak Bundle (*.flatpak)"));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
105
    if (!path.isEmpty()) {
106
        ICore::self()->runController()->registerJob(new ExecuteCompositeJob(runtime, runtime->exportBundle(path)));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
107 108 109
    }
}

110 111
void FlatpakPlugin::createRuntime(const KDevelop::Path &file, const QString &arch)
{
112
    QTemporaryDir* dir = new QTemporaryDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/kdevelop-flatpak-"));
113
    const KDevelop::Path path(dir->path());
114 115

    auto process = FlatpakRuntime::createBuildDirectory(path, file, arch);
116
    connect(process, &KJob::finished, this, [path, file, arch, dir] (KJob* job) {
117 118
        if (job->error() != 0) {
            delete dir;
119
            return;
120
        }
121

122 123 124
        auto rt = new FlatpakRuntime(path, file, arch);
        connect(rt, &QObject::destroyed, rt, [dir]() { delete dir; });
        ICore::self()->runtimeController()->addRuntimes(rt);
125 126 127 128
    });
    process->start();
}

129 130 131 132 133
static QStringList availableArches(const KDevelop::Path& url)
{
    QProcess supportedArchesProcess;
    QStringList ret;

134 135 136 137
    const auto doc = FlatpakRuntime::config(url);
    const QString sdkName = doc[QLatin1String("sdk")].toString();
    const QString runtimeVersion = doc[QLatin1String("runtime-version")].toString();
    const QString match = sdkName + QLatin1String("/(.+)/") + runtimeVersion + QLatin1Char('$');
138
    QObject::connect(&supportedArchesProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
139
                     &supportedArchesProcess, [&supportedArchesProcess, &match, &ret]() {
140
        QTextStream stream(&supportedArchesProcess);
141
        QRegularExpression rx(match);
142 143
        while (!stream.atEnd()) {
            const QString line = stream.readLine();
144 145 146 147
            auto m = rx.match(line);
            if (m.hasMatch()) {
                ret << m.captured(1);
            }
148 149 150
        }
    });

151
    supportedArchesProcess.start(QStringLiteral("flatpak"), {QStringLiteral("list"), QStringLiteral("--runtime"), QStringLiteral("--columns=ref") });
152 153 154 155
    supportedArchesProcess.waitForFinished();
    return ret;
}

156
KDevelop::ContextMenuExtension FlatpakPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
157 158 159 160
{
    QList<QUrl> urls;

    if ( context->type() == KDevelop::Context::FileContext ) {
161
        auto* filectx = static_cast<KDevelop::FileContext*>(context);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
162 163
        urls = filectx->urls();
    } else if ( context->type() == KDevelop::Context::ProjectItemContext ) {
164
        auto* projctx = static_cast<KDevelop::ProjectItemContext*>(context);
165 166
        const auto items = projctx->items();
        for (KDevelop::ProjectBaseItem* item : items) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
167 168 169 170 171 172
            if ( item->file() ) {
                urls << item->file()->path().toUrl();
            }
        }
    }

173
    const QRegularExpression nameRx(QStringLiteral(".*\\..*\\..*\\.json$"));
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
174 175 176 177 178 179 180 181 182 183
    for(auto it = urls.begin(); it != urls.end(); ) {
        if (it->isLocalFile() && it->path().contains(nameRx)) {
            ++it;
        } else {
            it = urls.erase(it);
        }
    }

    if ( !urls.isEmpty() ) {
        KDevelop::ContextMenuExtension ext;
184
        for (const QUrl& url : qAsConst(urls)) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
185
            const KDevelop::Path file(url);
186 187
            const auto arches = availableArches(file);
            for (const QString& arch : arches) {
188
                auto action = new QAction(i18n("Build flatpak %1 for %2", file.lastPathSegment(), arch), parent);
189 190
                connect(action, &QAction::triggered, this, [this, file, arch]() {
                    createRuntime(file, arch);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
191
                });
192 193
                ext.addAction(KDevelop::ContextMenuExtension::RunGroup, action);
            }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
194 195 196 197 198
        }

        return ext;
    }

199
    return KDevelop::IPlugin::contextMenuExtension(context, parent);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
200 201
}

202 203 204 205 206
void FlatpakPlugin::executeOnRemoteDevice()
{
    const auto runtime = qobject_cast<FlatpakRuntime*>(ICore::self()->runtimeController()->currentRuntime());
    Q_ASSERT(runtime);

207 208
    KConfigGroup group(KSharedConfig::openConfig(), "Flatpak");
    const QString lastDeviceAddress = group.readEntry("DeviceAddress");
209 210 211 212 213 214 215
    const QString host = QInputDialog::getText(
        ICore::self()->uiController()->activeMainWindow(), i18n("Choose tag name..."),
        i18n("Device hostname"),
        QLineEdit::Normal, lastDeviceAddress
    );
    if (host.isEmpty())
        return;
216
    group.writeEntry("DeviceAddress", host);
217

218
    QTemporaryFile* file = new QTemporaryFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + runtime->name() + QLatin1String("XXXXXX.flatpak"));
219 220 221 222 223 224 225
    file->open();
    file->close();
    auto job = runtime->executeOnDevice(host, file->fileName());
    file->setParent(file);

    ICore::self()->runController()->registerJob(job);
}
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
226 227

#include "flatpakplugin.moc"