Skip to content

Commit 0c32e8d

Browse files
committed
refactor(apple): evaluate Hermes from JS
1 parent e9fa6f7 commit 0c32e8d

File tree

7 files changed

+118
-86
lines changed

7 files changed

+118
-86
lines changed

ios/app.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import {
1313
import { cp_r, mkdir_p, rm_r } from "../scripts/utils/filesystem.mjs";
1414
import { generateAssetsCatalogs } from "./assetsCatalog.mjs";
1515
import { generateEntitlements } from "./entitlements.mjs";
16-
import { isBridgelessEnabled, isNewArchEnabled } from "./features.mjs";
16+
import {
17+
isBridgelessEnabled,
18+
isHermesEnabled,
19+
isNewArchEnabled,
20+
} from "./features.mjs";
1721
import { generateInfoPlist } from "./infoPlist.mjs";
1822
import { generateLocalizations, getProductName } from "./localizations.mjs";
1923
import { generatePrivacyManifest } from "./privacyManifest.mjs";
@@ -220,8 +224,6 @@ export function generateProject(
220224
fs
221225
);
222226
const reactNativeVersion = readPackageVersion(reactNativePath, fs);
223-
const useNewArch = isNewArchEnabled(options, reactNativeVersion);
224-
const useBridgeless = isBridgelessEnabled(options, reactNativeVersion);
225227

226228
/** @type {ProjectConfiguration} */
227229
const project = {
@@ -234,8 +236,9 @@ export function generateProject(
234236
reactNativeVersion,
235237
fs
236238
),
237-
useNewArch,
238-
useBridgeless,
239+
useHermes: isHermesEnabled(targetPlatform, reactNativeVersion, options),
240+
useNewArch: isNewArchEnabled(reactNativeVersion, options),
241+
useBridgeless: isBridgelessEnabled(reactNativeVersion, options),
239242
buildSettings: {},
240243
testsBuildSettings: {},
241244
uitestsBuildSettings: {},

ios/features.mjs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @ts-check
2-
/** @import { JSONObject } from "../scripts/types.ts"; */
2+
/** @import { ApplePlatform, JSONObject } from "../scripts/types.ts"; */
33
import { v } from "../scripts/helpers.js";
44

55
/**
@@ -10,18 +10,18 @@ function supportsNewArch(reactNativeVersion) {
1010
}
1111

1212
/**
13-
* @param {JSONObject} options
1413
* @param {number} reactNativeVersion
14+
* @param {JSONObject} options
1515
* @returns {boolean}
1616
*/
17-
export function isNewArchEnabled(options, reactNativeVersion) {
17+
export function isNewArchEnabled(reactNativeVersion, options) {
1818
if (!supportsNewArch(reactNativeVersion)) {
1919
return false;
2020
}
2121

22-
const envVar = process.env["RCT_NEW_ARCH_ENABLED"];
23-
if (typeof envVar === "string") {
24-
return envVar !== "0";
22+
const newArchEnabled = process.env["RCT_NEW_ARCH_ENABLED"];
23+
if (typeof newArchEnabled === "string") {
24+
return newArchEnabled !== "0";
2525
}
2626

2727
if ("newArchEnabled" in options) {
@@ -37,12 +37,12 @@ export function isNewArchEnabled(options, reactNativeVersion) {
3737
}
3838

3939
/**
40-
* @param {JSONObject} options
4140
* @param {number} reactNativeVersion
41+
* @param {JSONObject} options
4242
* @returns {boolean}
4343
*/
44-
export function isBridgelessEnabled(options, reactNativeVersion) {
45-
if (isNewArchEnabled(options, reactNativeVersion)) {
44+
export function isBridgelessEnabled(reactNativeVersion, options) {
45+
if (isNewArchEnabled(reactNativeVersion, options)) {
4646
if (reactNativeVersion >= v(0, 74, 0)) {
4747
return options["bridgelessEnabled"] !== false;
4848
}
@@ -52,3 +52,24 @@ export function isBridgelessEnabled(options, reactNativeVersion) {
5252
}
5353
return false;
5454
}
55+
56+
/**
57+
* @param {ApplePlatform} platform
58+
* @param {number} reactNativeVersion
59+
* @param {JSONObject} options
60+
* @returns {boolean | "from-source"}
61+
*/
62+
export function isHermesEnabled(platform, reactNativeVersion, options) {
63+
const useHermes = process.env["USE_HERMES"];
64+
const enabled =
65+
typeof useHermes === "string"
66+
? useHermes === "1"
67+
: options["hermesEnabled"] === true;
68+
69+
// Hermes prebuilds for visionOS was introduced in 0.76
70+
if (enabled && platform === "visionos" && reactNativeVersion < v(0, 76, 0)) {
71+
return "from-source";
72+
}
73+
74+
return enabled;
75+
}

ios/pod_helpers.rb

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,9 @@ def find_file(file_name, current_dir)
2828
end
2929

3030
def use_hermes?(options)
31-
use_hermes = ENV.fetch('USE_HERMES', nil)
32-
return use_hermes == '1' unless use_hermes.nil?
33-
34-
# Hermes prebuilds for visionOS was introduced in 0.76
35-
is_visionos = options[:path].end_with?('react-native-visionos')
36-
ENV['RCT_BUILD_HERMES_FROM_SOURCE'] = 'true' if is_visionos && options[:version] < v(0, 76, 0)
37-
38-
options[:hermes_enabled] == true
31+
use_hermes = options[:use_hermes]
32+
ENV['RCT_BUILD_HERMES_FROM_SOURCE'] = 'true' if use_hermes == 'from-source'
33+
use_hermes != false
3934
end
4035

4136
def use_new_architecture!(options, react_native_version)

ios/test_app.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def use_react_native!(project_root, project, options)
111111
app_path: find_file('package.json', project_root).parent.to_s,
112112
path: react_native_path.relative_path_from(project_root).to_s,
113113
rta_project_root: project_root,
114+
use_hermes: project[:use_hermes],
114115
use_new_arch: project[:use_new_arch],
115116
use_bridgeless: project[:use_bridgeless],
116117
version: project[:react_native_version])
@@ -168,6 +169,7 @@ def make_project!(project_root, target_platform, options)
168169
:react_native_version => project['reactNativeVersion'],
169170
:react_native_host_path => project['reactNativeHostPath'],
170171
:community_autolinking_script_path => project['communityAutolinkingScriptPath'],
172+
:use_hermes => project['useHermes'],
171173
:use_new_arch => project['useNewArch'],
172174
:use_bridgeless => project['useBridgeless'],
173175
:code_sign_identity => build_settings[CODE_SIGN_IDENTITY] || '',

scripts/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export type ProjectConfiguration = {
139139
reactNativeHostPath: string;
140140
communityAutolinkingScriptPath?: string;
141141
singleApp?: string;
142+
useHermes: boolean | "from-source";
142143
useNewArch: boolean;
143144
useBridgeless: boolean;
144145
buildSettings: Record<string, string | string[]>;

test/ios/features.test.ts

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { ok } from "node:assert/strict";
1+
import { equal, ok } from "node:assert/strict";
22
import { afterEach, before, describe, it } from "node:test";
3-
import { isBridgelessEnabled, isNewArchEnabled } from "../../ios/features.mjs";
3+
import {
4+
isBridgelessEnabled,
5+
isHermesEnabled,
6+
isNewArchEnabled,
7+
} from "../../ios/features.mjs";
48
import { v } from "../../scripts/helpers.js";
59

610
describe("isBridgelessEnabled()", () => {
@@ -19,34 +23,76 @@ describe("isBridgelessEnabled()", () => {
1923
});
2024

2125
it("returns true only when new architecture is enabled", () => {
22-
ok(!isBridgelessEnabled({}, 0));
23-
ok(!isBridgelessEnabled({}, firstAvailableVersion));
26+
ok(!isBridgelessEnabled(0, {}));
27+
ok(!isBridgelessEnabled(firstAvailableVersion, {}));
2428

2529
const options = { bridgelessEnabled: true, fabricEnabled: true };
2630

27-
ok(!isBridgelessEnabled(options, v(0, 72, 999)));
28-
ok(isBridgelessEnabled(options, firstAvailableVersion));
31+
ok(!isBridgelessEnabled(v(0, 72, 999), options));
32+
ok(isBridgelessEnabled(firstAvailableVersion, options));
2933
});
3034

3135
it("returns true by default starting with 0.74", () => {
3236
// Bridgeless mode is enabled by default starting with 0.74 unless opted-out of
33-
ok(isBridgelessEnabled({ fabricEnabled: true }, defaultVersion));
37+
ok(isBridgelessEnabled(defaultVersion, { fabricEnabled: true }));
3438
ok(
35-
!isBridgelessEnabled(
36-
{ bridgelessEnabled: false, fabricEnabled: true },
37-
defaultVersion
38-
)
39+
!isBridgelessEnabled(defaultVersion, {
40+
bridgelessEnabled: false,
41+
fabricEnabled: true,
42+
})
3943
);
4044
});
4145

4246
it("does not return true just because `RCT_NEW_ARCH_ENABLED` is set", () => {
4347
// `RCT_NEW_ARCH_ENABLED` does not enable bridgeless on older versions
4448
process.env["RCT_NEW_ARCH_ENABLED"] = "1";
4549

46-
ok(!isBridgelessEnabled({}, v(0, 72, 999)));
47-
ok(!isBridgelessEnabled({}, firstAvailableVersion));
48-
ok(isBridgelessEnabled({}, defaultVersion));
49-
ok(!isBridgelessEnabled({ bridgelessEnabled: false }, defaultVersion));
50+
ok(!isBridgelessEnabled(v(0, 72, 999), {}));
51+
ok(!isBridgelessEnabled(firstAvailableVersion, {}));
52+
ok(isBridgelessEnabled(defaultVersion, {}));
53+
ok(!isBridgelessEnabled(defaultVersion, { bridgelessEnabled: false }));
54+
});
55+
});
56+
57+
describe("isHermesEnabled()", () => {
58+
before(() => {
59+
delete process.env["USE_HERMES"];
60+
});
61+
62+
afterEach(() => {
63+
delete process.env["USE_HERMES"];
64+
});
65+
66+
for (const platform of ["ios", "macos", "visionos"] as const) {
67+
it(`[${platform}] is disabled by default`, () => {
68+
ok(!isHermesEnabled(platform, v(1, 0, 0), {}));
69+
});
70+
71+
it(`[${platform}] returns true when enabled`, () => {
72+
ok(isHermesEnabled(platform, v(1, 0, 0), { hermesEnabled: true }));
73+
});
74+
75+
it(`[${platform}] returns true if 'USE_HERMES=1'`, () => {
76+
process.env["USE_HERMES"] = "1";
77+
ok(isHermesEnabled(platform, v(1, 0, 0), {}));
78+
});
79+
80+
it(`[${platform}] returns false if 'USE_HERMES=0'`, () => {
81+
process.env["USE_HERMES"] = "0";
82+
ok(!isHermesEnabled(platform, v(1, 0, 0), { hermesEnabled: true }));
83+
});
84+
}
85+
86+
it("[visionos] builds from source when necessary", () => {
87+
const options = { hermesEnabled: true };
88+
89+
equal(isHermesEnabled("visionos", v(0, 75, 0), options), "from-source");
90+
equal(isHermesEnabled("visionos", v(0, 76, 0), options), true);
91+
92+
process.env["USE_HERMES"] = "1";
93+
94+
equal(isHermesEnabled("visionos", v(0, 75, 0), {}), "from-source");
95+
equal(isHermesEnabled("visionos", v(0, 76, 0), {}), true);
5096
});
5197
});
5298

@@ -63,27 +109,27 @@ describe("isNewArchEnabled()", () => {
63109
});
64110

65111
it("returns true if New Architecture is available and enabled", () => {
66-
ok(!isNewArchEnabled({}, 0));
67-
ok(!isNewArchEnabled({}, firstAvailableVersion));
112+
ok(!isNewArchEnabled(0, {}));
113+
ok(!isNewArchEnabled(firstAvailableVersion, {}));
68114

69115
// New architecture is first publicly available in 0.68, but we'll require 0.71
70-
ok(!isNewArchEnabled({ fabricEnabled: true }, v(0, 70, 999)));
71-
ok(isNewArchEnabled({ fabricEnabled: true }, firstAvailableVersion));
72-
ok(isNewArchEnabled({ newArchEnabled: true }, firstAvailableVersion));
116+
ok(!isNewArchEnabled(v(0, 70, 999), { fabricEnabled: true }));
117+
ok(isNewArchEnabled(firstAvailableVersion, { fabricEnabled: true }));
118+
ok(isNewArchEnabled(firstAvailableVersion, { newArchEnabled: true }));
73119
});
74120

75121
it("returns true if `RCT_NEW_ARCH_ENABLED=1`", () => {
76122
process.env["RCT_NEW_ARCH_ENABLED"] = "1";
77123

78-
ok(!isNewArchEnabled({}, v(0, 70, 999)));
79-
ok(isNewArchEnabled({}, firstAvailableVersion));
124+
ok(!isNewArchEnabled(v(0, 70, 999), {}));
125+
ok(isNewArchEnabled(firstAvailableVersion, {}));
80126
});
81127

82128
it("returns false if `RCT_NEW_ARCH_ENABLED=0`", () => {
83129
process.env["RCT_NEW_ARCH_ENABLED"] = "0";
84130

85-
ok(!isNewArchEnabled({}, v(0, 70, 999)));
86-
ok(!isNewArchEnabled({}, firstAvailableVersion));
87-
ok(!isNewArchEnabled({ fabric_enabled: true }, firstAvailableVersion));
131+
ok(!isNewArchEnabled(v(0, 70, 999), {}));
132+
ok(!isNewArchEnabled(firstAvailableVersion, {}));
133+
ok(!isNewArchEnabled(firstAvailableVersion, { fabric_enabled: true }));
88134
});
89135
});

test/test_pod_helpers.rb

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,12 @@ def test_assert_version
1818
end
1919

2020
def test_use_hermes?
21-
options = { path: '../node_modules/react-native' }
22-
23-
ENV.delete('RCT_BUILD_HERMES_FROM_SOURCE')
24-
ENV.delete('USE_HERMES')
25-
26-
refute(use_hermes?(options))
27-
assert(use_hermes?({ **options, hermes_enabled: true }))
28-
refute(ENV.fetch('RCT_BUILD_HERMES_FROM_SOURCE', nil))
29-
30-
ENV['USE_HERMES'] = '0'
31-
32-
refute(use_hermes?(options))
33-
refute(use_hermes?({ **options, hermes_enabled: true }))
34-
refute(ENV.fetch('RCT_BUILD_HERMES_FROM_SOURCE', nil))
35-
36-
ENV['USE_HERMES'] = '1'
37-
38-
assert(use_hermes?(options))
39-
assert(use_hermes?({ **options, hermes_enabled: true }))
21+
refute(use_hermes?({ use_hermes: false }))
22+
assert(use_hermes?({ use_hermes: true }))
4023
refute(ENV.fetch('RCT_BUILD_HERMES_FROM_SOURCE', nil))
4124

42-
ENV.delete('RCT_BUILD_HERMES_FROM_SOURCE')
43-
ENV.delete('USE_HERMES')
44-
end
45-
46-
def test_use_hermes_visionos?
47-
options = {
48-
path: '../node_modules/@callstack/react-native-visionos',
49-
hermes_enabled: true,
50-
}
51-
52-
ENV.delete('RCT_BUILD_HERMES_FROM_SOURCE')
53-
ENV.delete('USE_HERMES')
54-
55-
assert(use_hermes?({ **options, version: v(0, 76, 0) }))
56-
refute(ENV.fetch('RCT_BUILD_HERMES_FROM_SOURCE', nil))
57-
58-
assert(use_hermes?({ **options, version: v(0, 75, 0) }))
25+
assert(use_hermes?({ use_hermes: 'from-source' }))
5926
assert_equal('true', ENV.fetch('RCT_BUILD_HERMES_FROM_SOURCE'))
60-
61-
ENV.delete('RCT_BUILD_HERMES_FROM_SOURCE')
62-
ENV.delete('USE_HERMES')
6327
end
6428

6529
def test_v

0 commit comments

Comments
 (0)