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 }