Skip to content

Commit 8398f76

Browse files
authored
Add visionOS and tvOS triplets to Ferric (#294)
* Refactored "ensureInstalledTargets" into "ensureAvailableTargets" * Assert nightly toolchain * Add visionos target * Add job to test building Ferric Apple triplets * Add tvos targets * Rename "build-weak-node-api:all-triplets" to "build-weak-node-api:all" and add ":apple" and ":android" scripts * Refactor scripts into a common "prepare-weak-node-api" script * Add missing targets
1 parent 9411a8c commit 8398f76

File tree

5 files changed

+157
-32
lines changed

5 files changed

+157
-32
lines changed

.github/workflows/check.yml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ jobs:
152152
sudo udevadm control --reload-rules
153153
sudo udevadm trigger --name-match=kvm
154154
- name: Build weak-node-api for all architectures
155-
run: npm run build-weak-node-api -- --android
155+
run: npm run build-weak-node-api:android
156156
working-directory: packages/host
157157
- name: Build ferric-example for all architectures
158158
run: npm run build -- --android
@@ -188,3 +188,52 @@ jobs:
188188
with:
189189
name: emulator-logcat
190190
path: apps/test-app/emulator-logcat.txt
191+
test-ferric-apple-triplets:
192+
if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'Ferric 🦀')
193+
name: Test ferric Apple triplets
194+
runs-on: macos-latest
195+
steps:
196+
- uses: actions/checkout@v4
197+
- uses: actions/setup-node@v4
198+
with:
199+
node-version: lts/jod
200+
- name: Set up JDK 17
201+
uses: actions/setup-java@v3
202+
with:
203+
java-version: "17"
204+
distribution: "temurin"
205+
- run: rustup target add x86_64-apple-darwin x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim
206+
- run: rustup toolchain install nightly --component rust-src
207+
- run: npm ci
208+
- run: npm run build
209+
# Build weak-node-api for all Apple architectures
210+
- run: |
211+
npm run prepare-weak-node-api
212+
npm run build-weak-node-api:apple
213+
working-directory: packages/host
214+
# Build Ferric example for all Apple architectures
215+
- run: npx ferric --apple
216+
working-directory: packages/ferric-example
217+
- name: Inspect the structure of the prebuilt binary
218+
run: lipo -info ferric_example.apple.node/*/libferric_example.framework/libferric_example > lipo-info.txt
219+
working-directory: packages/ferric-example
220+
- name: Upload lipo info
221+
uses: actions/upload-artifact@v4
222+
with:
223+
name: lipo-info
224+
path: packages/ferric-example/lipo-info.txt
225+
- name: Verify Apple triplet builds
226+
run: |
227+
# Create expected fixture content
228+
cat > expected-lipo-info.txt << 'EOF'
229+
Architectures in the fat file: ferric_example.apple.node/ios-arm64_x86_64-simulator/libferric_example.framework/libferric_example are: x86_64 arm64
230+
Architectures in the fat file: ferric_example.apple.node/macos-arm64_x86_64/libferric_example.framework/libferric_example are: x86_64 arm64
231+
Architectures in the fat file: ferric_example.apple.node/tvos-arm64_x86_64-simulator/libferric_example.framework/libferric_example are: x86_64 arm64
232+
Non-fat file: ferric_example.apple.node/ios-arm64/libferric_example.framework/libferric_example is architecture: arm64
233+
Non-fat file: ferric_example.apple.node/tvos-arm64/libferric_example.framework/libferric_example is architecture: arm64
234+
Non-fat file: ferric_example.apple.node/xros-arm64-simulator/libferric_example.framework/libferric_example is architecture: arm64
235+
Non-fat file: ferric_example.apple.node/xros-arm64/libferric_example.framework/libferric_example is architecture: arm64
236+
EOF
237+
# Compare with expected fixture (will fail if files differ)
238+
diff expected-lipo-info.txt lipo-info.txt
239+
working-directory: packages/ferric-example

packages/ferric/src/build.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
AndroidTargetName,
3030
APPLE_TARGETS,
3131
AppleTargetName,
32-
ensureInstalledTargets,
32+
ensureAvailableTargets,
3333
filterTargetsByPlatform,
3434
} from "./targets.js";
3535
import { generateTypeScriptDeclarations } from "./napi-rs.js";
@@ -164,7 +164,7 @@ export const buildCommand = new Command("build")
164164
);
165165
}
166166
ensureCargo();
167-
ensureInstalledTargets(targets);
167+
ensureAvailableTargets(targets);
168168

169169
const appleTargets = filterTargetsByPlatform(targets, "apple");
170170
const androidTargets = filterTargetsByPlatform(targets, "android");
@@ -340,6 +340,7 @@ async function combineLibraries(
340340
const result = [];
341341
const darwinLibraries = [];
342342
const iosSimulatorLibraries = [];
343+
const tvosSimulatorLibraries = [];
343344
for (const [target, libraryPath] of libraries) {
344345
if (target.endsWith("-darwin")) {
345346
darwinLibraries.push(libraryPath);
@@ -348,6 +349,11 @@ async function combineLibraries(
348349
target === "x86_64-apple-ios" // Simulator despite name missing -sim suffix
349350
) {
350351
iosSimulatorLibraries.push(libraryPath);
352+
} else if (
353+
target === "aarch64-apple-tvos-sim" ||
354+
target === "x86_64-apple-tvos" // Simulator despite name missing -sim suffix
355+
) {
356+
tvosSimulatorLibraries.push(libraryPath);
351357
} else {
352358
result.push(libraryPath);
353359
}
@@ -356,6 +362,7 @@ async function combineLibraries(
356362
const combinedLibraryPaths = await createUniversalAppleLibraries([
357363
darwinLibraries,
358364
iosSimulatorLibraries,
365+
tvosSimulatorLibraries,
359366
]);
360367

361368
return [...result, ...combinedLibraryPaths];

packages/ferric/src/cargo.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
AppleTargetName,
1717
isAndroidTarget,
1818
isAppleTarget,
19+
isThirdTierTarget,
1920
} from "./targets.js";
2021

2122
const APPLE_XCFRAMEWORK_CHILDS_PER_TARGET: Record<AppleTargetName, string> = {
@@ -26,12 +27,17 @@ const APPLE_XCFRAMEWORK_CHILDS_PER_TARGET: Record<AppleTargetName, string> = {
2627
"aarch64-apple-ios-sim": "ios-arm64_x86_64-simulator", // Universal
2728
"x86_64-apple-ios": "ios-arm64_x86_64-simulator", // Universal
2829

30+
"aarch64-apple-visionos": "xros-arm64",
31+
"aarch64-apple-visionos-sim": "xros-arm64_x86_64-simulator", // Universal
32+
// The x86_64 target for vision simulator isn't supported
33+
// see https://doc.rust-lang.org/rustc/platform-support.html
34+
35+
"aarch64-apple-tvos": "tvos-arm64",
36+
"aarch64-apple-tvos-sim": "tvos-arm64_x86_64-simulator",
37+
"x86_64-apple-tvos": "tvos-arm64_x86_64-simulator",
38+
2939
// "aarch64-apple-ios-macabi": "", // Catalyst
3040
// "x86_64-apple-ios-macabi": "ios-x86_64-simulator",
31-
// "aarch64-apple-tvos": "tvos-arm64",
32-
// "aarch64-apple-tvos-sim": "tvos-arm64-simulator",
33-
// "aarch64-apple-visionos": "xros-arm64",
34-
// "aarch64-apple-visionos-sim": "xros-arm64-simulator",
3541
};
3642

3743
const ANDROID_ARCH_PR_TARGET: Record<AndroidTargetName, string> = {
@@ -84,6 +90,14 @@ export async function build(options: BuildOptions) {
8490
if (configuration.toLowerCase() === "release") {
8591
args.push("--release");
8692
}
93+
if (isThirdTierTarget(target)) {
94+
// Use the nightly toolchain for third tier targets
95+
args.splice(0, 0, "+nightly");
96+
// Passing the nightly "build-std" to
97+
// > Enable Cargo to compile the standard library itself as part of a crate graph compilation
98+
// See https://doc.rust-lang.org/rustc/platform-support/apple-visionos.html#building-the-target
99+
args.push("-Z", "build-std=std,panic_abort");
100+
}
87101
await spawn("cargo", args, {
88102
outputMode: "buffered",
89103
env: {

packages/ferric/src/targets.ts

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { chalk, UsageError } from "@react-native-node-api/cli-utils";
1+
import cp from "node:child_process";
2+
3+
import { assertFixable } from "@react-native-node-api/cli-utils";
24
import { getInstalledTargets } from "./rustup.js";
35

46
export const ANDROID_TARGETS = [
@@ -24,25 +26,23 @@ export const APPLE_TARGETS = [
2426
// "aarch64-apple-ios-macabi", // Catalyst
2527
// "x86_64-apple-ios-macabi", // Catalyst
2628

27-
// TODO: Re-enabled these when we know how to install them 🙈
28-
/*
29-
"aarch64-apple-tvos",
30-
"aarch64-apple-tvos-sim",
3129
"aarch64-apple-visionos",
3230
"aarch64-apple-visionos-sim",
33-
*/
31+
32+
"aarch64-apple-tvos",
33+
// "arm64e-apple-tvos",
34+
"aarch64-apple-tvos-sim",
35+
"x86_64-apple-tvos", // Simulator (despite the missing -sim suffix)
3436

3537
// "aarch64-apple-watchos",
3638
// "aarch64-apple-watchos-sim",
3739
// "arm64_32-apple-watchos",
3840
// "arm64e-apple-darwin",
3941
// "arm64e-apple-ios",
40-
// "arm64e-apple-tvos",
4142
// "armv7k-apple-watchos",
4243
// "armv7s-apple-ios",
4344
// "i386-apple-ios",
4445
// "i686-apple-darwin",
45-
// "x86_64-apple-tvos",
4646
// "x86_64-apple-watchos-sim",
4747
// "x86_64h-apple-darwin",
4848
] as const;
@@ -51,24 +51,72 @@ export type AppleTargetName = (typeof APPLE_TARGETS)[number];
5151
export const ALL_TARGETS = [...ANDROID_TARGETS, ...APPLE_TARGETS] as const;
5252
export type TargetName = (typeof ALL_TARGETS)[number];
5353

54+
const THIRD_TIER_TARGETS: Set<TargetName> = new Set([
55+
"aarch64-apple-visionos",
56+
"aarch64-apple-visionos-sim",
57+
58+
"aarch64-apple-tvos",
59+
"aarch64-apple-tvos-sim",
60+
"x86_64-apple-tvos",
61+
]);
62+
63+
export function assertNightlyToolchain() {
64+
const toolchainLines = cp
65+
.execFileSync("rustup", ["toolchain", "list"], {
66+
encoding: "utf-8",
67+
})
68+
.split("\n");
69+
70+
const nightlyLines = toolchainLines.filter((line) =>
71+
line.startsWith("nightly-"),
72+
);
73+
assertFixable(
74+
nightlyLines.length > 0,
75+
"You need to use a nightly Rust toolchain",
76+
{
77+
command: "rustup toolchain install nightly --component rust-src",
78+
},
79+
);
80+
81+
const componentLines = cp
82+
.execFileSync("rustup", ["component", "list", "--toolchain", "nightly"], {
83+
encoding: "utf-8",
84+
})
85+
.split("\n");
86+
assertFixable(
87+
componentLines.some((line) => line === "rust-src (installed)"),
88+
"You need to install the rust-src component for the nightly Rust toolchain",
89+
{
90+
command: "rustup toolchain install nightly --component rust-src",
91+
},
92+
);
93+
}
94+
5495
/**
55-
* Ensure the targets are installed into the Rust toolchain
96+
* Ensure the targets are either installed into the Rust toolchain or available via nightly Rust toolchain.
5697
* We do this up-front because the error message and fix is very unclear from the failure when missing.
5798
*/
58-
export function ensureInstalledTargets(expectedTargets: Set<TargetName>) {
99+
export function ensureAvailableTargets(expectedTargets: Set<TargetName>) {
59100
const installedTargets = getInstalledTargets();
60-
const missingTargets = new Set([
61-
...[...expectedTargets].filter((target) => !installedTargets.has(target)),
62-
]);
63-
if (missingTargets.size > 0) {
64-
// TODO: Ask the user if they want to run this
65-
throw new UsageError(
66-
`You're missing ${
67-
missingTargets.size
68-
} targets - to fix this, run:\n\n${chalk.italic(
69-
`rustup target add ${[...missingTargets].join(" ")}`,
70-
)}`,
71-
);
101+
102+
const missingInstallableTargets = expectedTargets
103+
.difference(installedTargets)
104+
.difference(THIRD_TIER_TARGETS);
105+
106+
assertFixable(
107+
missingInstallableTargets.size === 0,
108+
`You need to add these targets to your toolchain: ${[
109+
...missingInstallableTargets,
110+
].join(", ")}`,
111+
{
112+
command: `rustup target add ${[...missingInstallableTargets].join(" ")}`,
113+
},
114+
);
115+
116+
const expectedThirdTierTargets =
117+
expectedTargets.intersection(THIRD_TIER_TARGETS);
118+
if (expectedThirdTierTargets.size > 0) {
119+
assertNightlyToolchain();
72120
}
73121
}
74122

@@ -82,6 +130,10 @@ export function isAppleTarget(target: TargetName): target is AppleTargetName {
82130
return APPLE_TARGETS.includes(target as (typeof APPLE_TARGETS)[number]);
83131
}
84132

133+
export function isThirdTierTarget(target: TargetName): boolean {
134+
return THIRD_TIER_TARGETS.has(target);
135+
}
136+
85137
export function filterTargetsByPlatform(
86138
targets: Set<TargetName>,
87139
platform: "android",

packages/host/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@
4646
"copy-node-api-headers": "tsx scripts/copy-node-api-headers.ts",
4747
"generate-weak-node-api": "tsx scripts/generate-weak-node-api.ts",
4848
"generate-weak-node-api-injector": "tsx scripts/generate-weak-node-api-injector.ts",
49+
"prepare-weak-node-api": "node --run copy-node-api-headers && node --run generate-weak-node-api-injector && node --run generate-weak-node-api",
4950
"build-weak-node-api": "cmake-rn --no-auto-link --no-weak-node-api-linkage --xcframework-extension --source ./weak-node-api --out ./weak-node-api",
50-
"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",
51+
"build-weak-node-api:android": "node --run build-weak-node-api -- --android",
52+
"build-weak-node-api:apple": "node --run build-weak-node-api -- --apple",
53+
"build-weak-node-api:all": "node --run build-weak-node-api -- --android --apple",
5154
"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",
5255
"test:gradle": "ENABLE_GRADLE_TESTS=true node --run test",
53-
"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",
54-
"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"
56+
"bootstrap": "node --run prepare-weak-node-api && node --run build-weak-node-api",
57+
"prerelease": "node --run prepare-weak-node-api && node --run build-weak-node-api:all"
5558
},
5659
"keywords": [
5760
"react-native",

0 commit comments

Comments
 (0)