File indexing completed on 2024-05-05 16:45:14

0001 // SPDX-FileCopyrightText: 2022 Gleb Popov <arrowd@FreeBSD.org>
0002 // SPDX-License-Identifier: BSD-3-Clause
0003 
0004 #include "craftruntime.h"
0005 #include "debug_craft.h"
0006 
0007 #include <QFileInfo>
0008 #include <QStandardPaths>
0009 #include <QProcess>
0010 #include <KProcess>
0011 
0012 using namespace KDevelop;
0013 
0014 namespace {
0015 auto craftSetupHelperRelativePath()
0016 {
0017     return QLatin1String{"/craft/bin/CraftSetupHelper.py"};
0018 }
0019 }
0020 
0021 CraftRuntime::CraftRuntime(const QString& craftRoot, const QString& pythonExecutable)
0022     : m_craftRoot(craftRoot)
0023     , m_pythonExecutable(pythonExecutable)
0024 {
0025     Q_ASSERT(!pythonExecutable.isEmpty());
0026 
0027     m_watcher.addPath(craftRoot + craftSetupHelperRelativePath());
0028 
0029     connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, [this](const QString& path) {
0030         if (QFileInfo::exists(path)) {
0031             refreshEnvCache();
0032             if (!m_watcher.files().contains(path)) {
0033                 m_watcher.addPath(path);
0034             }
0035         }
0036     });
0037     refreshEnvCache();
0038 }
0039 
0040 QString CraftRuntime::name() const
0041 {
0042     return QStringLiteral("Craft [%1]").arg(m_craftRoot);
0043 }
0044 
0045 QString CraftRuntime::findCraftRoot(Path startingPoint)
0046 {
0047     // CraftRuntime doesn't handle remote directories, because it needs
0048     // to check file existence in the findCraftRoot() function
0049     if (startingPoint.isRemote())
0050         return QString();
0051 
0052     QString craftRoot;
0053     while (true) {
0054         bool craftSettingsIniExists = QFileInfo::exists(startingPoint.path() + QLatin1String("/etc/CraftSettings.ini"));
0055         bool craftSetupHelperExists = QFileInfo::exists(startingPoint.path() + craftSetupHelperRelativePath());
0056         if (craftSettingsIniExists && craftSetupHelperExists) {
0057             craftRoot = startingPoint.path();
0058             break;
0059         }
0060 
0061         if (!startingPoint.hasParent())
0062             break;
0063         startingPoint = startingPoint.parent();
0064     }
0065 
0066     return QFileInfo(craftRoot).canonicalFilePath();
0067 }
0068 
0069 QString CraftRuntime::findPython()
0070 {
0071     // Craft requires Python 3.6+, not any "python3", but
0072     // - If the user set up Craft already, there is a high probability that
0073     //   "python3" is a correct one
0074     // - We are running only CraftSetupHelper.py, not the whole Craft, so
0075     //   the 3.6+ requirement might be not relevant for this case.
0076     // So just search for "python3" and hope for the best.
0077     return QStandardPaths::findExecutable(QStringLiteral("python3"));
0078 }
0079 
0080 void CraftRuntime::setEnabled(bool enabled)
0081 {
0082     if (enabled)
0083         qCDebug(CRAFT) << "Enabling Craft runtime at" << m_craftRoot << "with" << m_pythonExecutable;
0084 }
0085 
0086 void CraftRuntime::refreshEnvCache()
0087 {
0088     QProcess python;
0089     python.start(m_pythonExecutable,
0090                  QStringList{m_craftRoot + craftSetupHelperRelativePath(), QStringLiteral("--getenv")});
0091     python.waitForFinished(5000);
0092 
0093     if (python.error() != QProcess::UnknownError) {
0094         if (python.error() == QProcess::Timedout)
0095             qCWarning(CRAFT) << "CraftSetupHelper.py execution timed out";
0096         else
0097             qCWarning(CRAFT) << "CraftSetupHelper.py execution failed:" << python.error() << python.errorString();
0098         return;
0099     }
0100 
0101     if (python.exitCode()) {
0102         qCWarning(CRAFT) << "CraftSetupHelper.py execution failed with code" << python.exitCode();
0103         return;
0104     }
0105 
0106     m_envCache.clear();
0107 
0108     const QList<QByteArray> output = python.readAllStandardOutput().split('\n');
0109     for (const auto& line : output) {
0110         // line contains things like "VAR=VALUE"
0111         int equalsSignIndex = line.indexOf('=');
0112         if (equalsSignIndex == -1)
0113             continue;
0114 
0115         QByteArray varName = line.left(equalsSignIndex);
0116         QByteArray value = line.mid(equalsSignIndex + 1);
0117         m_envCache.emplace_back(varName, value);
0118     }
0119 }
0120 
0121 QByteArray CraftRuntime::getenv(const QByteArray& varname) const
0122 {
0123     auto it = std::find_if(m_envCache.begin(), m_envCache.end(), [&varname](const EnvironmentVariable& envVar) {
0124         return envVar.name == varname;
0125     });
0126 
0127     return it != m_envCache.end() ? it->value : QByteArray();
0128 }
0129 
0130 QString CraftRuntime::findExecutable(const QString& executableName) const
0131 {
0132     auto runtimePaths = QString::fromLocal8Bit(getenv(QByteArrayLiteral("PATH"))).split(QLatin1Char(':'));
0133 
0134     return QStandardPaths::findExecutable(executableName, runtimePaths);
0135 }
0136 
0137 Path CraftRuntime::pathInHost(const Path& runtimePath) const
0138 {
0139     return runtimePath;
0140 }
0141 
0142 Path CraftRuntime::pathInRuntime(const Path& localPath) const
0143 {
0144     return localPath;
0145 }
0146 
0147 void CraftRuntime::startProcess(KProcess* process) const
0148 {
0149     QStringList program = process->program();
0150     QString executableInRuntime = findExecutable(program.constFirst());
0151     if (executableInRuntime != program.constFirst()) {
0152         program.first() = std::move(executableInRuntime);
0153         process->setProgram(program);
0154     }
0155     setEnvironmentVariables(process);
0156     process->start();
0157 }
0158 
0159 void CraftRuntime::startProcess(QProcess* process) const
0160 {
0161     QString executableInRuntime = findExecutable(process->program());
0162     process->setProgram(executableInRuntime);
0163     setEnvironmentVariables(process);
0164     process->start();
0165 }
0166 
0167 void CraftRuntime::setEnvironmentVariables(QProcess* process) const
0168 {
0169     auto env = process->processEnvironment();
0170 
0171     for (const auto& envVar : m_envCache) {
0172         env.insert(QString::fromLocal8Bit(envVar.name), QString::fromLocal8Bit(envVar.value));
0173     }
0174 
0175     process->setProcessEnvironment(env);
0176 }
0177 
0178 EnvironmentVariable::EnvironmentVariable(const QByteArray& name, const QByteArray& value)
0179     : name(name.trimmed())
0180     , value(value)
0181 {
0182 }
0183 
0184 #include "moc_craftruntime.cpp"