Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ DerivedData
*.xcuserstate
project.xcworkspace
**/.xcode.env.local
/poackages/react-native/sdks/downloads/

# Gradle
/build/
Expand Down
23 changes: 15 additions & 8 deletions packages/react-native/scripts/cocoapods/__tests__/utils-test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,26 @@ def test_hasPod_whenInstallerHasPod_returnTrue
# 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),
}
)

Expand All @@ -201,11 +206,13 @@ def test_SetGCCPreprocessorDefinitionForReactHermes_itSetsThePreprocessorForDebu

# 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

# ============================ #
Expand Down
4 changes: 3 additions & 1 deletion packages/react-native/scripts/cocoapods/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def self.has_pod(installer, name)

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)
Expand Down Expand Up @@ -153,7 +154,8 @@ def self.add_build_settings_to_pod(installer, settings_name, settings_value, tar
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
Expand Down
31 changes: 23 additions & 8 deletions packages/react-native/sdks/hermes-engine/hermes-engine.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -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"
Expand All @@ -42,22 +39,40 @@ 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"

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"
ss.ios.vendored_frameworks = "destroot/Library/Frameworks/universal/hermes.xcframework"
ss.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"
end


# To remove the PRODUCTION flag, we want to use the right version of hermes based on the configuration choosen in Xcode
# We do so in a pre-build script we invoke from the Xcode build script pipeline: we check the Configuration and we
# replace the hermes-engine before building and linking the app.
# We use this approach only for Apps created using the template: RNTester and Nightlies should not be used to build for Release.
# If a specific Hermes tarball is provided, that has priority on this logic. If a tarball is provided, is probably because there is
# something specific in that tarball that needs to be tested.
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|
Expand Down Expand Up @@ -96,15 +111,15 @@ 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
},
{
: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
Expand Down
38 changes: 25 additions & 13 deletions packages/react-native/sdks/hermes-engine/hermes-utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def abort_if_invalid_tarball_provided!()
# - 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')
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -110,16 +131,7 @@ def putsIfPodPresent(message, level = 'warning')
# 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Loading