1 /*
2 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "cmakeimportjsonjob.h"
8
9 #include "cmakeutils.h"
10 #include "cmakeprojectdata.h"
11 #include "cmakemodelitems.h"
12 #include "debug.h"
13
14 #include <makefileresolver/makefileresolver.h>
15 #include <language/duchain/duchain.h>
16 #include <language/duchain/duchainlock.h>
17 #include <interfaces/iproject.h>
18 #include <interfaces/icore.h>
19 #include <interfaces/iruntime.h>
20 #include <interfaces/iruntimecontroller.h>
21
22 #include <KShell>
23 #include <QJsonDocument>
24 #include <QJsonObject>
25 #include <QJsonArray>
26 #include <QtConcurrentRun>
27 #include <QFutureWatcher>
28 #include <QRegularExpression>
29
30 using namespace KDevelop;
31
32 namespace {
33
importCommands(const Path & commandsFile)34 CMakeFilesCompilationData importCommands(const Path& commandsFile)
35 {
36 // NOTE: to get compile_commands.json, you need -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
37 QFile f(commandsFile.toLocalFile());
38 bool r = f.open(QFile::ReadOnly|QFile::Text);
39 if(!r) {
40 qCWarning(CMAKE) << "Couldn't open commands file" << commandsFile;
41 return {};
42 }
43
44 qCDebug(CMAKE) << "Found commands file" << commandsFile;
45
46 CMakeFilesCompilationData data;
47 QJsonParseError error;
48 const QJsonDocument document = QJsonDocument::fromJson(f.readAll(), &error);
49 if (error.error) {
50 qCWarning(CMAKE) << "Failed to parse JSON in commands file:" << error.errorString() << commandsFile;
51 data.isValid = false;
52 return data;
53 } else if (!document.isArray()) {
54 qCWarning(CMAKE) << "JSON document in commands file is not an array: " << commandsFile;
55 data.isValid = false;
56 return data;
57 }
58
59 MakeFileResolver resolver;
60 const QString KEY_COMMAND = QStringLiteral("command");
61 const QString KEY_DIRECTORY = QStringLiteral("directory");
62 const QString KEY_FILE = QStringLiteral("file");
63 auto rt = ICore::self()->runtimeController()->currentRuntime();
64 const auto values = document.array();
65 for (const QJsonValue& value : values) {
66 if (!value.isObject()) {
67 qCWarning(CMAKE) << "JSON command file entry is not an object:" << value;
68 continue;
69 }
70 const QJsonObject entry = value.toObject();
71 if (!entry.contains(KEY_FILE) || !entry.contains(KEY_COMMAND) || !entry.contains(KEY_DIRECTORY)) {
72 qCWarning(CMAKE) << "JSON command file entry does not contain required keys:" << entry;
73 continue;
74 }
75
76 PathResolutionResult result = resolver.processOutput(entry[KEY_COMMAND].toString(), entry[KEY_DIRECTORY].toString());
77
78 auto convert = [rt](const Path &path) { return rt->pathInHost(path); };
79
80 CMakeFile ret;
81 ret.includes = kTransform<Path::List>(result.paths, convert);
82 ret.frameworkDirectories = kTransform<Path::List>(result.frameworkDirectories, convert);
83 ret.defines = result.defines;
84 const Path path(rt->pathInHost(Path(entry[KEY_FILE].toString())));
85 qCDebug(CMAKE) << "entering..." << path << entry[KEY_FILE];
86 data.files[path] = ret;
87 }
88
89 data.isValid = true;
90 data.rebuildFileForFolderMapping();
91 return data;
92 }
93
import(const Path & commandsFile,const Path & targetsFilePath,const QString & sourceDir,const KDevelop::Path & buildPath)94 ImportData import(const Path& commandsFile, const Path &targetsFilePath, const QString &sourceDir, const KDevelop::Path &buildPath)
95 {
96 QHash<KDevelop::Path, QVector<CMakeTarget>> cmakeTargets;
97
98 //we don't have target type information in json, so we just announce all of them as exes
99 const auto targets = CMake::enumerateTargets(targetsFilePath, sourceDir, buildPath);
100 for(auto it = targets.constBegin(), itEnd = targets.constEnd(); it!=itEnd; ++it) {
101 cmakeTargets[it.key()] = kTransform<QVector<CMakeTarget>>(*it, [](const QString &targetName) {
102 return CMakeTarget{
103 CMakeTarget::Executable,
104 targetName,
105 KDevelop::Path::List(),
106 KDevelop::Path::List(),
107 QString()
108 };
109 });
110 }
111
112 return ImportData {
113 importCommands(commandsFile),
114 cmakeTargets,
115 CMake::importTestSuites(buildPath)
116 };
117 }
118
119 }
120
CMakeImportJsonJob(IProject * project,QObject * parent)121 CMakeImportJsonJob::CMakeImportJsonJob(IProject* project, QObject* parent)
122 : KJob(parent)
123 , m_project(project)
124 , m_data({})
125 {
126 connect(&m_futureWatcher, &QFutureWatcher<ImportData>::finished, this, &CMakeImportJsonJob::importCompileCommandsJsonFinished);
127 }
128
~CMakeImportJsonJob()129 CMakeImportJsonJob::~CMakeImportJsonJob()
130 {}
131
start()132 void CMakeImportJsonJob::start()
133 {
134 auto commandsFile = CMake::commandsFile(project());
135 if (!QFileInfo::exists(commandsFile.toLocalFile())) {
136 qCWarning(CMAKE) << "Could not import CMake project" << project()->path() << "('compile_commands.json' missing)";
137 emitResult();
138 return;
139 }
140
141 const Path currentBuildDir = CMake::currentBuildDir(m_project);
142 Q_ASSERT (!currentBuildDir.isEmpty());
143
144 const Path targetsFilePath = CMake::targetDirectoriesFile(m_project);
145 const QString sourceDir = m_project->path().toLocalFile();
146 auto rt = ICore::self()->runtimeController()->currentRuntime();
147
148 auto future = QtConcurrent::run(import, commandsFile, targetsFilePath, sourceDir, rt->pathInRuntime(currentBuildDir));
149 m_futureWatcher.setFuture(future);
150 }
151
importCompileCommandsJsonFinished()152 void CMakeImportJsonJob::importCompileCommandsJsonFinished()
153 {
154 Q_ASSERT(m_project->thread() == QThread::currentThread());
155 Q_ASSERT(m_futureWatcher.isFinished());
156
157 auto future = m_futureWatcher.future();
158 auto data = future.result();
159 if (!data.compilationData.isValid) {
160 qCWarning(CMAKE) << "Could not import CMake project ('compile_commands.json' invalid)";
161 emitResult();
162 return;
163 }
164
165 m_data = {data.compilationData, data.targets, data.testSuites, {}};
166 qCDebug(CMAKE) << "Done importing, found" << data.compilationData.files.count() << "entries for" << project()->path();
167
168 emitResult();
169 }
170
project() const171 IProject* CMakeImportJsonJob::project() const
172 {
173 return m_project;
174 }
175
projectData() const176 CMakeProjectData CMakeImportJsonJob::projectData() const
177 {
178 Q_ASSERT(!m_futureWatcher.isRunning());
179 return m_data;
180 }
181
182 #include "moc_cmakeimportjsonjob.cpp"
183