Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class ReactPlugin : Plugin<Project> {
val versionAndGroupStrings =
readVersionAndGroupStrings(project, propertiesFile, hermesVersionPropertiesFile)
configureDependencies(project, versionAndGroupStrings)
configureRepositories(project, versionAndGroupStrings.isNightly)
configureRepositories(project, versionAndGroupStrings)
}

configureReactNativeNdk(project, extension)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.gradle.api.artifacts.repositories.MavenArtifactRepository
internal object DependencyUtils {
private const val REACT_NATIVE_MAVEN_MIRROR_URL = "https://repo.reactnative.dev/maven2"
private const val REACT_NATIVE_MAVEN_MIRROR_ENABLED_ENV = "RCT_REACT_NATIVE_MAVEN_MIRROR_ENABLED"
private const val UNPUBLISHED_MAVEN_VERSION = "1000.0.0"

internal data class Coordinates(
val versionString: String,
Expand Down Expand Up @@ -124,6 +125,41 @@ internal object DependencyUtils {
}
}

/**
* Configures repositories without asking remote repositories for versions that are known to be
* unpublished.
*/
fun configureRepositories(project: Project, coordinates: Coordinates) {
configureRepositories(project, coordinates.isNightly)

project.rootProject.allprojects { eachProject ->
eachProject.repositories.withType(MavenArtifactRepository::class.java).configureEach { repo ->
if (repo.url.scheme != "file") {
repo.content { content ->
if (!coordinates.versionString.isMavenArtifactVersionPublished()) {
setOf(DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP, coordinates.reactGroupString)
.forEach { group ->
content.excludeVersion(group, "react-native", UNPUBLISHED_MAVEN_VERSION)
content.excludeVersion(group, "react-android", UNPUBLISHED_MAVEN_VERSION)
}
}
if (!coordinates.hermesVersionString.isMavenArtifactVersionPublished()) {
setOf(
DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP,
DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP,
coordinates.hermesGroupString,
)
.forEach { group ->
content.excludeVersion(group, "hermes-engine", UNPUBLISHED_MAVEN_VERSION)
content.excludeVersion(group, "hermes-android", UNPUBLISHED_MAVEN_VERSION)
}
}
}
}
}
}
}

/**
* This method takes care of configuring the resolution strategy for both the app and all the 3rd
* party libraries which are auto-linked. Specifically it takes care of:
Expand Down Expand Up @@ -303,6 +339,9 @@ internal object DependencyUtils {

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

internal fun String.isMavenArtifactVersionPublished(): Boolean =
this != UNPUBLISHED_MAVEN_VERSION

internal fun Project.exclusiveEnterpriseRepository() =
when {
hasProperty(SCOPED_EXCLUSIVE_ENTEPRISE_REPOSITORY) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ 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.isMavenArtifactVersionPublished
import com.facebook.react.utils.DependencyUtils.isNightly
import com.facebook.react.utils.DependencyUtils.isReactNativeMavenMirrorEnabled
import com.facebook.react.utils.DependencyUtils.mavenRepoFromURI
import com.facebook.react.utils.DependencyUtils.mavenRepoFromUrl
import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
import com.facebook.react.utils.DependencyUtils.shouldAddJitPack
import com.sun.net.httpserver.HttpServer
import java.net.InetSocketAddress
import java.net.URI
import java.util.concurrent.atomic.AtomicInteger
import org.assertj.core.api.Assertions.assertThat
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.testfixtures.ProjectBuilder
Expand Down Expand Up @@ -78,6 +82,41 @@ class DependencyUtilsTest {
.isNotNull()
}

@Test
fun configureRepositories_withUnpublishedVersion_doesNotQueryRemoteRepository() {
val requests = AtomicInteger()
val server = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0)
server.createContext("/") { exchange ->
requests.incrementAndGet()
exchange.sendResponseHeaders(404, -1)
exchange.close()
}
server.start()

try {
val project = createProject()
project.extensions.extraProperties.set(
"exclusiveEnterpriseRepository",
"http://127.0.0.1:${server.address.port}",
)
configureRepositories(project, DependencyUtils.Coordinates("1000.0.0", "4.5.6"))
(project.repositories.first() as MavenArtifactRepository).isAllowInsecureProtocol = true

val published = project.configurations.create("published")
project.dependencies.add(published.name, "com.facebook.react:react-android:0.88.0")
assertThat(runCatching { published.resolve() }.isFailure).isTrue()
assertThat(requests.get()).isGreaterThan(0)

requests.set(0)
val unpublished = project.configurations.create("unpublished")
project.dependencies.add(unpublished.name, "com.facebook.react:react-android:1000.0.0")
assertThat(runCatching { unpublished.resolve() }.isFailure).isTrue()
assertThat(requests.get()).isZero()
} finally {
server.stop(0)
}
}

@Test
fun configureRepositories_containsGoogleRepo() {
val repositoryURI = URI.create("https://dl.google.com/dl/android/maven2/")
Expand Down Expand Up @@ -414,6 +453,32 @@ class DependencyUtilsTest {
assertThat(project.configurations.first().resolutionStrategy.forcedModules.isEmpty()).isTrue()
}

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

configureDependencies(project, DependencyUtils.Coordinates("1000.0.0", "4.5.6"))

val forcedModules = project.configurations.first().resolutionStrategy.forcedModules
assertThat(
forcedModules.any {
it.toString() == "com.facebook.react:react-android:1000.0.0"
},
)
.isTrue()
assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:4.5.6" })
.isTrue()

val dependencySubstitutions =
getDependencySubstitutions(DependencyUtils.Coordinates("1000.0.0", "4.5.6"))
assertThat(
dependencySubstitutions.any {
it.second == "com.facebook.react:react-android:1000.0.0"
},
)
.isTrue()
}

@Test
fun configureDependencies_withVersionString_appliesResolutionStrategy() {
val project = createProject()
Expand Down Expand Up @@ -577,6 +642,12 @@ class DependencyUtilsTest {
assertThat(hermesVersionString).isEqualTo("1000.0.0")
}

@Test
fun isMavenArtifactVersionPublished_withMainVersion_returnsFalse() {
assertThat("1000.0.0".isMavenArtifactVersionPublished()).isFalse()
assertThat("0.88.0".isMavenArtifactVersionPublished()).isTrue()
}

@Test
fun readVersionString_withNightlyVersionString_returnsSnapshotVersion() {
val propertiesFile =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

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

class MavenMirrorFlagTests < Test::Unit::TestCase
Expand Down Expand Up @@ -39,4 +41,31 @@ def test_mavenMirror_isDisabledWhenExplicitlySetToFalse
assert_false(ReactNativePodsUtils.react_native_maven_mirror_enabled?)
assert_false(react_native_maven_mirror_enabled?)
end

def test_unpublishedVersion_skipsAllArtifactLookups
assert_false(ReactNativePodsUtils.maven_artifact_version_published?('1000.0.0'))
assert_false(ReactNativePodsUtils.artifact_exists?('https://repo.reactnative.dev/maven2/example/1000.0.0/example.tar.gz'))
assert_false(ReactNativePodsUtils.artifact_exists?('https://central.sonatype.com/example/1000.0.0-SNAPSHOT/example.tar.gz'))
assert_false(ReactNativeCoreUtils.release_artifact_exists('1000.0.0'))
assert_false(ReactNativeCoreUtils.nightly_artifact_exists('1000.0.0'))
assert_false(ReactNativeDependenciesUtils.release_artifact_exists('1000.0.0'))
assert_false(ReactNativeDependenciesUtils.nightly_artifact_exists('1000.0.0'))
assert_false(release_artifact_exists('1000.0.0'))
assert_false(hermes_artifact_exists('https://repo.reactnative.dev/maven2/example/1000.0.0/example.tar.gz'))

assert_equal(
ReactNativeCoreUtils.stable_tarball_urls('1000.0.0', :debug).first,
ReactNativeCoreUtils.stable_tarball_url('1000.0.0', :debug),
)
assert_equal(
ReactNativeDependenciesUtils.release_tarball_urls('1000.0.0', :debug).first,
ReactNativeDependenciesUtils.release_tarball_url('1000.0.0', :debug),
)
assert_equal(release_tarball_urls('1000.0.0', :debug).first, release_tarball_url('1000.0.0', :debug))
end

def test_releaseVersion_isPublished
assert_true(ReactNativePodsUtils.maven_artifact_version_published?('0.88.0'))
assert_true(ReactNativePodsUtils.maven_artifact_version_published?('1000.0.0-abcdef123'))
end
end
4 changes: 4 additions & 0 deletions packages/react-native/scripts/cocoapods/rncore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ def self.stable_tarball_urls(version, build_type, dsyms = false)
end

def self.nightly_tarball_url(version, configuration, dsyms = false)
return "" if !ReactNativePodsUtils.maven_artifact_version_published?(version)

artefact_coordinate = "react-native-artifacts"
artefact_name = "reactnative-core-#{dsyms ? "dSYM-" : ""}#{configuration ? configuration : "debug"}.tar.gz"
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artefact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
Expand Down Expand Up @@ -469,6 +471,8 @@ def self.release_artifact_exists(version)
end

def self.nightly_artifact_exists(version)
return false if !ReactNativePodsUtils.maven_artifact_version_published?(version)

return artifact_exists(nightly_tarball_url(version, :debug).gsub("\\", ""))
end

Expand Down
4 changes: 4 additions & 0 deletions packages/react-native/scripts/cocoapods/rndependencies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ def self.release_tarball_urls(version, build_type)
end

def self.nightly_tarball_url(version, build_type)
return "" if !ReactNativePodsUtils.maven_artifact_version_published?(version)

artifact_coordinate = "react-native-artifacts"
artifact_name = "reactnative-dependencies-#{build_type.to_s}.tar.gz"
xml_url = "https://central.sonatype.com/repository/maven-snapshots/com/facebook/react/#{artifact_coordinate}/#{version}-SNAPSHOT/maven-metadata.xml"
Expand Down Expand Up @@ -377,6 +379,8 @@ def self.release_artifact_exists(version)
end

def self.nightly_artifact_exists(version)
return false if !ReactNativePodsUtils.maven_artifact_version_published?(version)

return artifact_exists(nightly_tarball_url(version, :debug).gsub("\\", ""))
end

Expand Down
9 changes: 9 additions & 0 deletions packages/react-native/scripts/cocoapods/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class ReactNativePodsUtils
MAVEN_CENTRAL_REPOSITORY = "https://repo1.maven.org/maven2"
REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = "https://repo.reactnative.dev/maven2"
UNPUBLISHED_MAVEN_VERSION = "1000.0.0"

# Opt-in removal of the legacy TurboModule and component interop layers. Both are
# off by default and will become the default in a future React Native release.
Expand Down Expand Up @@ -49,6 +50,11 @@ def self.react_native_maven_mirror_enabled?()
value.downcase != "false" && value != "0"
end

def self.maven_artifact_version_published?(version)
# 1000.0.0 identifies a source checkout on main and is never published to Maven.
return version != UNPUBLISHED_MAVEN_VERSION
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 Expand Up @@ -833,6 +839,9 @@ def self.resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil
# (DNS failure, no route, ...) the probe is left uncached so that a
# transient hiccup doesn't permanently mark the artifact as missing.
def self.artifact_exists?(tarball_url)
unpublished_version = Regexp.escape(UNPUBLISHED_MAVEN_VERSION)
return false if tarball_url.match?(%r{/#{unpublished_version}(?:-SNAPSHOT)?/})

unless @@artifact_exists_cache.key?(tarball_url)
# -L is used to follow redirects, useful for the nightlies
# The url is wrapped in quotes to avoid escaping & and ?.
Expand Down
52 changes: 52 additions & 0 deletions packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* 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
* @noflow
*/

'use strict';

const {
computeNightlyTarballURL,
isMavenArtifactVersionPublished,
} = require('../utils');

describe('isMavenArtifactVersionPublished', () => {
it('rejects the unpublished main version', () => {
expect(isMavenArtifactVersionPublished('1000.0.0')).toBe(false);
});

it.each([
'0.88.0',
'0.89.0-nightly-20260909-abcdef123',
'1000.0.0-abcdef123',
])('accepts published artifact version %s', version => {
expect(isMavenArtifactVersionPublished(version)).toBe(true);
});
});

describe('computeNightlyTarballURL', () => {
it('does not query snapshot metadata for the unpublished main version', async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = jest.fn();

try {
await expect(
computeNightlyTarballURL(
'1000.0.0',
'Debug',
'react',
'react-native-artifacts',
'reactnative-dependencies-debug.tar.gz',
),
).rejects.toThrow(/artifacts are not published/);
expect(globalThis.fetch).not.toHaveBeenCalled();
} finally {
globalThis.fetch = originalFetch;
}
});
});
15 changes: 14 additions & 1 deletion packages/react-native/scripts/ios-prebuild/hermes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
* @format
*/

const {createLogger, getMavenRepositoryUrls} = require('./utils');
const {
createLogger,
getMavenRepositoryUrls,
isMavenArtifactVersionPublished,
} = require('./utils');
const {execSync} = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
Expand Down Expand Up @@ -204,6 +208,10 @@ async function findExistingTarballUrl(
version /*: string */,
buildType /*: BuildFlavor */,
) /*: Promise<?string> */ {
if (!isMavenArtifactVersionPublished(version)) {
return null;
}

const candidates = getTarballUrls(version, buildType);
for (const url of candidates) {
if (await hermesArtifactExists(url)) {
Expand Down Expand Up @@ -341,6 +349,11 @@ async function downloadHermesTarball(
const tmpFile = `${artifactsPath}/hermes-ios.download`;
try {
fs.mkdirSync(artifactsPath, {recursive: true});
if (!isMavenArtifactVersionPublished(version)) {
throw new Error(
`Maven artifacts are not published for the development version ${version}`,
);
}
hermesLog(`Downloading Hermes tarball from ${tarballUrl}`);

const response /*: Response */ = await fetch(tarballUrl);
Expand Down
Loading
Loading