File indexing completed on 2024-05-05 04:38:44
0001 /* 0002 SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "environmentprofilelist.h" 0008 #include "kdevstringhandler.h" 0009 #include "debug.h" 0010 0011 #include <QMap> 0012 #include <QStringList> 0013 #include <QString> 0014 0015 #include <KConfigGroup> 0016 0017 #include <QProcessEnvironment> 0018 0019 namespace KDevelop { 0020 class EnvironmentProfileListPrivate 0021 { 0022 public: 0023 QMap<QString, QMap<QString, QString>> m_profiles; 0024 QString m_defaultProfileName; 0025 }; 0026 } 0027 0028 using namespace KDevelop; 0029 0030 namespace { 0031 0032 namespace Strings { 0033 // TODO: migrate to more consistent key term "Default Environment Profile" 0034 inline QString defaultEnvProfileKey() { return QStringLiteral("Default Environment Group"); } 0035 inline QString envGroup() { return QStringLiteral("Environment Settings"); } 0036 // TODO: migrate to more consistent key term "Profile List" 0037 inline QString profileListKey() { return QStringLiteral("Group List"); } 0038 inline QString defaultProfileName() { return QStringLiteral("default"); } 0039 } 0040 0041 void decode(KConfig* config, EnvironmentProfileListPrivate* d) 0042 { 0043 KConfigGroup cfg(config, Strings::envGroup()); 0044 d->m_defaultProfileName = cfg.readEntry(Strings::defaultEnvProfileKey(), Strings::defaultProfileName()); 0045 const QStringList profileNames = 0046 cfg.readEntry(Strings::profileListKey(), QStringList{Strings::defaultProfileName()}); 0047 for (const auto& profileName : profileNames) { 0048 KConfigGroup envgrp(&cfg, profileName); 0049 QMap<QString, QString> variables; 0050 const auto varNames = envgrp.keyList(); 0051 for (const QString& varname : varNames) { 0052 variables[varname] = envgrp.readEntry(varname, QString()); 0053 } 0054 0055 d->m_profiles.insert(profileName, variables); 0056 } 0057 } 0058 0059 void encode(KConfig* config, const EnvironmentProfileListPrivate* d) 0060 { 0061 KConfigGroup cfg(config, Strings::envGroup()); 0062 cfg.writeEntry(Strings::defaultEnvProfileKey(), d->m_defaultProfileName); 0063 cfg.writeEntry(Strings::profileListKey(), d->m_profiles.keys()); 0064 const auto oldGroupList = cfg.groupList(); 0065 for (const QString& group : oldGroupList) { 0066 if (!d->m_profiles.contains(group)) { 0067 cfg.deleteGroup(group); 0068 } 0069 } 0070 0071 for (auto it = d->m_profiles.cbegin(), itEnd = d->m_profiles.cend(); it != itEnd; ++it) { 0072 KConfigGroup envgrp(&cfg, it.key()); 0073 envgrp.deleteGroup(); 0074 0075 const auto val = it.value(); 0076 for (auto it2 = val.cbegin(), it2End = val.cend(); it2 != it2End; ++it2) { 0077 envgrp.writeEntry(it2.key(), *it2); 0078 } 0079 } 0080 0081 cfg.sync(); 0082 } 0083 0084 } 0085 0086 EnvironmentProfileList::EnvironmentProfileList(const EnvironmentProfileList& rhs) 0087 : d_ptr(new EnvironmentProfileListPrivate(*rhs.d_ptr)) 0088 { 0089 } 0090 0091 EnvironmentProfileList& EnvironmentProfileList::operator=(const EnvironmentProfileList& rhs) 0092 { 0093 Q_D(EnvironmentProfileList); 0094 0095 *d = *rhs.d_ptr; 0096 return *this; 0097 } 0098 0099 EnvironmentProfileList::EnvironmentProfileList(const KSharedConfigPtr& config) 0100 : d_ptr(new EnvironmentProfileListPrivate) 0101 { 0102 Q_D(EnvironmentProfileList); 0103 0104 decode(config.data(), d); 0105 } 0106 0107 EnvironmentProfileList::EnvironmentProfileList(KConfig* config) 0108 : d_ptr(new EnvironmentProfileListPrivate) 0109 { 0110 Q_D(EnvironmentProfileList); 0111 0112 decode(config, d); 0113 } 0114 0115 EnvironmentProfileList::~EnvironmentProfileList() = default; 0116 0117 QMap<QString, QString> EnvironmentProfileList::variables(const QString& profileName) const 0118 { 0119 Q_D(const EnvironmentProfileList); 0120 0121 return d->m_profiles.value(profileName.isEmpty() ? d->m_defaultProfileName : profileName); 0122 } 0123 0124 QMap<QString, QString>& EnvironmentProfileList::variables(const QString& profileName) 0125 { 0126 Q_D(EnvironmentProfileList); 0127 0128 return d->m_profiles[profileName.isEmpty() ? d->m_defaultProfileName : profileName]; 0129 } 0130 0131 QString EnvironmentProfileList::defaultProfileName() const 0132 { 0133 Q_D(const EnvironmentProfileList); 0134 0135 return d->m_defaultProfileName; 0136 } 0137 0138 void EnvironmentProfileList::setDefaultProfile(const QString& profileName) 0139 { 0140 Q_D(EnvironmentProfileList); 0141 0142 if (profileName.isEmpty() || 0143 !d->m_profiles.contains(profileName)) { 0144 return; 0145 } 0146 0147 d->m_defaultProfileName = profileName; 0148 } 0149 0150 void EnvironmentProfileList::saveSettings(KConfig* config) const 0151 { 0152 Q_D(const EnvironmentProfileList); 0153 0154 encode(config, d); 0155 config->sync(); 0156 } 0157 0158 void EnvironmentProfileList::loadSettings(KConfig* config) 0159 { 0160 Q_D(EnvironmentProfileList); 0161 0162 d->m_profiles.clear(); 0163 decode(config, d); 0164 } 0165 0166 QStringList EnvironmentProfileList::profileNames() const 0167 { 0168 Q_D(const EnvironmentProfileList); 0169 0170 return d->m_profiles.keys(); 0171 } 0172 0173 void EnvironmentProfileList::removeProfile(const QString& profileName) 0174 { 0175 Q_D(EnvironmentProfileList); 0176 0177 d->m_profiles.remove(profileName); 0178 } 0179 0180 EnvironmentProfileList::EnvironmentProfileList() 0181 : d_ptr(new EnvironmentProfileListPrivate) 0182 { 0183 } 0184 0185 QStringList EnvironmentProfileList::createEnvironment(const QString& profileName, 0186 const QStringList& defaultEnvironment) const 0187 { 0188 QMap<QString, QString> retMap; 0189 for (const QString& line : defaultEnvironment) { 0190 QString varName = line.section(QLatin1Char('='), 0, 0); 0191 QString varValue = line.section(QLatin1Char('='), 1); 0192 retMap.insert(varName, varValue); 0193 } 0194 0195 if (!profileName.isEmpty()) { 0196 const auto userMap = variables(profileName); 0197 0198 for (QMap<QString, QString>::const_iterator it = userMap.constBegin(); 0199 it != userMap.constEnd(); ++it) { 0200 retMap.insert(it.key(), it.value()); 0201 } 0202 } 0203 0204 QStringList env; 0205 env.reserve(retMap.size()); 0206 for (QMap<QString, QString>::const_iterator it = retMap.constBegin(); 0207 it != retMap.constEnd(); ++it) { 0208 env << it.key() + QLatin1Char('=') + it.value(); 0209 } 0210 0211 return env; 0212 } 0213 0214 static QString expandVariable(const QString &key, const QString &value, 0215 QMap<QString, QString> &output, 0216 const QMap<QString, QString> &input, 0217 const QProcessEnvironment &environment) 0218 { 0219 if (value.isEmpty()) 0220 return QString(); 0221 0222 auto it = output.constFind(key); 0223 if (it != output.constEnd()) { 0224 // nothing to do, value was expanded already 0225 return *it; 0226 } 0227 0228 // not yet expanded, do that now 0229 0230 auto variableValue = [&](const QString &variable) { 0231 if (environment.contains(variable)) { 0232 return environment.value(variable); 0233 } else if (variable == key) { 0234 // This must be a reference to a system environment variable of the 0235 // same name, which happens to be unset. Treat it as empty. 0236 return QString(); 0237 } else if (input.contains(variable)) { 0238 return expandVariable(variable, input.value(variable), output, input, environment); 0239 } else { 0240 qCWarning(UTIL) << "Couldn't find replacement for" << variable; 0241 return QString(); 0242 } 0243 }; 0244 0245 constexpr ushort escapeChar{'\\'}; 0246 constexpr ushort variableStartChar{'$'}; 0247 const auto isSpecialSymbol = [=](QChar c) { 0248 return c.unicode() == escapeChar || c.unicode() == variableStartChar; 0249 }; 0250 0251 auto& expanded = output[key]; 0252 expanded.reserve(value.size()); 0253 const int lastIndex = value.size() - 1; 0254 int i = 0; 0255 // Never treat value.back() as a special symbol (nothing to escape or start). 0256 while (i < lastIndex) { 0257 const auto currentChar = value[i]; 0258 switch (currentChar.unicode()) { 0259 case escapeChar: { 0260 const auto nextChar = value[i+1]; 0261 if (!isSpecialSymbol(nextChar)) { 0262 expanded += currentChar; // Nothing to escape => keep the escapeChar. 0263 } 0264 expanded += nextChar; 0265 i += 2; 0266 break; 0267 } 0268 case variableStartChar: { 0269 ++i; 0270 const auto match = matchPossiblyBracedAsciiVariable(value.midRef(i)); 0271 if (match.length == 0) { 0272 expanded += currentChar; // Not a variable name start. 0273 } else { 0274 expanded += variableValue(match.name); 0275 i += match.length; 0276 } 0277 break; 0278 } 0279 default: 0280 expanded += currentChar; 0281 ++i; 0282 } 0283 } 0284 if (i == lastIndex) { 0285 expanded += value[i]; 0286 } 0287 return expanded; 0288 } 0289 0290 void KDevelop::expandVariables(QMap<QString, QString>& variables, const QProcessEnvironment& environment) 0291 { 0292 QMap<QString, QString> expanded; 0293 for (auto it = variables.cbegin(), end = variables.cend(); it != end; ++it) { 0294 expandVariable(it.key(), it.value(), expanded, variables, environment); 0295 } 0296 variables = expanded; 0297 }