Skip to content

Commit

Permalink
Fix Gradle instrumentation to support v8.10 (#7443)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikita-tkachenko-datadog authored Aug 15, 2024
1 parent cb14799 commit fcd4bc4
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
package datadog.trace.instrumentation.gradle;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.initialization.ClassLoaderRegistry;
import org.gradle.internal.service.DefaultServiceRegistry;
import org.gradle.internal.service.ServiceRegistration;
import org.gradle.internal.service.ServiceRegistry;
import org.gradle.internal.service.scopes.BuildScopeServices;

public class CiVisibilityGradleListenerInjector {

public static void inject(ServiceRegistry parentServices, BuildScopeServices buildScopeServices) {
ClassLoaderRegistry classLoaderRegistry = parentServices.get(ClassLoaderRegistry.class);
Class<?> ciVisibilityGradleListener = loadCiVisibilityGradleListener(classLoaderRegistry);
buildScopeServices.register(
serviceRegistration -> serviceRegistration.add(ciVisibilityGradleListener));
private static final Logger LOGGER = Logging.getLogger(CiVisibilityGradleListenerInjector.class);

public static ClassLoaderRegistry getClassLoaderRegistry(ServiceRegistry[] serviceRegistries) {
for (ServiceRegistry serviceRegistry : serviceRegistries) {
ClassLoaderRegistry classLoaderRegistry =
(ClassLoaderRegistry) serviceRegistry.find(ClassLoaderRegistry.class);
if (classLoaderRegistry != null) {
return classLoaderRegistry;
}
}
throw new RuntimeException(
"Could not find ClassLoaderRegistry service in " + Arrays.toString(serviceRegistries));
}

/**
Expand All @@ -22,7 +35,7 @@ public static void inject(ServiceRegistry parentServices, BuildScopeServices bui
* org.gradle.api.tasks.testing.Test} task), which is a plugin. Therefore, we cannot reference its
* {@code Class} instance directly, and instead have to load it explicitly.
*/
private static Class<?> loadCiVisibilityGradleListener(ClassLoaderRegistry classLoaderRegistry) {
public static Class<?> loadCiVisibilityGradleListener(ClassLoaderRegistry classLoaderRegistry) {
try {
return classLoaderRegistry
.getPluginsClassLoader()
Expand All @@ -31,4 +44,57 @@ private static Class<?> loadCiVisibilityGradleListener(ClassLoaderRegistry class
throw new RuntimeException("Could not load CI Visibility Gradle Listener", e);
}
}

/**
* Performs listener injection for Gradle v8.10+. As the tracer currently uses v8.4, some of the
* required interfaces are not available at compile time, which is why reflection is used.
*/
// TODO: once the tracer is bumped to use Gradle v8.10 replace reflection with regular invocations
public static void injectCiVisibilityGradleListener(
DefaultServiceRegistry buildScopeServices, ServiceRegistry... parentServices) {
try {
ClassLoaderRegistry classLoaderRegistry = getClassLoaderRegistry(parentServices);
ClassLoader coreApiClassLoader = classLoaderRegistry.getGradleCoreApiClassLoader();
Class<?> serviceRegistrationActionClass =
coreApiClassLoader.loadClass("org.gradle.internal.service.ServiceRegistrationAction");

Object serviceRegistrationAction =
Proxy.newProxyInstance(
coreApiClassLoader,
new Class<?>[] {serviceRegistrationActionClass},
(proxy, method, args) -> {
if (method.getName().equals("registerServices")) {
ServiceRegistration serviceRegistration = (ServiceRegistration) args[0];
Class<?> ciVisibilityGradleListener =
CiVisibilityGradleListenerInjector.loadCiVisibilityGradleListener(
classLoaderRegistry);
serviceRegistration.add(ciVisibilityGradleListener);
return null;
}
throw new UnsupportedOperationException("Method not implemented");
});

Method register =
DefaultServiceRegistry.class.getMethod("register", serviceRegistrationActionClass);
register.invoke(buildScopeServices, serviceRegistrationAction);

} catch (Exception e) {
LOGGER.warn("Could not inject CI Visibility Gradle listener", e);
}
}

/** Performs listener injection for Gradle v8.3 - 8.9 */
public static void injectCiVisibilityGradleListenerLegacy(
DefaultServiceRegistry buildScopeServices, ServiceRegistry... parentServices) {
try {
ClassLoaderRegistry classLoaderRegistry =
CiVisibilityGradleListenerInjector.getClassLoaderRegistry(parentServices);
Class<?> ciVisibilityGradleListener =
CiVisibilityGradleListenerInjector.loadCiVisibilityGradleListener(classLoaderRegistry);
buildScopeServices.register(
serviceRegistration -> serviceRegistration.add(ciVisibilityGradleListener));
} catch (Exception e) {
LOGGER.warn("Could not inject CI Visibility Gradle listener", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package datadog.trace.instrumentation.gradle;

import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.Config;
import java.util.Set;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatcher;
import org.gradle.internal.service.DefaultServiceRegistry;
import org.gradle.internal.service.ServiceRegistry;
import org.gradle.internal.service.scopes.Scope;

@AutoService(InstrumenterModule.class)
public class GradleBuildScopeServices_8_10_Instrumentation extends InstrumenterModule.CiVisibility
implements Instrumenter.ForSingleType {

public GradleBuildScopeServices_8_10_Instrumentation() {
super("gradle", "gradle-build-scope-services");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// Instrument Gradle [8.10 ... )
return hasClassNamed("org.gradle.internal.classpath.transforms.AdhocInterceptors");
}

@Override
public String instrumentedType() {
return "org.gradle.internal.service.ScopedServiceRegistry";
}

@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".CiVisibilityGradleListenerInjector",
};
}

@Override
public boolean isApplicable(Set<TargetSystem> enabledSystems) {
return super.isApplicable(enabledSystems)
&& Config.get().isCiVisibilityBuildInstrumentationEnabled();
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
isConstructor()
.and(takesArgument(0, Class.class))
.and(takesArgument(1, boolean.class))
.and(takesArgument(3, named("org.gradle.internal.service.ServiceRegistry[]"))),
getClass().getName() + "$Construct");
}

public static class Construct {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void afterConstructor(
@Advice.This final DefaultServiceRegistry buildScopeServices,
@Advice.Argument(0) final Class<? extends Scope> scope,
@Advice.Argument(3) final ServiceRegistry[] parentServices) {
if (scope.getSimpleName().equals("Build")) {
CiVisibilityGradleListenerInjector.injectCiVisibilityGradleListener(
buildScopeServices, parentServices);
}
}
}

@Override
public String muzzleDirective() {
return "skipMuzzle";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.not;

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

@AutoService(InstrumenterModule.class)
public class GradleBuildScopeServicesInstrumentation extends InstrumenterModule.CiVisibility
public class GradleBuildScopeServices_8_3_Instrumentation extends InstrumenterModule.CiVisibility
implements Instrumenter.ForSingleType {

public GradleBuildScopeServicesInstrumentation() {
public GradleBuildScopeServices_8_3_Instrumentation() {
super("gradle", "gradle-build-scope-services");
}

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

@Override
Expand Down Expand Up @@ -55,7 +57,8 @@ public static class Construct {
public static void afterConstructor(
@Advice.This final BuildScopeServices buildScopeServices,
@Advice.Argument(0) final ServiceRegistry parentServices) {
CiVisibilityGradleListenerInjector.inject(parentServices, buildScopeServices);
CiVisibilityGradleListenerInjector.injectCiVisibilityGradleListenerLegacy(
buildScopeServices, parentServices);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ class GradleDaemonSmokeTest extends CiVisibilitySmokeTest {
where:
gradleVersion | projectName | configurationCache | successExpected | flakyRetries | expectedTraces | expectedCoverages
"8.3" | "test-succeed-new-instrumentation" | false | true | false | 5 | 1
"8.9" | "test-succeed-new-instrumentation" | false | true | false | 5 | 1
LATEST_GRADLE_VERSION | "test-succeed-new-instrumentation" | false | true | false | 5 | 1
"8.3" | "test-succeed-new-instrumentation" | true | true | false | 5 | 1
"8.9" | "test-succeed-new-instrumentation" | true | true | false | 5 | 1
LATEST_GRADLE_VERSION | "test-succeed-new-instrumentation" | true | true | false | 5 | 1
LATEST_GRADLE_VERSION | "test-succeed-multi-module-new-instrumentation" | false | true | false | 7 | 2
LATEST_GRADLE_VERSION | "test-succeed-multi-forks-new-instrumentation" | false | true | false | 6 | 2
Expand Down Expand Up @@ -187,10 +189,7 @@ class GradleDaemonSmokeTest extends CiVisibilitySmokeTest {

private BuildResult runGradleTests(String gradleVersion, boolean successExpected = true, boolean configurationCache = false) {
def arguments = ["test", "--stacktrace"]
if (gradleVersion > "5.6") {
// fail on warnings is available starting from Gradle 5.6
arguments += ["--warning-mode", "fail"]
} else if (gradleVersion > "4.5") {
if (gradleVersion > "4.5") {
// warning mode available starting from Gradle 4.5
arguments += ["--warning-mode", "all"]
}
Expand Down

0 comments on commit fcd4bc4

Please sign in to comment.