forked from microsoft/react-native-macos
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolve-hermes.mts
More file actions
250 lines (219 loc) · 8.54 KB
/
resolve-hermes.mts
File metadata and controls
250 lines (219 loc) · 8.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/usr/bin/env node
/**
* CI entry point for resolving Hermes artifacts.
*
* Commands:
* node resolve-hermes.mts download-hermes [Debug|Release]
* node resolve-hermes.mts recompose-xcframework <tarball> <destroot>
* node resolve-hermes.mts resolve-commit
*
* Each command writes results to $GITHUB_OUTPUT for use in GitHub Actions.
*/
import { createRequire } from 'node:module';
import os from 'node:os';
import { parseArgs } from 'node:util';
import { $, echo, fs, path } from 'zx';
// Use createRequire to import CommonJS modules from ESM context
const require = createRequire(import.meta.url);
const {
findMatchingHermesVersion,
findVersionAtMergeBase,
getLatestStableVersionFromNPM,
hermesCommitAtMergeBase,
} = require('../../packages/react-native/scripts/ios-prebuild/microsoft-hermes.js');
const {
computeNightlyTarballURL,
} = require('../../packages/react-native/scripts/ios-prebuild/utils.js');
function setActionOutput(key: string, value: string) {
const outputFile = process.env.GITHUB_OUTPUT;
if (outputFile) {
fs.appendFileSync(outputFile, `${key}=${value}\n`);
}
}
/**
* Downloads the upstream Hermes tarball from Maven or Sonatype.
*
* Tries multiple version resolution strategies in order:
* 1. Mapped version from peerDependencies (stable branches)
* 2. Version at merge base with facebook/react-native (main branch)
* 3. Latest stable version from npm (last resort)
*
* Returns {tarballPath, version} on success, or null if no tarball is available.
*/
async function downloadUpstreamHermesTarball(
buildType: string = 'Debug',
): Promise<{ tarballPath: string; version: string } | null> {
const packageJsonPath = path.resolve(
import.meta.dirname!, '..', '..', 'packages', 'react-native', 'package.json',
);
// Build a list of candidate versions to try (in priority order)
const candidates: string[] = [];
const mapped = findMatchingHermesVersion(packageJsonPath);
if (mapped != null) {
candidates.push(mapped);
}
const mergeBaseVersion = findVersionAtMergeBase();
if (mergeBaseVersion != null && !candidates.includes(mergeBaseVersion)) {
candidates.push(mergeBaseVersion);
}
try {
const latestStable = await getLatestStableVersionFromNPM();
if (!candidates.includes(latestStable)) {
candidates.push(latestStable);
}
} catch {
// npm lookup failed, continue with what we have
}
if (candidates.length === 0) {
echo('Could not determine any upstream version to download Hermes tarball');
return null;
}
const mavenRepoUrl = 'https://repo1.maven.org/maven2';
const namespace = 'com/facebook/react';
for (const version of candidates) {
const releaseUrl = `${mavenRepoUrl}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-hermes-ios-${buildType.toLowerCase()}.tar.gz`;
const nightlyUrl = await computeNightlyTarballURL(
version,
buildType,
'react-native-artifacts',
`hermes-ios-${buildType.toLowerCase()}.tar.gz`,
);
const urlsToTry = [releaseUrl];
if (nightlyUrl) {
urlsToTry.push(nightlyUrl);
}
for (const tarballUrl of urlsToTry) {
echo(`Trying upstream Hermes tarball (version: ${version}, ${buildType}) at ${tarballUrl}...`);
try {
const response = await fetch(tarballUrl);
if (!response.ok) {
echo(`Tarball not available: ${response.status} ${response.statusText}`);
continue;
}
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-'));
const tarballPath = path.join(tmpDir, 'hermes-ios.tar.gz');
const buffer = await response.arrayBuffer();
fs.writeFileSync(tarballPath, Buffer.from(buffer));
echo(`Downloaded upstream Hermes tarball (${version}) to ${tarballPath}`);
return { tarballPath, version };
} catch (e: any) {
echo(`Error downloading tarball for ${version}: ${e.message}`);
continue;
}
}
}
echo('No upstream Hermes tarball found for any candidate version — will build from source.');
return null;
}
/**
* Extracts an upstream Hermes tarball and recomposes the xcframework to include
* the macOS slice, if needed.
*
* Upstream tarballs ship a universal xcframework (iOS, simulator, catalyst,
* tvOS, visionOS) plus a standalone macosx/hermes.framework. This function
* merges the standalone macOS framework into the universal xcframework using
* `xcodebuild -create-xcframework`.
*
* NOTE: Once upstream Hermes includes macOS in the universal xcframework
* natively, this function will detect the existing macOS slice and skip
* the recompose. At that point, this step can be removed entirely.
* Tracking PRs:
* - https://github.com/facebook/hermes/pull/1958
* - https://github.com/facebook/hermes/pull/1970
* - https://github.com/facebook/hermes/pull/1971
*/
async function recomposeHermesXcframework(
tarballPath: string,
destroot: string,
): Promise<boolean> {
// Extract tarball
fs.mkdirSync(destroot, { recursive: true });
await $`tar -xzf ${tarballPath} -C ${destroot} --strip-components=2`;
const frameworksDir = path.join(destroot, 'Library', 'Frameworks');
const xcfwPath = path.join(frameworksDir, 'universal', 'hermes.xcframework');
echo('Upstream tarball contents:');
await $`ls -la ${frameworksDir}`;
// Check if macOS is already in the universal xcframework — if so, no recompose needed
const xcfwContents = fs.readdirSync(xcfwPath);
const hasMacSlice = xcfwContents.some(
(entry: string) => entry.startsWith('macos') && entry.includes('arm64'),
);
if (hasMacSlice) {
echo('macOS slice already present in universal xcframework, skipping recompose');
const standaloneMacDir = path.join(frameworksDir, 'macosx');
if (fs.existsSync(standaloneMacDir)) {
fs.removeSync(standaloneMacDir);
}
return true;
}
// Check for standalone macOS framework
const standaloneMacFw = path.join(frameworksDir, 'macosx', 'hermes.framework');
if (!fs.existsSync(standaloneMacFw)) {
echo('ERROR: Upstream tarball missing macosx/hermes.framework');
return false;
}
// Collect existing frameworks from inside the universal xcframework
const frameworkArgs: string[] = [];
for (const entry of xcfwContents) {
const fwPath = path.join(xcfwPath, entry, 'hermes.framework');
if (fs.existsSync(fwPath) && fs.statSync(fwPath).isDirectory()) {
echo(`Found slice: ${fwPath}`);
frameworkArgs.push('-framework', fwPath);
}
}
// Add the standalone macOS framework
echo(`Found standalone macOS slice: ${standaloneMacFw}`);
frameworkArgs.push('-framework', standaloneMacFw);
// Build new xcframework at a temp path (frameworks reference paths inside the old xcfw)
const xcfwNew = path.join(frameworksDir, 'universal', 'hermes-new.xcframework');
const sliceCount = frameworkArgs.filter(f => f !== '-framework').length;
echo(`Creating new universal xcframework with ${sliceCount} slices...`);
await $`xcodebuild -create-xcframework ${frameworkArgs} -output ${xcfwNew} -allow-internal-distribution`;
// Swap in the recomposed xcframework
fs.removeSync(xcfwPath);
fs.renameSync(xcfwNew, xcfwPath);
// Clean up standalone macOS dir (now included in universal)
fs.removeSync(path.join(frameworksDir, 'macosx'));
echo('Recomposed xcframework:');
await $`ls -la ${xcfwPath}/`;
return true;
}
// --- CLI dispatch ---
const { positionals } = parseArgs({
allowPositionals: true,
strict: false,
});
const [command, ...args] = positionals;
switch (command) {
case 'download-hermes': {
const buildType = args[0] || 'Debug';
const result = await downloadUpstreamHermesTarball(buildType);
if (result != null) {
setActionOutput('tarball', result.tarballPath);
setActionOutput('version', result.version);
echo(`Downloaded upstream Hermes tarball for version ${result.version}`);
} else {
echo('No upstream tarball available');
}
break;
}
case 'recompose-xcframework': {
const [tarball, destroot] = args;
if (!tarball || !destroot) {
echo('Usage: node resolve-hermes.mts recompose-xcframework <tarball> <destroot>');
process.exit(1);
}
const recomposed = await recomposeHermesXcframework(tarball, destroot);
setActionOutput('recomposed', String(recomposed));
break;
}
case 'resolve-commit': {
const { commit } = hermesCommitAtMergeBase();
setActionOutput('hermes-commit', commit);
echo(`Resolved Hermes commit: ${commit}`);
break;
}
default:
echo(`Unknown command: ${command ?? '(none)'}. Available: download-hermes, recompose-xcframework, resolve-commit`);
process.exit(1);
}