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"