Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Gradle instrumentation to support v8.10 #7443

Merged
merged 3 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,74 @@
package datadog.trace.instrumentation.gradle.v8_10;

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 datadog.trace.instrumentation.gradle.CiVisibilityGradleListenerInjector;
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 GradleBuildScopeServicesInstrumentationCopy extends InstrumenterModule.CiVisibility
mcculls marked this conversation as resolved.
Show resolved Hide resolved
implements Instrumenter.ForSingleType {

public GradleBuildScopeServicesInstrumentationCopy() {
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[] {
"datadog.trace.instrumentation.gradle.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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package datadog.trace.instrumentation.gradle;
package datadog.trace.instrumentation.gradle.v8_3;

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;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.Config;
import datadog.trace.instrumentation.gradle.CiVisibilityGradleListenerInjector;
import java.util.Set;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatcher;
Expand All @@ -23,8 +25,9 @@ public GradleBuildScopeServicesInstrumentation() {

@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 All @@ -35,7 +38,7 @@ public String instrumentedType() {
@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".CiVisibilityGradleListenerInjector",
"datadog.trace.instrumentation.gradle.CiVisibilityGradleListenerInjector",
};
}

Expand All @@ -55,7 +58,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
Loading