处理多个项目可能需要与多个版本的 Java 语言进行交互。即使在单个项目中,由于向后兼容性要求,代码库的不同部分也可能被固定到特定的语言级别。这意味着必须在构建项目的每台计算机上安装和管理相同工具(工具链)的不同版本。

Java工具链是一组用于构建和运行 Java 项目的工具,通常由环境通过本地 JRE 或 JDK 安装提供。编译任务可以使用javac其编译器,测试和执行任务可以使用命令java,而javadoc将用于生成文档。

默认情况下,Gradle 使用相同的 Java 工具链来运行 Gradle 本身和构建 JVM 项目。然而,这可能只是有时是可取的。在不同的开发人员机器和 CI 服务器上构建具有不同 Java 版本的项目可能会导致意外问题。此外,您可能想要使用不支持运行 Gradle 的 Java 版本来构建项目。

为了提高构建的可重复性并使构建需求更加清晰,Gradle 允许在项目和任务级别上配置工具链。

项目工具链

您可以通过在扩展块中声明 Java 语言版本来定义项目使用的工具链java

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

执行构建(例如使用gradle check)现在将为您和其他运行构建的人处理一些事情:

  1. Gradle 配置所有编译、测试和 javadoc 任务以使用定义的工具链。

  2. Gradle 检测本地安装的工具链

  3. Gradle 选择符合要求的工具链(上例中的任何 Java 17 工具链)。

  4. 如果没有找到匹配的工具链,Gradle 会根据配置的工具链下载存储库自动下载匹配的工具链。

Java 插件及其定义的任务中提供了工具链支持。

对于 Groovy 插件,支持编译,但尚不支持 Groovydoc 生成。对于Scala插件,支持编译和Scaladoc生成。

按供应商选择工具链

如果您的构建对所使用的 JRE/JDK 有特定要求,您可能还需要定义工具链的供应商。 JvmVendorSpec拥有 Gradle 认可的知名 JVM 供应商列表。优点是 Gradle 可以处理 JDK 版本之间 JVM 对供应商信息编码方式的任何不一致。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.ADOPTIUM
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.ADOPTIUM
    }
}

如果您要定位的供应商不是已知供应商,您仍然可以将工具链限制为与java.vendor可用工具链的系统属性相匹配的工具链。

以下代码片段使用过滤来包含可用工具链的子集。此示例仅包含java.vendor属性包含给定匹配字符串的工具链。匹配以不区分大小写的方式完成。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.matching("customString")
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.matching("customString")
    }
}

通过虚拟机实现选择工具链

如果您的项目需要特定的实现,您也可以根据实现进行过滤。目前可供选择的实现有:

VENDOR_SPECIFIC

充当占位符并匹配任何供应商的任何实现(例如热点、zulu,...​)

J9

仅匹配使用 OpenJ9/IBM J9 运行时引擎的虚拟机实现。

例如,要使用通过AdoptOpenJDK分发的IBM JVM ,您可以指定过滤器,如下例所示。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.IBM
        implementation = JvmImplementation.J9
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.IBM
        implementation = JvmImplementation.J9
    }
}
Java 主要版本、供应商(如果指定)和实现(如果指定)将作为编译和测试执行的输入进行跟踪。

配置工具链规范

Gradle 允许配置影响工具链选择的多个属性,例如语言版本或供应商。尽管这些属性可以独立配置,但配置必须遵循一定的规则才能形成有效的规范。

A在两种情况下JavaToolchainSpec被认为是有效的:

  1. 当没有设置任何属性时,即规范为

  2. languageVersion设置后,可以选择设置任何其他属性。

换句话说,如果指定了供应商或实现,则它们必须附有语言版本。 Gradle 区分配置语言版本的工具链规范和不配置语言版本的工具链规范。在大多数情况下,没有语言版本的规范将被视为选择当前构建的工具链。

自 Gradle 8.0 起,使用无效实例JavaToolchainSpec会导致构建错误。

任务工具链

如果您想调整用于特定任务的工具链,您可以指定任务正在使用的确切工具。例如,该Test任务公开一个JavaLauncher属性,该属性定义用于启动测试的 java 可执行文件。

在下面的示例中,我们将所有 java 编译任务配置为使用 Java 8。此外,我们引入了一个新Test任务,该任务将使用 JDK 17 运行我们的单元测试。

list/build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    javaCompiler = javaToolchains.compilerFor {
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register<Test>("testsOn17") {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
list/build.gradle
tasks.withType(JavaCompile).configureEach {
    javaCompiler = javaToolchains.compilerFor {
        languageVersion = JavaLanguageVersion.of(8)
    }
}

task('testsOn17', type: Test) {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

此外,在application子项目中,我们添加了另一个 Java 执行任务来使用 JDK 17 运行我们的应用程序。

application/build.gradle.kts
tasks.register<JavaExec>("runOn17") {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }

    classpath = sourceSets["main"].runtimeClasspath
    mainClass = application.mainClass
}
application/build.gradle
task('runOn17', type: JavaExec) {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }

    classpath = sourceSets.main.runtimeClasspath
    mainClass = application.mainClass
}

根据任务的不同,JRE 可能就足够了,而对于其他任务(例如编译),则需要 JDK。默认情况下,如果安装的 JDK 能够满足要求,Gradle 会优先选择安装的 JDK 而不是 JRE。

Toolchains工具提供者可以从扩展中获得javaToolchains

三个工具可用:

  • A这是JavaCompile任务JavaCompiler使用的工具

  • A这是JavaExecTest任务JavaLauncher使用的工具

  • A这是Javadoc任务JavadocTool使用的工具

与依赖 Java 可执行文件或 Java home 的任务集成

任何可以配置 Java 可执行文件路径或 Java 主位置的任务都可以从工具链中受益。

虽然您无法直接连接工具链工具,但它们都具有元数据,可以访问它们的完整路径或它们所属的 Java 安装的路径。

例如,您可以java按如下方式配置任务的可执行文件:

build.gradle.kts
val launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.sampleTask {
    javaExecutable = launcher.map { it.executablePath }
}
build.gradle
def launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.named('sampleTask') {
    javaExecutable = launcher.map { it.executablePath }
}

作为另一个示例,您可以为任务配置Java Home ,如下所示:

build.gradle.kts
val launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.anotherSampleTask {
    javaHome = launcher.map { it.metadata.installationPath }
}
build.gradle
def launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.named('anotherSampleTask') {
    javaHome = launcher.map { it.metadata.installationPath }
}

如果您需要特定工具(例如Java编译器)的路径,可以通过以下方式获取:

build.gradle.kts
val compiler = javaToolchains.compilerFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.yetAnotherSampleTask {
    javaCompilerExecutable = compiler.map { it.executablePath }
}
build.gradle
def compiler = javaToolchains.compilerFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.named('yetAnotherSampleTask') {
    javaCompilerExecutable = compiler.map { it.executablePath }
}
上面的示例使用带有允许延迟配置的属性的RegularFileProperty任务。DirectoryProperty分别执行launcher.get().executablePath或将为您提供给定工具链的完整路径,但请注意,这可能会急切地实现(并提供)工具链 launcher.get().metadata.installationPathcompiler.get().executablePath

自动检测已安装的工具链

默认情况下,Gradle 会自动检测本地 JRE/JDK 安装,因此用户无需进一步配置。以下是 JVM 自动检测支持的常见包管理器、工具和位置的列表。

JVM 自动检测知道如何使用:

在所有检测到的 JRE/JDK 安装集合中,将根据工具链优先规则选择一个。

无论您是使用工具链自动检测还是配置自定义工具链位置,不存在或没有bin/java可执行文件的安装都将被忽略并显示警告,但不会生成错误。

如何禁用自动检测

为了禁用自动检测,您可以使用org.gradle.java.installations.auto-detectGradle 属性:

  • 要么使用 gradle 启动-Porg.gradle.java.installations.auto-detect=false

  • 或者放入你的文件org.gradle.java.installations.auto-detect=false中。gradle.properties

自动配置

如果 Gradle 无法在本地找到符合构建要求的工具链,它可以自动下载一个(只要已经配置了工具链下载存储库;详细信息请参阅相关部分)。 Gradle 将下载的 JDK 安装在Gradle 用户主页中。

Gradle 仅下载 GA 版本的 JDK 版本。不支持下载早期访问版本。

一旦安装在Gradle User Home中,预配的 JDK 就会成为自动检测可见的 JDK 之一,并且可以由任何后续构建使用,就像系统上安装的任何其他 JDK 一样。

由于自动配置仅在自动检测无法找到匹配的 JDK 时才会启动,因此自动配置只能下载新的 JDK,而不会参与更新任何已安装的 JDK。任何自动配置的 JDK 都不会被自动配置重新访问和自动更新,即使有更新的次要版本可供使用。

工具链下载存储库

通过应用特定的设置插件将工具链下载存储库定义添加到构建中。有关编写此类插件的详细信息,请参阅工具链解析器插件页面。

工具链解析器插件的一个示例是Disco Toolchains Plugin,它基于foojay Disco API。它甚至还有一个约定变体,只需应用即可自动处理所有所需的配置:

settings.gradle.kts
plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0")
}
settings.gradle
plugins {
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}

一般来说,在应用工具链解析器插件时,还需要配置其提供的工具链下载解析器。我们用一个例子来说明一下。考虑构建应用的两个工具链解析器插件:

  • 一种是上面提到的 Foojay 插件,它通过它FoojayToolchainResolver提供的下载工具链。

  • 另一个包含一个名为 的虚构解析器MadeUpResolver

以下示例通过toolchainManagement设置文件中的块在构建中使用这些工具链解析器:

settings.gradle.kts
toolchainManagement {
    jvm { (1)
        javaRepositories {
            repository("foojay") { (2)
                resolverClass = org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java
            }
            repository("made_up") { (3)
                resolverClass = MadeUpResolver::class.java
                credentials {
                    username = "user"
                    password = "password"
                }
                authentication {
                    create<DigestAuthentication>("digest")
                } (4)
            }
        }
    }
}
settings.gradle
toolchainManagement {
    jvm { (1)
        javaRepositories {
            repository('foojay') { (2)
                resolverClass = org.gradle.toolchains.foojay.FoojayToolchainResolver
            }
            repository('made_up') { (3)
                resolverClass = MadeUpResolver
                credentials {
                    username "user"
                    password "password"
                }
                authentication {
                    digest(BasicAuthentication)
                } (4)
            }
        }
    }
}
1 在该toolchainManagement块中,该jvm块包含Java工具链的配置。
2 javaRepositories块定义了命名的 Java 工具链存储库配置。使用该resolverClass属性将这些配置链接到插件。
3 工具链声明顺序很重要。 Gradle 从提供匹配的第一个存储库下载,从列表中的第一个存储库开始。
4 您可以使用用于依赖项管理的同一组身份验证和授权选项来配置工具链存储库。
仅在应用工具链解析器插件后才能解析 该jvm块。toolchainManagement

查看和调试工具链

Gradle 可以显示所有检测到的工具链的列表,包括其元数据。

例如,要显示项目的所有工具链,请运行:

gradle -q javaToolchains
输出gradle -q javaToolchains
> gradle -q javaToolchains

 + Options
     | Auto-detection:     Enabled
     | Auto-download:      Enabled

 + AdoptOpenJDK 1.8.0_242
     | Location:           /Users/username/myJavaInstalls/8.0.242.hs-adpt/jre
     | Language Version:   8
     | Vendor:             AdoptOpenJDK
     | Architecture:       x86_64
     | Is JDK:             false
     | Detected by:        Gradle property 'org.gradle.java.installations.paths'

 + Microsoft JDK 16.0.2+7
     | Location:           /Users/username/.sdkman/candidates/java/16.0.2.7.1-ms
     | Language Version:   16
     | Vendor:             Microsoft
     | Architecture:       aarch64
     | Is JDK:             true
     | Detected by:        SDKMAN!

 + OpenJDK 15-ea
     | Location:           /Users/user/customJdks/15.ea.21-open
     | Language Version:   15
     | Vendor:             AdoptOpenJDK
     | Architecture:       x86_64
     | Is JDK:             true
     | Detected by:        environment variable 'JDK16'

 + Oracle JDK 1.7.0_80
     | Location:           /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/jre
     | Language Version:   7
     | Vendor:             Oracle
     | Architecture:       x86_64
     | Is JDK:             false
     | Detected by:        MacOS java_home

这可以帮助调试哪些工具链可用于构建、如何检测它们以及 Gradle 了解这些工具链的元数据类型。

如何禁用自动配置

为了禁用自动配置,您可以使用org.gradle.java.installations.auto-downloadGradle 属性:

  • 要么使用 gradle 启动-Porg.gradle.java.installations.auto-download=false

  • 或者放入文件org.gradle.java.installations.auto-download=falsegradle.properties

自定义工具链位置

如果自动检测本地工具链不够或被禁用,您可以通过其他方式让 Gradle 了解已安装的工具链。

如果您的设置已经提供了指向已安装 JVM 的环境变量,您还可以让 Gradle 知道要考虑哪些环境变量。假设环境变量JDK8JRE17指向有效的 java 安装,以下内容指示 Gradle 解析这些环境变量并在寻找匹配的工具链时考虑这些安装。

org.gradle.java.installations.fromEnv=JDK8,JRE17

此外,您可以使用该属性提供特定安装的以逗号分隔的路径列表org.gradle.java.installations.paths。例如,在您的gradle.properties遗嘱中使用以下内容,让 Gradle 知道在检测工具链时要查看哪些目录。 Gradle 会将这些目录视为可能的安装,但不会下降到任何嵌套目录。

org.gradle.java.installations.paths=/custom/path/jdk1.8,/shared/jre11

Gradle 不会将自定义工具链优先于自动检测的工具链。如果您在构建中启用自动检测,自定义工具链将扩展工具链位置集。 Gradle 根据优先级规则选择工具链。

工具链安装优先级

Gradle 将对与构建的工具链规范匹配的所有 JDK/JRE 安装进行排序,并选择第一个。排序是根据以下规则进行的:

  1. 当前运行 Gradle 的安装优先于任何其他安装

  2. JDK 安装优于 JRE 安装

  3. 某些供应商优先于其他供应商;它们的顺序(从最高优先级到最低优先级):

    1. 领养

    2. 采用OpenJDK

    3. 亚马逊

    4. 苹果

    5. 蔚蓝

    6. 贝尔软件公司

    7. GRAAL_VM

    8. 惠普_派克

    9. 国际商业机器公司

    10. 捷布恩斯

    11. 微软

    12. Oracle公司

    13. 树液

    14. 腾讯

    15. 其他一切

  4. 较高的主要版本优先于较低的版本

  5. 较高的次要版本优先于较低的次要版本

  6. 安装路径根据其字典顺序优先(确定性地决定来自同一供应商和同一版本的相同类型的安装的最后手段)

所有这些规则都按照所示的顺序作为多级排序标准应用。我们用一个例子来说明一下。工具链规范要求 Java 版本 17。Gradle 检测以下匹配的安装:

  • Oracle JRE v17.0.1

  • Oracle JDK v17.0.0

  • 微软JDK 17.0.0

  • 微软JRE 17.0.1

  • 微软JDK 17.0.1

假设 Gradle 在除 17 之外的主要 Java 版本上运行。否则,该安装将具有优先权。

当我们应用上述规则对该集合进行排序时,我们最终将得到以下排序:

  1. 微软JDK 17.0.1

  2. 微软JDK 17.0.0

  3. Oracle JDK v17.0.0

  4. 微软JRE v17.0.1

  5. Oracle JRE v17.0.1

Gradle 更喜欢 JDK 而不是 JRE,因此 JRE 排在最后。 Gradle 更喜欢 Microsoft 供应商而不是 Oracle,因此 Microsoft 安装排在第一位。 Gradle 更喜欢更高的版本号,因此 JDK 17.0.1 位于 JDK 17.0.0 之前。

因此 Gradle 按此顺序选择第一个匹配项:Microsoft JDK 17.0.1。

插件作者的工具链

创建使用工具链的插件或任务时,必须提供合理的默认值并允许用户覆盖它们。

对于 JVM 项目,通常可以安全地假设java插件已应用于项目。该java插件会自动应用于核心 Groovy 和 Scala 插件以及 Kotlin 插件。在这种情况下,使用通过扩展定义的工具链java作为工具属性的默认值是合适的。这样,用户只需在项目级别配置工具链一次。

下面的示例展示了如何使用默认工具链作为约定,同时允许用户针对每个任务单独配置工具链。

build.gradle.kts
abstract class CustomTaskUsingToolchains : DefaultTask() {

    @get:Nested
    abstract val launcher: Property<JavaLauncher> (1)

    init {
        val toolchain = project.extensions.getByType<JavaPluginExtension>().toolchain (2)
        val defaultLauncher = javaToolchainService.launcherFor(toolchain) (3)
        launcher.convention(defaultLauncher) (4)
    }

    @TaskAction
    fun showConfiguredToolchain() {
        println(launcher.get().executablePath)
        println(launcher.get().metadata.installationPath)
    }

    @get:Inject
    protected abstract val javaToolchainService: JavaToolchainService
}
build.gradle
abstract class CustomTaskUsingToolchains extends DefaultTask {

    @Nested
    abstract Property<JavaLauncher> getLauncher() (1)

    CustomTaskUsingToolchains() {
        def toolchain = project.extensions.getByType(JavaPluginExtension.class).toolchain (2)
        Provider<JavaLauncher> defaultLauncher = getJavaToolchainService().launcherFor(toolchain) (3)
        launcher.convention(defaultLauncher) (4)
    }

    @TaskAction
    def showConfiguredToolchain() {
        println launcher.get().executablePath
        println launcher.get().metadata.installationPath
    }

    @Inject
    protected abstract JavaToolchainService getJavaToolchainService()
}
1 我们声明JavaLauncher任务的属性。该属性必须标记为@Nested输入,以确保任务响应工具链更改。
2 我们从扩展中获取工具链规范,java并将其用作默认值。
3 使用 ,我们可以获得与工具链匹配JavaToolchainService的 提供者。JavaLauncher
4 最后,我们连接启动器提供程序作为我们财产的约定。

在应用了该插件的项目中java,我们可以按如下方式使用该任务:

build.gradle.kts
plugins {
    java
}

java {
    toolchain { (1)
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register<CustomTaskUsingToolchains>("showDefaultToolchain") (2)

tasks.register<CustomTaskUsingToolchains>("showCustomToolchain") {
    launcher = javaToolchains.launcherFor { (3)
        languageVersion = JavaLanguageVersion.of(17)
    }
}
build.gradle
plugins {
    id 'java'
}

java {
    toolchain { (1)
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register('showDefaultToolchain', CustomTaskUsingToolchains) (2)

tasks.register('showCustomToolchain', CustomTaskUsingToolchains) {
    launcher = javaToolchains.launcherFor { (3)
        languageVersion = JavaLanguageVersion.of(17)
    }
}
1 java默认情况下,使用扩展上定义的工具链来解析启动器。
2 无需额外配置的自定义任务将使用默认的 Java 8 工具链。
3 另一个任务通过使用javaToolchains服务选择不同的工具链来覆盖启动器的值。

当任务需要访问工具链而不java应用插件时,可以直接使用工具链服务。如果向服务提供了未配置的工具链规范,它将始终返回运行 Gradle 的工具链的工具提供程序。这可以通过在请求工具时传递一个空的 lambda 来实现:javaToolchainService.launcherFor({})

您可以在创作任务文档中找到有关定义自定义任务的更多详细信息。

工具链限制

当 Gradle 在针对C 标准库的musl替代实现编译的 JVM 中运行时,它可能会错误地检测工具链。编译的 JVMmusl有时可以覆盖LD_LIBRARY_PATH环境变量来控制动态库解析。这可能会影响 Gradle 启动的分叉 java 进程,从而导致意外行为。

因此,在具有该musl库的环境中不鼓励使用多个 java 工具链。大多数 Alpine 发行版都是这种情况 - 请考虑使用其他发行版,例如 Ubuntu。如果您使用单个工具链(运行 Gradle 的 JVM)来构建和运行应用程序,则可以安全地忽略此限制。