MetadataGenerator.hpp
1 #pragma once
2 #include <score/tools/File.hpp>
3 
4 #include <QDir>
5 #include <QDirIterator>
6 #include <QFile>
7 #include <QJsonDocument>
8 #include <QJsonObject>
9 #include <QRegularExpression>
10 #include <QStringBuilder>
11 
12 #include <vector>
13 
14 namespace Jit
15 {
16 
17 struct AddonData
18 {
19  QJsonObject addon_info;
20  std::string unity_cpp;
21  // Warning ! if ever changing that to QByteArray, look for mapFile usage as right now the file is read with mmap
22  std::vector<std::pair<QString, QString>> files;
23  std::vector<std::string> flags;
24 };
25 
29 static void loadBasicAddon(const QString& addon, AddonData& data)
30 {
31  std::string cpp_files;
32  std::vector<std::pair<QString, QString>> files;
33  QDirIterator it{
34  addon,
35  {"*.cpp", "*.hpp"},
36  QDir::Filter::Files | QDir::Filter::NoDotAndDotDot,
37  QDirIterator::Subdirectories};
38 
39  while(it.hasNext())
40  {
41  if(QFile f(it.next()); f.open(QIODevice::ReadOnly))
42  {
43  QFileInfo fi{f};
44  if(fi.suffix() == "cpp")
45  {
46  data.unity_cpp.append("#include \"" + it.filePath().toStdString() + "\"\n");
47  }
48 
49  data.files.push_back({fi.filePath(), f.readAll()});
50  }
51  }
52 }
53 
54 static void loadCMakeAddon(const QString& addon, AddonData& data, QString cm)
55 {
56  static const QRegularExpression space{R"_(\s+)_"};
57  static const QRegularExpression sourceFiles{
58  R"_(add_library\‍(\s*[[:graph:]]+([a-zA-Z0-9_.\/\n ]*)\))_"};
59  static const QRegularExpression definitions{
60  R"_(target_compile_definitions\‍(\s*[[:graph:]]+\s*[[:graph:]]+([a-zA-Z0-9_"= ]+)\))_"};
61  static const QRegularExpression includes{
62  R"_(target_include_directories\‍(\s*[[:graph:]]+\s*[[:graph:]]+([a-zA-Z0-9_\/ ]+)\))_"};
63 
64  auto files = sourceFiles.globalMatch(cm);
65  auto defs = definitions.globalMatch(cm);
66  auto incs = includes.globalMatch(cm);
67 
68  while(files.hasNext())
69  {
70  auto m = files.next();
71  auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
72  for(const QString& file : res)
73  {
74  QString filename = QString{R"_(%1/%2)_"}.arg(addon).arg(file);
75 
76  QString path = QString{R"_(#include "%1/%2"
77 )_"}
78  .arg(addon)
79  .arg(file);
80  data.unity_cpp.append(path.toStdString());
81 
82  QFile f{filename};
83  f.open(QIODevice::ReadOnly);
84  data.files.push_back({filename, score::readFileAsQString(f)});
85  }
86  }
87  while(defs.hasNext())
88  {
89  auto m = defs.next();
90  auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
91  for(const QString& define : res)
92  {
93  data.flags.push_back(QString{R"_(-D%1)_"}.arg(define).toStdString());
94  }
95  }
96 
97  while(incs.hasNext())
98  {
99  auto m = incs.next();
100  auto res = m.captured(1).replace('\n', ' ').split(space, Qt::SkipEmptyParts);
101 
102  for(const QString& path : res)
103  {
104  data.flags.push_back(QString{R"_(-I%1/%2)_"}.arg(addon).arg(path).toStdString());
105  }
106  }
107 }
108 
109 static AddonData loadAddon(const QString& addon)
110 {
111  AddonData data;
112  if(QFile f(addon + QDir::separator() + "addon.json"); f.open(QIODevice::ReadOnly))
113  {
114  qDebug() << "Loading addon info from: " << f.fileName();
115  f.setTextModeEnabled(true);
116  data.addon_info = QJsonDocument::fromJson(f.readAll()).object();
117  }
118 
119  if(QFile f(addon + QDir::separator() + "CMakeLists.txt"); f.open(QIODevice::ReadOnly))
120  {
121  qDebug() << "Loading CMake-based add-on: " << f.fileName();
122  // Needed because regex uses \n
123  f.setTextModeEnabled(true);
124  loadCMakeAddon(addon, data, f.readAll());
125  }
126  else
127  {
128  qDebug() << "Loading non-CMake-based add-on";
129  loadBasicAddon(addon, data);
130  }
131 
132  return data;
133 }
134 
137 static void generateCommandFiles(
138  const QString& output, const QString& addon_path,
139  const std::vector<std::pair<QString, QString>>& files)
140 {
141  QRegularExpression decl(
142  "SCORE_COMMAND_DECL\\([A-Za-z_0-9,:<>\r\n\t "
143  "]*\\(\\)[A-Za-z_0-9,\"':<>\r\n\t ]*\\)");
144  QRegularExpression decl_t("SCORE_COMMAND_DECL_T\\([A-Za-z_0-9,:<>\r\n\t ]*\\)");
145 
146  QString includes;
147  QString commands;
148  for(const auto& f : files)
149  {
150  {
151  auto res = decl.globalMatch(f.second);
152  while(res.hasNext())
153  {
154  auto match = res.next();
155  if(auto txt = match.capturedTexts(); !txt.empty())
156  {
157  if(auto split = txt[0].split(","); split.size() > 1)
158  {
159  auto filename = f.first;
160  filename.remove(addon_path + "/");
161  includes += "#include <" + filename + ">\n";
162  commands += split[1] + ",\n";
163  }
164  }
165  }
166  }
167  }
168  commands.remove(commands.length() - 2, 2);
169  commands.push_back("\n");
170  QDir{}.mkpath(output);
171  auto out_name = QFileInfo{addon_path}.fileName().replace("-", "_");
172  {
173  QFile cmd_f{output + "/" + out_name + "_commands_files.hpp"};
174  cmd_f.open(QIODevice::WriteOnly);
175  cmd_f.write(includes.toUtf8());
176  cmd_f.close();
177  }
178  {
179  QFile cmd_f{output + "/" + out_name + "_commands.hpp"};
180  cmd_f.open(QIODevice::WriteOnly);
181  cmd_f.write(commands.toUtf8());
182  cmd_f.close();
183  }
184 }
185 
187 static void generateExportFile(
188  const QString& addon_files_path, const QString& addon_name,
189  const QByteArray& addon_export)
190 {
191  QFile export_file = QString{addon_files_path + "/" + addon_name + "_export.h"};
192  export_file.open(QIODevice::WriteOnly);
193  QByteArray export_data{
194  "#ifndef " + addon_export + "_EXPORT_H\n"
195  "#define " + addon_export + "_EXPORT_H\n"
196  "#define " + addon_export + "_EXPORT __attribute__((visibility(\"default\")))\n"
197  "#define " + addon_export + "_DEPRECATED [[deprecated]]\n"
198  "#endif\n"
199  };
200  export_file.write(export_data);
201  export_file.close();
202 }
203 
206 static QString generateAddonFiles(
207  QString addon_name, const QString& addon,
208  const std::vector<std::pair<QString, QString>>& files)
209 {
210  addon_name.replace("-", "_");
211  QByteArray addon_export = addon_name.toUpper().toUtf8();
212 
213  QString addon_files_path = QDir::tempPath() + "/score-tmp-build/" + addon_name;
214  QDir{}.mkpath(addon_files_path);
215  generateExportFile(addon_files_path, addon_name, addon_export);
216  generateCommandFiles(addon_files_path, addon, files);
217  return addon_files_path;
218 }
219 
220 }