diff --git a/packages/react-native/scripts/hermes/__tests__/hermes-utils-test.js b/packages/react-native/scripts/hermes/__tests__/hermes-utils-test.js index f295569ab721..7c7d02186a2a 100644 --- a/packages/react-native/scripts/hermes/__tests__/hermes-utils-test.js +++ b/packages/react-native/scripts/hermes/__tests__/hermes-utils-test.js @@ -22,6 +22,7 @@ const { getHermesTagSHA, getHermesTarballDownloadPath, readHermesTag, + readHermesV1Tag, setHermesTag, shouldUsePrebuiltHermesC, } = require('../hermes-utils'); @@ -31,6 +32,7 @@ const os = require('os'); const hermesTag = 'hermes-2022-04-28-RNv0.69.0-15d07c2edd29a4ea0b8f15ab0588a0c1adb1200f'; +const hermesV1Tag = '250829098.0.0'; const tarballContents = 'dummy string'; const hermescContents = 'dummy string'; const hermesTagSha = '5244f819b2f3949ca94a3a1bf75d54a8ed59d94a'; @@ -182,18 +184,26 @@ describe('hermes-utils', () => { }); describe('setHermesTag', () => { - it('should write tag to .hermesversion file', () => { - setHermesTag(hermesTag); + it('should write tag to .hermesversion file', async () => { + await setHermesTag(hermesTag, hermesV1Tag); expect( fs.readFileSync(path.join(SDKS_DIR, '.hermesversion'), { encoding: 'utf8', flag: 'r', }), ).toEqual(hermesTag); + + expect( + fs.readFileSync(path.join(SDKS_DIR, '.hermesv1version'), { + encoding: 'utf8', + flag: 'r', + }), + ).toEqual(hermesV1Tag); }); - it('should set Hermes tag and read it back', () => { - setHermesTag(hermesTag); + it('should set Hermes tag and read it back', async () => { + await setHermesTag(hermesTag, hermesV1Tag); expect(readHermesTag()).toEqual(hermesTag); + expect(readHermesV1Tag()).toEqual(hermesV1Tag); }); }); diff --git a/packages/react-native/scripts/hermes/bump-hermes-version.js b/packages/react-native/scripts/hermes/bump-hermes-version.js index 3ac8c9711b63..a27aabed40d1 100755 --- a/packages/react-native/scripts/hermes/bump-hermes-version.js +++ b/packages/react-native/scripts/hermes/bump-hermes-version.js @@ -11,6 +11,13 @@ 'use strict'; +const { + updateHermesCompilerVersionInDependencies, + updateHermesRuntimeDependenciesVersions, +} = require('../../../../scripts/releases/utils/hermes-utils'); +const { + getPackageVersionStrByTag, +} = require('../../../../scripts/releases/utils/npm-utils'); const {setHermesTag} = require('./hermes-utils'); // $FlowFixMe[untyped-import] const inquirer = require('inquirer'); @@ -21,28 +28,88 @@ const inquirer = require('inquirer'); const {exit} = require('shelljs'); const yargs = require('yargs'); -let argv = yargs.option('t', { - alias: 'tag', - describe: - 'Hermes release tag to use for this React Native release, ex. hermes-2022-02-21-RNv0.68.0', - required: true, -}).argv; +let argv = yargs + .option('t', { + alias: 'tag', + describe: + 'Hermes release tag to use for this React Native release, ex. hermes-2022-02-21-RNv0.68.0. This tag will be used when building Hermes from source.', + required: true, + }) + .option('s', { + alias: 'v1-tag', + describe: + 'Hermes V1 release tag to use for this React Native release, ex. 250829098.0.0. This tag will be used when building Hermes V1 from source.', + required: true, + }) + .option('h', { + alias: 'hermes-version', + describe: + 'Hermes version to use for this React Native release, ex. 250829098.0.0. This version will be used when consuming Hermes from a prebuilt package.', + required: false, + }) + .option('v', { + alias: 'hermes-v1-version', + describe: + 'Hermes V1 version to use for this React Native release, ex. 250829098.0.0. This version will be used when consuming Hermes V1 from a prebuilt package.', + required: false, + }).argv; async function main() { // $FlowFixMe[prop-missing] const hermesTag = argv.tag; - const {confirmHermesTag} = await inquirer.prompt({ + // $FlowFixMe[prop-missing] + const hermesV1Tag = argv['v1-tag']; + // $FlowFixMe[prop-missing] + let hermesVersion = argv['hermes-version']; + // $FlowFixMe[prop-missing] + let hermesV1Version = argv['hermes-v1-version']; + + if (!hermesVersion) { + console.log( + 'No Hermes version provided. Fetching the latest version from NPM...', + ); + hermesVersion = await getPackageVersionStrByTag( + 'hermes-compiler', + 'latest-v0', + ); + } + + if (!hermesV1Version) { + console.log( + 'No Hermes V1 version provided. Fetching the latest version from NPM...', + ); + hermesV1Version = await getPackageVersionStrByTag( + 'hermes-compiler', + 'latest-v1', + ); + } + + const {confirmHermesVersions} = await inquirer.prompt({ type: 'confirm', - name: 'confirmHermesTag', - message: `Do you want to use the Hermes release tagged "${hermesTag}"?`, + name: 'confirmHermesVersions', + message: `Do you want to use the Hermes version "${hermesVersion}" and Hermes V1 version "${hermesV1Version}" (for prebuilt)?`, }); - if (!confirmHermesTag) { + if (!confirmHermesVersions) { console.log('Aborting.'); return; } - setHermesTag(hermesTag); + const {confirmHermesTags} = await inquirer.prompt({ + type: 'confirm', + name: 'confirmHermesTags', + message: `Do you want to use the Hermes release tagged "${hermesTag}" and Hermes V1 release tagged "${hermesV1Tag}" (for building from source)?`, + }); + + if (!confirmHermesTags) { + console.log('Aborting.'); + return; + } + + await setHermesTag(hermesTag, hermesV1Tag); + + await updateHermesCompilerVersionInDependencies(hermesVersion); + await updateHermesRuntimeDependenciesVersions(hermesVersion, hermesV1Version); } void main().then(() => { diff --git a/packages/react-native/scripts/hermes/hermes-utils.js b/packages/react-native/scripts/hermes/hermes-utils.js index 991a525b52d8..fe4b25d30780 100644 --- a/packages/react-native/scripts/hermes/hermes-utils.js +++ b/packages/react-native/scripts/hermes/hermes-utils.js @@ -12,6 +12,8 @@ const {execSync, spawnSync} = require('child_process'); const fs = require('fs'); +// $FlowFixMe[untyped-import] +const inquirer = require('inquirer'); const os = require('os'); const path = require('path'); @@ -22,6 +24,7 @@ type BuildType = 'dry-run' | 'release' | 'nightly'; const SDKS_DIR = path.normalize(path.join(__dirname, '..', '..', 'sdks')); const HERMES_DIR = path.join(SDKS_DIR, 'hermes'); const HERMES_TAG_FILE_PATH = path.join(SDKS_DIR, '.hermesversion'); +const HERMES_V1_TAG_FILE_PATH = path.join(SDKS_DIR, '.hermesv1version'); const HERMES_SOURCE_TARBALL_BASE_URL = 'https://github.com/facebook/hermes/tarball/'; const HERMES_TARBALL_DOWNLOAD_DIR = path.join(SDKS_DIR, 'download'); @@ -66,17 +69,66 @@ function readHermesTag() /*: string */ { return 'main'; } -function setHermesTag(hermesTag /*: string */) { - if (readHermesTag() === hermesTag) { - // No need to update. - return; +function readHermesV1Tag() /*: string */ { + if (fs.existsSync(HERMES_V1_TAG_FILE_PATH)) { + const data = fs + .readFileSync(HERMES_V1_TAG_FILE_PATH, { + encoding: 'utf8', + flag: 'r', + }) + .trim(); + + if (data.length > 0) { + return data; + } else { + throw new Error('[Hermes] .hermesv1version file is empty.'); + } } + throw new Error('[Hermes] .hermesv1version does not exist.'); +} + +async function updateHermesTag( + tagFile /*: string */, + hermesTag /*: string */, + prompt /*: string */, +) { + if (!fs.existsSync(tagFile)) { + fs.writeFileSync(tagFile, hermesTag.trim()); + } else { + const previousHermesTag = fs.readFileSync(tagFile, { + encoding: 'utf8', + flag: 'r', + }); + + if (previousHermesTag.trim() !== hermesTag.trim()) { + const {confirmHermesTag} = await inquirer.prompt({ + type: 'confirm', + name: 'confirmHermesTag', + message: `Do you want to use updtate release tag for ${prompt} from "${previousHermesTag}" to "${hermesTag}"?`, + }); + + if (confirmHermesTag) { + fs.writeFileSync(tagFile, hermesTag.trim()); + } else { + console.log(`[${prompt}] .hermesversion file is unchanged.`); + } + } else { + console.log(`[${prompt}] .hermesversion file is unchanged.`); + } + } +} + +async function setHermesTag( + hermesTag /*: string */, + hermesV1Tag /*: string */, +) { if (!fs.existsSync(SDKS_DIR)) { fs.mkdirSync(SDKS_DIR, {recursive: true}); } - fs.writeFileSync(HERMES_TAG_FILE_PATH, hermesTag.trim()); - console.log('Hermes tag has been updated. Please commit your changes.'); + + await updateHermesTag(HERMES_TAG_FILE_PATH, hermesTag, 'Hermes'); + await updateHermesTag(HERMES_V1_TAG_FILE_PATH, hermesV1Tag, 'Hermes V1'); } function getHermesTagSHA(hermesTag /*: string */) /*: string */ { @@ -337,6 +389,7 @@ module.exports = { getHermesTarballDownloadPath, getHermesPrebuiltArtifactsTarballName, readHermesTag, + readHermesV1Tag, setHermesTag, shouldBuildHermesFromSource, shouldUsePrebuiltHermesC, diff --git a/packages/react-native/sdks/hermes-engine/version.properties b/packages/react-native/sdks/hermes-engine/version.properties index 0f5449c49f60..81629a9921cc 100644 --- a/packages/react-native/sdks/hermes-engine/version.properties +++ b/packages/react-native/sdks/hermes-engine/version.properties @@ -1 +1,2 @@ +HERMES_VERSION_NAME=1000.0.0 HERMES_V1_VERSION_NAME=250829098.0.1 diff --git a/scripts/releases-ci/__tests__/publish-npm-test.js b/scripts/releases-ci/__tests__/publish-npm-test.js index 28f711b17ab1..a494dc31a57e 100644 --- a/scripts/releases-ci/__tests__/publish-npm-test.js +++ b/scripts/releases-ci/__tests__/publish-npm-test.js @@ -20,6 +20,7 @@ const publishPackageMock = jest.fn(); const getNpmInfoMock = jest.fn(); const generateAndroidArtifactsMock = jest.fn(); const getPackagesMock = jest.fn(); +const updateHermesVersionsToNightlyMock = jest.fn(); const {REPO_ROOT} = require('../../shared/consts'); const {publishNpm} = require('../publish-npm'); @@ -53,6 +54,9 @@ describe('publish-npm', () => { ...jest.requireActual('../../releases/utils/npm-utils'), publishPackage: publishPackageMock, getNpmInfo: getNpmInfoMock, + })) + .mock('../../releases/utils/hermes-utils', () => ({ + updateHermesVersionsToNightly: updateHermesVersionsToNightlyMock, })); }); @@ -98,6 +102,7 @@ describe('publish-npm', () => { await publishNpm('dry-run'); + expect(updateHermesVersionsToNightlyMock).toHaveBeenCalled(); expect(setVersionMock).not.toBeCalled(); expect(updateReactNativeArtifactsMock).toBeCalledWith(version, 'dry-run'); @@ -155,6 +160,8 @@ describe('publish-npm', () => { // Generate Android artifacts is now delegate to build_android entirely expect(generateAndroidArtifactsMock).not.toHaveBeenCalled(); + expect(updateHermesVersionsToNightlyMock).toHaveBeenCalled(); + expect(publishPackageMock.mock.calls).toEqual([ [ 'path/to/monorepo/pkg-a', diff --git a/scripts/releases-ci/publish-npm.js b/scripts/releases-ci/publish-npm.js index 8d5b8ff2e1f1..75193c1f4ca7 100755 --- a/scripts/releases-ci/publish-npm.js +++ b/scripts/releases-ci/publish-npm.js @@ -18,6 +18,9 @@ const { updateReactNativeArtifacts, } = require('../releases/set-rn-artifacts-version'); const {setVersion} = require('../releases/set-version'); +const { + updateHermesVersionsToNightly, +} = require('../releases/utils/hermes-utils'); const {getNpmInfo, publishPackage} = require('../releases/utils/npm-utils'); const { publishAndroidArtifactsToMaven, @@ -96,6 +99,9 @@ async function publishNpm(buildType /*: BuildType */) /*: Promise */ { // For stable releases, ci job `prepare_package_for_release` handles this if (buildType === 'nightly') { + // Set hermes versions to latest available + await updateHermesVersionsToNightly(); + // Set same version for all monorepo packages await setVersion(version); await publishMonorepoPackages(tag); @@ -113,6 +119,8 @@ async function publishNpm(buildType /*: BuildType */) /*: Promise */ { const packageJson = JSON.parse(packageJsonContent); if (packageJson.version === '1000.0.0') { + // Set hermes versions to latest available + await updateHermesVersionsToNightly(); await updateReactNativeArtifacts(version, buildType); } } diff --git a/scripts/releases/set-rn-artifacts-version.js b/scripts/releases/set-rn-artifacts-version.js index 9341d5951062..bf8671237180 100755 --- a/scripts/releases/set-rn-artifacts-version.js +++ b/scripts/releases/set-rn-artifacts-version.js @@ -155,8 +155,6 @@ function updateTestFiles( async function updateGradleFile(version /*: string */) /*: Promise */ { const contents = await fs.readFile(GRADLE_FILE_PATH, 'utf-8'); - // TODO: T231755027 set HERMES_VERSION_NAME - return fs.writeFile( GRADLE_FILE_PATH, contents.replace(/^VERSION_NAME=.*/, `VERSION_NAME=${version}`), diff --git a/scripts/releases/utils/hermes-utils.js b/scripts/releases/utils/hermes-utils.js new file mode 100644 index 000000000000..37b57893663c --- /dev/null +++ b/scripts/releases/utils/hermes-utils.js @@ -0,0 +1,99 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +const {REACT_NATIVE_PACKAGE_DIR} = require('../../shared/consts'); +const { + getPackages, + getWorkspaceRoot, + updatePackageJson, +} = require('../../shared/monorepoUtils'); +const {getPackageVersionStrByTag} = require('./npm-utils'); +const {promises: fs} = require('fs'); +const path = require('path'); + +const MAVEN_VERSIONS_FILE_PATH = path.join( + REACT_NATIVE_PACKAGE_DIR, + 'sdks', + 'hermes-engine', + 'version.properties', +); + +async function getLatestHermesNightlyVersion() /*: Promise<{ + compilerVersion: string, + runtimeVersion: string, + runtimeV1Version: string, +}> */ { + // fetch the latest commitly version of hermes v0 + const compilerVersion = await getPackageVersionStrByTag( + 'hermes-compiler', + 'nightly', + ); + // fetch the latest version of hermes v1 + const compilerV1Version = await getPackageVersionStrByTag( + 'hermes-compiler', + 'latest-v1', + ); + + return { + compilerVersion, + // runtime version should match the compiler version + runtimeVersion: compilerVersion, + runtimeV1Version: compilerV1Version, + }; +} + +async function updateHermesCompilerVersionInDependencies( + hermesVersion /*: string */, +) /*: Promise */ { + const packages = await getPackages({ + includePrivate: true, + includeReactNative: true, + }); + + const packagesToUpdate = [ + await getWorkspaceRoot(), + ...Object.values(packages), + ]; + + // Update generated files in packages/react-native/ + await Promise.all( + packagesToUpdate.map(pkg => + updatePackageJson(pkg, {'hermes-compiler': hermesVersion}), + ), + ); +} + +async function updateHermesRuntimeDependenciesVersions( + hermesVersion /*: string */, + hermesV1Version /*: string */, +) /*: Promise */ { + const newVersionsFile = + `HERMES_VERSION_NAME=${hermesVersion}\n` + + `HERMES_V1_VERSION_NAME=${hermesV1Version}`; + + await fs.writeFile(MAVEN_VERSIONS_FILE_PATH, newVersionsFile.trim() + '\n'); +} + +async function updateHermesVersionsToNightly() { + const hermesVersions = await getLatestHermesNightlyVersion(); + await updateHermesCompilerVersionInDependencies( + hermesVersions.compilerVersion, + ); + await updateHermesRuntimeDependenciesVersions( + hermesVersions.runtimeVersion, + hermesVersions.runtimeV1Version, + ); +} + +module.exports = { + updateHermesVersionsToNightly, + updateHermesCompilerVersionInDependencies, + updateHermesRuntimeDependenciesVersions, +}; diff --git a/scripts/releases/utils/npm-utils.js b/scripts/releases/utils/npm-utils.js index 97889e85d540..fa721ad59054 100644 --- a/scripts/releases/utils/npm-utils.js +++ b/scripts/releases/utils/npm-utils.js @@ -174,4 +174,5 @@ function getPackageVersionStrByTag( module.exports = { getNpmInfo, publishPackage, + getPackageVersionStrByTag, };