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 }