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 &currentCompiler, 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 }