處理多個專案可能需要與多個 Java 語言版本互動。即使在單一專案中,由於回溯相容性需求,程式碼庫的不同部分也可能固定在特定的語言層級。這表示每個建置專案的機器上都必須安裝和管理相同工具(工具鏈)的不同版本。
Java 工具鏈是一組用於建置和執行 Java 專案的工具,通常由環境透過本機 JRE 或 JDK 安裝提供。編譯工作可能會使用 javac
作為其編譯器,測試和執行工作可能會使用 java
命令,而 javadoc
將用於產生文件。
依預設,Gradle 使用相同的 Java 工具鏈來執行 Gradle 本身和建置 JVM 專案。但是,這可能只在某些時候是理想的。在不同的開發人員機器和 CI 伺服器上使用不同的 Java 版本建置專案可能會導致非預期的問題。此外,您可能想要使用 Gradle 不支援執行的 Java 版本來建置專案。
為了提高建置的可重現性並使建置需求更清晰,Gradle 允許在專案和工作層級配置工具鏈。您也可以使用Daemon 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 home 的工作整合
任何可以使用 Java 可執行檔路徑或 Java home 位置配置的工作都可以從工具鏈中受益。
雖然您將無法直接連接工具鏈工具,但它們都具有中繼資料,可讓您存取其完整路徑或其所屬 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。
工具鏈下載儲存庫
工具鏈下載儲存庫定義透過應用特定的設定外掛程式新增至建置。如需編寫此類外掛程式的詳細資訊,請參閱工具鏈解析器外掛程式頁面。
工具鏈解析器外掛程式的一個範例是 Foojay Toolchains 外掛程式,它基於 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
的虛構解析器。
以下範例透過設定檔中的 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 Daemon,以便可以為下一個建置重新初始化。您可以使用 |
移除自動佈建的工具鏈
當需要移除自動佈建的工具鏈時,請移除 Gradle 使用者首頁中 /jdks
目錄內的相關工具鏈。
Gradle Daemon 快取有關專案的資訊,包括配置詳細資訊,例如工具鏈路徑或版本。專案的工具鏈配置變更可能僅在重新啟動 Gradle Daemon 後才會發生。建議停止 Gradle Daemon,以確保 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 提供者。 |
4 | 最後,我們將啟動器提供者連接為我們屬性的慣例。 |
在應用了 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 擴充功能上定義的工具鏈依預設用於解析啟動器。 |
2 | 沒有其他配置的自訂工作將使用預設 Java 8 工具鏈。 |
3 | 另一個工作透過使用 javaToolchains 服務選取不同的工具鏈來覆寫啟動器的值。 |
當工作需要存取工具鏈而未應用 java
外掛程式時,可以直接使用工具鏈服務。如果將未配置的工具鏈規格提供給服務,它將始終傳回執行 Gradle 的工具鏈的工具提供者。這可以透過在請求工具時傳遞空 lambda 來實現:javaToolchainService.launcherFor({})
。
您可以在編寫工作文件中找到有關定義自訂工作的更多詳細資訊。
工具鏈限制
當 Gradle 在針對 musl
編譯的 JVM 中執行時,可能會錯誤地偵測工具鏈,musl
是 C 標準程式庫的替代實作。針對 musl
編譯的 JVM 有時可以覆寫 LD_LIBRARY_PATH
環境變數來控制動態程式庫解析。這可能會影響 Gradle 啟動的分支 java 處理程序,從而導致非預期的行為。
因此,在具有 musl
程式庫的環境中不建議使用多個 java 工具鏈。大多數 Alpine 發行版本都是這種情況 — 請考慮改用另一個發行版本,例如 Ubuntu。如果您使用單一工具鏈(執行 Gradle 的 JVM)來建置和執行您的應用程式,您可以安全地忽略此限制。