在多个项目上工作可能需要与多个 Java 语言版本交互。即使在单个项目中,由于向后兼容性要求,代码库的不同部分也可能固定到特定的语言级别。这意味着必须在构建项目的每台机器上安装和管理相同工具(工具链)的不同版本。
Java 工具链是一组用于构建和运行 Java 项目的工具,通常由环境通过本地 JRE 或 JDK 安装提供。编译任务可以使用 javac
作为其编译器,测试和 exec 任务可以使用 java
命令,而 javadoc
将用于生成文档。
默认情况下,Gradle 使用相同的 Java 工具链来运行 Gradle 本身和构建 JVM 项目。但是,这可能只是有时是期望的。在不同的开发人员机器和 CI 服务器上使用不同的 Java 版本构建项目可能会导致意外问题。此外,您可能希望使用 Gradle 运行时不支持的 Java 版本来构建项目。
为了提高构建的可重现性并使构建要求更清晰,Gradle 允许在项目和任务级别配置工具链。您还可以使用 守护进程 JVM 标准来控制用于运行 Gradle 本身的 JVM。
项目的工具链
您可以通过在 java
扩展块中声明 Java 语言版本来定义项目要使用的工具链
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
现在执行构建(例如使用 gradle check
)将为您和其他运行构建的人处理几件事
Java 插件以及它们定义的任务中提供了工具链支持。 对于 Groovy 插件,支持编译,但尚不支持 Groovydoc 生成。对于 Scala 插件,支持编译和 Scaladoc 生成。 |
按供应商选择工具链
如果您的构建对使用的 JRE/JDK 有特定要求,您可能还需要定义工具链的供应商。JvmVendorSpec
具有 Gradle 识别的知名 JVM 供应商列表。这样做的好处是 Gradle 可以处理 JDK 版本在 JVM 如何编码供应商信息方面的任何不一致之处。
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.ADOPTIUM
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.ADOPTIUM
}
}
如果您要定位的供应商不是已知供应商,您仍然可以将工具链限制为与可用工具链的 java.vendor
系统属性匹配的工具链。
以下代码片段使用过滤器来包含可用工具链的子集。此示例仅包含 java.vendor
属性包含给定匹配字符串的工具链。匹配以不区分大小写的方式完成。
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.matching("customString")
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.matching("customString")
}
}
按虚拟机实现选择工具链
如果您的项目需要特定的实现,您也可以根据实现进行过滤。目前可供选择的实现有
VENDOR_SPECIFIC
-
充当占位符,并匹配任何供应商的任何实现(例如 hotspot、zulu 等)
J9
-
仅匹配使用 OpenJ9/IBM J9 运行时引擎的虚拟机实现。
例如,要使用通过 AdoptOpenJDK 分发的 IBM JVM,您可以按以下示例所示指定过滤器。
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.IBM
implementation = JvmImplementation.J9
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.IBM
implementation = JvmImplementation.J9
}
}
Java 主版本、供应商(如果指定)和实现(如果指定)将被跟踪为编译和测试执行的输入。 |
配置工具链规范
Gradle 允许配置影响工具链选择的多个属性,例如语言版本或供应商。即使这些属性可以独立配置,配置也必须遵循某些规则才能形成有效的规范。
在两种情况下,JavaToolchainSpec
被认为是有效的
-
当未设置任何属性时,即规范是空的;
-
当
languageVersion
已设置时,可以选择在之后设置任何其他属性。
换句话说,如果指定了供应商或实现,则它们必须伴随语言版本。Gradle 区分配置语言版本的工具链规范和不配置语言版本的工具链规范。在大多数情况下,没有语言版本的规范将被视为选择当前构建的工具链的规范。
自 Gradle 8.0 起,使用 无效 的 JavaToolchainSpec
实例会导致构建错误。
任务的工具链
如果您想调整特定任务使用的工具链,您可以指定任务正在使用的确切工具。例如,Test
任务公开了一个 JavaLauncher
属性,该属性定义了用于启动测试的 java 可执行文件。
在下面的示例中,我们将所有 java 编译任务配置为使用 Java 8。此外,我们引入了一个新的 Test
任务,它将使用 JDK 17 运行我们的单元测试。
tasks.withType<JavaCompile>().configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(8)
}
}
tasks.register<Test>("testsOn17") {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(8)
}
}
tasks.register('testsOn17', Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
}
此外,在 application
子项目中,我们添加了另一个 Java 执行任务,以使用 JDK 17 运行我们的应用程序。
tasks.register<JavaExec>("runOn17") {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
classpath = sourceSets["main"].runtimeClasspath
mainClass = application.mainClass
}
tasks.register('runOn17', JavaExec) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
classpath = sourceSets.main.runtimeClasspath
mainClass = application.mainClass
}
根据任务的不同,JRE 可能就足够了,而对于其他任务(例如编译),则需要 JDK。默认情况下,如果 JDK 可以满足要求,Gradle 优先选择已安装的 JDK 而不是 JRE。
工具链工具提供程序可以从 javaToolchains
扩展中获得。
提供三种工具
-
JavaCompiler
,它是 JavaCompile 任务使用的工具 -
JavadocTool
,它是 Javadoc 任务使用的工具
与依赖 Java 可执行文件或 Java 主目录的任务集成
任何可以使用 Java 可执行文件的路径或 Java 主目录位置配置的任务都可以从工具链中受益。
虽然您无法直接连接工具链工具,但它们都具有元数据,可以访问它们的完整路径或它们所属的 Java 安装的路径。
例如,您可以按如下方式配置任务的 java
可执行文件
val launcher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
tasks.sampleTask {
javaExecutable = launcher.map { it.executablePath }
}
def launcher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
tasks.named('sampleTask') {
javaExecutable = launcher.map { it.executablePath }
}
作为另一个示例,您可以按如下方式配置任务的 Java Home
val launcher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
tasks.anotherSampleTask {
javaHome = launcher.map { it.metadata.installationPath }
}
def launcher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
tasks.named('anotherSampleTask') {
javaHome = launcher.map { it.metadata.installationPath }
}
如果您需要特定工具(如 Java 编译器)的路径,您可以按如下方式获取它
val compiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(11)
}
tasks.yetAnotherSampleTask {
javaCompilerExecutable = compiler.map { it.executablePath }
}
def compiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(11)
}
tasks.named('yetAnotherSampleTask') {
javaCompilerExecutable = compiler.map { it.executablePath }
}
上面的示例使用了具有 RegularFileProperty 和 DirectoryProperty 属性的任务,这些属性允许延迟配置。分别执行 launcher.get().executablePath 、launcher.get().metadata.installationPath 或 compiler.get().executablePath 将为您提供给定工具链的完整路径,但请注意,这可能会急切地实现(和配置)工具链。 |
自动检测已安装的工具链
默认情况下,Gradle 自动检测本地 JRE/JDK 安装,因此用户无需进一步配置。以下是 JVM 自动检测支持的常见软件包管理器、工具和位置列表。
JVM 自动检测知道如何与以下工具配合使用
-
操作系统特定位置:Linux、macOS、Windows
在所有检测到的 JRE/JDK 安装集合中,将根据 工具链优先级规则选择一个。
无论您是使用工具链自动检测还是配置 自定义工具链位置,不存在或没有 bin/java 可执行文件的安装都将被忽略并发出警告,但它们不会生成错误。 |
如何禁用自动检测
为了禁用自动检测,您可以使用 org.gradle.java.installations.auto-detect
Gradle 属性
-
使用
-Porg.gradle.java.installations.auto-detect=false
启动 gradle -
或将
org.gradle.java.installations.auto-detect=false
放入您的gradle.properties
文件中。
自动配置
如果 Gradle 找不到本地可用的与构建要求匹配的工具链,它可以自动下载一个(只要配置了工具链下载仓库;有关详细信息,请参阅 相关部分)。Gradle 将下载的 JDK 安装在 Gradle 用户主目录中。
Gradle 仅下载 GA 版本的 JDK 版本。不支持下载早期访问版本。 |
一旦安装在 Gradle 用户主目录中,配置的 JDK 将成为 自动检测可见的 JDK 之一,并且可以像系统上安装的任何其他 JDK 一样被后续构建使用。
由于自动配置仅在自动检测未能找到匹配的 JDK 时才会启动,因此自动配置只能下载新的 JDK,并且绝不涉及更新任何已安装的 JDK。即使有较新的次要版本可用,自动配置也不会重新访问和自动更新任何自动配置的 JDK。
工具链下载仓库
工具链下载仓库定义通过应用特定的 settings 插件添加到构建中。有关编写此类插件的详细信息,请查阅 工具链解析器插件页面。
工具链解析器插件的一个示例是 Foojay Toolchains Plugin,它基于 foojay Disco API。它甚至具有一个约定变体,只需应用即可自动处理所有需要的配置
plugins {
id("org.gradle.toolchains.foojay-resolver-convention").version("0.9.0")
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0'
}
对于高级或高度特定的配置,应使用自定义工具链解析器插件。
通常,在应用工具链解析器插件时,还需要配置它们提供的工具链下载解析器。让我们用一个例子来说明。考虑构建应用的两个工具链解析器插件
-
一个是上面提到的 Foojay 插件,它通过它提供的
FoojayToolchainResolver
下载工具链。 -
另一个包含一个名为
MadeUpResolver
的虚构解析器。
以下示例通过 settings 文件中的 toolchainManagement
块在构建中使用这些工具链解析器
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)
}
}
}
}
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 | 您可以使用与依赖管理相同的 身份验证和授权选项集来配置工具链仓库。 |
toolchainManagement 中的 jvm 块仅在应用工具链解析器插件后解析。 |
查看和调试工具链
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-download
Gradle 属性
-
使用
-Porg.gradle.java.installations.auto-download=false
启动 gradle -
或将
org.gradle.java.installations.auto-download=false
放入gradle.properties
文件中。
禁用自动配置后,请确保构建文件中指定的 JRE/JDK 版本已在本地安装。然后,停止 Gradle 守护进程,以便可以为下一次构建重新初始化它。您可以使用 |
删除自动配置的工具链
当需要删除自动配置的工具链时,请删除位于 Gradle 用户主目录中的 /jdks
目录内的相关工具链。
Gradle 守护进程缓存有关您的项目的信息,包括配置详细信息,例如工具链路径或版本。对项目工具链配置的更改可能仅在 Gradle 守护进程重新启动后才会发生。建议 停止 Gradle 守护进程,以确保 Gradle 更新后续构建的配置。 |
自定义工具链位置
如果自动检测本地工具链不足或已禁用,您还可以通过其他方式让 Gradle 了解已安装的工具链。
如果您的设置已提供指向已安装 JVM 的环境变量,您还可以让 Gradle 知道要考虑哪些环境变量。假设环境变量 JDK8
和 JRE17
指向有效的 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 将对与构建的工具链规范匹配的所有 JDK/JRE 安装进行排序,并选择第一个。排序基于以下规则完成
-
当前运行 Gradle 的安装优先于任何其他安装
-
JDK 安装优先于 JRE 安装
-
某些供应商优先于其他供应商;它们的顺序(从最高优先级到最低优先级)
-
ADOPTIUM
-
ADOPTOPENJDK
-
AMAZON
-
APPLE
-
AZUL
-
BELLSOFT
-
GRAAL_VM
-
HEWLETT_PACKARD
-
IBM
-
JETBRAINS
-
MICROSOFT
-
ORACLE
-
SAP
-
TENCENT
-
其他所有
-
-
较高的主版本优先于较低的版本
-
较高的次版本优先于较低的版本
-
安装路径根据其词典编纂顺序优先(确定性地在相同类型、来自相同供应商和具有相同版本的安装之间进行决定的最后手段)
所有这些规则都作为多级排序标准应用,按显示的顺序排列。让我们用一个例子来说明。工具链规范请求 Java 版本 17。Gradle 检测到以下匹配的安装
-
Oracle JRE v17.0.1
-
Oracle JDK v17.0.0
-
Microsoft JDK 17.0.0
-
Microsoft JRE 17.0.1
-
Microsoft JDK 17.0.1
假设 Gradle 在主要 Java 版本 17 以外的版本上运行。否则,该安装将具有优先级。
当我们应用上述规则对这组安装进行排序时,我们将得到以下顺序
-
Microsoft JDK 17.0.1
-
Microsoft JDK 17.0.0
-
Oracle JDK v17.0.0
-
Microsoft JRE v17.0.1
-
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
扩展定义的工具链作为工具属性的默认值是合适的。这样,用户只需在项目级别配置工具链一次。
下面的示例展示了如何使用默认工具链作为约定,同时允许用户单独配置每个任务的工具链。
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
}
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 的 provider。 |
4 | 最后,我们将 launcher provider 作为我们属性的约定连接起来。 |
在应用了 java
插件的项目中,我们可以按如下方式使用该任务
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)
}
}
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 扩展上定义的工具链默认用于解析 launcher。 |
2 | 没有额外配置的自定义任务将使用默认的 Java 8 工具链。 |
3 | 另一个任务通过使用 javaToolchains 服务选择不同的工具链来覆盖 launcher 的值。 |
当任务需要在未应用 java
插件的情况下访问工具链时,可以直接使用工具链服务。如果向服务提供了 未配置的 工具链规范,它将始终为运行 Gradle 的工具链返回工具 provider。这可以通过在请求工具时传递一个空 lambda 来实现:javaToolchainService.launcherFor({})
。
您可以在 编写任务 文档中找到有关定义自定义任务的更多详细信息。