Skip to content

Commit 58f636d

Browse files
feat: add AGP usage visibility for Gradle projects (#11869)
feat: add AGP usage visibility for Gradle projects Detect Android Gradle Plugin projects at the build-system level and surface an is_android signal independent of the test runner: - test.is_android span tag on the build-system module, rolled up to the session via the tag propagator - IsAndroid dimension on the EVENT_FINISHED telemetry metric - detection via com.android.base and com.android.kotlin.multiplatform.library (the KMP library plugin, AGP 8.8+, does not apply com.android.base) Exercised by the Robolectric Gradle smoke test through a com.android.base stand-in plugin. Co-authored-by: daniel.mohedano <daniel.mohedano@datadoghq.com>
1 parent 073ad18 commit 58f636d

15 files changed

Lines changed: 345 additions & 50 deletions

File tree

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestModule.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric;
88
import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector;
99
import datadog.trace.api.civisibility.telemetry.tag.EventType;
10+
import datadog.trace.api.civisibility.telemetry.tag.IsAndroid;
1011
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
1112
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
1213
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
@@ -114,6 +115,10 @@ public void end(@Nullable Long endTime) {
114115
span.finish();
115116
}
116117

117-
metricCollector.add(CiVisibilityCountMetric.EVENT_FINISHED, 1, EventType.MODULE);
118+
metricCollector.add(
119+
CiVisibilityCountMetric.EVENT_FINISHED,
120+
1,
121+
EventType.MODULE,
122+
span.getTag(Tags.TEST_IS_ANDROID) != null ? IsAndroid.TRUE : null);
118123
}
119124
}

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import datadog.trace.api.civisibility.telemetry.tag.FailFastTestOrderEnabled;
2020
import datadog.trace.api.civisibility.telemetry.tag.FailedTestReplayEnabled;
2121
import datadog.trace.api.civisibility.telemetry.tag.HasCodeowner;
22+
import datadog.trace.api.civisibility.telemetry.tag.IsAndroid;
2223
import datadog.trace.api.civisibility.telemetry.tag.IsHeadless;
2324
import datadog.trace.api.civisibility.telemetry.tag.IsUnsupportedCI;
2425
import datadog.trace.api.civisibility.telemetry.tag.Provider;
@@ -200,6 +201,9 @@ protected Collection<TagValue> additionalTelemetryTags() {
200201
if (span.getTag(DDTags.TEST_HAS_FAILED_TEST_REPLAY) != null) {
201202
tags.add(FailedTestReplayEnabled.SessionMetric.TRUE);
202203
}
204+
if (span.getTag(Tags.TEST_IS_ANDROID) != null) {
205+
tags.add(IsAndroid.TRUE);
206+
}
203207
return tags;
204208
}
205209
}

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/buildsystem/BuildSystemSessionImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ private void onModuleFinish(AgentSpan moduleSpan) {
194194
TagMergeSpec.of(Tags.TEST_ITR_TESTS_SKIPPING_COUNT, Long::sum),
195195
TagMergeSpec.of(DDTags.CI_ITR_TESTS_SKIPPED, Boolean::logicalOr),
196196
TagMergeSpec.of(Tags.TEST_TEST_MANAGEMENT_ENABLED, Boolean::logicalOr),
197+
TagMergeSpec.of(Tags.TEST_IS_ANDROID, Boolean::logicalOr),
197198
TagMergeSpec.of(DDTags.TEST_HAS_FAILED_TEST_REPLAY, Boolean::logicalOr),
198199
TagMergeSpec.of(DDTags.CI_LIBRARY_CONFIGURATION_ERROR_SETTINGS, Boolean::logicalOr),
199200
TagMergeSpec.of(DDTags.CI_LIBRARY_CONFIGURATION_ERROR_SKIPPABLE_TESTS, Boolean::logicalOr),

dd-java-agent/instrumentation/gradle/gradle-3.0/src/main/groovy/datadog/trace/instrumentation/gradle/legacy/GradleBuildListener.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import datadog.trace.api.civisibility.domain.BuildSessionSettings;
88
import datadog.trace.api.civisibility.domain.JavaAgent;
99
import datadog.trace.api.civisibility.events.BuildEventsHandler;
10+
import datadog.trace.bootstrap.instrumentation.api.Tags;
1011
import java.nio.file.Path;
1112
import java.util.Collections;
1213
import java.util.List;
@@ -111,9 +112,22 @@ public void beforeExecute(@Nonnull Task task) {
111112
List<Path> classpath = GradleUtils.getClasspath(task);
112113
JavaAgent jacocoAgent = GradleUtils.getJacocoAgent(task);
113114

115+
// "com.android.base" is applied transitively by every Android Gradle Plugin, so it is a
116+
// reliable single marker for an Android project regardless of how its tests are executed.
117+
Map<String, Object> additionalTags =
118+
project.getPluginManager().hasPlugin("com.android.base")
119+
? Collections.singletonMap(Tags.TEST_IS_ANDROID, true)
120+
: Collections.emptyMap();
121+
114122
BuildModuleSettings moduleSettings =
115123
buildEventsHandler.onTestModuleStart(
116-
gradle, taskPath, moduleLayout, jvmExecutable, classpath, jacocoAgent, null);
124+
gradle,
125+
taskPath,
126+
moduleLayout,
127+
jvmExecutable,
128+
classpath,
129+
jacocoAgent,
130+
additionalTags);
117131
Map<String, String> systemProperties = moduleSettings.getSystemProperties();
118132
GradleProjectConfigurator.INSTANCE.configureTracer(task, systemProperties);
119133
}

dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityGradleListener.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ public void beforeExecute(TaskIdentity<?> taskIdentity) {
183183
Project project = gradle.getRootProject().project(projectPath);
184184
Test task = (Test) project.getTasks().getByName(taskIdentity.name);
185185

186+
// "com.android.base" is applied transitively by the application/library/dynamic-feature/test
187+
// Android Gradle Plugins. The Android KMP library plugin (AGP 8.8+) is a separate entry point
188+
// that does NOT apply com.android.base, so it must be checked explicitly.
189+
PluginManager pluginManager = project.getPluginManager();
190+
boolean isAndroid =
191+
pluginManager.hasPlugin("com.android.base")
192+
|| pluginManager.hasPlugin("com.android.kotlin.multiplatform.library");
193+
186194
Map<String, Object> inputProperties = task.getInputs().getProperties();
187195
BuildModuleLayout moduleLayout =
188196
(BuildModuleLayout) inputProperties.get(CiVisibilityPluginExtension.MODULE_LAYOUT_PROPERTY);
@@ -197,7 +205,7 @@ public void beforeExecute(TaskIdentity<?> taskIdentity) {
197205
List<Path> taskClasspath = CiVisibilityPluginExtension.getClasspath(task);
198206

199207
ciVisibilityService.onModuleStart(
200-
taskPath, moduleLayout, jvmExecutable, taskClasspath, jacocoAgent);
208+
taskPath, isAndroid, moduleLayout, jvmExecutable, taskClasspath, jacocoAgent);
201209
}
202210

203211
private JavaAgent getJacocoAgent(Test task) {

dd-java-agent/instrumentation/gradle/gradle-8.3/src/main/groovy/datadog/trace/instrumentation/gradle/CiVisibilityService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,21 @@ public void onBuildTaskFinish(String taskPath, @Nullable Throwable failure) {
123123

124124
public void onModuleStart(
125125
String taskPath,
126+
boolean isAndroid,
126127
BuildModuleLayout moduleLayout,
127128
Path jvmExecutable,
128129
Collection<Path> taskClasspath,
129130
JavaAgent jacocoAgent) {
131+
Map<String, Object> additionalTags =
132+
isAndroid ? Collections.singletonMap(Tags.TEST_IS_ANDROID, true) : Collections.emptyMap();
130133
buildEventsHandler.onTestModuleStart(
131-
SESSION_KEY, taskPath, moduleLayout, jvmExecutable, taskClasspath, jacocoAgent, null);
134+
SESSION_KEY,
135+
taskPath,
136+
moduleLayout,
137+
jvmExecutable,
138+
taskClasspath,
139+
jacocoAgent,
140+
additionalTags);
132141
}
133142

134143
public void onModuleFinish(

dd-smoke-tests/gradle/src/test/java/datadog/smoketest/GradleDaemonSmokeTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ void testNew(
131131

132132
@TableTest({
133133
"scenario | gradleVersion | projectName | expectedTraces",
134-
"robolectric-latest | latest | test-succeed-robolectric | 6 "
134+
"robolectric-latest | latest | test-succeed-robolectric | 7 "
135135
})
136136
@ParameterizedTest
137137
void testRobolectric(String gradleVersion, String projectName, int expectedTraces)

dd-smoke-tests/gradle/src/test/resources/test-succeed-robolectric/build.gradleTest

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import org.gradle.api.attributes.CompatibilityCheckDetails
88
import org.gradle.api.attributes.LibraryElements
99

1010
apply plugin: 'java'
11+
// Inert stand-in for the Android Gradle Plugin (see buildSrc/): real AGP needs the Android SDK,
12+
// which is absent in the smoke-test environment. Our build-level Android detection only checks for
13+
// the 'com.android.base' plugin id, so applying an id-only stand-in exercises it.
14+
apply plugin: 'com.android.base'
1115

1216
repositories {
1317
mavenLocal()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apply plugin: 'java'
2+
3+
repositories {
4+
mavenLocal()
5+
6+
def proxyUrl = System.getenv("MAVEN_REPOSITORY_PROXY")
7+
if (proxyUrl) {
8+
maven {
9+
url = proxyUrl
10+
allowInsecureProtocol = true
11+
}
12+
}
13+
14+
mavenCentral()
15+
}
16+
17+
dependencies {
18+
// Plugin<Project> lives in the Gradle API; the CI Visibility javac plugin is auto-injected into
19+
// this compile task too, so the repositories above must be able to resolve it.
20+
implementation gradleApi()
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package datadog.smoke.gradle;
2+
3+
import org.gradle.api.Plugin;
4+
import org.gradle.api.Project;
5+
6+
/**
7+
* No-op stand-in for the Android Gradle Plugin's {@code com.android.base} plugin.
8+
*
9+
* <p>Real AGP requires the Android SDK to be installed, which is not available in the smoke-test
10+
* environment. The CI Visibility build-level Android detection only checks whether a plugin
11+
* registered under the {@code com.android.base} id has been applied (see {@code
12+
* CiVisibilityGradleListener}); every AGP variant applies {@code com.android.base} transitively.
13+
* Registering an inert plugin under that exact id therefore exercises the detection, the
14+
* module→session tag propagation and the {@code is_android} telemetry faithfully, without pulling
15+
* in AGP or the SDK.
16+
*/
17+
public class AndroidBaseStandInPlugin implements Plugin<Project> {
18+
@Override
19+
public void apply(Project project) {
20+
// intentionally empty: only the plugin id matters for detection
21+
}
22+
}

0 commit comments

Comments
 (0)