From f70ab114480852f5dcc9e35ef69f02ca40a007fc Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Mon, 14 Sep 2026 18:43:42 -0400 Subject: [PATCH 1/3] Prevent nested smoke builds from falling back to Maven Central --- .../proxy-repositories.init.gradle.kts | 8 +++- .../smoketest/SmokeTestAppEndToEndTest.kt | 41 ++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts b/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts index 70fe8054b90..66e312c9569 100644 --- a/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts +++ b/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts @@ -22,7 +22,9 @@ gradle.beforeSettings(Action { } } gradlePluginPortal() - mavenCentral() + if (mavenRepositoryProxy.isNullOrBlank()) { + mavenCentral() + } } } @@ -35,7 +37,9 @@ gradle.beforeSettings(Action { isAllowInsecureProtocol = true } } - mavenCentral() + if (mavenRepositoryProxy.isNullOrBlank()) { + mavenCentral() + } } }) }) diff --git a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt index 6aa17ed4b7c..f44ac2a7d90 100644 --- a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt +++ b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt @@ -265,7 +265,7 @@ class SmokeTestAppEndToEndTest { } @Test - fun `local proxy property prepends Maven proxy repositories exactly once`() { + fun `Maven proxy replaces Maven Central in nested repositories`() { writeOuterSettings() val proxyRepository = projectDir.resolve("proxy-maven-repo").toFile() val projectRepository = projectDir.resolve("project-maven-repo").toFile() @@ -279,7 +279,19 @@ class SmokeTestAppEndToEndTest { sysProperty = "resolved.repositories.path", ), ) - writeInnerSettings() + writeInnerSettings( + """ + gradle.settingsEvaluated { + val outputDir = java.io.File(providers.gradleProperty("appBuildDir").get()) + outputDir.mkdirs() + outputDir.resolve("plugin-repositories.txt").writeText( + pluginManagement.repositories + .withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository::class.java) + .joinToString(System.lineSeparator()) { "repository=" + it.url } + ) + } + """.trimIndent(), + ) writeInnerBuild( """ repositories { @@ -301,8 +313,12 @@ class SmokeTestAppEndToEndTest { val artifacts = configurations.compileClasspath.get() .sortedBy { it.name } .map { it.name + "=" + it.readText() } + val repositoryUrls = repositories + .withType(org.gradle.api.artifacts.repositories.MavenArtifactRepository::class.java) + .map { "repository=" + it.url } resolved.get().asFile.writeText( - (listOf("init-script-count=" + gradle.startParameter.initScripts.size) + artifacts) + (listOf("init-script-count=" + gradle.startParameter.initScripts.size) + + repositoryUrls + artifacts) .joinToString(System.lineSeparator()) ) } @@ -319,11 +335,25 @@ class SmokeTestAppEndToEndTest { assertThat(result.task(":resolveRepositories")?.outcome).isEqualTo(TaskOutcome.SUCCESS) val resolvedFile = applicationOutput("resolved-repositories.txt") assertThat(resolvedFile).exists() - assertThat(resolvedFile.readLines()).containsExactly( + val resolvedLines = resolvedFile.readLines() + assertThat(resolvedLines).contains( "init-script-count=1", + "repository=${proxyRepository.toURI()}", "project-only-1.0.jar=project-only", "shared-1.0.jar=proxy", ) + assertThat(resolvedLines).doesNotContain( + "repository=https://repo.maven.apache.org/maven2/", + ) + val pluginRepositoriesFile = applicationOutput("plugin-repositories.txt") + assertThat(pluginRepositoriesFile).exists() + val pluginRepositoryLines = pluginRepositoriesFile.readLines() + assertThat(pluginRepositoryLines).contains( + "repository=${proxyRepository.toURI()}", + ) + assertThat(pluginRepositoryLines).doesNotContain( + "repository=https://repo.maven.apache.org/maven2/", + ) } @Test @@ -649,10 +679,11 @@ class SmokeTestAppEndToEndTest { "fake-mvnw" } - private fun writeInnerSettings() { + private fun writeInnerSettings(additionalContent: String = "") { File(applicationDir, "settings.gradle.kts").writeText( """ rootProject.name = "smoke-test-app-fixture-application" + $additionalContent """.trimIndent(), ) } From 2255fe0648ea34626473d4973b10a125e51f40d0 Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Mon, 14 Sep 2026 19:44:29 -0400 Subject: [PATCH 2/3] Redirect nested Maven Central declarations --- .../proxy-repositories.init.gradle.kts | 22 +++++++++++++++++++ .../smoketest/SmokeTestAppEndToEndTest.kt | 7 ++++++ 2 files changed, 29 insertions(+) diff --git a/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts b/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts index 66e312c9569..f76f23d8586 100644 --- a/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts +++ b/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts @@ -1,10 +1,30 @@ import org.gradle.api.Action import org.gradle.api.Project +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.initialization.Settings gradle.beforeSettings(Action { val gradlePluginProxy = providers.gradleProperty("gradlePluginProxy").orNull val mavenRepositoryProxy = providers.gradleProperty("mavenRepositoryProxy").orNull + val mavenCentralUrls = setOf( + "https://repo.maven.apache.org/maven2", + "https://repo1.maven.org/maven2", + ) + + fun RepositoryHandler.redirectMavenCentral() { + val proxy = mavenRepositoryProxy?.takeIf { it.isNotBlank() } ?: return + withType(MavenArtifactRepository::class.java).configureEach { + if (url.toString().trimEnd('/') in mavenCentralUrls) { + url = java.net.URI(proxy) + isAllowInsecureProtocol = true + } + } + } + + buildscript.repositories.redirectMavenCentral() + pluginManagement.repositories.redirectMavenCentral() + dependencyResolutionManagement.repositories.redirectMavenCentral() pluginManagement { repositories { @@ -29,6 +49,8 @@ gradle.beforeSettings(Action { } gradle.beforeProject(Action { + repositories.redirectMavenCentral() + buildscript.repositories.redirectMavenCentral() repositories { mavenLocal() mavenRepositoryProxy?.takeIf { it.isNotBlank() }?.let { proxy -> diff --git a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt index f44ac2a7d90..7942653e40a 100644 --- a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt +++ b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt @@ -281,6 +281,12 @@ class SmokeTestAppEndToEndTest { ) writeInnerSettings( """ + pluginManagement { + repositories { + mavenCentral() + } + } + gradle.settingsEvaluated { val outputDir = java.io.File(providers.gradleProperty("appBuildDir").get()) outputDir.mkdirs() @@ -295,6 +301,7 @@ class SmokeTestAppEndToEndTest { writeInnerBuild( """ repositories { + mavenCentral() maven { url = uri("${projectRepository.toURI()}") } From 0dfd0c6d5f55043c44547c64aeaf56a7358bbb7d Mon Sep 17 00:00:00 2001 From: Alexey Kuznetsov Date: Tue, 15 Sep 2026 20:00:31 -0400 Subject: [PATCH 3/3] Deduplicate nested Maven proxy repositories The init script injects the repository proxy and redirectMavenCentral() rewrites any mavenCentral() the nested build declares onto that same URL, so both the plugin-management and project repository lists end up holding the proxy twice. They stay distinct Gradle repositories, duplicating metadata and artifact requests on every cache miss. Drop the redundant entries, keeping the repository this script injected: it is the only one known to be unrestricted. A declared mavenCentral() may carry content filters or narrowed metadata sources that the redirect does not lift, so keeping such a declaration instead would stop resolving whatever it excludes. Containers this script never injected into are left untouched, so nothing removes a proxy the nested build owns outright. Also handle a repository declared without a URL, which would otherwise fail with a NullPointerException thrown from an injected init script rather than Gradle's own error. The test fixture now declares Maven Central with a content filter before the injection happens, which is the ordering that makes the choice of survivor observable. Co-Authored-By: Claude Opus 5 (1M context) --- .../proxy-repositories.init.gradle.kts | 39 ++++++++++++++++--- .../smoketest/SmokeTestAppEndToEndTest.kt | 14 +++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts b/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts index f76f23d8586..53d998e203c 100644 --- a/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts +++ b/build-logic/smoke-test/src/main/resources/datadog/buildlogic/smoketest/proxy-repositories.init.gradle.kts @@ -3,6 +3,7 @@ import org.gradle.api.Project import org.gradle.api.artifacts.dsl.RepositoryHandler import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.initialization.Settings +import java.net.URI gradle.beforeSettings(Action { val gradlePluginProxy = providers.gradleProperty("gradlePluginProxy").orNull @@ -11,17 +12,35 @@ gradle.beforeSettings(Action { "https://repo.maven.apache.org/maven2", "https://repo1.maven.org/maven2", ) + // Marks the proxy repository this script injects, so the deduplication below can tell it apart + // from a redirected mavenCentral() the nested build declared itself. + val injectedProxyName = "ddSmokeTestMavenRepositoryProxy" fun RepositoryHandler.redirectMavenCentral() { val proxy = mavenRepositoryProxy?.takeIf { it.isNotBlank() } ?: return withType(MavenArtifactRepository::class.java).configureEach { - if (url.toString().trimEnd('/') in mavenCentralUrls) { - url = java.net.URI(proxy) + // A repository declared without a URL has a null one until Gradle validates it; leave it be + // so the nested build reports that itself instead of failing inside this init script. + val repositoryUrl = url?.toString()?.trimEnd('/') + if (repositoryUrl != null && repositoryUrl in mavenCentralUrls) { + url = URI(proxy) isAllowInsecureProtocol = true } } } + fun RepositoryHandler.removeDuplicateMavenProxy() { + val proxyUrl = mavenRepositoryProxy?.takeIf { it.isNotBlank() }?.trimEnd('/') ?: return + val proxies = withType(MavenArtifactRepository::class.java) + .filter { it.url?.toString()?.trimEnd('/') == proxyUrl } + // Keep the injected repository: it is the only one known to be unrestricted, since a declared + // mavenCentral() may carry content filters that redirectMavenCentral() does not lift. + if (proxies.none { it.name == injectedProxyName }) { + return + } + proxies.filter { it.name != injectedProxyName }.forEach { remove(it) } + } + buildscript.repositories.redirectMavenCentral() pluginManagement.repositories.redirectMavenCentral() dependencyResolutionManagement.repositories.redirectMavenCentral() @@ -31,13 +50,14 @@ gradle.beforeSettings(Action { mavenLocal() gradlePluginProxy?.takeIf { it.isNotBlank() }?.let { proxy -> maven { - url = java.net.URI(proxy) + url = URI(proxy) isAllowInsecureProtocol = true } } mavenRepositoryProxy?.takeIf { it.isNotBlank() }?.let { proxy -> maven { - url = java.net.URI(proxy) + name = injectedProxyName + url = URI(proxy) isAllowInsecureProtocol = true } } @@ -48,6 +68,10 @@ gradle.beforeSettings(Action { } } + gradle.settingsEvaluated(Action { + pluginManagement.repositories.removeDuplicateMavenProxy() + }) + gradle.beforeProject(Action { repositories.redirectMavenCentral() buildscript.repositories.redirectMavenCentral() @@ -55,7 +79,8 @@ gradle.beforeSettings(Action { mavenLocal() mavenRepositoryProxy?.takeIf { it.isNotBlank() }?.let { proxy -> maven { - url = java.net.URI(proxy) + name = injectedProxyName + url = URI(proxy) isAllowInsecureProtocol = true } } @@ -64,4 +89,8 @@ gradle.beforeSettings(Action { } } }) + + gradle.afterProject(Action { + repositories.removeDuplicateMavenProxy() + }) }) diff --git a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt index 7942653e40a..656fbeedd49 100644 --- a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt +++ b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppEndToEndTest.kt @@ -296,6 +296,14 @@ class SmokeTestAppEndToEndTest { .joinToString(System.lineSeparator()) { "repository=" + it.url } ) } + + gradle.projectsLoaded { + gradle.rootProject.repositories.mavenCentral { + content { + includeGroup("com.unrelated") + } + } + } """.trimIndent(), ) writeInnerBuild( @@ -349,6 +357,9 @@ class SmokeTestAppEndToEndTest { "project-only-1.0.jar=project-only", "shared-1.0.jar=proxy", ) + assertThat(resolvedLines).containsOnlyOnce( + "repository=${proxyRepository.toURI()}", + ) assertThat(resolvedLines).doesNotContain( "repository=https://repo.maven.apache.org/maven2/", ) @@ -358,6 +369,9 @@ class SmokeTestAppEndToEndTest { assertThat(pluginRepositoryLines).contains( "repository=${proxyRepository.toURI()}", ) + assertThat(pluginRepositoryLines).containsOnlyOnce( + "repository=${proxyRepository.toURI()}", + ) assertThat(pluginRepositoryLines).doesNotContain( "repository=https://repo.maven.apache.org/maven2/", )