From ea458cf91ffd0f064a753f5bd0353c75c3edf0ab Mon Sep 17 00:00:00 2001 From: Ivan Matkov Date: Mon, 3 Aug 2026 17:30:01 +0200 Subject: [PATCH] Allow redirecting to unreleased androidx builds A library state could only be merged into jb-main after Google published it to Google Maven, because that is the first moment the redirect target of an artifact redirection becomes resolvable. Google publishes every build cut to androidx.dev about a week earlier, but pointing the fork at it by hand needs an unfiltered repository: one build's repository answers for every androidx coordinate, and since library groups are released from different commits while sharing the version string X.Y.Z-SNAPSHOT, a group pinned to one build silently resolves from another. Record the build instead. redirectversions.toml gains an optional [[snapshots]] array keyed by androidx.dev build id, listing the group prefixes whose [versions] entry is a -SNAPSHOT. RedirectVersionsService parses and validates it: every listed group exists in [versions] and is a -SNAPSHOT, every -SNAPSHOT is listed exactly once, and the repository flavour is known. Each entry becomes one maven repository filtered on group prefix AND exact version, so overlapping prefixes and two groups on two builds stay separated, and a released version is never accepted by a snapshot repository. The repositories are declared from settings-fork.gradle, the single site that configures project repositories, via a new fork-only script rather than repos.gradle: that file is shared byte-identically with AOSP and also configures buildscript classpaths, which never resolve redirect coordinates. The script carries its own tomlj classpath because a settings buildscript classpath is not visible to the script plugins applied from it. jbVerifyDependencyVersions ranks a -SNAPSHOT below every publishable version, which would fail the Dev and personal Snapshot pipelines as hard as Release and defeat the purpose. Exempt a dependency only when the project version carries semver build metadata -- which a public release version never does -- and the dependency matches a registered snapshot prefix at its exact recorded version. Deliberately left out: automating the flip back to released versions, and any relaxation of the release gate. A snapshot-backed state must not reach a public release, and androidx.dev per-build repositories are not kept forever. printAndroidxSnapshots feeds the same registry to the Gradle plugin build of compose-multiplatform, so a dev build that redirects to snapshots stays consumable outside this repository. --- buildSrc-fork/androidxSnapshotRepos.gradle | 170 +++++++++++++ .../androidx/build/ArtifactRedirection.kt | 225 ++++++++++++++++-- .../build/JetBrainsAndroidXRootImplPlugin.kt | 2 + .../JetBrainsVerifyDependencyVersionsTask.kt | 37 +++ redirectversions.toml | 19 ++ settings-fork.gradle | 3 + 6 files changed, 439 insertions(+), 17 deletions(-) create mode 100644 buildSrc-fork/androidxSnapshotRepos.gradle diff --git a/buildSrc-fork/androidxSnapshotRepos.gradle b/buildSrc-fork/androidxSnapshotRepos.gradle new file mode 100644 index 0000000000000..da605d0b97994 --- /dev/null +++ b/buildSrc-fork/androidxSnapshotRepos.gradle @@ -0,0 +1,170 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import groovy.transform.Field +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.tomlj.Toml + +import java.util.regex.Pattern + +buildscript { + // The settings buildscript classpath is not visible to script plugins applied from it, so this + // script carries its own. tomlj is already trusted in gradle/verification-metadata.xml. + repositories { + mavenCentral() + } + dependencies { + classpath("org.tomlj:tomlj:1.0.0") + } +} + +/* + * Declares the androidx.dev repositories serving the artifact redirects that `redirectversions.toml` + * pins to a `-SNAPSHOT`, i.e. to an androidx build Google has cut but not yet published to Google + * Maven. Each `[[snapshots]]` entry becomes one repository, filtered down to exactly the group + * prefixes and versions recorded for it. + * + * Kept out of `repos.gradle` — that file is shared byte-identically with AOSP and also configures + * buildscript classpaths, which never resolve redirect coordinates. + * + * The registry is validated in `ArtifactRedirection.kt`; this reader reports only what it must read. + */ + +@Field final String TOML_FILE_NAME = "redirectversions.toml" + +@Field List snapshotBuildsCache = null + +ext.androidxSnapshots = new Properties() +ext.androidxSnapshots.addRepositories = this.&addRepositories +ext.androidxSnapshots.disableChangingModuleCache = this.&disableChangingModuleCache + +String repositoryUrl(String flavour, String buildId) { + switch (flavour) { + case "kmp": + return "https://androidx.dev/kmp/builds/$buildId/artifacts/snapshots/repository" + case "androidx": + return "https://androidx.dev/snapshots/builds/$buildId/artifacts/repository" + default: + throw new GradleException("$TOML_FILE_NAME: [[snapshots]] buildId \"$buildId\" has " + + "repo = \"$flavour\"; known flavours are \"kmp\" and \"androidx\".") + } +} + +/** + * Parses `[[snapshots]]` into one entry per androidx.dev build: + * `[buildId: String, url: String, versionsByGroup: Map]`. + * Empty when every redirect is on a released version. + */ +List snapshotBuilds(File rootDir) { + if (snapshotBuildsCache != null) return snapshotBuildsCache + + def tomlFile = new File(rootDir, TOML_FILE_NAME) + if (!tomlFile.exists()) { + snapshotBuildsCache = [] + return snapshotBuildsCache + } + def parsed = Toml.parse(tomlFile.toPath()) + if (parsed.hasErrors()) { + def issues = parsed.errors().collect { "$TOML_FILE_NAME:${it.position()}: ${it.message}" } + throw new GradleException("$TOML_FILE_NAME has issues.\n${issues.join("\n")}") + } + def entries = parsed.getArray("snapshots") + if (entries == null) { + snapshotBuildsCache = [] + return snapshotBuildsCache + } + def versions = parsed.getTable("versions") + if (versions == null) { + throw new GradleException("$TOML_FILE_NAME is missing the [versions] table") + } + + def builds = [] + for (int i = 0; i < entries.size(); i++) { + def entry = entries.getTable(i) + def buildId = entry.getString("buildId") + if (buildId == null) { + throw new GradleException("$TOML_FILE_NAME: [[snapshots]] entry #${i + 1} must declare " + + "a string \"buildId\" naming the androidx.dev build") + } + def groups = entry.getArray("groups") + if (groups == null || !groups.containsStrings()) { + throw new GradleException("$TOML_FILE_NAME: [[snapshots]] buildId \"$buildId\" must " + + "declare a \"groups\" array of redirect group prefixes") + } + def versionsByGroup = [:] + for (int g = 0; g < groups.size(); g++) { + def group = groups.getString(g) + // tomlj treats a dotted String key as a path lookup, so a dotted group key must be read + // via the literal single-segment List overload. + def version = versions.getString([group]) + if (version == null) { + throw new GradleException("$TOML_FILE_NAME: [[snapshots]] build \"$buildId\" lists " + + "group \"$group\", which has no entry in the [versions] table.") + } + versionsByGroup[group] = version + } + builds << [ + buildId : buildId, + url : repositoryUrl(entry.getString("repo") ?: "kmp", buildId), + versionsByGroup: versionsByGroup, + ] + } + snapshotBuildsCache = builds + return snapshotBuildsCache +} + +/** + * Adds one filtered repository per registered androidx.dev build to [handler]. No-op when the + * registry is empty, which is the state of a released branch. + */ +def addRepositories(RepositoryHandler handler, File rootDir) { + def added = snapshotBuilds(rootDir).collect { build -> + handler.maven { repo -> + repo.name = "androidxDevBuild${build.buildId}" + repo.url = build.url + repo.content { content -> + // Group prefix AND exact version. The version half is what keeps overlapping + // prefixes (androidx.compose vs androidx.compose.material3) and two groups pinned to + // two different builds apart: a repository answers only for what it was recorded for. + build.versionsByGroup.each { group, version -> + content.includeVersionByRegex( + Pattern.quote(group) + "(\\..*)?", + ".*", + Pattern.quote(version)) + } + } + } + } + if (!added.isEmpty()) { + // Ahead of `mavenLocal()` and the unfiltered Sonatype snapshots repo, both of which would + // otherwise be asked for these coordinates first. + handler.removeAll(added) + handler.addAll(0, added) + } +} + +/** + * Opts [project] out of Gradle's 24h changing-module cache while any redirect is on a snapshot. + * Google reuses the version string `X.Y.Z-SNAPSHOT` across builds, so moving the registry to a new + * buildId would otherwise keep serving the previous build's artifacts, silently. No-op on a released + * state; `--refresh-dependencies` remains the manual escape hatch. + */ +def disableChangingModuleCache(Project project) { + if (snapshotBuilds(project.rootDir).isEmpty()) return + project.configurations.configureEach { configuration -> + configuration.resolutionStrategy.cacheChangingModulesFor(0, "seconds") + } +} diff --git a/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/ArtifactRedirection.kt b/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/ArtifactRedirection.kt index 8d64b6803c2ba..9e15e85a29148 100644 --- a/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/ArtifactRedirection.kt +++ b/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/ArtifactRedirection.kt @@ -28,10 +28,49 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.tomlj.Toml import org.tomlj.TomlTable +/** + * The androidx.dev repository flavour an [AndroidxSnapshotBuild] was published to, selected by the + * `repo` field of a `[[snapshots]]` entry. + */ +enum class AndroidxSnapshotRepoFlavour(val id: String) { + /** The KMP build repository, carrying the multiplatform artifacts. */ + KMP("kmp"), + /** The Android-only build repository, for groups the KMP build does not carry. */ + ANDROIDX("androidx"); + + fun repositoryUrl(buildId: String): String = + when (this) { + KMP -> "https://androidx.dev/kmp/builds/$buildId/artifacts/snapshots/repository" + ANDROIDX -> "https://androidx.dev/snapshots/builds/$buildId/artifacts/repository" + } + + companion object { + const val DEFAULT_ID = "kmp" + + fun fromId(id: String): AndroidxSnapshotRepoFlavour? = values().firstOrNull { it.id == id } + } +} + +/** + * One `[[snapshots]]` entry of `redirectversions.toml`: the androidx.dev build a set of redirect + * group prefixes take their `-SNAPSHOT` version from. One build normally covers several groups, + * which is how Google cuts them. + */ +data class AndroidxSnapshotBuild( + val buildId: String, + val flavour: AndroidxSnapshotRepoFlavour, + val groups: List, +) { + val repositoryUrl: String + get() = flavour.repositoryUrl(buildId) +} + /** * Loads the artifact-redirection version registry from `redirectversions.toml` (repo root) once per * build. The `[versions]` table maps a redirect-coordinate group prefix (e.g. `androidx.compose`) to - * the `androidx.*` version the redirect points at. + * the `androidx.*` version the redirect points at. The optional `[[snapshots]]` array records which + * androidx.dev build backs each group whose version is a `-SNAPSHOT`, so those coordinates stay + * resolvable before Google publishes them to Google Maven. */ abstract class RedirectVersionsService : BuildService { interface Parameters : BuildServiceParameters { @@ -39,30 +78,128 @@ abstract class RedirectVersionsService : BuildService } + private data class Registry( + val versions: Map, + val snapshots: List, + ) + + private val registry: Registry by lazy { parseRegistry() } + /** Group prefix (e.g. `androidx.compose`) -> redirect version. */ - val versions: Map by lazy { + val versions: Map + get() = registry.versions + + /** `[[snapshots]]` entries in file order. Empty when every redirect is on a released version. */ + val snapshots: List + get() = registry.snapshots + + /** Group prefix -> the androidx.dev build its `-SNAPSHOT` version comes from. */ + val snapshotBuildsByGroup: Map by lazy { + registry.snapshots.flatMap { build -> build.groups.map { it to build } }.toMap() + } + + private fun parseRegistry(): Registry { + val fileName = parameters.tomlFileName val parsed = Toml.parse(parameters.tomlFileContents.get()) if (parsed.hasErrors()) { - val issues = - parsed.errors().joinToString("\n") { - "${parameters.tomlFileName}:${it.position()}: ${it.message}" - } - throw GradleException("${parameters.tomlFileName} has issues.\n$issues") + val issues = parsed.errors().joinToString("\n") { "$fileName:${it.position()}: ${it.message}" } + throw GradleException("$fileName has issues.\n$issues") } val table: TomlTable = parsed.getTable("versions") - ?: throw GradleException("${parameters.tomlFileName} is missing the [versions] table") + ?: throw GradleException("$fileName is missing the [versions] table") // tomlj treats a dotted String key as a path lookup, so the dotted group keys must be read // via the literal single-segment List overload (getString(listOf(key))), not getString(key). - table.keySet().associateWith { key -> - table.getString(listOf(key)) - ?: throw GradleException( - "${parameters.tomlFileName}: [versions] \"$key\" must be a string", + val versions = + table.keySet().associateWith { key -> + table.getString(listOf(key)) + ?: throw GradleException("$fileName: [versions] \"$key\" must be a string") + } + + val entries = parsed.getArray("snapshots") + val snapshots = + (0 until (entries?.size() ?: 0)).map { index -> + val entry = + entries!!.getTable(index) + ?: throw GradleException( + "$fileName: [[snapshots]] entry #${index + 1} must be a table", + ) + val buildId = + entry.getString("buildId") + ?: throw GradleException( + "$fileName: [[snapshots]] entry #${index + 1} must declare a string " + + "\"buildId\" naming the androidx.dev build", + ) + val flavourId = entry.getString("repo") ?: AndroidxSnapshotRepoFlavour.DEFAULT_ID + val flavour = + AndroidxSnapshotRepoFlavour.fromId(flavourId) + ?: throw GradleException( + "$fileName: [[snapshots]] buildId \"$buildId\" has repo = \"$flavourId\"; " + + "known flavours are " + + AndroidxSnapshotRepoFlavour.values().joinToString { "\"${it.id}\"" }, + ) + val groups = + entry.getArray("groups")?.takeIf { it.containsStrings() } + ?: throw GradleException( + "$fileName: [[snapshots]] buildId \"$buildId\" must declare a \"groups\" " + + "array of redirect group prefixes", + ) + AndroidxSnapshotBuild( + buildId = buildId, + flavour = flavour, + groups = (0 until groups.size()).map { groups.getString(it) }, ) + } + validate(fileName, versions, snapshots) + return Registry(versions, snapshots) + } + + private fun validate( + fileName: String, + versions: Map, + snapshots: List, + ) { + val buildIdByGroup = mutableMapOf() + snapshots.forEach { build -> + build.groups.forEach { group -> + buildIdByGroup.put(group, build.buildId)?.let { owner -> + throw GradleException( + "$fileName: group \"$group\" is listed in the [[snapshots]] entries of both " + + "build \"$owner\" and build \"${build.buildId}\". A group takes its " + + "version from exactly one androidx.dev build.", + ) + } + val version = + versions[group] + ?: throw GradleException( + "$fileName: [[snapshots]] build \"${build.buildId}\" lists group " + + "\"$group\", which has no entry in the [versions] table.", + ) + if (!version.endsWith(SNAPSHOT_SUFFIX)) { + throw GradleException( + "$fileName: group \"$group\" is pinned to androidx.dev build " + + "\"${build.buildId}\" but its [versions] entry is \"$version\". Only " + + "$SNAPSHOT_SUFFIX versions are served by androidx.dev; a released " + + "version must not carry a buildId.", + ) + } + } + } + versions.forEach { (group, version) -> + if (version.endsWith(SNAPSHOT_SUFFIX) && group !in buildIdByGroup) { + throw GradleException( + "$fileName: [versions] \"$group\" is \"$version\" but no [[snapshots]] entry " + + "lists it. A snapshot version without an androidx.dev buildId is not " + + "resolvable — add the group to the [[snapshots]] entry for the build it " + + "was merged from.", + ) + } } } companion object { + private const val SNAPSHOT_SUFFIX = "-SNAPSHOT" + private const val TOML_FILE_NAME = "redirectversions.toml" internal fun registerOrGet(project: Project): Provider { @@ -121,9 +258,56 @@ internal fun Project.registerRedirectVersionsExtension() { */ fun Project.findArtifactRedirectionVersion(groupId: String): String? { val versions = RedirectVersionsService.registerOrGet(this).get().versions - val parts = groupId.split(".") + name - val variations = (parts.size downTo 1).map { i -> parts.take(i).joinToString(".") } - return variations.firstNotNullOfOrNull { versions[it] } + return groupPrefixes(groupId, name).firstNotNullOfOrNull { versions[it] } +} + +/** + * Look up the androidx.dev build the redirect version of [groupId] comes from. Resolves the same + * prefix [findArtifactRedirectionVersion] does and asks only that one: a more specific prefix on a + * released version (`androidx.compose.material3`) must not inherit the snapshot of a less specific + * one (`androidx.compose`). Null when the redirect is on a version already on Google Maven. + */ +fun Project.findArtifactRedirectionSnapshot(groupId: String): AndroidxSnapshotBuild? { + val redirects = RedirectVersionsService.registerOrGet(this).get() + val prefix = groupPrefixes(groupId, name).firstOrNull { it in redirects.versions } ?: return null + return redirects.snapshotBuildsByGroup[prefix] +} + +/** + * Group prefixes of [groupId] from the most specific (`.`) down to the least + * specific (the leading segment), the lookup order of the redirect registry. + */ +private fun groupPrefixes(groupId: String, projectName: String): List { + val parts = groupId.split(".") + projectName + return (parts.size downTo 1).map { i -> parts.take(i).joinToString(".") } +} + +/** + * Registers `printAndroidxSnapshots`, which prints the registered androidx.dev builds as a flat + * comma-separated list of `:::`, empty when no redirect is on a + * snapshot. Consumed by the Gradle plugin build of `compose-multiplatform`, which bakes the ids into + * the published plugin so external consumers of a dev build can resolve the same coordinates. + */ +internal fun Project.registerPrintAndroidxSnapshotsTask() { + val service = RedirectVersionsService.registerOrGet(this) + val registry = + service.map { versions -> + versions.snapshots + .flatMap { build -> + build.groups.map { group -> + "$group:${versions.versions.getValue(group)}:${build.buildId}:${build.flavour.id}" + } + } + .joinToString(",") + } + tasks.register("printAndroidxSnapshots") { task -> + task.group = "Compose Multiplatform" + task.description = + "Prints the androidx.dev builds backing the -SNAPSHOT artifact redirects, as " + + "comma-separated ::: records." + task.usesService(service) + task.doLast { println(registry.get()) } + } } /** @@ -247,9 +431,16 @@ internal fun Project.applyParallelRedirectGraph( if (redirectJavaTasks == null || jc.name in redirectJavaTasks) jc.setSource(files()) } + // Name the androidx.dev build when the redirect is on a snapshot: the version string alone + // ("1.13.0-SNAPSHOT") is reused across builds and does not say what is being compiled against. + val snapshotBuild = findArtifactRedirectionSnapshot(redirectCoord.substringBefore(':')) logger.lifecycle( - "[artifactRedirection] {} -> {} (parallel graph: {} redirect target(s), forkBuilt={})", - path, redirectCoord, redirectTargetNames.size, forkBuiltExists, + "[artifactRedirection] {} -> {}{} (parallel graph: {} redirect target(s), forkBuilt={})", + path, + redirectCoord, + snapshotBuild?.let { " from androidx.dev build ${it.buildId}" } ?: "", + redirectTargetNames.size, + forkBuiltExists, ) } } diff --git a/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsAndroidXRootImplPlugin.kt b/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsAndroidXRootImplPlugin.kt index 901374486e0d8..982b4bcbe79cf 100644 --- a/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsAndroidXRootImplPlugin.kt +++ b/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsAndroidXRootImplPlugin.kt @@ -34,6 +34,8 @@ class JetBrainsAndroidXRootImplPlugin @Inject constructor( val componentFactory: SoftwareComponentFactory ) : Plugin { override fun apply(project: Project) { + project.registerPrintAndroidxSnapshotsTask() + project.allprojects { subproject -> // Apply capability rule to resolve conflicts between org.jetbrains.androidx.* and androidx.* subproject.configureJetBrainsCapabilityResolution() diff --git a/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsVerifyDependencyVersionsTask.kt b/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsVerifyDependencyVersionsTask.kt index ea655ace6c28b..f9ee328e25509 100644 --- a/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsVerifyDependencyVersionsTask.kt +++ b/buildSrc-fork/private/src/main/kotlin/org/jetbrains/androidx/build/JetBrainsVerifyDependencyVersionsTask.kt @@ -23,6 +23,7 @@ import androidx.build.uptodatedness.cacheEvenIfNoOutputs import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Project +import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.CacheableTask @@ -48,6 +49,14 @@ abstract class JetBrainsVerifyDependencyVersionsTask : DefaultTask() { @get:Input val androidXDependencySet: SetProperty = project.objects.setProperty() + /** + * Group prefix -> redirect version, for the prefixes the `[[snapshots]]` table of + * `redirectversions.toml` pins to an androidx.dev build. Empty when every redirect is on a + * released version. + */ + @get:Input + abstract val snapshotRedirectVersions: MapProperty + /** * Iterate through the dependencies of the project and ensure none of them are of an inferior * release. This means that a beta project should not have any alpha dependencies, an rc project @@ -75,6 +84,7 @@ abstract class JetBrainsVerifyDependencyVersionsTask : DefaultTask() { ) } if (dependencyReleasePhase < projectReleasePhase) { + if (isRegisteredSnapshotRedirect(projectVersion, dependency)) return throw GradleException( "Project with version ${version.get()} may " + "not take a dependency on less-stable artifact ${dependency.group}:" + @@ -85,6 +95,27 @@ abstract class JetBrainsVerifyDependencyVersionsTask : DefaultTask() { } } + /** + * True when [dependency] is an artifact redirect this repository deliberately pinned to an + * androidx.dev build **and** the project is being published to a non-release channel. Semver + * build metadata is the discriminator: a public release version has none, `+dev…` and + * `+snapshot.…` do. Requiring the dependency to match a registered prefix and its exact + * `[versions]` entry keeps this from becoming a blanket "dev builds may depend on anything". + */ + private fun isRegisteredSnapshotRedirect( + projectVersion: String, + dependency: AndroidXDependency, + ): Boolean { + if (Version(projectVersion).buildMetadata == null) return false + val registry = snapshotRedirectVersions.get() + val parts = dependency.group.split(".") + val prefix = + (parts.size downTo 1) + .map { i -> parts.take(i).joinToString(".") } + .firstOrNull { it in registry } ?: return false + return registry[prefix] == dependency.version + } + private fun releasePhase(versionString: String): Int { val version = Version(versionString) return when { @@ -107,6 +138,12 @@ internal fun Project.configureDependencyVerification() { JetBrainsVerifyDependencyVersionsTask::class.java ) { task -> task.version.set(project.provider { project.version.toString() }) + task.snapshotRedirectVersions.set( + project.provider { + val redirects = RedirectVersionsService.registerOrGet(project).get() + redirects.snapshotBuildsByGroup.keys.associateWith { redirects.versions.getValue(it) } + } + ) task.androidXDependencySet.set( project.provider { multiplatformExtension!! diff --git a/redirectversions.toml b/redirectversions.toml index 7f3d9499575bd..b0be2b2e48e75 100644 --- a/redirectversions.toml +++ b/redirectversions.toml @@ -24,3 +24,22 @@ "androidx.performance" = "1.0.0-alpha01" "androidx.savedstate" = "1.5.0-alpha01" "androidx.window" = "1.5.0" + +# androidx.dev builds backing the `-SNAPSHOT` entries above, for library states merged from a commit +# Google has cut but not yet published to Google Maven. One build normally covers several groups, +# which is why the build id is the key. Absent entirely on a released state, and it must be absent +# again before a release branch is cut. +# +# `groups` must name group prefixes whose `[versions]` value ends in `-SNAPSHOT`, and every +# `-SNAPSHOT` above must appear in exactly one entry — a snapshot version with no build id is not +# resolvable. `repo` is optional and defaults to "kmp" +# (https://androidx.dev/kmp/builds//artifacts/snapshots/repository); "androidx" +# (https://androidx.dev/snapshots/builds//artifacts/repository) serves the Android-only +# groups the KMP build does not carry. +# +# After changing a build id, run once with `--refresh-dependencies`: Google reuses the version string +# across builds, so Gradle's changing-module cache would keep serving the previous build. +# +# [[snapshots]] +# buildId = "15316886" +# groups = ["androidx.compose", "androidx.compose.material3"] diff --git a/settings-fork.gradle b/settings-fork.gradle index ab471d0a1ec06..5fdd028f734ac 100644 --- a/settings-fork.gradle +++ b/settings-fork.gradle @@ -35,12 +35,14 @@ def prebuiltsRoot = new File( def rootProjectRepositories apply from: "buildSrc-fork/settingsScripts/out-setup.groovy" +apply from: "buildSrc-fork/androidxSnapshotRepos.gradle" getGradle().beforeProject { project -> // Migrate to dependencyResolutionManagement.repositories when // https://github.com/gradle/gradle/issues/17295 is fixed if (project.path == ":") { repos.addMavenRepositories(project.repositories) + androidxSnapshots.addRepositories(project.repositories, supportRootFolder) rootProjectRepositories = project.repositories } else { // Performance optimization because it is more efficient to reuse @@ -48,6 +50,7 @@ getGradle().beforeProject { project -> // on each project project.repositories.addAll(rootProjectRepositories) } + androidxSnapshots.disableChangingModuleCache(project) project.ext.supportRootFolder = supportRootFolder project.ext.prebuiltsRoot = prebuiltsRoot def checkoutRoot = new File("${buildscript.sourceFile.parent}")