From f09e91a7798e0b56a5f3365a08a8cd96af7e4d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 8 Nov 2025 20:49:39 +0100 Subject: [PATCH 1/2] Refactor generation into templated strings and functions --- .../scripts/generate-weak-node-api.ts | 164 ++++++++++-------- 1 file changed, 93 insertions(+), 71 deletions(-) diff --git a/packages/weak-node-api/scripts/generate-weak-node-api.ts b/packages/weak-node-api/scripts/generate-weak-node-api.ts index b47aed27..50097db6 100644 --- a/packages/weak-node-api/scripts/generate-weak-node-api.ts +++ b/packages/weak-node-api/scripts/generate-weak-node-api.ts @@ -9,92 +9,114 @@ import { export const OUTPUT_PATH = path.join(import.meta.dirname, "../generated"); +type GenerateFileOptions = { + functions: FunctionDecl[]; + fileName: string; + generator: (functions: FunctionDecl[]) => string; +}; + +async function generateFile({ + functions, + fileName, + generator, +}: GenerateFileOptions) { + const output = generator(functions); + const outputPath = path.join(OUTPUT_PATH, fileName); + await fs.promises.writeFile(outputPath, output, "utf-8"); + cp.spawnSync("clang-format", ["-i", outputPath], { stdio: "inherit" }); +} + +async function run() { + await fs.promises.mkdir(OUTPUT_PATH, { recursive: true }); + + const functions = getNodeApiFunctions(); + await generateFile({ + functions, + fileName: "weak_node_api.hpp", + generator: generateHeader, + }); + await generateFile({ + functions, + fileName: "weak_node_api.cpp", + generator: generateSource, + }); +} + +export function generateFunctionDecl({ + returnType, + name, + argumentTypes, +}: FunctionDecl) { + return `${returnType} (*${name})(${argumentTypes.join(", ")});`; +} + /** * Generates source code for a version script for the given Node API version. */ export function generateHeader(functions: FunctionDecl[]) { - return [ - "// This file is generated by react-native-node-api", - "#include ", // Node-API - "#include ", // fprintf() - "#include ", // abort() - "", + return ` + // This file is generated by react-native-node-api + #include // Node-API + #include // fprintf() + #include // abort() + // Ideally we would have just used NAPI_NO_RETURN, but // __declspec(noreturn) (when building with Microsoft Visual C++) cannot be used on members of a struct // TODO: If we targeted C++23 we could use std::unreachable() - "#if defined(__GNUC__)", - "#define WEAK_NODE_API_UNREACHABLE __builtin_unreachable();", - "#else", - "#define WEAK_NODE_API_UNREACHABLE __assume(0);", - "#endif", - "", + + #if defined(__GNUC__) + #define WEAK_NODE_API_UNREACHABLE __builtin_unreachable(); + #else + #define WEAK_NODE_API_UNREACHABLE __assume(0); + #endif + // Generate the struct of function pointers - "struct WeakNodeApiHost {", - ...functions.map(({ returnType, name, argumentTypes }) => - [ - returnType, - // Signature - `(*${name})(${argumentTypes.join(", ")});`, - ].join(" "), - ), - "};", - "typedef void(*InjectHostFunction)(const WeakNodeApiHost&);", - `extern "C" void inject_weak_node_api_host(const WeakNodeApiHost& host);`, - ].join("\n"); + struct WeakNodeApiHost { + ${functions.map(generateFunctionDecl).join("\n")} + }; + typedef void(*InjectHostFunction)(const WeakNodeApiHost&); + extern "C" void inject_weak_node_api_host(const WeakNodeApiHost& host); + `; +} + +function generateFunctionImpl({ + returnType, + name, + argumentTypes, + noReturn, +}: FunctionDecl) { + return ` + extern "C" ${returnType} ${name}( + ${argumentTypes.map((type, index) => `${type} arg${index}`).join(", ")} + ) { + if (g_host.${name} == nullptr) { + fprintf(stderr, "Node-API function '${name}' called before it was injected!\\n"); + abort(); + } + ${returnType === "void" ? "" : "return "} g_host.${name}( + ${argumentTypes.map((_, index) => `arg${index}`).join(", ")} + ); + ${noReturn ? "WEAK_NODE_API_UNREACHABLE" : ""} + }; + `; } /** * Generates source code for a version script for the given Node API version. */ export function generateSource(functions: FunctionDecl[]) { - return [ - "// This file is generated by react-native-node-api", - `#include "weak_node_api.hpp"`, // Generated header - // Generate the struct of function pointers - "WeakNodeApiHost g_host;", - "void inject_weak_node_api_host(const WeakNodeApiHost& host) {", - " g_host = host;", - "};", - ``, - // Generate function calling into the host - ...functions.flatMap(({ returnType, name, argumentTypes, noReturn }) => { - return [ - 'extern "C"', - returnType, - name, - "(", - argumentTypes.map((type, index) => `${type} arg${index}`).join(", "), - ") {", - `if (g_host.${name} == nullptr) {`, - ` fprintf(stderr, "Node-API function '${name}' called before it was injected!\\n");`, - " abort();", - "}", - returnType === "void" ? "" : "return ", - `g_host.${name}`, - "(", - argumentTypes.map((_, index) => `arg${index}`).join(", "), - ");", - noReturn ? "WEAK_NODE_API_UNREACHABLE" : "", - "};", - ].join(" "); - }), - ].join("\n"); -} + return ` + // This file is generated by react-native-node-api + #include "weak_node_api.hpp" // Generated header -async function run() { - await fs.promises.mkdir(OUTPUT_PATH, { recursive: true }); - - const nodeApiFunctions = getNodeApiFunctions(); - - const header = generateHeader(nodeApiFunctions); - const headerPath = path.join(OUTPUT_PATH, "weak_node_api.hpp"); - await fs.promises.writeFile(headerPath, header, "utf-8"); - cp.spawnSync("clang-format", ["-i", headerPath], { stdio: "inherit" }); - - const source = generateSource(nodeApiFunctions); - const sourcePath = path.join(OUTPUT_PATH, "weak_node_api.cpp"); - await fs.promises.writeFile(sourcePath, source, "utf-8"); - cp.spawnSync("clang-format", ["-i", sourcePath], { stdio: "inherit" }); + WeakNodeApiHost g_host; + void inject_weak_node_api_host(const WeakNodeApiHost& host) { + g_host = host; + }; + + // Generate function calling into the host + ${functions.map(generateFunctionImpl).join("\n")} + `; } run().catch((err) => { From 2f7c744be9622bda4aa9de7aa208671a59391d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Sat, 8 Nov 2025 22:22:45 +0100 Subject: [PATCH 2/2] Drive the configure, build and test from package scripts --- packages/weak-node-api/.gitignore | 1 + packages/weak-node-api/package.json | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/weak-node-api/.gitignore b/packages/weak-node-api/.gitignore index 5cc9e939..652a5f16 100644 --- a/packages/weak-node-api/.gitignore +++ b/packages/weak-node-api/.gitignore @@ -2,6 +2,7 @@ # Everything in weak-node-api is generated, except for the configurations # Generated and built via `npm run bootstrap` /build/ +/build-tests/ /*.xcframework /*.android.node /generated/weak_node_api.cpp diff --git a/packages/weak-node-api/package.json b/packages/weak-node-api/package.json index 90de2de8..84a510f3 100644 --- a/packages/weak-node-api/package.json +++ b/packages/weak-node-api/package.json @@ -33,6 +33,9 @@ "build-weak-node-api:apple": "node --run build-weak-node-api -- --apple", "build-weak-node-api:all": "node --run build-weak-node-api -- --android --apple", "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", + "test:configure": "cmake -S . -B build-tests -DBUILD_TESTS=ON", + "test:build": "cmake --build build-tests", + "test:run": "ctest --test-dir build-tests --output-on-failure", "bootstrap": "node --run prepare-weak-node-api && node --run build-weak-node-api", "prerelease": "node --run prepare-weak-node-api && node --run build-weak-node-api:all" },