-
Notifications
You must be signed in to change notification settings - Fork 8
/
generate-bindings.js
175 lines (149 loc) · 5.93 KB
/
generate-bindings.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// clang-format off
// Generates the JSBindings.h file, which contains all the JS bindings for the server and client
// Usage: node tools/generate-bindings.js [basePath] [scope=shared|client|server]
const fs = require("fs").promises;
const { constants } = require("fs");
const pathUtil = require("path");
const crypto = require("crypto");
// Base path should point to the main directory of the repo
if (process.argv.length < 3) {
showError("Missing 'basePath' argument");
showUsage();
process.exit(1);
}
const basePath = process.argv[2];
// Paths to search for JS bindings
const paths = [
{ path: "client/js/", scope: "client" },
{ path: "server/js/", scope: "server" },
{ path: "shared/js/", scope: "shared" }
];
// Full output file
const resultTemplate = `// !!! THIS FILE WAS AUTOMATICALLY GENERATED (ON {DATE}), DO NOT EDIT MANUALLY !!!
#include "Bindings.h"
namespace js {
std::unordered_map<std::string, Binding> Binding::__bindings =
{
{BINDINGS_LIST}
};
}
`;
// Template for each binding item in the bindings map
const bindingTemplate = `{ "{BINDING_NAME}", Binding{ "{BINDING_NAME}", Binding::Scope::{BINDING_SCOPE}, { {BINDING_SRC} } } }`;
// Result bindings output path
const outputPath = "build/BindingsMap.cpp";
const hashesOutputPath = "build/bindings-hashes.json";
(async () => {
const fileHashes = {};
const previousHashes = {};
let anyHashChanged = false;
const hashesOutputPathResolved = resolvePath(hashesOutputPath);
const outputPathResolved = resolvePath(outputPath);
await fs.mkdir(resolvePath("build"), { recursive: true });
if ((await doesFileExist(outputPathResolved)) && (await doesFileExist(hashesOutputPathResolved))) {
const hashesStr = await fs.readFile(hashesOutputPathResolved, "utf8");
Object.assign(previousHashes, JSON.parse(hashesStr));
showLog("Loaded previous bindings hashes");
}
const bindings = [];
for (const { path, scope: pathScope } of paths) {
const bindingsPath = resolvePath(path);
for await (const file of getBindingFiles(bindingsPath)) {
const name = pathUtil.relative(bindingsPath, file).replace(/\\/g, "/");
const bindingName = `${pathScope}/${name}`;
// Generate the binding data
const src = await fs.readFile(file, "utf8");
bindings.push({
name: bindingName,
src: getBindingCodeChars(src, name === "bootstrap.js"),
scope: pathScope.toUpperCase()
});
// Store hash
fileHashes[bindingName] = getHash(src);
if (fileHashes[bindingName] != previousHashes[bindingName]) anyHashChanged = true;
showLog(`Generated bindings for: ${pathUtil.relative(`${__dirname}/..`, file).replace(/\\/g, "/")}`);
}
}
if (!anyHashChanged) {
showLog("No bindings changed, skipping writing bindings result");
return;
}
// Generate data for the bindings map
let bindingsList = "";
for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i];
const bindingStr = bindingTemplate
.replace(/\{BINDING_NAME\}/g, binding.name)
.replace("{BINDING_SCOPE}", binding.scope)
.replace("{BINDING_SRC}", binding.src);
bindingsList += bindingStr;
if (i < bindings.length - 1) bindingsList += ",\n ";
}
// Store file hashes
await fs.writeFile(hashesOutputPathResolved, JSON.stringify(fileHashes));
showLog(`Wrote bindings hashes to file: ${hashesOutputPath}`);
const outputStr = resultTemplate.replace("{DATE}", `${getDate()} ${getTime()}`).replace("{BINDINGS_LIST}", bindingsList);
await fs.writeFile(outputPathResolved, outputStr);
showLog(`Wrote bindings result to file: ${outputPath}`);
})();
// Recursively gets all binding files in the directory, returns an async iterator
async function* getBindingFiles(dir) {
const items = await fs.readdir(dir, { withFileTypes: true });
for (const item of items) {
const path = pathUtil.resolve(dir, item.name);
if (item.isDirectory()) yield* getBindingFiles(path);
if (!path.endsWith(".js")) continue;
else yield path;
}
}
/**
* @param {string} src
*/
function getBindingCodeChars(src, shouldSkipAddingConsts) {
// These consts have to be added so the bindings work at runtime, as the globals are removed after loading the bindings
let code = src;
if (!shouldSkipAddingConsts && !code.includes("const alt =")) code = `const alt = __alt;\n${code}`;
if (!shouldSkipAddingConsts && !code.includes("const cppBindings =")) code = `const cppBindings = __cppBindings;\n${code}`;
const chars = code.split("").map((char) => char.charCodeAt(0));
return chars.toString();
}
function getHash(str) {
const hash = crypto.createHash("sha256");
hash.update(str);
return hash.digest("hex");
}
async function doesFileExist(path) {
try {
await fs.access(path, constants.F_OK);
return true;
} catch (e) {
return false;
}
}
function resolvePath(path) {
return pathUtil.resolve(__dirname, basePath, path);
}
function getDate() {
const date = new Date();
const day = date.getDate(),
month = date.getMonth() + 1,
year = date.getFullYear();
return `${day < 10 ? `0${day}` : day}/${month < 10 ? `0${month}` : month}/${year}`;
}
function getTime() {
const date = new Date();
const hours = date.getHours(),
minutes = date.getMinutes(),
seconds = date.getSeconds();
return `${hours < 10 ? `0${hours}` : hours}:${minutes < 10 ? `0${minutes}` : minutes}:${seconds < 10 ? `0${seconds}` : seconds}`;
}
function showLog(...args) {
console.log(`[${getTime()}]`, ...args);
}
function showError(...args) {
console.error(`[${getTime()}]`, ...args);
}
function showUsage() {
showLog("Usage: convert-bindings.js <basePath>");
showLog("<basePath>: Path to the base of the repository");
}