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"