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

0001 /*
0002     SPDX-FileCopyrightText: 2014 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "compilerprovider.h"
0008 
0009 #include "debug.h"
0010 #include "compilerfactories.h"
0011 #include "settingsmanager.h"
0012 
0013 #include <interfaces/iruntime.h>
0014 #include <interfaces/iruntimecontroller.h>
0015 #include <interfaces/iproject.h>
0016 #include <interfaces/iprojectcontroller.h>
0017 #include <interfaces/ibuildsystemmanager.h>
0018 #include <project/projectmodel.h>
0019 
0020 #include <KLocalizedString>
0021 #include <QStandardPaths>
0022 #include <QDir>
0023 
0024 using namespace KDevelop;
0025 
0026 namespace
0027 {
0028 class NoCompiler : public ICompiler
0029 {
0030 public:
0031     NoCompiler():
0032         ICompiler(i18nc("@item no compiler", "None"), QString(), QString(), false)
0033     {}
0034 
0035     QHash< QString, QString > defines(Utils::LanguageType, const QString&) const override
0036     {
0037         return {};
0038     }
0039 
0040     Path::List includes(Utils::LanguageType, const QString&) const override
0041     {
0042         return {};
0043     }
0044 };
0045 
0046 static CompilerPointer createDummyCompiler()
0047 {
0048     static CompilerPointer compiler(new NoCompiler());
0049     return compiler;
0050 }
0051 
0052 ConfigEntry configForItem(KDevelop::ProjectBaseItem* item)
0053 {
0054     if(!item){
0055         return ConfigEntry();
0056     }
0057 
0058     const Path itemPath = item->path();
0059     const Path rootDirectory = item->project()->path();
0060 
0061     const auto paths = SettingsManager::globalInstance()->readPaths(item->project()->projectConfiguration().data());
0062     ConfigEntry config;
0063     Path closestPath;
0064 
0065     // find config entry closest to the requested item
0066     for (const auto& entry : paths) {
0067         auto configEntry = entry;
0068         Path targetDirectory = rootDirectory;
0069 
0070         targetDirectory.addPath(entry.path);
0071 
0072         if (targetDirectory == itemPath) {
0073             return configEntry;
0074         }
0075 
0076         if (targetDirectory.isParentOf(itemPath)) {
0077             if (config.path.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()) {
0078                 config = configEntry;
0079                 closestPath = targetDirectory;
0080             }
0081         }
0082     }
0083 
0084     return config;
0085 }
0086 
0087 QString parserArguments( const ConfigEntry& config, Utils::LanguageType languageType, ProjectBaseItem* item )
0088 {
0089     auto args = config.parserArguments[languageType];
0090     if (item && item->project()->buildSystemManager()) {
0091         args += QLatin1Char(' ');
0092         args += item->project()->buildSystemManager()->extraArguments(item);
0093     }
0094     return args;
0095 }
0096 }
0097 
0098 ProjectTargetItem* findCompiledTarget(ProjectBaseItem* item)
0099 {
0100     const auto targets = item->targetList();
0101     for (auto* item : targets) {
0102         if (item->type() == ProjectBaseItem::ExecutableTarget || item->type() == ProjectBaseItem::LibraryTarget) {
0103             return item;
0104         }
0105     }
0106 
0107     const auto folders = item->folderList();
0108     for (auto* folder: folders) {
0109         auto target = findCompiledTarget(folder);
0110         if (target)
0111             return target;
0112     }
0113     return nullptr;
0114 }
0115 
0116 CompilerProvider::CompilerProvider( SettingsManager* settings, QObject* parent )
0117     : QObject( parent )
0118     , m_settings(settings)
0119 {
0120     m_factories = {
0121         CompilerFactoryPointer(new GccFactory()),
0122         CompilerFactoryPointer(new ClangFactory()),
0123 #ifdef _WIN32
0124         CompilerFactoryPointer(new MsvcFactory()),
0125 #endif
0126     };
0127 
0128     if (!QStandardPaths::findExecutable( QStringLiteral("clang") ).isEmpty()) {
0129         m_factories[1]->registerDefaultCompilers(this);
0130     }
0131     if (!QStandardPaths::findExecutable( QStringLiteral("gcc") ).isEmpty()) {
0132         m_factories[0]->registerDefaultCompilers(this);
0133     }
0134 #ifdef _WIN32
0135     if (!QStandardPaths::findExecutable(QStringLiteral("cl.exe")).isEmpty()) {
0136         m_factories[2]->registerDefaultCompilers(this);
0137     }
0138 #endif
0139 
0140     registerCompiler(createDummyCompiler());
0141     retrieveUserDefinedCompilers();
0142 
0143     connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, [this]() { m_defaultProvider.clear(); });
0144     connect(ICore::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &CompilerProvider::projectChanged);
0145     connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &CompilerProvider::projectChanged);
0146 }
0147 
0148 CompilerProvider::~CompilerProvider() = default;
0149 
0150 void CompilerProvider::projectChanged(KDevelop::IProject* p)
0151 {
0152     const auto target = findCompiledTarget(p->projectItem());
0153     if (!target)
0154         return;
0155 
0156     auto path = p->buildSystemManager()->compiler(target);
0157     qCDebug(DEFINESANDINCLUDES) << "found compiler" << path;
0158     if (path.isEmpty())
0159         return;
0160 
0161     Q_ASSERT(QDir::isAbsolutePath(path.toLocalFile()));
0162     const auto pathString = path.toLocalFile();
0163     auto it = std::find_if(m_compilers.begin(), m_compilers.end(),
0164         [&pathString](const CompilerPointer& compiler) { return compiler->path() == pathString; });
0165     if (it != m_compilers.end()) {
0166         m_defaultProvider = *it;
0167         return;
0168     }
0169 
0170     //we need to search, sdk compiler names are weird: arm-linux-androideabi-g++
0171     for (auto& factory : qAsConst(m_factories)) {
0172         if (factory->isSupported(path)) {
0173             auto compiler = factory->createCompiler(path.lastPathSegment(), pathString);
0174             registerCompiler(compiler);
0175             m_defaultProvider = compiler;
0176         }
0177     }
0178 
0179     qCDebug(DEFINESANDINCLUDES) << "using compiler" << m_defaultProvider << path;
0180 }
0181 
0182 QHash<QString, QString> CompilerProvider::defines( const QString& path ) const
0183 {
0184     auto config = configForItem(nullptr);
0185     auto languageType = Utils::languageType(path, config.parserArguments.parseAmbiguousAsCPP);
0186     // If called on files that we can't compile, return an empty set of defines.
0187     if (languageType == Utils::Other) {
0188         return {};
0189     }
0190 
0191     return config.compiler->defines(languageType, parserArguments(config, languageType, nullptr));
0192 }
0193 
0194 QHash<QString, QString> CompilerProvider::defines( ProjectBaseItem* item ) const
0195 {
0196     auto config = configForItem(item);
0197     auto languageType = Utils::Cpp;
0198     if (item) {
0199         languageType = Utils::languageType(item->path().path(), config.parserArguments.parseAmbiguousAsCPP);
0200     }
0201     // If called on files that we can't compile, return an empty set of defines.
0202     if (languageType == Utils::Other) {
0203         return {};
0204     }
0205 
0206     return config.compiler->defines(languageType, parserArguments(config, languageType, item));
0207 }
0208 
0209 Path::List CompilerProvider::includes( const QString& path ) const
0210 {
0211     auto config = configForItem(nullptr);
0212     auto languageType = Utils::languageType(path, config.parserArguments.parseAmbiguousAsCPP);
0213     // If called on files that we can't compile, return an empty set of includes.
0214     if (languageType == Utils::Other) {
0215         return {};
0216     }
0217 
0218     return config.compiler->includes(languageType, parserArguments(config, languageType, nullptr));
0219 }
0220 
0221 Path::List CompilerProvider::includes( ProjectBaseItem* item ) const
0222 {
0223     auto config = configForItem(item);
0224     auto languageType = Utils::Cpp;
0225     if (item) {
0226         languageType = Utils::languageType(item->path().path(), config.parserArguments.parseAmbiguousAsCPP);
0227     }
0228     // If called on files that we can't compile, return an empty set of includes.
0229     if (languageType == Utils::Other) {
0230         return {};
0231     }
0232 
0233     return config.compiler->includes(languageType, parserArguments(config, languageType, item));
0234 }
0235 
0236 Path::List CompilerProvider::frameworkDirectories( const QString& /* path */ ) const
0237 {
0238     return {};
0239 }
0240 
0241 Path::List CompilerProvider::frameworkDirectories( ProjectBaseItem* /* item */ ) const
0242 {
0243     return {};
0244 }
0245 
0246 IDefinesAndIncludesManager::Type CompilerProvider::type() const
0247 {
0248     return IDefinesAndIncludesManager::CompilerSpecific;
0249 }
0250 
0251 CompilerPointer CompilerProvider::defaultCompiler() const
0252 {
0253     if (m_defaultProvider)
0254         return m_defaultProvider;
0255 
0256     auto rt = ICore::self()->runtimeController()->currentRuntime();
0257 
0258     for ( const CompilerPointer& compiler : m_compilers ) {
0259         if (rt->findExecutable(compiler->path()).isEmpty())
0260             continue;
0261         m_defaultProvider = compiler;
0262         break;
0263     }
0264     if (!m_defaultProvider)
0265         m_defaultProvider = createDummyCompiler();
0266 
0267     qCDebug(DEFINESANDINCLUDES) << "new default compiler" << rt->name() << m_defaultProvider->name() << m_defaultProvider->path();
0268     return m_defaultProvider;
0269 }
0270 
0271 QVector< CompilerPointer > CompilerProvider::compilers() const
0272 {
0273     return m_compilers;
0274 }
0275 
0276 CompilerPointer CompilerProvider::compilerForItem( KDevelop::ProjectBaseItem* item ) const
0277 {
0278     auto compiler = configForItem(item).compiler;
0279     Q_ASSERT(compiler);
0280     return compiler;
0281 }
0282 
0283 bool CompilerProvider::registerCompiler(const CompilerPointer& compiler)
0284 {
0285     if (!compiler) {
0286         return false;
0287     }
0288 
0289     for (auto& c : qAsConst(m_compilers)) {
0290         if (c->name() == compiler->name()) {
0291             return false;
0292         }
0293     }
0294     m_compilers.append(compiler);
0295     return true;
0296 }
0297 
0298 void CompilerProvider::unregisterCompiler(const CompilerPointer& compiler)
0299 {
0300     if (!compiler->editable()) {
0301         return;
0302     }
0303 
0304     for (int i = 0; i < m_compilers.count(); i++) {
0305         if (m_compilers[i]->name() == compiler->name()) {
0306             m_compilers.remove(i);
0307             break;
0308         }
0309     }
0310 }
0311 
0312 QVector< CompilerFactoryPointer > CompilerProvider::compilerFactories() const
0313 {
0314     return m_factories;
0315 }
0316 
0317 void CompilerProvider::retrieveUserDefinedCompilers()
0318 {
0319     const auto compilers = m_settings->userDefinedCompilers();
0320     for (auto& c : compilers) {
0321         registerCompiler(c);
0322     }
0323 }
0324 
0325 #include "moc_compilerprovider.cpp"