Skip to content

Commit 91f5e74

Browse files
bric3sarahchen6
authored andcommitted
Drop the requirement of jvm environment variables for testJvm constraint (#9968)
* chore: Drop org.gradle.java.installations.fromEnv toolchain restriction * chore: Drop org.gradle.java.installations toolchain restriction for local env, but keep them in CI * chore: Drop org.gradle.java.installations toolchain restriction for local env, but keep them in CI * feat: Allow discovery of testJvm without environment variables * feat: Only rely on Gradle's toolchain to run tests * fix: Auto discover JAVA_xx_HOME variables from the build image * typo: Use `-P` Co-authored-by: Sarah Chen <[email protected]> --------- Co-authored-by: Sarah Chen <[email protected]>
1 parent 2018e0c commit 91f5e74

File tree

3 files changed

+127
-51
lines changed

3 files changed

+127
-51
lines changed

.gitlab-ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,11 @@ default:
172172
- export ORG_GRADLE_PROJECT_mavenRepositoryProxy=$MAVEN_REPOSITORY_PROXY
173173
- export ORG_GRADLE_PROJECT_gradlePluginProxy=$GRADLE_PLUGIN_PROXY
174174
- |
175-
cat >> gradle.properties <<'EOF'
175+
JAVA_HOMES=$(env | grep -E '^JAVA_[A-Z0-9_]+_HOME=' | sed 's/=.*//' | paste -sd,)
176+
cat >> gradle.properties <<EOF
176177
org.gradle.java.installations.auto-detect=false
177178
org.gradle.java.installations.auto-download=false
178-
org.gradle.java.installations.fromEnv=JAVA_8_HOME,JAVA_11_HOME,JAVA_17_HOME,JAVA_21_HOME,JAVA_25_HOME
179+
org.gradle.java.installations.fromEnv=$JAVA_HOMES
179180
EOF
180181
- mkdir -p .gradle
181182
- export GRADLE_USER_HOME=$(pwd)/.gradle
Lines changed: 123 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,162 @@
11
package datadog.gradle.plugin.testJvmConstraints
22

3-
import org.gradle.kotlin.dsl.support.serviceOf
43
import org.gradle.api.GradleException
54
import org.gradle.api.Project
5+
import org.gradle.api.internal.provider.PropertyFactory
6+
import org.gradle.api.provider.Provider
7+
import org.gradle.jvm.toolchain.JavaLanguageVersion
68
import org.gradle.jvm.toolchain.JavaLauncher
79
import org.gradle.jvm.toolchain.JavaToolchainService
10+
import org.gradle.jvm.toolchain.JavaToolchainSpec
11+
import org.gradle.jvm.toolchain.JvmImplementation
12+
import org.gradle.jvm.toolchain.JvmVendorSpec
13+
import org.gradle.jvm.toolchain.internal.DefaultToolchainSpec
14+
import org.gradle.jvm.toolchain.internal.SpecificInstallationToolchainSpec
15+
import org.gradle.kotlin.dsl.support.serviceOf
816
import java.nio.file.Files
917
import java.nio.file.Path
1018
import java.nio.file.Paths
1119

12-
20+
/**
21+
* Handles the `testJvm` property to resolve a Java launcher for testing.
22+
*
23+
* The `testJvm` property can be set via command line or environment variable to specify
24+
* which JVM to use for running tests. E.g.
25+
*
26+
* ```shell
27+
* ./gradlew test -PtestJvm=ZULU11
28+
* ```
29+
*
30+
* This handles local setup, and CI environment, where the environment variables are defined here:
31+
* * https://github.com/DataDog/dd-trace-java-docker-build/blob/a4f4bfa9d7fe0708858e595697dc67970a2a458f/Dockerfile#L182-L188
32+
* * https://github.com/DataDog/dd-trace-java-docker-build/blob/a4f4bfa9d7fe0708858e595697dc67970a2a458f/Dockerfile#L222-L241
33+
*/
1334
class TestJvmSpec(val project: Project) {
1435
companion object {
1536
const val TEST_JVM = "testJvm"
1637
}
1738

1839
private val currentJavaHomePath = project.providers.systemProperty("java.home").map { it.normalizeToJDKJavaHome() }
19-
20-
val testJvmProperty = project.providers.gradleProperty(TEST_JVM)
2140

22-
val normalizedTestJvm = testJvmProperty.map { testJvm ->
41+
/**
42+
* The raw `testJvm` property as passed via command line or environment variable.
43+
*/
44+
val testJvmProperty: Provider<String> = project.providers.gradleProperty(TEST_JVM)
45+
46+
/**
47+
* Normalized `stable` string to the highest JAVA_X_HOME found in environment variables.
48+
*/
49+
val normalizedTestJvm: Provider<String> = testJvmProperty.map { testJvm ->
2350
if (testJvm.isBlank()) {
2451
throw GradleException("testJvm property is blank")
2552
}
2653

2754
// "stable" is calculated as the largest X found in JAVA_X_HOME
28-
if (testJvm == "stable") {
29-
val javaVersions = project.providers.environmentVariablesPrefixedBy("JAVA_").map { javaHomes ->
30-
javaHomes
31-
.filter { it.key.matches(Regex("^JAVA_[0-9]+_HOME$")) }
32-
.map { Regex("^JAVA_(\\d+)_HOME$").find(it.key)!!.groupValues[1].toInt() }
33-
}.get()
34-
35-
if (javaVersions.isEmpty()) {
36-
throw GradleException("No valid JAVA_X_HOME environment variables found.")
55+
when (testJvm) {
56+
"stable" -> {
57+
val javaVersions = project.providers.environmentVariablesPrefixedBy("JAVA_").map { javaHomes ->
58+
javaHomes
59+
.filter { it.key.matches(Regex("^JAVA_[0-9]+_HOME$")) }
60+
.map { Regex("^JAVA_(\\d+)_HOME$").find(it.key)!!.groupValues[1].toInt() }
61+
}.get()
62+
63+
if (javaVersions.isEmpty()) {
64+
throw GradleException("No valid JAVA_X_HOME environment variables found.")
65+
}
66+
67+
javaVersions.max().toString()
3768
}
3869

39-
javaVersions.max().toString()
40-
} else {
41-
testJvm
70+
else -> testJvm
4271
}
4372
}.map { project.logger.info("normalized testJvm: $it"); it }
4473

45-
val testJvmHomePath = normalizedTestJvm.map {
46-
if (Files.exists(Paths.get(it))) {
47-
it.normalizeToJDKJavaHome()
48-
} else {
49-
val matcher = Regex("([a-zA-Z]*)([0-9]+)").find(it)
50-
if (matcher == null) {
51-
throw GradleException("Unable to find launcher for Java '$it'. It needs to match '([a-zA-Z]*)([0-9]+)'.")
52-
}
53-
val testJvmEnv = "JAVA_${it}_HOME"
54-
val testJvmHome = project.providers.environmentVariable(testJvmEnv).orNull
55-
if (testJvmHome == null) {
56-
throw GradleException("Unable to find launcher for Java '$it'. Have you set '$testJvmEnv'?")
74+
/**
75+
* The home path of the test JVM.
76+
*
77+
* The `<testJvm>` string (`8`, `11`, `ZULU8`, `GRAALVM25`, etc.) is interpreted in that order:
78+
* 1. Lookup for a valid path,
79+
* 2. Look JVM via Gradle toolchains
80+
*
81+
* Holds the resolved JavaToolchainSpec for the test JVM.
82+
*/
83+
private val testJvmSpec = normalizedTestJvm.map {
84+
val (distribution, version) = Regex("([a-zA-Z]*)([0-9]+)").matchEntire(it)?.groupValues?.drop(1) ?: listOf("", "")
85+
86+
when {
87+
Files.exists(Paths.get(it)) -> it.normalizeToJDKJavaHome().toToolchainSpec()
88+
89+
version.isNotBlank() -> {
90+
// Best effort to make a spec for the passed testJvm
91+
// `8`, `11`, `ZULU8`, `GRAALVM25`, etc.
92+
// if it is an integer, we assume it's a Java version
93+
// also we can handle on macOs oracle, zulu, semeru, graalvm prefixes
94+
95+
// This is using internal APIs
96+
DefaultToolchainSpec(project.serviceOf<PropertyFactory>()).apply {
97+
languageVersion.set(JavaLanguageVersion.of(version.toInt()))
98+
when (distribution.lowercase()) {
99+
"oracle" -> {
100+
vendor.set(JvmVendorSpec.ORACLE)
101+
}
102+
103+
"zulu" -> {
104+
vendor.set(JvmVendorSpec.AZUL)
105+
}
106+
107+
"semeru" -> {
108+
vendor.set(JvmVendorSpec.IBM)
109+
implementation.set(JvmImplementation.J9)
110+
}
111+
112+
"graalvm" -> {
113+
vendor.set(JvmVendorSpec.GRAAL_VM)
114+
nativeImageCapable.set(true)
115+
}
116+
}
117+
}
57118
}
58119

59-
testJvmHome.normalizeToJDKJavaHome()
120+
else -> throw GradleException(
121+
"""
122+
Unable to find launcher for Java '$it'. It needs to be:
123+
1. A valid path to a JDK home, or
124+
2. An environment variable named 'JAVA_<testJvm>_HOME' or '<testJvm>' pointing to a JDK home, or
125+
3. A Java version or a known distribution+version combination (e.g. '11', 'zulu8', 'graalvm11', etc.) that can be resolved via Gradle toolchains.
126+
4. If using Gradle toolchains, ensure that the requested JDK is installed and configured correctly.
127+
""".trimIndent()
128+
)
60129
}
61130
}.map { project.logger.info("testJvm home path: $it"); it }
62131

63-
val javaTestLauncher = project.providers.zip(testJvmHomePath, normalizedTestJvm) { testJvmHome, testJvm ->
64-
// Only change test JVM if it's not the one we are running the gradle build with
65-
if (currentJavaHomePath.get() == testJvmHome) {
66-
project.providers.provider<JavaLauncher?> { null }
67-
} else {
68-
// This is using internal APIs
69-
val jvmSpec = org.gradle.jvm.toolchain.internal.SpecificInstallationToolchainSpec(
70-
project.serviceOf<org.gradle.api.internal.provider.PropertyFactory>(),
71-
project.file(testJvmHome)
72-
)
73-
74-
// The provider always says that a value is present so we need to wrap it for proper error messages
75-
project.javaToolchains.launcherFor(jvmSpec).orElse(project.providers.provider {
76-
throw GradleException("Unable to find launcher for Java $testJvm. Does '$testJvmHome' point to a JDK?")
77-
})
78-
}
79-
}.flatMap { it }.map { project.logger.info("testJvm launcher: ${it.executablePath}"); it }
132+
/**
133+
* The Java launcher for the test JVM.
134+
*
135+
* Current JVM or a launcher specified via the testJvm.
136+
*/
137+
val javaTestLauncher: Provider<JavaLauncher> =
138+
project.providers.zip(testJvmSpec, normalizedTestJvm) { jvmSpec, testJvm ->
139+
// Only change test JVM if it's not the one we are running the gradle build with
140+
if ((jvmSpec as? SpecificInstallationToolchainSpec)?.javaHome == currentJavaHomePath.get()) {
141+
project.providers.provider<JavaLauncher?> { null }
142+
} else {
143+
// The provider always says that a value is present so we need to wrap it for proper error messages
144+
project.javaToolchains.launcherFor(jvmSpec).orElse(project.providers.provider {
145+
throw GradleException("Unable to find launcher for Java '$testJvm'. Does $TEST_JVM point to a JDK?")
146+
})
147+
}
148+
}.flatMap { it }.map { project.logger.info("testJvm launcher: ${it.executablePath}"); it }
80149

81150
private fun String.normalizeToJDKJavaHome(): Path {
82151
val javaHome = project.file(this).toPath().toRealPath()
83152
return if (javaHome.endsWith("jre")) javaHome.parent else javaHome
84153
}
85154

86-
private val Project.javaToolchains: JavaToolchainService get() =
87-
extensions.getByName("javaToolchains") as JavaToolchainService
155+
private fun Path.toToolchainSpec(): JavaToolchainSpec =
156+
// This is using internal APIs
157+
SpecificInstallationToolchainSpec(project.serviceOf<PropertyFactory>(), project.file(this))
158+
159+
private val Project.javaToolchains: JavaToolchainService
160+
get() =
161+
extensions.getByName("javaToolchains") as JavaToolchainService
88162
}

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pluginManagement {
2121

2222
plugins {
2323
id("com.gradle.develocity") version "4.2.2"
24+
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
2425
}
2526

2627
val isCI = providers.environmentVariable("CI")

0 commit comments

Comments
 (0)