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 "flatpakruntime.h" 0008 #include "flatpakplugin.h" 0009 #include "debug_flatpak.h" 0010 0011 #include <util/executecompositejob.h> 0012 #include <outputview/outputexecutejob.h> 0013 #include <interfaces/iruncontroller.h> 0014 #include <interfaces/icore.h> 0015 0016 #include <KLocalizedString> 0017 #include <KProcess> 0018 #include <KActionCollection> 0019 #include <QProcess> 0020 #include <QTemporaryDir> 0021 #include <QDir> 0022 #include <QJsonDocument> 0023 #include <QJsonArray> 0024 #include <QJsonObject> 0025 #include <QStandardPaths> 0026 0027 using namespace KDevelop; 0028 0029 template <typename T, typename Q, typename W> 0030 static T kTransform(const Q& list, W func) 0031 { 0032 T ret; 0033 ret.reserve(list.size()); 0034 for (auto it = list.constBegin(), itEnd = list.constEnd(); it!=itEnd; ++it) 0035 ret += func(*it); 0036 return ret; 0037 } 0038 0039 static KJob* createExecuteJob(const QStringList &program, const QString &title, const QUrl &wd = {}, bool checkExitCode = true) 0040 { 0041 auto* process = new OutputExecuteJob; 0042 process->setProperties(OutputExecuteJob::DisplayStdout | OutputExecuteJob::DisplayStderr); 0043 process->setExecuteOnHost(true); 0044 process->setJobName(title); 0045 process->setWorkingDirectory(wd); 0046 process->setCheckExitCode(checkExitCode); 0047 // TODO: call process->setStandardToolView(IOutputView::?); to prevent creating a new tool view for each 0048 // job in OutputJob::startOutput(). Such nonstandard and unshared tool views are also not configurable. 0049 *process << program; 0050 return process; 0051 } 0052 0053 KJob* FlatpakRuntime::createBuildDirectory(const KDevelop::Path &buildDirectory, const KDevelop::Path &file, const QString &arch) 0054 { 0055 return createExecuteJob(QStringList{QStringLiteral("flatpak-builder"), QLatin1String("--arch=")+arch, QStringLiteral("--build-only"), buildDirectory.toLocalFile(), file.toLocalFile() }, i18n("Flatpak"), file.parent().toUrl()); 0056 } 0057 0058 FlatpakRuntime::FlatpakRuntime(const KDevelop::Path &buildDirectory, const KDevelop::Path &file, const QString &arch) 0059 : KDevelop::IRuntime() 0060 , m_file(file) 0061 , m_buildDirectory(buildDirectory) 0062 , m_arch(arch) 0063 { 0064 refreshJson(); 0065 } 0066 0067 FlatpakRuntime::~FlatpakRuntime() 0068 { 0069 } 0070 0071 void FlatpakRuntime::refreshJson() 0072 { 0073 const auto doc = config(); 0074 const QString sdkName = doc[QLatin1String("sdk")].toString(); 0075 const QString runtimeVersion = doc.value(QLatin1String("runtime-version")).toString(); 0076 const QString usedRuntime = sdkName + QLatin1Char('/') + m_arch + QLatin1Char('/') + runtimeVersion; 0077 0078 //First check if local user has flatpak runtime before checking system runtimes. 0079 m_sdkPath = KDevelop::Path(QDir::homePath() + QLatin1String("/.local/share/flatpak/runtime/") + usedRuntime + QLatin1String("/active/files")); 0080 if(!QFile::exists(m_sdkPath.toLocalFile())) { 0081 m_sdkPath = KDevelop::Path(QLatin1String("/var/lib/flatpak/runtime/") + usedRuntime + QLatin1String("/active/files")); 0082 } 0083 qCDebug(FLATPAK) << "flatpak runtime path..." << name() << m_sdkPath; 0084 Q_ASSERT(QFile::exists(m_sdkPath.toLocalFile())); 0085 0086 m_finishArgs = kTransform<QStringList>(doc[QLatin1String("finish-args")].toArray(), [](const QJsonValue& val){ return val.toString(); }); 0087 } 0088 0089 void FlatpakRuntime::setEnabled(bool /*enable*/) 0090 { 0091 } 0092 0093 void FlatpakRuntime::startProcess(QProcess* process) const 0094 { 0095 //Take any environment variables specified in process and pass through to flatpak. 0096 QStringList env_args; 0097 const QStringList env_vars = process->processEnvironment().toStringList(); 0098 for (const QString& env_var : env_vars) { 0099 env_args << QLatin1String("--env=") + env_var; 0100 } 0101 const QStringList args = m_finishArgs + env_args + QStringList{QStringLiteral("build"), QStringLiteral("--talk-name=org.freedesktop.DBus"), m_buildDirectory.toLocalFile(), process->program()} << process->arguments(); 0102 process->setProgram(QStringLiteral("flatpak")); 0103 process->setArguments(args); 0104 0105 qCDebug(FLATPAK) << "starting qprocess" << process->program() << process->arguments(); 0106 process->start(); 0107 } 0108 0109 void FlatpakRuntime::startProcess(KProcess* process) const 0110 { 0111 //Take any environment variables specified in process and pass through to flatpak. 0112 QStringList env_args; 0113 const QStringList env_vars = process->processEnvironment().toStringList(); 0114 for (const QString& env_var : env_vars) { 0115 env_args << QLatin1String("--env=") + env_var; 0116 } 0117 process->setProgram(QStringList{QStringLiteral("flatpak")} << m_finishArgs << env_args << QStringList{QStringLiteral("build"), QStringLiteral("--talk-name=org.freedesktop.DBus"), m_buildDirectory.toLocalFile() } << process->program()); 0118 0119 qCDebug(FLATPAK) << "starting kprocess" << process->program().join(QLatin1Char(' ')); 0120 process->start(); 0121 } 0122 0123 KJob* FlatpakRuntime::rebuild() 0124 { 0125 QDir(m_buildDirectory.toLocalFile()).removeRecursively(); 0126 auto job = createBuildDirectory(m_buildDirectory, m_file, m_arch); 0127 refreshJson(); 0128 return job; 0129 } 0130 0131 QList<KJob*> FlatpakRuntime::exportBundle(const QString &path) const 0132 { 0133 const auto doc = config(); 0134 0135 auto* dir = new QTemporaryDir(QDir::tempPath()+QLatin1String("/flatpak-tmp-repo")); 0136 if (!dir->isValid() || doc.isEmpty()) { 0137 qCWarning(FLATPAK) << "Couldn't export:" << path << dir->isValid() << dir->path() << doc.isEmpty(); 0138 return {}; 0139 } 0140 0141 const QString name = doc[QLatin1String("id")].toString(); 0142 QStringList args = m_finishArgs; 0143 if (doc.contains(QLatin1String("command"))) 0144 args << QLatin1String("--command=")+doc[QLatin1String("command")].toString(); 0145 0146 const QString title = i18n("Bundling"); 0147 const QList<KJob*> jobs = { 0148 createExecuteJob(QStringList{QStringLiteral("flatpak"), QStringLiteral("build-finish"), m_buildDirectory.toLocalFile()} << args, title, {}, false), 0149 createExecuteJob(QStringList{QStringLiteral("flatpak"), QStringLiteral("build-export"), QLatin1String("--arch=")+m_arch, dir->path(), m_buildDirectory.toLocalFile()}, title), 0150 createExecuteJob(QStringList{QStringLiteral("flatpak"), QStringLiteral("build-bundle"), QLatin1String("--arch=")+m_arch, dir->path(), path, name }, title) 0151 }; 0152 connect(jobs.last(), &QObject::destroyed, jobs.last(), [dir]() { delete dir; }); 0153 return jobs; 0154 } 0155 0156 QString FlatpakRuntime::name() const 0157 { 0158 return QStringLiteral("%1 - %2").arg(m_arch, m_file.lastPathSegment()); 0159 } 0160 0161 KJob * FlatpakRuntime::executeOnDevice(const QString& host, const QString &path) const 0162 { 0163 const QString name = config()[QLatin1String("id")].toString(); 0164 const QString destPath = QStringLiteral("/tmp/kdevelop-test-app.flatpak"); 0165 const QString replicatePath = QStringLiteral("/tmp/replicate.sh"); 0166 const QString localReplicatePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdevflatpak/replicate.sh")); 0167 0168 const QString title = i18n("Run on Device"); 0169 const QList<KJob*> jobs = exportBundle(path) << QList<KJob*> { 0170 createExecuteJob({QStringLiteral("scp"), path, host+QLatin1Char(':')+destPath}, title), 0171 createExecuteJob({QStringLiteral("scp"), localReplicatePath, host+QLatin1Char(':')+replicatePath}, title), 0172 createExecuteJob({QStringLiteral("ssh"), host, QStringLiteral("flatpak"), QStringLiteral("install"), QStringLiteral("--user"), QStringLiteral("--bundle"), QStringLiteral("-y"), destPath}, title), 0173 createExecuteJob({QStringLiteral("ssh"), host, QStringLiteral("bash"), replicatePath, QStringLiteral("plasmashell"), QStringLiteral("flatpak"), QStringLiteral("run"), name }, title), 0174 }; 0175 return new KDevelop::ExecuteCompositeJob( parent(), jobs ); 0176 } 0177 0178 QJsonObject FlatpakRuntime::config(const KDevelop::Path& path) 0179 { 0180 QFile f(path.toLocalFile()); 0181 if (!f.open(QIODevice::ReadOnly)) { 0182 qCWarning(FLATPAK) << "couldn't open" << path; 0183 return {}; 0184 } 0185 0186 QJsonParseError error; 0187 auto doc = QJsonDocument::fromJson(f.readAll(), &error); 0188 if (error.error) { 0189 qCWarning(FLATPAK) << "couldn't parse" << path << error.errorString(); 0190 return {}; 0191 } 0192 0193 return doc.object(); 0194 } 0195 0196 QJsonObject FlatpakRuntime::config() const 0197 { 0198 return config(m_file); 0199 } 0200 0201 Path FlatpakRuntime::pathInHost(const KDevelop::Path& runtimePath) const 0202 { 0203 KDevelop::Path ret = runtimePath; 0204 if (!runtimePath.isLocalFile()) { 0205 return ret; 0206 } 0207 0208 const auto prefix = runtimePath.segments().at(0); 0209 if (prefix == QLatin1String("usr")) { 0210 const auto relpath = KDevelop::Path(QStringLiteral("/usr")).relativePath(runtimePath); 0211 ret = Path(m_sdkPath, relpath); 0212 } else if (prefix == QLatin1String("app")) { 0213 const auto relpath = KDevelop::Path(QStringLiteral("/app")).relativePath(runtimePath); 0214 ret = Path(m_buildDirectory, QLatin1String("/active/files/") + relpath); 0215 } 0216 0217 qCDebug(FLATPAK) << "path in host" << runtimePath << ret; 0218 return ret; 0219 } 0220 0221 Path FlatpakRuntime::pathInRuntime(const KDevelop::Path& localPath) const 0222 { 0223 KDevelop::Path ret = localPath; 0224 if (m_sdkPath.isParentOf(localPath)) { 0225 const auto relpath = m_sdkPath.relativePath(localPath); 0226 ret = Path(Path(QStringLiteral("/usr")), relpath); 0227 } else { 0228 const Path bdfiles(m_buildDirectory, QStringLiteral("/active/files")); 0229 if (bdfiles.isParentOf(localPath)) { 0230 const auto relpath = bdfiles.relativePath(localPath); 0231 ret = Path(Path(QStringLiteral("/app")), relpath); 0232 } 0233 } 0234 0235 qCDebug(FLATPAK) << "path in runtime" << localPath << ret; 0236 return ret; 0237 } 0238 0239 QString FlatpakRuntime::findExecutable(const QString& executableName) const 0240 { 0241 QStringList rtPaths; 0242 0243 auto envPaths = getenv(QByteArrayLiteral("PATH")).split(':'); 0244 std::transform(envPaths.begin(), envPaths.end(), std::back_inserter(rtPaths), 0245 [this](QByteArray p) { 0246 return pathInHost(Path(QString::fromLocal8Bit(p))).toLocalFile(); 0247 }); 0248 0249 return QStandardPaths::findExecutable(executableName, rtPaths); 0250 } 0251 0252 QByteArray FlatpakRuntime::getenv(const QByteArray& varname) const 0253 { 0254 if (varname == "KDEV_DEFAULT_INSTALL_PREFIX") 0255 return "/app"; 0256 return qgetenv(varname.constData()); 0257 } 0258 0259 KDevelop::Path FlatpakRuntime::buildPath() const 0260 { 0261 auto file = m_file; 0262 file.setLastPathSegment(QStringLiteral(".flatpak-builder")); 0263 file.addPath(QStringLiteral("kdevelop")); 0264 return file; 0265 } 0266 0267 #include "moc_flatpakruntime.cpp"