Refactor hermes-utils.rb (#38963)

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

This diff refactors `hermes-utils.rb` in the following way:
- Explicitly define all the scenarios how `hermes-engine.podspec` can consume its source.
- Try to be explicit and verbose about when those scenarios take place.
- Split `compute_hermes_source` into two functions:
    - `hermes_source_type` that determines the podspec source type.
    - `podspec_source` that builds the podspec source based on the source type provided.

Also `hermes-engine.podspec` now uses source type returned by `hermes_source_type` instead of derived values `:git` and `:http`, which were not descriptive and granular enough.
This refactoring should make adding new cases and altering existing ones easier. Conditions, precedence and how to act for each scenario is much more explicit.

Changelog: [Internal]

Reviewed By: cipolleschi

Differential Revision: D48161239

fbshipit-source-id: 3d3d24aa1e05458e1f877153e43ebc2b437352e9
This commit is contained in:
Dmitry Rykun
2023-08-24 03:45:24 -07:00
committed by Facebook GitHub Bot
parent 970ba05127
commit 4b664fc6c2
2 changed files with 151 additions and 89 deletions
@@ -12,15 +12,8 @@ react_native_path = File.join(__dir__, "..", "..")
package = JSON.parse(File.read(File.join(react_native_path, "package.json")))
version = package['version']
# sdks/.hermesversion
hermestag_file = File.join(react_native_path, "sdks", ".hermesversion")
build_from_source = ENV['BUILD_FROM_SOURCE'] === 'true'
git = "https://github.com/facebook/hermes.git"
abort_if_invalid_tarball_provided!
source = compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path)
source_type = hermes_source_type(version, react_native_path)
source = podspec_source(source_type, version, react_native_path)
Pod::Spec.new do |spec|
spec.name = "hermes-engine"
@@ -44,7 +37,7 @@ Pod::Spec.new do |spec|
spec.ios.vendored_frameworks = "destroot/Library/Frameworks/ios/hermes.framework"
spec.osx.vendored_frameworks = "destroot/Library/Frameworks/macosx/hermes.framework"
if source[:http] then
if HermesEngineSourceType::isPrebuilt(source_type) then
spec.subspec 'Pre-built' do |ss|
ss.preserve_paths = ["destroot/bin/*"].concat(["**/*.{h,c,cpp}"])
@@ -61,7 +54,7 @@ Pod::Spec.new do |spec|
# 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/')
if source_type == HermesEngineSourceType::DOWNLOAD_PREBUILD_RELEASE_TARBALL
spec.script_phase = {
:name => "[Hermes] Replace Hermes for the right configuration, if needed",
:execution_position => :before_compile,
@@ -72,7 +65,7 @@ Pod::Spec.new do |spec|
}
end
elsif source[:git] then
elsif source_type == HermesEngineSourceType::isFromSource(source_type) then
spec.subspec 'Hermes' do |ss|
ss.source_files = ''
@@ -6,100 +6,166 @@
require 'net/http'
require 'rexml/document'
# This function abort the build if the `HERMES_ENGINE_TARBALL_PATH` ENV variable is set with an invalid path
def abort_if_invalid_tarball_provided!()
if ENV.has_key?('HERMES_ENGINE_TARBALL_PATH') && !File.exist?(ENV['HERMES_ENGINE_TARBALL_PATH'])
abort "[Hermes] HERMES_ENGINE_TARBALL_PATH is set, but points to a non-existing file: \"#{ENV['HERMES_ENGINE_TARBALL_PATH']}\"\nIf you don't want to use tarball, run `unset HERMES_ENGINE_TARBALL_PATH`"
HERMES_GITHUB_URL = "https://github.com/facebook/hermes.git"
module HermesEngineSourceType
LOCAL_PREBUILT_TARBALL = :local_prebuilt_tarball
DOWNLOAD_PREBUILD_RELEASE_TARBALL = :download_prebuild_release_tarball
DOWNLOAD_PREBUILT_NIGHTLY_TARBALL = :download_prebuilt_nightly_tarball
BUILD_FROM_GITHUB_COMMIT = :build_from_github_commit
BUILD_FROM_GITHUB_TAG = :build_from_github_tag
BUILD_FROM_GITHUB_MAIN = :build_from_github_main
def HermesEngineSourceType.isPrebuilt(source_type)
return source_type == LOCAL_PREBUILT_TARBALL || source_type == DOWNLOAD_PREBUILD_RELEASE_TARBALL || source_type == DOWNLOAD_PREBUILT_NIGHTLY_TARBALL
end
def HermesEngineSourceType.isFromSource(source_type)
return source_type == BUILD_FROM_GITHUB_COMMIT || source_type == BUILD_FROM_GITHUB_TAG || source_type == BUILD_FROM_GITHUB_MAIN
end
end
# It computes the right value for the hermes-engine.podspec's source.
# Computes the hermes-engine.podspec's source type.
# - To use a specific tarball, install the dependencies with:
# `HERMES_ENGINE_TARBALL_PATH=<path_to_tarball> bundle exec pod install`
# - To force a build from source, install the dependencies with:
# `BUILD_FROM_SOURCE=true bundle exec pod install`
# If none of the two are provided, Cocoapods will check whether there is a tarball for the current version
# (either release or nightly). If not, it will fall back building from source (the latest commit on main).
# (either release or nightly). If not, it will fall back to building from source (the latest commit on main).
#
# Parameters:
# - build_from_source: boolean to force a build from source.
# - hermestag_file: path to the hermes tag file.
# - git: uri to the hermes repository
# - version: current version of the pod
# - build_type: build type of the hermes engine. It can be `:release` or `:debug`
# - react_native_path: path to react native
#
# Returns: a properly configured source object
def compute_hermes_source(build_from_source, hermestag_file, git, version, react_native_path)
source = {}
if ENV.has_key?('HERMES_ENGINE_TARBALL_PATH')
use_tarball(source)
elsif ENV.has_key?('HERMES_COMMIT')
build_hermes_from_commit(source, git, ENV['HERMES_COMMIT'])
elsif build_from_source
if File.exist?(hermestag_file)
build_from_tagfile(source, git, hermestag_file)
else
build_hermes_from_source(source, git)
end
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, version)
else
build_hermes_from_source(source, git)
# Returns: hermes-engine source type
def hermes_source_type(version, react_native_path)
if hermes_engine_tarball_envvar_defined()
return HermesEngineSourceType::LOCAL_PREBUILT_TARBALL
end
return source
if hermes_commit_envvar_defined()
return HermesEngineSourceType::BUILD_FROM_GITHUB_COMMIT
end
if force_build_from_tag(react_native_path)
return HermesEngineSourceType::BUILD_FROM_GITHUB_TAG
end
if force_build_from_main(react_native_path)
return HermesEngineSourceType::BUILD_FROM_GITHUB_MAIN
end
if release_artifact_exists(version)
return HermesEngineSourceType::DOWNLOAD_PREBUILD_RELEASE_TARBALL
end
if nightly_artifact_exists(version)
return HermesEngineSourceType::DOWNLOAD_PREBUILT_NIGHTLY_TARBALL
end
return HermesEngineSourceType::BUILD_FROM_GITHUB_MAIN
end
def use_tarball(source)
def hermes_engine_tarball_envvar_defined()
return ENV.has_key?('HERMES_ENGINE_TARBALL_PATH')
end
def hermes_commit_envvar_defined()
return ENV.has_key?('HERMES_COMMIT')
end
def force_build_from_tag(react_native_path)
return ENV['BUILD_FROM_SOURCE'] === 'true' && File.exist?(hermestag_file(react_native_path))
end
def force_build_from_main(react_native_path)
return ENV['BUILD_FROM_SOURCE'] === 'true' && !File.exist?(hermestag_file(react_native_path))
end
def release_artifact_exists(version)
return hermes_artifact_exists(release_tarball_url(version, :debug))
end
def nightly_artifact_exists(version)
return hermes_artifact_exists(nightly_tarball_url(version).gsub("\\", ""))
end
def podspec_source(source_type, version, react_native_path)
case source_type
when HermesEngineSourceType::LOCAL_PREBUILT_TARBALL
return podspec_source_local_prebuilt_tarball()
when HermesEngineSourceType::BUILD_FROM_GITHUB_COMMIT
return podspec_source_build_from_github_commit()
when HermesEngineSourceType::BUILD_FROM_GITHUB_TAG
return podspec_source_build_from_github_tag(react_native_path)
when HermesEngineSourceType::BUILD_FROM_GITHUB_MAIN
return podspec_source_build_from_github_main()
when HermesEngineSourceType::DOWNLOAD_PREBUILD_RELEASE_TARBALL
return podspec_source_download_prebuild_release_tarball(react_native_path, version)
when HermesEngineSourceType::DOWNLOAD_PREBUILT_NIGHTLY_TARBALL
return podspec_source_download_prebuilt_nightly_tarball()
else
abort "[Hermes] Unsupported or invalid source type provided: #{source_type}"
end
end
def podspec_source_local_prebuilt_tarball()
tarball_path = ENV['HERMES_ENGINE_TARBALL_PATH']
putsIfPodPresent("[Hermes] Using pre-built Hermes binaries from local path: #{tarball_path}")
source[:http] = "file://#{tarball_path}"
if File.exist?(tarball_path)
hermes_log("Using pre-built binary from local path defined by HERMES_ENGINE_TARBALL_PATH envvar: #{tarball_path}")
return {:http => "file://#{tarball_path}"}
end
abort <<-EOS
[Hermes] HERMES_ENGINE_TARBALL_PATH is set, but points to a non-existing file: \"#{tarball_path}\"
If you don't want to use tarball, run `unset HERMES_ENGINE_TARBALL_PATH`
EOS
end
def build_from_tagfile(source, git, hermestag_file)
hermestag = File.read(hermestag_file).strip
putsIfPodPresent("[Hermes] Building Hermes from source from tag #{hermestag}...")
source[:git] = git
source[:tag] = hermestag
def podspec_source_build_from_github_commit()
commit = ENV['HERMES_COMMIT']
hermes_log("Using commit defined by HERMES_COMMIT envvar: #{commit}")
return {:git => HERMES_GITHUB_URL, :commit => commit}
end
def use_release_tarball(source, version, build_type)
# Sample url from Maven:
# https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.71.0/react-native-artifacts-0.71.0-hermes-ios-debug.tar.gz
putsIfPodPresent('[Hermes] Using the release tarball from Maven Central', 'info')
source[:http] = release_tarball_url(version, build_type)
def podspec_source_build_from_github_tag(react_native_path)
tag = File.read(hermestag_file(react_native_path)).strip
hermes_log("Using tag difined in sdks/.hermesversion: #{tag}")
return {:git => HERMES_GITHUB_URL, :tag => tag}
end
def podspec_source_build_from_github_main()
hermes_log("Using the latest commit from main.")
return {:git => HERMES_GITHUB_URL, :commit => `git ls-remote #{HERMES_GITHUB_URL} main | cut -f 1`.strip}
end
def podspec_source_download_prebuild_release_tarball(react_native_path, version)
url = release_tarball_url(version, :debug)
hermes_log("Using release tarball from URL: #{url}")
download_stable_hermes(react_native_path, version, :debug)
download_stable_hermes(react_native_path, version, :release)
return {:http => url}
end
def podspec_source_download_prebuilt_nightly_tarball()
destination_path = download_nightly_hermes(react_native_path, version)
url = nightly_tarball_url(version)
hermes_log("Using nightly tarball from URL: #{url}")
return {:http => url}
end
# HELPERS
def hermestag_file(react_native_path)
return File.join(react_native_path, "sdks", ".hermesversion")
end
def release_tarball_url(version, build_type)
# Sample url from Maven:
# https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.71.0/react-native-artifacts-0.71.0-hermes-ios-debug.tar.gz
return "https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/#{version}/react-native-artifacts-#{version}-hermes-ios-#{build_type.to_s}.tar.gz"
end
def use_nightly_tarball(source, version)
putsIfPodPresent('[Hermes] Nightly version, download pre-built for Hermes')
source[:http] = nightly_tarball_url(version)
end
def putsIfPodPresent(message, level = 'warning')
unless Object.const_defined?("Pod::UI")
return
end
case level
when 'info'
Pod::UI.puts message.green
when 'error'
Pod::UI.puts message.red
else
Pod::UI.puts message.yellow
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)
@@ -124,18 +190,6 @@ def nightly_tarball_url(version)
return resolve_url_redirects("http://oss.sonatype.org/service/local/artifact/maven/redirect\?#{params}")
end
def build_hermes_from_source(source, git)
putsIfPodPresent('[Hermes] Installing hermes-engine may take slightly longer, building Hermes compiler from source...')
source[:git] = git
source[:commit] = `git ls-remote https://github.com/facebook/hermes main | cut -f 1`.strip
end
def build_hermes_from_commit(source, git, commit)
putsIfPodPresent("[Hermes] Installing hermes-engine from commit #{commit}. It may take a while.")
source[:git] = git
source[:commit] = commit
end
def resolve_url_redirects(url)
return (`curl -Ls -o /dev/null -w %{url_effective} \"#{url}\"`)
end
@@ -151,3 +205,18 @@ def hermes_artifact_exists(tarball_url)
# I also needed to wrap the url in quotes to avoid escaping & and ?.
return (`curl -o /dev/null --silent -Iw '%{http_code}' -L "#{tarball_url}"` == "200")
end
def hermes_log(message, level = :warning)
if !Object.const_defined?("Pod::UI")
return
end
hermes_log_messgae = '[Hermes] ' + message
case level
when :info
Pod::UI.puts hermes_log_messgae.green
when :error
Pod::UI.puts hermes_log_messgae.red
else
Pod::UI.puts hermes_log_messgae.yellow
end
end