diff --git a/dd-java-agent/instrumentation/gradle/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy b/dd-java-agent/instrumentation/gradle/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy index 43d4fb64128..d2ef66ce277 100644 --- a/dd-java-agent/instrumentation/gradle/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy +++ b/dd-java-agent/instrumentation/gradle/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleProjectConfigurator.groovy @@ -43,6 +43,10 @@ class GradleProjectConfigurator { public static final GradleProjectConfigurator INSTANCE = new GradleProjectConfigurator() private static final String JACOCO_PLUGIN_ID = 'jacoco' + private static final String JACOCO_AGENT_CONFIGURATION_NAME = 'jacocoAgent' + private static final String JACOCO_ANT_CONFIGURATION_NAME = 'jacocoAnt' + private static final String DEPENDENCY_VERIFICATION_WARNING_EMITTED = + 'datadog.civisibility.dependency-verification-warning-emitted' void configureTracer(Task task, Map propagatedSystemProperties) { def config = Config.get() @@ -130,15 +134,8 @@ class GradleProjectConfigurator { def ddJavacPlugin = project.configurations.detachedConfiguration(project.dependencies.create("com.datadoghq:dd-javac-plugin:$compilerPluginVersion")) def ddJavacPluginClient = project.configurations.detachedConfiguration(project.dependencies.create("com.datadoghq:dd-javac-plugin-client:$compilerPluginVersion")) - // if instrumented project does dependency verification, - // we need to exclude the two detached configurations that we're adding - // as corresponding entries are not in the project's verification-metadata.xml - if (ddJavacPlugin.resolutionStrategy.respondsTo("disableDependencyVerification")) { - ddJavacPlugin.resolutionStrategy.disableDependencyVerification() - } - if (ddJavacPluginClient.resolutionStrategy.respondsTo("disableDependencyVerification")) { - ddJavacPluginClient.resolutionStrategy.disableDependencyVerification() - } + disableDependencyVerificationIfConfigured(project, ddJavacPlugin) + disableDependencyVerificationIfConfigured(project, ddJavacPluginClient) task.classpath = (task.classpath ?: project.files([])) + ddJavacPluginClient.asFileTree @@ -199,14 +196,11 @@ class GradleProjectConfigurator { project.apply("plugin": JACOCO_PLUGIN_ID) project.jacoco.toolVersion = config.ciVisibilityJacocoPluginVersion - // if instrumented project does dependency verification, - // we need to exclude configurations added by Jacoco - // as corresponding entries are not in the project's verification-metadata.xml - def jacocoConfigurations = project.configurations.findAll { it.name.startsWith("jacoco") } + def jacocoConfigurations = project.configurations.findAll { + it.name == JACOCO_AGENT_CONFIGURATION_NAME || it.name == JACOCO_ANT_CONFIGURATION_NAME + } for (def jacocoConfiguration : jacocoConfigurations) { - if (jacocoConfiguration.resolutionStrategy.respondsTo("disableDependencyVerification")) { - jacocoConfiguration.resolutionStrategy.disableDependencyVerification() - } + disableDependencyVerificationIfConfigured(project, jacocoConfiguration) } def coverageIncludedPackages = sessionSettings.getCoverageIncludedPackages() @@ -227,6 +221,28 @@ class GradleProjectConfigurator { } } + private static void disableDependencyVerificationIfConfigured(Project project, def configuration) { + if (Config.get().ciVisibilityGradleDependencyVerificationEnabled + || !configuration.resolutionStrategy.respondsTo("disableDependencyVerification")) { + return + } + + if (project.rootProject.file('gradle/verification-metadata.xml').isFile()) { + def extraProperties = project.rootProject.extensions.extraProperties + if (!extraProperties.has(DEPENDENCY_VERIFICATION_WARNING_EMITTED)) { + extraProperties.set(DEPENDENCY_VERIFICATION_WARNING_EMITTED, true) + project.logger.warn( + "Datadog Test Optimization disabled Gradle dependency verification for dependencies " + + "injected into this build. To keep verification enabled, set " + + "DD_CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED=true and add " + + "the Datadog compiler plugin and injected JaCoCo dependencies to " + + "gradle/verification-metadata.xml. If the metadata is not updated, Gradle will " + + "fail the build while resolving these dependencies.") + } + } + configuration.resolutionStrategy.disableDependencyVerification() + } + private static void forEveryTestTask(Project project, Closure closure) { def c = { task -> if (GradleUtils.isTestTask(task)) { diff --git a/dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java b/dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java index 6ac57672d66..4d7ad9c2c26 100644 --- a/dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java +++ b/dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityPlugin.java @@ -1,5 +1,6 @@ package datadog.trace.instrumentation.gradle; +import datadog.trace.api.Config; import datadog.trace.api.civisibility.domain.BuildModuleLayout; import java.io.File; import java.nio.file.Files; @@ -30,6 +31,10 @@ public abstract class CiVisibilityPlugin implements Plugin { private static final String PLUGIN_EXTENSION_NAME = "dd-ci-visibility"; private static final String JACOCO_PLUGIN_ID = "jacoco"; + private static final String JACOCO_AGENT_CONFIGURATION_NAME = "jacocoAgent"; + private static final String JACOCO_ANT_CONFIGURATION_NAME = "jacocoAnt"; + private static final String DEPENDENCY_VERIFICATION_WARNING_EMITTED = + "datadog.civisibility.dependency-verification-warning-emitted"; private Project project; @@ -105,14 +110,44 @@ public void addCompilerPluginConfigurations(CiVisibilityPluginExtension extensio "com.datadoghq:dd-javac-plugin-client:%s", extension.getCompilerPluginVersion()))); - // if instrumented project does dependency verification, - // we need to exclude the detached configurations that we're adding - // as corresponding entries are not in the project's verification-metadata.xml - configuration.getResolutionStrategy().disableDependencyVerification(); + disableDependencyVerificationIfConfigured(configuration); extension.setCompilerPluginClasspath(configuration); } + private void disableDependencyVerificationIfConfigured(Configuration configuration) { + if (Config.get().isCiVisibilityGradleDependencyVerificationEnabled()) { + return; + } + + if (Files.isRegularFile( + project + .getRootProject() + .getProjectDir() + .toPath() + .resolve("gradle/verification-metadata.xml"))) { + if (!project + .getRootProject() + .getExtensions() + .getExtraProperties() + .has(DEPENDENCY_VERIFICATION_WARNING_EMITTED)) { + project + .getRootProject() + .getExtensions() + .getExtraProperties() + .set(DEPENDENCY_VERIFICATION_WARNING_EMITTED, true); + LOGGER.warn( + "Datadog Test Optimization disabled Gradle dependency verification for dependencies " + + "injected into this build. To keep verification enabled, set " + + "DD_CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED=true and add " + + "the Datadog compiler plugin and injected JaCoCo dependencies to " + + "gradle/verification-metadata.xml. If the metadata is not updated, Gradle will " + + "fail the build while resolving these dependencies."); + } + } + configuration.getResolutionStrategy().disableDependencyVerification(); + } + private void addModuleName(CiVisibilityPluginExtension extension) { extension.setModuleName(getModuleName()); } @@ -156,13 +191,13 @@ private void applyJacocoPlugin(CiVisibilityPluginExtension extension) { List jacocoConfigurations = project.getConfigurations().stream() - .filter(c -> c.getName().startsWith("jacoco")) + .filter( + c -> + JACOCO_AGENT_CONFIGURATION_NAME.equals(c.getName()) + || JACOCO_ANT_CONFIGURATION_NAME.equals(c.getName())) .collect(Collectors.toList()); for (Configuration jacocoConfiguration : jacocoConfigurations) { - // if instrumented project does dependency verification, - // we need to exclude configurations added by Jacoco - // as corresponding entries are not in the project's verification-metadata.xml - jacocoConfiguration.getResolutionStrategy().disableDependencyVerification(); + disableDependencyVerificationIfConfigured(jacocoConfiguration); } } diff --git a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java index 5b21058b906..f86716bc8ec 100644 --- a/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java +++ b/dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java @@ -1,5 +1,6 @@ package datadog.smoketest; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -161,6 +162,45 @@ void testRobolectric(String gradleVersion, String projectName, int expectedTrace mockBackend.waitForCoverages(0)); } + @TableTest({ + "scenario | gradleVersion | verificationEnabled", + "legacy-default | 7.6.4 | false ", + "legacy-enabled | 7.6.4 | true ", + "modern-default | 8.3 | false ", + "modern-enabled | 8.3 | true " + }) + @ParameterizedTest + void testInjectedDependencyVerification(String gradleVersion, boolean verificationEnabled) + throws IOException { + givenGradleVersionIsCompatibleWithCurrentJvm(gradleVersion); + givenGradleVersionIsSupportedByCurrentGradleTestKit(gradleVersion); + givenGradleProjectFiles("test-gradle-dependency-verification"); + + Map additionalArgs = new HashMap<>(); + if (verificationEnabled) { + additionalArgs.put( + CiVisibilityConfig.CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED, "true"); + } + givenGradleProjectProperties(additionalArgs); + ensureDependenciesDownloaded(gradleVersion); + + BuildResult buildResult = + runGradle( + gradleVersion, Arrays.asList("compileJava", "--stacktrace"), !verificationEnabled); + + if (verificationEnabled) { + assertTrue(buildResult.getOutput().contains("Dependency verification failed")); + } else { + assertBuildSuccessful(buildResult); + String warning = + "Datadog Test Optimization disabled Gradle dependency verification for dependencies " + + "injected into this build."; + int firstWarning = buildResult.getOutput().indexOf(warning); + assertTrue(firstWarning >= 0); + assertEquals(firstWarning, buildResult.getOutput().lastIndexOf(warning)); + } + } + // TODO: add back LATEST_GRADLE_VERSION after fixing ordering on Gradle 9.3.0 @TableTest({ "scenario | gradleVersion | projectName | flakyTests | expectedOrder | eventsNumber", @@ -280,14 +320,19 @@ private void runGradleTest( } private void givenGradleProjectProperties() throws IOException { + givenGradleProjectProperties(Collections.emptyMap()); + } + + private void givenGradleProjectProperties(Map additionalArgs) throws IOException { assertTrue(new java.io.File(AGENT_JAR).isFile()); Path ddApiKeyPath = testKitFolder.resolve(".dd.api.key"); Files.write(ddApiKeyPath, "dummy".getBytes()); - Map additionalArgs = new HashMap<>(); - additionalArgs.put(GeneralConfig.API_KEY_FILE, ddApiKeyPath.toAbsolutePath().toString()); - additionalArgs.put( + Map effectiveAdditionalArgs = new HashMap<>(additionalArgs); + effectiveAdditionalArgs.put( + GeneralConfig.API_KEY_FILE, ddApiKeyPath.toAbsolutePath().toString()); + effectiveAdditionalArgs.put( CiVisibilityConfig.CIVISIBILITY_JACOCO_PLUGIN_VERSION, JACOCO_PLUGIN_VERSION); /* * Some of the smoke tests (in particular the one with the Gradle plugin), are using Gradle Test Kit for their tests. @@ -298,9 +343,9 @@ private void givenGradleProjectProperties() throws IOException { * This causes the tests to fail because the number of reported traces is different. * To avoid this discrepancy between local and CI runs, we disable tracing instrumentations. */ - additionalArgs.put(TraceInstrumentationConfig.TRACE_ENABLED, "false"); + effectiveAdditionalArgs.put(TraceInstrumentationConfig.TRACE_ENABLED, "false"); List arguments = - buildJvmArguments(mockBackend.getIntakeUrl(), TEST_SERVICE_NAME, additionalArgs); + buildJvmArguments(mockBackend.getIntakeUrl(), TEST_SERVICE_NAME, effectiveAdditionalArgs); String gradleProperties = "org.gradle.jvmargs=" + String.join(" ", arguments); // Write to projectFolder (per-test) instead of testKitFolder (shared), so each diff --git a/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/build.gradleTest b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/build.gradleTest new file mode 100644 index 00000000000..48dc7441e51 --- /dev/null +++ b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/build.gradleTest @@ -0,0 +1,16 @@ +apply plugin: 'java' + +repositories { + mavenLocal() + + def proxyUrl = System.getenv("MAVEN_REPOSITORY_PROXY") + if (proxyUrl) { + println "Using proxy repository: $proxyUrl" + maven { + url = proxyUrl + allowInsecureProtocol = true + } + } + + mavenCentral() +} diff --git a/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/gradle/verification-metadata.xml b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/gradle/verification-metadata.xml new file mode 100644 index 00000000000..327bacbf662 --- /dev/null +++ b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/gradle/verification-metadata.xml @@ -0,0 +1,10 @@ + + + + true + false + + + diff --git a/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/settings.gradleTest b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/settings.gradleTest new file mode 100644 index 00000000000..917d76766e3 --- /dev/null +++ b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/settings.gradleTest @@ -0,0 +1 @@ +rootProject.name = 'gradle-dependency-verification-test-project' diff --git a/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/src/main/java/datadog/smoke/Calculator.java b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/src/main/java/datadog/smoke/Calculator.java new file mode 100644 index 00000000000..50e8f1c746e --- /dev/null +++ b/dd-smoke-tests/gradle/src/test/resources/test-gradle-dependency-verification/src/main/java/datadog/smoke/Calculator.java @@ -0,0 +1,8 @@ +package datadog.smoke; + +public class Calculator { + + public int add(int left, int right) { + return left + right; + } +} diff --git a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java index 40d5753c478..25140de627e 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java @@ -198,6 +198,7 @@ public final class ConfigDefaults { static final boolean DEFAULT_CIVISIBILITY_BUILD_INSTRUMENTATION_ENABLED = true; static final boolean DEFAULT_CIVISIBILITY_AUTO_CONFIGURATION_ENABLED = true; static final boolean DEFAULT_CIVISIBILITY_COMPILER_PLUGIN_AUTO_CONFIGURATION_ENABLED = true; + static final boolean DEFAULT_CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED = false; static final String DEFAULT_CIVISIBILITY_COMPILER_PLUGIN_VERSION = "0.2.4"; static final String DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_VERSION = "0.8.15"; static final String DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_EXCLUDES = diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java index 7aa7be07380..a42de2ebc98 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/CiVisibilityConfig.java @@ -83,6 +83,8 @@ public final class CiVisibilityConfig { public static final String TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES = "test.management.attempt.to.fix.retries"; public static final String TEST_FAILED_TEST_REPLAY_ENABLED = "test.failed.test.replay.enabled"; + public static final String CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED = + "civisibility.gradle.dependency.verification.enabled"; /* Git PR info */ public static final String GIT_PULL_REQUEST_BASE_BRANCH = "git.pull.request.base.branch"; diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 0fa16a5b838..d7e1b281a7e 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -35,6 +35,7 @@ import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_GIT_UNSHALLOW_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_GIT_UPLOAD_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_GIT_UPLOAD_TIMEOUT_MILLIS; +import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED; import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_EXCLUDES; import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_JACOCO_PLUGIN_VERSION; import static datadog.trace.api.ConfigDefaults.DEFAULT_CIVISIBILITY_RESOURCE_FOLDER_NAMES; @@ -277,6 +278,7 @@ import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GIT_UNSHALLOW_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GIT_UPLOAD_TIMEOUT_MILLIS; +import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_GRADLE_SOURCE_SETS; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED; import static datadog.trace.api.config.CiVisibilityConfig.CIVISIBILITY_INJECTED_TRACER_VERSION; @@ -1149,6 +1151,7 @@ public static String getHostName() { private final boolean ciVisibilityAutoConfigurationEnabled; private final String ciVisibilityAdditionalChildProcessJvmArgs; private final boolean ciVisibilityCompilerPluginAutoConfigurationEnabled; + private final boolean ciVisibilityGradleDependencyVerificationEnabled; private final boolean ciVisibilityCodeCoverageEnabled; private final Boolean ciVisibilityCoverageLinesEnabled; private final String ciVisibilityCodeCoverageReportDumpDir; @@ -2703,6 +2706,10 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) configProvider.getBoolean( CIVISIBILITY_COMPILER_PLUGIN_AUTO_CONFIGURATION_ENABLED, DEFAULT_CIVISIBILITY_COMPILER_PLUGIN_AUTO_CONFIGURATION_ENABLED); + ciVisibilityGradleDependencyVerificationEnabled = + configProvider.getBoolean( + CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED, + DEFAULT_CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED); ciVisibilityCodeCoverageEnabled = configProvider.getBoolean(CIVISIBILITY_CODE_COVERAGE_ENABLED, true); ciVisibilityCoverageLinesEnabled = @@ -4447,6 +4454,10 @@ public List getCiVisibilityJacocoGradleSourceSets() { return ciVisibilityJacocoGradleSourceSets; } + public boolean isCiVisibilityGradleDependencyVerificationEnabled() { + return ciVisibilityGradleDependencyVerificationEnabled; + } + public boolean isCiVisibilityCodeCoverageReportUploadEnabled() { return ciVisibilityCodeCoverageReportUploadEnabled; } @@ -6820,6 +6831,8 @@ public String toString() { + otlpTracesCompression + ", otlpTracesTimeout=" + otlpTracesTimeout + + ", ciVisibilityGradleDependencyVerificationEnabled=" + + ciVisibilityGradleDependencyVerificationEnabled + ", serviceDiscoveryEnabled=" + serviceDiscoveryEnabled + ", sfnInjectDatadogAttributeEnabled=" diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index d6b02fbfa28..1245c9a18c3 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -713,6 +713,14 @@ "aliases": [] } ], + "DD_CIVISIBILITY_GRADLE_DEPENDENCY_VERIFICATION_ENABLED": [ + { + "version": "A", + "type": "boolean", + "default": "false", + "aliases": [] + } + ], "DD_CIVISIBILITY_GRADLE_SOURCESETS": [ { "version": "A",