1 #include "compiledbreader.h"
2
3 #include <QDir>
4 #include <QFile>
5 #include <QFileInfo>
6 #include <QJsonArray>
7 #include <QJsonDocument>
8 #include <QJsonObject>
9 #include <QJsonValue>
10 #include <QProcess>
11 #include <QVariant>
12
13 #include <KTextEditor/MainWindow>
14
15 #include "gitprocess.h"
16
17 #include <optional>
18
19 // TODO: copied from kate project plugin, move to shared/
getDotGitPath(const QString & repo)20 std::optional<QString> getDotGitPath(const QString &repo)
21 {
22 /* This call is intentionally blocking because we need git path for everything else */
23 QProcess git;
24 if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--absolute-git-dir")})) {
25 return std::nullopt;
26 }
27 git.start(QProcess::ReadOnly);
28 if (git.waitForStarted() && git.waitForFinished(-1)) {
29 if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
30 return std::nullopt;
31 }
32 QString dotGitPath = QString::fromUtf8(git.readAllStandardOutput());
33 if (dotGitPath.endsWith(QLatin1String("\n"))) {
34 dotGitPath.remove(QLatin1String(".git\n"));
35 } else {
36 dotGitPath.remove(QLatin1String(".git"));
37 }
38 return dotGitPath;
39 }
40 return std::nullopt;
41 }
42
locateCompileCommands(KTextEditor::MainWindow * mw,const QString & openedFile)43 QString CompileDBReader::locateCompileCommands(KTextEditor::MainWindow *mw, const QString &openedFile)
44 {
45 Q_ASSERT(mw);
46
47 // Check using project
48 QObject *project = mw->pluginView(QStringLiteral("kateprojectplugin"));
49 if (project) {
50 QString baseDir = project->property("projectBaseDir").toString();
51 if (baseDir.endsWith(QLatin1Char('/'))) {
52 baseDir.chop(1);
53 }
54
55 if (QFile::exists(baseDir + QStringLiteral("/compile_commands.json"))) {
56 return baseDir + QStringLiteral("/compile_commands.json");
57 }
58 }
59
60 // Check using git
61 // For now it only checks for compile_commands in the .git/../ directory
62 QFileInfo fi(openedFile);
63 if (fi.exists()) {
64 QString base = fi.absolutePath();
65 auto basePathOptional = getDotGitPath(base);
66 if (basePathOptional.has_value()) {
67 auto basePath = basePathOptional.value();
68 if (basePath.endsWith(QLatin1Char('/'))) {
69 basePath.chop(1);
70 }
71 if (QFile::exists(basePath + QStringLiteral("/compile_commands.json"))) {
72 return basePath + QStringLiteral("/compile_commands.json");
73 }
74 }
75 }
76
77 qWarning() << "Compile DB not found for file: " << openedFile;
78
79 return QString();
80 }
81
argsForFile(const QString & compile_commandsPath,const QString & file)82 QString CompileDBReader::argsForFile(const QString &compile_commandsPath, const QString &file)
83 {
84 QFile f(compile_commandsPath);
85 if (!f.open(QFile::ReadOnly)) {
86 // TODO: Use Output view to report error
87 qWarning() << "Failed to load compile_commands: " << f.errorString();
88 return {};
89 }
90
91 QJsonParseError error;
92 QJsonDocument cmdCmds = QJsonDocument::fromJson(f.readAll(), &error);
93 if (error.error != QJsonParseError::NoError) {
94 qWarning() << "Failed to read compile_commands: " << error.errorString();
95 return {};
96 }
97
98 if (!cmdCmds.isArray()) {
99 qWarning() << "Invalid compile_commands, root element is not an array";
100 return {};
101 }
102
103 QJsonArray commandsArray = cmdCmds.array();
104
105 for (const auto &cmdJV : commandsArray) {
106 auto compileCommand = cmdJV.toObject();
107 auto cmpCmdFile = compileCommand.value(QStringLiteral("file")).toString();
108
109 QFileInfo fi(cmpCmdFile);
110 if (fi.isRelative()) {
111 QString dir = QDir::cleanPath(compileCommand.value(QStringLiteral("directory")).toString());
112 // QString file = QDir::cleanPath(dir + QStringLiteral("/") + cmpCmdFile);
113 } else {
114 if (fi.canonicalFilePath() == file) {
115 return compileCommand.value(QStringLiteral("command")).toString();
116 }
117 }
118 }
119
120 qWarning() << "compile_command for " << file << " not found";
121 return {};
122 }
123
addCurrentFilePathToCompileCommands(const QString & currentCompiler,QStringList & commands,const QString & fileBasePath)124 static void addCurrentFilePathToCompileCommands(const QString ¤tCompiler, QStringList &commands, const QString &fileBasePath)
125 {
126 // For these compilers we include the current file path to
127 // the compiler commands
128 QStringList compilers = {
129 QStringLiteral("c++"),
130 QStringLiteral("g++"),
131 QStringLiteral("gcc"),
132 QStringLiteral("clang"),
133 QStringLiteral("clang++"),
134 };
135
136 for (const auto &c : compilers) {
137 if (currentCompiler.contains(c)) {
138 commands << QStringLiteral("-I") + fileBasePath;
139 }
140 }
141 }
142
143 /*
144 * Remove args like "-include xyz.h"
145 * CompilerExplorer doesn't like them
146 */
removeIncludeArgument(QStringList & commands)147 static void removeIncludeArgument(QStringList &commands)
148 {
149 QStringList toRemove;
150 for (int i = 0; i < commands.size(); ++i) {
151 if (commands.at(i) == QStringLiteral("-include")) {
152 if (i + 1 < commands.size()) {
153 toRemove << commands.at(i);
154 toRemove << commands.at(i + 1);
155 ++i;
156 }
157 }
158 }
159
160 for (const auto &rem : qAsConst(toRemove)) {
161 commands.removeAll(rem);
162 }
163 }
164
filteredArgsForFile(const QString & compile_commandsPath,const QString & file)165 QString CompileDBReader::filteredArgsForFile(const QString &compile_commandsPath, const QString &file)
166 {
167 QString args = argsForFile(compile_commandsPath, file);
168
169 QFileInfo fi(file);
170 QString fileBasePath = fi.canonicalPath();
171
172 QStringList argsList = args.split(QLatin1Char(' '));
173 QString currentCompiler = argsList.takeFirst(); // First is the compiler, drop it
174 QStringList finalArgs;
175 finalArgs.reserve(argsList.size() - 2);
176
177 for (auto &&arg : argsList) {
178 if (arg == QStringLiteral("-o"))
179 continue;
180 if (arg.endsWith(QStringLiteral(".o")))
181 continue;
182 if (arg == QStringLiteral("-c"))
183 continue;
184 if (file == arg || file.contains(arg))
185 continue;
186
187 finalArgs << arg;
188 }
189
190 removeIncludeArgument(finalArgs);
191
192 addCurrentFilePathToCompileCommands(currentCompiler, finalArgs, fileBasePath);
193
194 return finalArgs.join(QLatin1Char(' '));
195 }
196