Skip to content

Commit 770111c

Browse files
committed
chore: Moved test jvm constraint logic to convention plugin
1 parent 914c780 commit 770111c

File tree

7 files changed

+351
-228
lines changed

7 files changed

+351
-228
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import datadog.gradle.plugin.testJvmConstraints.ProvideJvmArgsOnJvmLauncherVersion
2+
import datadog.gradle.plugin.testJvmConstraints.TestJvmConstraintsExtension
3+
import datadog.gradle.plugin.testJvmConstraints.TestJvmJavaLauncher
4+
import datadog.gradle.plugin.testJvmConstraints.*
5+
import org.gradle.api.JavaVersion
6+
import org.gradle.api.provider.Provider
7+
import org.gradle.kotlin.dsl.*
8+
9+
plugins {
10+
java
11+
jacoco apply false
12+
}
13+
14+
val testJvmJavaLauncher = TestJvmJavaLauncher(project)
15+
16+
tasks.withType<Test>().configureEach {
17+
if (extensions.findByName(TestJvmConstraintsExtension.NAME) != null) {
18+
return@configureEach
19+
}
20+
21+
inputs.property("testJvm", providers.gradleProperty("testJvm"))
22+
23+
val extension = project.objects.newInstance<TestJvmConstraintsExtension>(name, project.objects, project.providers, project)
24+
inputs.property("${TestJvmConstraintsExtension.NAME}.allowReflectiveAccessToJdk", extension.allowReflectiveAccessToJdk)
25+
inputs.property("${TestJvmConstraintsExtension.NAME}.excludeJdk", extension.excludeJdk)
26+
inputs.property("${TestJvmConstraintsExtension.NAME}.forceJdk", extension.forceJdk)
27+
inputs.property("${TestJvmConstraintsExtension.NAME}.minJavaVersionForTests", extension.minJavaVersionForTests)
28+
inputs.property("${TestJvmConstraintsExtension.NAME}.maxJavaVersionForTests", extension.maxJavaVersionForTests)
29+
30+
extensions.add(TestJvmConstraintsExtension.NAME, extension)
31+
32+
configureTestJvm(extension)
33+
}
34+
35+
// TODO make this part of the testJvm test task extension
36+
fun Test.configureJvmArgs(
37+
applyFromVersion: JavaVersion,
38+
jvmArgsToApply: List<String>,
39+
additionalCondition: Provider<Boolean>? = null
40+
) {
41+
jvmArgumentProviders.add(
42+
ProvideJvmArgsOnJvmLauncherVersion(
43+
this,
44+
applyFromVersion,
45+
jvmArgsToApply,
46+
additionalCondition ?: project.providers.provider { true }
47+
)
48+
)
49+
}
50+
51+
fun Test.configureTestJvm(extension: TestJvmConstraintsExtension) {
52+
if (testJvmJavaLauncher.javaTestLauncher.isPresent) {
53+
javaLauncher = testJvmJavaLauncher.javaTestLauncher
54+
onlyIf("Allowed or forced JDK") {
55+
!extension.isJdkExcluded(testJvmJavaLauncher.normalizedTestJvm.get()) &&
56+
(extension.isJavaLauncherAllowed(testJvmJavaLauncher.javaTestLauncher.get()) ||
57+
extension.isJdkForced(testJvmJavaLauncher.normalizedTestJvm.get()))
58+
}
59+
60+
// TODO refactor out ?
61+
// Disable jacoco for additional 'testJvm' tests to speed things up a bit
62+
extensions.configure<JacocoTaskExtension> {
63+
val hasCoverage: Boolean by project.extra
64+
// TODO read enabled ?
65+
if (hasCoverage) {
66+
isEnabled = false
67+
}
68+
}
69+
} else {
70+
onlyIf("Is current Daemon JVM allowed") {
71+
extension.isJavaVersionAllowed(JavaVersion.current())
72+
}
73+
}
74+
75+
// temporary workaround when using Java16+: some tests require reflective access to java.lang/java.util
76+
configureJvmArgs(
77+
JavaVersion.VERSION_16,
78+
listOf(
79+
"--add-opens=java.base/java.lang=ALL-UNNAMED",
80+
"--add-opens=java.base/java.util=ALL-UNNAMED"
81+
),
82+
extension.allowReflectiveAccessToJdk
83+
)
84+
}
85+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.api.JavaVersion
4+
import org.gradle.api.provider.Provider
5+
import org.gradle.api.tasks.Input
6+
import org.gradle.api.tasks.Internal
7+
import org.gradle.api.tasks.testing.Test
8+
import org.gradle.process.CommandLineArgumentProvider
9+
10+
class ProvideJvmArgsOnJvmLauncherVersion(
11+
@get:Internal
12+
val test: Test,
13+
14+
@get:Input
15+
val applyFromVersion: JavaVersion,
16+
17+
@get:Input
18+
val jvmArgsToApply: List<String>,
19+
20+
@get:Input
21+
val additionalCondition: Provider<Boolean>
22+
) : CommandLineArgumentProvider {
23+
24+
override fun asArguments(): Iterable<String> {
25+
val launcherVersion = test.javaLauncher
26+
.map { JavaVersion.toVersion(it.metadata.languageVersion.asInt()) }
27+
.orElse(JavaVersion.current())
28+
.get()
29+
30+
return if (launcherVersion.isCompatibleWith(applyFromVersion) && additionalCondition.getOrElse(true)) {
31+
jvmArgsToApply
32+
} else {
33+
emptyList()
34+
}
35+
}
36+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.api.JavaVersion
4+
import org.gradle.api.Project
5+
import org.gradle.api.model.ObjectFactory
6+
import org.gradle.api.provider.ProviderFactory
7+
import org.gradle.kotlin.dsl.extra
8+
import org.gradle.kotlin.dsl.listProperty
9+
import org.gradle.kotlin.dsl.property
10+
import javax.inject.Inject
11+
12+
abstract class TestJvmConstraintsExtension @Inject constructor(
13+
private val taskName: String,
14+
private val objects: ObjectFactory,
15+
private val providers: ProviderFactory,
16+
private val project: Project,
17+
) {
18+
val minJavaVersionForTests = objects.property<JavaVersion>()
19+
.convention(
20+
providers.provider { project.extra["${taskName}MinJavaVersionForTests"] as JavaVersion }.orElse(
21+
providers.provider { project.extra["minJavaVersionForTests"] as? JavaVersion }
22+
)
23+
)
24+
val maxJavaVersionForTests = objects.property<JavaVersion>()
25+
.convention(
26+
providers.provider { project.extra["${taskName}MaxJavaVersionForTests"] as JavaVersion }.orElse(
27+
providers.provider { project.extra["maxJavaVersionForTests"] as? JavaVersion }
28+
)
29+
)
30+
val forceJdk = objects.listProperty<String>().convention(emptyList())
31+
.convention(providers.provider {
32+
@Suppress("UNCHECKED_CAST")
33+
project.extra["forceJdk"] as? List<String> ?: emptyList()
34+
})
35+
val excludeJdk = objects.listProperty<String>().convention(emptyList())
36+
.convention(providers.provider {
37+
@Suppress("UNCHECKED_CAST")
38+
project.extra["excludeJdk"] as? List<String> ?: emptyList()
39+
})
40+
val allowReflectiveAccessToJdk = objects.property<Boolean>().convention(
41+
providers.provider { project.extra["allowReflectiveAccessToJdk"] as? Boolean ?: false }
42+
)
43+
44+
companion object {
45+
const val NAME = "jvmConstraint"
46+
}
47+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.api.JavaVersion
4+
import org.gradle.api.logging.Logging
5+
import org.gradle.jvm.toolchain.JavaLauncher
6+
7+
private val logger = Logging.getLogger("TestJvmConstraintsUtils")
8+
9+
private fun TestJvmConstraintsExtension.isJavaVersionAllowedForProperty(currentJvmVersion: JavaVersion): Boolean {
10+
val definedMin = minJavaVersionForTests.isPresent
11+
val definedMax = maxJavaVersionForTests.isPresent
12+
13+
if (definedMin && (minJavaVersionForTests.get()) > currentJvmVersion) {
14+
logger.info("isJavaVersionAllowedForProperty returns false b/o minProp=${minJavaVersionForTests.get()} is defined and greater than version=$currentJvmVersion")
15+
return false
16+
}
17+
18+
if (definedMax && (maxJavaVersionForTests.get()) < currentJvmVersion) {
19+
logger.info("isJavaVersionAllowedForProperty returns false b/o maxProp=${maxJavaVersionForTests.get()} is defined and lower than version=$currentJvmVersion")
20+
return false
21+
}
22+
23+
return true
24+
}
25+
26+
internal fun TestJvmConstraintsExtension.isJavaVersionAllowed(version: JavaVersion): Boolean {
27+
return isJavaVersionAllowedForProperty(version)
28+
}
29+
30+
/**
31+
* Convenience method to call [isJavaVersionAllowed]
32+
*/
33+
internal fun TestJvmConstraintsExtension.isJavaLauncherAllowed(javaLauncher: JavaLauncher): Boolean {
34+
val launcherVersion = JavaVersion.toVersion(javaLauncher.metadata.languageVersion.asInt())
35+
return isJavaVersionAllowed(launcherVersion)
36+
}
37+
38+
internal fun TestJvmConstraintsExtension.isJdkForced(javaName: String): Boolean {
39+
return forceJdk.get().any { it.equals(javaName, ignoreCase = true) }
40+
}
41+
42+
internal fun TestJvmConstraintsExtension.isJdkExcluded(javaName: String): Boolean {
43+
return excludeJdk.get().any { it.equals(javaName, ignoreCase = true) }
44+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package datadog.gradle.plugin.testJvmConstraints
2+
3+
import org.gradle.kotlin.dsl.support.serviceOf
4+
import org.gradle.api.GradleException
5+
import org.gradle.api.Project
6+
import org.gradle.jvm.toolchain.JavaLauncher
7+
import org.gradle.jvm.toolchain.JavaToolchainService
8+
import java.nio.file.Files
9+
import java.nio.file.Path
10+
import java.nio.file.Paths
11+
12+
class TestJvmJavaLauncher(val project: Project) {
13+
val currentJavaHomePath = System.getProperty("java.home").normalizeToJDKJavaHome()
14+
val normalizedTestJvm = project.providers.gradleProperty("testJvm").map { testJvm ->
15+
if (testJvm.isBlank()) {
16+
throw GradleException("testJvm property is blank")
17+
}
18+
19+
// "stable" is calculated as the largest X found in JAVA_X_HOME
20+
if (testJvm == "stable") {
21+
val javaVersions = project.providers.environmentVariablesPrefixedBy("JAVA_").map { javaHomes ->
22+
javaHomes
23+
.filter { it.key.matches(Regex("^JAVA_[0-9]+_HOME$")) }
24+
.map { Regex("^JAVA_(\\d+)_HOME$").find(it.key)!!.groupValues[1].toInt() }
25+
}.get()
26+
27+
if (javaVersions.isEmpty()) {
28+
throw GradleException("No valid JAVA_X_HOME environment variables found.")
29+
}
30+
31+
javaVersions.max().toString()
32+
} else {
33+
testJvm
34+
}
35+
}.map { project.logger.info("normalized testJvm: $it"); it }
36+
37+
val testJvmHomePath = normalizedTestJvm.map {
38+
if (Files.exists(Paths.get(it))) {
39+
it.normalizeToJDKJavaHome()
40+
} else {
41+
val matcher = Regex("([a-zA-Z]*)([0-9]+)").find(it)
42+
if (matcher == null) {
43+
throw GradleException("Unable to find launcher for Java '$it'. It needs to match '([a-zA-Z]*)([0-9]+)'.")
44+
}
45+
val testJvmEnv = "JAVA_${it}_HOME"
46+
val testJvmHome = project.providers.environmentVariable(testJvmEnv).orNull
47+
if (testJvmHome == null) {
48+
throw GradleException("Unable to find launcher for Java '$it'. Have you set '$testJvmEnv'?")
49+
}
50+
51+
testJvmHome.normalizeToJDKJavaHome()
52+
}
53+
}.map { project.logger.info("testJvm home path: $it"); it }
54+
55+
val javaTestLauncher = project.providers.zip(testJvmHomePath, normalizedTestJvm) { testJvmHome, testJvm ->
56+
// Only change test JVM if it's not the one we are running the gradle build with
57+
if (currentJavaHomePath == testJvmHome) {
58+
project.providers.provider<JavaLauncher?> { null }
59+
} else {
60+
// This is using internal APIs
61+
val jvmSpec = org.gradle.jvm.toolchain.internal.SpecificInstallationToolchainSpec(
62+
project.serviceOf<org.gradle.api.internal.provider.PropertyFactory>(),
63+
project.file(testJvmHome)
64+
)
65+
66+
// The provider always says that a value is present so we need to wrap it for proper error messages
67+
project.javaToolchains.launcherFor(jvmSpec).orElse(project.providers.provider {
68+
throw GradleException("Unable to find launcher for Java $testJvm. Does '$testJvmHome' point to a JDK?")
69+
})
70+
}
71+
}.flatMap { it }.map { project.logger.info("testJvm launcher: ${it.executablePath}"); it }
72+
73+
private fun String.normalizeToJDKJavaHome(): Path {
74+
val javaHome = project.file(this).toPath().toRealPath()
75+
return if (javaHome.endsWith("jre")) javaHome.parent else javaHome
76+
}
77+
78+
private val Project.javaToolchains: JavaToolchainService get() =
79+
extensions.getByName("javaToolchains") as JavaToolchainService
80+
}

0 commit comments

Comments
 (0)