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 50097db6..7b99d472 100644 --- a/packages/weak-node-api/scripts/generate-weak-node-api.ts +++ b/packages/weak-node-api/scripts/generate-weak-node-api.ts @@ -1,3 +1,4 @@ +import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import cp from "node:child_process"; @@ -7,6 +8,8 @@ import { getNodeApiFunctions, } from "../src/node-api-functions.js"; +import * as weakNodeApiGenerator from "./generators/weak-node-api.js"; + export const OUTPUT_PATH = path.join(import.meta.dirname, "../generated"); type GenerateFileOptions = { @@ -20,10 +23,18 @@ async function generateFile({ fileName, generator, }: GenerateFileOptions) { - const output = generator(functions); + const generated = generator(functions); + const output = `// This file is generated - don't edit it directly\n\n${generated}`; const outputPath = path.join(OUTPUT_PATH, fileName); await fs.promises.writeFile(outputPath, output, "utf-8"); - cp.spawnSync("clang-format", ["-i", outputPath], { stdio: "inherit" }); + const { status, stderr = "No error output" } = cp.spawnSync( + "clang-format", + ["-i", outputPath], + { + encoding: "utf8", + }, + ); + assert.equal(status, 0, `Failed to format ${fileName}: ${stderr}`); } async function run() { @@ -33,92 +44,15 @@ async function run() { await generateFile({ functions, fileName: "weak_node_api.hpp", - generator: generateHeader, + generator: weakNodeApiGenerator.generateHeader, }); await generateFile({ functions, fileName: "weak_node_api.cpp", - generator: generateSource, + generator: weakNodeApiGenerator.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() - - // 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 - - // Generate the struct of function pointers - 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 - - 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) => { console.error(err); process.exitCode = 1; diff --git a/packages/weak-node-api/scripts/generators/shared.ts b/packages/weak-node-api/scripts/generators/shared.ts new file mode 100644 index 00000000..88104e58 --- /dev/null +++ b/packages/weak-node-api/scripts/generators/shared.ts @@ -0,0 +1,28 @@ +import type { FunctionDecl } from "../../src/node-api-functions.js"; + +type FunctionOptions = FunctionDecl & { + extern?: true; + static?: true; + namespace?: string; + body?: string; + argumentNames?: string[]; +}; + +export function generateFunction({ + extern, + static: staticMember, + returnType, + namespace, + name, + argumentTypes, + argumentNames = [], + noReturn, + body, +}: FunctionOptions) { + return ` + ${staticMember ? "static " : ""}${extern ? 'extern "C" ' : ""}${returnType} ${namespace ? namespace + "::" : ""}${name}( + ${argumentTypes.map((type, index) => `${type} ` + (argumentNames[index] ?? `arg${index}`)).join(", ")} + ) ${body ? `{ ${body} ${noReturn ? "WEAK_NODE_API_UNREACHABLE;" : ""}\n}` : ""} + ; + `; +} diff --git a/packages/weak-node-api/scripts/generators/weak-node-api.ts b/packages/weak-node-api/scripts/generators/weak-node-api.ts new file mode 100644 index 00000000..964b3d73 --- /dev/null +++ b/packages/weak-node-api/scripts/generators/weak-node-api.ts @@ -0,0 +1,74 @@ +import type { FunctionDecl } from "../../src/node-api-functions.js"; +import { generateFunction } from "./shared.js"; + +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 ` + #pragma once + + #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 + + // Generate the struct of function pointers + 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(fn: FunctionDecl) { + const { name, returnType, argumentTypes } = fn; + return generateFunction({ + ...fn, + extern: true, + body: ` + 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(", ")} + ); + `, + }); +} + +/** + * Generates source code for a version script for the given Node API version. + */ +export function generateSource(functions: FunctionDecl[]) { + return ` + #include "weak_node_api.hpp" + + 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")} + `; +}