Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/weak-node-api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions packages/weak-node-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
164 changes: 93 additions & 71 deletions packages/weak-node-api/scripts/generate-weak-node-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.h>", // Node-API
"#include <stdio.h>", // fprintf()
"#include <stdlib.h>", // abort()
"",
return `
// This file is generated by react-native-node-api
#include <node_api.h> // Node-API
#include <stdio.h> // fprintf()
#include <stdlib.h> // 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) => {
Expand Down
Loading