File indexing completed on 2024-04-28 05:48:21
0001 #include "compiledbreader.h" 0002 0003 #include <QDir> 0004 #include <QFile> 0005 #include <QFileInfo> 0006 #include <QJsonArray> 0007 #include <QJsonDocument> 0008 #include <QJsonObject> 0009 #include <QJsonValue> 0010 #include <QProcess> 0011 #include <QVariant> 0012 0013 #include <KTextEditor/MainWindow> 0014 0015 #include "gitprocess.h" 0016 0017 #include <optional> 0018 0019 QString CompileDBReader::locateCompileCommands(KTextEditor::MainWindow *mw, const QString &openedFile) 0020 { 0021 Q_ASSERT(mw); 0022 0023 // Check using project 0024 QObject *project = mw->pluginView(QStringLiteral("kateprojectplugin")); 0025 if (project) { 0026 QString baseDir = project->property("projectBaseDir").toString(); 0027 if (baseDir.endsWith(QLatin1Char('/'))) { 0028 baseDir.chop(1); 0029 } 0030 0031 if (QFile::exists(baseDir + QStringLiteral("/compile_commands.json"))) { 0032 return baseDir + QStringLiteral("/compile_commands.json"); 0033 } 0034 } 0035 0036 // Check using git 0037 // For now it only checks for compile_commands in the .git/../ directory 0038 QFileInfo fi(openedFile); 0039 if (fi.exists()) { 0040 auto basePathOptional = getRepoBasePath(fi.absolutePath()); 0041 if (basePathOptional.has_value()) { 0042 auto basePath = basePathOptional.value(); 0043 if (basePath.endsWith(QLatin1Char('/'))) { 0044 basePath.chop(1); 0045 } 0046 if (QFile::exists(basePath + QStringLiteral("/compile_commands.json"))) { 0047 return basePath + QStringLiteral("/compile_commands.json"); 0048 } 0049 } 0050 } 0051 0052 qWarning() << "Compile DB not found for file: " << openedFile; 0053 0054 return QString(); 0055 } 0056 0057 QString CompileDBReader::argsForFile(const QString &compile_commandsPath, const QString &file) 0058 { 0059 QFile f(compile_commandsPath); 0060 if (!f.open(QFile::ReadOnly)) { 0061 // TODO: Use Output view to report error 0062 qWarning() << "Failed to load compile_commands: " << f.errorString(); 0063 return {}; 0064 } 0065 0066 QJsonParseError error; 0067 QJsonDocument cmdCmds = QJsonDocument::fromJson(f.readAll(), &error); 0068 if (error.error != QJsonParseError::NoError) { 0069 qWarning() << "Failed to read compile_commands: " << error.errorString(); 0070 return {}; 0071 } 0072 0073 if (!cmdCmds.isArray()) { 0074 qWarning() << "Invalid compile_commands, root element is not an array"; 0075 return {}; 0076 } 0077 0078 QJsonArray commandsArray = cmdCmds.array(); 0079 0080 for (const auto &cmdJV : commandsArray) { 0081 auto compileCommand = cmdJV.toObject(); 0082 auto cmpCmdFile = compileCommand.value(QStringLiteral("file")).toString(); 0083 0084 QFileInfo fi(cmpCmdFile); 0085 if (fi.isRelative()) { 0086 QString dir = QDir::cleanPath(compileCommand.value(QStringLiteral("directory")).toString()); 0087 // QString file = QDir::cleanPath(dir + QStringLiteral("/") + cmpCmdFile); 0088 } else { 0089 if (fi.canonicalFilePath() == file) { 0090 return compileCommand.value(QStringLiteral("command")).toString(); 0091 } 0092 } 0093 } 0094 0095 qWarning() << "compile_command for " << file << " not found"; 0096 return {}; 0097 } 0098 0099 static void addCurrentFilePathToCompileCommands(const QString ¤tCompiler, QStringList &commands, const QString &fileBasePath) 0100 { 0101 // For these compilers we include the current file path to 0102 // the compiler commands 0103 QStringList compilers = { 0104 QStringLiteral("c++"), 0105 QStringLiteral("g++"), 0106 QStringLiteral("gcc"), 0107 QStringLiteral("clang"), 0108 QStringLiteral("clang++"), 0109 }; 0110 0111 for (const auto &c : compilers) { 0112 if (currentCompiler.contains(c)) { 0113 commands << QStringLiteral("-I") + fileBasePath; 0114 } 0115 } 0116 } 0117 0118 /* 0119 * Remove args like "-include xyz.h" 0120 * CompilerExplorer doesn't like them 0121 */ 0122 static void removeIncludeArgument(QStringList &commands) 0123 { 0124 QStringList toRemove; 0125 for (int i = 0; i < commands.size(); ++i) { 0126 if (commands.at(i) == QStringLiteral("-include")) { 0127 if (i + 1 < commands.size()) { 0128 toRemove << commands.at(i); 0129 toRemove << commands.at(i + 1); 0130 ++i; 0131 } 0132 } 0133 } 0134 0135 for (const auto &rem : qAsConst(toRemove)) { 0136 commands.removeAll(rem); 0137 } 0138 } 0139 0140 QString CompileDBReader::filteredArgsForFile(const QString &compile_commandsPath, const QString &file) 0141 { 0142 QString args = argsForFile(compile_commandsPath, file); 0143 0144 QFileInfo fi(file); 0145 QString fileBasePath = fi.canonicalPath(); 0146 0147 QStringList argsList = args.split(QLatin1Char(' ')); 0148 QString currentCompiler = argsList.takeFirst(); // First is the compiler, drop it 0149 QStringList finalArgs; 0150 finalArgs.reserve(argsList.size() - 2); 0151 0152 for (auto &&arg : argsList) { 0153 if (arg == QStringLiteral("-o")) 0154 continue; 0155 if (arg.endsWith(QStringLiteral(".o"))) 0156 continue; 0157 if (arg == QStringLiteral("-c")) 0158 continue; 0159 if (file == arg || file.contains(arg)) 0160 continue; 0161 0162 finalArgs << arg; 0163 } 0164 0165 removeIncludeArgument(finalArgs); 0166 0167 addCurrentFilePathToCompileCommands(currentCompiler, finalArgs, fileBasePath); 0168 0169 return finalArgs.join(QLatin1Char(' ')); 0170 }