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\‍(([a-zA-Z0-9_\/.\n ]+)\))_"};
67 static const QRegularExpression avnd_finalize{
68 R"_(avnd_score_plugin_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 QString path = QString{R"_(#include "%1/%2"
86)_"}
87 .arg(addon)
88 .arg(file);
89 data.unity_cpp.append(path.toStdString());
90
91 QFile f{filename};
92 f.open(QIODevice::ReadOnly);
93 data.files.push_back({filename, score::readFileAsQString(f)});
94 }
95 }
96 while(defs.hasNext())
97 {
98 auto m = defs.next();
99 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
100 for(const QString& define : res)
101 {
102 data.flags.push_back(QString{R"_(-D%1)_"}.arg(define).toStdString());
103 }
104 }
105
106 while(incs.hasNext())
107 {
108 auto m = incs.next();
109 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
110
111 for(const QString& path : res)
112 {
113 data.flags.push_back(QString{R"_(-I%1/%2)_"}.arg(addon).arg(path).toStdString());
114 }
115 }
116
117 // Process avendish wrappers
118 struct avnd_plugin
119 {
120 QString base_target;
121 QString main_class;
122 QString target;
123 QString avnd_namespace;
124 struct source_file
125 {
126 QString path;
127 QString filename;
128 QString file;
129 };
130 std::vector<source_file> files;
131 };
132
133 struct avnd_plugin_group
134 {
135 QString base_target;
136 QString version;
137 QString uuid;
138 std::vector<avnd_plugin> plugins;
139 };
140
141 std::vector<avnd_plugin_group> groups;
142 qDebug() << "Found avnd finalize? " << avnd_finalizes.hasNext();
143 while(avnd_finalizes.hasNext())
144 {
145 auto m = avnd_finalizes.next();
146 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
147 qDebug() << res;
148 avnd_plugin_group plug;
149 for(auto it = res.begin(); it != res.end(); ++it)
150 {
151 if(*it == "BASE_TARGET")
152 {
153 ++it;
154 if(it != res.end())
155 plug.base_target = *it;
156 }
157 else if(*it == "PLUGIN_VERSION")
158 {
159 ++it;
160 if(it != res.end())
161 plug.version = *it;
162 }
163 else if(*it == "PLUGIN_UUID")
164 {
165 ++it;
166 if(it != res.end())
167 plug.uuid = *it;
168 }
169 }
170
171 if(!plug.base_target.isEmpty())
172 groups.push_back(std::move(plug));
173 }
174
175 while(avnds.hasNext())
176 {
177 auto m = avnds.next();
178 auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
179 avnd_plugin plug;
180 for(auto it = res.begin(); it != res.end(); ++it)
181 {
182 if(*it == "BASE_TARGET")
183 {
184 ++it;
185 if(it != res.end())
186 plug.base_target = *it;
187 }
188 else if(*it == "SOURCES")
189 {
190 ++it;
191 while(it != res.end() && QFile::exists(QString{"%1/%2"}.arg(addon).arg(*it)))
192 {
193 avnd_plugin::source_file sf;
194 sf.filename = QString{"%1/%2"}.arg(addon).arg(*it);
195 sf.path = QString{"#include \"%1/%2\"\n"}.arg(addon).arg(*it);
196
197 QFile f{sf.filename};
198 if(f.open(QIODevice::ReadOnly))
199 {
200 sf.file = score::readFileAsQString(f);
201 data.files.push_back({sf.filename, sf.file});
202 data.unity_cpp.append(sf.path.toStdString());
203 plug.files.push_back(std::move(sf));
204 }
205 ++it;
206 }
207
208 --it;
209 }
210 else if(*it == "MAIN_CLASS")
211 {
212 ++it;
213 if(it != res.end())
214 plug.main_class = *it;
215 }
216 else if(*it == "TARGET")
217 {
218 ++it;
219 if(it != res.end())
220 plug.target = *it;
221 }
222 else if(*it == "NAMESPACE")
223 {
224 ++it;
225 if(it != res.end())
226 plug.avnd_namespace = *it;
227 }
228 }
229
230 if(plug.files.empty())
231 {
232 qDebug() << "no files found";
233 return;
234 }
235
236 for(auto& gp : groups)
237 {
238 if(plug.base_target == gp.base_target)
239 gp.plugins.push_back(std::move(plug));
240 }
241 }
242
243 auto sdk_location = locateSDKWithFallback();
244 auto qsdk = QString::fromStdString(sdk_location.path);
245 QString prototypes_folders;
246 if(sdk_location.sdk_kind == located_sdk::official && sdk_location.deploying)
247 {
248 prototypes_folders = qsdk + "/lib/cmake/score";
249 }
250 else
251 {
252 prototypes_folders
253 = QString::fromUtf8(SCORE_ROOT_SOURCE_DIR) + "/src/plugins/score-plugin-avnd/";
254 }
255
256 for(auto& gp : groups)
257 {
258 auto avnd_plugin_version = gp.version.toUtf8();
259 auto avnd_plugin_uuid = gp.uuid.toUtf8();
260 avnd_plugin_uuid.removeIf([](char c) { return c == '"'; });
261 auto avnd_base_target = gp.base_target.toUtf8();
262
263 QFile proto_file = QFile(prototypes_folders + "/prototype.cpp.in");
264 proto_file.open(QIODevice::ReadOnly);
265 auto proto = proto_file.readAll();
266
267 QFile cpp_proto_file = QFile(prototypes_folders + "/plugin_prototype.cpp.in");
268 cpp_proto_file.open(QIODevice::ReadOnly);
269 auto cpp_proto = cpp_proto_file.readAll();
270
271 QFile hpp_proto_file = QFile(prototypes_folders + "/plugin_prototype.hpp.in");
272 hpp_proto_file.open(QIODevice::ReadOnly);
273 auto hpp_proto = hpp_proto_file.readAll();
274
275 QByteArray cpp_proto_replaced = cpp_proto;
276 cpp_proto_replaced.replace(R"_(#include "@AVND_BASE_TARGET@.hpp")_", "");
277 QByteArray hpp_proto_replaced = hpp_proto;
278
279 QString avnd_additional_classes;
280 QString avnd_custom_factories;
281 QByteArray protos;
282 for(auto& plug : gp.plugins)
283 {
284 QByteArray proto_replaced = proto;
285 proto_replaced.replace(R"_(#cmakedefine AVND_REFLECTION_HELPERS)_", "");
286 auto avnd_main_file = plug.files[0].filename.toUtf8();
287 auto avnd_qualified = (plug.avnd_namespace + "::" + plug.main_class).toUtf8();
288 proto_replaced.replace("@AVND_MAIN_FILE@", avnd_main_file);
289 proto_replaced.replace("@AVND_QUALIFIED@", avnd_qualified);
290 proto_replaced.replace("@AVND_BASE_TARGET@", avnd_base_target);
291 if(!plug.avnd_namespace.isEmpty())
292 {
293 avnd_additional_classes.append(QString("namespace %1 { struct %2; }\n")
294 .arg(plug.avnd_namespace)
295 .arg(plug.main_class)
296 .toUtf8());
297 avnd_custom_factories.append(
298 QString("::oscr::custom_factories<%1>(fx, ctx, key); \n")
299 .arg(avnd_qualified)
300 .toUtf8());
301 }
302 else
303 {
304 avnd_additional_classes.append(
305 QString("struct %1; \n").arg(plug.main_class).toUtf8());
306 avnd_custom_factories.append(
307 QString("::oscr::custom_factories<%1>(fx, ctx, key); \n")
308 .arg(plug.main_class)
309 .toUtf8());
310 }
311 protos.append(proto_replaced);
312 }
313 for(QByteArray& f : {std::ref(cpp_proto_replaced), std::ref(hpp_proto_replaced)})
314 {
315 f.replace("@AVND_PLUGIN_VERSION@", avnd_plugin_version);
316 f.replace("@AVND_PLUGIN_UUID@", avnd_plugin_uuid);
317 f.replace("@AVND_BASE_TARGET@", avnd_base_target);
318 f.replace("@AVND_ADDITIONAL_CLASSES@", avnd_additional_classes.toUtf8());
319 f.replace("@AVND_CUSTOM_FACTORIES@", avnd_custom_factories.toUtf8());
320 }
321
322 data.unity_cpp.append(hpp_proto_replaced);
323 data.unity_cpp.append(protos);
324 data.unity_cpp.append(cpp_proto_replaced);
325
326 qDebug().noquote().nospace() << "===============================================\n"
327 << data.unity_cpp.data();
328 }
329}
330
331static AddonData loadAddon(const QString& addon)
332{
333 AddonData data;
334 if(QFile f(addon + QDir::separator() + "addon.json"); f.open(QIODevice::ReadOnly))
335 {
336 qDebug() << "Loading addon info from: " << f.fileName();
337 f.setTextModeEnabled(true);
338 data.addon_info = QJsonDocument::fromJson(f.readAll()).object();
339 }
340
341 if(QFile f(addon + QDir::separator() + "CMakeLists.txt"); f.open(QIODevice::ReadOnly))
342 {
343 qDebug() << "Loading CMake-based add-on: " << f.fileName();
344 // Needed because regex uses \n
345 f.setTextModeEnabled(true);
346 loadCMakeAddon(addon, data, f.readAll());
347 }
348 else
349 {
350 qDebug() << "Loading non-CMake-based add-on";
351 loadBasicAddon(addon, data);
352 }
353
354 return data;
355}
356
359static void generateCommandFiles(
360 const QString& output, const QString& addon_path,
361 const std::vector<std::pair<QString, QString>>& files)
362{
363 QRegularExpression decl(
364 "SCORE_COMMAND_DECL\\([A-Za-z_0-9,:<>\r\n\t "
365 "]*\\(\\)[A-Za-z_0-9,\"':<>\r\n\t ]*\\)");
366 QRegularExpression decl_t("SCORE_COMMAND_DECL_T\\([A-Za-z_0-9,:<>\r\n\t ]*\\)");
367
368 QString includes;
369 QString commands;
370 for(const auto& f : files)
371 {
372 {
373 auto res = decl.globalMatch(f.second);
374 while(res.hasNext())
375 {
376 auto match = res.next();
377 if(auto txt = match.capturedTexts(); !txt.empty())
378 {
379 if(auto split = txt[0].split(","); split.size() > 1)
380 {
381 auto filename = f.first;
382 filename.remove(addon_path + "/");
383 includes += "#include <" + filename + ">\n";
384 commands += split[1] + ",\n";
385 }
386 }
387 }
388 }
389 }
390 commands.remove(commands.length() - 2, 2);
391 commands.push_back("\n");
392 QDir{}.mkpath(output);
393 auto out_name = QFileInfo{addon_path}.fileName().replace("-", "_");
394 {
395 QFile cmd_f{output + "/" + out_name + "_commands_files.hpp"};
396 cmd_f.open(QIODevice::WriteOnly);
397 cmd_f.write(includes.toUtf8());
398 cmd_f.close();
399 }
400 {
401 QFile cmd_f{output + "/" + out_name + "_commands.hpp"};
402 cmd_f.open(QIODevice::WriteOnly);
403 cmd_f.write(commands.toUtf8());
404 cmd_f.close();
405 }
406}
407
409static void generateExportFile(
410 const QString& addon_files_path, const QString& addon_name,
411 const QByteArray& addon_export)
412{
413 QFile export_file = QFile{addon_files_path + "/" + addon_name + "_export.h"};
414 export_file.open(QIODevice::WriteOnly);
415 QByteArray export_data{
416 "#ifndef " + addon_export + "_EXPORT_H\n"
417 "#define " + addon_export + "_EXPORT_H\n"
418 "#define " + addon_export + "_EXPORT __attribute__((visibility(\"default\")))\n"
419 "#define " + addon_export + "_DEPRECATED [[deprecated]]\n"
420 "#endif\n"
421 };
422 export_file.write(export_data);
423 export_file.close();
424}
425
428static QString generateAddonFiles(
429 QString addon_name, const QString& addon,
430 const std::vector<std::pair<QString, QString>>& files)
431{
432 addon_name.replace("-", "_");
433 QByteArray addon_export = addon_name.toUpper().toUtf8();
434
435 QString addon_files_path = QDir::tempPath() + "/score-tmp-build/" + addon_name;
436 QDir{}.mkpath(addon_files_path);
437 generateExportFile(addon_files_path, addon_name, addon_export);
438 generateCommandFiles(addon_files_path, addon, files);
439 return addon_files_path;
440}
441
442}
Definition MetadataGenerator.hpp:20