diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 890edb58bd86..f3b5072dce93 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -95,7 +95,7 @@ class ReactPlugin : Plugin { val versionAndGroupStrings = readVersionAndGroupStrings(project, propertiesFile, hermesVersionPropertiesFile) configureDependencies(project, versionAndGroupStrings) - configureRepositories(project, versionAndGroupStrings.isNightly) + configureRepositories(project, versionAndGroupStrings) } configureReactNativeNdk(project, extension) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt index 4b4ffd0c1dd2..3b52d55d4659 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/DependencyUtils.kt @@ -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, @@ -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: @@ -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) -> diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt index fed57c2025c3..17aca0a4afbf 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/DependencyUtilsTest.kt @@ -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 @@ -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/") @@ -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() @@ -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 = diff --git a/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb b/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb index 9f75f25fd7ae..c5ed599ee714 100644 --- a/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb +++ b/packages/react-native/scripts/cocoapods/__tests__/maven_mirror_flag-test.rb @@ -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 @@ -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 diff --git a/packages/react-native/scripts/cocoapods/rncore.rb b/packages/react-native/scripts/cocoapods/rncore.rb index e56ce29b8a15..f588ad771f0b 100644 --- a/packages/react-native/scripts/cocoapods/rncore.rb +++ b/packages/react-native/scripts/cocoapods/rncore.rb @@ -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" @@ -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 diff --git a/packages/react-native/scripts/cocoapods/rndependencies.rb b/packages/react-native/scripts/cocoapods/rndependencies.rb index c38755263963..eb4bde3b3cfe 100644 --- a/packages/react-native/scripts/cocoapods/rndependencies.rb +++ b/packages/react-native/scripts/cocoapods/rndependencies.rb @@ -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" @@ -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 diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index a31cab7442f6..3038897c8545 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -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. @@ -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).' @@ -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 ?. diff --git a/packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js b/packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js new file mode 100644 index 000000000000..7d443bd217b1 --- /dev/null +++ b/packages/react-native/scripts/ios-prebuild/__tests__/utils-test.js @@ -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; + } + }); +}); diff --git a/packages/react-native/scripts/ios-prebuild/hermes.js b/packages/react-native/scripts/ios-prebuild/hermes.js index 7e20ddf4f978..8956337fd3f2 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -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'); @@ -204,6 +208,10 @@ async function findExistingTarballUrl( version /*: string */, buildType /*: BuildFlavor */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + return null; + } + const candidates = getTarballUrls(version, buildType); for (const url of candidates) { if (await hermesArtifactExists(url)) { @@ -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); diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index bcc325ee0ea6..50b279709373 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -14,6 +14,7 @@ const { computeNightlyTarballURL, createLogger, getMavenRepositoryUrls, + isMavenArtifactVersionPublished, } = require('./utils'); const {execSync} = require('node:child_process'); const fs = require('node:fs'); @@ -236,6 +237,10 @@ async function findExistingTarballUrl( version /*: string */, buildType /*: BuildFlavor */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + return null; + } + const candidates = getTarballUrls(version, buildType); for (const url of candidates) { if (await reactNativeDependenciesArtifactExists(url)) { @@ -401,6 +406,11 @@ async function downloadReactNativeDependenciesTarball( const tmpFile = `${artifactsPath}/reactnative-dependencies.download`; try { fs.mkdirSync(artifactsPath, {recursive: true}); + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } dependencyLog( `Downloading ReactNativeDependencies tarball from ${tarballUrl}`, ); diff --git a/packages/react-native/scripts/ios-prebuild/utils.js b/packages/react-native/scripts/ios-prebuild/utils.js index 036b398f750d..13e2aefe2213 100644 --- a/packages/react-native/scripts/ios-prebuild/utils.js +++ b/packages/react-native/scripts/ios-prebuild/utils.js @@ -17,6 +17,7 @@ const path = require('node:path'); const MAVEN_CENTRAL_REPOSITORY = 'https://repo1.maven.org/maven2'; const REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = 'https://repo.reactnative.dev/maven2'; +const UNPUBLISHED_MAVEN_VERSION = '1000.0.0'; /** * Creates a folder if it does not exist @@ -96,6 +97,12 @@ async function computeNightlyTarballURL( artifactCoordinate /*: string */, artifactName /*: string */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } + const xmlUrl = `https://central.sonatype.com/repository/maven-snapshots/com/facebook/${subGroup}/${artifactCoordinate}/${version}-SNAPSHOT/maven-metadata.xml`; const response = await fetch(xmlUrl); @@ -156,6 +163,11 @@ function isReactNativeMavenMirrorEnabled() /*: boolean */ { return value.toLowerCase() !== 'false' && value !== '0'; } +function isMavenArtifactVersionPublished(version /*: string */) /*: boolean */ { + // 1000.0.0 identifies a source checkout on main and is never published to Maven. + return version !== UNPUBLISHED_MAVEN_VERSION; +} + module.exports = { createFolderIfNotExists, findFirst, @@ -163,4 +175,5 @@ module.exports = { createLogger, computeNightlyTarballURL, getMavenRepositoryUrls, + isMavenArtifactVersionPublished, }; diff --git a/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js b/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js index 3faaf5e8b087..2a24dbff8c5d 100644 --- a/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js +++ b/packages/react-native/scripts/spm/__tests__/download-spm-artifacts-test.js @@ -18,6 +18,7 @@ const { formatBytes, formatSpeed, hermesReleaseUrls, + isMavenArtifactVersionPublished, mavenRepositoryUrls, reactNativeMavenMirrorEnabled, resolveCacheSlotVersion, @@ -377,6 +378,27 @@ describe('mavenRepositoryUrls', () => { // --------------------------------------------------------------------------- describe('release URL builders', () => { + it('preserves Maven URLs for the unpublished main version', () => { + expect(isMavenArtifactVersionPublished('1000.0.0')).toBe(false); + expect(rnCoreReleaseUrls('1000.0.0', 'debug')).toHaveLength(2); + expect(rnDepsReleaseUrls('1000.0.0', 'debug')).toHaveLength(2); + expect(hermesReleaseUrls('1000.0.0', 'debug')).toHaveLength(2); + }); + + it('does not probe Maven for the unpublished main version', async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = jest.fn(); + + try { + await expect( + exists(rnCoreReleaseUrls('1000.0.0', 'debug')[0]), + ).resolves.toBe(false); + expect(globalThis.fetch).not.toHaveBeenCalled(); + } finally { + globalThis.fetch = originalFetch; + } + }); + it('rnCoreReleaseUrls builds a candidate per repository for the reactnative-core classifier', () => { const suffix = '/com/facebook/react/react-native-artifacts/0.85.0/' + @@ -558,6 +580,20 @@ describe('resolveSnapshotUrl', () => { ); }); + it('does not query snapshot metadata for the unpublished main version', async () => { + globalThis.fetch = jest.fn(); + + await expect( + resolveSnapshotUrl( + '1000.0.0', + 'react', + 'react-native-artifacts', + 'reactnative-core-debug.tar.gz', + ), + ).rejects.toThrow(/artifacts are not published/); + expect(globalThis.fetch).not.toHaveBeenCalled(); + }); + it('throws when the metadata request fails', async () => { globalThis.fetch = routerFetch({ 'maven-metadata.xml': {ok: false, status: 500}, diff --git a/packages/react-native/scripts/spm/download-spm-artifacts.js b/packages/react-native/scripts/spm/download-spm-artifacts.js index f84dc017b60b..82cb1dfc4ea9 100644 --- a/packages/react-native/scripts/spm/download-spm-artifacts.js +++ b/packages/react-native/scripts/spm/download-spm-artifacts.js @@ -141,9 +141,22 @@ function parseArgs(argv /*: Array */) /*: DownloadArgs */ { const MAVEN_CENTRAL_REPOSITORY = 'https://repo1.maven.org/maven2'; const REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = 'https://repo.reactnative.dev/maven2'; +const UNPUBLISHED_MAVEN_VERSION = '1000.0.0'; const MAVEN_SNAPSHOT = 'https://central.sonatype.com/repository/maven-snapshots'; +function isMavenArtifactVersionPublished(version /*: string */) /*: boolean */ { + // 1000.0.0 identifies a source checkout on main and is never published to Maven. + return version !== UNPUBLISHED_MAVEN_VERSION; +} + +function isMavenArtifactUrlPublished(url /*: string */) /*: boolean */ { + return ( + !url.includes(`/${UNPUBLISHED_MAVEN_VERSION}/`) && + !url.includes(`/${UNPUBLISHED_MAVEN_VERSION}-SNAPSHOT/`) + ); +} + /** * The mirror is ON unless RCT_REACT_NATIVE_MAVEN_MIRROR_ENABLED is * explicitly "false"/"0". @@ -219,6 +232,12 @@ async function resolveSnapshotUrl( coordinate /*: string */, artifactName /*: string */, ) /*: Promise */ { + if (!isMavenArtifactVersionPublished(version)) { + throw new Error( + `Maven artifacts are not published for the development version ${version}`, + ); + } + const metadataUrl = `${MAVEN_SNAPSHOT}/com/facebook/${subGroup}/${coordinate}/` + `${version}-SNAPSHOT/maven-metadata.xml`; @@ -353,6 +372,10 @@ async function resolveLatestV1Version() /*: Promise */ { } async function exists(url /*: string */) /*: Promise */ { + if (!isMavenArtifactUrlPublished(url)) { + return false; + } + try { // $FlowFixMe[incompatible-call] global fetch not in Flow stubs const res = await fetch(url, {method: 'HEAD'}); @@ -1510,6 +1533,7 @@ module.exports = { validateArtifactsCache, // Exposed for unit tests (pure / fetch-stubbable helpers). mavenRepositoryUrls, + isMavenArtifactVersionPublished, reactNativeMavenMirrorEnabled, rnCoreReleaseUrls, rnDepsReleaseUrls, diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index 71ef0481bbd4..ee13fc7c9240 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -9,6 +9,7 @@ ENV_BUILD_FROM_SOURCE = "RCT_BUILD_HERMES_FROM_SOURCE" MAVEN_CENTRAL_REPOSITORY = "https://repo1.maven.org/maven2" REACT_NATIVE_MAVEN_MIRROR_REPOSITORY = "https://repo.reactnative.dev/maven2" +UNPUBLISHED_HERMES_VERSION = "1000.0.0" # Memoized results of requests to the Maven repositories (mirror or central). # hermes-engine.podspec is evaluated several times during a single @@ -356,6 +357,9 @@ def resolve_url_redirects(url) # Parameters # - tarball_url: the URL of the Hermes artifact to probe def hermes_artifact_exists(tarball_url) + unpublished_version = Regexp.escape(UNPUBLISHED_HERMES_VERSION) + return false if tarball_url.match?(%r{/#{unpublished_version}(?:-SNAPSHOT)?/}) + unless HERMES_ARTIFACT_EXISTS_CACHE.key?(tarball_url) # -L is used to follow redirects, useful for the nightlies # I also needed to wrap the url in quotes to avoid escaping & and ?.