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