Skip to content

Commit fcd4bc4

Browse files
Fix Gradle instrumentation to support v8.10 (#7443)
1 parent cb14799 commit fcd4bc4

4 files changed

Lines changed: 162 additions & 16 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
package datadog.trace.instrumentation.gradle;
22

3+
import java.lang.reflect.Method;
4+
import java.lang.reflect.Proxy;
5+
import java.util.Arrays;
6+
import org.gradle.api.logging.Logger;
7+
import org.gradle.api.logging.Logging;
38
import org.gradle.initialization.ClassLoaderRegistry;
9+
import org.gradle.internal.service.DefaultServiceRegistry;
10+
import org.gradle.internal.service.ServiceRegistration;
411
import org.gradle.internal.service.ServiceRegistry;
5-
import org.gradle.internal.service.scopes.BuildScopeServices;
612

713
public class CiVisibilityGradleListenerInjector {
814

9-
public static void inject(ServiceRegistry parentServices, BuildScopeServices buildScopeServices) {
10-
ClassLoaderRegistry classLoaderRegistry = parentServices.get(ClassLoaderRegistry.class);
11-
Class<?> ciVisibilityGradleListener = loadCiVisibilityGradleListener(classLoaderRegistry);
12-
buildScopeServices.register(
13-
serviceRegistration -> serviceRegistration.add(ciVisibilityGradleListener));
15+
private static final Logger LOGGER = Logging.getLogger(CiVisibilityGradleListenerInjector.class);
16+
17+
public static ClassLoaderRegistry getClassLoaderRegistry(ServiceRegistry[] serviceRegistries) {
18+
for (ServiceRegistry serviceRegistry : serviceRegistries) {
19+
ClassLoaderRegistry classLoaderRegistry =
20+
(ClassLoaderRegistry) serviceRegistry.find(ClassLoaderRegistry.class);
21+
if (classLoaderRegistry != null) {
22+
return classLoaderRegistry;
23+
}
24+
}
25+
throw new RuntimeException(
26+
"Could not find ClassLoaderRegistry service in " + Arrays.toString(serviceRegistries));
1427
}
1528

1629
/**
@@ -22,7 +35,7 @@ public static void inject(ServiceRegistry parentServices, BuildScopeServices bui
2235
* org.gradle.api.tasks.testing.Test} task), which is a plugin. Therefore, we cannot reference its
2336
* {@code Class} instance directly, and instead have to load it explicitly.
2437
*/
25-
private static Class<?> loadCiVisibilityGradleListener(ClassLoaderRegistry classLoaderRegistry) {
38+
public static Class<?> loadCiVisibilityGradleListener(ClassLoaderRegistry classLoaderRegistry) {
2639
try {
2740
return classLoaderRegistry
2841
.getPluginsClassLoader()
@@ -31,4 +44,57 @@ private static Class<?> loadCiVisibilityGradleListener(ClassLoaderRegistry class
3144
throw new RuntimeException("Could not load CI Visibility Gradle Listener", e);
3245
}
3346
}
47+
48+
/**
49+
* Performs listener injection for Gradle v8.10+. As the tracer currently uses v8.4, some of the
50+
* required interfaces are not available at compile time, which is why reflection is used.
51+
*/
52+
// TODO: once the tracer is bumped to use Gradle v8.10 replace reflection with regular invocations
53+
public static void injectCiVisibilityGradleListener(
54+
DefaultServiceRegistry buildScopeServices, ServiceRegistry... parentServices) {
55+
try {
56+
ClassLoaderRegistry classLoaderRegistry = getClassLoaderRegistry(parentServices);
57+
ClassLoader coreApiClassLoader = classLoaderRegistry.getGradleCoreApiClassLoader();
58+
Class<?> serviceRegistrationActionClass =
59+
coreApiClassLoader.loadClass("org.gradle.internal.service.ServiceRegistrationAction");
60+
61+
Object serviceRegistrationAction =
62+
Proxy.newProxyInstance(
63+
coreApiClassLoader,
64+
new Class<?>[] {serviceRegistrationActionClass},
65+
(proxy, method, args) -> {
66+
if (method.getName().equals("registerServices")) {
67+
ServiceRegistration serviceRegistration = (ServiceRegistration) args[0];
68+
Class<?> ciVisibilityGradleListener =
69+
CiVisibilityGradleListenerInjector.loadCiVisibilityGradleListener(
70+
classLoaderRegistry);
71+
serviceRegistration.add(ciVisibilityGradleListener);
72+
return null;
73+
}
74+
throw new UnsupportedOperationException("Method not implemented");
75+
});
76+
77+
Method register =
78+
DefaultServiceRegistry.class.getMethod("register", serviceRegistrationActionClass);
79+
register.invoke(buildScopeServices, serviceRegistrationAction);
80+
81+
} catch (Exception e) {
82+
LOGGER.warn("Could not inject CI Visibility Gradle listener", e);
83+
}
84+
}
85+
86+
/** Performs listener injection for Gradle v8.3 - 8.9 */
87+
public static void injectCiVisibilityGradleListenerLegacy(
88+
DefaultServiceRegistry buildScopeServices, ServiceRegistry... parentServices) {
89+
try {
90+
ClassLoaderRegistry classLoaderRegistry =
91+
CiVisibilityGradleListenerInjector.getClassLoaderRegistry(parentServices);
92+
Class<?> ciVisibilityGradleListener =
93+
CiVisibilityGradleListenerInjector.loadCiVisibilityGradleListener(classLoaderRegistry);
94+
buildScopeServices.register(
95+
serviceRegistration -> serviceRegistration.add(ciVisibilityGradleListener));
96+
} catch (Exception e) {
97+
LOGGER.warn("Could not inject CI Visibility Gradle listener", e);
98+
}
99+
}
34100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package datadog.trace.instrumentation.gradle;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
5+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
6+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
7+
8+
import com.google.auto.service.AutoService;
9+
import datadog.trace.agent.tooling.Instrumenter;
10+
import datadog.trace.agent.tooling.InstrumenterModule;
11+
import datadog.trace.api.Config;
12+
import java.util.Set;
13+
import net.bytebuddy.asm.Advice;
14+
import net.bytebuddy.matcher.ElementMatcher;
15+
import org.gradle.internal.service.DefaultServiceRegistry;
16+
import org.gradle.internal.service.ServiceRegistry;
17+
import org.gradle.internal.service.scopes.Scope;
18+
19+
@AutoService(InstrumenterModule.class)
20+
public class GradleBuildScopeServices_8_10_Instrumentation extends InstrumenterModule.CiVisibility
21+
implements Instrumenter.ForSingleType {
22+
23+
public GradleBuildScopeServices_8_10_Instrumentation() {
24+
super("gradle", "gradle-build-scope-services");
25+
}
26+
27+
@Override
28+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
29+
// Instrument Gradle [8.10 ... )
30+
return hasClassNamed("org.gradle.internal.classpath.transforms.AdhocInterceptors");
31+
}
32+
33+
@Override
34+
public String instrumentedType() {
35+
return "org.gradle.internal.service.ScopedServiceRegistry";
36+
}
37+
38+
@Override
39+
public String[] helperClassNames() {
40+
return new String[] {
41+
packageName + ".CiVisibilityGradleListenerInjector",
42+
};
43+
}
44+
45+
@Override
46+
public boolean isApplicable(Set<TargetSystem> enabledSystems) {
47+
return super.isApplicable(enabledSystems)
48+
&& Config.get().isCiVisibilityBuildInstrumentationEnabled();
49+
}
50+
51+
@Override
52+
public void methodAdvice(MethodTransformer transformer) {
53+
transformer.applyAdvice(
54+
isConstructor()
55+
.and(takesArgument(0, Class.class))
56+
.and(takesArgument(1, boolean.class))
57+
.and(takesArgument(3, named("org.gradle.internal.service.ServiceRegistry[]"))),
58+
getClass().getName() + "$Construct");
59+
}
60+
61+
public static class Construct {
62+
@Advice.OnMethodExit(suppress = Throwable.class)
63+
public static void afterConstructor(
64+
@Advice.This final DefaultServiceRegistry buildScopeServices,
65+
@Advice.Argument(0) final Class<? extends Scope> scope,
66+
@Advice.Argument(3) final ServiceRegistry[] parentServices) {
67+
if (scope.getSimpleName().equals("Build")) {
68+
CiVisibilityGradleListenerInjector.injectCiVisibilityGradleListener(
69+
buildScopeServices, parentServices);
70+
}
71+
}
72+
}
73+
74+
@Override
75+
public String muzzleDirective() {
76+
return "skipMuzzle";
77+
}
78+
}

dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildScopeServicesInstrumentation.java renamed to dd-java-agent/instrumentation/gradle/src/main/groovy/datadog/trace/instrumentation/gradle/GradleBuildScopeServices_8_3_Instrumentation.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
44
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
5+
import static net.bytebuddy.matcher.ElementMatchers.not;
56

67
import com.google.auto.service.AutoService;
78
import datadog.trace.agent.tooling.Instrumenter;
@@ -14,17 +15,18 @@
1415
import org.gradle.internal.service.scopes.BuildScopeServices;
1516

1617
@AutoService(InstrumenterModule.class)
17-
public class GradleBuildScopeServicesInstrumentation extends InstrumenterModule.CiVisibility
18+
public class GradleBuildScopeServices_8_3_Instrumentation extends InstrumenterModule.CiVisibility
1819
implements Instrumenter.ForSingleType {
1920

20-
public GradleBuildScopeServicesInstrumentation() {
21+
public GradleBuildScopeServices_8_3_Instrumentation() {
2122
super("gradle", "gradle-build-scope-services");
2223
}
2324

2425
@Override
2526
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
26-
// Only instrument Gradle 8.3+
27-
return hasClassNamed("org.gradle.api.file.ConfigurableFilePermissions");
27+
// Instrument Gradle [8.3 ... 8.10)
28+
return hasClassNamed("org.gradle.api.file.ConfigurableFilePermissions")
29+
.and(not(hasClassNamed("org.gradle.internal.classpath.transforms.AdhocInterceptors")));
2830
}
2931

3032
@Override
@@ -55,7 +57,8 @@ public static class Construct {
5557
public static void afterConstructor(
5658
@Advice.This final BuildScopeServices buildScopeServices,
5759
@Advice.Argument(0) final ServiceRegistry parentServices) {
58-
CiVisibilityGradleListenerInjector.inject(parentServices, buildScopeServices);
60+
CiVisibilityGradleListenerInjector.injectCiVisibilityGradleListenerLegacy(
61+
buildScopeServices, parentServices);
5962
}
6063
}
6164
}

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ class GradleDaemonSmokeTest extends CiVisibilitySmokeTest {
9696
where:
9797
gradleVersion | projectName | configurationCache | successExpected | flakyRetries | expectedTraces | expectedCoverages
9898
"8.3" | "test-succeed-new-instrumentation" | false | true | false | 5 | 1
99+
"8.9" | "test-succeed-new-instrumentation" | false | true | false | 5 | 1
99100
LATEST_GRADLE_VERSION | "test-succeed-new-instrumentation" | false | true | false | 5 | 1
100101
"8.3" | "test-succeed-new-instrumentation" | true | true | false | 5 | 1
102+
"8.9" | "test-succeed-new-instrumentation" | true | true | false | 5 | 1
101103
LATEST_GRADLE_VERSION | "test-succeed-new-instrumentation" | true | true | false | 5 | 1
102104
LATEST_GRADLE_VERSION | "test-succeed-multi-module-new-instrumentation" | false | true | false | 7 | 2
103105
LATEST_GRADLE_VERSION | "test-succeed-multi-forks-new-instrumentation" | false | true | false | 6 | 2
@@ -187,10 +189,7 @@ class GradleDaemonSmokeTest extends CiVisibilitySmokeTest {
187189

188190
private BuildResult runGradleTests(String gradleVersion, boolean successExpected = true, boolean configurationCache = false) {
189191
def arguments = ["test", "--stacktrace"]
190-
if (gradleVersion > "5.6") {
191-
// fail on warnings is available starting from Gradle 5.6
192-
arguments += ["--warning-mode", "fail"]
193-
} else if (gradleVersion > "4.5") {
192+
if (gradleVersion > "4.5") {
194193
// warning mode available starting from Gradle 4.5
195194
arguments += ["--warning-mode", "all"]
196195
}

0 commit comments

Comments
 (0)