Skip to content

Commit b055376

Browse files
Instrument Gradle Launcher to avoid overwriting org.gradle.jvmargs property (#8001)
1 parent b36006c commit b055376

File tree

4 files changed

+115
-2
lines changed

4 files changed

+115
-2
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,21 @@ public boolean isChild() {
5050
* not one of the supported build system processes.
5151
*/
5252
public boolean isHeadless() {
53-
return !isChild() && !isParent();
53+
return !isChild() && !isParent() && !isWrapper();
5454
}
5555

5656
private boolean isParent() {
5757
return isMavenParent() || isGradleDaemon();
5858
}
5959

60+
/**
61+
* Determines if current process is a wrapper that starts the build system. In other words a
62+
* process that is not a build system, and not a JVM that runs tests.
63+
*/
64+
private boolean isWrapper() {
65+
return isGradleLauncher();
66+
}
67+
6068
private boolean isMavenParent() {
6169
return System.getProperty("maven.home") != null
6270
&& System.getProperty("classworlds.conf") != null;
@@ -70,6 +78,12 @@ private boolean isGradleDaemon() {
7078
&& System.getProperties().getProperty("org.gradle.internal.worker.tmpdir") == null;
7179
}
7280

81+
private boolean isGradleLauncher() {
82+
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
83+
return contextClassLoader.getResource("org/gradle/launcher/Main.class") != null
84+
|| contextClassLoader.getResource("org/gradle/launcher/GradleMain.class") != null;
85+
}
86+
7387
@Nullable
7488
public InetSocketAddress getSignalServerAddress() {
7589
// System.getProperty is used rather than Config,

dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/test/ExecutionStrategy.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,11 @@ public TestRetryPolicy retryPolicy(TestIdentifier test) {
9999
public boolean isEarlyFlakeDetectionLimitReached() {
100100
int detectionsUsed = earlyFlakeDetectionsUsed.get();
101101
Collection<TestIdentifier> knownTests = executionSettings.getKnownTests();
102-
int totalTests = knownTests.size() + detectionsUsed;
102+
if (knownTests == null) {
103+
return false;
104+
}
103105

106+
int totalTests = knownTests.size() + detectionsUsed;
104107
EarlyFlakeDetectionSettings earlyFlakeDetectionSettings =
105108
executionSettings.getEarlyFlakeDetectionSettings();
106109
int threshold =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package datadog.trace.instrumentation.gradle;
2+
3+
import datadog.trace.api.Config;
4+
import java.io.File;
5+
import java.nio.file.Path;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.Properties;
9+
10+
public class GradleDaemonInjectionUtils {
11+
12+
public static Map<String, String> addJavaagentToGradleDaemonProperties(
13+
Map<String, String> jvmOptions) {
14+
File agentJar = Config.get().getCiVisibilityAgentJarFile();
15+
Path agentJarPath = agentJar.toPath();
16+
17+
StringBuilder agentArg = new StringBuilder("-javaagent:").append(agentJarPath).append('=');
18+
19+
Properties systemProperties = System.getProperties();
20+
for (Map.Entry<Object, Object> e : systemProperties.entrySet()) {
21+
String propertyName = (String) e.getKey();
22+
Object propertyValue = e.getValue();
23+
if (propertyName.startsWith(Config.PREFIX)) {
24+
agentArg.append(propertyName).append('=').append(propertyValue).append(',');
25+
}
26+
}
27+
28+
// creating a new map in case jvmOptions is immutable
29+
Map<String, String> updatedJvmOptions = new HashMap<>(jvmOptions);
30+
updatedJvmOptions.merge("org.gradle.jvmargs", agentArg.toString(), (o, n) -> o + " " + n);
31+
return updatedJvmOptions;
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package datadog.trace.instrumentation.gradle;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface;
4+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
5+
6+
import com.google.auto.service.AutoService;
7+
import datadog.trace.agent.tooling.Instrumenter;
8+
import datadog.trace.agent.tooling.InstrumenterModule;
9+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
10+
import java.util.Map;
11+
import net.bytebuddy.asm.Advice;
12+
import net.bytebuddy.description.type.TypeDescription;
13+
import net.bytebuddy.matcher.ElementMatcher;
14+
15+
/**
16+
* This instrumentation targets Gradle Launcher, which is the process that is started with
17+
* `gradle`/`gradlew` commands. The launcher starts Gradle Daemon (if not started yet), which is a
18+
* long-lived process that actually runs builds. The instrumentation injects the tracer and its
19+
* config properties into Gradle Daemon JVM settings when the daemon is started.
20+
*/
21+
@AutoService(InstrumenterModule.class)
22+
public class GradleLauncherInstrumentation extends InstrumenterModule.CiVisibility
23+
implements Instrumenter.ForTypeHierarchy {
24+
25+
public GradleLauncherInstrumentation() {
26+
super("gradle", "gradle-daemon-jvm-options");
27+
}
28+
29+
@Override
30+
public String hierarchyMarkerType() {
31+
return "org.gradle.launcher.configuration.AllProperties";
32+
}
33+
34+
@Override
35+
public ElementMatcher<TypeDescription> hierarchyMatcher() {
36+
return implementsInterface(named(hierarchyMarkerType()));
37+
}
38+
39+
@Override
40+
public void methodAdvice(MethodTransformer transformer) {
41+
transformer.applyAdvice(
42+
named("getProperties"),
43+
GradleLauncherInstrumentation.class.getName() + "$PropertiesAugmentationAdvice");
44+
}
45+
46+
@Override
47+
public String[] helperClassNames() {
48+
return new String[] {
49+
packageName + ".GradleDaemonInjectionUtils",
50+
};
51+
}
52+
53+
@SuppressFBWarnings(
54+
value = "UC_USELESS_OBJECT",
55+
justification = "jvmOptions is the return value of the original method")
56+
public static class PropertiesAugmentationAdvice {
57+
@Advice.OnMethodExit(suppress = Throwable.class)
58+
public static void addJavaagentToGradleDaemonProperties(
59+
@Advice.Return(readOnly = false) Map<String, String> jvmOptions) {
60+
jvmOptions = GradleDaemonInjectionUtils.addJavaagentToGradleDaemonProperties(jvmOptions);
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)