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 "gcclikecompiler.h"
0008 
0009 #include <QFileInfo>
0010 #include <QProcess>
0011 #include <QRegularExpression>
0012 #include <QMap>
0013 #include <QScopeGuard>
0014 
0015 #include <interfaces/iruntime.h>
0016 #include <interfaces/iruntimecontroller.h>
0017 
0018 #include <debug.h>
0019 
0020 using namespace KDevelop;
0021 
0022 namespace
0023 {
0024 
0025 QString languageOption(Utils::LanguageType type)
0026 {
0027     switch (type) {
0028         case Utils::C:
0029             return QStringLiteral("-xc");
0030         case Utils::Cpp:
0031             return QStringLiteral("-xc++");
0032         case Utils::OpenCl:
0033             return QStringLiteral("-xcl");
0034         case Utils::Cuda:
0035             return QStringLiteral("-xcuda");
0036         case Utils::ObjC:
0037             return QStringLiteral("-xobjective-c");
0038         case Utils::ObjCpp:
0039             return QStringLiteral("-xobjective-c++");
0040         default:
0041             Q_UNREACHABLE();
0042     }
0043 }
0044 
0045 QString languageStandard(const QString& arguments, Utils::LanguageType type)
0046 {
0047     // TODO: handle -ansi flag: In C mode, this is equivalent to -std=c90. In C++ mode, it is equivalent to -std=c++98.
0048     // NOTE: we put the greedy .* in front to find the last occurrence
0049     const QRegularExpression regexp(QStringLiteral(".*(-std=\\S+)"));
0050     auto result = regexp.match(arguments);
0051     if (result.hasMatch()) {
0052         return result.captured(1);
0053     }
0054 
0055     switch (type) {
0056         case Utils::C:
0057         case Utils::ObjC:
0058             return QStringLiteral("-std=c99");
0059         case Utils::Cpp:
0060         case Utils::ObjCpp:
0061         case Utils::Cuda:
0062             return QStringLiteral("-std=c++11");
0063         case Utils::OpenCl:
0064             return QStringLiteral("-cl-std=CL1.1");
0065         default:
0066             Q_UNREACHABLE();
0067     }
0068 }
0069 
0070 }
0071 
0072 Defines GccLikeCompiler::defines(Utils::LanguageType type, const QString& arguments) const
0073 {
0074     // first do a lookup by type and arguments
0075     auto& data = m_definesIncludes[type][arguments];
0076     if (data.definedMacros.wasCached) {
0077         return data.definedMacros.data;
0078     }
0079 
0080     // TODO: what about -mXXX or -target= flags, some of these change search paths/defines
0081     const QStringList compilerArguments{
0082         languageOption(type),
0083         languageStandard(arguments, type),
0084         QStringLiteral("-dM"),
0085         QStringLiteral("-E"),
0086         QStringLiteral("-"),
0087     };
0088 
0089     // if that fails, do a lookup based on the actual compiler arguments
0090     // often these are much less variable than the arguments passed per TU
0091     // so here we can better exploit the cache by doing this two-phase lookup
0092     auto& cachedData = m_defines[compilerArguments];
0093     auto updateDataOnExit = qScopeGuard([&] {
0094         // we don't want to run the below code more than once
0095         // even if it errors out
0096         cachedData.wasCached = true;
0097         data.definedMacros = cachedData;
0098     });
0099 
0100     if (cachedData.wasCached) {
0101         return cachedData.data;
0102     }
0103 
0104     const auto rt = ICore::self()->runtimeController()->currentRuntime();
0105     QProcess proc;
0106     proc.setProcessChannelMode(QProcess::MergedChannels);
0107     proc.setStandardInputFile(QProcess::nullDevice());
0108     proc.setProgram(path());
0109     proc.setArguments(compilerArguments);
0110     rt->startProcess(&proc);
0111 
0112     if ( !proc.waitForStarted( 2000 ) || !proc.waitForFinished( 2000 ) ) {
0113         qCDebug(DEFINESANDINCLUDES) <<  "Unable to read standard macro definitions from "<< path() << compilerArguments;
0114         return {};
0115     }
0116 
0117     if (proc.exitCode() != 0) {
0118         qCWarning(DEFINESANDINCLUDES) <<  "error while fetching defines for the compiler:" << path() << compilerArguments << proc.readAll();
0119         return {};
0120     }
0121 
0122     // #define a 1
0123     // #define a
0124     QRegExp defineExpression(QStringLiteral("#define\\s+(\\S+)(?:\\s+(.*)\\s*)?"));
0125 
0126     while ( proc.canReadLine() ) {
0127         auto line = proc.readLine();
0128 
0129         if ( defineExpression.indexIn(QString::fromUtf8(line)) != -1 ) {
0130             cachedData.data[defineExpression.cap(1)] = defineExpression.cap(2).trimmed();
0131         }
0132     }
0133 
0134     return cachedData.data;
0135 }
0136 
0137 Path::List GccLikeCompiler::includes(Utils::LanguageType type, const QString& arguments) const
0138 {
0139     // first do a lookup by type and arguments
0140     auto& data = m_definesIncludes[type][arguments];
0141     if (data.includePaths.wasCached) {
0142         return data.includePaths.data;
0143     }
0144 
0145     const QStringList compilerArguments {
0146         languageOption(type), languageStandard(arguments, type), QStringLiteral("-E"), QStringLiteral("-v"),
0147         QStringLiteral("-"),
0148     };
0149 
0150     // if that fails, do a lookup based on the actual compiler arguments
0151     // often these are much less variable than the arguments passed per TU
0152     // so here we can better exploit the cache by doing this two-phase lookup
0153     auto& cachedData = m_includes[compilerArguments];
0154     auto updateDataOnExit = qScopeGuard([&] {
0155         // we don't want to run the below code more than once
0156         // even if it errors out
0157         cachedData.wasCached = true;
0158         data.includePaths = cachedData;
0159     });
0160 
0161     if (cachedData.wasCached) {
0162         return cachedData.data;
0163     }
0164 
0165     const auto rt = ICore::self()->runtimeController()->currentRuntime();
0166     QProcess proc;
0167     proc.setProcessChannelMode( QProcess::MergedChannels );
0168 
0169     // The following command will spit out a bunch of information we don't care
0170     // about before spitting out the include paths.  The parts we care about
0171     // look like this:
0172     // #include "..." search starts here:
0173     // #include <...> search starts here:
0174     //  /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2
0175     //  /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/i486-linux-gnu
0176     //  /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/backward
0177     //  /usr/local/include
0178     //  /usr/lib/gcc/i486-linux-gnu/4.1.2/include
0179     //  /usr/include
0180     // End of search list.
0181 
0182     proc.setStandardInputFile(QProcess::nullDevice());
0183     proc.setProgram(path());
0184     proc.setArguments(compilerArguments);
0185     rt->startProcess(&proc);
0186 
0187     if ( !proc.waitForStarted( 2000 ) || !proc.waitForFinished( 2000 ) ) {
0188         qCDebug(DEFINESANDINCLUDES) <<  "Unable to read standard include paths from " << path();
0189         return {};
0190     }
0191 
0192     if (proc.exitCode() != 0) {
0193         qCWarning(DEFINESANDINCLUDES) <<  "error while fetching includes for the compiler:" << path() << proc.readAll();
0194         return {};
0195     }
0196 
0197     // We'll use the following constants to know what we're currently parsing.
0198     enum Status {
0199         Initial,
0200         FirstSearch,
0201         Includes,
0202         Finished
0203     };
0204     Status mode = Initial;
0205 
0206     const auto output = QString::fromLocal8Bit( proc.readAllStandardOutput() );
0207     const auto lines = output.splitRef(QLatin1Char('\n'));
0208     for (const auto& line : lines) {
0209         switch ( mode ) {
0210             case Initial:
0211                 if ( line.indexOf( QLatin1String("#include \"...\"") ) != -1 ) {
0212                     mode = FirstSearch;
0213                 }
0214                 break;
0215             case FirstSearch:
0216                 if ( line.indexOf( QLatin1String("#include <...>") ) != -1 ) {
0217                     mode = Includes;
0218                 }
0219                 break;
0220             case Includes:
0221                 //Detect the include-paths by the first space that is prepended. Reason: The list may contain relative paths like "."
0222                 if (!line.startsWith(QLatin1Char(' '))) {
0223                     // We've reached the end of the list.
0224                     mode = Finished;
0225                 } else {
0226                     // This is an include path, add it to the list.
0227                     auto hostPath = rt->pathInHost(Path(QFileInfo(line.trimmed().toString()).canonicalFilePath()));
0228                     // but skip folders with compiler builtins, we cannot parse these with clang
0229                     if (!QFile::exists(hostPath.toLocalFile() + QLatin1String("/cpuid.h"))) {
0230                         cachedData.data << Path(QFileInfo(hostPath.toLocalFile()).canonicalFilePath());
0231                     }
0232                 }
0233                 break;
0234             default:
0235                 break;
0236         }
0237         if ( mode == Finished ) {
0238             break;
0239         }
0240     }
0241 
0242     return cachedData.data;
0243 }
0244 
0245 void GccLikeCompiler::invalidateCache()
0246 {
0247     m_definesIncludes.clear();
0248 }
0249 
0250 GccLikeCompiler::GccLikeCompiler(const QString& name, const QString& path, bool editable, const QString& factoryName):
0251     ICompiler(name, path, factoryName, editable)
0252 {
0253     connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &GccLikeCompiler::invalidateCache);
0254 }
0255 
0256 #include "moc_gcclikecompiler.cpp"