diff --git a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedBuildProjectJar.kt b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedBuildProjectJar.kt index 503f78c47b4..4fb6cb373ff 100644 --- a/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedBuildProjectJar.kt +++ b/build-logic/smoke-test/src/main/kotlin/datadog/buildlogic/smoketest/NestedBuildProjectJar.kt @@ -1,24 +1,53 @@ package datadog.buildlogic.smoketest +import org.gradle.api.Transformer +import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity +import java.util.jar.JarFile /** * A jar produced by the root build that needs to be forwarded into a [NestedGradleBuild]. * * At execution time the task adds `-P${propertyName}=` to the nested * Gradle invocation, so the inner build script can pick it up via `findProperty(...)`. + * + * Cached JVM and native fixtures can retain earlier embedded commit stamps. */ abstract class NestedBuildProjectJar { @get:Input abstract val propertyName: Property - @get:InputFile - @get:PathSensitive(PathSensitivity.NONE) + /** + * Runtime classpath hashing tracks classes and resources: .classdata, manifests, instrumentation + * indexes, configuration files and native libraries. Changes to normalised contents invalidate + * application builds. + */ + @get:Classpath abstract val file: RegularFileProperty + + /** + * `project.version` is the base version (e.g. `1.69.0-SNAPSHOT`); `WriteVersionFile` appends `~`. + * Runtime-classpath normalisation excludes the complete `*.version` resources. + * Tracking the base version separately invalidates application builds on version changes, + * while changing only the commit stamp allows reuse when the other inputs match. + */ + @get:Input + abstract val version: Property + + /** Gradle CI Visibility loads `dd-java-agent.version`, so its presence affects smoke results. */ + @get:Input + val neededResourcePaths: Provider> + get() = file.map(existingResourcePaths("dd-java-agent.version")) + + private fun existingResourcePaths(vararg paths: String): Transformer, RegularFile> = + Transformer { file -> + JarFile(file.asFile).use { jar -> + paths.filter { jar.getJarEntry(it)?.isDirectory == false }.sorted() + } + } } 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 49fc7bcbb01..b65928d6597 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 @@ -163,17 +163,18 @@ abstract class NestedGradleBuild @Inject constructor( @get:OutputDirectory abstract val applicationBuildDir: DirectoryProperty - /** Forward a root-build jar as `-P=` into the nested build. */ - fun projectJar(name: String, file: Provider) { + /** Forward a JAR with runtime-classpath normalisation and an explicit base [version]. */ + fun projectJar(name: String, file: Provider, version: Provider) { projectJars.add( objects.newInstance().apply { propertyName.set(name) this.file.set(file) + this.version.set(version) }, ) } - /** Configure additional aspects of the nested build via a typed action. */ + /** Configure a normalised JAR input, including its base version, via a typed action. */ fun projectJar(action: Action) { projectJars.add( objects.newInstance().also(action::execute), 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 da6a7c1f0a3..589baca8a0c 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 @@ -213,31 +213,31 @@ abstract class SmokeTestAppExtension @Inject constructor( } /** - * Forward the default `jar` artifact from [sourceProject] into the nested build as - * `-P=`. The jar is consumed via a resolvable [Configuration], - * which both establishes the correct task dependency and lets Gradle resolve the artifact - * lazily — no `evaluationDependsOn` is needed. + * Forward a JAR from [sourceProject] as `-P=`. + * [configuration] selects an outgoing artifact configuration, such as `shadow`; + * the default artifact is used when it is omitted. Resolution is lazy and carries + * the producing task dependency. + * + * Fingerprints use this project's runtime-classpath normalisation and track the + * producing project's version separately. Classes, `.classdata` and other tracked + * resources remain inputs; ZIP metadata and configured resource exclusions are ignored. + * Cached JVM applications and native executables can retain earlier embedded agent + * and API commit stamps when only excluded version metadata changes. */ - fun projectJar(propertyName: String, sourceProject: Project) { - val cfg = createExtraJarConfiguration(propertyName) - project.dependencies.add(cfg.name, sourceProject) - addProjectJarFromConfiguration(propertyName, cfg) - } - - /** - * Forward a non-default artifact configuration from [sourceProject]. Use this when the - * upstream project exposes its build output under a configuration other than the default - * (e.g. `shadowJar`). - */ - fun projectJar(propertyName: String, sourceProject: Project, configuration: String) { + @JvmOverloads + fun projectJar(propertyName: String, sourceProject: Project, configuration: String? = null) { val cfg = createExtraJarConfiguration(propertyName) project.dependencies.add( cfg.name, - project.dependencies.project( + if (configuration == null) sourceProject else project.dependencies.project( mapOf("path" to sourceProject.path, "configuration" to configuration), ), ) - addProjectJarFromConfiguration(propertyName, cfg) + projectJar( + propertyName, + project.layout.file(cfg.elements.map { files -> files.single().asFile }), + sourceProject.provider { sourceProject.version.toString() }, + ) } private fun createExtraJarConfiguration(propertyName: String): Configuration { @@ -253,29 +253,15 @@ abstract class SmokeTestAppExtension @Inject constructor( /** * Lower-level overload for the rare case where the caller already has a provider of the - * file. The caller is responsible for the upstream task dependency. + * file and its base [version]. Uses the same normalisation as the project overload; + * the caller is responsible for the upstream task dependency. */ - fun projectJar(propertyName: String, file: Provider) { + fun projectJar(propertyName: String, file: Provider, version: Provider) { projectJars.add( project.objects.newInstance().apply { this.propertyName.set(propertyName) this.file.set(file) - }, - ) - } - - private fun addProjectJarFromConfiguration(propertyName: String, cfg: Configuration) { - projectJars.add( - project.objects.newInstance().apply { - this.propertyName.set(propertyName) - // Configuration.elements yields a Provider that carries the producing task dependency, - // so wiring it into the task's @InputFile both tracks file contents and arranges build - // order. - this.file.set( - cfg.elements.map { files -> - project.objects.fileProperty().fileValue(files.single().asFile).get() - }, - ) + this.version.set(version) }, ) } 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 01ae03fe534..01b0ed89f74 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 @@ -14,6 +14,9 @@ import org.tabletest.junit.TableTest import java.io.File import java.nio.file.Files import java.nio.file.Path +import java.util.jar.JarEntry +import java.util.jar.JarFile +import java.util.jar.JarOutputStream /** * End-to-end tests that drive the plugin through the Gradle Test Kit and a temporary, @@ -66,6 +69,128 @@ class SmokeTestAppEndToEndTest { assertThat(applicationOutput("libs/sample.jar")).exists() } + @TableTest( + "scenario | configuration | change | expected ", + "commit stamp ignored | default | stamp | FROM_CACHE ", + "ZIP timestamps ignored | default | zip | FROM_CACHE ", + "ZIP entry order ignored | default | order | FROM_CACHE ", + "project version tracked | default | version | SUCCESS ", + "class contents tracked | default | class | SUCCESS ", + "classdata tracked | default | classdata | SUCCESS ", + "other resources tracked | default | resource | SUCCESS ", + "agent version added | default | added | SUCCESS ", + "agent version removed | default | removed | SUCCESS ", + "other version paths ignored | default | paths | FROM_CACHE ", + "shadow commit stamp ignored | shadow | stamp | FROM_CACHE ", + "shadow ZIP timestamps ignored | shadow | zip | FROM_CACHE ", + "shadow ZIP entry order ignored | shadow | order | FROM_CACHE ", + "shadow project version tracked | shadow | version | SUCCESS ", + "shadow class contents tracked | shadow | class | SUCCESS ", + "shadow classdata tracked | shadow | classdata | SUCCESS ", + "shadow other resources tracked | shadow | resource | SUCCESS ", + "shadow agent version added | shadow | added | SUCCESS ", + "shadow agent version removed | shadow | removed | SUCCESS ", + "shadow other versions ignored | shadow | paths | FROM_CACHE ", + ) + fun `project JAR fingerprints ignore metadata and track source changes`( + configuration: String, + change: String, + expected: TaskOutcome, + ) { + writeOuterSettings(withLocalBuildCache = true) + outerSettings.appendText("\ninclude(\":api\")\n") + val apiDirectory = projectDir.resolve("api").toFile().apply { mkdirs() } + File(apiDirectory, "build.gradle.kts").writeText( + """ + plugins { base } + version = providers.gradleProperty("apiVersion").getOrElse("1.0") + val prepareJar = tasks.register("prepareJar") { + from("input.jar") + into(layout.buildDirectory.dir("libs")) + } + val outgoing = configurations.${if (configuration == "default") "named(\"default\")" else "create(\"shadow\")"} { + isCanBeConsumed = true + isCanBeResolved = false + } + artifacts.add(outgoing.name, layout.buildDirectory.file("libs/input.jar")) { + builtBy(prepareJar) + } + """.trimIndent(), + ) + writeSmokeTestAppBuild( + """ + ${smokeTestGradleApplication("packageApp", "embedded-api.jar", "api.path")} + projectJar("apiJar", project(":api")${if (configuration == "default") "" else ", \"shadow\""}) + """, + extraImports = "import datadog.buildlogic.smoketest.NestedGradleBuild", + extraPreamble = """ + normalization.runtimeClasspath.ignore("**/*.version") + tasks.withType().configureEach { + actions.clear() + val inputJar = projectJars.flatMap { it.single().file } + val target = applicationBuildDir.file("embedded-api.jar") + doLast { + target.get().asFile.apply { + parentFile.mkdirs() + inputJar.get().asFile.copyTo(this, overwrite = true) + } + } + } + """, + ) + + fun writeApiJar(changed: Boolean) { + val entries = mutableMapOf( + "api.version" to if (changed && change == "stamp") "1.0~next" else "1.0~initial", + "example/Api.class" to if (changed && change == "class") "changed class" else "class", + "example/Agent.classdata" to if (changed && change == "classdata") "changed classdata" else "classdata", + "config.txt" to if (changed && change == "resource") "changed resource" else "resource", + ) + if (changed && change == "paths") { + entries["optional.version"] = "1.0~initial" + } + if ((changed && change == "added") || (!changed && change == "removed")) { + entries["dd-java-agent.version"] = "1.0~initial" + } + JarOutputStream(File(apiDirectory, "input.jar").outputStream()).use { jar -> + val orderedEntries = if (changed && change == "order") entries.toList().reversed() else entries.toList() + orderedEntries.forEach { (name, contents) -> + jar.putNextEntry(JarEntry(name).apply { + time = if (changed && change == "zip") 1700000000000L else 1000000000000L + }) + jar.write(contents.toByteArray(Charsets.UTF_8)) + jar.closeEntry() + } + } + } + + writeApiJar(changed = false) + // Re-snapshot the JAR rewritten outside Gradle between runs. + val first = runner("packageApp", "--build-cache", "--configuration-cache", "--no-watch-fs", "-PapiVersion=1.0").build() + assertThat(first.task(":api:prepareJar")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + assertThat(first.task(":packageApp")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + + applicationBuildDir.deleteRecursively() + writeApiJar(changed = true) + val second = runner( + "packageApp", "--build-cache", "--configuration-cache", "--no-watch-fs", + "-PapiVersion=${if (change == "version") "2.0" else "1.0"}", + ).build() + if (change != "version") { + assertThat(second.task(":api:prepareJar")?.outcome).isEqualTo(TaskOutcome.SUCCESS) + } + assertThat(second.task(":packageApp")?.outcome).isEqualTo(expected) + if (change != "version") { + assertThat(second.output).contains("Reusing configuration cache.") + } + JarFile(applicationOutput("embedded-api.jar")).use { jar -> + val stamp = jar.getInputStream(jar.getJarEntry("api.version")).bufferedReader().use { it.readText() } + assertThat(stamp).isEqualTo("1.0~initial") + assertThat(jar.getJarEntry("dd-java-agent.version") != null).isEqualTo(change == "added") + assertThat(jar.getJarEntry("optional.version")).isNull() + } + } + @Test fun `nested Maven build produces the configured artifact`() { writeOuterSettings() @@ -373,7 +498,7 @@ class SmokeTestAppEndToEndTest { fun `nested build receives native app environment and provider backed file inputs`() { writeOuterSettings() val graalvmHome = projectDir.resolve("test-graalvm").toFile() - File(projectDir.toFile(), "agent.jar").writeText("agent") + JarOutputStream(File(projectDir.toFile(), "agent.jar").outputStream()).use { } writeSmokeTestAppBuild( """ ${smokeTestGradleApplication( @@ -385,7 +510,11 @@ class SmokeTestAppEndToEndTest { environment.put("GRAALVM_HOME", providers.provider { layout.projectDirectory.dir("test-graalvm").asFile.absolutePath }) """, )} - projectJar("agentPath", providers.provider { layout.projectDirectory.file("agent.jar") }) + projectJar( + "agentPath", + providers.provider { layout.projectDirectory.file("agent.jar") }, + providers.provider { "1.0" }, + ) """, ) writeInnerSettings() diff --git a/dd-java-agent/agent-jmxfetch/build.gradle b/dd-java-agent/agent-jmxfetch/build.gradle index bb1249350b0..3e9e6f8bbcf 100644 --- a/dd-java-agent/agent-jmxfetch/build.gradle +++ b/dd-java-agent/agent-jmxfetch/build.gradle @@ -67,6 +67,8 @@ abstract class CopyMetricConfigsTask extends DefaultTask { def configDirectoryPath = createConfigDirectory() def configPaths = Files.find(this.inputDirectory.toPath(), 10, this::checkMetricsFile) .map(p -> copyMetricsFile(configDirectoryPath, p)) + // Keep the packaged list independent of filesystem traversal order. + .sorted() .collect(Collectors.toList()) saveConfig(configDirectoryPath.parent, configPaths) } diff --git a/dd-smoke-tests/apm-tracing-disabled/application/build.gradle b/dd-smoke-tests/apm-tracing-disabled/application/build.gradle index cac44f5c2dd..c5c53aa544d 100644 --- a/dd-smoke-tests/apm-tracing-disabled/application/build.gradle +++ b/dd-smoke-tests/apm-tracing-disabled/application/build.gradle @@ -23,16 +23,6 @@ java { sourceCompatibility = '1.8' } -if (hasProperty('apiJar')) { - dependencies { - implementation files(property('apiJar')) - } -} else { - dependencies { - implementation "com.datadoghq:dd-trace-api:+" - } -} - dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' // OpenTracing 0.32.0 is the last release and is intentionally pinned: this smoke test diff --git a/dd-smoke-tests/apm-tracing-disabled/build.gradle b/dd-smoke-tests/apm-tracing-disabled/build.gradle index 70cfa763036..12e76629b11 100644 --- a/dd-smoke-tests/apm-tracing-disabled/build.gradle +++ b/dd-smoke-tests/apm-tracing-disabled/build.gradle @@ -12,7 +12,6 @@ smokeTestApp { artifactPath = 'libs/apm-tracing-disabled-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/apm-tracing-disabled/gradle.lockfile b/dd-smoke-tests/apm-tracing-disabled/gradle.lockfile index c2deddd2dcf..7ef29f459b4 100644 --- a/dd-smoke-tests/apm-tracing-disabled/gradle.lockfile +++ b/dd-smoke-tests/apm-tracing-disabled/gradle.lockfile @@ -128,4 +128,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath diff --git a/dd-smoke-tests/armeria-grpc/application/build.gradle b/dd-smoke-tests/armeria-grpc/application/build.gradle index 7800b4519a1..a16a2601ef9 100644 --- a/dd-smoke-tests/armeria-grpc/application/build.gradle +++ b/dd-smoke-tests/armeria-grpc/application/build.gradle @@ -61,7 +61,7 @@ dependencies { runtimeOnly 'ch.qos.logback:logback-classic:1.4.7' runtimeOnly 'org.slf4j:log4j-over-slf4j:1.7.36' - if (hasProperty('apiJar')) { + if (project.hasProperty('apiJar')) { implementation files(property('apiJar')) } else { implementation "com.datadoghq:dd-trace-api:+" diff --git a/dd-smoke-tests/build.gradle b/dd-smoke-tests/build.gradle index fed2b5b5d1e..19dc8b32224 100644 --- a/dd-smoke-tests/build.gradle +++ b/dd-smoke-tests/build.gradle @@ -1,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import java.util.jar.JarFile plugins { id 'dd-trace-java.module.smoke-test' @@ -38,6 +39,7 @@ allprojects { Project subProj -> def diagnosticsTaskRef = project(':dd-java-agent:instrumentation-testing').tasks.named('scopeDiagnosticsAgentJar', Jar) def agentArguments = subProj.objects.newInstance(SmokeAgentArguments) agentArguments.tracerJar.set(agentTaskRef.flatMap { it.archiveFile }) + agentArguments.tracerVersion.set(subProj.version.toString()) agentArguments.diagnosticsJar.set(diagnosticsTaskRef.flatMap { it.archiveFile }) jvmArgumentProviders.add(agentArguments) @@ -50,10 +52,24 @@ allprojects { Project subProj -> } abstract class SmokeAgentArguments implements CommandLineArgumentProvider { - @InputFile - @PathSensitive(PathSensitivity.NONE) + // Runtime classpath hashing tracks classes and resources: .classdata, manifests, instrumentation + // indexes, configuration files and native libraries. Changes to normalised contents invalidate tests. + @Classpath abstract RegularFileProperty getTracerJar() + // project.version is the base version (e.g. 1.69.0-SNAPSHOT); WriteVersionFile appends ~. + // Runtime-classpath normalisation excludes the complete *.version resources. + // Tracking the base version separately invalidates results on version changes, + // while changing only the commit stamp allows reuse when the other inputs match. + @Input + abstract Property getTracerVersion() + + // Gradle CI Visibility loads dd-java-agent.version, so its presence affects smoke results. + @Input + Provider> getNeededResourcePaths() { + tracerJar.map(existingResourcePaths('dd-java-agent.version')) + } + @InputFile @PathSensitive(PathSensitivity.NONE) abstract RegularFileProperty getDiagnosticsJar() @@ -65,4 +81,12 @@ abstract class SmokeAgentArguments implements CommandLineArgumentProvider { '-Ddatadog.smoketest.scopeDiagnostics.agent.path=' + diagnosticsJar.get().asFile.absolutePath ] } + + private static Transformer, RegularFile> existingResourcePaths(String... paths) { + return { RegularFile file -> + new JarFile(file.asFile).withCloseable { jar -> + paths.findAll { jar.getJarEntry(it) != null }.sort() + } + } as Transformer, RegularFile> + } } diff --git a/dd-smoke-tests/kafka-2/application/build.gradle b/dd-smoke-tests/kafka-2/application/build.gradle index 31411ab9e3e..6c0627d7632 100644 --- a/dd-smoke-tests/kafka-2/application/build.gradle +++ b/dd-smoke-tests/kafka-2/application/build.gradle @@ -25,16 +25,6 @@ java { sourceCompatibility = JavaVersion.VERSION_1_8 } -// `iastUtilJar` is wired up by the outer `smokeTestApp { projectJar('iastUtilJar', ...) }` -// block in ../build.gradle: it passes the path of the built `:dd-smoke-tests:iast-util` -// jar into this nested build as a Gradle property. We add it as a flat-file dependency -// because the outer build's projects are not addressable from inside the nested build. -if (hasProperty('iastUtilJar')) { - dependencies { - implementation files(property('iastUtilJar')) - } -} - dependencies { implementation('org.springframework.boot:spring-boot-starter-web') implementation('org.springframework.boot:spring-boot-starter-actuator') diff --git a/dd-smoke-tests/kafka-2/build.gradle b/dd-smoke-tests/kafka-2/build.gradle index 3da5e63bd78..0685f5f3999 100644 --- a/dd-smoke-tests/kafka-2/build.gradle +++ b/dd-smoke-tests/kafka-2/build.gradle @@ -12,7 +12,6 @@ smokeTestApp { artifactPath = 'libs/kafka-2-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.shadowJar.path' } - projectJar('iastUtilJar', project(':dd-smoke-tests:iast-util')) } dependencies { diff --git a/dd-smoke-tests/kafka-2/gradle.lockfile b/dd-smoke-tests/kafka-2/gradle.lockfile index ed7f4754dde..65da39a89f6 100644 --- a/dd-smoke-tests/kafka-2/gradle.lockfile +++ b/dd-smoke-tests/kafka-2/gradle.lockfile @@ -179,4 +179,4 @@ org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xerial.snappy:snappy-java:1.1.8.1=testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarIastUtilJar,spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath diff --git a/dd-smoke-tests/quarkus-native/application/build.gradle b/dd-smoke-tests/quarkus-native/application/build.gradle index 65beb08e810..18bff4374d2 100644 --- a/dd-smoke-tests/quarkus-native/application/build.gradle +++ b/dd-smoke-tests/quarkus-native/application/build.gradle @@ -22,7 +22,7 @@ def ciBuildCpuCount = providers.environmentVariable("KUBERNETES_CPU_REQUEST").ge dependencies { implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") implementation 'io.quarkus:quarkus-resteasy' - if (hasProperty('apiJar')) { + if (project.hasProperty('apiJar')) { implementation files(property('apiJar')) } else { implementation "com.datadoghq:dd-trace-api:1.38.1" @@ -44,7 +44,12 @@ if (hasProperty('agentJar')) { "-J-javaagent:${agentJar}", "-J-Ddatadog.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd'T'HH:mm:ss.SSS'Z [dd.trace]'", "-J-Ddd.profiling.enabled=true", - "-march=native", + // The S3 prefix separates OS/architecture, but not CPU features. Use a common baseline + // so cached executables can run on other CI hosts. + // x86-64-v3 (AMD64) and armv8.1-a (AArch64) are candidates once all build and execution + // hosts of each architecture are guaranteed to support the corresponding CPU features. + // https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/#optimizing-for-specific-machines + "-march=compatibility", ] if (isCI) { nativeBuildArgs.add("-H:NumberOfThreads=$ciBuildCpuCount") diff --git a/dd-smoke-tests/quarkus/application/build.gradle b/dd-smoke-tests/quarkus/application/build.gradle index 7fa5badaf5e..bea67fa528f 100644 --- a/dd-smoke-tests/quarkus/application/build.gradle +++ b/dd-smoke-tests/quarkus/application/build.gradle @@ -23,7 +23,7 @@ java { dependencies { implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") implementation 'io.quarkus:quarkus-resteasy' - if (hasProperty('apiJar')) { + if (project.hasProperty('apiJar')) { implementation files(property('apiJar')) } else { implementation "com.datadoghq:dd-trace-api:0.68.0" diff --git a/dd-smoke-tests/rum/wildfly-15/build.gradle b/dd-smoke-tests/rum/wildfly-15/build.gradle index 4dd772bd308..d32ce0634c0 100644 --- a/dd-smoke-tests/rum/wildfly-15/build.gradle +++ b/dd-smoke-tests/rum/wildfly-15/build.gradle @@ -56,16 +56,12 @@ dependencies { } def appBuildDir = layout.buildDirectory.dir("rm-ear") -evaluationDependsOn(':dd-trace-api') -def apiJarTask = project(':dd-trace-api').tasks.named('jar') // Run the nested EAR build through the Gradle Tooling API. tasks.register('earBuild', NestedGradleBuild) { applicationDir = layout.projectDirectory.dir("rum-ear") applicationBuildDir = appBuildDir tasksToRun = ['assemble'] - projectJar('apiJar', apiJarTask.flatMap { it.archiveFile }) - dependsOn apiJarTask } tasks.named("compileTestGroovy", GroovyCompile) { diff --git a/dd-smoke-tests/spring-boot-2.7-webflux/application/build.gradle b/dd-smoke-tests/spring-boot-2.7-webflux/application/build.gradle index 58a25bc14e9..1e2f3e955da 100644 --- a/dd-smoke-tests/spring-boot-2.7-webflux/application/build.gradle +++ b/dd-smoke-tests/spring-boot-2.7-webflux/application/build.gradle @@ -26,10 +26,4 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation group: 'com.h2database', name: 'h2', version: '2.1.214' compileOnly group:"com.google.code.findbugs", name:"jsr305", version:"3.0.2" - - if (hasProperty('apiJar')) { - implementation files(property('apiJar')) - } else { - implementation "com.datadoghq:dd-trace-api:0.110.0" - } } 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 f45d3a0089a..381c9ecd6c7 100644 --- a/dd-smoke-tests/spring-boot-2.7-webflux/build.gradle +++ b/dd-smoke-tests/spring-boot-2.7-webflux/build.gradle @@ -12,7 +12,6 @@ smokeTestApp { artifactPath = 'libs/webflux-2.7-smoketest.jar' sysProperty = 'datadog.smoketest.webflux.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/spring-boot-2.7-webflux/gradle.lockfile b/dd-smoke-tests/spring-boot-2.7-webflux/gradle.lockfile index b779974320e..1ebd715d8d5 100644 --- a/dd-smoke-tests/spring-boot-2.7-webflux/gradle.lockfile +++ b/dd-smoke-tests/spring-boot-2.7-webflux/gradle.lockfile @@ -122,4 +122,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle b/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle index 08545cf1e16..36ed50638f6 100644 --- a/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-native/application/build.gradle @@ -30,7 +30,7 @@ tasks.withType(JavaCompile).configureEach { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - if (hasProperty('apiJar')) { + if (project.hasProperty('apiJar')) { implementation files(property('apiJar')) } else { implementation "com.datadoghq:dd-trace-api:1.2.0" @@ -44,6 +44,12 @@ if (hasProperty('agentPath')) { main { // quick build mode, enough for smoke test buildArgs.add("-Ob") + // The S3 prefix separates OS/architecture, but not CPU features. Use a common baseline + // so cached executables can run on other CI hosts. + // x86-64-v3 (AMD64) and armv8.1-a (AArch64) are candidates once all build and execution + // hosts of each architecture are guaranteed to support the corresponding CPU features. + // https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/#optimizing-for-specific-machines + buildArgs.add("-march=compatibility") buildArgs.add("-J-javaagent:$agentPath") buildArgs.add("-J-Dnet.bytebuddy.safe=false") if (withProfiler && property('profiler') == 'true') { diff --git a/dd-smoke-tests/spring-boot-3.0-webflux/application/build.gradle b/dd-smoke-tests/spring-boot-3.0-webflux/application/build.gradle index 76dcf9c3da2..810a2688d83 100644 --- a/dd-smoke-tests/spring-boot-3.0-webflux/application/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-webflux/application/build.gradle @@ -27,10 +27,4 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation group: 'com.h2database', name: 'h2', version: '2.1.214' compileOnly group:"com.google.code.findbugs", name:"jsr305", version:"3.0.2" - - if (hasProperty('apiJar')) { - implementation files(property('apiJar')) - } else { - implementation "com.datadoghq:dd-trace-api:1.2.0" - } } 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 a6b5fe85c1b..16baa866183 100644 --- a/dd-smoke-tests/spring-boot-3.0-webflux/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-webflux/build.gradle @@ -16,7 +16,6 @@ smokeTestApp { artifactPath = 'libs/webflux-3.0-smoketest.jar' sysProperty = 'datadog.smoketest.webflux.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/spring-boot-3.0-webflux/gradle.lockfile b/dd-smoke-tests/spring-boot-3.0-webflux/gradle.lockfile index 0a383ac1f2b..ffa7cdbd752 100644 --- a/dd-smoke-tests/spring-boot-3.0-webflux/gradle.lockfile +++ b/dd-smoke-tests/spring-boot-3.0-webflux/gradle.lockfile @@ -122,4 +122,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/spring-boot-3.0-webmvc/application/build.gradle b/dd-smoke-tests/spring-boot-3.0-webmvc/application/build.gradle index c94214d70ba..487b808088a 100644 --- a/dd-smoke-tests/spring-boot-3.0-webmvc/application/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-webmvc/application/build.gradle @@ -27,11 +27,4 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation group: 'com.h2database', name: 'h2', version: '2.1.214' compileOnly group:"com.google.code.findbugs", name:"jsr305", version:"3.0.2" - - - if (hasProperty('apiJar')) { - implementation files(property('apiJar')) - } else { - implementation "com.datadoghq:dd-trace-api:1.2.0" - } } 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 ca46c109f87..18bd3001e7c 100644 --- a/dd-smoke-tests/spring-boot-3.0-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-3.0-webmvc/build.gradle @@ -16,7 +16,6 @@ smokeTestApp { artifactPath = 'libs/webmvc-3.0-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/spring-boot-3.0-webmvc/gradle.lockfile b/dd-smoke-tests/spring-boot-3.0-webmvc/gradle.lockfile index 6069aa936b8..ea9ef3244cb 100644 --- a/dd-smoke-tests/spring-boot-3.0-webmvc/gradle.lockfile +++ b/dd-smoke-tests/spring-boot-3.0-webmvc/gradle.lockfile @@ -122,4 +122,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/spring-boot-3.3-webmvc/application/build.gradle b/dd-smoke-tests/spring-boot-3.3-webmvc/application/build.gradle index 0ab5e0481a4..57ed9252722 100644 --- a/dd-smoke-tests/spring-boot-3.3-webmvc/application/build.gradle +++ b/dd-smoke-tests/spring-boot-3.3-webmvc/application/build.gradle @@ -28,11 +28,4 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation group: 'com.h2database', name: 'h2', version: '2.1.214' compileOnly group:"com.google.code.findbugs", name:"jsr305", version:"3.0.2" - - - if (hasProperty('apiJar')) { - implementation files(property('apiJar')) - } else { - implementation "com.datadoghq:dd-trace-api:1.2.0" - } } 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 278a7c6e739..ec7c362c9a1 100644 --- a/dd-smoke-tests/spring-boot-3.3-webmvc/build.gradle +++ b/dd-smoke-tests/spring-boot-3.3-webmvc/build.gradle @@ -16,7 +16,6 @@ smokeTestApp { artifactPath = 'libs/webmvc-3.3-smoketest.jar' sysProperty = 'datadog.smoketest.springboot.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/spring-boot-3.3-webmvc/gradle.lockfile b/dd-smoke-tests/spring-boot-3.3-webmvc/gradle.lockfile index 68ff85edbf7..6d03d465147 100644 --- a/dd-smoke-tests/spring-boot-3.3-webmvc/gradle.lockfile +++ b/dd-smoke-tests/spring-boot-3.3-webmvc/gradle.lockfile @@ -122,4 +122,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/vertx-3.4/build.gradle b/dd-smoke-tests/vertx-3.4/build.gradle index a2f900c3c61..cb49858b575 100644 --- a/dd-smoke-tests/vertx-3.4/build.gradle +++ b/dd-smoke-tests/vertx-3.4/build.gradle @@ -14,7 +14,6 @@ smokeTestApp { artifactPath = 'libs/vertx-3.4-1.0.0-SNAPSHOT-fat.jar' sysProperty = 'datadog.smoketest.vertx.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/vertx-3.4/gradle.lockfile b/dd-smoke-tests/vertx-3.4/gradle.lockfile index ca94970f6c7..e8a60c366e6 100644 --- a/dd-smoke-tests/vertx-3.4/gradle.lockfile +++ b/dd-smoke-tests/vertx-3.4/gradle.lockfile @@ -127,4 +127,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath diff --git a/dd-smoke-tests/vertx-3.9-resteasy/build.gradle b/dd-smoke-tests/vertx-3.9-resteasy/build.gradle index cec001ad620..bc6b022dd6d 100644 --- a/dd-smoke-tests/vertx-3.9-resteasy/build.gradle +++ b/dd-smoke-tests/vertx-3.9-resteasy/build.gradle @@ -13,7 +13,6 @@ smokeTestApp { artifactPath = 'libs/vertx-3.9-resteasy-1.0.0-SNAPSHOT-fat.jar' sysProperty = 'datadog.smoketest.vertx.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/vertx-3.9-resteasy/gradle.lockfile b/dd-smoke-tests/vertx-3.9-resteasy/gradle.lockfile index fc01499b6f7..df2ed0dc12b 100644 --- a/dd-smoke-tests/vertx-3.9-resteasy/gradle.lockfile +++ b/dd-smoke-tests/vertx-3.9-resteasy/gradle.lockfile @@ -122,4 +122,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/vertx-3.9/build.gradle b/dd-smoke-tests/vertx-3.9/build.gradle index 1ec45904cea..8d2bf6e471e 100644 --- a/dd-smoke-tests/vertx-3.9/build.gradle +++ b/dd-smoke-tests/vertx-3.9/build.gradle @@ -13,7 +13,6 @@ smokeTestApp { artifactPath = 'libs/vertx-3.9-1.0.0-SNAPSHOT-fat.jar' sysProperty = 'datadog.smoketest.vertx.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/vertx-3.9/gradle.lockfile b/dd-smoke-tests/vertx-3.9/gradle.lockfile index ccf878cc619..37936affd68 100644 --- a/dd-smoke-tests/vertx-3.9/gradle.lockfile +++ b/dd-smoke-tests/vertx-3.9/gradle.lockfile @@ -128,4 +128,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/vertx-4.2/build.gradle b/dd-smoke-tests/vertx-4.2/build.gradle index b36ab24d335..8879a970a5e 100644 --- a/dd-smoke-tests/vertx-4.2/build.gradle +++ b/dd-smoke-tests/vertx-4.2/build.gradle @@ -13,7 +13,6 @@ smokeTestApp { artifactPath = 'libs/vertx-4.2-1.0.0-SNAPSHOT-fat.jar' sysProperty = 'datadog.smoketest.vertx.uberJar.path' } - projectJar('apiJar', project(':dd-trace-api')) } dependencies { diff --git a/dd-smoke-tests/vertx-4.2/gradle.lockfile b/dd-smoke-tests/vertx-4.2/gradle.lockfile index f7f03c6b64d..b5eff801f62 100644 --- a/dd-smoke-tests/vertx-4.2/gradle.lockfile +++ b/dd-smoke-tests/vertx-4.2/gradle.lockfile @@ -127,4 +127,4 @@ org.tabletest:tabletest-junit:1.2.2=testCompileClasspath,testRuntimeClasspath org.tabletest:tabletest-parser:1.2.1=testCompileClasspath,testRuntimeClasspath org.testcontainers:testcontainers:1.21.4=testCompileClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=spotbugs -empty=annotationProcessor,runtimeClasspath,smokeTestAppExtraJarApiJar,spotbugsPlugins,testAnnotationProcessor +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/wildfly/build.gradle b/dd-smoke-tests/wildfly/build.gradle index ef5b9c9c317..210e7a10cf1 100644 --- a/dd-smoke-tests/wildfly/build.gradle +++ b/dd-smoke-tests/wildfly/build.gradle @@ -56,16 +56,12 @@ dependencies { } def appBuildDir = layout.buildDirectory.dir("spring-ear") -evaluationDependsOn(':dd-trace-api') -def apiJarTask = project(':dd-trace-api').tasks.named('jar') // Run the nested EAR build through the Gradle Tooling API. tasks.register('earBuild', NestedGradleBuild) { applicationDir = layout.projectDirectory.dir("spring-ear") applicationBuildDir = appBuildDir tasksToRun = ['assemble'] - projectJar('apiJar', apiJarTask.flatMap { it.archiveFile }) - dependsOn apiJarTask } tasks.named("compileTestGroovy", GroovyCompile) { diff --git a/dd-smoke-tests/wildfly/spring-ear/build.gradle b/dd-smoke-tests/wildfly/spring-ear/build.gradle index df6b5a82cb0..e021ec72721 100644 --- a/dd-smoke-tests/wildfly/spring-ear/build.gradle +++ b/dd-smoke-tests/wildfly/spring-ear/build.gradle @@ -25,10 +25,4 @@ java { dependencies { earlib 'org.springframework:spring-webmvc:5.3.0' deploy project(path: ':war', configuration: 'archives') - - if (hasProperty('apiJar')) { - implementation files(property('apiJar')) - } else { - implementation "com.datadoghq:dd-trace-api:1.2.0" - } } diff --git a/docs/gradle_build_cache.md b/docs/gradle_build_cache.md index 23682a2ffa6..f819196e938 100644 --- a/docs/gradle_build_cache.md +++ b/docs/gradle_build_cache.md @@ -70,31 +70,168 @@ If no summary appears, try `--info` and check cache and statistics settings. | Native smoke-test failures | Use `SKIP_BUILD_CACHE=true` to rebuild the executable in the failing job. Check that task inputs track changes to the toolchain and builder image. | | Hits without faster CI | Transfers, configuration time, tasks that still execute, and the pipeline's critical path. | -Compare task inputs in [Build Scans](how_to_work_with_gradle.md#build-scans-develocity). -Muzzle jobs disable scans; use logs instead. For detailed fingerprints: - -```shell -./gradlew :dd-trace-api:clean :dd-trace-api:test \ - --build-cache --info -Dorg.gradle.caching.debug=true -``` - -Replace the module/task as needed. Run twice and compare keys and input hashes. -Cleaning outputs allows restoration; omit `--rerun-tasks` when checking hits. -Local runs diagnose inputs, but do not verify CI's S3 access. +Compare task inputs in [Build Scans](how_to_work_with_gradle.md#build-scans-develocity) +or [compare their fingerprints in logs](#compare-fingerprints-in-logs). +Muzzle jobs disable scans; use logs instead. S3 misses can also mean HTTP 403 (access denied) or an entry larger than the plugin's `maximumCachedObjectLength`. Use `--info` to check the key and reason before assuming -an entry is missing. +an entry is missing. This build allows entries up to 256 MiB so smoke application builds +can be stored and restored. Check build logic, dependencies, JVM/toolchains, flags, platform, container digests, and generated files. Timestamps and absolute paths can cause unnecessary misses; -missing inputs can cause incorrect hits. See Gradle's -[cache debugging guide](https://docs.gradle.org/current/userguide/build_cache_debugging.html). +missing inputs can cause incorrect hits. + +## Choose input fingerprints + +Choose the input declaration according to what the task consumes. A file's extension alone +does not determine its fingerprint: a JAR can be an exact archive or a runtime classpath. + +| Declaration | Use when | Changes that invalidate the input | +| ---------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `@InputFile` / `@InputFiles` | Exact file contents matter, such as a checksum, signed archive or configuration file. | File bytes and the paths selected by `@PathSensitive`. ZIP metadata is part of the bytes. | +| `@Classpath` | The task executes or inspects JVM classes and resources, including a Java agent. | Normalised class and resource contents, and classpath entry order. JAR names, locations, ZIP timestamps and ZIP entry order are ignored. | +| `@CompileClasspath` | Only the Java API visible to compilation matters. | API changes. Method bodies, private members and resources are ignored. | +| `@Input` | A value affects execution, such as a version, flag or immutable container digest. | The value changes. | + +In a custom task or nested input object, declare these properties on their getters: + +```kotlin +@get:InputFile +@get:PathSensitive(PathSensitivity.NONE) +abstract val distributionArchive: RegularFileProperty + +@get:Classpath +abstract val tracerJar: RegularFileProperty + +@get:Input +abstract val tracerVersion: Property +``` + +`PathSensitivity.NONE` removes paths from the fingerprint while preserving file contents. +Use `RELATIVE` for a source tree whose internal paths matter. The default `ABSOLUTE` includes +the checkout location and prevents reuse across checkouts. `@Classpath` already defines its +path handling; use it without `@PathSensitive`. A task that embeds absolute paths in its +outputs also needs reproducible outputs before its cache can be shared. + +Use `@Classpath` for the smoke-test agent. It fingerprints `.classdata` as resources, so +changes to renamed agent classes still invalidate results. `@CompileClasspath` would ignore +those resources and implementation changes. See Gradle's +[`Classpath`](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/Classpath.html), +[`CompileClasspath`](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/CompileClasspath.html) +and [path sensitivity](https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/PathSensitivity.html). + +### Normalise runtime inputs + +Normalisation defines which input differences can reuse the same cached outputs. It changes +the fingerprint; the task still receives the original file. Configure runtime normalisation +in the consuming project. This build applies the following rule to all projects: + +```kotlin +normalization { + runtimeClasspath { + ignore("**/*.version") + } +} +``` + +This excludes complete matching resources, including their presence and contents, from +`@Classpath` fingerprints. It does not affect `@InputFile` fingerprints. Keep exclusions +narrow and track any excluded information that still affects execution separately. +For a `.properties` resource, Gradle also supports +[ignoring individual properties](https://docs.gradle.org/current/javadoc/org/gradle/normalization/RuntimeClasspathNormalization.html) +instead of excluding the whole file. + +`SmokeAgentArguments.tracerVersion` tracks `project.version`, for example `1.69.0-SNAPSHOT`. +`WriteVersionFile` appends `~` when generating `.version` resources. Changing only the +SHA leaves both the normalised JAR and the explicit version input unchanged. Changing the +base version invalidates results even when classes are unchanged. `-SNAPSHOT` has no special +treatment in the fingerprint. + +Nested JVM and native application builds use the same normalisation with +`smokeTestApp.projectJar('apiJar', project(':dd-trace-api'))`. +The producing project's version is tracked separately, including for non-default artifacts +such as `projectJar('agentJar', project(':dd-java-agent'), 'shadow')`. +Cached application artifacts and native executables can retain earlier embedded agent +and API commit stamps when only excluded version metadata changes. +Use raw file inputs when cached artifacts must contain the current stamp. + +### Track excluded resource presence + +Resource presence can affect smoke-test results even when its contents are excluded. The +Gradle CI Visibility smoke tests load +[`CiVisibilityGradleListener`](../dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListener.java), +which reads `dd-java-agent.version` during class initialisation. Removing that resource +can break the listener even when the agent's classes are unchanged. +`SmokeAgentArguments` and `NestedBuildProjectJar` each expose an +`neededResourcePaths` input derived lazily from their JAR file. It records the names of +resources required by the smoke tests, currently only `dd-java-agent.version`. Adding or +removing that resource changes the list and invalidates results. Changing its stamp leaves +the list and the normalised classpath unchanged. Other excluded `.version` resources have +no separate presence input. + +This is input fingerprinting and does not execute packaging checks. `verifyAgentJarContents` +stays in the agent's verification flow. Smoke tests depend on the agent artifact and declare +the inputs they consume; they do not look up a producer's verification task. When another +project needs an additional artifact, publish it through a consumable configuration, as +described in Gradle's [guide to sharing outputs between projects](https://docs.gradle.org/current/userguide/how_to_share_outputs_between_projects.html). + +The excluded stamp contents are intentionally outside the smoke-test cache key. A restored +test result does not establish that version metadata was validated for the current commit. + +## Compare fingerprints in logs + +Capture the same task on both sides of the change with detailed cache logging: + +```shell +./gradlew :dd-smoke-tests:quarkus:clean :dd-smoke-tests:quarkus:test \ + --build-cache --info --console=plain -Dorg.gradle.caching.debug=true \ + > before.log 2>&1 +``` + +Repeat with `after.log` after the change. Replace the task as needed and keep the JVM, +dependencies and flags comparable. Cleaning outputs allows restoration; omit `--rerun-tasks` +when checking hits. Local runs diagnose inputs, but do not verify CI's S3 access. + +Locate the task's block and compare these lines. The following is a shortened example; +property prefixes and hashes vary by task: + +```text +Appending implementation to build cache key: @ +Appending input value fingerprint for '...tracerVersion' to build cache key: +Appending input value fingerprint for '...neededResourcePaths' to build cache key: +Appending input file fingerprints for '...tracerJar' to build cache key: - CLASSPATH{...} +Appending output property name to build cache key: +Build cache key for task ':dd-smoke-tests:quarkus:test' is +``` + +```shell +rg -n --max-columns 300 --max-columns-preview \ + 'Build cache key|Appending .*implementation|Appending input|Appending output|Caching disabled|not up-to-date' \ + before.log after.log +``` -Smoke tests track the complete agent JAR through `SmokeAgentArguments.tracerJar` (`@InputFile`). -Its generated `.version` files contain the commit hash, so a new commit can cause smoke-test -misses even with identical sources. Compilation may still hit the cache. Rerun the same commit -with matching inputs to measure smoke-test reuse. +Compare implementation and action hashes first: Gradle, build logic and plugin changes can +invalidate tasks even when input files match. Then compare each named value and file +fingerprint. Within a changed file property, compare normalised paths and individual file +hashes. `ABSOLUTE_PATH` can reveal checkout-path sensitivity; `CLASSPATH` identifies runtime +normalisation. An absolute path printed for locating a file does not by itself mean the path +contributes to the fingerprint. + +If `tracerVersion` matches but `tracerJar` differs, inspect the packaged classes and tracked +resources. ZIP timestamps are already ignored for `@Classpath`. A text resource containing +the same filenames in a different order still changes its fingerprint. This happened with +JMXFetch's `metricconfigs.txt`; sorting the names before writing the file fixed the cause. +Compare archive entries and their contents rather than only the whole-file checksum. + +`No history is available` explains a local up-to-date miss, not a failed cache lookup. +`Caching disabled for task` gives a reason that must be resolved before expecting cache +restoration. When the final keys match but restoration still fails, check the namespace, +permissions, entry-size limit and cache read settings, and whether the producer stored the +entry. Start with the earliest unexpected miss in the dependency chain: a rebuilt fixture +can change downstream test inputs. +See Gradle's [cache debugging guide](https://docs.gradle.org/current/userguide/build_cache_debugging.html). ## Inspect a cache entry diff --git a/settings.gradle.kts b/settings.gradle.kts index cd24f069740..0470313a5b6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -94,6 +94,8 @@ if (isCI.isPresent) { isPush = true lookupDefaultAwsCredentials = true isReducedRedundancy = false + // Smoke application build entries exceed the plugin's 50 MiB default limit. + maximumCachedObjectLength = 256L * 1024 * 1024 } } }