diff --git a/repo-config/package.json b/repo-config/package.json index ae56af9bbc495c..b16cb4e29627c9 100644 --- a/repo-config/package.json +++ b/repo-config/package.json @@ -40,6 +40,7 @@ "jest-junit": "^10.0.0", "jscodeshift": "^0.13.1", "metro-babel-register": "0.70.3", + "metro-memory-fs": "0.70.2", "mkdirp": "^0.5.1", "prettier": "^2.4.1", "react": "18.0.0", diff --git a/scripts/__tests__/hermes-utils-test.js b/scripts/__tests__/hermes-utils-test.js new file mode 100644 index 00000000000000..6044afab155b59 --- /dev/null +++ b/scripts/__tests__/hermes-utils-test.js @@ -0,0 +1,242 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import * as path from 'path'; + +const { + copyBuildScripts, + downloadHermesTarball, + expandHermesTarball, + getHermesTagSHA, + readHermesTag, + setHermesTag, +} = require('../hermes/hermes-utils'); + +const hermesTag = + 'hermes-2022-04-28-RNv0.69.0-15d07c2edd29a4ea0b8f15ab0588a0c1adb1200f'; +const tarballContents = 'dummy string'; +const hermesTagSha = '5244f819b2f3949ca94a3a1bf75d54a8ed59d94a'; + +const ROOT_DIR = path.normalize(path.join(__dirname, '..', '..')); +const SDKS_DIR = path.join(ROOT_DIR, 'sdks'); + +let execCalls; +let fs; +let shelljs; + +jest.mock('shelljs', () => ({ + echo: jest.fn(), + exec: jest.fn(command => { + if (command.startsWith('curl')) { + fs.writeFileSync( + path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), + tarballContents, + ); + execCalls.curl = true; + return {code: 0}; + } + + if (command.startsWith('git')) { + execCalls.git = true; + return hermesTagSha + '\n'; + } + + if (command.startsWith('tar')) { + fs.mkdirSync(path.join(SDKS_DIR, 'hermes', 'utils'), { + recursive: true, + }); + fs.writeFileSync(path.join(SDKS_DIR, 'hermes', `package.json`), '{}'); + execCalls.tar = true; + return {code: 0}; + } + }), + exit: jest.fn(), +})); + +function populateMockFilesystem() { + fs.mkdirSync(path.join(SDKS_DIR, 'hermes-engine', 'utils'), { + recursive: true, + }); + fs.writeFileSync( + path.join( + ROOT_DIR, + 'sdks', + 'hermes-engine', + 'utils', + 'build-apple-framework.sh', + ), + 'Dummy file', + ); + fs.writeFileSync( + path.join( + ROOT_DIR, + 'sdks', + 'hermes-engine', + 'utils', + 'build-ios-framework.sh', + ), + 'Dummy file', + ); + fs.writeFileSync( + path.join( + ROOT_DIR, + 'sdks', + 'hermes-engine', + 'utils', + 'build-mac-framework.sh', + ), + 'Dummy file', + ); + fs.writeFileSync( + path.join(SDKS_DIR, 'hermes-engine', 'hermes-engine.podspec'), + 'Dummy file', + ); +} + +describe('hermes-utils', () => { + beforeEach(() => { + jest.resetModules(); + + jest.mock('fs', () => new (require('metro-memory-fs'))()); + fs = require('fs'); + fs.reset(); + + populateMockFilesystem(); + + execCalls = Object.create(null); + shelljs = require('shelljs'); + }); + describe('readHermesTag', () => { + it('should return main if .hermesversion does not exist', () => { + expect(readHermesTag()).toEqual('main'); + }); + it('should return tag from .hermesversion if file exists', () => { + fs.writeFileSync(path.join(SDKS_DIR, '.hermesversion'), hermesTag); + expect(readHermesTag()).toEqual(hermesTag); + }); + }); + describe('setHermesTag', () => { + it('should write tag to .hermesversion file', () => { + setHermesTag(hermesTag); + expect( + fs.readFileSync(path.join(SDKS_DIR, '.hermesversion'), { + encoding: 'utf8', + flag: 'r', + }), + ).toEqual(hermesTag); + }); + it('should set Hermes tag and read it back', () => { + setHermesTag(hermesTag); + expect(readHermesTag()).toEqual(hermesTag); + }); + }); + describe('getHermesTagSHA', () => { + it('should return trimmed commit SHA for Hermes tag', () => { + expect(getHermesTagSHA(hermesTag)).toEqual(hermesTagSha); + expect(execCalls.git).toBeTruthy(); + }); + }); + describe('downloadHermesTarball', () => { + it('should download Hermes tarball to download dir', () => { + downloadHermesTarball(); + expect(execCalls.curl).toBeTruthy(); + expect( + fs.readFileSync( + path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ).toEqual(tarballContents); + }); + it('should not re-download Hermes tarball if tarball exists', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'download'), {recursive: true}); + fs.writeFileSync( + path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), + tarballContents, + ); + + downloadHermesTarball(); + expect(execCalls.curl).toBeUndefined(); + }); + }); + describe('expandHermesTarball', () => { + it('should expand Hermes tarball to Hermes source dir', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'download'), {recursive: true}); + fs.writeFileSync( + path.join(SDKS_DIR, 'download', `hermes-${hermesTagSha}.tgz`), + tarballContents, + ); + expect(fs.existsSync(path.join(SDKS_DIR, 'hermes'))).toBeFalsy(); + expandHermesTarball(); + expect(execCalls.tar).toBe(true); + expect(fs.existsSync(path.join(SDKS_DIR, 'hermes'))).toBeTruthy(); + }); + it('should fail if Hermes tarball does not exist', () => { + expandHermesTarball(); + expect(execCalls.tar).toBeUndefined(); + expect(shelljs.exit.mock.calls.length).toBeGreaterThan(0); + }); + }); + describe('copyBuildScripts', () => { + it('should copy React Native Hermes build scripts to Hermes source directory', () => { + fs.mkdirSync(path.join(SDKS_DIR, 'hermes', 'utils'), { + recursive: true, + }); + copyBuildScripts(); + expect( + fs.readFileSync( + path.join( + ROOT_DIR, + 'sdks', + 'hermes', + 'utils', + 'build-mac-framework.sh', + ), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ).toEqual( + fs.readFileSync( + path.join( + ROOT_DIR, + 'sdks', + 'hermes-engine', + 'utils', + 'build-mac-framework.sh', + ), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ); + expect( + fs.readFileSync( + path.join(SDKS_DIR, 'hermes', 'hermes-engine.podspec'), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ).toEqual( + fs.readFileSync( + path.join(SDKS_DIR, 'hermes-engine', 'hermes-engine.podspec'), + { + encoding: 'utf8', + flag: 'r', + }, + ), + ); + }); + }); +}); diff --git a/scripts/hermes/hermes-utils.js b/scripts/hermes/hermes-utils.js index 5729a3ec83ab7a..1cbbc7b979e8dc 100644 --- a/scripts/hermes/hermes-utils.js +++ b/scripts/hermes/hermes-utils.js @@ -66,7 +66,7 @@ function getHermesTagSHA(hermesTag) { function getHermesTarballDownloadPath(hermesTag) { const hermesTagSHA = getHermesTagSHA(hermesTag); - return `${HERMES_TARBALL_DOWNLOAD_DIR}/hermes-${hermesTagSHA}.tgz`; + return path.join(HERMES_TARBALL_DOWNLOAD_DIR, `hermes-${hermesTagSHA}.tgz`); } function downloadHermesTarball() { @@ -117,21 +117,29 @@ function expandHermesTarball() { } function copyBuildScripts() { + if (!fs.existsSync(HERMES_DIR)) { + echo( + '[Hermes] Failed to copy Hermes build scripts, no Hermes source directory found.', + ); + exit(1); + return; + } + fs.copyFileSync( - `${SDKS_DIR}/hermes-engine/hermes-engine.podspec`, - `${HERMES_DIR}/hermes-engine.podspec`, + path.join(SDKS_DIR, 'hermes-engine', 'hermes-engine.podspec'), + path.join(HERMES_DIR, 'hermes-engine.podspec'), ); fs.copyFileSync( - `${SDKS_DIR}/hermes-engine/utils/build-apple-framework.sh`, - `${HERMES_DIR}/utils/build-apple-framework.sh`, + path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-apple-framework.sh'), + path.join(HERMES_DIR, 'utils', 'build-apple-framework.sh'), ); fs.copyFileSync( - `${SDKS_DIR}/hermes-engine/utils/build-ios-framework.sh`, - `${HERMES_DIR}/utils/build-ios-framework.sh`, + path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-ios-framework.sh'), + path.join(HERMES_DIR, 'utils', 'build-ios-framework.sh'), ); fs.copyFileSync( - `${SDKS_DIR}/hermes-engine/utils/build-mac-framework.sh`, - `${HERMES_DIR}/utils/build-mac-framework.sh`, + path.join(SDKS_DIR, 'hermes-engine', 'utils', 'build-mac-framework.sh'), + path.join(HERMES_DIR, 'utils', 'build-mac-framework.sh'), ); } diff --git a/yarn.lock b/yarn.lock index 7a8c6fcf0f1ab1..806d9d039b5501 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4973,6 +4973,11 @@ metro-inspector-proxy@0.70.3: ws "^7.5.1" yargs "^15.3.1" +metro-memory-fs@0.70.2: + version "0.70.2" + resolved "https://registry.yarnpkg.com/metro-memory-fs/-/metro-memory-fs-0.70.2.tgz#abf175204e37aae7f4eec712a24644af950a4b16" + integrity sha512-fLMvoh4vkYRi5SvDDgTJSOCt5PIGL1nhvcxrKSBmE9UZvhjPFMmkBHVrWuVUpmamzP0PCwCWTcabFlCkl4HvUw== + metro-minify-uglify@0.70.3: version "0.70.3" resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.70.3.tgz#2f28129ca5b8ef958f3e3fcf004c3707c7732e1e"