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 os = require('os'); 14const path = require('path'); 15const {execSync, spawnSync} = require('child_process'); 16 17const SDKS_DIR = path.normalize(path.join(__dirname, '..', '..', 'sdks')); 18const HERMES_DIR = path.join(SDKS_DIR, 'hermes'); 19const HERMES_TAG_FILE_PATH = path.join(SDKS_DIR, '.hermesversion'); 20const HERMES_SOURCE_TARBALL_BASE_URL = 21 'https://github.com/facebook/hermes/tarball/'; 22const HERMES_TARBALL_DOWNLOAD_DIR = path.join(SDKS_DIR, 'download'); 23const MACOS_BIN_DIR = path.join(SDKS_DIR, 'hermesc', 'osx-bin'); 24const MACOS_HERMESC_PATH = path.join(MACOS_BIN_DIR, 'hermesc'); 25const MACOS_IMPORT_HERMESC_PATH = path.join( 26 MACOS_BIN_DIR, 27 'ImportHermesc.cmake', 28); 29 30/** 31 * Delegate execution to the supplied command. 32 * 33 * @param command Path to the command. 34 * @param args Array of arguments pass to the command. 35 * @param options child process options. 36 */ 37function delegateSync(command, args, options) { 38 return spawnSync(command, args, {stdio: 'inherit', ...options}); 39} 40 41function readHermesTag() { 42 if (fs.existsSync(HERMES_TAG_FILE_PATH)) { 43 const data = fs 44 .readFileSync(HERMES_TAG_FILE_PATH, { 45 encoding: 'utf8', 46 flag: 'r', 47 }) 48 .trim(); 49 50 if (data.length > 0) { 51 return data; 52 } else { 53 throw new Error('[Hermes] .hermesversion file is empty.'); 54 } 55 } 56 57 return 'main'; 58} 59 60function setHermesTag(hermesTag) { 61 if (readHermesTag() === hermesTag) { 62 // No need to update. 63 return; 64 } 65 66 if (!fs.existsSync(SDKS_DIR)) { 67 fs.mkdirSync(SDKS_DIR, {recursive: true}); 68 } 69 fs.writeFileSync(HERMES_TAG_FILE_PATH, hermesTag.trim()); 70 console.log('Hermes tag has been updated. Please commit your changes.'); 71} 72 73function getHermesTagSHA(hermesTag) { 74 return execSync( 75 `git ls-remote https://github.com/facebook/hermes ${hermesTag} | cut -f 1`, 76 ) 77 .toString() 78 .trim(); 79} 80 81function getHermesTarballDownloadPath(hermesTag) { 82 const hermesTagSHA = getHermesTagSHA(hermesTag); 83 return path.join(HERMES_TARBALL_DOWNLOAD_DIR, `hermes-${hermesTagSHA}.tgz`); 84} 85 86function downloadHermesSourceTarball() { 87 const hermesTag = readHermesTag(); 88 const hermesTagSHA = getHermesTagSHA(hermesTag); 89 const hermesTarballDownloadPath = getHermesTarballDownloadPath(hermesTag); 90 let hermesTarballUrl = HERMES_SOURCE_TARBALL_BASE_URL + hermesTag; 91 92 if (fs.existsSync(hermesTarballDownloadPath)) { 93 return; 94 } 95 96 if (!fs.existsSync(HERMES_TARBALL_DOWNLOAD_DIR)) { 97 fs.mkdirSync(HERMES_TARBALL_DOWNLOAD_DIR, {recursive: true}); 98 } 99 100 console.info( 101 `[Hermes] Downloading Hermes source code for commit ${hermesTagSHA}`, 102 ); 103 try { 104 delegateSync('curl', [hermesTarballUrl, '-Lo', hermesTarballDownloadPath]); 105 } catch (error) { 106 throw new Error(`[Hermes] Failed to download Hermes tarball. ${error}`); 107 } 108} 109 110function expandHermesSourceTarball() { 111 const hermesTag = readHermesTag(); 112 const hermesTagSHA = getHermesTagSHA(hermesTag); 113 const hermesTarballDownloadPath = getHermesTarballDownloadPath(hermesTag); 114 115 if (!fs.existsSync(hermesTarballDownloadPath)) { 116 throw new Error('[Hermes] Could not locate Hermes tarball.'); 117 } 118 119 if (!fs.existsSync(HERMES_DIR)) { 120 fs.mkdirSync(HERMES_DIR, {recursive: true}); 121 } 122 console.info(`[Hermes] Expanding Hermes tarball for commit ${hermesTagSHA}`); 123 try { 124 delegateSync('tar', [ 125 '-zxf', 126 hermesTarballDownloadPath, 127 '--strip-components=1', 128 '--directory', 129 HERMES_DIR, 130 ]); 131 } catch (error) { 132 throw new Error('[Hermes] Failed to expand Hermes tarball.'); 133 } 134} 135 136function copyBuildScripts() { 137 if (!fs.existsSync(SDKS_DIR)) { 138 throw new Error( 139 '[Hermes] Failed to copy Hermes build scripts, no SDKs directory found.', 140 ); 141 } 142 143 if (!fs.existsSync(HERMES_DIR)) { 144 fs.mkdirSync(path.join(HERMES_DIR, 'utils'), {recursive: true}); 145 } 146 147 fs.copyFileSync( 148 path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-apple-framework.sh'), 149 path.join(HERMES_DIR, 'utils', 'build-apple-framework.sh'), 150 ); 151 fs.copyFileSync( 152 path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-ios-framework.sh'), 153 path.join(HERMES_DIR, 'utils', 'build-ios-framework.sh'), 154 ); 155 fs.copyFileSync( 156 path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-mac-framework.sh'), 157 path.join(HERMES_DIR, 'utils', 'build-mac-framework.sh'), 158 ); 159} 160 161function copyPodSpec() { 162 if (!fs.existsSync(SDKS_DIR)) { 163 throw new Error( 164 '[Hermes] Failed to copy Hermes Podspec, no SDKs directory found.', 165 ); 166 } 167 168 if (!fs.existsSync(HERMES_DIR)) { 169 fs.mkdirSync(HERMES_DIR, {recursive: true}); 170 } 171 const podspec = 'hermes-engine.podspec'; 172 fs.copyFileSync( 173 path.join(SDKS_DIR, 'hermes-engine', podspec), 174 path.join(HERMES_DIR, podspec), 175 ); 176 const utils = 'hermes-utils.rb'; 177 fs.copyFileSync( 178 path.join(SDKS_DIR, 'hermes-engine', utils), 179 path.join(HERMES_DIR, utils), 180 ); 181} 182 183function isTestingAgainstLocalHermesTarball() { 184 return 'HERMES_ENGINE_TARBALL_PATH' in process.env; 185} 186 187function shouldBuildHermesFromSource(isInCI) { 188 return !isTestingAgainstLocalHermesTarball() && isInCI; 189} 190 191function shouldUsePrebuiltHermesC(platform) { 192 if (platform === 'macos') { 193 return fs.existsSync(MACOS_HERMESC_PATH); 194 } 195 196 return false; 197} 198 199function configureMakeForPrebuiltHermesC() { 200 const IMPORT_HERMESC_TEMPLATE = `add_executable(native-hermesc IMPORTED) 201set_target_properties(native-hermesc PROPERTIES 202 IMPORTED_LOCATION "${MACOS_HERMESC_PATH}" 203 )`; 204 205 try { 206 fs.mkdirSync(MACOS_BIN_DIR, {recursive: true}); 207 fs.writeFileSync(MACOS_IMPORT_HERMESC_PATH, IMPORT_HERMESC_TEMPLATE); 208 } catch (error) { 209 console.warn( 210 `[Hermes] Re-compiling hermesc. Unable to configure make: ${error}`, 211 ); 212 } 213} 214 215function getHermesPrebuiltArtifactsTarballName(buildType) { 216 if (!buildType) { 217 throw Error('Did not specify build type.'); 218 } 219 return `hermes-ios-${buildType.toLowerCase()}.tar.gz`; 220} 221 222/** 223 * Creates a tarball with the contents of the supplied directory. 224 */ 225function createTarballFromDirectory(directory, filename) { 226 const args = ['-C', directory, '-czvf', filename, '.']; 227 delegateSync('tar', args); 228} 229 230function createHermesPrebuiltArtifactsTarball( 231 hermesDir, 232 buildType, 233 tarballOutputDir, 234 excludeDebugSymbols, 235) { 236 validateHermesFrameworksExist(path.join(hermesDir, 'destroot')); 237 238 if (!fs.existsSync(tarballOutputDir)) { 239 fs.mkdirSync(tarballOutputDir, {recursive: true}); 240 } 241 242 let tarballTempDir; 243 try { 244 tarballTempDir = fs.mkdtempSync( 245 path.join(os.tmpdir(), 'hermes-engine-destroot-'), 246 ); 247 248 let args = ['-a']; 249 if (excludeDebugSymbols) { 250 args.push('--exclude=dSYMs/'); 251 args.push('--exclude=*.dSYM/'); 252 } 253 args.push('./destroot'); 254 args.push(tarballTempDir); 255 delegateSync('rsync', args, { 256 cwd: hermesDir, 257 }); 258 if (fs.existsSync(path.join(hermesDir, 'LICENSE'))) { 259 delegateSync('cp', ['LICENSE', tarballTempDir], {cwd: hermesDir}); 260 } 261 } catch (error) { 262 throw new Error(`Failed to copy destroot to tempdir: ${error}`); 263 } 264 265 const tarballFilename = path.join( 266 tarballOutputDir, 267 getHermesPrebuiltArtifactsTarballName(buildType), 268 ); 269 270 try { 271 createTarballFromDirectory(tarballTempDir, tarballFilename); 272 } catch (error) { 273 throw new Error(`[Hermes] Failed to create tarball: ${error}`); 274 } 275 276 if (!fs.existsSync(tarballFilename)) { 277 throw new Error( 278 `Tarball creation failed, could not locate tarball at ${tarballFilename}`, 279 ); 280 } 281 282 return tarballFilename; 283} 284 285function validateHermesFrameworksExist(destrootDir) { 286 if ( 287 !fs.existsSync( 288 path.join(destrootDir, 'Library/Frameworks/macosx/hermes.framework'), 289 ) 290 ) { 291 throw new Error( 292 'Error: Hermes macOS Framework not found. Are you sure Hermes has been built?', 293 ); 294 } 295 if ( 296 !fs.existsSync( 297 path.join(destrootDir, 'Library/Frameworks/universal/hermes.xcframework'), 298 ) 299 ) { 300 throw new Error( 301 'Error: Hermes iOS XCFramework not found. Are you sure Hermes has been built?', 302 ); 303 } 304} 305 306module.exports = { 307 configureMakeForPrebuiltHermesC, 308 copyBuildScripts, 309 copyPodSpec, 310 createHermesPrebuiltArtifactsTarball, 311 createTarballFromDirectory, 312 downloadHermesSourceTarball, 313 expandHermesSourceTarball, 314 getHermesTagSHA, 315 getHermesTarballDownloadPath, 316 getHermesPrebuiltArtifactsTarballName, 317 readHermesTag, 318 setHermesTag, 319 shouldBuildHermesFromSource, 320 shouldUsePrebuiltHermesC, 321}; 322