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 9219923e5fb..49fc7bcbb01 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 @@ -43,6 +43,8 @@ import javax.inject.Inject * from the root build can be forwarded via [projectJar]; each entry is passed as * `-P=` and tracked as a task input so the nested build re-runs * when the upstream jar changes. + * + * The temporary Gradle user home is preserved when daemon shutdown cannot be confirmed. */ @CacheableTask abstract class NestedGradleBuild @Inject constructor( @@ -137,7 +139,11 @@ abstract class NestedGradleBuild @Inject constructor( @get:Input abstract val buildCacheEnabled: Property - /** Timeout, in seconds, for stopping the nested Gradle daemon after the build. */ + /** + * Timeout, in seconds, applied separately to the `--stop` command and the subsequent daemon + * exit wait. The two waits can take up to twice this value in total. When unset, `--stop` + * waits indefinitely and daemon exit waits up to 30 seconds. + */ @get:Input @get:Optional abstract val stopTimeoutSeconds: Property @@ -356,6 +362,12 @@ abstract class NestedGradleBuild @Inject constructor( private fun createGradleUserHome(): File { val directory = temporaryDir.resolve("gradle-user-home") deleteGradleUserHome(directory) + if (directory.exists()) { + throw GradleException( + "Could not clean up existing nested Gradle user home: ${directory.absolutePath}. " + + "Check the preceding daemon shutdown and cleanup warnings.", + ) + } if (!directory.mkdirs()) { throw GradleException( "Could not create nested Gradle user home: ${directory.absolutePath}", @@ -364,6 +376,7 @@ abstract class NestedGradleBuild @Inject constructor( return directory } + // Gradle's internal log naming also drives buildSrc's GradleFixture.stopDaemonsIn. private fun findGradleDaemons(directory: File): List = directory.resolve("daemon").walkTopDown() .filter { it.isFile && it.name.startsWith("daemon-") && it.name.endsWith(".out.log") } 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 71175e894e0..92a9551d068 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 @@ -312,7 +312,7 @@ abstract class GradleAppSpec @Inject constructor() : ApplicationSpec() { */ abstract val buildCacheEnabled: Property - /** Timeout, in seconds, for stopping the nested Gradle daemon after the build. */ + /** Timeout, in seconds, for each shutdown wait. See [NestedGradleBuild.stopTimeoutSeconds]. */ abstract val stopTimeoutSeconds: Property } 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 dee9db72e73..b1ee1333f8d 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 @@ -4,6 +4,8 @@ import datadog.buildlogic.smoketest.NestedGradleBuild.Companion.gradleExecutable import datadog.buildlogic.smoketest.NestedGradleBuild.Companion.isGradleDaemonCandidate import datadog.buildlogic.smoketest.NestedMavenBuild.Companion.mavenWrapperName import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.gradle.api.GradleException import org.gradle.api.plugins.JavaPlugin import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.kotlin.dsl.apply @@ -111,6 +113,32 @@ class SmokeTestAppPluginTest { } } + @Test + fun `nested Gradle build reports a user home retained after cleanup`() { + val project = ProjectBuilder.builder().build() + project.apply() + val task = project.tasks.register("nestedBuild", NestedGradleBuild::class.java).get() + task.applicationDir.set(project.layout.projectDirectory) + task.applicationBuildDir.set(project.layout.buildDirectory.dir("application")) + // Resolve the toolchain before simulating interrupted cleanup. + task.javaLauncher.get() + val userHome = task.temporaryDir.resolve("gradle-user-home") + assertThat(userHome.mkdirs()).isTrue() + + Thread.currentThread().interrupt() + try { + assertThatThrownBy { task.runNestedBuild() } + .isInstanceOf(GradleException::class.java) + .hasMessageContaining("Could not clean up existing nested Gradle user home:") + .hasMessageContaining(userHome.absolutePath) + + assertThat(Thread.currentThread().isInterrupted).isTrue() + assertThat(userHome).exists() + } finally { + Thread.interrupted() + } + } + @Test fun `applying the plugin creates the smokeTestApp extension`() { val project = ProjectBuilder.builder().build()