Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY
import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY_DEFAULT
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_VERSION_NAME
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_CACHE_ENABLED
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_USE_HERMES_NIGHTLY
Expand All @@ -27,6 +28,9 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.MavenArtifactRepository

internal object DependencyUtils {
private const val REACT_NATIVE_MAVEN_CACHE_URL = "https://repo.reactnative.dev/maven2"
private const val REACT_NATIVE_MAVEN_CACHE_ENABLED_ENV =
"RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED"

internal data class Coordinates(
val versionString: String,
Expand Down Expand Up @@ -75,6 +79,17 @@ internal object DependencyUtils {
repo.content { it.excludeGroup("org.webkit") }
}
}
// The React Native Maven cache must be added before Maven Central so it can serve cached
// artifacts first, while Maven Central remains the fallback.
if (!hasProperty(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO) &&
isReactNativeMavenCacheEnabled()) {
mavenRepoFromUrl(REACT_NATIVE_MAVEN_CACHE_URL) { repo ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment mentioning that this is added "first" intentionally

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 479acf1

repo.content {
it.includeGroupByRegex("com\\.facebook\\.react.*")
it.includeGroupByRegex("com\\.facebook\\.hermes.*")
}
}
}
repositories.mavenCentral { repo ->
// We don't want to fetch JSC from Maven Central as there are older versions there.
repo.content { it.excludeGroup("org.webkit") }
Expand Down Expand Up @@ -271,6 +286,17 @@ internal object DependencyUtils {
else -> INCLUDE_JITPACK_REPOSITORY_DEFAULT
}

internal fun Project.isReactNativeMavenCacheEnabled(
environmentValue: String? = System.getenv(REACT_NATIVE_MAVEN_CACHE_ENABLED_ENV)
): Boolean =
when {
hasProperty(INTERNAL_REACT_NATIVE_MAVEN_CACHE_ENABLED) ->
property(INTERNAL_REACT_NATIVE_MAVEN_CACHE_ENABLED).toString().toBoolean()
!environmentValue.isNullOrEmpty() ->
!environmentValue.equals("false", ignoreCase = true) && environmentValue != "0"
else -> false
}

internal fun String.isNightly(): Boolean = this.startsWith("0.0.0") || "-nightly-" in this

internal fun Project.exclusiveEnterpriseRepository() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ object PropertyUtils {
*/
const val INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO = "react.internal.mavenLocalRepo"

/** Internal property that controls whether the React Native Maven cache is enabled. */
const val INTERNAL_REACT_NATIVE_MAVEN_CACHE_ENABLED =
"react.internal.reactNativeMavenCacheEnabled"

/**
* Internal property used to specify where the Windows Bash executable is located. This is useful
* for contributors who are running Windows on their machine.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.facebook.react.utils.DependencyUtils.configureDependencies
import com.facebook.react.utils.DependencyUtils.configureRepositories
import com.facebook.react.utils.DependencyUtils.exclusiveEnterpriseRepository
import com.facebook.react.utils.DependencyUtils.getDependencySubstitutions
import com.facebook.react.utils.DependencyUtils.isReactNativeMavenCacheEnabled
import com.facebook.react.utils.DependencyUtils.isNightly
import com.facebook.react.utils.DependencyUtils.mavenRepoFromURI
import com.facebook.react.utils.DependencyUtils.mavenRepoFromUrl
Expand Down Expand Up @@ -61,6 +62,22 @@ class DependencyUtilsTest {
.isNotNull()
}

@Test
fun configureRepositories_withReactNativeMavenCacheEnabled_containsMavenCache() {
val repositoryURI = URI.create("https://repo.reactnative.dev/maven2")
val project = createProject()
project.extensions.extraProperties.set("react.internal.reactNativeMavenCacheEnabled", "true")

configureRepositories(project, false)

assertThat(
project.repositories.firstOrNull {
it is MavenArtifactRepository && it.url == repositoryURI
}
)
.isNotNull()
}

@Test
fun configureRepositories_containsGoogleRepo() {
val repositoryURI = URI.create("https://dl.google.com/dl/android/maven2/")
Expand Down Expand Up @@ -221,6 +238,101 @@ class DependencyUtilsTest {
assertThat(indexOfLocalRepo < indexOfMavenCentral).isTrue()
}

@Test
fun configureRepositories_mavenCacheHasHigherPriorityThanMavenCentral() {
val mavenCacheURI = URI.create("https://repo.reactnative.dev/maven2")
val mavenCentralURI = URI.create("https://repo.maven.apache.org/maven2/")
val project = createProject()
project.extensions.extraProperties.set("react.internal.reactNativeMavenCacheEnabled", "true")

configureRepositories(project, false)

val indexOfMavenCache =
project.repositories.indexOfFirst {
it is MavenArtifactRepository && it.url == mavenCacheURI
}
val indexOfMavenCentral =
project.repositories.indexOfFirst {
it is MavenArtifactRepository && it.url == mavenCentralURI
}
assertThat(indexOfMavenCache < indexOfMavenCentral).isTrue()
}

@Test
fun configureRepositories_withProjectPropertySet_doesNotContainMavenCache() {
val localMaven = tempFolder.newFolder("m2")
val mavenCacheURI = URI.create("https://repo.reactnative.dev/maven2")
val project = createProject()
project.extensions.extraProperties.set("react.internal.mavenLocalRepo", localMaven.absolutePath)

configureRepositories(project, false)

assertThat(
project.repositories.firstOrNull {
it is MavenArtifactRepository && it.url == mavenCacheURI
}
)
.isNull()
}

@Test
fun configureRepositories_byDefault_doesNotContainMavenCache() {
val mavenCacheURI = URI.create("https://repo.reactnative.dev/maven2")
val project = createProject()

configureRepositories(project, false)

assertThat(
project.repositories.firstOrNull {
it is MavenArtifactRepository && it.url == mavenCacheURI
}
)
.isNull()
}

@Test
fun configureRepositories_withReactNativeMavenCacheDisabled_doesNotContainMavenCache() {
val mavenCacheURI = URI.create("https://repo.reactnative.dev/maven2")
val project = createProject()
project.extensions.extraProperties.set("react.internal.reactNativeMavenCacheEnabled", "false")

configureRepositories(project, false)

assertThat(
project.repositories.firstOrNull {
it is MavenArtifactRepository && it.url == mavenCacheURI
}
)
.isNull()
}

@Test
fun isReactNativeMavenCacheEnabled_withEnvironmentVariableEnabled_returnsTrue() {
val project = createProject()

assertThat(project.isReactNativeMavenCacheEnabled("true")).isTrue()
assertThat(project.isReactNativeMavenCacheEnabled("1")).isTrue()
}

@Test
fun isReactNativeMavenCacheEnabled_withEnvironmentVariableDisabled_returnsFalse() {
val project = createProject()

assertThat(project.isReactNativeMavenCacheEnabled(null)).isFalse()
assertThat(project.isReactNativeMavenCacheEnabled("")).isFalse()
assertThat(project.isReactNativeMavenCacheEnabled("false")).isFalse()
assertThat(project.isReactNativeMavenCacheEnabled("FALSE")).isFalse()
assertThat(project.isReactNativeMavenCacheEnabled("0")).isFalse()
}

@Test
fun isReactNativeMavenCacheEnabled_withProjectProperty_ignoresEnvironmentVariable() {
val project = createProject()
project.extensions.extraProperties.set("react.internal.reactNativeMavenCacheEnabled", "false")

assertThat(project.isReactNativeMavenCacheEnabled("true")).isFalse()
}

@Test
fun configureRepositories_snapshotRepoHasHigherPriorityThanMavenCentral() {
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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.

require "test/unit"
require_relative "../utils.rb"
require_relative "../../../sdks/hermes-engine/hermes-utils.rb"

class MavenCacheFlagTests < Test::Unit::TestCase
def setup
@original_value = ENV['RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED']
ENV.delete('RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED')
end

def teardown
if @original_value == nil
ENV.delete('RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED')
else
ENV['RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED'] = @original_value
end
end

def test_mavenCache_isDisabledByDefault
assert_false(ReactNativePodsUtils.react_native_maven_cache_enabled?)
assert_false(react_native_maven_cache_enabled?)
end

def test_mavenCache_isEnabledWhenExplicitlySetToTrue
ENV['RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED'] = 'true'

assert_true(ReactNativePodsUtils.react_native_maven_cache_enabled?)
assert_true(react_native_maven_cache_enabled?)
end

def test_mavenCache_isDisabledWhenExplicitlySetToFalse
ENV['RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED'] = 'false'

assert_false(ReactNativePodsUtils.react_native_maven_cache_enabled?)
assert_false(react_native_maven_cache_enabled?)
end
end
21 changes: 11 additions & 10 deletions packages/react-native/scripts/cocoapods/rncore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,16 +364,17 @@ def self.generate_plist_content(mappings)
end

def self.stable_tarball_url(version, build_type, dsyms = false)
## You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded.
## The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central.
maven_repo_url =
ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != "" ?
ENV['ENTERPRISE_REPOSITORY'] :
"https://repo1.maven.org/maven2"
candidates = stable_tarball_urls(version, build_type, dsyms)
return candidates.find { |url| artifact_exists(url) } || candidates.first
end

def self.stable_tarball_urls(version, build_type, dsyms = false)
group = "com/facebook/react"
# Sample url from Maven:
# https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.0/react-native-artifacts-0.81.0-reactnative-core-debug.tar.gz
return "#{maven_repo_url}/#{group}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-reactnative-core-#{dsyms ? "dSYM-" : ""}#{build_type.to_s}.tar.gz"
return ReactNativePodsUtils.maven_repository_urls().map { |maven_repo_url|
# Sample url from Maven:
# https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.0/react-native-artifacts-0.81.0-reactnative-core-debug.tar.gz
"#{maven_repo_url}/#{group}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-reactnative-core-#{dsyms ? "dSYM-" : ""}#{build_type.to_s}.tar.gz"
}
end

def self.nightly_tarball_url(version, configuration, dsyms = false)
Expand Down Expand Up @@ -475,7 +476,7 @@ def self.download_rncore_tarball(react_native_path, tarball_url, version, config
end

def self.release_artifact_exists(version)
return artifact_exists(stable_tarball_url(version, :debug))
return stable_tarball_urls(version, :debug).any? { |url| artifact_exists(url) }
end

def self.nightly_artifact_exists(version)
Expand Down
19 changes: 9 additions & 10 deletions packages/react-native/scripts/cocoapods/rndependencies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,15 @@ def self.podspec_source_download_prebuild_release_tarball()
end

def self.release_tarball_url(version, build_type)
## You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded.
## The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central.
maven_repo_url =
ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != "" ?
ENV['ENTERPRISE_REPOSITORY'] :
"https://repo1.maven.org/maven2"
candidates = release_tarball_urls(version, build_type)
return candidates.find { |url| artifact_exists(url) } || candidates.first
end

def self.release_tarball_urls(version, build_type)
group = "com/facebook/react"
# Sample url from Maven:
# https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.79.0-rc.0/react-native-artifacts-0.79.0-rc.0-reactnative-dependencies-debug.tar.gz
return "#{maven_repo_url}/#{group}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-reactnative-dependencies-#{build_type.to_s}.tar.gz"
return ReactNativePodsUtils.maven_repository_urls().map { |maven_repo_url|
"#{maven_repo_url}/#{group}/react-native-artifacts/#{version}/react-native-artifacts-#{version}-reactnative-dependencies-#{build_type.to_s}.tar.gz"
}
end

def self.nightly_tarball_url(version, build_type)
Expand Down Expand Up @@ -301,7 +300,7 @@ def self.download_rndeps_tarball(react_native_path, tarball_url, version, config
end

def self.release_artifact_exists(version)
return artifact_exists(release_tarball_url(version, :debug))
return release_tarball_urls(version, :debug).any? { |url| artifact_exists(url) }
end

def self.nightly_artifact_exists(version)
Expand Down
24 changes: 24 additions & 0 deletions packages/react-native/scripts/cocoapods/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,35 @@

# Utilities class for React Native Cocoapods
class ReactNativePodsUtils
MAVEN_CENTRAL_REPOSITORY = "https://repo1.maven.org/maven2"
REACT_NATIVE_MAVEN_CACHE_REPOSITORY = "https://repo.reactnative.dev/maven2"

# URI::File.build validates path components as ASCII, so escape the filesystem path first.
def self.local_file_uri(path)
URI::File.build(path: URI::DEFAULT_PARSER.escape(path)).to_s
end

def self.maven_repository_urls()
## You can use the `ENTERPRISE_REPOSITORY` variable to customise the base url from which artifacts will be downloaded.
## The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central.
if ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != ""
return [ENV['ENTERPRISE_REPOSITORY'].delete_suffix("/")]
end

# Keep the React Native Maven cache before Maven Central so cached artifacts are tried first
# and Maven Central remains the fallback.
return react_native_maven_cache_enabled? ?
[REACT_NATIVE_MAVEN_CACHE_REPOSITORY, MAVEN_CENTRAL_REPOSITORY] :
[MAVEN_CENTRAL_REPOSITORY]
end

def self.react_native_maven_cache_enabled?()
value = ENV['RCT_REACT_NATIVE_MAVEN_CACHE_ENABLED']
return false if value == nil || value == ""

value.downcase != "false" && value != "0"
end

def self.warn_if_not_on_arm64
if SysctlChecker.new().call_sysctl_arm64() == 1 && !Environment.new().ruby_platform().include?('arm64')
Pod::UI.warn 'Do not use "pod install" from inside Rosetta2 (x86_64 emulation on arm64).'
Expand Down
Loading