Skip to content

Commit 14d6d45

Browse files
authored
Refactor weak-node-api generator (#327)
* Refactor generation into templated strings and functions * Drive the configure, build and test from package scripts
1 parent 4afc24e commit 14d6d45

File tree

3 files changed

+97
-71
lines changed

3 files changed

+97
-71
lines changed

packages/weak-node-api/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Everything in weak-node-api is generated, except for the configurations
33
# Generated and built via `npm run bootstrap`
44
/build/
5+
/build-tests/
56
/*.xcframework
67
/*.android.node
78
/generated/weak_node_api.cpp

packages/weak-node-api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
"build-weak-node-api:apple": "node --run build-weak-node-api -- --apple",
3434
"build-weak-node-api:all": "node --run build-weak-node-api -- --android --apple",
3535
"test": "tsx --test --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=stdout src/node/**/*.test.ts src/node/*.test.ts",
36+
"test:configure": "cmake -S . -B build-tests -DBUILD_TESTS=ON",
37+
"test:build": "cmake --build build-tests",
38+
"test:run": "ctest --test-dir build-tests --output-on-failure",
3639
"bootstrap": "node --run prepare-weak-node-api && node --run build-weak-node-api",
3740
"prerelease": "node --run prepare-weak-node-api && node --run build-weak-node-api:all"
3841
},

packages/weak-node-api/scripts/generate-weak-node-api.ts

Lines changed: 93 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,92 +9,114 @@ import {
99

1010
export const OUTPUT_PATH = path.join(import.meta.dirname, "../generated");
1111

12+
type GenerateFileOptions = {
13+
functions: FunctionDecl[];
14+
fileName: string;
15+
generator: (functions: FunctionDecl[]) => string;
16+
};
17+
18+
async function generateFile({
19+
functions,
20+
fileName,
21+
generator,
22+
}: GenerateFileOptions) {
23+
const output = generator(functions);
24+
const outputPath = path.join(OUTPUT_PATH, fileName);
25+
await fs.promises.writeFile(outputPath, output, "utf-8");
26+
cp.spawnSync("clang-format", ["-i", outputPath], { stdio: "inherit" });
27+
}
28+
29+
async function run() {
30+
await fs.promises.mkdir(OUTPUT_PATH, { recursive: true });
31+
32+
const functions = getNodeApiFunctions();
33+
await generateFile({
34+
functions,
35+
fileName: "weak_node_api.hpp",
36+
generator: generateHeader,
37+
});
38+
await generateFile({
39+
functions,
40+
fileName: "weak_node_api.cpp",
41+
generator: generateSource,
42+
});
43+
}
44+
45+
export function generateFunctionDecl({
46+
returnType,
47+
name,
48+
argumentTypes,
49+
}: FunctionDecl) {
50+
return `${returnType} (*${name})(${argumentTypes.join(", ")});`;
51+
}
52+
1253
/**
1354
* Generates source code for a version script for the given Node API version.
1455
*/
1556
export function generateHeader(functions: FunctionDecl[]) {
16-
return [
17-
"// This file is generated by react-native-node-api",
18-
"#include <node_api.h>", // Node-API
19-
"#include <stdio.h>", // fprintf()
20-
"#include <stdlib.h>", // abort()
21-
"",
57+
return `
58+
// This file is generated by react-native-node-api
59+
#include <node_api.h> // Node-API
60+
#include <stdio.h> // fprintf()
61+
#include <stdlib.h> // abort()
62+
2263
// Ideally we would have just used NAPI_NO_RETURN, but
2364
// __declspec(noreturn) (when building with Microsoft Visual C++) cannot be used on members of a struct
2465
// TODO: If we targeted C++23 we could use std::unreachable()
25-
"#if defined(__GNUC__)",
26-
"#define WEAK_NODE_API_UNREACHABLE __builtin_unreachable();",
27-
"#else",
28-
"#define WEAK_NODE_API_UNREACHABLE __assume(0);",
29-
"#endif",
30-
"",
66+
67+
#if defined(__GNUC__)
68+
#define WEAK_NODE_API_UNREACHABLE __builtin_unreachable();
69+
#else
70+
#define WEAK_NODE_API_UNREACHABLE __assume(0);
71+
#endif
72+
3173
// Generate the struct of function pointers
32-
"struct WeakNodeApiHost {",
33-
...functions.map(({ returnType, name, argumentTypes }) =>
34-
[
35-
returnType,
36-
// Signature
37-
`(*${name})(${argumentTypes.join(", ")});`,
38-
].join(" "),
39-
),
40-
"};",
41-
"typedef void(*InjectHostFunction)(const WeakNodeApiHost&);",
42-
`extern "C" void inject_weak_node_api_host(const WeakNodeApiHost& host);`,
43-
].join("\n");
74+
struct WeakNodeApiHost {
75+
${functions.map(generateFunctionDecl).join("\n")}
76+
};
77+
typedef void(*InjectHostFunction)(const WeakNodeApiHost&);
78+
extern "C" void inject_weak_node_api_host(const WeakNodeApiHost& host);
79+
`;
80+
}
81+
82+
function generateFunctionImpl({
83+
returnType,
84+
name,
85+
argumentTypes,
86+
noReturn,
87+
}: FunctionDecl) {
88+
return `
89+
extern "C" ${returnType} ${name}(
90+
${argumentTypes.map((type, index) => `${type} arg${index}`).join(", ")}
91+
) {
92+
if (g_host.${name} == nullptr) {
93+
fprintf(stderr, "Node-API function '${name}' called before it was injected!\\n");
94+
abort();
95+
}
96+
${returnType === "void" ? "" : "return "} g_host.${name}(
97+
${argumentTypes.map((_, index) => `arg${index}`).join(", ")}
98+
);
99+
${noReturn ? "WEAK_NODE_API_UNREACHABLE" : ""}
100+
};
101+
`;
44102
}
45103

46104
/**
47105
* Generates source code for a version script for the given Node API version.
48106
*/
49107
export function generateSource(functions: FunctionDecl[]) {
50-
return [
51-
"// This file is generated by react-native-node-api",
52-
`#include "weak_node_api.hpp"`, // Generated header
53-
// Generate the struct of function pointers
54-
"WeakNodeApiHost g_host;",
55-
"void inject_weak_node_api_host(const WeakNodeApiHost& host) {",
56-
" g_host = host;",
57-
"};",
58-
``,
59-
// Generate function calling into the host
60-
...functions.flatMap(({ returnType, name, argumentTypes, noReturn }) => {
61-
return [
62-
'extern "C"',
63-
returnType,
64-
name,
65-
"(",
66-
argumentTypes.map((type, index) => `${type} arg${index}`).join(", "),
67-
") {",
68-
`if (g_host.${name} == nullptr) {`,
69-
` fprintf(stderr, "Node-API function '${name}' called before it was injected!\\n");`,
70-
" abort();",
71-
"}",
72-
returnType === "void" ? "" : "return ",
73-
`g_host.${name}`,
74-
"(",
75-
argumentTypes.map((_, index) => `arg${index}`).join(", "),
76-
");",
77-
noReturn ? "WEAK_NODE_API_UNREACHABLE" : "",
78-
"};",
79-
].join(" ");
80-
}),
81-
].join("\n");
82-
}
108+
return `
109+
// This file is generated by react-native-node-api
110+
#include "weak_node_api.hpp" // Generated header
83111
84-
async function run() {
85-
await fs.promises.mkdir(OUTPUT_PATH, { recursive: true });
86-
87-
const nodeApiFunctions = getNodeApiFunctions();
88-
89-
const header = generateHeader(nodeApiFunctions);
90-
const headerPath = path.join(OUTPUT_PATH, "weak_node_api.hpp");
91-
await fs.promises.writeFile(headerPath, header, "utf-8");
92-
cp.spawnSync("clang-format", ["-i", headerPath], { stdio: "inherit" });
93-
94-
const source = generateSource(nodeApiFunctions);
95-
const sourcePath = path.join(OUTPUT_PATH, "weak_node_api.cpp");
96-
await fs.promises.writeFile(sourcePath, source, "utf-8");
97-
cp.spawnSync("clang-format", ["-i", sourcePath], { stdio: "inherit" });
112+
WeakNodeApiHost g_host;
113+
void inject_weak_node_api_host(const WeakNodeApiHost& host) {
114+
g_host = host;
115+
};
116+
117+
// Generate function calling into the host
118+
${functions.map(generateFunctionImpl).join("\n")}
119+
`;
98120
}
99121

100122
run().catch((err) => {

0 commit comments

Comments
 (0)