download hermes-engine when the configuration change (#37850)

Summary:
Currently, we ask users to reinstall the pods using the `PRODUCTION` flag when they want to either profile their app or prepare a release.

This way of working with the Release mode is not standard. One of the reason why we introduced it was to provide a different binary for Hermes and reinstalling the pods was the quickest way.

With this change, we are deferring the decision on when Hermes should be installed for apps to the moment where the app is actually build by the system.

These changes are not applied to Nightlies, when a specific tarball is passed to the cocoapods using the `HERMES_ENGINE_TARBALL_PATH` env var, and when hermes is built from source as in these scenarios we are usually not interested in building for Release.

The system is also smart enough not to redownload the tarball if the configuration does not change. It assumes that the default configuration when the pods are installed for the first time is Debug.

## Changelog:

[IOS] [CHANGED] - Download the right `hermes-engine` configuration at build time.

Pull Request resolved: https://github.com/facebook/react-native/pull/37850

Test Plan:
- CircleCI green for the Release template jobs
- Tested locally modifying the `hermes-utils` to force specific versions.
- Tested locally with RNTestProject

Reviewed By: dmytrorykun

Differential Revision: D46687390

Pulled By: cipolleschi

fbshipit-source-id: 375406e0ab351a5d1f5d5146e724f5ed0cd77949
This commit is contained in:
Riccardo Cipolleschi
2023-06-14 10:10:16 -07:00
committed by Facebook GitHub Bot
parent b3cc19ce64
commit 332be0f0c8
7 changed files with 212 additions and 71 deletions
+1
View File
@@ -21,6 +21,7 @@ DerivedData
*.xcuserstate
project.xcworkspace
**/.xcode.env.local
/poackages/react-native/sdks/downloads/
# Gradle
/build/
@@ -178,21 +178,26 @@ class UtilsTests < Test::Unit::TestCase
# Test - Set GCC Preprocessor Definition for React-hermes #
# ======================================================== #
def test_SetGCCPreprocessorDefinitionForReactHermes_itSetsThePreprocessorForDebug
def test_SetGCCPreprocessorDefinitionForHermes_itSetsThePreprocessorForDebug
# Arrange
react_hermes_name = "React-hermes"
react_core_name = "React-Core"
react_hermes_debug_config = BuildConfigurationMock.new("Debug")
react_hermes_release_config = BuildConfigurationMock.new("Release")
react_core_debug_config = BuildConfigurationMock.new("Debug")
react_core_release_config = BuildConfigurationMock.new("Release")
react_hermes_target = TargetMock.new(react_hermes_name, [react_hermes_debug_config, react_hermes_release_config])
react_core_target = TargetMock.new(react_core_name, [react_core_debug_config, react_core_release_config])
hermes_engine_name = "hermes-engine"
react_hermes_debug_config = BuildConfigurationMock.new("Debug")
react_hermes_release_config = BuildConfigurationMock.new("Release")
react_core_debug_config = BuildConfigurationMock.new("Debug")
react_core_release_config = BuildConfigurationMock.new("Release")
hermes_engine_debug_config = BuildConfigurationMock.new("Debug")
hermes_engine_release_config = BuildConfigurationMock.new("Release")
react_hermes_target = TargetMock.new(react_hermes_name, [react_hermes_debug_config, react_hermes_release_config])
react_core_target = TargetMock.new(react_core_name, [react_core_debug_config, react_core_release_config])
hermes_engine_target = TargetMock.new(hermes_engine_name, [hermes_engine_debug_config, hermes_engine_release_config])
installer = InstallerMock.new(
:pod_target_installation_results => {
react_hermes_name => TargetInstallationResultMock.new(react_hermes_target, react_hermes_target),
react_core_name => TargetInstallationResultMock.new(react_core_target, react_core_target),
hermes_engine_name => TargetInstallationResultMock.new(hermes_engine_target, hermes_engine_target),
}
)
@@ -201,11 +206,13 @@ class UtilsTests < Test::Unit::TestCase
# Assert
build_setting = "GCC_PREPROCESSOR_DEFINITIONS"
expected_value = "HERMES_ENABLE_DEBUGGER=1"
expected_value = "$(inherited) HERMES_ENABLE_DEBUGGER=1"
assert_equal(expected_value, react_hermes_debug_config.build_settings[build_setting])
assert_nil(react_hermes_release_config.build_settings[build_setting])
assert_nil(react_core_debug_config.build_settings[build_setting])
assert_nil(react_core_release_config.build_settings[build_setting])
assert_equal(expected_value, hermes_engine_debug_config.build_settings[build_setting])
assert_nil(hermes_engine_release_config.build_settings[build_setting])
end
# ============================ #
@@ -41,6 +41,7 @@ class ReactNativePodsUtils
def self.set_gcc_preprocessor_definition_for_React_hermes(installer)
self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", "Debug")
self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "hermes-engine", "Debug")
end
def self.turn_off_resource_bundle_react_core(installer)
@@ -153,7 +154,8 @@ class ReactNativePodsUtils
if pod_name.to_s == target_pod_name
target_installation_result.native_target.build_configurations.each do |config|
if configuration == nil || (configuration != nil && configuration == config.name)
config.build_settings[settings_name] = settings_value
config.build_settings[settings_name] ||= '$(inherited) '
config.build_settings[settings_name] << settings_value
end
end
end
@@ -8,9 +8,6 @@ require_relative "./hermes-utils.rb"
react_native_path = File.join(__dir__, "..", "..")
# Whether Hermes is built for Release or Debug is determined by the PRODUCTION envvar.
build_type = ENV['PRODUCTION'] == "1" ? :release : :debug
# package.json
package = JSON.parse(File.read(File.join(react_native_path, "package.json")))
version = package['version']
@@ -23,7 +20,7 @@ git = "https://github.com/facebook/hermes.git"
abort_if_invalid_tarball_provided!
source = compute_hermes_source(build_from_source, hermestag_file, git, version, build_type, react_native_path)
source = compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path)
Pod::Spec.new do |spec|
spec.name = "hermes-engine"
@@ -42,7 +39,7 @@ Pod::Spec.new do |spec|
spec.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
"CLANG_CXX_LIBRARY" => "compiler-default"
}.merge!(build_type == :debug ? { "GCC_PREPROCESSOR_DEFINITIONS" => "HERMES_ENABLE_DEBUGGER=1" } : {})
}
spec.ios.vendored_frameworks = "destroot/Library/Frameworks/ios/hermes.framework"
spec.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"
@@ -50,7 +47,7 @@ Pod::Spec.new do |spec|
if source[:http] then
spec.subspec 'Pre-built' do |ss|
ss.preserve_paths = ["destroot/bin/*"].concat(build_type == :debug ? ["**/*.{h,c,cpp}"] : [])
ss.preserve_paths = ["destroot/bin/*"].concat(["**/*.{h,c,cpp}"])
ss.source_files = "destroot/include/**/*.h"
ss.exclude_files = ["destroot/include/jsi/jsi/JSIDynamic.{h,cpp}", "destroot/include/jsi/jsi/jsilib-*.{h,cpp}"]
ss.header_mappings_dir = "destroot/include"
@@ -58,6 +55,24 @@ Pod::Spec.new do |spec|
ss.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"
end
# Right now, even reinstalling pods with the PRODUCTION flag turned on, does not change the version of hermes that is downloaded
# To remove the PRODUCTION flag, we want to download the right version of hermes on the flight
# we do so in a pre-build script we invoke from the Xcode build pipeline
# We use this only for Apps created using the template. RNTester and Nightlies should not be used to build for Release.
# We ignore this if we provide a specific tarball: the assumption here is that if you are providing a tarball, is because you want to
# test something specific for that tarball.
if source[:http].include?('https://repo1.maven.org/')
spec.script_phase = {
:name => "[Hermes] Replace Hermes for the right configuration, if needed",
:execution_position => :before_compile,
:script => <<-EOS
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
"$NODE_BINARY" "$REACT_NATIVE_PATH/sdks/hermes-engine/utils/replace_hermes_version.js" -c "$CONFIGURATION" -r "#{version}" -p "$REACT_NATIVE_PATH"
EOS
}
end
elsif source[:git] then
spec.subspec 'Hermes' do |ss|
@@ -96,7 +111,7 @@ Pod::Spec.new do |spec|
{
:name => '[RN] [1] Build Hermesc',
:script => <<-EOS
. ${PODS_ROOT}/../.xcode.env
. "${REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh"
export CMAKE_BINARY=${CMAKE_BINARY:-#{CMAKE_BINARY}}
. ${REACT_NATIVE_PATH}/sdks/hermes-engine/utils/build-hermesc-xcode.sh #{hermesc_path}
EOS
@@ -104,7 +119,7 @@ Pod::Spec.new do |spec|
{
:name => '[RN] [2] Build Hermes',
:script => <<-EOS
. ${PODS_ROOT}/../.xcode.env
. "${REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh"
export CMAKE_BINARY=${CMAKE_BINARY:-#{CMAKE_BINARY}}
. ${REACT_NATIVE_PATH}/sdks/hermes-engine/utils/build-hermes-xcode.sh #{version} #{hermesc_path}/ImportHermesc.cmake
EOS
@@ -30,7 +30,7 @@ end
# - react_native_path: path to react native
#
# Returns: a properly configured source object
def compute_hermes_source(build_from_source, hermestag_file, git, version, build_type, react_native_path)
def compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path)
source = {}
if ENV.has_key?('HERMES_ENGINE_TARBALL_PATH')
@@ -43,8 +43,10 @@ def compute_hermes_source(build_from_source, hermestag_file, git, version, build
else
build_hermes_from_source(source, git)
end
elsif hermes_artifact_exists(release_tarball_url(version, build_type))
use_release_tarball(source, version, build_type)
elsif hermes_artifact_exists(release_tarball_url(version, :debug))
use_release_tarball(source, version, :debug)
download_stable_hermes(react_native_path, version, :debug)
download_stable_hermes(react_native_path, version, :release)
elsif hermes_artifact_exists(nightly_tarball_url(version).gsub("\\", ""))
use_nightly_tarball(source, react_native_path, version)
else
@@ -100,6 +102,25 @@ def putsIfPodPresent(message, level = 'warning')
end
end
def download_stable_hermes(react_native_path, version, configuration)
tarball_url = release_tarball_url(version, configuration)
download_hermes_tarball(react_native_path, tarball_url, version, configuration)
end
def download_hermes_tarball(react_native_path, tarball_url, version, configuration)
destination_folder = "#{react_native_path}/sdks/downloads"
destination_path = configuration == nil ?
"#{destination_folder}/hermes-ios-#{version}.tar.gz" :
"#{destination_folder}/hermes-ios-#{version}-#{configuration}.tar.gz"
unless File.exist?(destination_path)
# Download to a temporary file first so we don't cache incomplete downloads.
tmp_file = "#{destination_folder}/hermes-ios.download"
`mkdir -p "#{destination_folder}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
end
return destination_path
end
# This function downloads the nightly prebuilt version of Hermes based on the passed version
# and save it in the node_module/react_native/sdks/downloads folder
# It then returns the path to the hermes tarball
@@ -110,16 +131,7 @@ end
# Returns: the path to the downloaded Hermes tarball
def download_nightly_hermes(react_native_path, version)
tarball_url = nightly_tarball_url(version)
destination_folder = "#{react_native_path}/sdks/downloads"
destination_path = "#{destination_folder}/hermes-ios-#{version}.tar.gz"
unless File.exist?(destination_path)
# Download to a temporary file first so we don't cache incomplete downloads.
tmp_file = "#{destination_folder}/hermes-ios.download"
`mkdir -p "#{destination_folder}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
end
return destination_path
return download_stable_hermes(react_native_path, tarball_url, version, nil)
end
def nightly_tarball_url(version)
@@ -0,0 +1,104 @@
/**
* 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
*/
'use strict';
const yargs = require('yargs');
const fs = require('fs');
const {execSync} = require('child_process');
const LAST_BUILD_FILENAME = '.last_build_configuration';
function validateBuildConfiguration(configuration) {
if (!['Debug', 'Release'].includes(configuration)) {
throw new Error(`Invalid configuration ${configuration}`);
}
}
function validateVersion(version) {
if (version == null || version === '') {
throw new Error('Version cannot be empty');
}
}
function shouldReplaceHermesConfiguration(configuration) {
const fileExists = fs.existsSync(LAST_BUILD_FILENAME);
if (fileExists) {
console.log(`Found ${LAST_BUILD_FILENAME} file`);
const oldConfiguration = fs.readFileSync(LAST_BUILD_FILENAME).toString();
if (oldConfiguration === configuration) {
console.log('No need to download a new build of Hermes!');
return false;
}
}
// Assumption: if there is no stored last build, we assume that it was build for debug.
if (!fs.existsSync && configuration === 'Debug') {
console.log(
'File does not exists, but Debug configuration. No need to download a new build of Hermes!',
);
return false;
}
return true;
}
function replaceHermesConfiguration(configuration, version, reactNativePath) {
const tarballURLPath = `${reactNativePath}/sdks/downloads/hermes-ios-${version}-${configuration}.tar.gz`;
const finalLocation = 'hermes-engine';
console.log('Preparing the final location');
fs.rmSync(finalLocation, {force: true, recursive: true});
fs.mkdirSync(finalLocation, {recursive: true});
console.log('Extracting the tarball');
execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`);
}
function updateLastBuildConfiguration(configuration) {
fs.writeFileSync(LAST_BUILD_FILENAME, configuration);
}
function main(configuration, version, reactNativePath) {
validateBuildConfiguration(configuration);
validateVersion(version);
if (!shouldReplaceHermesConfiguration(configuration)) {
return;
}
replaceHermesConfiguration(configuration, version, reactNativePath);
updateLastBuildConfiguration(configuration);
console.log('Done replacing hermes-engine');
}
// This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok
const argv = yargs
.option('c', {
alias: 'configuration',
description:
'Configuration to use to download the right Hermes version. Allowed values are "Debug" and "Release".',
})
.option('r', {
alias: 'reactNativeVersion',
description:
'The Version of React Native associated with the Hermes tarball.',
})
.option('p', {
alias: 'reactNativePath',
description: 'The path to the React Native root folder',
})
.usage('Usage: $0 -c Debug -r <version> -p <path/to/react-native>').argv;
const configuration = argv.configuration;
const version = argv.reactNativeVersion;
const reactNativePath = argv.reactNativePath;
main(configuration, version, reactNativePath);
+41 -41
View File
@@ -1242,8 +1242,8 @@ SPEC CHECKSUMS:
boost: 57d2868c099736d80fcd648bf211b4431e51a558
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
FBLazyVector: 9c79ec2238e065a949c9bb7aebdf7cebd6203015
FBReactNativeSpec: 66b1b6348a3f6c3133e6e437ad50b46f4fef812f
FBLazyVector: ca580232ba491b6601ee57bcdc874fda97a459a5
FBReactNativeSpec: 4db5acd51db3de49e59e3740f03c94ce1282a31e
Flipper: 6edb735e6c3e332975d1b17956bcc584eccf5818
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
@@ -1254,53 +1254,53 @@ SPEC CHECKSUMS:
FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 99bd064df01718db56b8f75e6b5ea3051c7dad0a
hermes-engine: 6085d07261e8a8bfe708e4b0dcd0f3eae72a8e4d
hermes-engine: 4ea4b12e82d2ccfd03b29e3d9191b716b4c8f476
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
OCMock: 9491e4bec59e0b267d52a9184ff5605995e74be8
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
RCT-Folly: b0d1393cb3763d71efca99db314c65f0072eb0fe
RCTRequired: 40ffd795b32a630f147fcc15247920e5161b6a4e
RCTTypeSafety: fd9d941c329580ea280cad3565bb2e1656969f41
React: 47363de0e2c161a347eef1cb57cba33f1a4b7377
React-callinvoker: 28e99e6254975d5be67eafeedbcc3a45bb1ace94
RCTRequired: 5394bb1f71591633e1158bdf0534206cb5ccbeb5
RCTTypeSafety: ba46a7f654566047a9358f2e74b2eafd97ccba9b
React: 8a004085056a81c7820f65a8bc0a92e1f7ef6878
React-callinvoker: c2b59f2cbf0d8bcdc18b33f53e097907c12a149c
React-Codegen: 9ecf53f804f4d1d2a5d29bb2154e22a30c381f81
React-Core: f2a04589df942502db2f3ef84ddc83a35a7086d3
React-CoreModules: 4fe81e5b80d5daa3e81151dfc9e516ae1a0e575f
React-cxxreact: 1235a3b3d8ea0639f95741cc907ddbb41da4bea3
React-debug: 9c6d0fd7867cc4a8f435d528e2c5fa8465e9c054
React-Fabric: b429ac248d619825f97ceb607d1a2d556a49c47c
React-FabricImage: 8136841d81a7005ac3c294623f87497f6bbf8f09
React-graphics: ed6de536e153c7bc852f9c773a3c9b21c2ee22da
React-hermes: b494c9c833faa6217b2f666d13aa52623c34c2e9
React-ImageManager: 3e990b6ffbc7634556ddf5b90e5bb1025c397a4c
React-jsi: 00d3c30ce021ff1a074315b824b22108db01131f
React-jsiexecutor: 473c386e219c256066689a334663e8813cdc65ef
React-jsinspector: 25164fdf93d5be275ba455d916e96adb49fa407c
React-logger: a2165169d9c1897c1a139c6b158f6f79090ee174
React-NativeModulesApple: 5ca2a6989b4549ecf557958c72d514bda071922d
React-perflogger: b5ecf879c705577c80e5299ae678b9375fe2f998
React-RCTActionSheet: 4a241f444d11926261dd904449a31f35157d4eee
React-RCTAnimation: cf7c9f502e597ee97ce18d1bc4eb8a5836ecf21d
React-RCTAppDelegate: da46fbaa608f201a832d1310bd2e8ce4234642b1
React-RCTBlob: 48eae02e3f75777da97e9282a5861d04471624a9
React-RCTFabric: 4b122d0e96f6a581046b8cb950e3277036a5d45a
React-RCTImage: 3efab57255aeb986f89ddc8719fbffd4559954ad
React-RCTLinking: 708dbd9b0aa0845c735e5d6b0e9f7f268008a830
React-RCTNetwork: d88896f8b7adf85ba366168c5bf1e03d7c235493
React-RCTPushNotification: dbacfd181137a62deebc62fd386419e707cdcf95
React-RCTSettings: 17cb468bba42b17b7b0e40ea971d6bd8a78baa3f
React-RCTTest: d7142acb2544c01f53deb2b0543d63df6e214ac2
React-RCTText: 8cb3ebda2675a02de0cb363a765fafb7d6c31cc1
React-RCTVibration: c40f0f3e0114354bbf4b418e381c2c96d1d22d48
React-rncore: 11d8412fb1e339cd1be30aa33cb3b265b46a542b
React-runtimeexecutor: c810e5b7d7500a9ad3ed66b834503e412684936d
ReactCommon: a7972e80473d6ba9bcf3ee53bcc310ac6b944a49
ReactCommon-Samples: 93c677b30087b6aa82bbccb4458a2028cd6ebd10
React-Core: d730664c1ee918190a0c26bbf5c993f57e46ff5d
React-CoreModules: 390071843c203f516ec8dbc1c9ce1a815d35e908
React-cxxreact: 39409697255baeb1ac42e436daee59913036109d
React-debug: d69fb5b4e3a47e8d5f8fdb01c1ec8983b7c1afec
React-Fabric: b19750f6cd0fae4e87173b1c2b6f9c94621b6679
React-FabricImage: dc031099e2ae4734b70f1f66b7edf4f1cc40335a
React-graphics: e2c5d8a680cd5db24ab87756f688ecd24fd4628e
React-hermes: 4e22050b18127c27759488b703c0a9d5e6a341ba
React-ImageManager: f6792f5987f85271cbbc31e99dc16e63fd82fccb
React-jsi: 57f6f99db8d596dd599450f921b77229616e983b
React-jsiexecutor: 05d7b49ae31548ed320bb17d97bf5d648406278b
React-jsinspector: 03d477e4dd236c5411ef9a2f8c52cc2951ab6149
React-logger: adce225eaf6d94e0e2568ba586c3f6e857fc8862
React-NativeModulesApple: 67bb14d796e45adad89238b96a26b0f0daa5fa72
React-perflogger: 8889aa68bda6d4cb649f890677d36c7b1eadfcd9
React-RCTActionSheet: f5f5aa4079e8316562171fdf6346883d79fc51b1
React-RCTAnimation: 0a3247bc23fd71f8eff8ccdb1bdeebbcb1de8af3
React-RCTAppDelegate: 4d24c5365e3581fe22a05f0fadf5595b18d8c456
React-RCTBlob: 484c22d88cbfc3d3441c1e54686f7793eaf30fa3
React-RCTFabric: cd58dfa8a168d41363f84e78abe2d71d081581fb
React-RCTImage: 65ef7ecc938dea1decadd876448c47bc71e9d60b
React-RCTLinking: 100b9d0a895d95ea1e59e62375fe9b95d83fe031
React-RCTNetwork: 710d044df7ddbe7be1ecae2608fc2ae2c5096c9a
React-RCTPushNotification: 502720de4bfd05358169c10e1ab791a794f550b0
React-RCTSettings: f0f73ba95d32777386c294572c0e7f757bd33e08
React-RCTTest: 6978788114214cf1f3004c04c5c2b882d9c0c0ea
React-RCTText: a64c63e34beec7c9893042a200d48accde4a406e
React-RCTVibration: e16f969546e42256468f7681d6d6ec3d2cab4524
React-rncore: 4b86b32071e99b892eefaeee55dcec300e3e9194
React-runtimeexecutor: 5499cb980788a75a5a474ab07b480e73f87e76b0
ReactCommon: ed5e5ad966bbc436641a15fedb1b20dd68a2d147
ReactCommon-Samples: cc63a8216d5e2090cc82c9427cd8d6675bef4d0f
ScreenshotManager: d39b964a374e5012e2b8c143e29ead86b1da6a3c
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: b82f2e3cbeb3d926329278b24d54d798215d4fa3
Yoga: 456e136b8de9c5360b609bd3238656cea243958f
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: bdab6add69d555774de227d7119a8f5ae02a670e
COCOAPODS: 1.12.0
COCOAPODS: 1.12.1