處理多個專案時可能需要與多個版本的 Java 語言互動。即使在單一專案中,程式碼庫的不同部分也可能因為向後相容性的需求而固定在特定語言層級。這表示在建置專案的每部機器上都必須安裝和管理不同版本的相同工具(工具鏈)。
Java 工具鏈是一組建置和執行 Java 專案的工具,通常由環境透過本機 JRE 或 JDK 安裝提供。編譯工作可以使用 javac
作為編譯器,測試和執行工作可以使用 java
指令,而 javadoc
則用於產生文件。
預設情況下,Gradle 使用相同的 Java 工具鏈來執行 Gradle 本身和建置 JVM 專案。不過,這有時可能不太理想。在不同的開發人員機器和 CI 伺服器上使用不同 Java 版本建置專案可能會導致意外問題。此外,您可能想要使用不支援執行 Gradle 的 Java 版本來建置專案。
為了提高建置的可重現性並讓建置需求更清楚,Gradle 允許在專案和工作層級設定工具鏈。
專案工具鏈
您可以在 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 可以處理 JVM 編碼供應商資訊時,不同 JDK 版本之間的任何不一致性。
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)
}
}
task('testsOn17', type: 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
}
task('runOn17', type: JavaExec) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}
classpath = sourceSets.main.runtimeClasspath
mainClass = application.mainClass
}
根據任務,JRE 可能就足夠了,而對於其他任務(例如編譯),則需要 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 主目錄,如下所示
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 都不會被自動提供再次造訪和自動更新,即使有較新的次要版本可用。
工具鏈下載儲存庫
工具鏈下載儲存庫定義會透過套用特定設定外掛程式來新增到建置中。有關撰寫此類外掛程式的詳細資訊,請參閱 工具鏈解析器外掛程式 頁面。
工具鏈解析器外掛程式的範例之一是 Disco Toolchains 外掛程式,它基於 foojay Disco API。它甚至有一個慣例變體,只要套用就能自動處理所有需要的設定
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0")
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
一般來說,在套用工具鏈解析器外掛時,也需要設定外掛提供的工具鏈下載解析器。我們舉個例子來說明。考慮 build 套用的兩個工具鏈解析器外掛
-
一個是前面提到的 Foojay 外掛,它透過提供的
FoojayToolchainResolver
下載工具鏈。 -
另一個包含名為
MadeUpResolver
的虛構解析器。
以下範例透過設定檔中的 toolchainManagement
區塊,在 build 中使用這些工具鏈解析器
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
這有助於除錯哪些工具鏈可供 build 使用、如何偵測這些工具鏈,以及 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
檔案中。
自訂工具鏈位置
如果自動偵測本機工具鏈不足或已停用,還有其他方法可以讓 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({})
。
您可以在 撰寫工作 文件中找到更多關於定義客製化工作的詳細資訊。