@@ -12,75 +12,317 @@ const {execSync} = require('child_process');
1212const fs = require ( 'fs' ) ;
1313const path = require ( 'path' ) ;
1414
15+ /**
16+ * Downloads hermes artifacts from the specified version and build type. If you want to specify a specific
17+ * version of hermes, use the HERMES_VERSION environment variable. The path to the artifacts will be inside
18+ * the .build/artifacts/hermes folder, but this can be overridden by setting the HERMES_ENGINE_TARBALL_PATH
19+ * environment variable. If this varuable is set, the script will use the local tarball instead of downloading it.
20+ */
1521async function prepareHermesArtifactsAsync (
1622 version /*:string*/ ,
17- buildType /*:string */ ,
23+ buildType /*: 'debug' | 'release' */ ,
1824) /*: Promise<string> */ {
19- // Check if the Hermes artifacts are already downloaded
25+ hermesLog ( `Preparing Hermes...` ) ;
26+
27+ // See if the user has set the HERMES_ENGINE_TARBALL_PATH environment variable
28+ let localPath = process . env . HERMES_ENGINE_TARBALL_PATH ?? '' ;
29+
30+ // Create artifacts folder
2031 const artifactsPath /*: string*/ = path . resolve (
2132 process . cwd ( ) ,
2233 '.build' ,
2334 'artifacts' ,
2435 'hermes' ,
2536 ) ;
26- if ( fs . existsSync ( artifactsPath ) ) {
27- return artifactsPath ;
37+
38+ // Ensure that the artifacts folder exists
39+ fs . mkdirSync ( artifactsPath , { recursive : true } ) ;
40+
41+ // Path for keeping track of the current version in the artifacts folder
42+ const versionFilePath = path . join ( artifactsPath , 'version.txt' ) ;
43+
44+ // Only check if the artifacts folder exists if we are not using a local tarball
45+ if ( ! localPath ) {
46+ // Resolve the version from the environment variable or use the default version
47+ const resolvedVersion = process . env . HERMES_VERSION ?? version ;
48+
49+ // Check if the Hermes artifacts are already downloaded
50+ if (
51+ checkExistingVersion (
52+ versionFilePath ,
53+ resolvedVersion ,
54+ buildType ,
55+ artifactsPath ,
56+ )
57+ ) {
58+ return artifactsPath ;
59+ }
60+
61+ const sourceType = hermesSourceType ( resolvedVersion , buildType ) ;
62+ localPath = resolveSourceFromSourceType (
63+ sourceType ,
64+ resolvedVersion ,
65+ buildType ,
66+ artifactsPath ,
67+ ) ;
68+ } else {
69+ hermesLog ( 'Using local tarball, skipping artifacts folder check' ) ;
70+ // Delete version.txt if it exists
71+ if ( fs . existsSync ( versionFilePath ) ) {
72+ fs . unlinkSync ( versionFilePath ) ;
73+ }
2874 }
2975
30- // Download the Hermes artifacts
31- const url = getHermesArtifactsUrl ( version , buildType ) ;
32- console . log ( `Downloading Hermes artifacts from ${ url } ...` ) ;
76+ // Extract the tar.gz
77+ execSync ( `tar -xzf "${ localPath } " -C "${ artifactsPath } "` , {
78+ stdio : 'inherit' ,
79+ } ) ;
80+
81+ // Delete the tarball after extraction
82+ if ( ! process . env . HERMES_ENGINE_TARBALL_PATH ) {
83+ fs . unlinkSync ( localPath ) ;
84+ }
3385
34- // download the file pointed to by the URL and store it in the ./.build/artifacts folder on disk
35- await downloadAndExtract ( url , artifactsPath ) ;
3686 return artifactsPath ;
3787}
3888
39- async function downloadAndExtract ( url /*:string*/ , targetFolder /*:string*/ ) {
40- const buildDir = path . resolve ( '.build' , 'artifacts' ) ;
41- const tarballPath = path . join ( buildDir , 'artifact.tar.gz' ) ;
89+ /*::
90+ type HermesEngineSourceType =
91+ | 'local_prebuilt_tarball'
92+ | 'download_prebuild_tarball'
93+ | 'download_prebuilt_nightly_tarball'
94+ */
95+
96+ const HermesEngineSourceTypes = {
97+ LOCAL_PREBUILT_TARBALL : 'local_prebuilt_tarball' ,
98+ DOWNLOAD_PREBUILD_TARBALL : 'download_prebuild_tarball' ,
99+ DOWNLOAD_PREBUILT_NIGHTLY_TARBALL : 'download_prebuilt_nightly_tarball' ,
100+ } ;
101+
102+ /**
103+ * Checks if the Hermes artifacts are already downloaded and up to date with the specified version.
104+ * Returns true if the artifacts are up to date, false otherwise.
105+ */
106+ function checkExistingVersion (
107+ versionFilePath /*: string */ ,
108+ version /*: string */ ,
109+ buildType /*: 'debug' | 'release' */ ,
110+ artifactsPath /*: string */ ,
111+ ) {
112+ const resolvedVersion = `${ version } -${ buildType } ` ;
113+ const hermesXCFramework = path . join (
114+ artifactsPath ,
115+ 'destroot' ,
116+ 'Libraries' ,
117+ 'Frameworks' ,
118+ 'universal' ,
119+ 'hermes.xcframework' ,
120+ ) ;
42121
43- // Ensure build directory exists
44- fs . mkdirSync ( targetFolder , { recursive : true } ) ;
122+ if ( fs . existsSync ( versionFilePath ) && fs . existsSync ( hermesXCFramework ) ) {
123+ const versionFileContent = fs . readFileSync ( versionFilePath , 'utf8' ) ;
124+ if ( versionFileContent . trim ( ) === resolvedVersion ) {
125+ hermesLog (
126+ `Hermes artifacts already downloaded and up to date: ${ artifactsPath } ` ,
127+ ) ;
128+ return true ;
129+ }
130+ }
131+ // If the version file does not exist or the version does not match, delete the artifacts folder
132+ fs . rmSync ( artifactsPath , { recursive : true , force : true } ) ;
133+ hermesLog (
134+ `Hermes artifacts folder already exists, but version does not match. Deleting: ${ artifactsPath } ` ,
135+ ) ;
136+ // Lets create the version.txt file
137+ fs . mkdirSync ( artifactsPath , { recursive : true } ) ;
138+ fs . writeFileSync ( versionFilePath , resolvedVersion , 'utf8' ) ;
139+ hermesLog (
140+ `Hermes artifacts folder created: ${ artifactsPath } with version: ${ resolvedVersion } ` ,
141+ ) ;
142+ return false ;
143+ }
45144
46- console . log ( `Downloading file from ${ url } to ${ tarballPath } ...` ) ;
145+ function hermesEngineTarballEnvvarDefined ( ) /*: boolean */ {
146+ return ! ! process . env . HERMES_ENGINE_TARBALL_PATH ;
147+ }
47148
149+ function getTarballUrl (
150+ version /*: string */ ,
151+ buildType /*: 'debug' | 'release' */ ,
152+ ) /*: string */ {
153+ const mavenRepoUrl = 'https://repo1.maven.org/maven2' ;
154+ const namespace = 'com/facebook/react' ;
155+ return `${ mavenRepoUrl } /${ namespace } /react-native-artifacts/${ version } /react-native-artifacts-${ version } -hermes-ios-${ buildType } .tar.gz` ;
156+ }
157+
158+ function getNightlyTarballUrl (
159+ version /*: string */ ,
160+ buildType /*: 'debug' | 'release' */ ,
161+ ) /*: string */ {
162+ const params = `r=snapshots&g=com.facebook.react&a=react-native-artifacts&c=hermes-ios-${ buildType } &e=tar.gz&v=${ version } -SNAPSHOT` ;
163+ return resolveUrlRedirects (
164+ `https://oss.sonatype.org/service/local/artifact/maven/redirect?${ params } ` ,
165+ ) ;
166+ }
167+
168+ function resolveUrlRedirects ( url /*: string */ ) /*: string */ {
169+ // Synchronously resolve the final URL after redirects using curl
48170 try {
49- // Download the file using curl via execSync
50- execSync ( `curl -L "${ url } " -o "${ tarballPath } "` , { stdio : 'inherit' } ) ;
171+ return execSync ( `curl -Ls -o /dev/null -w '%{url_effective}' "${ url } "` )
172+ . toString ( )
173+ . trim ( ) ;
174+ } catch ( e ) {
175+ hermesLog ( `Failed to resolve URL redirects\n${ e } ` , 'error' ) ;
176+ return url ;
177+ }
178+ }
51179
52- console . log ( 'Download complete. Extracting...' ) ;
180+ function hermesArtifactExists ( tarballUrl /*: string */ ) /*: boolean */ {
181+ try {
182+ const code = execSync (
183+ `curl -o /dev/null --silent -Iw '%{http_code}' -L "${ tarballUrl } "` ,
184+ )
185+ . toString ( )
186+ . trim ( ) ;
187+ return code === '200' ;
188+ } catch ( e ) {
189+ return false ;
190+ }
191+ }
53192
54- // Extract the tar.gz using execSync
55- execSync ( `tar -xzf "${ tarballPath } " -C "${ targetFolder } "` , {
56- stdio : 'inherit' ,
57- } ) ;
193+ function hermesSourceType (
194+ version /*: string */ ,
195+ buildType /*: 'debug' | 'release' */ ,
196+ ) /*: HermesEngineSourceType */ {
197+ if ( hermesEngineTarballEnvvarDefined ( ) ) {
198+ hermesLog ( 'Using local prebuild tarball' ) ;
199+ return HermesEngineSourceTypes . LOCAL_PREBUILT_TARBALL ;
200+ }
201+ if ( hermesArtifactExists ( getTarballUrl ( version , buildType ) ) ) {
202+ hermesLog ( `Using download prebuild ${ buildType } tarball` ) ;
203+ return HermesEngineSourceTypes . DOWNLOAD_PREBUILD_TARBALL ;
204+ }
205+ if (
206+ hermesArtifactExists (
207+ getNightlyTarballUrl ( version , buildType ) . replace ( / \\ / g, '' ) ,
208+ )
209+ ) {
210+ hermesLog ( 'Using download prebuild nightly tarball' ) ;
211+ return HermesEngineSourceTypes . DOWNLOAD_PREBUILT_NIGHTLY_TARBALL ;
212+ }
213+ hermesLog (
214+ 'Using download prebuild nightly tarball - this is a fallback and might not work.' ,
215+ ) ;
216+ return HermesEngineSourceTypes . DOWNLOAD_PREBUILT_NIGHTLY_TARBALL ;
217+ }
58218
59- // Delete the tarball after extraction
60- fs . unlinkSync ( tarballPath ) ;
219+ function resolveSourceFromSourceType (
220+ sourceType /*: HermesEngineSourceType */ ,
221+ version /*: string */ ,
222+ buildType /*: 'debug' | 'release' */ ,
223+ artifactsPath /*: string*/ ,
224+ ) /*: string */ {
225+ switch ( sourceType ) {
226+ case HermesEngineSourceTypes . LOCAL_PREBUILT_TARBALL :
227+ return localPrebuiltTarball ( ) ;
228+ case HermesEngineSourceTypes . DOWNLOAD_PREBUILD_TARBALL :
229+ return downloadPrebuildTarball ( version , buildType , artifactsPath ) ;
230+ case HermesEngineSourceTypes . DOWNLOAD_PREBUILT_NIGHTLY_TARBALL :
231+ return downloadPrebuiltNightlyTarball ( version , buildType , artifactsPath ) ;
232+ default :
233+ abort (
234+ `[Hermes] Unsupported or invalid source type provided: ${ sourceType } ` ,
235+ ) ;
236+ return '' ;
237+ }
238+ }
61239
62- console . log ( 'Download and extraction complete.' ) ;
63- } catch ( error ) {
64- if ( fs . existsSync ( tarballPath ) ) {
65- fs . unlinkSync ( tarballPath ) ;
240+ function localPrebuiltTarball ( ) /*: string */ {
241+ const tarballPath = process . env . HERMES_ENGINE_TARBALL_PATH ;
242+ if ( tarballPath && fs . existsSync ( tarballPath ) ) {
243+ hermesLog (
244+ `Using pre-built binary from local path defined by HERMES_ENGINE_TARBALL_PATH envvar: ${ tarballPath } ` ,
245+ ) ;
246+ return `file://${ tarballPath } ` ;
247+ }
248+ abort (
249+ `[Hermes] HERMES_ENGINE_TARBALL_PATH is set, but points to a non-existing file: "${ tarballPath ?? 'unknown' } "\nIf you don't want to use tarball, run 'unset HERMES_ENGINE_TARBALL_PATH'` ,
250+ ) ;
251+ return '' ;
252+ }
253+
254+ function downloadPrebuildTarball (
255+ version /*: string */ ,
256+ buildType /*: 'debug' | 'release' */ ,
257+ artifactsPath /*: string*/ ,
258+ ) /*: string */ {
259+ const url = getTarballUrl ( version , buildType ) ;
260+ hermesLog ( `Using release tarball from URL: ${ url } ` ) ;
261+ return downloadStableHermes ( version , buildType , artifactsPath ) ;
262+ }
263+
264+ function downloadPrebuiltNightlyTarball (
265+ version /*: string */ ,
266+ buildType /*: 'debug' | 'release' */ ,
267+ artifactsPath /*: string*/ ,
268+ ) /*: string */ {
269+ const url = getNightlyTarballUrl ( version , buildType ) ;
270+ hermesLog ( `Using nightly tarball from URL: ${ url } ` ) ;
271+ return downloadHermesTarball ( url , version , buildType , artifactsPath ) ;
272+ }
273+
274+ function downloadStableHermes (
275+ version /*: string */ ,
276+ buildType /*: 'debug' | 'release' */ ,
277+ artifactsPath /*: string */ ,
278+ ) /*: string */ {
279+ const tarballUrl = getTarballUrl ( version , buildType ) ;
280+ return downloadHermesTarball ( tarballUrl , version , buildType , artifactsPath ) ;
281+ }
282+
283+ function downloadHermesTarball (
284+ tarballUrl /*: string */ ,
285+ version /*: string */ ,
286+ buildType /*: 'debug' | 'release' */ ,
287+ artifactsPath /*: string */ ,
288+ ) /*: string */ {
289+ const destPath = buildType
290+ ? `${ artifactsPath } /hermes-ios-${ version } -${ buildType } .tar.gz`
291+ : `${ artifactsPath } /hermes-ios-${ version } .tar.gz` ;
292+ if ( ! fs . existsSync ( destPath ) ) {
293+ const tmpFile = `${ artifactsPath } /hermes-ios.download` ;
294+ try {
295+ fs . mkdirSync ( artifactsPath , { recursive : true } ) ;
296+ hermesLog ( `Downloading Hermes tarball from ${ tarballUrl } ` ) ;
297+ execSync (
298+ `curl "${ tarballUrl } " -Lo "${ tmpFile } " && mv "${ tmpFile } " "${ destPath } "` ,
299+ ) ;
300+ } catch ( e ) {
301+ abort ( `Failed to download Hermes tarball from ${ tarballUrl } ` ) ;
66302 }
67- throw new Error ( `Failed to download or extract: ${ error . message } ` ) ;
68303 }
304+ return destPath ;
69305}
70306
71- function getHermesArtifactsUrl (
72- version /*:string*/ ,
73- buildType /*:string*/ ,
74- ) /*:string*/ {
75- // Define the URL for the Hermes artifacts
76- // The URL format is:
77- // https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/<version>/react-native-artifacts-<version>-hermes-ios-<buildType>.tar.gz
78- // where <version> is the version of React Native and <buildType> is the build type (e.g., "debug" or "release")
79- // The Maven repository URL and namespace
80- // are hardcoded for simplicity, but they could be parameterized if needed
81- const maven_repo_url = 'https://repo1.maven.org/maven2' ;
82- const namespace = 'com/facebook/react' ;
83- return `${ maven_repo_url } /${ namespace } /react-native-artifacts/${ version } /react-native-artifacts-${ version } -hermes-ios-${ buildType } .tar.gz` ;
307+ function abort ( message /*: string */ ) {
308+ hermesLog ( message , 'error' ) ;
309+ throw new Error ( message ) ;
310+ }
311+
312+ function hermesLog (
313+ message /*: string */ ,
314+ level /*: 'info' | 'warning' | 'error' */ = 'warning' ,
315+ ) {
316+ // Simple log coloring for terminal output
317+ const prefix = '[Hermes] ' ;
318+ let colorFn = ( x /*:string*/ ) => x ;
319+ if ( process . stdout . isTTY ) {
320+ if ( level === 'info' ) colorFn = x => `\x1b[32m${ x } \x1b[0m` ;
321+ else if ( level === 'error' ) colorFn = x => `\x1b[31m${ x } \x1b[0m` ;
322+ else colorFn = x => `\x1b[33m${ x } \x1b[0m` ;
323+ }
324+
325+ console . log ( colorFn ( prefix + message ) ) ;
84326}
85327
86328module . exports = {
0 commit comments