diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 520cd6ab224..6a21e09634f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -62,6 +62,8 @@ variables: GRADLE_PLUGIN_PROXY: "https://depot-read-api-java.us1.ddbuild.io/magicmirror/magicmirror/@current/" BUILDER_IMAGE_REPO: "registry.ddbuild.io/images/mirror/dd-trace-java-docker-build" # images are pinned in images/mirror.lock.yaml in the ddoghq/images repo BUILDER_IMAGE_VERSION_PREFIX: "ci-" # use either an empty string (e.g. "") for latest images or a version followed by a hyphen (e.g. "ci-" or "123_merge-") + # shortcut: tag republishing is not detected; use a content hash when CI builds the image. + ORG_GRADLE_PROJECT_ciBuilderImage: "${BUILDER_IMAGE_REPO}:${BUILDER_IMAGE_VERSION_PREFIX}" TEST_COUNTS_S3_BUCKET: "dd-trace-java-ci-test-reports" REPO_NOTIFICATION_CHANNEL: "#apm-java-escalations" DEFAULT_TEST_JVMS: /^(8|11|17|21|25|tip)$/ # the latest "tip" version is 27 diff --git a/build-logic/conventions/README.md b/build-logic/conventions/README.md new file mode 100644 index 00000000000..0683a5b133e --- /dev/null +++ b/build-logic/conventions/README.md @@ -0,0 +1,24 @@ +# CI builder image inputs + +The `dd-trace-java.ci-builder-inputs` convention tracks the CI builder image identity +in Gradle build cache keys. It is enabled only in CI (`CI=true`). +It reads the `ciBuilderImage` Gradle property and adds +`ciBuilderIdentity` inputs to `Test`, `Exec` and `JavaExec` tasks in projects that apply it. + +> [!NOTE] +> The property is an opaque identity. It can hold the current builder repository +> and version prefix, such as `registry.example/builder:ci-`, or a future content +> hash. Moving a tag to different contents without changing the property does not +> invalidate cached results. + +For other tasks, declare the input explicitly. The provider returns an empty map locally: + +```Gradle Kotlin DSL +plugins { + id("dd-trace-java.ci-builder-inputs") +} + +tasks.named("generateNativeLibrary") { + inputs.property("ciBuilderIdentity", ciBuilderInputs.identity) +} +``` diff --git a/build-logic/conventions/src/main/kotlin/datadog/buildlogic/ci/CiBuilderInputsExtension.kt b/build-logic/conventions/src/main/kotlin/datadog/buildlogic/ci/CiBuilderInputsExtension.kt new file mode 100644 index 00000000000..24a47fe96e0 --- /dev/null +++ b/build-logic/conventions/src/main/kotlin/datadog/buildlogic/ci/CiBuilderInputsExtension.kt @@ -0,0 +1,40 @@ +package datadog.buildlogic.ci + +import org.gradle.api.GradleException +import org.gradle.api.Task +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import javax.inject.Inject + +/** Tracks the declared builder identity and platform used by selected CI tasks. */ +open class CiBuilderInputsExtension + @Inject + constructor( + providers: ProviderFactory, + ) { + // CI selection changes task configuration, so it is a configuration-cache input. + private val enabled = providers.environmentVariable("CI").orNull == "true" + + /** Builder identity and platform in CI; an empty map in local builds. */ + val identity: Provider> = + if (enabled) { + providers.gradleProperty("ciBuilderImage").orElse("").map { image -> + if (image.isBlank()) { + throw GradleException("ciBuilderImage must be a nonblank CI builder identity when CI=true") + } + mapOf( + "image" to image, + "operatingSystem" to System.getProperty("os.name"), + "architecture" to System.getProperty("os.arch"), + ) + } + } else { + providers.provider { emptyMap() } + } + + internal fun addTo(task: Task) { + if (enabled) { + task.inputs.property("ciBuilderIdentity", identity) + } + } + } diff --git a/build-logic/conventions/src/main/kotlin/dd-trace-java.ci-builder-inputs.gradle.kts b/build-logic/conventions/src/main/kotlin/dd-trace-java.ci-builder-inputs.gradle.kts new file mode 100644 index 00000000000..2a40e51b02c --- /dev/null +++ b/build-logic/conventions/src/main/kotlin/dd-trace-java.ci-builder-inputs.gradle.kts @@ -0,0 +1,20 @@ +import datadog.buildlogic.ci.CiBuilderInputsExtension +import org.gradle.api.tasks.Exec +import org.gradle.api.tasks.JavaExec +import org.gradle.api.tasks.testing.Test +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.withType + +val ciBuilderInputs = extensions.create("ciBuilderInputs") + +tasks.withType().configureEach { + ciBuilderInputs.addTo(this) +} + +tasks.withType().configureEach { + ciBuilderInputs.addTo(this) +} + +tasks.withType().configureEach { + ciBuilderInputs.addTo(this) +} diff --git a/dd-java-agent/agent-ci-visibility/build.gradle b/dd-java-agent/agent-ci-visibility/build.gradle index 375df7c0315..91d07ab4efa 100644 --- a/dd-java-agent/agent-ci-visibility/build.gradle +++ b/dd-java-agent/agent-ci-visibility/build.gradle @@ -3,6 +3,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion plugins { + // Git tests generate and read pack files using the image's Git executable. + id 'dd-trace-java.ci-builder-inputs' id 'com.gradleup.shadow' id 'org.jetbrains.kotlin.jvm' id 'dd-trace-java.version-file' diff --git a/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle b/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle index 1b31128050a..354dfc5d348 100644 --- a/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle +++ b/dd-java-agent/agent-profiling/profiling-controller-ddprof/build.gradle @@ -1,4 +1,6 @@ plugins { + // Tests load the native profiler selected for the image's libc variant. + id 'dd-trace-java.ci-builder-inputs' id 'dd-trace-java.module.internal-library' } diff --git a/dd-smoke-tests/quarkus-native/build.gradle b/dd-smoke-tests/quarkus-native/build.gradle index dc2c747fd46..f139cd30d1d 100644 --- a/dd-smoke-tests/quarkus-native/build.gradle +++ b/dd-smoke-tests/quarkus-native/build.gradle @@ -2,6 +2,8 @@ import java.util.concurrent.Callable import java.util.regex.Pattern plugins { + // Native smoke builds use GraalVM and native tools from the builder environment. + id 'dd-trace-java.ci-builder-inputs' id 'dd-trace-java.smoke-test-app' id 'dd-trace-java.module.smoke-test' } @@ -46,6 +48,7 @@ smokeTestApp { environment.put('GRAALVM_HOME', graalvmHome) inputs { property('graalvmRelease', fileContent(graalvmHome, 'release')) + property('ciBuilderIdentity', ciBuilderInputs.identity) } } projectJar('apiJar', project(':dd-trace-api')) 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 2424d22e6ad..7ad10fc9e99 100644 --- a/dd-smoke-tests/spring-boot-3.0-native/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-native/build.gradle @@ -2,6 +2,8 @@ import java.util.concurrent.Callable import java.util.regex.Pattern plugins { + // Native smoke builds use GraalVM and native tools from the builder environment. + id 'dd-trace-java.ci-builder-inputs' id 'dd-trace-java.smoke-test-app' id 'dd-trace-java.module.smoke-test' } @@ -44,6 +46,7 @@ smokeTestApp { environment.put('NATIVE_IMAGE_DEPRECATED_BUILDER_SANITATION', 'true') inputs { property('graalvmRelease', fileContent(graalvmHome, 'release')) + property('ciBuilderIdentity', ciBuilderInputs.identity) } } projectJar('apiJar', project(':dd-trace-api')) diff --git a/docs/how_to_work_with_gradle.md b/docs/how_to_work_with_gradle.md index aca5060e459..476061e3c45 100644 --- a/docs/how_to_work_with_gradle.md +++ b/docs/how_to_work_with_gradle.md @@ -386,6 +386,18 @@ tasks.register("processData") { > Prefer annotations in custom task classes for better type safety and documentation. > Use the programmatic API for ad-hoc tasks registered with `tasks.register`. +#### CI Builder Image Inputs + +Apply the [`dd-trace-java.ci-builder-inputs` convention](../build-logic/conventions/README.md) +when a module's cacheable tasks depend on the CI image's native toolchain, runtime libraries, +or tool-specific output, such as Git pack generation. Routine uses of `cat`, `echo`, `ls` +or `sleep` alone do not justify invalidating tasks on every image change. + +The convention adds the builder identity to `Test`, `Exec` and `JavaExec` inputs when +`CI=true`. Other task types, including native smoke producers, declare the identity +explicitly. See the convention reference for the CI property and input declarations. +Local builds require no configuration. + #### Lazy Configuration with Providers Gradle uses **lazy configuration** to defer value resolution until needed. Instead of setting values directly, you use `Property` and `Provider` types: