File indexing completed on 2024-05-05 04:39:50
0001 /* 0002 SPDX-FileCopyrightText: 2017 Aleix Pol Gonzalez <aleixpol@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "flatpakplugin.h" 0008 #include "flatpakruntime.h" 0009 #include <interfaces/icore.h> 0010 #include <interfaces/iruntimecontroller.h> 0011 #include <interfaces/iuicontroller.h> 0012 #include <interfaces/iprojectcontroller.h> 0013 #include <interfaces/iruncontroller.h> 0014 #include <interfaces/context.h> 0015 #include <interfaces/contextmenuextension.h> 0016 #include <project/projectmodel.h> 0017 #include <util/executecompositejob.h> 0018 0019 #include <QTextStream> 0020 #include <QStandardPaths> 0021 #include <QAction> 0022 #include <QProcess> 0023 #include <QRegularExpression> 0024 #include <QInputDialog> 0025 #include <QTemporaryDir> 0026 #include <QTemporaryFile> 0027 #include <QFileDialog> 0028 #include <KPluginFactory> 0029 #include <KActionCollection> 0030 #include <KLocalizedString> 0031 #include <KParts/MainWindow> 0032 #include <KJob> 0033 #include <KSharedConfig> 0034 #include <KConfigGroup> 0035 0036 K_PLUGIN_FACTORY_WITH_JSON(KDevFlatpakFactory, "kdevflatpak.json", registerPlugin<FlatpakPlugin>();) 0037 0038 using namespace KDevelop; 0039 0040 FlatpakPlugin::FlatpakPlugin(QObject *parent, const QVariantList & /*args*/) 0041 : KDevelop::IPlugin( QStringLiteral("kdevflatpak"), parent ) 0042 { 0043 auto ac = actionCollection(); 0044 0045 auto action = new QAction(QIcon::fromTheme(QStringLiteral("run-build-clean")), i18nc("@action", "Rebuild Environment"), this); 0046 action->setWhatsThis(i18nc("@info:whatsthis", "Recompiles all dependencies for a fresh environment.")); 0047 ac->setDefaultShortcut(action, Qt::CTRL | Qt::META | Qt::Key_X); 0048 connect(action, &QAction::triggered, this, &FlatpakPlugin::rebuildCurrent); 0049 ac->addAction(QStringLiteral("runtime_flatpak_rebuild"), action); 0050 0051 auto exportAction = new QAction(QIcon::fromTheme(QStringLiteral("document-export")), i18nc("@action", "Export Flatpak Bundle..."), this); 0052 exportAction->setWhatsThis(i18nc("@info:whatsthis", "Exports the current build into a 'bundle.flatpak' file.")); 0053 ac->setDefaultShortcut(exportAction, Qt::CTRL | Qt::META | Qt::Key_E); 0054 connect(exportAction, &QAction::triggered, this, &FlatpakPlugin::exportCurrent); 0055 ac->addAction(QStringLiteral("runtime_flatpak_export"), exportAction); 0056 0057 auto remoteAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-remote-symbolic")), i18nc("@action", "Send to Device..."), this); 0058 ac->setDefaultShortcut(remoteAction, Qt::CTRL | Qt::META | Qt::Key_D); 0059 connect(remoteAction, &QAction::triggered, this, &FlatpakPlugin::executeOnRemoteDevice); 0060 ac->addAction(QStringLiteral("runtime_flatpak_remote"), remoteAction); 0061 0062 runtimeChanged(ICore::self()->runtimeController()->currentRuntime()); 0063 0064 setXMLFile( QStringLiteral("kdevflatpakplugin.rc") ); 0065 connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &FlatpakPlugin::runtimeChanged); 0066 } 0067 0068 FlatpakPlugin::~FlatpakPlugin() = default; 0069 0070 void FlatpakPlugin::runtimeChanged(KDevelop::IRuntime* newRuntime) 0071 { 0072 const bool isFlatpak = qobject_cast<FlatpakRuntime*>(newRuntime); 0073 0074 const auto& actions = actionCollection()->actions(); 0075 for (auto action: actions) { 0076 action->setEnabled(isFlatpak); 0077 } 0078 } 0079 0080 void FlatpakPlugin::rebuildCurrent() 0081 { 0082 const auto runtime = qobject_cast<FlatpakRuntime*>(ICore::self()->runtimeController()->currentRuntime()); 0083 Q_ASSERT(runtime); 0084 ICore::self()->runController()->registerJob(runtime->rebuild()); 0085 } 0086 0087 void FlatpakPlugin::exportCurrent() 0088 { 0089 const auto runtime = qobject_cast<FlatpakRuntime*>(ICore::self()->runtimeController()->currentRuntime()); 0090 Q_ASSERT(runtime); 0091 0092 const QString path = QFileDialog::getSaveFileName(ICore::self()->uiController()->activeMainWindow(), i18nc("@title:window", "Export %1", runtime->name()), {}, i18n("Flatpak Bundle (*.flatpak)")); 0093 if (!path.isEmpty()) { 0094 ICore::self()->runController()->registerJob(new ExecuteCompositeJob(runtime, runtime->exportBundle(path))); 0095 } 0096 } 0097 0098 void FlatpakPlugin::createRuntime(const KDevelop::Path &file, const QString &arch) 0099 { 0100 auto* dir = new QTemporaryDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/kdevelop-flatpak-")); 0101 const KDevelop::Path path(dir->path()); 0102 0103 auto process = FlatpakRuntime::createBuildDirectory(path, file, arch); 0104 connect(process, &KJob::finished, this, [path, file, arch, dir] (KJob* job) { 0105 if (job->error() != 0) { 0106 delete dir; 0107 return; 0108 } 0109 0110 auto rt = new FlatpakRuntime(path, file, arch); 0111 connect(rt, &QObject::destroyed, rt, [dir]() { delete dir; }); 0112 ICore::self()->runtimeController()->addRuntimes(rt); 0113 }); 0114 process->start(); 0115 } 0116 0117 static QStringList availableArches(const KDevelop::Path& url) 0118 { 0119 QProcess supportedArchesProcess; 0120 QStringList ret; 0121 0122 const auto doc = FlatpakRuntime::config(url); 0123 const QString sdkName = doc[QLatin1String("sdk")].toString(); 0124 const QString runtimeVersion = doc[QLatin1String("runtime-version")].toString(); 0125 const QString match = sdkName + QLatin1String("/(.+)/") + runtimeVersion; 0126 QObject::connect(&supportedArchesProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), 0127 &supportedArchesProcess, [&supportedArchesProcess, &match, &ret]() { 0128 QTextStream stream(&supportedArchesProcess); 0129 QRegularExpression rx(match); 0130 while (!stream.atEnd()) { 0131 const QString line = stream.readLine(); 0132 auto m = rx.match(line); 0133 if (m.hasMatch()) { 0134 ret << m.captured(1); 0135 } 0136 } 0137 }); 0138 0139 supportedArchesProcess.start(QStringLiteral("flatpak"), {QStringLiteral("list"), QStringLiteral("--runtime"), QStringLiteral("-d") }); 0140 supportedArchesProcess.waitForFinished(); 0141 return ret; 0142 } 0143 0144 KDevelop::ContextMenuExtension FlatpakPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) 0145 { 0146 QList<QUrl> urls; 0147 0148 if ( context->type() == KDevelop::Context::FileContext ) { 0149 auto* filectx = static_cast<KDevelop::FileContext*>(context); 0150 urls = filectx->urls(); 0151 } else if ( context->type() == KDevelop::Context::ProjectItemContext ) { 0152 auto* projctx = static_cast<KDevelop::ProjectItemContext*>(context); 0153 const auto items = projctx->items(); 0154 for (KDevelop::ProjectBaseItem* item : items) { 0155 if ( item->file() ) { 0156 urls << item->file()->path().toUrl(); 0157 } 0158 } 0159 } 0160 0161 const QRegularExpression nameRx(QStringLiteral(".*\\..*\\..*\\.json$")); 0162 for(auto it = urls.begin(); it != urls.end(); ) { 0163 if (it->isLocalFile() && it->path().contains(nameRx)) { 0164 ++it; 0165 } else { 0166 it = urls.erase(it); 0167 } 0168 } 0169 0170 if ( !urls.isEmpty() ) { 0171 KDevelop::ContextMenuExtension ext; 0172 for (const QUrl& url : qAsConst(urls)) { 0173 const KDevelop::Path file(url); 0174 const auto arches = availableArches(file); 0175 for (const QString& arch : arches) { 0176 auto action = new QAction(i18nc("@action:inmenu", "Build Flatpak %1 for %2", file.lastPathSegment(), arch), parent); 0177 connect(action, &QAction::triggered, this, [this, file, arch]() { 0178 createRuntime(file, arch); 0179 }); 0180 ext.addAction(KDevelop::ContextMenuExtension::RunGroup, action); 0181 } 0182 } 0183 0184 return ext; 0185 } 0186 0187 return KDevelop::IPlugin::contextMenuExtension(context, parent); 0188 } 0189 0190 void FlatpakPlugin::executeOnRemoteDevice() 0191 { 0192 const auto runtime = qobject_cast<FlatpakRuntime*>(ICore::self()->runtimeController()->currentRuntime()); 0193 Q_ASSERT(runtime); 0194 0195 KConfigGroup group(KSharedConfig::openConfig(), "Flatpak"); 0196 const QString lastDeviceAddress = group.readEntry("DeviceAddress"); 0197 const QString host = QInputDialog::getText( 0198 ICore::self()->uiController()->activeMainWindow(), i18nc("@title:window", "Choose Tag Name"), 0199 i18nc("@label:textbox", "Device hostname:"), 0200 QLineEdit::Normal, lastDeviceAddress 0201 ); 0202 if (host.isEmpty()) 0203 return; 0204 group.writeEntry("DeviceAddress", host); 0205 0206 auto* file = new QTemporaryFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') + runtime->name() + QLatin1String("XXXXXX.flatpak")); 0207 file->open(); 0208 file->close(); 0209 auto job = runtime->executeOnDevice(host, file->fileName()); 0210 file->setParent(file); 0211 0212 ICore::self()->runController()->registerJob(job); 0213 } 0214 0215 #include "flatpakplugin.moc" 0216 #include "moc_flatpakplugin.cpp"