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
5 changes: 5 additions & 0 deletions .changeset/many-candies-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-node-api": patch
---

Fix auto-linking from Gradle builds on Windows
5 changes: 5 additions & 0 deletions .changeset/silver-suits-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-node-api": minor
---

Assert that REACT_NATIVE_OVERRIDE_HERMES_DIR is set when Android / Gradle projects depend on the host package
2 changes: 2 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Check
env:
# Version here should match the one in React Native template and packages/cmake-rn/src/cli.ts
NDK_VERSION: 27.1.12297006
# Enabling the Gradle test on CI (disabled by default because it downloads a lot)
ENABLE_GRADLE_TESTS: true

on:
push:
Expand Down
25 changes: 22 additions & 3 deletions packages/host/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import java.nio.file.Paths
import groovy.json.JsonSlurper
import org.gradle.internal.os.OperatingSystem

buildscript {
ext.getExtOrDefault = {name ->
Expand Down Expand Up @@ -134,12 +135,30 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

task checkHermesOverride {
doFirst {
if (!System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR")) {
throw new GradleException([
"React Native Node-API needs a custom version of Hermes with Node-API enabled.",
"Run the following in your terminal, to clone Hermes and instruct React Native to use it:",
"",
"export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent --force`",
"",
"And follow this guide to build React Native from source:",
"https://reactnative.dev/contributing/how-to-build-from-source#update-your-project-to-build-from-source"
].join('\n'))
}
}
}

def commandLinePrefix = OperatingSystem.current().isWindows() ? ["cmd", "/c", "node"] : []
def cliPath = file("../bin/react-native-node-api.mjs")

// Custom task to fetch jniLibs paths via CLI
task linkNodeApiModules {
doLast {
exec {
// TODO: Support --strip-path-suffix
commandLine 'npx', 'react-native-node-api', 'link', '--android', rootProject.rootDir.absolutePath
commandLine commandLinePrefix + [cliPath, 'link', '--android', rootProject.rootDir.absolutePath]
standardOutput = System.out
errorOutput = System.err
// Enable color output
Expand All @@ -150,5 +169,5 @@ task linkNodeApiModules {
}
}

preBuild.dependsOn linkNodeApiModules
preBuild.dependsOn checkHermesOverride, linkNodeApiModules

1 change: 1 addition & 0 deletions packages/host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"build-weak-node-api": "cmake-rn --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api --out ./weak-node-api",
"build-weak-node-api:all-triplets": "cmake-rn --android --apple --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api --out ./weak-node-api",
"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:gradle": "ENABLE_GRADLE_TESTS=true node --run test",
"bootstrap": "node --run copy-node-api-headers && node --run generate-weak-node-api-injector && node --run generate-weak-node-api && node --run build-weak-node-api",
"prerelease": "node --run copy-node-api-headers && node --run generate-weak-node-api-injector && node --run generate-weak-node-api && node --run build-weak-node-api:all-triplets"
},
Expand Down
57 changes: 57 additions & 0 deletions packages/host/src/node/cli/bin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import cp from "node:child_process";
import path from "node:path";

const PACKAGE_ROOT = path.join(__dirname, "../../..");
const BIN_PATH = path.join(PACKAGE_ROOT, "bin/react-native-node-api.mjs");

describe("bin", () => {
describe("help command", () => {
it("should succeed with a mention of usage", () => {
const { status, stdout, stderr } = cp.spawnSync(
process.execPath,
[BIN_PATH, "help"],
{
cwd: PACKAGE_ROOT,
encoding: "utf8",
},
);

assert.equal(
status,
0,
`Expected success (got ${status}): ${stdout} ${stderr}`,
);
assert.match(
stdout,
/Usage: react-native-node-api/,
`Failed to find expected output (stdout: ${stdout} stderr: ${stderr})`,
);
});
});

describe("link command", () => {
it("should succeed with a mention of Node-API modules", () => {
const { status, stdout, stderr } = cp.spawnSync(
process.execPath,
[BIN_PATH, "link", "--android", "--apple"],
{
cwd: PACKAGE_ROOT,
encoding: "utf8",
},
);

assert.equal(
status,
0,
`Expected success (got ${status}): ${stdout} ${stderr}`,
);
assert.match(
stdout + stderr,
/Auto-linking Node-API modules/,
`Failed to find expected output (stdout: ${stdout} stderr: ${stderr})`,
);
});
});
});
66 changes: 66 additions & 0 deletions packages/host/src/node/gradle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import cp from "node:child_process";
import path from "node:path";

const PACKAGE_ROOT = path.join(__dirname, "../..");
const MONOREPO_ROOT = path.join(PACKAGE_ROOT, "../..");
const TEST_APP_ANDROID_PATH = path.join(MONOREPO_ROOT, "apps/test-app/android");

describe(
"Gradle tasks",
// Skipping these tests by default, as they download a lot and takes a long time
{ skip: process.env.ENABLE_GRADLE_TESTS !== "true" },
() => {
describe("checkHermesOverride task", () => {
it("should fail if REACT_NATIVE_OVERRIDE_HERMES_DIR is not set", () => {
const { status, stdout, stderr } = cp.spawnSync(
"sh",
["gradlew", "react-native-node-api:checkHermesOverride"],
{
cwd: TEST_APP_ANDROID_PATH,
env: {
...process.env,
REACT_NATIVE_OVERRIDE_HERMES_DIR: undefined,
},
encoding: "utf-8",
},
);

assert.notEqual(status, 0, `Expected failure: ${stdout} ${stderr}`);
assert.match(
stderr,
/React Native Node-API needs a custom version of Hermes with Node-API enabled/,
);
assert.match(
stderr,
/Run the following in your terminal, to clone Hermes and instruct React Native to use it/,
);
assert.match(
stderr,
/export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent --force`/,
);
assert.match(
stderr,
/And follow this guide to build React Native from source/,
);
});
});

describe("linkNodeApiModules task", () => {
it("should call the CLI to autolink", () => {
const { status, stdout, stderr } = cp.spawnSync(
"sh",
["gradlew", "react-native-node-api:linkNodeApiModules"],
{
cwd: TEST_APP_ANDROID_PATH,
encoding: "utf-8",
},
);

assert.equal(status, 0, `Expected success: ${stdout} ${stderr}`);
assert.match(stdout, /Auto-linking Node-API modules/);
});
});
},
);
Loading