1/** 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 * 7 * @format 8 */ 9 10'use strict'; 11 12const fs = require('fs'); 13const path = require('path'); 14const {execSync} = require('child_process'); 15 16const SDKS_DIR = path.normalize(path.join(__dirname, '..', '..', 'sdks')); 17const HERMES_DIR = path.join(SDKS_DIR, 'hermes'); 18const DEFAULT_HERMES_TAG = 'main'; 19const HERMES_TAG_FILE_PATH = path.join(SDKS_DIR, '.hermesversion'); 20const HERMES_TARBALL_BASE_URL = 'https://github.com/facebook/hermes/tarball/'; 21const HERMES_TARBALL_DOWNLOAD_DIR = path.join(SDKS_DIR, 'download'); 22const MACOS_BIN_DIR = path.join(SDKS_DIR, 'hermesc', 'osx-bin'); 23const MACOS_HERMESC_PATH = path.join(MACOS_BIN_DIR, 'hermesc'); 24const MACOS_IMPORT_HERMESC_PATH = path.join( 25 MACOS_BIN_DIR, 26 'ImportHermesc.cmake', 27); 28 29function readHermesTag() { 30 if (fs.existsSync(HERMES_TAG_FILE_PATH)) { 31 const data = fs 32 .readFileSync(HERMES_TAG_FILE_PATH, { 33 encoding: 'utf8', 34 flag: 'r', 35 }) 36 .trim(); 37 38 if (data.length > 0) { 39 return data; 40 } else { 41 throw new Error('[Hermes] .hermesversion file is empty.'); 42 } 43 } 44 45 return 'main'; 46} 47 48function setHermesTag(hermesTag) { 49 if (readHermesTag() === hermesTag) { 50 // No need to update. 51 return; 52 } 53 54 if (!fs.existsSync(SDKS_DIR)) { 55 fs.mkdirSync(SDKS_DIR, {recursive: true}); 56 } 57 fs.writeFileSync(HERMES_TAG_FILE_PATH, hermesTag.trim()); 58 console.log('Hermes tag has been updated. Please commit your changes.'); 59} 60 61function getHermesTagSHA(hermesTag) { 62 return execSync( 63 `git ls-remote https://github.com/facebook/hermes ${hermesTag} | cut -f 1`, 64 ) 65 .toString() 66 .trim(); 67} 68 69function getHermesTarballDownloadPath(hermesTag) { 70 const hermesTagSHA = getHermesTagSHA(hermesTag); 71 return path.join(HERMES_TARBALL_DOWNLOAD_DIR, `hermes-${hermesTagSHA}.tgz`); 72} 73 74function downloadHermesTarball() { 75 const hermesTag = readHermesTag(); 76 const hermesTagSHA = getHermesTagSHA(hermesTag); 77 const hermesTarballDownloadPath = getHermesTarballDownloadPath(hermesTag); 78 let hermesTarballUrl = HERMES_TARBALL_BASE_URL + hermesTag; 79 80 if (fs.existsSync(hermesTarballDownloadPath)) { 81 return; 82 } 83 84 if (!fs.existsSync(HERMES_TARBALL_DOWNLOAD_DIR)) { 85 fs.mkdirSync(HERMES_TARBALL_DOWNLOAD_DIR, {recursive: true}); 86 } 87 88 console.info( 89 `[Hermes] Downloading Hermes source code for commit ${hermesTagSHA}`, 90 ); 91 try { 92 execSync(`curl ${hermesTarballUrl} -Lo ${hermesTarballDownloadPath}`); 93 } catch (error) { 94 throw new Error(`[Hermes] Failed to download Hermes tarball. ${error}`); 95 } 96} 97 98function expandHermesTarball() { 99 const hermesTag = readHermesTag(); 100 const hermesTagSHA = getHermesTagSHA(hermesTag); 101 const hermesTarballDownloadPath = getHermesTarballDownloadPath(hermesTag); 102 103 if (!fs.existsSync(hermesTarballDownloadPath)) { 104 throw new Error('[Hermes] Could not locate Hermes tarball.'); 105 } 106 107 if (!fs.existsSync(HERMES_DIR)) { 108 fs.mkdirSync(HERMES_DIR, {recursive: true}); 109 } 110 console.info(`[Hermes] Expanding Hermes tarball for commit ${hermesTagSHA}`); 111 try { 112 execSync( 113 `tar -zxf ${hermesTarballDownloadPath} --strip-components=1 --directory ${HERMES_DIR}`, 114 ); 115 } catch (error) { 116 throw new Error('[Hermes] Failed to expand Hermes tarball.'); 117 } 118} 119 120function copyBuildScripts() { 121 if (!fs.existsSync(SDKS_DIR)) { 122 throw new Error( 123 '[Hermes] Failed to copy Hermes build scripts, no SDKs directory found.', 124 ); 125 } 126 127 if (!fs.existsSync(HERMES_DIR)) { 128 fs.mkdirSync(path.join(HERMES_DIR, 'utils'), {recursive: true}); 129 } 130 131 fs.copyFileSync( 132 path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-apple-framework.sh'), 133 path.join(HERMES_DIR, 'utils', 'build-apple-framework.sh'), 134 ); 135 fs.copyFileSync( 136 path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-ios-framework.sh'), 137 path.join(HERMES_DIR, 'utils', 'build-ios-framework.sh'), 138 ); 139 fs.copyFileSync( 140 path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-mac-framework.sh'), 141 path.join(HERMES_DIR, 'utils', 'build-mac-framework.sh'), 142 ); 143} 144 145function copyPodSpec() { 146 if (!fs.existsSync(SDKS_DIR)) { 147 throw new Error( 148 '[Hermes] Failed to copy Hermes Podspec, no SDKs directory found.', 149 ); 150 } 151 152 if (!fs.existsSync(HERMES_DIR)) { 153 fs.mkdirSync(HERMES_DIR, {recursive: true}); 154 } 155 fs.copyFileSync( 156 path.join(SDKS_DIR, 'hermes-engine', 'hermes-engine.podspec'), 157 path.join(HERMES_DIR, 'hermes-engine.podspec'), 158 ); 159} 160 161function isTestingAgainstLocalHermesTarball() { 162 return 'HERMES_ENGINE_TARBALL_PATH' in process.env; 163} 164 165function isOnAReactNativeReleaseBranch() { 166 try { 167 let currentBranch = execSync('git rev-parse --abbrev-ref HEAD') 168 .toString() 169 .trim(); 170 let currentRemote = execSync('git config --get remote.origin.url') 171 .toString() 172 .trim(); 173 return ( 174 currentBranch.endsWith('-stable') && 175 currentRemote.endsWith('facebook/react-native.git') 176 ); 177 } catch (error) { 178 // If not inside a git repo, we're going to fail here and return. 179 return false; 180 } 181} 182 183function isOnAReactNativeReleaseTag() { 184 try { 185 // If on a named tag, this method will return the tag name. 186 // If not, it will throw as the return code is not 0. 187 execSync('git describe --exact-match HEAD', {stdio: 'ignore'}); 188 } catch (error) { 189 return false; 190 } 191 let currentRemote = execSync('git config --get remote.origin.url') 192 .toString() 193 .trim(); 194 return currentRemote.endsWith('facebook/react-native.git'); 195} 196 197function isRequestingLatestCommitFromHermesMainBranch() { 198 const hermesTag = readHermesTag(); 199 return hermesTag === DEFAULT_HERMES_TAG; 200} 201 202function shouldBuildHermesFromSource() { 203 return ( 204 !isTestingAgainstLocalHermesTarball() && 205 (isOnAReactNativeReleaseBranch() || 206 isOnAReactNativeReleaseTag() || 207 isRequestingLatestCommitFromHermesMainBranch()) 208 ); 209} 210 211function shouldUsePrebuiltHermesC(os) { 212 if (os === 'macos') { 213 return fs.existsSync(MACOS_HERMESC_PATH); 214 } 215 216 return false; 217} 218 219function configureMakeForPrebuiltHermesC() { 220 const IMPORT_HERMESC_TEMPLATE = `add_executable(native-hermesc IMPORTED) 221set_target_properties(native-hermesc PROPERTIES 222 IMPORTED_LOCATION "${MACOS_HERMESC_PATH}" 223 )`; 224 225 try { 226 fs.mkdirSync(MACOS_BIN_DIR, {recursive: true}); 227 fs.writeFileSync(MACOS_IMPORT_HERMESC_PATH, IMPORT_HERMESC_TEMPLATE); 228 } catch (error) { 229 console.warn( 230 `[Hermes] Re-compiling hermesc. Unable to configure make: ${error}`, 231 ); 232 } 233} 234 235module.exports = { 236 configureMakeForPrebuiltHermesC, 237 copyBuildScripts, 238 copyPodSpec, 239 downloadHermesTarball, 240 expandHermesTarball, 241 getHermesTagSHA, 242 readHermesTag, 243 setHermesTag, 244 shouldBuildHermesFromSource, 245 shouldUsePrebuiltHermesC, 246}; 247