diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cf1cb6d91b5..780fb720177 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -242,8 +242,9 @@ default: EOF - mkdir -p .gradle - export GRADLE_USER_HOME=$(pwd)/.gradle - # replace maven central part by MAVEN_REPOSITORY_PROXY in .mvn/wrapper/maven-wrapper.properties - - sed -i "s|https://repo.maven.apache.org/maven2/|$MAVEN_REPOSITORY_PROXY|g" .mvn/wrapper/maven-wrapper.properties + # Apache Maven Wrapper supports MVNW_REPOURL for repository-manager downloads: + # https://maven.apache.org/tools/wrapper/#Using_a_Maven_Repository_Manager + - export MVNW_REPOURL=${MAVEN_REPOSITORY_PROXY%/} # Route Gradle distribution download through MASS pull-through cache - | mass_read_host="${MASS_READ_URL#https://}" @@ -784,7 +785,9 @@ muzzle-dep-report: # and Gradle does `chmod 700 .gradle` on startup which requires user ownership. - sudo chown -R 1001:1001 .gradle - export GRADLE_USER_HOME=$(pwd)/.gradle - - sed -i "s|https://repo.maven.apache.org/maven2/|$MAVEN_REPOSITORY_PROXY|g" .mvn/wrapper/maven-wrapper.properties + # Apache Maven Wrapper supports MVNW_REPOURL for repository-manager downloads: + # https://maven.apache.org/tools/wrapper/#Using_a_Maven_Repository_Manager + - export MVNW_REPOURL=${MAVEN_REPOSITORY_PROXY%/} - *normalize_node_index - *prepare_test_env # Disable CDS in forked JVMs to avoid SIGSEGVs on Linux arm64. 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 2e7f06c93ab..33da53475d1 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 @@ -113,6 +113,11 @@ abstract class NestedGradleBuild @Inject constructor( @get:Input abstract val buildCacheEnabled: Property + /** Timeout, in seconds, for stopping the nested Gradle daemon after the build. */ + @get:Input + @get:Optional + abstract val stopTimeoutSeconds: Property + /** * Extra environment variables for the nested Gradle daemon. Merged on top of the outer process * environment; Gradle launcher variables are reserved by this task so nested builds do not @@ -236,9 +241,17 @@ abstract class NestedGradleBuild @Inject constructor( } val process = processBuilder.start() - if (!process.waitFor(GRADLE_STOP_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + val timeoutSeconds = stopTimeoutSeconds.orNull + val completed = + if (timeoutSeconds == null) { + process.waitFor() + true + } else { + process.waitFor(timeoutSeconds, TimeUnit.SECONDS) + } + if (!completed) { process.destroyForcibly() - logger.warn("Timed out while stopping nested Gradle daemon") + logger.warn("Timed out after {} seconds while stopping nested Gradle daemon", timeoutSeconds) return } val exitCode = process.exitValue() @@ -270,13 +283,6 @@ abstract class NestedGradleBuild @Inject constructor( file.parentFile?.name == "bin" } - private fun gradleExecutableName(): String = - if (System.getProperty("os.name").lowercase().contains("windows")) { - "gradle.bat" - } else { - "gradle" - } - private fun createGradleUserHome(): File { val directory = temporaryDir.resolve("gradle-user-home") deleteGradleUserHome(directory) @@ -294,8 +300,13 @@ abstract class NestedGradleBuild @Inject constructor( } } - private companion object { - const val GRADLE_STOP_TIMEOUT_SECONDS = 30L + companion object { + internal fun gradleExecutableName(osName: String = System.getProperty("os.name")): String = + if (isWindows(osName)) { + "gradle.bat" + } else { + "gradle" + } } } diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedMavenBuild.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedMavenBuild.kt new file mode 100644 index 00000000000..28234528be2 --- /dev/null +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedMavenBuild.kt @@ -0,0 +1,201 @@ +package datadog.buildlogic.smoketest + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileTree +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +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 +import org.gradle.api.tasks.TaskAction +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.jvm.toolchain.JavaToolchainService +import java.io.File +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * Runs a nested Maven build inside [applicationDir] via the root Maven wrapper. + * + * The nested build is expected to honour `-Dtarget.dir=` and write outputs under that + * directory so Gradle can track the artifact under [applicationBuildDir]. + */ +@CacheableTask +abstract class NestedMavenBuild @Inject constructor( + private val objects: ObjectFactory, + javaToolchains: JavaToolchainService, +) : DefaultTask() { + + init { + javaLauncher.convention( + javaToolchains.launcherFor { + languageVersion.set(JavaLanguageVersion.of(DEFAULT_NESTED_JAVA_VERSION)) + }, + ) + goals.convention(listOf("package")) + arguments.convention(emptyList()) + environment.convention(emptyMap()) + mavenOpts.convention("") + useMavenLocalRepository.convention(false) + } + + @get:Internal + abstract val applicationDir: DirectoryProperty + + @get:InputFiles + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + val applicationSources: FileTree = + objects.fileTree().from(applicationDir).matching { + exclude("target/**") + } + + @get:OutputDirectory + abstract val applicationBuildDir: DirectoryProperty + + @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val mavenExecutable: RegularFileProperty + + @get:InputFiles + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + val mavenWrapperFiles: FileTree = + objects.fileTree().from(project.rootProject.layout.projectDirectory.dir(".mvn/wrapper")).matching { + include("maven-wrapper.properties", "maven-wrapper.jar") + } + + @get:Nested + abstract val javaLauncher: Property + + @get:Input + abstract val goals: ListProperty + + @get:Input + abstract val arguments: ListProperty + + @get:Input + abstract val environment: MapProperty + + @get:Input + @get:Optional + abstract val mavenOpts: Property + + @get:Input + @get:Optional + abstract val mavenRepositoryProxy: Property + + @get:Input + abstract val useMavenLocalRepository: Property + + /** Timeout, in seconds, for the nested Maven build process. */ + @get:Input + @get:Optional + abstract val buildTimeoutSeconds: Property + + @get:Internal + abstract val mavenLocalRepository: DirectoryProperty + + @TaskAction + fun runNestedBuild() { + val appDir = applicationDir.get().asFile + val appBuildDirFile = applicationBuildDir.get().asFile + val targetDir = appBuildDirFile.resolve("target") + val daemonJavaHome = javaLauncher.get().metadata.installationPath.asFile + val command = mavenCommand(mavenExecutable.get().asFile).apply { + add("-Dtarget.dir=${targetDir.absolutePath}") + if (useMavenLocalRepository.get()) { + add("-Dmaven.repo.local=${mavenLocalRepository.get().asFile.absolutePath}") + } + addAll(arguments.get()) + addAll(goals.get()) + } + + val process = ProcessBuilder(command) + .directory(appDir) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .apply { + environment().apply { + clear() + putAll(nestedEnvironment(daemonJavaHome)) + } + } + .start() + + try { + val timeoutSeconds = buildTimeoutSeconds.orNull + val completed = + if (timeoutSeconds == null) { + process.waitFor() + true + } else { + process.waitFor(timeoutSeconds, TimeUnit.SECONDS) + } + if (!completed) { + process.destroyForcibly() + throw GradleException( + "Nested Maven build timed out after $timeoutSeconds seconds: " + + command.joinToString(" "), + ) + } + } catch (e: InterruptedException) { + process.destroyForcibly() + Thread.currentThread().interrupt() + throw GradleException("Interrupted while running nested Maven build", e) + } + val exitCode = process.exitValue() + if (exitCode != 0) { + throw GradleException( + "Nested Maven build failed with exit code $exitCode: ${command.joinToString(" ")}", + ) + } + } + + private fun nestedEnvironment(daemonJavaHome: File): Map { + val env = System.getenv().toMutableMap() + val repositoryProxy = mavenRepositoryProxy.orNull?.takeIf { it.isNotBlank() } + if (repositoryProxy != null) { + env["MAVEN_REPOSITORY_PROXY"] = repositoryProxy + env.putIfAbsent("MVNW_REPOURL", repositoryProxy.trimEnd('/')) + } + val opts = mavenOpts.orNull + if (!opts.isNullOrBlank()) { + env["MAVEN_OPTS"] = opts + } + env["JAVA_HOME"] = daemonJavaHome.absolutePath + env.putAll(environment.get()) + return env + } + + private fun mavenCommand(executable: File): MutableList = + if (isWindows()) { + mutableListOf("cmd", "/c", executable.absolutePath) + } else { + mutableListOf(executable.absolutePath) + } + + companion object { + internal fun mavenWrapperName(osName: String = System.getProperty("os.name")): String = + if (isWindows(osName)) { + "mvnw.cmd" + } else { + "mvnw" + } + + } +} diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/OperatingSystem.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/OperatingSystem.kt new file mode 100644 index 00000000000..a7e46fc640e --- /dev/null +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/OperatingSystem.kt @@ -0,0 +1,7 @@ +package datadog.buildlogic.smoketest + +import java.util.Locale + +internal fun isWindows(osName: String = System.getProperty("os.name")): Boolean = + osName.lowercase(Locale.ROOT).contains("windows") + 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 231816c547a..a34ee321cbc 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 @@ -5,6 +5,7 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -22,10 +23,10 @@ import java.util.Locale import javax.inject.Inject /** - * Project extension that wires a [NestedGradleBuild] task for a smoke-test application. + * Project extension that wires a nested build task for a smoke-test application. * - * The plugin only contributes a task when the consumer calls [application]; if the extension - * stays unconfigured, the plugin is a no-op and consumers can register [NestedGradleBuild] + * The plugin only contributes a task when the consumer calls [gradleApp] or [mavenApp]; if + * the extension stays unconfigured, the plugin is a no-op and consumers can register tasks * directly. */ abstract class SmokeTestAppExtension @Inject constructor( @@ -47,8 +48,8 @@ abstract class SmokeTestAppExtension @Inject constructor( 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. + * JDK used by the nested build. Defaults to a [DEFAULT_NESTED_JAVA_VERSION] toolchain; + * override to pin a different JDK if the nested application's build chain requires it. * The inner build script is responsible for pinning the produced bytecode level (e.g. * `java { sourceCompatibility = JavaVersion.VERSION_1_8 }`). */ @@ -58,8 +59,9 @@ abstract class SmokeTestAppExtension @Inject constructor( abstract val applicationDir: DirectoryProperty /** - * Directory the nested build writes its outputs to. The nested build script is expected to - * honour `-PappBuildDir=`; see the existing smoke-test inner builds for the pattern. + * Directory the nested build writes its outputs to. Gradle applications are expected to + * honour `-PappBuildDir=`; Maven applications are expected to honour + * `-Dtarget.dir=`. */ abstract val applicationBuildDir: DirectoryProperty @@ -108,19 +110,19 @@ abstract class SmokeTestAppExtension @Inject constructor( /** * Register the nested-build task and wire the produced artifact into every `Test` task as * a system property. Calling this triggers task registration; consumers that prefer to - * register [NestedGradleBuild] manually can leave [application] uncalled. + * register [NestedGradleBuild] manually can leave [gradleApp] uncalled. */ - fun application(action: Action) { - val spec = project.objects.newInstance() + fun gradleApp(action: Action) { + val spec = project.objects.newInstance() action.execute(spec) val taskName = requireNotNull(spec.taskName.orNull) { - "smokeTestApp.application { taskName = ... } is required" + "smokeTestApp.gradleApp { taskName = ... } is required" } val artifactPath = requireNotNull(spec.artifactPath.orNull) { - "smokeTestApp.application { artifactPath = ... } is required" + "smokeTestApp.gradleApp { artifactPath = ... } is required" } val sysProperty = requireNotNull(spec.sysProperty.orNull) { - "smokeTestApp.application { sysProperty = ... } is required" + "smokeTestApp.gradleApp { sysProperty = ... } is required" } val nestedTasks = spec.nestedTasks.orNull?.takeIf { it.isNotEmpty() } ?: listOf(taskName) @@ -135,11 +137,61 @@ abstract class SmokeTestAppExtension @Inject constructor( buildArguments.set(spec.buildArguments) environment.set(spec.environment) buildCacheEnabled.set(spec.buildCacheEnabled) + spec.stopTimeoutSeconds.orNull?.let(stopTimeoutSeconds::set) projectJars.set(this@SmokeTestAppExtension.projectJars) } + wireTestTasks(taskProvider, artifactPath, sysProperty, spec.additionalSystemProperties) + } + + /** Register a Maven nested-build task and expose its artifact to every `Test` task. */ + fun mavenApp(action: Action) { + val spec = project.objects.newInstance() + spec.mavenExecutable.convention(rootMavenExecutable()) + spec.mavenRepositoryProxy.convention( + project.providers.environmentVariable("MAVEN_REPOSITORY_PROXY"), + ) + spec.mavenLocalRepository.convention(project.rootProject.layout.projectDirectory.dir(".mvn/caches")) + spec.useMavenLocalRepository.convention(isCiProvider()) + action.execute(spec) + + val taskName = requireNotNull(spec.taskName.orNull) { + "smokeTestApp.mavenApp { taskName = ... } is required" + } + val artifactPath = requireNotNull(spec.artifactPath.orNull) { + "smokeTestApp.mavenApp { artifactPath = ... } is required" + } + val sysProperty = requireNotNull(spec.sysProperty.orNull) { + "smokeTestApp.mavenApp { sysProperty = ... } is required" + } + + val taskProvider: TaskProvider = + project.tasks.register(taskName) { + applicationDir.set(this@SmokeTestAppExtension.applicationDir) + applicationBuildDir.set(this@SmokeTestAppExtension.applicationBuildDir) + javaLauncher.set(this@SmokeTestAppExtension.javaLauncher) + mavenExecutable.set(spec.mavenExecutable) + goals.set(spec.goals) + arguments.set(spec.arguments) + environment.set(spec.environment) + mavenOpts.set(spec.mavenOpts) + mavenRepositoryProxy.set(spec.mavenRepositoryProxy) + useMavenLocalRepository.set(spec.useMavenLocalRepository) + mavenLocalRepository.set(spec.mavenLocalRepository) + spec.buildTimeoutSeconds.orNull?.let(buildTimeoutSeconds::set) + } + + wireTestTasks(taskProvider, artifactPath, sysProperty, spec.additionalSystemProperties) + } + + private fun wireTestTasks( + taskProvider: TaskProvider<*>, + artifactPath: String, + sysProperty: String, + additionalSystemProperties: MapProperty, + ) { val artifactProvider: Provider = applicationBuildDir.file(artifactPath) - val extras = spec.additionalSystemProperties.get().mapValues { (_, relativePath) -> + val extras = additionalSystemProperties.get().mapValues { (_, relativePath) -> applicationBuildDir.file(relativePath) } project.tasks.withType().configureEach { @@ -229,16 +281,26 @@ abstract class SmokeTestAppExtension @Inject constructor( properties[name] = value } } + + private fun rootMavenExecutable(): Provider = + project.providers.provider { + project.rootProject.layout.projectDirectory.file(NestedMavenBuild.mavenWrapperName()) + } + + private fun isCiProvider(): Provider = + project.providers.environmentVariable("CI") + .map { it.equals("true", ignoreCase = true) } + .orElse(false) } -/** DSL describing the nested-build invocation for one smoke-test application. */ +/** Common DSL for a smoke-test application artifact. */ abstract class ApplicationSpec @Inject constructor() { init { - buildCacheEnabled.convention(false) + additionalSystemProperties.convention(emptyMap()) } - /** Outer task name; the nested daemon runs the same task by default. */ + /** Outer task name registered by the smoke-test plugin. */ abstract val taskName: Property /** Path to the produced artifact, relative to `applicationBuildDir`. */ @@ -247,6 +309,23 @@ abstract class ApplicationSpec @Inject constructor() { /** System property name set on Test tasks to point them at the produced artifact. */ abstract val sysProperty: Property + /** + * Additional system properties to forward to every `Test` task, keyed by property name with + * values resolved against `applicationBuildDir`. Use this for smoke tests that need more + * than the single primary artifact path (e.g. a separately unpacked server install). + */ + abstract val additionalSystemProperties: MapProperty +} + +/** DSL describing a nested Gradle invocation for one smoke-test application. */ +abstract class GradleAppSpec @Inject constructor() : ApplicationSpec() { + + init { + buildCacheEnabled.convention(false) + buildArguments.convention(emptyList()) + environment.convention(emptyMap()) + } + /** Tasks run inside the nested build. Defaults to `[taskName]`. */ abstract val nestedTasks: ListProperty @@ -261,13 +340,6 @@ abstract class ApplicationSpec @Inject constructor() { */ abstract val environment: MapProperty - /** - * Additional system properties to forward to every `Test` task, keyed by property name with - * values resolved against `applicationBuildDir`. Use this for smoke tests that need more - * than the single primary artifact path (e.g. a separately unpacked server install). - */ - abstract val additionalSystemProperties: MapProperty - /** * Whether to enable the build cache in the nested Gradle invocation. * Gradle's org.gradle.caching flag is resolved from many sources (project, @@ -278,6 +350,47 @@ abstract class ApplicationSpec @Inject constructor() { * `--build-cache` / `--no-build-cache` is passed explicitly either way. */ abstract val buildCacheEnabled: Property + + /** Timeout, in seconds, for stopping the nested Gradle daemon after the build. */ + abstract val stopTimeoutSeconds: Property +} + +/** DSL describing a nested Maven invocation for one smoke-test application. */ +abstract class MavenAppSpec @Inject constructor() : ApplicationSpec() { + + init { + goals.convention(listOf("package")) + arguments.convention(emptyList()) + environment.convention(emptyMap()) + mavenOpts.convention("") + } + + /** Goals run inside the nested Maven build. Defaults to `package`. */ + abstract val goals: ListProperty + + /** Extra arguments passed to the nested Maven invocation before [goals]. */ + abstract val arguments: ListProperty + + /** Extra environment variables exposed to the nested Maven process. */ + abstract val environment: MapProperty + + /** Optional `MAVEN_OPTS` value for the nested Maven process. */ + abstract val mavenOpts: Property + + /** Root Maven wrapper executable used to launch the nested build. */ + abstract val mavenExecutable: RegularFileProperty + + /** Maven repository proxy used by the wrapper and by Maven builds that read the env var. */ + abstract val mavenRepositoryProxy: Property + + /** Whether to pass [mavenLocalRepository] as `-Dmaven.repo.local`. Defaults to CI only. */ + abstract val useMavenLocalRepository: Property + + /** Local repository path used when [useMavenLocalRepository] is enabled. */ + abstract val mavenLocalRepository: DirectoryProperty + + /** Timeout, in seconds, for the nested Maven build process. */ + abstract val buildTimeoutSeconds: Property } /** @@ -288,8 +401,8 @@ abstract class ApplicationSpec @Inject constructor() { const val DEFAULT_NESTED_GRADLE_VERSION = "8.14.5" /** - * Default JDK language version for the nested daemon. JDK 21 is the version the root build - * requires for Gradle 9; standardising the nested daemon on the same JDK avoids pulling a + * Default JDK language version for the nested build. JDK 21 is the version the root build + * requires for Gradle 9; standardising nested builds on the same JDK avoids pulling a * second toolchain onto dev machines and CI runners. Inner build scripts cross-compile down * to their actual bytecode target via `java { sourceCompatibility = ... }`. */ diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPlugin.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPlugin.kt index 06c97503202..30f2476debd 100644 --- a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPlugin.kt +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/SmokeTestAppPlugin.kt @@ -6,14 +6,13 @@ import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.withType /** - * Exposes the [NestedGradleBuild] task type plus a `smokeTestApp` extension that wires the - * nested-build task and Test-side system properties for a smoke-test application. + * Exposes nested build task types plus a `smokeTestApp` extension that wires a smoke-test + * application build and Test-side system properties. * * Consumers can either: - * - configure `smokeTestApp { application { ... } }` to let the plugin register the task and - * wire it into every `Test` task, or - * - leave the extension untouched and register a [NestedGradleBuild] task manually (for cases - * that need more control, e.g. additional `Exec`-like task wiring). + * - configure `smokeTestApp { gradleApp { ... } }` or `smokeTestApp { mavenApp { ... } }` to + * let the plugin register the task and wire it into every `Test` task, or + * - leave the extension untouched and register a task manually for cases that need more control. */ class SmokeTestAppPlugin : Plugin { override fun apply(project: Project) { 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 b8e8cd757f1..086accd5ce3 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 @@ -41,7 +41,7 @@ class SmokeTestAppEndToEndTest { fun `nested build produces the configured artifact`() { writeOuterSettings() writeSmokeTestAppBuild( - smokeTestApplication( + smokeTestGradleApplication( taskName = "buildJar", artifactPath = "libs/sample.jar", sysProperty = "sample.path", @@ -65,13 +65,70 @@ class SmokeTestAppEndToEndTest { assertThat(applicationOutput("libs/sample.jar")).exists() } + @Test + fun `nested Maven build produces the configured artifact`() { + writeOuterSettings() + writeFakeMavenWrapper() + writeSmokeTestAppBuild( + smokeTestMavenApplication( + taskName = "packageApp", + artifactPath = "target/sample.jar", + sysProperty = "sample.path", + additionalConfig = """ + mavenExecutable.set(layout.projectDirectory.file("${fakeMavenWrapperName()}")) + mavenOpts.set("-Xmx512M") + """, + ), + ) + File(applicationDir, "pom.xml").writeText("") + + val result = runner( + "packageApp", + environment = mapOf("MAVEN_REPOSITORY_PROXY" to "https://repo.example/maven2/"), + ).build() + + assertThat(result.task(":packageApp")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + assertThat(applicationOutput("target/sample.jar")).exists() + assertThat(applicationOutput("target/maven-env.txt").readLines()).contains( + "MAVEN_OPTS=-Xmx512M", + "MVNW_REPOURL=https://repo.example/maven2", + ) + } + + @Test + fun `nested Maven build output is restored from the outer build cache`() { + writeOuterSettings(withLocalBuildCache = true) + writeFakeMavenWrapper() + writeSmokeTestAppBuild( + smokeTestMavenApplication( + taskName = "packageApp", + artifactPath = "target/sample.jar", + sysProperty = "sample.path", + additionalConfig = """ + mavenExecutable.set(layout.projectDirectory.file("${fakeMavenWrapperName()}")) + """, + ), + ) + File(applicationDir, "pom.xml").writeText("") + + val first = runner("packageApp", "--build-cache").build() + assertThat(first.task(":packageApp")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + assertThat(applicationOutput("target/sample.jar")).exists() + + applicationBuildDir.deleteRecursively() + + val second = runner("packageApp", "--build-cache").build() + assertThat(second.task(":packageApp")?.outcome).isEqualTo(TaskOutcome.FROM_CACHE) + assertThat(applicationOutput("target/sample.jar")).exists() + } + @Test fun `nested build clears inherited Gradle launcher environment`() { writeOuterSettings() val inheritedGradleUserHome = projectDir.resolve("inherited-gradle-user-home").toFile() inheritedGradleUserHome.mkdirs() writeSmokeTestAppBuild( - smokeTestApplication( + smokeTestGradleApplication( taskName = "recordGradleEnvironment", artifactPath = "gradle-env.txt", sysProperty = "gradle.env.path", @@ -129,7 +186,7 @@ class SmokeTestAppEndToEndTest { File(projectDir.toFile(), "agent.jar").writeText("agent") writeSmokeTestAppBuild( """ - ${smokeTestApplication( + ${smokeTestGradleApplication( taskName = "recordNativeInputs", artifactPath = "native-inputs.txt", sysProperty = "native.inputs.path", @@ -176,7 +233,7 @@ class SmokeTestAppEndToEndTest { fun `init scripts are not added outside CI`() { writeOuterSettings() writeSmokeTestAppBuild( - smokeTestApplication( + smokeTestGradleApplication( taskName = "recordInitScripts", artifactPath = "init-script-count.txt", sysProperty = "init.script.count.path", @@ -216,7 +273,7 @@ class SmokeTestAppEndToEndTest { writeMavenArtifact(projectRepository, "com.example", "shared", "1.0", "project") writeMavenArtifact(projectRepository, "com.example", "project-only", "1.0", "project-only") writeSmokeTestAppBuild( - smokeTestApplication( + smokeTestGradleApplication( taskName = "resolveRepositories", artifactPath = "resolved-repositories.txt", sysProperty = "resolved.repositories.path", @@ -281,7 +338,7 @@ class SmokeTestAppEndToEndTest { ) { writeOuterSettings() writeSmokeTestAppBuild( - smokeTestApplication( + smokeTestGradleApplication( taskName = "recordCacheFlag", artifactPath = "cache-flag.txt", sysProperty = "cache.flag.path", @@ -331,7 +388,7 @@ class SmokeTestAppEndToEndTest { ) { writeOuterSettings(withLocalBuildCache = true) writeSmokeTestAppBuild( - smokeTestApplication( + smokeTestGradleApplication( taskName = "buildJar", artifactPath = "libs/sample.jar", sysProperty = "sample.path", @@ -370,6 +427,7 @@ class SmokeTestAppEndToEndTest { ).toTypedArray() val second = runner(*secondArgs).build() assertThat(second.task(":buildJar")?.outcome).isEqualTo(expectedSecondOutcome) + assertThat(applicationOutput("libs/sample.jar")).exists() } private fun writeOuterSettings(withLocalBuildCache: Boolean = false) { @@ -433,25 +491,63 @@ class SmokeTestAppEndToEndTest { ) } - private fun smokeTestApplication( + private fun smokeTestGradleApplication( taskName: String, artifactPath: String, sysProperty: String, additionalConfig: String = "", ): String { val config = additionalConfig.trimIndent() - return buildString { - appendLine("application {") - appendLine(" taskName.set(\"$taskName\")") - appendLine(" artifactPath.set(\"$artifactPath\")") - appendLine(" sysProperty.set(\"$sysProperty\")") - if (config.isNotBlank()) { - appendLine(config.prependIndent(" ")) + return listOfNotNull( + """ + gradleApp { + taskName.set("$taskName") + artifactPath.set("$artifactPath") + sysProperty.set("$sysProperty") + """.trimIndent(), + config.takeIf { it.isNotBlank() }?.prependIndent(" "), + "}", + ).joinToString(System.lineSeparator()) + } + + private fun smokeTestMavenApplication( + taskName: String, + artifactPath: String, + sysProperty: String, + additionalConfig: String = "", + ): String { + val config = additionalConfig.trimIndent() + return listOfNotNull( + """ + mavenApp { + taskName.set("$taskName") + artifactPath.set("$artifactPath") + sysProperty.set("$sysProperty") + """.trimIndent(), + config.takeIf { it.isNotBlank() }?.prependIndent(" "), + "}", + ).joinToString(System.lineSeparator()) + } + + private fun writeFakeMavenWrapper() { + val wrapper = File(projectDir.toFile(), fakeMavenWrapperName()) + val resource = + requireNotNull(javaClass.getResource(fakeMavenWrapperName())) { + "Missing fake Maven wrapper test resource" } - appendLine("}") + wrapper.writeBytes(resource.readBytes()) + if (!isWindows()) { + wrapper.setExecutable(true) } } + private fun fakeMavenWrapperName(): String = + if (isWindows()) { + "fake-mvnw.cmd" + } else { + "fake-mvnw" + } + private fun writeInnerSettings() { File(applicationDir, "settings.gradle.kts").writeText( """ @@ -542,7 +638,7 @@ class SmokeTestAppEndToEndTest { @JvmStatic fun buildCacheFlagCases(): List = listOf( - // (scenario name, DSL line added to the `application { … }` block, expected + // (scenario name, DSL line added to the `gradleApp { … }` block, expected // `gradle.startParameter.isBuildCacheEnabled` value seen by the nested daemon) Arguments.of("default off", "", "false"), Arguments.of("explicit true", "buildCacheEnabled.set(true)", "true"), 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 9df643a976c..010af208667 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 @@ -1,5 +1,7 @@ package datadog.buildlogic.smoketest +import datadog.buildlogic.smoketest.NestedGradleBuild.Companion.gradleExecutableName +import datadog.buildlogic.smoketest.NestedMavenBuild.Companion.mavenWrapperName import org.assertj.core.api.Assertions.assertThat import org.gradle.api.plugins.JavaPlugin import org.gradle.jvm.toolchain.JavaLanguageVersion @@ -26,13 +28,14 @@ class SmokeTestAppPluginTest { } @Test - fun `plugin is a no-op when smokeTestApp_application is never called`() { + fun `plugin is a no-op when no smokeTestApp application is configured`() { val project = ProjectBuilder.builder().build() project.plugins.apply("dd-trace-java.smoke-test-app") - // No task of our type should be registered until `application { }` is invoked. + // No nested build task should be registered until `gradleApp { }` or `mavenApp { }` is invoked. assertThat(project.tasks.withType()).isEmpty() + assertThat(project.tasks.withType()).isEmpty() } @Test @@ -68,7 +71,7 @@ class SmokeTestAppPluginTest { } @Test - fun `application block registers NestedGradleBuild task with configured inputs`() { + fun `gradleApp block registers NestedGradleBuild task with configured inputs`() { val project = ProjectBuilder.builder().build() project.apply() project.plugins.apply("dd-trace-java.smoke-test-app") @@ -76,7 +79,7 @@ class SmokeTestAppPluginTest { val extension = project.extensions.getByType() extension.gradleVersion.set("8.14.5") extension.gradleDistributionBaseUrl.set("https://mass.example") - extension.application { + extension.gradleApp { taskName.set("packageApp") artifactPath.set("libs/test.jar") sysProperty.set("test.path") @@ -98,6 +101,86 @@ class SmokeTestAppPluginTest { assertThat(task.buildArguments.get()).containsExactly("-Ddemo=true") assertThat(task.environment.get()).containsEntry("DEMO_ENV", "true") assertThat(task.buildCacheEnabled.get()).isTrue() + assertThat(task.stopTimeoutSeconds.isPresent).isFalse() + } + + @Test + fun `mavenApp block registers NestedMavenBuild task with configured inputs`() { + val project = ProjectBuilder.builder().build() + val wrapperProperties = project.file(".mvn/wrapper/maven-wrapper.properties") + wrapperProperties.parentFile.mkdirs() + wrapperProperties.writeText("distributionUrl=https://repo.example/maven2/test.zip") + project.apply() + project.plugins.apply("dd-trace-java.smoke-test-app") + + val extension = project.extensions.getByType() + extension.mavenApp { + taskName.set("packageApp") + artifactPath.set("target/test.jar") + sysProperty.set("test.path") + goals.set(listOf("verify")) + arguments.add("-Ddemo=true") + environment.put("DEMO_ENV", "true") + mavenOpts.set("-Xmx512M") + mavenExecutable.set(project.layout.projectDirectory.file("mvnw")) + mavenRepositoryProxy.set("https://repo.example") + useMavenLocalRepository.set(true) + mavenLocalRepository.set(project.layout.projectDirectory.dir("m2")) + } + + val task = project.tasks.getByName("packageApp") as NestedMavenBuild + + assertThat(task.applicationDir.get().asFile) + .isEqualTo(project.layout.projectDirectory.dir("application").asFile) + assertThat(task.applicationBuildDir.get().asFile) + .isEqualTo(project.layout.buildDirectory.dir("application").get().asFile) + assertThat(task.goals.get()).containsExactly("verify") + assertThat(task.arguments.get()).containsExactly("-Ddemo=true") + assertThat(task.environment.get()).containsEntry("DEMO_ENV", "true") + assertThat(task.mavenOpts.get()).isEqualTo("-Xmx512M") + assertThat(task.mavenExecutable.get().asFile) + .isEqualTo(project.layout.projectDirectory.file("mvnw").asFile) + assertThat(task.mavenWrapperFiles.files).containsExactly(wrapperProperties) + assertThat(task.mavenRepositoryProxy.get()).isEqualTo("https://repo.example") + assertThat(task.useMavenLocalRepository.get()).isTrue() + assertThat(task.mavenLocalRepository.get().asFile) + .isEqualTo(project.layout.projectDirectory.dir("m2").asFile) + assertThat(task.buildTimeoutSeconds.isPresent).isFalse() + } + + @Test + fun `nested build timeouts can be overridden`() { + val project = ProjectBuilder.builder().build() + project.apply() + project.plugins.apply("dd-trace-java.smoke-test-app") + + val extension = project.extensions.getByType() + extension.gradleApp { + taskName.set("packageGradleApp") + artifactPath.set("libs/test.jar") + sysProperty.set("test.gradle.path") + stopTimeoutSeconds.set(45L) + } + extension.mavenApp { + taskName.set("packageMavenApp") + artifactPath.set("target/test.jar") + sysProperty.set("test.maven.path") + buildTimeoutSeconds.set(60L) + } + + val gradleTask = project.tasks.getByName("packageGradleApp") as NestedGradleBuild + val mavenTask = project.tasks.getByName("packageMavenApp") as NestedMavenBuild + + assertThat(gradleTask.stopTimeoutSeconds.get()).isEqualTo(45L) + assertThat(mavenTask.buildTimeoutSeconds.get()).isEqualTo(60L) + } + + @Test + fun `wrapper executable names follow the host operating system`() { + assertThat(mavenWrapperName("Windows 11")).isEqualTo("mvnw.cmd") + assertThat(mavenWrapperName("Mac OS X")).isEqualTo("mvnw") + assertThat(gradleExecutableName("Windows Server 2022")).isEqualTo("gradle.bat") + assertThat(gradleExecutableName("Linux")).isEqualTo("gradle") } @Test diff --git a/build-logic/smoke-test/src/test/resources/datadog/buildlogic/smoketest/fake-mvnw b/build-logic/smoke-test/src/test/resources/datadog/buildlogic/smoketest/fake-mvnw new file mode 100644 index 00000000000..7eaa9c6feae --- /dev/null +++ b/build-logic/smoke-test/src/test/resources/datadog/buildlogic/smoketest/fake-mvnw @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +target="" +while [ "$#" -gt 0 ]; do + case "$1" in + -Dtarget.dir=*) target="${1#-Dtarget.dir=}" ;; + esac + shift +done + +[ -n "$target" ] || exit 2 +mkdir -p "$target" +printf "jar" > "$target/sample.jar" +{ + printf "MAVEN_OPTS=%s\n" "${MAVEN_OPTS:-}" + printf "MVNW_REPOURL=%s\n" "${MVNW_REPOURL:-}" +} > "$target/maven-env.txt" diff --git a/build-logic/smoke-test/src/test/resources/datadog/buildlogic/smoketest/fake-mvnw.cmd b/build-logic/smoke-test/src/test/resources/datadog/buildlogic/smoketest/fake-mvnw.cmd new file mode 100644 index 00000000000..0a1c101024c --- /dev/null +++ b/build-logic/smoke-test/src/test/resources/datadog/buildlogic/smoketest/fake-mvnw.cmd @@ -0,0 +1,19 @@ +@echo off +setlocal enabledelayedexpansion + +set "target=" +:parse +if "%~1"=="" goto run +set "arg=%~1" +if "!arg:~0,13!"=="-Dtarget.dir=" set "target=!arg:~13!" +shift +goto parse + +:run +if "%target%"=="" exit /b 2 +mkdir "%target%" 2>nul +echo jar>"%target%\sample.jar" +( + echo MAVEN_OPTS=%MAVEN_OPTS% + echo MVNW_REPOURL=%MVNW_REPOURL% +) > "%target%\maven-env.txt" diff --git a/dd-smoke-tests/apm-tracing-disabled/build.gradle b/dd-smoke-tests/apm-tracing-disabled/build.gradle index 7b5d43b11db..d1a5504043a 100644 --- a/dd-smoke-tests/apm-tracing-disabled/build.gradle +++ b/dd-smoke-tests/apm-tracing-disabled/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'ASM Standalone Billing Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/apm-tracing-disabled-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/armeria-grpc/build.gradle b/dd-smoke-tests/armeria-grpc/build.gradle index ffc3ce45789..835d07ca040 100644 --- a/dd-smoke-tests/armeria-grpc/build.gradle +++ b/dd-smoke-tests/armeria-grpc/build.gradle @@ -43,7 +43,7 @@ protobuf { } smokeTestApp { - application { + gradleApp { taskName = 'armeriaBuild' nestedTasks = ['build'] artifactPath = 'libs/armeria-smoketest-all.jar' diff --git a/dd-smoke-tests/kafka-2/build.gradle b/dd-smoke-tests/kafka-2/build.gradle index 29a18a59f20..ec1e85d52e3 100644 --- a/dd-smoke-tests/kafka-2/build.gradle +++ b/dd-smoke-tests/kafka-2/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'Kafka 2.x Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/kafka-2-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/kafka-3/build.gradle b/dd-smoke-tests/kafka-3/build.gradle index 9ae27138114..b21a187b8f2 100644 --- a/dd-smoke-tests/kafka-3/build.gradle +++ b/dd-smoke-tests/kafka-3/build.gradle @@ -12,7 +12,7 @@ testJvmConstraints { description = 'Kafka 3.x Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/kafka-3-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/openfeature/build.gradle b/dd-smoke-tests/openfeature/build.gradle index cb2c83d112d..fb3a68a2638 100644 --- a/dd-smoke-tests/openfeature/build.gradle +++ b/dd-smoke-tests/openfeature/build.gradle @@ -11,7 +11,7 @@ testJvmConstraints { } smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/openfeature-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/quarkus-native/build.gradle b/dd-smoke-tests/quarkus-native/build.gradle index 447be38bcd7..d06e823c2d1 100644 --- a/dd-smoke-tests/quarkus-native/build.gradle +++ b/dd-smoke-tests/quarkus-native/build.gradle @@ -34,7 +34,7 @@ Provider graalvmHome = providers.provider({ } as Callable) smokeTestApp { - application { + gradleApp { taskName = 'quarkusNativeBuild' nestedTasks = ['build'] artifactPath = 'quarkus-native-smoketest--runner' diff --git a/dd-smoke-tests/quarkus/build.gradle b/dd-smoke-tests/quarkus/build.gradle index 9817589dc0a..f9967bbd48f 100644 --- a/dd-smoke-tests/quarkus/build.gradle +++ b/dd-smoke-tests/quarkus/build.gradle @@ -16,7 +16,7 @@ smokeTestApp { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(11) } - application { + gradleApp { taskName = 'quarkusBuild' nestedTasks = ['build'] // Quarkus' uber-jar is named `-runner.jar`; here the inner project has no diff --git a/dd-smoke-tests/spring-boot-2.7-webflux/build.gradle b/dd-smoke-tests/spring-boot-2.7-webflux/build.gradle index b637f51a344..adae38e89e8 100644 --- a/dd-smoke-tests/spring-boot-2.7-webflux/build.gradle +++ b/dd-smoke-tests/spring-boot-2.7-webflux/build.gradle @@ -7,7 +7,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'Spring Boot 2.7 Webflux Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'webfluxBuild' nestedTasks = ['bootJar'] artifactPath = 'libs/webflux-2.7-smoketest.jar' diff --git a/dd-smoke-tests/spring-boot-3.0-native/build.gradle b/dd-smoke-tests/spring-boot-3.0-native/build.gradle index 91290953ef7..c986996bfd3 100644 --- a/dd-smoke-tests/spring-boot-3.0-native/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-native/build.gradle @@ -34,7 +34,7 @@ Provider graalvmHome = providers.provider({ } as Callable) smokeTestApp { - application { + gradleApp { taskName = 'springNativeBuild' nestedTasks = ['nativeCompile'] artifactPath = 'native/nativeCompile/native-3.0-smoketest' diff --git a/dd-smoke-tests/spring-boot-3.0-webflux/build.gradle b/dd-smoke-tests/spring-boot-3.0-webflux/build.gradle index 006f844ab11..2479e28ac89 100644 --- a/dd-smoke-tests/spring-boot-3.0-webflux/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-webflux/build.gradle @@ -11,7 +11,7 @@ testJvmConstraints { description = 'Spring Boot 3.0 Webflux Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'webfluxBuild30' nestedTasks = ['bootJar'] artifactPath = 'libs/webflux-3.0-smoketest.jar' diff --git a/dd-smoke-tests/spring-boot-3.0-webmvc/build.gradle b/dd-smoke-tests/spring-boot-3.0-webmvc/build.gradle index f8ec1ce3975..07e850c365a 100644 --- a/dd-smoke-tests/spring-boot-3.0-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-webmvc/build.gradle @@ -11,7 +11,7 @@ testJvmConstraints { description = 'Spring Boot 3.0 WebMvc Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'webmvcBuild30' nestedTasks = ['bootJar'] artifactPath = 'libs/webmvc-3.0-smoketest.jar' diff --git a/dd-smoke-tests/spring-boot-3.3-webmvc/build.gradle b/dd-smoke-tests/spring-boot-3.3-webmvc/build.gradle index dc252457801..188088ad129 100644 --- a/dd-smoke-tests/spring-boot-3.3-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-3.3-webmvc/build.gradle @@ -11,7 +11,7 @@ testJvmConstraints { description = 'Spring Boot 3.3 WebMvc Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'webmvcBuild3' nestedTasks = ['bootJar'] artifactPath = 'libs/webmvc-3.3-smoketest.jar' diff --git a/dd-smoke-tests/springboot-freemarker/build.gradle b/dd-smoke-tests/springboot-freemarker/build.gradle index ca3fd1c8991..3094e88112b 100644 --- a/dd-smoke-tests/springboot-freemarker/build.gradle +++ b/dd-smoke-tests/springboot-freemarker/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot Freemarker Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/springboot-freemarker-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/springboot-java-11/build.gradle b/dd-smoke-tests/springboot-java-11/build.gradle index 142199fc645..3d1d775edaa 100644 --- a/dd-smoke-tests/springboot-java-11/build.gradle +++ b/dd-smoke-tests/springboot-java-11/build.gradle @@ -12,7 +12,7 @@ testJvmConstraints { } smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/springboot-java-11-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/springboot-java-17/build.gradle b/dd-smoke-tests/springboot-java-17/build.gradle index c896ee5efee..ca3a160449d 100644 --- a/dd-smoke-tests/springboot-java-17/build.gradle +++ b/dd-smoke-tests/springboot-java-17/build.gradle @@ -12,7 +12,7 @@ testJvmConstraints { } smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/springboot-java-17-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/springboot-jetty-jsp/build.gradle b/dd-smoke-tests/springboot-jetty-jsp/build.gradle index 29abca9fab9..09dbde5de64 100644 --- a/dd-smoke-tests/springboot-jetty-jsp/build.gradle +++ b/dd-smoke-tests/springboot-jetty-jsp/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot Jetty JSP Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootWar' artifactPath = 'libs/springboot-jetty-jsp-smoketest.war' sysProperty = 'datadog.smoketest.springboot.war.path' diff --git a/dd-smoke-tests/springboot-jpa/build.gradle b/dd-smoke-tests/springboot-jpa/build.gradle index b70934ee101..130752d60fc 100644 --- a/dd-smoke-tests/springboot-jpa/build.gradle +++ b/dd-smoke-tests/springboot-jpa/build.gradle @@ -7,7 +7,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot JPA Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootWar' artifactPath = 'libs/springboot-jpa-smoketest.war' sysProperty = 'datadog.smoketest.springboot.bootWar.path' diff --git a/dd-smoke-tests/springboot-openliberty-20/build.gradle b/dd-smoke-tests/springboot-openliberty-20/build.gradle index 0215eb17761..a277ba1ec49 100644 --- a/dd-smoke-tests/springboot-openliberty-20/build.gradle +++ b/dd-smoke-tests/springboot-openliberty-20/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'dd-trace-java.smoke-test-app' +} + apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot Open Liberty 20-22 Smoke Tests' @@ -5,53 +9,30 @@ dependencies { testImplementation project(':dd-smoke-tests') } -def appDir = "$projectDir/application" def jarName = "demo-open-liberty-app.jar" -def jarPath = "$buildDir/application/target/${jarName}" -def isWindows = System.getProperty("os.name").toLowerCase().contains("win") -def mvnwCommand = isWindows ? 'mvnw.cmd' : 'mvnw' - -// Keep the Maven wrapper here until more Maven smoke-test applications need nested builds. -// The smoke-test-app plugin is Gradle-focused, and a Maven sibling task is not worth the -// extra API surface for only the two OpenLiberty applications. -// compile the Open liberty spring boot server -tasks.register('mvnStage', Exec) { - workingDir("$appDir") - environment += [ - "MAVEN_OPTS": "-Xmx512M", - "JAVA_HOME": getLazyJavaHomeFor(8) - ] - List mvnArgs = ["$rootDir/${mvnwCommand}", "-Dtarget.dir=${buildDir}/application/target", "package"] - - // Specify caches folder on CI. - if (providers.environmentVariable("CI").isPresent()) { - mvnArgs.add(1, "-Dmaven.repo.local=$rootDir/.mvn/caches") +smokeTestApp { + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } + mavenApp { + taskName = 'mvnStage' + artifactPath = "target/${jarName}" + sysProperty = 'datadog.smoketest.openliberty.jar.path' + mavenOpts = '-Xmx512M' } - - commandLine(mvnArgs) - - inputs.dir "$appDir/src" - inputs.file "$appDir/pom.xml" - outputs.file jarPath } tasks.named("compileTestGroovy", GroovyCompile) { dependsOn 'mvnStage' outputs.upToDateWhen { - !mvnStage.didWork + !tasks.named('mvnStage').get().didWork } } -// compiled dir of the packaged spring boot app with embedded openliberty -tasks.withType(Test).configureEach { - jvmArgs "-Ddatadog.smoketest.openliberty.jar.path=${jarPath}" -} - - spotless { java { - target fileTree("$appDir") { + target fileTree("$projectDir/application") { include "**/*.java" } } diff --git a/dd-smoke-tests/springboot-openliberty-23/build.gradle b/dd-smoke-tests/springboot-openliberty-23/build.gradle index 43c7f8a193a..73e290faf2e 100644 --- a/dd-smoke-tests/springboot-openliberty-23/build.gradle +++ b/dd-smoke-tests/springboot-openliberty-23/build.gradle @@ -1,3 +1,7 @@ +plugins { + id 'dd-trace-java.smoke-test-app' +} + apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot Open Liberty Smoke Tests' @@ -5,53 +9,30 @@ dependencies { testImplementation project(':dd-smoke-tests') } -def appDir = "$projectDir/application" def jarName = "demo-open-liberty-app.jar" -def jarPath = "$buildDir/application/target/${jarName}" -def isWindows = System.getProperty("os.name").toLowerCase().contains("win") -def mvnwCommand = isWindows ? 'mvnw.cmd' : 'mvnw' - -// Keep the Maven wrapper here until more Maven smoke-test applications need nested builds. -// The smoke-test-app plugin is Gradle-focused, and a Maven sibling task is not worth the -// extra API surface for only the two OpenLiberty applications. -// compile the Open liberty spring boot server -tasks.register('mvnStage', Exec) { - workingDir("$appDir") - environment += [ - "MAVEN_OPTS": "-Xmx512M", - "JAVA_HOME": getLazyJavaHomeFor(8) - ] - List mvnArgs = ["$rootDir/${mvnwCommand}", "-Dtarget.dir=${buildDir}/application/target", "package"] - - // Specify caches folder on CI. - if (providers.environmentVariable("CI").isPresent()) { - mvnArgs.add(1, "-Dmaven.repo.local=$rootDir/.mvn/caches") +smokeTestApp { + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(8) + } + mavenApp { + taskName = 'mvnStage' + artifactPath = "target/${jarName}" + sysProperty = 'datadog.smoketest.openliberty.jar.path' + mavenOpts = '-Xmx512M' } - - commandLine(mvnArgs) - - inputs.dir "$appDir/src" - inputs.file "$appDir/pom.xml" - outputs.file jarPath } tasks.named("compileTestGroovy", GroovyCompile) { dependsOn 'mvnStage' outputs.upToDateWhen { - !mvnStage.didWork + !tasks.named('mvnStage').get().didWork } } -// compiled dir of the packaged spring boot app with embedded openliberty -tasks.withType(Test).configureEach { - jvmArgs "-Ddatadog.smoketest.openliberty.jar.path=${jarPath}" -} - - spotless { java { - target fileTree("$appDir") { + target fileTree("$projectDir/application") { include "**/*.java" } } diff --git a/dd-smoke-tests/springboot-thymeleaf/build.gradle b/dd-smoke-tests/springboot-thymeleaf/build.gradle index 4cd0d533f81..a92caa4098b 100644 --- a/dd-smoke-tests/springboot-thymeleaf/build.gradle +++ b/dd-smoke-tests/springboot-thymeleaf/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot thymeleaf 3 Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/springboot-thymeleaf-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/springboot-tomcat-jsp/build.gradle b/dd-smoke-tests/springboot-tomcat-jsp/build.gradle index 0cf65d39298..b82c8fcdda7 100644 --- a/dd-smoke-tests/springboot-tomcat-jsp/build.gradle +++ b/dd-smoke-tests/springboot-tomcat-jsp/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot Tomcat JSP Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootWar' artifactPath = 'libs/springboot-tomcat-jsp-smoketest.war' sysProperty = 'datadog.smoketest.springboot.war.path' diff --git a/dd-smoke-tests/springboot-tomcat/build.gradle b/dd-smoke-tests/springboot-tomcat/build.gradle index d7029149a9d..5c506614081 100644 --- a/dd-smoke-tests/springboot-tomcat/build.gradle +++ b/dd-smoke-tests/springboot-tomcat/build.gradle @@ -35,7 +35,7 @@ configurations { } smokeTestApp { - application { + gradleApp { taskName = 'bootWar' artifactPath = 'libs/springboot-tomcat-smoketest.war' sysProperty = 'datadog.smoketest.springboot.war.path' diff --git a/dd-smoke-tests/springboot-velocity/build.gradle b/dd-smoke-tests/springboot-velocity/build.gradle index 79f63efeb90..7e18918b074 100644 --- a/dd-smoke-tests/springboot-velocity/build.gradle +++ b/dd-smoke-tests/springboot-velocity/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'SpringBoot Velocity Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'bootJar' artifactPath = 'libs/springboot-velocity-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' diff --git a/dd-smoke-tests/vertx-3.4/build.gradle b/dd-smoke-tests/vertx-3.4/build.gradle index 77ae03e703d..3f268d856b6 100644 --- a/dd-smoke-tests/vertx-3.4/build.gradle +++ b/dd-smoke-tests/vertx-3.4/build.gradle @@ -9,7 +9,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'Vert.x 3.4 Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'vertxBuild' nestedTasks = ['assemble'] artifactPath = 'libs/vertx-3.4-1.0.0-SNAPSHOT-fat.jar' diff --git a/dd-smoke-tests/vertx-3.9-resteasy/build.gradle b/dd-smoke-tests/vertx-3.9-resteasy/build.gradle index 4bb83945540..b99c1f8ac74 100644 --- a/dd-smoke-tests/vertx-3.9-resteasy/build.gradle +++ b/dd-smoke-tests/vertx-3.9-resteasy/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'Vert.x 3.9 RestEasy Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'vertxBuild' nestedTasks = ['assemble'] artifactPath = 'libs/vertx-3.9-resteasy-1.0.0-SNAPSHOT-fat.jar' diff --git a/dd-smoke-tests/vertx-3.9/build.gradle b/dd-smoke-tests/vertx-3.9/build.gradle index 59255a35080..0747b1f6c35 100644 --- a/dd-smoke-tests/vertx-3.9/build.gradle +++ b/dd-smoke-tests/vertx-3.9/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'Vert.x 3.9 Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'vertxBuild' nestedTasks = ['assemble'] artifactPath = 'libs/vertx-3.9-1.0.0-SNAPSHOT-fat.jar' diff --git a/dd-smoke-tests/vertx-4.2/build.gradle b/dd-smoke-tests/vertx-4.2/build.gradle index 54465508016..57cba56c827 100644 --- a/dd-smoke-tests/vertx-4.2/build.gradle +++ b/dd-smoke-tests/vertx-4.2/build.gradle @@ -8,7 +8,7 @@ apply from: "$rootDir/gradle/java.gradle" description = 'Vert.x 4.2 Smoke Tests.' smokeTestApp { - application { + gradleApp { taskName = 'vertxBuild' nestedTasks = ['assemble'] artifactPath = 'libs/vertx-4.2-1.0.0-SNAPSHOT-fat.jar'