You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by vl...@apache.org on 2023/06/24 15:49:10 UTC

[jmeter] branch master updated: chore: use Gradle toolchains for JDK provisioning

This is an automated email from the ASF dual-hosted git repository.

vladimirsitnikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git


The following commit(s) were added to refs/heads/master by this push:
     new 1ee08f4e28 chore: use Gradle toolchains for JDK provisioning
1ee08f4e28 is described below

commit 1ee08f4e28d597715783c2766fb8a73f527ea60b
Author: Vladimir Sitnikov <si...@gmail.com>
AuthorDate: Sat Jun 17 14:28:54 2023 +0300

    chore: use Gradle toolchains for JDK provisioning
    
    It enables automatic JDK provisioning (e.g. download), and it enables
    using different JDKs for executing Gradle and for building JMeter
    
    Java version can be specified when building with -PjdkVersion=11
    
    You could use ./gradlew -q javaToolchains to list the detected toolchains.
    
    See https://docs.gradle.org/8.0/userguide/toolchains.html#sec:consuming
    
    Fixes https://github.com/apache/jmeter/issues/5986
---
 .github/workflows/main.yml                         | 18 ++++++++--
 .github/workflows/matrix.js                        | 29 ++++++++++-----
 .github/workflows/matrix_builder.js                | 23 +++++++++---
 README.md                                          | 18 ++++++++--
 build-logic/basics/build.gradle.kts                |  4 +++
 .../main/kotlin/ToolchainProperties.kt}            | 23 ++++++++++--
 .../basics/src/main/kotlin/configureToolchain.kt   | 41 ++++++++++++++++++++++
 build-logic/build-parameters/build.gradle.kts      | 29 +++++++++++++++
 .../src/main/kotlin/build-logic.groovy.gradle.kts  | 12 +++++++
 .../src/main/kotlin/build-logic.java.gradle.kts    | 23 +++++++++---
 .../src/main/kotlin/build-logic.kotlin.gradle.kts  | 18 +++++++++-
 .../main/kotlin/build-logic.test-base.gradle.kts   | 27 ++++++++++----
 .../main/kotlin/build-logic.root-build.gradle.kts  |  6 ++++
 .../kotlin/build-logic.checkerframework.gradle.kts |  6 ++--
 .../src/main/kotlin/build-logic.style.gradle.kts   |  2 +-
 gradle.md                                          |  2 +-
 settings.gradle.kts                                |  5 +++
 src/bom-testing/build.gradle.kts                   |  1 +
 src/components/build.gradle.kts                    | 21 ++---------
 src/dist/build.gradle.kts                          |  3 ++
 xdocs/changes.xml                                  |  1 +
 21 files changed, 257 insertions(+), 55 deletions(-)

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b25c8a08a2..7577181def 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -46,10 +46,19 @@ jobs:
     - uses: actions/checkout@v3
       with:
         fetch-depth: 50
-    - name: Set up Java ${{ matrix.java_version }}, ${{ matrix.java_distribution }}
+    - name: Set up Java ${{ matrix.java_version }}, oracle
+      if: ${{ matrix.oracle_java_website != '' }}
+      uses: oracle-actions/setup-java@1611a647972adb8b04779be3529a044d650fd510 # v1
+      with:
+        website: ${{ matrix.oracle_java_website }}
+        release: ${{ matrix.java_version }}
+    - name: Set up Java 17 and ${{ matrix.non_ea_java_version }}, ${{ matrix.java_distribution }}
       uses: actions/setup-java@v3
       with:
-        java-version: ${{ matrix.java_version }}
+        # The latest one will be the default, so we use Java 17 for launching Gradle
+        java-version: |
+          ${{ matrix.non_ea_java_version }}
+          17
         distribution: ${{ matrix.java_distribution }}
         architecture: x64
     - name: Steps to reproduce
@@ -68,6 +77,11 @@ jobs:
         properties: |
           testExtraJvmArgs=${{ matrix.testExtraJvmArgs }}
           testDisableCaching=${{ matrix.testDisableCaching }}
+          jdkBuildVersion=17
+          jdkTestVersion=${{ matrix.java_version }}
+          jdkTestVendor=${{ matrix.java_vendor }}
+          # We provision JDKs with GitHub Actions for caching purposes, so Gradle should rather fail in case JDK is not found
+          org.gradle.java.installations.auto-download=false
       env:
         _JAVA_OPTIONS: ${{ matrix.extraJvmArgs }}
         GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }}
diff --git a/.github/workflows/matrix.js b/.github/workflows/matrix.js
index 20c32abeb7..1a75468db3 100644
--- a/.github/workflows/matrix.js
+++ b/.github/workflows/matrix.js
@@ -8,16 +8,18 @@ const matrix = new MatrixBuilder();
 matrix.addAxis({
   name: 'java_distribution',
   values: [
-    {value: 'corretto', weight: 1},
-    {value: 'liberica', weight: 1},
-    {value: 'microsoft', weight: 1},
-    {value: 'oracle', weight: 1},
-    {value: 'semeru', weight: 4},
-    {value: 'temurin', weight: 1},
-    {value: 'zulu', weight: 1},
+    {value: 'corretto', vendor: 'amazon', weight: 1},
+    {value: 'liberica', vendor: 'bellsoft', weight: 1},
+    {value: 'microsoft', vendor: 'microsoft', weight: 1},
+    {value: 'oracle', vendor: 'oracle', weight: 1},
+    {value: 'semeru', vendor: 'ibm', weight: 4},
+    {value: 'temurin', vendor: 'eclipse', weight: 1},
+    {value: 'zulu', vendor: 'azul', weight: 1},
   ]
 });
 
+const eaJava = '21';
+
 matrix.addAxis({
   name: 'java_version',
   // Strings allow versions like 18-ea
@@ -25,6 +27,7 @@ matrix.addAxis({
     '8',
     '11',
     '17',
+    eaJava,
   ]
 });
 
@@ -74,10 +77,13 @@ matrix.setNamePattern(['java_version', 'java_distribution', 'hash', 'os', 'tz',
 
 // Semeru uses OpenJ9 jit which has no option for making hash codes the same
 matrix.exclude({java_distribution: {value: 'semeru'}, hash: {value: 'same'}});
+// Semeru 8 fails when Semeru 17 is on the PATH (we use 17 for build)
+matrix.exclude({java_distribution: {value: 'semeru'}, java_version: '8'});
 // Microsoft Java has no distribution for 8
 matrix.exclude({java_distribution: {value: 'microsoft'}, java_version: '8'});
 // Oracle JDK is only supported for JDK 17 and later
 matrix.exclude({java_distribution: {value: 'oracle'}, java_version: ['8', '11']});
+matrix.imply({java_version: eaJava}, {java_distribution: {value: 'oracle'}})
 // Ensure at least one job with "same" hashcode exists
 matrix.generateRow({hash: {value: 'same'}});
 // Ensure at least one Windows and at least one Linux job is present (macOS is almost the same as Linux)
@@ -90,6 +96,8 @@ matrix.generateRow({java_version: "8"});
 matrix.generateRow({java_version: "11"});
 // Ensure there will be at least one job with Java 17
 matrix.generateRow({java_version: "17"});
+// Ensure there will be at least one job with Java EA
+matrix.generateRow({java_version: eaJava});
 const include = matrix.generateRows(process.env.MATRIX_JOBS || 5);
 if (include.length === 0) {
   throw new Error('Matrix list is empty');
@@ -124,9 +132,14 @@ include.forEach(v => {
   jvmArgs.push(`-Duser.country=${v.locale.country}`);
   jvmArgs.push(`-Duser.language=${v.locale.language}`);
   v.java_distribution = v.java_distribution.value;
+  v.java_vendor = v.java_distribution.vendor;
+  if (v.java_distribution === 'oracle') {
+      v.oracle_java_website = v.java_version === eaJava ? 'jdk.java.net' : 'oracle.com';
+  }
+  v.non_ea_java_version = v.java_version === eaJava ? '' : v.java_version;
   if (v.java_distribution !== 'semeru' && Math.random() > 0.5) {
     // The following options randomize instruction selection in JIT compiler
-    // so it might reveal missing synchronization in TestNG code
+    // so it might reveal missing synchronization
     v.name += ', stress JIT';
     v.testDisableCaching = 'JIT randomization should not be cached';
     jvmArgs.push('-XX:+UnlockDiagnosticVMOptions');
diff --git a/.github/workflows/matrix_builder.js b/.github/workflows/matrix_builder.js
index fd765117e8..0c17190ede 100644
--- a/.github/workflows/matrix_builder.js
+++ b/.github/workflows/matrix_builder.js
@@ -18,7 +18,7 @@ class Axis {
     }
     if (Array.isArray(filter)) {
       // e.g. row={os: 'windows'}; filter=[{os: 'linux'}, {os: 'linux'}]
-      return filter.find(v => Axis.matches(row, v));
+      return filter.some(v => Axis.matches(row, v));
     }
     if (typeof filter === 'object') {
       // e.g. row={jdk: {name: 'openjdk', version: 8}}; filter={jdk: {version: 8}}
@@ -68,6 +68,7 @@ class MatrixBuilder {
     this.duplicates = {};
     this.excludes = [];
     this.includes = [];
+    this.implications = [];
     this.failOnUnsatisfiableFilters = false;
   }
 
@@ -80,13 +81,23 @@ class MatrixBuilder {
   }
 
   /**
-   * Specifies exclude filter (e.g. exclude a forbidden combination)
+   * Specifies exclude filter (e.g. exclude a forbidden combination).
    * @param filter
    */
   exclude(filter) {
     this.excludes.push(filter);
   }
 
+  /**
+   * Adds implication like `antecedent -> consequent`.
+   * In other words, if `antecedent` holds, then `consequent` must also hold.
+   * @param antecedent
+   * @param consequent
+   */
+  imply(antecedent, consequent) {
+    this.implications.push({antecedent: antecedent, consequent: consequent});
+  }
+
   addAxis({name, title, values}) {
     const axis = new Axis({name, title, values});
     this.axes.push(axis);
@@ -104,8 +115,10 @@ class MatrixBuilder {
    * @returns {boolean}
    */
   matches(row) {
-    return (this.excludes.length === 0 || !this.excludes.find(f => Axis.matches(row, f))) &&
-           (this.includes.length === 0 || this.includes.find(f => Axis.matches(row, f)));
+    return (this.excludes.length === 0 || !this.excludes.some(f => Axis.matches(row, f))) &&
+           (this.includes.length === 0 || this.includes.some(f => Axis.matches(row, f))) &&
+           (this.implications.length === 0 || (
+               this.implications.every(i => !Axis.matches(row, i.antecedent) || Axis.matches(row, i.consequent))));
   }
 
   failOnUnsatisfiableFilters(value) {
@@ -125,7 +138,7 @@ class MatrixBuilder {
     let res;
     if (filter) {
       // If matching row already exists, no need to generate more
-      res = this.rows.find(v => Axis.matches(v, filter));
+      res = this.rows.some(v => Axis.matches(v, filter));
       if (res) {
         return res;
       }
diff --git a/README.md b/README.md
index 1255834304..711bc5f9b9 100644
--- a/README.md
+++ b/README.md
@@ -175,7 +175,12 @@ systemProp.https.proxyPassword=your_password
 
 ### Test builds
 
-JMeter is built using Gradle.
+JMeter is built using Gradle, and it uses [Gradle's Toolchains for JVM projects](https://docs.gradle.org/current/userguide/toolchains.html)
+for provisioning JDKs. It means the code would search for the needed JDKs locally, or download them
+if they are not found.
+
+By default, the code would use JDK 17 for build purposes, however it would set the target release to 8,
+so the resulting artifacts would be compatible with Java 8.
 
 The following command builds and tests JMeter:
 
@@ -183,6 +188,15 @@ The following command builds and tests JMeter:
 ./gradlew build
 ```
 
+If you want to use a custom JDK for building you can set `-PjdkBuildVersion=11`,
+and you can select `-PjdkTestVersion=21` if you want to use a different JDK for testing.
+
+You can list the available build parameters by executing
+
+```sh
+./gradlew parameters
+```
+
 If the system does not have a GUI display then:
 
 ```sh
@@ -196,7 +210,7 @@ The following command would compile the application and enable you to run `jmete
 from the `bin` directory.
 
 > **Note** that it completely refreshes `lib/` contents,
-so it would remove custom plugins should you have them installed.
+so it would remove custom plugins should you have them installed to `lib/`. However, it would keep `lib/ext/` plugins intact.
 
 ```sh
 ./gradlew createDist
diff --git a/build-logic/basics/build.gradle.kts b/build-logic/basics/build.gradle.kts
index d8c115622b..a6dfaea589 100644
--- a/build-logic/basics/build.gradle.kts
+++ b/build-logic/basics/build.gradle.kts
@@ -18,3 +18,7 @@
 plugins {
     id("build-logic.kotlin-dsl-gradle-plugin")
 }
+
+dependencies {
+    api(projects.buildParameters)
+}
diff --git a/build-logic/basics/build.gradle.kts b/build-logic/basics/src/main/kotlin/ToolchainProperties.kt
similarity index 51%
copy from build-logic/basics/build.gradle.kts
copy to build-logic/basics/src/main/kotlin/ToolchainProperties.kt
index d8c115622b..d5b0c201e3 100644
--- a/build-logic/basics/build.gradle.kts
+++ b/build-logic/basics/src/main/kotlin/ToolchainProperties.kt
@@ -15,6 +15,23 @@
  * limitations under the License.
  */
 
-plugins {
-    id("build-logic.kotlin-dsl-gradle-plugin")
-}
+import buildparameters.BuildParametersExtension
+import org.gradle.api.JavaVersion
+
+class ToolchainProperties(
+    val version: Int,
+    val vendor: String?,
+    val implementation: String?,
+)
+
+val BuildParametersExtension.buildJdk: ToolchainProperties?
+    get() = jdkBuildVersion.takeIf { it != 0 }
+        ?.let { ToolchainProperties(it, jdkBuildVendor.orNull, jdkBuildImplementation.orNull) }
+
+val BuildParametersExtension.buildJdkVersion: Int
+    get() = buildJdk?.version ?: JavaVersion.current().majorVersion.toInt()
+
+val BuildParametersExtension.testJdk: ToolchainProperties?
+    get() = jdkTestVersion.orNull?.takeIf { it != 0 }
+        ?.let { ToolchainProperties(it, jdkTestVendor.orNull, jdkTestImplementation.orNull) }
+        ?: buildJdk
diff --git a/build-logic/basics/src/main/kotlin/configureToolchain.kt b/build-logic/basics/src/main/kotlin/configureToolchain.kt
new file mode 100644
index 0000000000..d797e7c4d6
--- /dev/null
+++ b/build-logic/basics/src/main/kotlin/configureToolchain.kt
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.gradle.api.provider.Provider
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+import org.gradle.jvm.toolchain.JavaLauncher
+import org.gradle.jvm.toolchain.JavaToolchainService
+import org.gradle.jvm.toolchain.JavaToolchainSpec
+import org.gradle.jvm.toolchain.JvmImplementation
+import org.gradle.jvm.toolchain.JvmVendorSpec
+
+fun JavaToolchainService.launcherFor(jdk: ToolchainProperties): Provider<JavaLauncher> = launcherFor {
+    configureToolchain(jdk)
+}
+
+fun JavaToolchainSpec.configureToolchain(jdk: ToolchainProperties?) {
+    if (jdk == null) {
+        return
+    }
+    languageVersion.set(JavaLanguageVersion.of(jdk.version))
+    jdk.vendor?.let {
+        vendor.set(JvmVendorSpec.matching(it))
+    }
+    if (jdk.implementation.equals("J9", ignoreCase = true)) {
+        implementation.set(JvmImplementation.J9)
+    }
+}
diff --git a/build-logic/build-parameters/build.gradle.kts b/build-logic/build-parameters/build.gradle.kts
index b160323b60..bd0c6c6d80 100644
--- a/build-logic/build-parameters/build.gradle.kts
+++ b/build-logic/build-parameters/build.gradle.kts
@@ -29,10 +29,39 @@ buildParameters {
         defaultValue.set(true)
         description.set("Add mavenLocal() to repositories")
     }
+    bool("enableJavaFx") {
+        defaultValue.set(false)
+        description.set("Build and include classes that depend on JavaFX. Disabled by default since JavaFX is not included in the default Java distributions")
+    }
     bool("coverage") {
         defaultValue.set(false)
         description.set("Collect test coverage")
     }
+    integer("targetJavaVersion") {
+        defaultValue.set(8)
+        mandatory.set(true)
+        description.set("Java version for source and target compatibility")
+    }
+    integer("jdkBuildVersion") {
+        defaultValue.set(17)
+        mandatory.set(true)
+        description.set("JDK version to use for building JMeter. If the value is 0, then the current Java is used. (see https://docs.gradle.org/8.0/userguide/toolchains.html#sec:consuming)")
+    }
+    string("jdkBuildVendor") {
+        description.set("JDK vendor to use building JMeter (see https://docs.gradle.org/8.0/userguide/toolchains.html#sec:vendors)")
+    }
+    string("jdkBuildImplementation") {
+        description.set("Vendor-specific virtual machine implementation to use building JMeter (see https://docs.gradle.org/8.0/userguide/toolchains.html#selecting_toolchains_by_virtual_machine_implementation)")
+    }
+    integer("jdkTestVersion") {
+        description.set("JDK version to use for testing JMeter. If the value is 0, then the current Java is used. (see https://docs.gradle.org/8.0/userguide/toolchains.html#sec:consuming)")
+    }
+    string("jdkTestVendor") {
+        description.set("JDK vendor to use testing JMeter (see https://docs.gradle.org/8.0/userguide/toolchains.html#sec:vendors)")
+    }
+    string("jdkTestImplementation") {
+        description.set("Vendor-specific virtual machine implementation to use testing JMeter (see https://docs.gradle.org/8.0/userguide/toolchains.html#selecting_toolchains_by_virtual_machine_implementation)")
+    }
     bool("spotbugs") {
         defaultValue.set(false)
         description.set("Run SpotBugs verifications")
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.groovy.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.groovy.gradle.kts
index 976a9a3cd8..1acd72cc94 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.groovy.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.groovy.gradle.kts
@@ -15,8 +15,20 @@
  * limitations under the License.
  */
 
+import com.github.vlsi.gradle.dsl.configureEach
+
 plugins {
     id("java")
     id("groovy")
     id("build-logic.test-spock")
+    id("com.github.vlsi.gradle-extensions")
+    id("build-logic.build-params")
+}
+
+tasks.configureEach<GroovyCompile> {
+    buildParameters.testJdk?.let {
+        javaLauncher.convention(javaToolchains.launcherFor(it))
+    }
+    // Support jdk-release to configure the target Java release when compiling the bytecode
+    // See https://issues.apache.org/jira/browse/GROOVY-11105
 }
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts
index df91b33dd1..01ad87f1dc 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.java.gradle.kts
@@ -33,8 +33,9 @@ plugins {
 }
 
 java {
-    sourceCompatibility = JavaVersion.VERSION_1_8
-    targetCompatibility = JavaVersion.VERSION_1_8
+    toolchain {
+        configureToolchain(buildParameters.buildJdk)
+    }
     consistentResolution {
         useCompileClasspathVersions()
     }
@@ -44,11 +45,25 @@ tasks.configureEach<JavaCompile> {
     // Use --release=8 for Java 10+ so the generated bytecode does not include methods introduced in Java 9+
     options.release.set(
         provider {
-            8.takeIf { javaCompiler.get().metadata.languageVersion.asInt() > 9 }
+            buildParameters.targetJavaVersion.takeIf {
+                javaCompiler.get().metadata.languageVersion.asInt() > 9
+            }
         }
     )
 }
 
+tasks.configureEach<JavaExec> {
+    buildParameters.testJdk?.let {
+        javaLauncher.convention(javaToolchains.launcherFor(it))
+    }
+}
+
+tasks.configureEach<Checkstyle> {
+    buildParameters.buildJdk?.let {
+        javaLauncher.convention(javaToolchains.launcherFor(it))
+    }
+}
+
 dependencies {
     findProject(":src:bom")?.let {
         api(platform(it))
@@ -116,7 +131,7 @@ tasks.configureEach<Javadoc> {
         val lastEditYear: String by rootProject.extra
         bottom =
             "Copyright &copy; 1998-$lastEditYear Apache Software Foundation. All Rights Reserved."
-        if (JavaVersion.current() >= JavaVersion.VERSION_1_9) {
+        if (buildParameters.buildJdkVersion > 8) {
             addBooleanOption("html5", true)
             links("https://docs.oracle.com/en/java/javase/11/docs/api/")
         } else {
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
index 628bbda55e..ca509a7c33 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.kotlin.gradle.kts
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("java-library")
+    id("build-logic.build-params")
     id("build-logic.java")
     id("build-logic.test-base")
     id("com.github.autostyle")
@@ -36,6 +37,9 @@ kotlin {
     if (props.bool("kotlin.explicitApi", default = true)) {
         explicitApi()
     }
+    jvmToolchain {
+        configureToolchain(buildParameters.buildJdk)
+    }
 }
 
 tasks.configureEach<KotlinCompile> {
@@ -44,7 +48,19 @@ tasks.configureEach<KotlinCompile> {
             apiVersion = "kotlin.api".v
         }
         freeCompilerArgs += "-Xjvm-default=all"
-        jvmTarget = java.targetCompatibility.toString()
+        val jdkRelease = buildParameters.targetJavaVersion.let {
+            when {
+                it < 9 -> "1.8"
+                else -> it.toString()
+            }
+        }
+        // jdk-release requires Java 9+
+        buildParameters.buildJdkVersion
+            .takeIf { it > 8 }
+            ?.let {
+                freeCompilerArgs += "-Xjdk-release=$jdkRelease"
+            }
+        kotlinOptions.jvmTarget = jdkRelease
     }
 }
 
diff --git a/build-logic/jvm/src/main/kotlin/build-logic.test-base.gradle.kts b/build-logic/jvm/src/main/kotlin/build-logic.test-base.gradle.kts
index 1f92a9c96c..830174dca2 100644
--- a/build-logic/jvm/src/main/kotlin/build-logic.test-base.gradle.kts
+++ b/build-logic/jvm/src/main/kotlin/build-logic.test-base.gradle.kts
@@ -17,13 +17,12 @@
 
 import com.github.vlsi.gradle.dsl.configureEach
 import com.github.vlsi.gradle.properties.dsl.props
-import org.gradle.api.JavaVersion
 import org.gradle.api.tasks.testing.Test
 import org.gradle.api.tasks.testing.logging.TestExceptionFormat
-import org.gradle.kotlin.dsl.project
 
 plugins {
     id("java-library")
+    id("build-logic.build-params")
 }
 
 dependencies {
@@ -37,6 +36,9 @@ tasks.configureEach<Test> {
         exceptionFormat = TestExceptionFormat.FULL
         showStandardStreams = true
     }
+    buildParameters.testJdk?.let {
+        javaLauncher.convention(javaToolchains.launcherFor(it))
+    }
     // Pass the property to tests
     fun passProperty(name: String, default: String? = null) {
         val value = System.getProperty(name) ?: default
@@ -57,9 +59,22 @@ tasks.configureEach<Test> {
     }
     passProperty("java.awt.headless")
     passProperty("skip.test_TestDNSCacheManager.testWithCustomResolverAnd1Server")
-    // Spock tests use cglib proxies that access ClassLoader.defineClass reflectively
+    // Enable testing ByteBuddy with EA Java versions
+    passProperty("net.bytebuddy.experimental", "true")
+    // Spock tests use cglib proxies that access ClassLoader.defineClass reflectively,
+    // So we pass --add-opens
     // See https://github.com/apache/jmeter/pull/5763
-    if (JavaVersion.current().isJava9Compatible) {
-        jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
-    }
+    jvmArgumentProviders += AddOpensArgumentsProvider(javaLauncher.map { it.metadata.languageVersion })
+}
+
+class AddOpensArgumentsProvider(
+    @Input val javaLanguageVersion: Provider<JavaLanguageVersion>
+): CommandLineArgumentProvider {
+    override fun asArguments(): Iterable<String> =
+        if (javaLanguageVersion.get().asInt() <= 8) {
+            // Jave 1.8 does not need add-opens, so we omit it
+            listOf()
+        } else {
+            listOf("--add-opens=java.base/java.lang=ALL-UNNAMED")
+        }
 }
diff --git a/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts b/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts
index 7fcb96ff8d..fd2f72337e 100644
--- a/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts
+++ b/build-logic/root-build/src/main/kotlin/build-logic.root-build.gradle.kts
@@ -66,3 +66,9 @@ if (buildParameters.coverage) {
         }
     }
 }
+
+tasks.register("parameters") {
+    group = HelpTasksPlugin.HELP_GROUP
+    description = "Displays the supported build parameters."
+    dependsOn(gradle.includedBuild("build-logic").task(":build-parameters:parameters"))
+}
diff --git a/build-logic/verification/src/main/kotlin/build-logic.checkerframework.gradle.kts b/build-logic/verification/src/main/kotlin/build-logic.checkerframework.gradle.kts
index 1b682cd747..88ac070c2e 100644
--- a/build-logic/verification/src/main/kotlin/build-logic.checkerframework.gradle.kts
+++ b/build-logic/verification/src/main/kotlin/build-logic.checkerframework.gradle.kts
@@ -15,10 +15,10 @@
  * limitations under the License.
  */
 
-import org.gradle.api.JavaVersion
 import org.gradle.kotlin.dsl.dependencies
 
 plugins {
+    id("build-logic.build-params")
     id("org.checkerframework")
 }
 
@@ -28,14 +28,14 @@ dependencies {
         ?.let {
             val checkerframeworkVersion = it.get()
             "checkerFramework"("org.checkerframework:checker:$checkerframeworkVersion")
-            if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
+            if (buildParameters.buildJdkVersion == 8) {
                 // only needed for JDK 8
                 "checkerFrameworkAnnotatedJDK"("org.checkerframework:jdk8:$checkerframeworkVersion")
             }
         } ?: run {
         val checkerframeworkVersion = "3.34.0"
         "checkerFramework"("org.checkerframework:checker:$checkerframeworkVersion")
-        if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
+        if (buildParameters.buildJdkVersion == 8) {
             // only needed for JDK 8
             "checkerFrameworkAnnotatedJDK"("org.checkerframework:jdk8:$checkerframeworkVersion")
         }
diff --git a/build-logic/verification/src/main/kotlin/build-logic.style.gradle.kts b/build-logic/verification/src/main/kotlin/build-logic.style.gradle.kts
index c36a060738..fa56eaa38f 100644
--- a/build-logic/verification/src/main/kotlin/build-logic.style.gradle.kts
+++ b/build-logic/verification/src/main/kotlin/build-logic.style.gradle.kts
@@ -28,7 +28,7 @@ if (!buildParameters.skipAutostyle) {
 
 val skipCheckstyle = buildParameters.skipCheckstyle || run {
     logger.info("Checkstyle requires Java 11+")
-    !JavaVersion.current().isJava11Compatible
+    buildParameters.buildJdkVersion < 11
 }
 
 if (!skipCheckstyle) {
diff --git a/gradle.md b/gradle.md
index 048b50580e..24b0aed5e1 100644
--- a/gradle.md
+++ b/gradle.md
@@ -24,7 +24,7 @@ Useful commands (`gw` comes from https://github.com/dougborg/gdub, otherwise `./
 ## List available build parameters
 
       # List all build parameters
-      gw :build-logic:build-parameters:parameters
+      gw parameters
 
 ## Cleaning build directories
 
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1c613ef91b..b28f8fce77 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -27,6 +27,7 @@ pluginManagement {
 plugins {
     id("com.gradle.enterprise") version "3.13.2"
     id("com.gradle.common-custom-user-data-gradle-plugin") version "1.10"
+    id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
 }
 
 dependencyResolutionManagement {
@@ -164,6 +165,10 @@ val expectedSha512 = mapOf(
         to "okhttp-4.1.0.jar",
     "93E7A41BE44CC17FB500EA5CD84D515204C180AEC934491D11FC6A71DAEA761FB0EECEF865D6FD5C3D88AAF55DCE3C2C424BE5BA5D43BEBF48D05F1FA63FA8A7"
         to "okio-2.2.2.jar",
+    "B9F87DECE28EABCCEDA58C77C3B602AEAE7A8AEF3D30DA838F4924A620B18C05D9DF86C5876BDE8AB5597C8C0CE808AD083CAF89C3A5AAC60C1E980C6C144A17"
+        to "foojay-resolver-0.5.0.jar",
+    "10BF91C79AB151B684834E3CA8BA7D7E19742A3EEB580BDE690FBA433F9FFFE3ABBD79ED3FE3F97986C3A2BADC4D14E28835A8EF89167B4B9CC6014242338769"
+        to "gson-2.9.1.jar",
     settings.extra["com.github.vlsi.checksum-dependency.sha512"].toString()
         to "checksum-dependency-plugin.jar"
 )
diff --git a/src/bom-testing/build.gradle.kts b/src/bom-testing/build.gradle.kts
index 272decda05..bd4ecf3265 100644
--- a/src/bom-testing/build.gradle.kts
+++ b/src/bom-testing/build.gradle.kts
@@ -39,6 +39,7 @@ dependencies {
         // to make runtime classpath consistent with the compile one.
         api("com.github.tomakehurst:wiremock-jre8:2.35.0")
         api("junit:junit:4.13.2")
+        api("net.bytebuddy:byte-buddy:1.14.5")
         api("nl.jqno.equalsverifier:equalsverifier:3.14.1")
         // activemq-all should not be used as it provides secondary slf4j binding
         api("org.apache.activemq:activemq-broker:5.16.6")
diff --git a/src/components/build.gradle.kts b/src/components/build.gradle.kts
index 272bf4cdb1..05a51f2efe 100644
--- a/src/components/build.gradle.kts
+++ b/src/components/build.gradle.kts
@@ -16,6 +16,7 @@
  */
 
 plugins {
+    id("build-logic.build-params")
     id("build-logic.jvm-published-library")
 }
 
@@ -88,25 +89,7 @@ dependencies {
     testImplementation(testFixtures(projects.src.testkitWiremock))
 }
 
-fun String?.toBool(nullAs: Boolean, blankAs: Boolean, default: Boolean) =
-    when {
-        this == null -> nullAs
-        isBlank() -> blankAs
-        default -> !equals("false", ignoreCase = true)
-        else -> equals("true", ignoreCase = true)
-    }
-
-fun classExists(name: String) =
-    try {
-        Class.forName(name)
-        true
-    } catch (e: Throwable) {
-        false
-    }
-
-if (!(project.findProperty("enableJavaFx") as? String)
-    .toBool(nullAs = classExists("javafx.application.Platform"), blankAs = true, default = false)
-) {
+if (!buildParameters.enableJavaFx) {
     // JavaFX is not present in Maven Central, so exclude the file unless explicitly asked by
     // -PenableJavaFx
     logger.lifecycle("RenderInBrowser is excluded from compilation. If you want to compile it, add -PenableJavaFx")
diff --git a/src/dist/build.gradle.kts b/src/dist/build.gradle.kts
index 000f95af26..a41d732dd7 100644
--- a/src/dist/build.gradle.kts
+++ b/src/dist/build.gradle.kts
@@ -632,6 +632,9 @@ val runGui by tasks.registering(JavaExec::class) {
     group = "Development"
     description = "Builds and starts JMeter GUI"
     dependsOn(createDist)
+    buildParameters.testJdk?.let {
+        javaLauncher.set(javaToolchains.launcherFor(it))
+    }
 
     workingDir = File(project.rootDir, "bin")
     mainClass.set("org.apache.jmeter.NewDriver")
diff --git a/xdocs/changes.xml b/xdocs/changes.xml
index 799b20e8a8..d87170a6fe 100644
--- a/xdocs/changes.xml
+++ b/xdocs/changes.xml
@@ -102,6 +102,7 @@ Summary
 <ch_section>Non-functional changes</ch_section>
 <ul>
   <li><pr>6000</pr>Add release-drafter for populating GitHub releases info based on the merged PRs</li>
+  <li><pr>5989</pr>Use Gradle toolchains for JDK provisioning, enable building and testing with different JDKs, start testing with Java 21</li>
 </ul>
 
  <!-- =================== Bug fixes =================== -->