1 /*
2 SPDX-FileCopyrightText: 2019 Daniel Mensinger <daniel@mensinger-ka.de>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "mesonintrospectjob.h"
8
9 #include "mesonconfig.h"
10 #include "mesonmanager.h"
11 #include "mesonoptions.h"
12 #include <debug.h>
13
14 #include <KLocalizedString>
15 #include <KProcess>
16
17 #include <QDir>
18 #include <QJsonArray>
19 #include <QJsonDocument>
20 #include <QJsonObject>
21 #include <QtConcurrentRun>
22
23 using namespace Meson;
24 using namespace KDevelop;
25
MesonIntrospectJob(KDevelop::IProject * project,QVector<MesonIntrospectJob::Type> types,MesonIntrospectJob::Mode mode,QObject * parent)26 MesonIntrospectJob::MesonIntrospectJob(KDevelop::IProject* project, QVector<MesonIntrospectJob::Type> types,
27 MesonIntrospectJob::Mode mode, QObject* parent)
28 : KJob(parent)
29 , m_types(types)
30 , m_mode(mode)
31 , m_project(project)
32 {
33 Q_ASSERT(project);
34
35 if (mode == MESON_FILE) {
36 // Since we are parsing the meson file in this mode, no build directory
37 // is required and we have to fake a build directory
38 m_buildDir.buildDir = project->path();
39 auto* bsm = project->buildSystemManager();
40 auto* manager = dynamic_cast<MesonManager*>(bsm);
41 if (manager) {
42 m_buildDir.mesonExecutable = manager->findMeson();
43 }
44 } else {
45 m_buildDir = Meson::currentBuildDir(project);
46 }
47
48 m_projectPath = project->path();
49 connect(&m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MesonIntrospectJob::finished);
50 }
51
MesonIntrospectJob(KDevelop::IProject * project,KDevelop::Path meson,QVector<MesonIntrospectJob::Type> types,QObject * parent)52 MesonIntrospectJob::MesonIntrospectJob(KDevelop::IProject* project, KDevelop::Path meson,
53 QVector<MesonIntrospectJob::Type> types, QObject* parent)
54 : KJob(parent)
55 , m_types(types)
56 , m_mode(MESON_FILE)
57 , m_project(project)
58 {
59 Q_ASSERT(project);
60
61 // Since we are parsing the meson file in this mode, no build directory
62 // is required and we have to fake a build directory
63 m_projectPath = project->path();
64 m_buildDir.buildDir = m_projectPath;
65 m_buildDir.mesonExecutable = meson;
66 connect(&m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MesonIntrospectJob::finished);
67 }
68
MesonIntrospectJob(KDevelop::IProject * project,Meson::BuildDir buildDir,QVector<MesonIntrospectJob::Type> types,MesonIntrospectJob::Mode mode,QObject * parent)69 MesonIntrospectJob::MesonIntrospectJob(KDevelop::IProject* project, Meson::BuildDir buildDir,
70 QVector<MesonIntrospectJob::Type> types, MesonIntrospectJob::Mode mode,
71 QObject* parent)
72 : KJob(parent)
73 , m_types(types)
74 , m_mode(mode)
75 , m_buildDir(buildDir)
76 , m_project(project)
77 {
78 Q_ASSERT(project);
79 m_projectPath = project->path();
80 connect(&m_futureWatcher, &QFutureWatcher<QString>::finished, this, &MesonIntrospectJob::finished);
81 }
82
getTypeString(MesonIntrospectJob::Type type) const83 QString MesonIntrospectJob::getTypeString(MesonIntrospectJob::Type type) const
84 {
85 switch (type) {
86 case BENCHMARKS:
87 return QStringLiteral("benchmarks");
88 case BUILDOPTIONS:
89 return QStringLiteral("buildoptions");
90 case BUILDSYSTEM_FILES:
91 return QStringLiteral("buildsystem_files");
92 case DEPENDENCIES:
93 return QStringLiteral("dependencies");
94 case INSTALLED:
95 return QStringLiteral("installed");
96 case PROJECTINFO:
97 return QStringLiteral("projectinfo");
98 case TARGETS:
99 return QStringLiteral("targets");
100 case TESTS:
101 return QStringLiteral("tests");
102 }
103
104 return QStringLiteral("error");
105 }
106
importJSONFile(const BuildDir & buildDir,MesonIntrospectJob::Type type,QJsonObject * out)107 QString MesonIntrospectJob::importJSONFile(const BuildDir& buildDir, MesonIntrospectJob::Type type, QJsonObject* out)
108 {
109 QString typeStr = getTypeString(type);
110 QString fileName = QStringLiteral("intro-") + typeStr + QStringLiteral(".json");
111 QString infoDir = buildDir.buildDir.toLocalFile() + QStringLiteral("/") + QStringLiteral("meson-info");
112 QFile introFile(infoDir + QStringLiteral("/") + fileName);
113
114 if (!introFile.exists()) {
115 return i18n("Introspection file '%1' does not exist", QFileInfo(introFile).canonicalFilePath());
116 }
117
118 if (!introFile.open(QFile::ReadOnly | QFile::Text)) {
119 return i18n("Failed to open introspection file '%1'", QFileInfo(introFile).canonicalFilePath());
120 }
121
122 QJsonParseError error;
123 QJsonDocument doc = QJsonDocument::fromJson(introFile.readAll(), &error);
124 if (error.error) {
125 return i18n("In %1:%2: %3", QFileInfo(introFile).canonicalFilePath(), error.offset, error.errorString());
126 }
127
128 if (doc.isArray()) {
129 (*out)[typeStr] = doc.array();
130 } else if (doc.isObject()) {
131 (*out)[typeStr] = doc.object();
132 } else {
133 return i18n("The introspection file '%1' contains neither an array nor an object",
134 QFileInfo(introFile).canonicalFilePath());
135 }
136
137 return QString();
138 }
139
importMesonAPI(const BuildDir & buildDir,MesonIntrospectJob::Type type,QJsonObject * out)140 QString MesonIntrospectJob::importMesonAPI(const BuildDir& buildDir, MesonIntrospectJob::Type type, QJsonObject* out)
141 {
142 QString typeStr = getTypeString(type);
143 QString option = QStringLiteral("--") + typeStr;
144 option.replace(QLatin1Char('_'), QLatin1Char('-'));
145
146 KProcess proc(this);
147 proc.setWorkingDirectory(m_projectPath.toLocalFile());
148 proc.setOutputChannelMode(KProcess::SeparateChannels);
149 proc.setProgram(buildDir.mesonExecutable.toLocalFile());
150 proc << QStringLiteral("introspect") << option << QStringLiteral("meson.build");
151
152 int ret = proc.execute();
153 if (ret != 0) {
154 return i18n("%1 returned %2", proc.program().join(QLatin1Char(' ')), ret);
155 }
156
157 QJsonParseError error;
158 QJsonDocument doc = QJsonDocument::fromJson(proc.readAll(), &error);
159 if (error.error) {
160 return i18n("JSON parser error: %1", error.errorString());
161 }
162
163 if (doc.isArray()) {
164 (*out)[typeStr] = doc.array();
165 } else if (doc.isObject()) {
166 (*out)[typeStr] = doc.object();
167 } else {
168 return i18n("The introspection output of '%1' contains neither an array nor an object",
169 proc.program().join(QLatin1Char(' ')));
170 }
171
172 return QString();
173 }
174
import(BuildDir buildDir)175 QString MesonIntrospectJob::import(BuildDir buildDir)
176 {
177 QJsonObject rawData;
178
179 // First load the complete JSON data
180 for (auto i : m_types) {
181 QString err;
182 switch (m_mode) {
183 case BUILD_DIR:
184 err = importJSONFile(buildDir, i, &rawData);
185 break;
186 case MESON_FILE:
187 err = importMesonAPI(buildDir, i, &rawData);
188 break;
189 }
190
191 if (!err.isEmpty()) {
192 qCWarning(KDEV_Meson) << "MINTRO: " << err;
193 setError(true);
194 setErrorText(err);
195 return err;
196 }
197 }
198
199 auto buildOptionsJSON = rawData[QStringLiteral("buildoptions")];
200 if (buildOptionsJSON.isArray()) {
201 m_res_options = std::make_shared<MesonOptions>(buildOptionsJSON.toArray());
202 if (m_res_options) {
203 qCDebug(KDEV_Meson) << "MINTRO: Imported " << m_res_options->options().size() << " buildoptions";
204 } else {
205 qCWarning(KDEV_Meson) << "MINTRO: Failed to parse buildoptions";
206 }
207 }
208
209 auto projectInfoJSON = rawData[QStringLiteral("projectinfo")];
210 if (projectInfoJSON.isObject()) {
211 m_res_projectInfo = std::make_shared<MesonProjectInfo>(projectInfoJSON.toObject());
212 if (!m_res_projectInfo) {
213 qCWarning(KDEV_Meson) << "MINTRO: Failed to parse projectinfo";
214 }
215 }
216
217 auto targetsJSON = rawData[QStringLiteral("targets")];
218 if (targetsJSON.isArray()) {
219 m_res_targets = std::make_shared<MesonTargets>(targetsJSON.toArray());
220 }
221
222 auto testsJSON = rawData[QStringLiteral("tests")];
223 if (testsJSON.isArray()) {
224 m_res_tests = std::make_shared<MesonTestSuites>(testsJSON.toArray(), m_project);
225 if (m_res_tests) {
226 qCDebug(KDEV_Meson) << "MINTRO: Imported " << m_res_tests->testSuites().size() << " test suites";
227 } else {
228 qCWarning(KDEV_Meson) << "MINTRO: Failed to parse tests";
229 }
230 }
231
232 return QString();
233 }
234
start()235 void MesonIntrospectJob::start()
236 {
237 qCDebug(KDEV_Meson) << "MINTRO: Starting meson introspection job";
238 if (!m_buildDir.isValid()) {
239 qCWarning(KDEV_Meson) << "The current build directory is invalid";
240 setError(true);
241 setErrorText(i18n("The current build directory is invalid"));
242 emitResult();
243 return;
244 }
245
246 auto future = QtConcurrent::run(this, &MesonIntrospectJob::import, m_buildDir);
247 m_futureWatcher.setFuture(future);
248 }
249
finished()250 void MesonIntrospectJob::finished()
251 {
252 qCDebug(KDEV_Meson) << "MINTRO: Meson introspection job finished";
253 emitResult();
254 }
255
doKill()256 bool MesonIntrospectJob::doKill()
257 {
258 if (m_futureWatcher.isRunning()) {
259 m_futureWatcher.cancel();
260 }
261 return true;
262 }
263
buildOptions()264 MesonOptsPtr MesonIntrospectJob::buildOptions()
265 {
266 return m_res_options;
267 }
268
projectInfo()269 MesonProjectInfoPtr MesonIntrospectJob::projectInfo()
270 {
271 return m_res_projectInfo;
272 }
273
targets()274 MesonTargetsPtr MesonIntrospectJob::targets()
275 {
276 return m_res_targets;
277 }
278
tests()279 MesonTestSuitesPtr MesonIntrospectJob::tests()
280 {
281 return m_res_tests;
282 }
283