Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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://}"
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ abstract class NestedGradleBuild @Inject constructor(
@get:Input
abstract val buildCacheEnabled: Property<Boolean>

/** Timeout, in seconds, for stopping the nested Gradle daemon after the build. */
@get:Input
@get:Optional
abstract val stopTimeoutSeconds: Property<Long>

/**
* 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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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=<path>` 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
Comment thread
bric3 marked this conversation as resolved.

@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val mavenExecutable: RegularFileProperty
Comment on lines +70 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Track the Maven wrapper properties as task inputs

Because this cacheable task only declares the wrapper script itself as an input, changes to .mvn/wrapper/maven-wrapper.properties are invisible even though the checked-in mvnw reads that file to choose the Maven distribution. When the pinned Maven version/checksum or wrapper distribution settings change, mvnStage can still be considered up-to-date or restored from cache and keep the artifact built with the previous Maven; please add the wrapper properties file (and any other wrapper files the script reads) as task inputs.

Useful? React with 👍 / 👎.


@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<JavaLauncher>

@get:Input
abstract val goals: ListProperty<String>

@get:Input
abstract val arguments: ListProperty<String>

@get:Input
abstract val environment: MapProperty<String, String>

@get:Input
@get:Optional
abstract val mavenOpts: Property<String>

@get:Input
@get:Optional
abstract val mavenRepositoryProxy: Property<String>

@get:Input
abstract val useMavenLocalRepository: Property<Boolean>

/** Timeout, in seconds, for the nested Maven build process. */
@get:Input
@get:Optional
abstract val buildTimeoutSeconds: Property<Long>

@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<String, String> {
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<String> =
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"
}

}
}
Original file line number Diff line number Diff line change
@@ -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")

Loading
Loading