Loading...
Searching...
No Matches
MetadataGenerator.hpp
1#pragma once
2#include <JitCpp/JitPlatform.hpp>
3
4#include <score/tools/File.hpp>
5
6#include <QDir>
7#include <QDirIterator>
8#include <QFile>
9#include <QJsonDocument>
10#include <QJsonObject>
11#include <QRegularExpression>
12#include <QStringBuilder>
13
14#include <vector>
15
16namespace Jit
17{
18
20{
21 QJsonObject addon_info;
22 std::string unity_cpp;
23 // Warning ! if ever changing that to QByteArray, look for mapFile usage as right now the file is read with mmap
24 std::vector<std::pair<QString, QString>> files;
25 std::vector<std::string> flags;
26};
27
31static void loadBasicAddon(const QString& addon, AddonData& data)
32{
33 std::string cpp_files;
34 std::vector<std::pair<QString, QString>> files;
35 QDirIterator it{
36 addon,
37 {"*.cpp", "*.hpp"},
38 QDir::Filter::Files | QDir::Filter::NoDotAndDotDot,
39 QDirIterator::Subdirectories};
40
41 while(it.hasNext())
42 {
43 if(QFile f(it.next()); f.open(QIODevice::ReadOnly))
44 {
45 QFileInfo fi{f};
46 if(fi.suffix() == "cpp")
47 {
48 data.unity_cpp.append("#include \"" + it.filePath().toStdString() + "\"\n");
49 }
50
51 data.files.push_back({fi.filePath(), f.readAll()});
52 }
53 }
54}
55
56static void loadCMakeAddon(const QString& addon, AddonData& data, QString cm)
57{
58 static const QRegularExpression space{R"_(\s+)_"};
59 static const QRegularExpression sourceFiles{
60 R"_(add_library\‍(\s*[[:graph:]]+([a-zA-Z0-9_.\/\n ]*)\))_"};
61 static const QRegularExpression definitions{
62 R"_(target_compile_definitions\‍(\s*[[:graph:]]+\s*[[:graph:]]+([a-zA-Z0-9_"= ]+)\))_"};
63 static const QRegularExpression includes{
64 R"_(target_include_directories\‍(\s*[[:graph:]]+\s*[[:graph:]]+([a-zA-Z0-9_\/ ]+)\))_"};
65 static const QRegularExpression avnd{
66 R"_((?:avnd_score_plugin_add|avnd_addon_object)\‍(([a-zA-Z0-9_\/.\n" -]+)\))_"};
67 static const QRegularExpression avnd_finalize{
68 R"_((?:avnd_score_plugin_finalize|avnd_addon_finalize)\‍(([a-zA-Z0-9_\/.\n" -]+)\))_"};
69
70 auto avnds = avnd.globalMatch(cm);
71 auto avnd_finalizes = avnd_finalize.globalMatch(cm);
72
73 // Process classic CMake libraries
74 auto files = sourceFiles.globalMatch(cm);
75 auto defs = definitions.globalMatch(cm);
76 auto incs = includes.globalMatch(cm);
77 while(files.hasNext())
78 {
79 auto m = files.next();
80 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
81 for(const QString& file : res)
82 {
83 QString filename = QString{R"_(%1/%2)_"}.arg(addon).arg(file);
84
85 // Skip add_library keywords (STATIC, INTERFACE, ...) and anything else
86 // that is not an actual file
87 if(!QFile::exists(filename))
88 continue;
89
90 QString path = QString{R"_(#include "%1/%2"
91)_"}
92 .arg(addon)
93 .arg(file);
94 data.unity_cpp.append(path.toStdString());
95
96 QFile f{filename};
97 f.open(QIODevice::ReadOnly);
98 data.files.push_back({filename, score::readFileAsQString(f)});
99 }
100 }
101 while(defs.hasNext())
102 {
103 auto m = defs.next();
104 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
105 for(const QString& define : res)
106 {
107 data.flags.push_back(QString{R"_(-D%1)_"}.arg(define).toStdString());
108 }
109 }
110
111 while(incs.hasNext())
112 {
113 auto m = incs.next();
114 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
115
116 for(const QString& path : res)
117 {
118 data.flags.push_back(QString{R"_(-I%1/%2)_"}.arg(addon).arg(path).toStdString());
119 }
120 }
121
122 // Process avendish wrappers
123 struct avnd_plugin
124 {
125 QString base_target;
126 QString main_class;
127 QString target;
128 QString avnd_namespace;
129 struct source_file
130 {
131 QString path;
132 QString filename;
133 QString file;
134 };
135 std::vector<source_file> files;
136 };
137
138 struct avnd_plugin_group
139 {
140 QString base_target;
141 QString version;
142 QString uuid;
143 std::vector<avnd_plugin> plugins;
144 };
145
146 std::vector<avnd_plugin_group> groups;
147 qDebug() << "Found avnd finalize? " << avnd_finalizes.hasNext();
148 while(avnd_finalizes.hasNext())
149 {
150 auto m = avnd_finalizes.next();
151 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
152 qDebug() << res;
153 avnd_plugin_group plug;
154 for(auto it = res.begin(); it != res.end(); ++it)
155 {
156 if(*it == "BASE_TARGET" || *it == "NAME")
157 {
158 ++it;
159 if(it != res.end())
160 plug.base_target = *it;
161 }
162 else if(*it == "PLUGIN_VERSION" || *it == "VERSION")
163 {
164 ++it;
165 if(it != res.end())
166 plug.version = *it;
167 }
168 else if(*it == "PLUGIN_UUID" || *it == "UUID")
169 {
170 ++it;
171 if(it != res.end())
172 plug.uuid = *it;
173 }
174 }
175
176 if(!plug.base_target.isEmpty())
177 groups.push_back(std::move(plug));
178 }
179
180 while(avnds.hasNext())
181 {
182 auto m = avnds.next();
183 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
184 avnd_plugin plug;
185 auto unquote = [](QString s) {
186 s.remove('"');
187 return s;
188 };
189 for(auto it = res.begin(); it != res.end(); ++it)
190 {
191 if(*it == "BASE_TARGET" || *it == "BASE")
192 {
193 ++it;
194 if(it != res.end())
195 plug.base_target = *it;
196 }
197 else if(*it == "SOURCES")
198 {
199 ++it;
200 while(it != res.end()
201 && QFile::exists(QString{"%1/%2"}.arg(addon).arg(unquote(*it))))
202 {
203 const QString file = unquote(*it);
204 avnd_plugin::source_file sf;
205 sf.filename = QString{"%1/%2"}.arg(addon).arg(file);
206 sf.path = QString{"#include \"%1/%2\"\n"}.arg(addon).arg(file);
207
208 QFile f{sf.filename};
209 if(f.open(QIODevice::ReadOnly))
210 {
211 sf.file = score::readFileAsQString(f);
212 data.files.push_back({sf.filename, sf.file});
213 data.unity_cpp.append(sf.path.toStdString());
214 plug.files.push_back(std::move(sf));
215 }
216 ++it;
217 }
218
219 --it;
220 }
221 else if(*it == "MAIN_CLASS" || *it == "CLASS")
222 {
223 ++it;
224 if(it != res.end())
225 plug.main_class = *it;
226 }
227 else if(*it == "TARGET" || *it == "C_NAME")
228 {
229 ++it;
230 if(it != res.end())
231 plug.target = *it;
232 }
233 else if(*it == "NAMESPACE")
234 {
235 ++it;
236 if(it != res.end())
237 plug.avnd_namespace = *it;
238 }
239 }
240
241 if(plug.files.empty())
242 {
243 qDebug() << "no files found";
244 return;
245 }
246
247 for(auto& gp : groups)
248 {
249 if(plug.base_target == gp.base_target)
250 gp.plugins.push_back(std::move(plug));
251 }
252 }
253
254 auto sdk_location = locateSDKWithFallback();
255 auto qsdk = QString::fromStdString(sdk_location.path);
256 QString prototypes_folders;
257 if(sdk_location.sdk_kind == located_sdk::official && sdk_location.deploying)
258 {
259 prototypes_folders = qsdk + "/lib/cmake/score";
260 }
261 else
262 {
263 prototypes_folders
264 = QString::fromUtf8(SCORE_ROOT_SOURCE_DIR) + "/src/plugins/score-plugin-avnd/";
265 }
266
267 for(auto& gp : groups)
268 {
269 auto avnd_plugin_version = gp.version.toUtf8();
270 auto avnd_plugin_uuid = gp.uuid.toUtf8();
271 avnd_plugin_uuid.removeIf([](char c) { return c == '"'; });
272 auto avnd_base_target = gp.base_target.toUtf8();
273
274 QFile proto_file = QFile(prototypes_folders + "/prototype.cpp.in");
275 proto_file.open(QIODevice::ReadOnly);
276 auto proto = proto_file.readAll();
277
278 QFile cpp_proto_file = QFile(prototypes_folders + "/plugin_prototype.cpp.in");
279 cpp_proto_file.open(QIODevice::ReadOnly);
280 auto cpp_proto = cpp_proto_file.readAll();
281
282 QFile hpp_proto_file = QFile(prototypes_folders + "/plugin_prototype.hpp.in");
283 hpp_proto_file.open(QIODevice::ReadOnly);
284 auto hpp_proto = hpp_proto_file.readAll();
285
286 QByteArray cpp_proto_replaced = cpp_proto;
287 cpp_proto_replaced.replace(R"_(#include "@AVND_BASE_TARGET@.hpp")_", "");
288 QByteArray hpp_proto_replaced = hpp_proto;
289
290 QString avnd_additional_classes;
291 QString avnd_custom_factories;
292 QByteArray protos;
293 for(auto& plug : gp.plugins)
294 {
295 QByteArray proto_replaced = proto;
296 proto_replaced.replace(R"_(#cmakedefine AVND_REFLECTION_HELPERS)_", "");
297 auto avnd_main_file = plug.files[0].filename.toUtf8();
298 auto avnd_qualified = (plug.avnd_namespace + "::" + plug.main_class).toUtf8();
299 proto_replaced.replace("@AVND_MAIN_FILE@", avnd_main_file);
300 proto_replaced.replace("@AVND_QUALIFIED@", avnd_qualified);
301 proto_replaced.replace("@AVND_BASE_TARGET@", avnd_base_target);
302 if(!plug.avnd_namespace.isEmpty())
303 {
304 avnd_additional_classes.append(QString("namespace %1 { struct %2; }\n")
305 .arg(plug.avnd_namespace)
306 .arg(plug.main_class)
307 .toUtf8());
308 avnd_custom_factories.append(
309 QString("::oscr::custom_factories<%1>(fx, ctx, key); \n")
310 .arg(avnd_qualified)
311 .toUtf8());
312 }
313 else
314 {
315 avnd_additional_classes.append(
316 QString("struct %1; \n").arg(plug.main_class).toUtf8());
317 avnd_custom_factories.append(
318 QString("::oscr::custom_factories<%1>(fx, ctx, key); \n")
319 .arg(plug.main_class)
320 .toUtf8());
321 }
322 protos.append(proto_replaced);
323 }
324 for(QByteArray& f : {std::ref(cpp_proto_replaced), std::ref(hpp_proto_replaced)})
325 {
326 f.replace("@AVND_PLUGIN_VERSION@", avnd_plugin_version);
327 f.replace("@AVND_PLUGIN_UUID@", avnd_plugin_uuid);
328 f.replace("@AVND_BASE_TARGET@", avnd_base_target);
329 f.replace("@AVND_ADDITIONAL_CLASSES@", avnd_additional_classes.toUtf8());
330 f.replace("@AVND_CUSTOM_FACTORIES@", avnd_custom_factories.toUtf8());
331 }
332
333 data.unity_cpp.append(hpp_proto_replaced);
334 data.unity_cpp.append(protos);
335 data.unity_cpp.append(cpp_proto_replaced);
336
337 qDebug().noquote().nospace() << "===============================================\n"
338 << data.unity_cpp.data();
339 }
340}
341
342static AddonData loadAddon(const QString& addon)
343{
344 AddonData data;
345 if(QFile f(addon + QDir::separator() + "addon.json"); f.open(QIODevice::ReadOnly))
346 {
347 qDebug() << "Loading addon info from: " << f.fileName();
348 f.setTextModeEnabled(true);
349 data.addon_info = QJsonDocument::fromJson(f.readAll()).object();
350 }
351
352 if(QFile f(addon + QDir::separator() + "CMakeLists.txt"); f.open(QIODevice::ReadOnly))
353 {
354 qDebug() << "Loading CMake-based add-on: " << f.fileName();
355 // Needed because regex uses \n
356 f.setTextModeEnabled(true);
357 loadCMakeAddon(addon, data, f.readAll());
358 }
359 else
360 {
361 qDebug() << "Loading non-CMake-based add-on";
362 loadBasicAddon(addon, data);
363 }
364
365 return data;
366}
367
370static void generateCommandFiles(
371 const QString& output, const QString& addon_path,
372 const std::vector<std::pair<QString, QString>>& files)
373{
374 QRegularExpression decl(
375 "SCORE_COMMAND_DECL\\([A-Za-z_0-9,:<>\r\n\t "
376 "]*\\(\\)[A-Za-z_0-9,\"':<>\r\n\t ]*\\)");
377 QRegularExpression decl_t("SCORE_COMMAND_DECL_T\\([A-Za-z_0-9,:<>\r\n\t ]*\\)");
378
379 QString includes;
380 QString commands;
381 for(const auto& f : files)
382 {
383 {
384 auto res = decl.globalMatch(f.second);
385 while(res.hasNext())
386 {
387 auto match = res.next();
388 if(auto txt = match.capturedTexts(); !txt.empty())
389 {
390 if(auto split = txt[0].split(","); split.size() > 1)
391 {
392 auto filename = f.first;
393 filename.remove(addon_path + "/");
394 includes += "#include <" + filename + ">\n";
395 commands += split[1] + ",\n";
396 }
397 }
398 }
399 }
400 }
401 commands.remove(commands.length() - 2, 2);
402 commands.push_back("\n");
403 QDir{}.mkpath(output);
404 auto out_name = QFileInfo{addon_path}.fileName().replace("-", "_");
405 {
406 QFile cmd_f{output + "/" + out_name + "_commands_files.hpp"};
407 cmd_f.open(QIODevice::WriteOnly);
408 cmd_f.write(includes.toUtf8());
409 cmd_f.close();
410 }
411 {
412 QFile cmd_f{output + "/" + out_name + "_commands.hpp"};
413 cmd_f.open(QIODevice::WriteOnly);
414 cmd_f.write(commands.toUtf8());
415 cmd_f.close();
416 }
417}
418
420static void generateExportFile(
421 const QString& addon_files_path, const QString& addon_name,
422 const QByteArray& addon_export)
423{
424 QFile export_file = QFile{addon_files_path + "/" + addon_name + "_export.h"};
425 export_file.open(QIODevice::WriteOnly);
426 QByteArray export_data{
427 "#ifndef " + addon_export + "_EXPORT_H\n"
428 "#define " + addon_export + "_EXPORT_H\n"
429 "#define " + addon_export + "_EXPORT __attribute__((visibility(\"default\")))\n"
430 "#define " + addon_export + "_DEPRECATED [[deprecated]]\n"
431 "#endif\n"
432 };
433 export_file.write(export_data);
434 export_file.close();
435}
436
439static QString generateAddonFiles(
440 QString addon_name, const QString& addon,
441 const std::vector<std::pair<QString, QString>>& files)
442{
443 addon_name.replace("-", "_");
444 QByteArray addon_export = addon_name.toUpper().toUtf8();
445
446 QString addon_files_path = QDir::tempPath() + "/score-tmp-build/" + addon_name;
447 QDir{}.mkpath(addon_files_path);
448 generateExportFile(addon_files_path, addon_name, addon_export);
449 generateCommandFiles(addon_files_path, addon, files);
450 return addon_files_path;
451}
452
453}
Definition MetadataGenerator.hpp:20