File indexing completed on 2024-05-12 04:39:39

0001 /*
0002     SPDX-FileCopyrightText: 2010 Andreas Pakulat <apaku@gmx.de>
0003     SPDX-FileCopyrightText: 2014 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "settingsmanager.h"
0009 
0010 #include <QDataStream>
0011 #include <QThread>
0012 #include <QCoreApplication>
0013 #include <QMimeDatabase>
0014 
0015 #include <KConfig>
0016 #include <KConfigGroup>
0017 
0018 #include <interfaces/iproject.h>
0019 #include <interfaces/icore.h>
0020 #include <project/projectmodel.h>
0021 
0022 #include <algorithm>
0023 
0024 #include "compilerprovider.h"
0025 
0026 using namespace KDevelop;
0027 
0028 namespace {
0029 constexpr Utils::LanguageType configurableLanguageTypes[] =
0030     { Utils::C, Utils::Cpp, Utils::OpenCl, Utils::Cuda };
0031 
0032 namespace ConfigConstants {
0033 inline QString configKey() { return QStringLiteral("CustomDefinesAndIncludes"); }
0034 inline QString definesKey() { return QStringLiteral("Defines"); }
0035 inline QString includesKey() { return QStringLiteral("Includes"); }
0036 inline QString projectPathPrefix() { return QStringLiteral("ProjectPath"); }
0037 inline QString projectPathKey() { return QStringLiteral("Path"); }
0038 
0039 inline QString customBuildSystemGroup() { return QStringLiteral("CustomBuildSystem"); }
0040 inline QString definesAndIncludesGroup() { return QStringLiteral("Defines And Includes"); }
0041 
0042 inline QString compilersGroup() { return QStringLiteral("Compilers"); }
0043 inline QString compilerNameKey() { return QStringLiteral("Name"); }
0044 inline QString compilerPathKey() { return QStringLiteral("Path"); }
0045 inline QString compilerTypeKey() { return QStringLiteral("Type"); }
0046 
0047 QString parserArgumentsKey(Utils::LanguageType languageType)
0048 {
0049     switch (languageType) {
0050     case Utils::C:
0051         return QStringLiteral("parserArgumentsC");
0052     case Utils::Cpp:
0053         return QStringLiteral("parserArguments");
0054     case Utils::OpenCl:
0055         return QStringLiteral("parserArgumentsOpenCL");
0056     case Utils::Cuda:
0057         return QStringLiteral("parserArgumentsCuda");
0058     // TODO: is there a need for "parserArgumentsObjC[++]" and if so how/where
0059     // if not, merge the ObjC cases with the C/C++ cases.
0060     case Utils::ObjC:
0061         return QStringLiteral("parserArgumentsC");
0062     case Utils::ObjCpp:
0063         return QStringLiteral("parserArguments");
0064     case Utils::Other:
0065         break;
0066     }
0067     Q_UNREACHABLE();
0068 }
0069 
0070 QString parseAmbiguousAsCPP()
0071 {
0072     return QStringLiteral("parseAmbiguousAsCPP");
0073 }
0074 }
0075 
0076 // the grouplist is randomly sorted b/c it uses QSet internally
0077 // we sort the keys here, as the structure is properly defined for us
0078 QStringList sorted(QStringList list)
0079 {
0080     std::sort(list.begin(), list.end());
0081     return list;
0082 }
0083 
0084 ParserArguments createDefaultArguments()
0085 {
0086     ParserArguments arguments;
0087     arguments[Utils::C] = QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c99");
0088     arguments[Utils::Cpp] = QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11");
0089     arguments[Utils::OpenCl] = QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -cl-std=CL1.1");
0090     arguments[Utils::Cuda] = QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11");
0091     // For now, use the same arguments for ObjC(++) as for C(++). -Wall enables a number
0092     // of language-specific warnings, removing the need to add them explicitly.
0093     // (https://embeddedartistry.com/blog/2017/3/7/clang-weverything)
0094     arguments[Utils::ObjC] = arguments[Utils::C];
0095     arguments[Utils::ObjCpp] = arguments[Utils::Cpp];
0096     arguments.parseAmbiguousAsCPP = true;
0097 
0098     return arguments;
0099 }
0100 
0101 const ParserArguments& defaultArguments()
0102 {
0103     static ParserArguments arguments = createDefaultArguments();
0104     return arguments;
0105 }
0106 
0107 CompilerPointer createCompilerFromConfig(KConfigGroup& cfg)
0108 {
0109     auto grp = cfg.group("Compiler");
0110     auto name = grp.readEntry( ConfigConstants::compilerNameKey(), QString() );
0111     if (name.isEmpty()) {
0112         return SettingsManager::globalInstance()->provider()->defaultCompiler();
0113     }
0114 
0115     const auto& compilers = SettingsManager::globalInstance()->provider()->compilers();
0116     for (auto& c : compilers) {
0117         if (c->name() == name) {
0118             return c;
0119         }
0120     }
0121 
0122     // Otherwise we have no such compiler registered (broken config file), return default one
0123     return SettingsManager::globalInstance()->provider()->defaultCompiler();
0124 }
0125 
0126 void writeCompilerToConfig(KConfigGroup& cfg, const CompilerPointer& compiler)
0127 {
0128     Q_ASSERT(compiler);
0129 
0130     auto grp = cfg.group("Compiler");
0131     // Store only compiler name, path and type retrieved from registered compilers
0132     grp.writeEntry(ConfigConstants::compilerNameKey(), compiler->name());
0133 }
0134 
0135 void doWriteSettings( KConfigGroup grp, const QVector<ConfigEntry>& paths )
0136 {
0137     int pathIndex = 0;
0138     for ( const auto& path : paths ) {
0139         KConfigGroup pathgrp = grp.group(ConfigConstants::projectPathPrefix() + QString::number(pathIndex++));
0140         pathgrp.writeEntry(ConfigConstants::projectPathKey(), path.path);
0141         for (auto type : configurableLanguageTypes) {
0142             pathgrp.writeEntry(ConfigConstants::parserArgumentsKey(type), path.parserArguments[type]);
0143         }
0144         pathgrp.writeEntry(ConfigConstants::parseAmbiguousAsCPP(), path.parserArguments.parseAmbiguousAsCPP);
0145 
0146         {
0147             int index = 0;
0148             KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey()));
0149             for (auto& include : path.includes) {
0150                 includes.writeEntry(QString::number(++index), include);
0151             }
0152 
0153         }
0154         {
0155             KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey()));
0156             for (auto it = path.defines.begin(); it != path.defines.end(); ++it) {
0157                 defines.writeEntry(it.key(), it.value());
0158             }
0159         }
0160         writeCompilerToConfig(pathgrp, path.compiler);
0161     }
0162 }
0163 
0164 /// @param remove if true all read entries will be removed from the config file
0165 QVector<ConfigEntry> doReadSettings( KConfigGroup grp, bool remove = false )
0166 {
0167     QVector<ConfigEntry> paths;
0168     const auto& sortedGroupNames = sorted(grp.groupList());
0169     for (const QString& grpName : sortedGroupNames) {
0170         if (!grpName.startsWith(ConfigConstants::projectPathPrefix())) {
0171             continue;
0172         }
0173         KConfigGroup pathgrp = grp.group( grpName );
0174 
0175         ConfigEntry path;
0176         path.path = pathgrp.readEntry(ConfigConstants::projectPathKey(), "");
0177         for (auto type : configurableLanguageTypes) {
0178             path.parserArguments[type] = pathgrp.readEntry(ConfigConstants::parserArgumentsKey(type), defaultArguments()[type]);
0179         }
0180         path.parserArguments.parseAmbiguousAsCPP = pathgrp.readEntry(ConfigConstants::parseAmbiguousAsCPP(), defaultArguments().parseAmbiguousAsCPP);
0181 
0182         for (auto type : configurableLanguageTypes) {
0183             if (path.parserArguments[type].isEmpty()) {
0184                 path.parserArguments[type] = defaultArguments()[type];
0185             }
0186         }
0187 
0188         { // defines
0189             // Backwards compatibility with old config style
0190             if(pathgrp.hasKey(ConfigConstants::definesKey())) {
0191                 QByteArray tmp = pathgrp.readEntry(ConfigConstants::definesKey(), QByteArray() );
0192                 QDataStream s( tmp );
0193                 s.setVersion( QDataStream::Qt_4_5 );
0194                 // backwards compatible reading
0195                 QHash<QString, QVariant> defines;
0196                 s >> defines;
0197                 path.setDefines(defines);
0198             } else {
0199                 KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey()));
0200                 QMap<QString, QString> defMap = defines.entryMap();
0201                 path.defines.reserve(defMap.size());
0202                 for(auto it = defMap.constBegin(); it != defMap.constEnd(); ++it) {
0203                     QString key = it.key();
0204                     if(key.isEmpty()) {
0205                         // Toss out the invalid key and value since a valid
0206                         // value needs a valid key
0207                         continue;
0208                     } else {
0209                             path.defines.insert(key, it.value());
0210                     }
0211                 }
0212             }
0213         }
0214 
0215         { // includes
0216             // Backwards compatibility with old config style
0217             if(pathgrp.hasKey(ConfigConstants::includesKey())){
0218                 QByteArray tmp = pathgrp.readEntry(ConfigConstants::includesKey(), QByteArray());
0219                 QDataStream s( tmp );
0220                 s.setVersion( QDataStream::Qt_4_5 );
0221                 s >> path.includes;
0222             } else {
0223                 KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey()));
0224                 const QMap<QString, QString> incMap = includes.entryMap();
0225                 for (auto& value :incMap) {
0226                     if(value.isEmpty()){
0227                         continue;
0228                     }
0229                     path.includes += value;
0230                 }
0231             }
0232         }
0233 
0234         path.compiler = createCompilerFromConfig(pathgrp);
0235 
0236         if ( remove ) {
0237             pathgrp.deleteGroup();
0238         }
0239 
0240         Q_ASSERT(!path.parserArguments.isAnyEmpty());
0241         paths << path;
0242     }
0243 
0244     return paths;
0245 }
0246 
0247 /**
0248  * Reads and converts paths from old (Custom Build System's) format to the current one.
0249  * @return all converted paths (if any)
0250  */
0251 QVector<ConfigEntry> convertedPaths( KConfig* cfg )
0252 {
0253     KConfigGroup group = cfg->group(ConfigConstants::customBuildSystemGroup());
0254     if ( !group.isValid() )
0255         return {};
0256 
0257     QVector<ConfigEntry> paths;
0258     const auto sortedGroupNames = sorted(group.groupList());
0259     for (const QString& grpName : sortedGroupNames) {
0260         KConfigGroup subgroup = group.group( grpName );
0261         if ( !subgroup.isValid() )
0262             continue;
0263 
0264         paths += doReadSettings( subgroup, true );
0265     }
0266 
0267     return paths;
0268 }
0269 
0270 }
0271 
0272 void ConfigEntry::setDefines(const QHash<QString, QVariant>& newDefines)
0273 {
0274     defines.clear();
0275     defines.reserve(newDefines.size());
0276     for (auto it = newDefines.begin(); it != newDefines.end(); ++it) {
0277         defines[it.key()] = it.value().toString();
0278     }
0279 }
0280 
0281 SettingsManager::SettingsManager()
0282   : m_provider(this)
0283 {}
0284 
0285 SettingsManager::~SettingsManager()
0286 {}
0287 
0288 SettingsManager* SettingsManager::globalInstance()
0289 {
0290     Q_ASSERT(QThread::currentThread() == qApp->thread());
0291     static SettingsManager s_globalInstance;
0292     return &s_globalInstance;
0293 }
0294 
0295 CompilerProvider* SettingsManager::provider()
0296 {
0297     return &m_provider;
0298 }
0299 
0300 const CompilerProvider* SettingsManager::provider() const
0301 {
0302     return &m_provider;
0303 }
0304 
0305 void SettingsManager::writePaths( KConfig* cfg, const QVector< ConfigEntry >& paths )
0306 {
0307     Q_ASSERT(QThread::currentThread() == qApp->thread());
0308 
0309     KConfigGroup grp = cfg->group(ConfigConstants::configKey());
0310     if ( !grp.isValid() )
0311         return;
0312 
0313     grp.deleteGroup();
0314 
0315     doWriteSettings( grp, paths );
0316 }
0317 
0318 QVector<ConfigEntry> SettingsManager::readPaths( KConfig* cfg ) const
0319 {
0320     Q_ASSERT(QThread::currentThread() == qApp->thread());
0321 
0322     auto converted = convertedPaths( cfg );
0323     if ( !converted.isEmpty() ) {
0324         const_cast<SettingsManager*>(this)->writePaths( cfg, converted );
0325         return converted;
0326     }
0327 
0328     KConfigGroup grp = cfg->group(ConfigConstants::configKey());
0329     if ( !grp.isValid() ) {
0330         return {};
0331     }
0332 
0333     return doReadSettings( grp );
0334 }
0335 
0336 bool SettingsManager::needToReparseCurrentProject( KConfig* cfg ) const
0337 {
0338     auto grp = cfg->group(ConfigConstants::definesAndIncludesGroup());
0339     return grp.readEntry( "reparse", true );
0340 }
0341 
0342 void SettingsManager::writeUserDefinedCompilers(const QVector< CompilerPointer >& compilers)
0343 {
0344     QVector< CompilerPointer > editableCompilers;
0345     for (const auto& compiler : compilers) {
0346         if (!compiler->editable()) {
0347             continue;
0348         }
0349         editableCompilers.append(compiler);
0350     }
0351 
0352     KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup());
0353     config.deleteGroup();
0354     config.writeEntry("number", editableCompilers.count());
0355     int i = 0;
0356     for (const auto& compiler : editableCompilers) {
0357         KConfigGroup grp = config.group(QString::number(i));
0358         ++i;
0359 
0360         grp.writeEntry(ConfigConstants::compilerNameKey(), compiler->name());
0361         grp.writeEntry(ConfigConstants::compilerPathKey(), compiler->path());
0362         grp.writeEntry(ConfigConstants::compilerTypeKey(), compiler->factoryName());
0363     }
0364     config.sync();
0365 }
0366 
0367 QVector< CompilerPointer > SettingsManager::userDefinedCompilers() const
0368 {
0369     QVector< CompilerPointer > compilers;
0370 
0371     KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup());
0372     int count = config.readEntry("number", 0);
0373     for (int i = 0; i < count; i++) {
0374         KConfigGroup grp = config.group(QString::number(i));
0375 
0376         auto name = grp.readEntry(ConfigConstants::compilerNameKey(), QString());
0377         auto path = grp.readEntry(ConfigConstants::compilerPathKey(), QString());
0378         auto type = grp.readEntry(ConfigConstants::compilerTypeKey(), QString());
0379 
0380         const auto cf = m_provider.compilerFactories();
0381         for (auto& f : cf) {
0382             if (f->name() == type) {
0383                 auto compiler = f->createCompiler(name, path);
0384                 compilers.append(compiler);
0385             }
0386         }
0387     }
0388     return compilers;
0389 }
0390 
0391 ParserArguments SettingsManager::defaultParserArguments() const
0392 {
0393     return defaultArguments();
0394 }
0395 
0396 ConfigEntry::ConfigEntry(const QString& path)
0397     : path(path)
0398     , compiler(SettingsManager::globalInstance()->provider()->defaultCompiler())
0399     , parserArguments(defaultArguments())
0400 {}
0401 
0402 namespace Utils {
0403 LanguageType languageType(const QString& path, bool treatAmbiguousAsCPP)
0404 {
0405     QMimeDatabase db;
0406     const auto mimeType = db.mimeTypeForFile(path).name();
0407     if (mimeType == QLatin1String("text/x-csrc") ||
0408         mimeType == QLatin1String("text/x-chdr") ) {
0409         if (treatAmbiguousAsCPP) {
0410             if (path.endsWith(QLatin1String(".h"), Qt::CaseInsensitive)) {
0411                 return Cpp;
0412             }
0413         }
0414 
0415         // TODO: No proper mime type detection possible yet
0416         // cf. https://bugs.freedesktop.org/show_bug.cgi?id=26913
0417         if (path.endsWith(QLatin1String(".cl"), Qt::CaseInsensitive)) {
0418             return OpenCl;
0419         }
0420 
0421         // TODO: No proper mime type detection possible yet
0422         // cf. https://bugs.freedesktop.org/show_bug.cgi?id=23700
0423         if (path.endsWith(QLatin1String(".cu"), Qt::CaseInsensitive)) {
0424             return Cuda;
0425         }
0426 
0427         return C;
0428     }
0429 
0430     if (mimeType == QLatin1String("text/x-c++src") ||
0431         mimeType == QLatin1String("text/x-c++hdr") ) {
0432         return Cpp;
0433     }
0434 
0435     if (mimeType == QLatin1String("text/x-objc++src")) {
0436         return ObjCpp;
0437     }
0438 
0439     if (mimeType == QLatin1String("text/x-objcsrc")) {
0440         return ObjC;
0441     }
0442 
0443     if (mimeType == QLatin1String("text/x-opencl-src")) {
0444         return OpenCl;
0445     }
0446 
0447     return Other;
0448 }
0449 }
0450 
0451 bool ParserArguments::isAnyEmpty() const
0452 {
0453     return std::any_of(std::begin(arguments), std::end(arguments),
0454         [](const QString& args) { return args.isEmpty(); }
0455     );
0456 }