Skip to content

Commit 50a65c5

Browse files
Riccardo Cipolleschipull[bot]
authored andcommitted
download hermes-engine when the configuration change (facebook#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: facebook#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
1 parent 0f50c37 commit 50a65c5

File tree

7 files changed

+212
-71
lines changed

7 files changed

+212
-71
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ DerivedData
2121
*.xcuserstate
2222
project.xcworkspace
2323
**/.xcode.env.local
24+
/poackages/react-native/sdks/downloads/
2425

2526
# Gradle
2627
/build/

packages/react-native/scripts/cocoapods/__tests__/utils-test.rb

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,21 +178,26 @@ def test_hasPod_whenInstallerHasPod_returnTrue
178178
# Test - Set GCC Preprocessor Definition for React-hermes #
179179
# ======================================================== #
180180

181-
def test_SetGCCPreprocessorDefinitionForReactHermes_itSetsThePreprocessorForDebug
181+
def test_SetGCCPreprocessorDefinitionForHermes_itSetsThePreprocessorForDebug
182182
# Arrange
183183
react_hermes_name = "React-hermes"
184184
react_core_name = "React-Core"
185-
react_hermes_debug_config = BuildConfigurationMock.new("Debug")
186-
react_hermes_release_config = BuildConfigurationMock.new("Release")
187-
react_core_debug_config = BuildConfigurationMock.new("Debug")
188-
react_core_release_config = BuildConfigurationMock.new("Release")
189-
react_hermes_target = TargetMock.new(react_hermes_name, [react_hermes_debug_config, react_hermes_release_config])
190-
react_core_target = TargetMock.new(react_core_name, [react_core_debug_config, react_core_release_config])
185+
hermes_engine_name = "hermes-engine"
186+
react_hermes_debug_config = BuildConfigurationMock.new("Debug")
187+
react_hermes_release_config = BuildConfigurationMock.new("Release")
188+
react_core_debug_config = BuildConfigurationMock.new("Debug")
189+
react_core_release_config = BuildConfigurationMock.new("Release")
190+
hermes_engine_debug_config = BuildConfigurationMock.new("Debug")
191+
hermes_engine_release_config = BuildConfigurationMock.new("Release")
192+
react_hermes_target = TargetMock.new(react_hermes_name, [react_hermes_debug_config, react_hermes_release_config])
193+
react_core_target = TargetMock.new(react_core_name, [react_core_debug_config, react_core_release_config])
194+
hermes_engine_target = TargetMock.new(hermes_engine_name, [hermes_engine_debug_config, hermes_engine_release_config])
191195

192196
installer = InstallerMock.new(
193197
:pod_target_installation_results => {
194198
react_hermes_name => TargetInstallationResultMock.new(react_hermes_target, react_hermes_target),
195199
react_core_name => TargetInstallationResultMock.new(react_core_target, react_core_target),
200+
hermes_engine_name => TargetInstallationResultMock.new(hermes_engine_target, hermes_engine_target),
196201
}
197202
)
198203

@@ -201,11 +206,13 @@ def test_SetGCCPreprocessorDefinitionForReactHermes_itSetsThePreprocessorForDebu
201206

202207
# Assert
203208
build_setting = "GCC_PREPROCESSOR_DEFINITIONS"
204-
expected_value = "HERMES_ENABLE_DEBUGGER=1"
209+
expected_value = "$(inherited) HERMES_ENABLE_DEBUGGER=1"
205210
assert_equal(expected_value, react_hermes_debug_config.build_settings[build_setting])
206211
assert_nil(react_hermes_release_config.build_settings[build_setting])
207212
assert_nil(react_core_debug_config.build_settings[build_setting])
208213
assert_nil(react_core_release_config.build_settings[build_setting])
214+
assert_equal(expected_value, hermes_engine_debug_config.build_settings[build_setting])
215+
assert_nil(hermes_engine_release_config.build_settings[build_setting])
209216
end
210217

211218
# ============================ #

packages/react-native/scripts/cocoapods/utils.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def self.has_pod(installer, name)
4141

4242
def self.set_gcc_preprocessor_definition_for_React_hermes(installer)
4343
self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "React-hermes", "Debug")
44+
self.add_build_settings_to_pod(installer, "GCC_PREPROCESSOR_DEFINITIONS", "HERMES_ENABLE_DEBUGGER=1", "hermes-engine", "Debug")
4445
end
4546

4647
def self.turn_off_resource_bundle_react_core(installer)
@@ -153,7 +154,8 @@ def self.add_build_settings_to_pod(installer, settings_name, settings_value, tar
153154
if pod_name.to_s == target_pod_name
154155
target_installation_result.native_target.build_configurations.each do |config|
155156
if configuration == nil || (configuration != nil && configuration == config.name)
156-
config.build_settings[settings_name] = settings_value
157+
config.build_settings[settings_name] ||= '$(inherited) '
158+
config.build_settings[settings_name] << settings_value
157159
end
158160
end
159161
end

packages/react-native/sdks/hermes-engine/hermes-engine.podspec

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ require_relative "./hermes-utils.rb"
88

99
react_native_path = File.join(__dir__, "..", "..")
1010

11-
# Whether Hermes is built for Release or Debug is determined by the PRODUCTION envvar.
12-
build_type = ENV['PRODUCTION'] == "1" ? :release : :debug
13-
1411
# package.json
1512
package = JSON.parse(File.read(File.join(react_native_path, "package.json")))
1613
version = package['version']
@@ -23,7 +20,7 @@ git = "https://github.com/facebook/hermes.git"
2320

2421
abort_if_invalid_tarball_provided!
2522

26-
source = compute_hermes_source(build_from_source, hermestag_file, git, version, build_type, react_native_path)
23+
source = compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path)
2724

2825
Pod::Spec.new do |spec|
2926
spec.name = "hermes-engine"
@@ -42,22 +39,40 @@ Pod::Spec.new do |spec|
4239
spec.pod_target_xcconfig = {
4340
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
4441
"CLANG_CXX_LIBRARY" => "compiler-default"
45-
}.merge!(build_type == :debug ? { "GCC_PREPROCESSOR_DEFINITIONS" => "HERMES_ENABLE_DEBUGGER=1" } : {})
42+
}
4643

4744
spec.ios.vendored_frameworks = "destroot/Library/Frameworks/ios/hermes.framework"
4845
spec.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"
4946

5047
if source[:http] then
5148

5249
spec.subspec 'Pre-built' do |ss|
53-
ss.preserve_paths = ["destroot/bin/*"].concat(build_type == :debug ? ["**/*.{h,c,cpp}"] : [])
50+
ss.preserve_paths = ["destroot/bin/*"].concat(["**/*.{h,c,cpp}"])
5451
ss.source_files = "destroot/include/**/*.h"
5552
ss.exclude_files = ["destroot/include/jsi/jsi/JSIDynamic.{h,cpp}", "destroot/include/jsi/jsi/jsilib-*.{h,cpp}"]
5653
ss.header_mappings_dir = "destroot/include"
5754
ss.ios.vendored_frameworks = "destroot/Library/Frameworks/universal/hermes.xcframework"
5855
ss.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"
5956
end
6057

58+
59+
# Right now, even reinstalling pods with the PRODUCTION flag turned on, does not change the version of hermes that is downloaded
60+
# To remove the PRODUCTION flag, we want to download the right version of hermes on the flight
61+
# we do so in a pre-build script we invoke from the Xcode build pipeline
62+
# We use this only for Apps created using the template. RNTester and Nightlies should not be used to build for Release.
63+
# 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
64+
# test something specific for that tarball.
65+
if source[:http].include?('https://repo1.maven.org/')
66+
spec.script_phase = {
67+
:name => "[Hermes] Replace Hermes for the right configuration, if needed",
68+
:execution_position => :before_compile,
69+
:script => <<-EOS
70+
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
71+
"$NODE_BINARY" "$REACT_NATIVE_PATH/sdks/hermes-engine/utils/replace_hermes_version.js" -c "$CONFIGURATION" -r "#{version}" -p "$REACT_NATIVE_PATH"
72+
EOS
73+
}
74+
end
75+
6176
elsif source[:git] then
6277

6378
spec.subspec 'Hermes' do |ss|
@@ -96,15 +111,15 @@ Pod::Spec.new do |spec|
96111
{
97112
:name => '[RN] [1] Build Hermesc',
98113
:script => <<-EOS
99-
. ${PODS_ROOT}/../.xcode.env
114+
. "${REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh"
100115
export CMAKE_BINARY=${CMAKE_BINARY:-#{CMAKE_BINARY}}
101116
. ${REACT_NATIVE_PATH}/sdks/hermes-engine/utils/build-hermesc-xcode.sh #{hermesc_path}
102117
EOS
103118
},
104119
{
105120
:name => '[RN] [2] Build Hermes',
106121
:script => <<-EOS
107-
. ${PODS_ROOT}/../.xcode.env
122+
. "${REACT_NATIVE_PATH}/scripts/xcode/with-environment.sh"
108123
export CMAKE_BINARY=${CMAKE_BINARY:-#{CMAKE_BINARY}}
109124
. ${REACT_NATIVE_PATH}/sdks/hermes-engine/utils/build-hermes-xcode.sh #{version} #{hermesc_path}/ImportHermesc.cmake
110125
EOS

packages/react-native/sdks/hermes-engine/hermes-utils.rb

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def abort_if_invalid_tarball_provided!()
3030
# - react_native_path: path to react native
3131
#
3232
# Returns: a properly configured source object
33-
def compute_hermes_source(build_from_source, hermestag_file, git, version, build_type, react_native_path)
33+
def compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path)
3434
source = {}
3535

3636
if ENV.has_key?('HERMES_ENGINE_TARBALL_PATH')
@@ -43,8 +43,10 @@ def compute_hermes_source(build_from_source, hermestag_file, git, version, build
4343
else
4444
build_hermes_from_source(source, git)
4545
end
46-
elsif hermes_artifact_exists(release_tarball_url(version, build_type))
47-
use_release_tarball(source, version, build_type)
46+
elsif hermes_artifact_exists(release_tarball_url(version, :debug))
47+
use_release_tarball(source, version, :debug)
48+
download_stable_hermes(react_native_path, version, :debug)
49+
download_stable_hermes(react_native_path, version, :release)
4850
elsif hermes_artifact_exists(nightly_tarball_url(version).gsub("\\", ""))
4951
use_nightly_tarball(source, react_native_path, version)
5052
else
@@ -100,6 +102,25 @@ def putsIfPodPresent(message, level = 'warning')
100102
end
101103
end
102104

105+
def download_stable_hermes(react_native_path, version, configuration)
106+
tarball_url = release_tarball_url(version, configuration)
107+
download_hermes_tarball(react_native_path, tarball_url, version, configuration)
108+
end
109+
110+
def download_hermes_tarball(react_native_path, tarball_url, version, configuration)
111+
destination_folder = "#{react_native_path}/sdks/downloads"
112+
destination_path = configuration == nil ?
113+
"#{destination_folder}/hermes-ios-#{version}.tar.gz" :
114+
"#{destination_folder}/hermes-ios-#{version}-#{configuration}.tar.gz"
115+
116+
unless File.exist?(destination_path)
117+
# Download to a temporary file first so we don't cache incomplete downloads.
118+
tmp_file = "#{destination_folder}/hermes-ios.download"
119+
`mkdir -p "#{destination_folder}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
120+
end
121+
return destination_path
122+
end
123+
103124
# This function downloads the nightly prebuilt version of Hermes based on the passed version
104125
# and save it in the node_module/react_native/sdks/downloads folder
105126
# It then returns the path to the hermes tarball
@@ -110,16 +131,7 @@ def putsIfPodPresent(message, level = 'warning')
110131
# Returns: the path to the downloaded Hermes tarball
111132
def download_nightly_hermes(react_native_path, version)
112133
tarball_url = nightly_tarball_url(version)
113-
114-
destination_folder = "#{react_native_path}/sdks/downloads"
115-
destination_path = "#{destination_folder}/hermes-ios-#{version}.tar.gz"
116-
117-
unless File.exist?(destination_path)
118-
# Download to a temporary file first so we don't cache incomplete downloads.
119-
tmp_file = "#{destination_folder}/hermes-ios.download"
120-
`mkdir -p "#{destination_folder}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"`
121-
end
122-
return destination_path
134+
return download_stable_hermes(react_native_path, tarball_url, version, nil)
123135
end
124136

125137
def nightly_tarball_url(version)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
12+
const yargs = require('yargs');
13+
const fs = require('fs');
14+
const {execSync} = require('child_process');
15+
16+
const LAST_BUILD_FILENAME = '.last_build_configuration';
17+
18+
function validateBuildConfiguration(configuration) {
19+
if (!['Debug', 'Release'].includes(configuration)) {
20+
throw new Error(`Invalid configuration ${configuration}`);
21+
}
22+
}
23+
24+
function validateVersion(version) {
25+
if (version == null || version === '') {
26+
throw new Error('Version cannot be empty');
27+
}
28+
}
29+
30+
function shouldReplaceHermesConfiguration(configuration) {
31+
const fileExists = fs.existsSync(LAST_BUILD_FILENAME);
32+
33+
if (fileExists) {
34+
console.log(`Found ${LAST_BUILD_FILENAME} file`);
35+
const oldConfiguration = fs.readFileSync(LAST_BUILD_FILENAME).toString();
36+
if (oldConfiguration === configuration) {
37+
console.log('No need to download a new build of Hermes!');
38+
return false;
39+
}
40+
}
41+
42+
// Assumption: if there is no stored last build, we assume that it was build for debug.
43+
if (!fs.existsSync && configuration === 'Debug') {
44+
console.log(
45+
'File does not exists, but Debug configuration. No need to download a new build of Hermes!',
46+
);
47+
return false;
48+
}
49+
50+
return true;
51+
}
52+
53+
function replaceHermesConfiguration(configuration, version, reactNativePath) {
54+
const tarballURLPath = `${reactNativePath}/sdks/downloads/hermes-ios-${version}-${configuration}.tar.gz`;
55+
56+
const finalLocation = 'hermes-engine';
57+
console.log('Preparing the final location');
58+
fs.rmSync(finalLocation, {force: true, recursive: true});
59+
fs.mkdirSync(finalLocation, {recursive: true});
60+
61+
console.log('Extracting the tarball');
62+
execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`);
63+
}
64+
65+
function updateLastBuildConfiguration(configuration) {
66+
fs.writeFileSync(LAST_BUILD_FILENAME, configuration);
67+
}
68+
69+
function main(configuration, version, reactNativePath) {
70+
validateBuildConfiguration(configuration);
71+
validateVersion(version);
72+
73+
if (!shouldReplaceHermesConfiguration(configuration)) {
74+
return;
75+
}
76+
77+
replaceHermesConfiguration(configuration, version, reactNativePath);
78+
updateLastBuildConfiguration(configuration);
79+
console.log('Done replacing hermes-engine');
80+
}
81+
82+
// This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok
83+
const argv = yargs
84+
.option('c', {
85+
alias: 'configuration',
86+
description:
87+
'Configuration to use to download the right Hermes version. Allowed values are "Debug" and "Release".',
88+
})
89+
.option('r', {
90+
alias: 'reactNativeVersion',
91+
description:
92+
'The Version of React Native associated with the Hermes tarball.',
93+
})
94+
.option('p', {
95+
alias: 'reactNativePath',
96+
description: 'The path to the React Native root folder',
97+
})
98+
.usage('Usage: $0 -c Debug -r <version> -p <path/to/react-native>').argv;
99+
100+
const configuration = argv.configuration;
101+
const version = argv.reactNativeVersion;
102+
const reactNativePath = argv.reactNativePath;
103+
104+
main(configuration, version, reactNativePath);

0 commit comments

Comments
 (0)