diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/GradleDistribution.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/GradleDistribution.kt new file mode 100644 index 00000000000..9e5f3a0672d --- /dev/null +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/GradleDistribution.kt @@ -0,0 +1,12 @@ +package datadog.buildlogic.smoketest + +import java.net.URI + +internal const val MASS_READ_URL_ENV = "MASS_READ_URL" + +internal fun gradleDistributionUri(massReadUrl: String, gradleVersion: String): URI { + val baseUrl = if (massReadUrl.endsWith("/")) massReadUrl else "$massReadUrl/" + return URI.create( + "${baseUrl}internal/artifact/services.gradle.org/distributions/gradle-$gradleVersion-bin.zip", + ) +} diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedGradleBuild.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedGradleBuild.kt index 3ee09d12c41..ffdefe82f35 100644 --- a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedGradleBuild.kt +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedGradleBuild.kt @@ -16,6 +16,7 @@ import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity @@ -47,6 +48,9 @@ abstract class NestedGradleBuild @Inject constructor( init { gradleVersion.convention(DEFAULT_NESTED_GRADLE_VERSION) + gradleDistributionBaseUrl.convention( + project.providers.environmentVariable(MASS_READ_URL_ENV), + ) javaLauncher.convention( javaToolchains.launcherFor { languageVersion.set(JavaLanguageVersion.of(DEFAULT_NESTED_JAVA_VERSION)) @@ -69,6 +73,14 @@ abstract class NestedGradleBuild @Inject constructor( @get:Input abstract val gradleVersion: Property + /** + * Optional base URL for Gradle distribution downloads. CI sets this to MASS so nested builds + * download through the pull-through cache instead of directly from services.gradle.org. + */ + @get:Input + @get:Optional + abstract val gradleDistributionBaseUrl: Property + @get:Nested abstract val javaLauncher: Property @@ -137,8 +149,17 @@ abstract class NestedGradleBuild @Inject constructor( } val connector = GradleConnector.newConnector() - .useGradleVersion(gradleVersion.get()) .forProjectDirectory(appDir) + .apply { + val distributionBaseUrl = gradleDistributionBaseUrl.orNull + if (distributionBaseUrl.isNullOrBlank()) { + useGradleVersion(gradleVersion.get()) + } else { + useDistribution( + gradleDistributionUri(distributionBaseUrl, gradleVersion.get()), + ) + } + } val extraEnv = environment.get() val mergedEnv: Map? = diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppExtension.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppExtension.kt index 1e1e2c476b5..7e49d1f4ba9 100644 --- a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppExtension.kt +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppExtension.kt @@ -40,6 +40,12 @@ abstract class SmokeTestAppExtension @Inject constructor( */ abstract val gradleVersion: Property + /** + * Optional base URL for Gradle distribution downloads. Defaults to the CI-provided MASS read + * URL when present, so Tooling API downloads go through the pull-through cache. + */ + abstract val gradleDistributionBaseUrl: Property + /** * JDK used by the nested daemon. Defaults to a [DEFAULT_NESTED_JAVA_VERSION] toolchain; * override to pin a different JDK if the nested application's plugin chain requires it. @@ -63,6 +69,9 @@ abstract class SmokeTestAppExtension @Inject constructor( applicationDir.convention(project.layout.projectDirectory.dir("application")) applicationBuildDir.convention(project.layout.buildDirectory.dir("application")) gradleVersion.convention(DEFAULT_NESTED_GRADLE_VERSION) + gradleDistributionBaseUrl.convention( + project.providers.environmentVariable(MASS_READ_URL_ENV), + ) javaLauncher.convention( javaToolchains.launcherFor { languageVersion.set(JavaLanguageVersion.of(DEFAULT_NESTED_JAVA_VERSION)) @@ -94,6 +103,7 @@ abstract class SmokeTestAppExtension @Inject constructor( applicationDir.set(this@SmokeTestAppExtension.applicationDir) applicationBuildDir.set(this@SmokeTestAppExtension.applicationBuildDir) gradleVersion.set(this@SmokeTestAppExtension.gradleVersion) + gradleDistributionBaseUrl.set(this@SmokeTestAppExtension.gradleDistributionBaseUrl) javaLauncher.set(this@SmokeTestAppExtension.javaLauncher) tasksToRun.set(nestedTasks) buildArguments.set(spec.buildArguments) diff --git a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPluginTest.kt b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPluginTest.kt index 22fd1ce93fe..7fa9d7c09f9 100644 --- a/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPluginTest.kt +++ b/build-logic/smoke-test/src/test/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPluginTest.kt @@ -67,6 +67,37 @@ class SmokeTestAppPluginTest { assertThat(extension.gradleVersion.get()).isEqualTo(DEFAULT_NESTED_GRADLE_VERSION) } + @Test + fun `application task receives configured Gradle distribution base URL`() { + val project = ProjectBuilder.builder().build() + project.apply() + project.plugins.apply("dd-trace-java.smoke-test-app") + + val extension = project.extensions.getByType() + extension.gradleDistributionBaseUrl.set("https://mass.example") + extension.application { + taskName.set("packageApp") + artifactPath.set("libs/test.jar") + sysProperty.set("test.path") + } + + val task = project.tasks.getByName("packageApp") as NestedGradleBuild + + assertThat(task.gradleDistributionBaseUrl.get()).isEqualTo("https://mass.example") + } + + @Test + fun `Gradle distribution URI routes through MASS artifact path`() { + assertThat(gradleDistributionUri("https://mass.example", "8.14.5").toString()) + .isEqualTo( + "https://mass.example/internal/artifact/services.gradle.org/distributions/gradle-8.14.5-bin.zip", + ) + assertThat(gradleDistributionUri("https://mass.example/", "8.14.5").toString()) + .isEqualTo( + "https://mass.example/internal/artifact/services.gradle.org/distributions/gradle-8.14.5-bin.zip", + ) + } + @Test fun `extension defaults javaLauncher to a JDK 21 toolchain`() { // JavaToolchainService is contributed by the `java-base` plugin; apply something that diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index 47cec74af8e..31d30a88998 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -24,7 +24,6 @@ import org.gradle.testkit.runner.TaskOutcome; import org.gradle.tooling.internal.consumer.DefaultGradleConnector; import org.gradle.util.GradleVersion; -import org.gradle.util.internal.DistributionLocator; import org.gradle.wrapper.Download; import org.gradle.wrapper.GradleUserHomeLookup; import org.gradle.wrapper.Install; @@ -275,8 +274,7 @@ private void ensureDependenciesDownloaded(String gradleVersion) { Install install = new Install(logger, download, new PathAssembler(userHomeDir, projectDir)); WrapperConfiguration configuration = new WrapperConfiguration(); - configuration.setDistribution( - new DistributionLocator().getDistributionFor(GradleVersion.version(gradleVersion))); + configuration.setDistribution(GradleDistribution.uriFor(gradleVersion)); configuration.setNetworkTimeout(GRADLE_DISTRIBUTION_NETWORK_TIMEOUT); // This will download distribution (if not downloaded yet to userHomeDir) and verify its SHA. @@ -292,17 +290,22 @@ private BuildResult runGradle( String gradleVersion, List arguments, boolean successExpected) throws IOException { Map buildEnv = new HashMap<>(); buildEnv.put("GRADLE_VERSION", gradleVersion); + buildEnv.put( + GradleDistribution.GRADLE_DISTRIBUTION_URL_ENV, + GradleDistribution.uriFor(gradleVersion).toString()); String mavenRepositoryProxy = System.getenv("MAVEN_REPOSITORY_PROXY"); if (mavenRepositoryProxy != null) { buildEnv.put("MAVEN_REPOSITORY_PROXY", mavenRepositoryProxy); } + GradleDistribution.propagateMassReadUrl(buildEnv); GradleRunner gradleRunner = - GradleRunner.create() - .withTestKitDir(testKitFolder.toFile()) - .withProjectDir(projectFolder.toFile()) - .withGradleVersion(gradleVersion) + GradleDistribution.withDistribution( + GradleRunner.create() + .withTestKitDir(testKitFolder.toFile()) + .withProjectDir(projectFolder.toFile()), + gradleVersion) .withArguments(arguments) .withEnvironment(buildEnv) .forwardOutput(); diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDistribution.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDistribution.java new file mode 100644 index 00000000000..edaa526dbac --- /dev/null +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDistribution.java @@ -0,0 +1,69 @@ +package datadog.smoketest; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.util.GradleVersion; +import org.gradle.util.internal.DistributionLocator; + +final class GradleDistribution { + + static final String GRADLE_DISTRIBUTION_URL_ENV = "GRADLE_DISTRIBUTION_URL"; + + private static final String MASS_READ_URL_ENV = "MASS_READ_URL"; + private static final Pattern DISTRIBUTION_URL_LINE = Pattern.compile("(?m)^distributionUrl=.*$"); + + private GradleDistribution() {} + + static URI uriFor(String gradleVersion) { + String massReadUrl = System.getenv(MASS_READ_URL_ENV); + if (massReadUrl == null || massReadUrl.trim().isEmpty()) { + return new DistributionLocator().getDistributionFor(GradleVersion.version(gradleVersion)); + } + return massUriFor(massReadUrl, gradleVersion); + } + + static GradleRunner withDistribution(GradleRunner runner, String gradleVersion) { + String massReadUrl = System.getenv(MASS_READ_URL_ENV); + if (massReadUrl == null || massReadUrl.trim().isEmpty()) { + return runner.withGradleVersion(gradleVersion); + } + return runner.withGradleDistribution(massUriFor(massReadUrl, gradleVersion)); + } + + static void propagateMassReadUrl(Map environment) { + String massReadUrl = System.getenv(MASS_READ_URL_ENV); + if (massReadUrl != null && !massReadUrl.trim().isEmpty()) { + environment.put(MASS_READ_URL_ENV, massReadUrl); + } + } + + static String uriPropertiesValueFor(String gradleVersion) { + return uriFor(gradleVersion).toString().replace(":", "\\:"); + } + + static void rewriteWrapperDistributionUrl(Path projectFolder, String gradleVersion) + throws IOException { + Path wrapperProperties = projectFolder.resolve("gradle/wrapper/gradle-wrapper.properties"); + String contents = new String(Files.readAllBytes(wrapperProperties), StandardCharsets.UTF_8); + String replacement = "distributionUrl=" + uriPropertiesValueFor(gradleVersion); + String updated = + DISTRIBUTION_URL_LINE.matcher(contents).replaceFirst(Matcher.quoteReplacement(replacement)); + Files.write(wrapperProperties, updated.getBytes(StandardCharsets.UTF_8)); + } + + private static URI massUriFor(String massReadUrl, String gradleVersion) { + String baseUrl = massReadUrl.endsWith("/") ? massReadUrl : massReadUrl + "/"; + return URI.create( + baseUrl + + "internal/artifact/services.gradle.org/distributions/gradle-" + + gradleVersion + + "-bin.zip"); + } +} diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java index dc5c4435d2c..822a981ea03 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleLauncherSmokeTest.java @@ -54,6 +54,8 @@ void testGradleLauncherInjectsTracerIntoGradleDaemon( Map> replacements = new HashMap<>(); Map versionMap = new HashMap<>(); versionMap.put("gradle-version", resolvedGradleVersion); + versionMap.put( + "gradle-distribution-url", GradleDistribution.uriPropertiesValueFor(resolvedGradleVersion)); replacements.put("gradle-wrapper.properties", versionMap); givenGradleProjectFiles("test-gradle-wrapper", replacements); // we want to check that instrumentation works with different wrapper versions too @@ -84,6 +86,7 @@ private void givenGradleWrapper(String gradleVersion) throws Exception { try { shellCommandExecutor.executeCommand( IOUtils::readFully, "./gradlew", "wrapper", "--gradle-version", gradleVersion); + GradleDistribution.rewriteWrapperDistributionUrl(projectFolder, gradleVersion); return; } catch (ShellCommandExecutor.ShellCommandFailedException e) { LOGGER.warn("Failed gradle wrapper resolution with exception: ", e); diff --git a/dd-smoke-tests/gradle/src/test/resources/test-gradle-wrapper/gradle/wrapper/gradle-wrapper.properties b/dd-smoke-tests/gradle/src/test/resources/test-gradle-wrapper/gradle/wrapper/gradle-wrapper.properties index 7b0b125d7b8..a1b0bd8df79 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-gradle-wrapper/gradle/wrapper/gradle-wrapper.properties +++ b/dd-smoke-tests/gradle/src/test/resources/test-gradle-wrapper/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-${gradle-version}-bin.zip +distributionUrl=${gradle-distribution-url} zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/coverages.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/coverages.ftl index 65148a80555..482b6832e98 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/coverages.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/coverages.ftl @@ -1,9 +1,9 @@ [ { "files" : [ { - "bitmap" : "AAAAx+EN", + "bitmap" : "AAAAfOxv", "filename" : "src/test/java/datadog/smoke/HelloPluginFunctionalTest.java" } ], "span_id" : ${content_span_id_4}, "test_session_id" : ${content_test_session_id}, "test_suite_id" : ${content_test_suite_id} -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl index ebcf3f8c6f3..dce4865bfc1 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/events.ftl @@ -216,8 +216,8 @@ "_dd.profiling.enabled" : 0, "_dd.trace_span_attribute_schema" : 0, "process_id" : ${content_metrics_process_id_2}, - "test.source.end" : 44, - "test.source.start" : 16 + "test.source.end" : 47, + "test.source.start" : 18 }, "name" : "junit5.test_suite", "resource" : "datadog.smoke.HelloPluginFunctionalTest", @@ -278,8 +278,8 @@ "_dd.profiling.enabled" : 0, "_dd.trace_span_attribute_schema" : 0, "process_id" : ${content_metrics_process_id_2}, - "test.source.end" : 43, - "test.source.start" : 30 + "test.source.end" : 46, + "test.source.start" : 34 }, "name" : "junit5.test", "parent_id" : ${content_parent_id}, @@ -469,4 +469,4 @@ }, "type" : "span", "version" : 1 -} ] \ No newline at end of file +} ] diff --git a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/src/test/java/datadog/smoke/HelloPluginFunctionalTest.java b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/src/test/java/datadog/smoke/HelloPluginFunctionalTest.java index fbb7be2ca29..bc71633bd91 100644 --- a/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/src/test/java/datadog/smoke/HelloPluginFunctionalTest.java +++ b/dd-smoke-tests/gradle/src/test/resources/test-succeed-gradle-plugin-test/src/test/java/datadog/smoke/HelloPluginFunctionalTest.java @@ -1,17 +1,19 @@ package datadog.smoke; +import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.gradle.testkit.runner.BuildResult; -import org.gradle.testkit.runner.GradleRunner; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; class HelloPluginFunctionalTest { @@ -22,22 +24,23 @@ class HelloPluginFunctionalTest { @BeforeEach void setUp() throws IOException { buildFile = testProjectDir.resolve("build.gradle").toFile(); - Files.write(buildFile.toPath(), "plugins { id 'datadog.smoke.helloplugin' }".getBytes(StandardCharsets.UTF_8)); + Files.write( + buildFile.toPath(), + "plugins { id 'datadog.smoke.helloplugin' }".getBytes(StandardCharsets.UTF_8)); } @Test void pluginPrintsHelloMessageOnGradle85() { - BuildResult result = GradleRunner.create() - .withProjectDir(testProjectDir.toFile()) - .withPluginClasspath() - // Use the same Gradle version as of the actual smoke test that builds this project. - // This is to ensure Gradle is already downloaded and available in the environment. - // Gradle Test Kit can download a Gradle distribution by itself, - // but sometimes these downloads fail, making the test flaky. - .withGradleVersion(System.getenv("GRADLE_VERSION")) - .withArguments("hello", "--stacktrace") - .forwardOutput() - .build(); + String gradleDistributionUrl = + requireNonNull(System.getenv("GRADLE_DISTRIBUTION_URL"), "GRADLE_DISTRIBUTION_URL"); + BuildResult result = + GradleRunner.create() + .withProjectDir(testProjectDir.toFile()) + .withPluginClasspath() + .withGradleDistribution(URI.create(gradleDistributionUrl)) + .withArguments("hello", "--stacktrace") + .forwardOutput() + .build(); assertTrue(result.getOutput().contains("Hello from my plugin!")); }