複合建置是一種包含其他建置的建置。

複合建置類似於 Gradle 多專案建置,不同之處在於複合建置包含的是整個 builds
,而不是 subprojects
。
複合建置允許您
-
組合通常獨立開發的建置,例如,當嘗試修復應用程式使用的函式庫中的錯誤時。
-
將大型多專案建置分解為更小、更隔離的區塊,這些區塊可以根據需要獨立或協同工作。
包含在複合建置中的建置稱為包含建置。包含建置不與複合建置或其他包含建置共享任何配置。每個包含建置都是獨立配置和執行的。
定義複合建置
以下範例示範了如何將兩個通常獨立開發的 Gradle 建置組合到一個複合建置中。
my-composite
├── gradle
├── gradlew
├── settings.gradle.kts
├── build.gradle.kts
├── my-app
│ ├── settings.gradle.kts
│ └── app
│ ├── build.gradle.kts
│ └── src/main/java/org/sample/my-app/Main.java
└── my-utils
├── settings.gradle.kts
├── number-utils
│ ├── build.gradle.kts
│ └── src/main/java/org/sample/numberutils/Numbers.java
└── string-utils
├── build.gradle.kts
└── src/main/java/org/sample/stringutils/Strings.java
my-utils
多專案建置產生兩個 Java 函式庫,number-utils
和 string-utils
。my-app
建置產生一個使用這些函式庫中函式的可執行檔。
my-app
建置不直接依賴於 my-utils
。相反地,它宣告對 my-utils
產生的函式庫的二進制相依性
plugins {
id("application")
}
application {
mainClass = "org.sample.myapp.Main"
}
dependencies {
implementation("org.sample:number-utils:1.0")
implementation("org.sample:string-utils:1.0")
}
plugins {
id 'application'
}
application {
mainClass = 'org.sample.myapp.Main'
}
dependencies {
implementation 'org.sample:number-utils:1.0'
implementation 'org.sample:string-utils:1.0'
}
透過 --include-build
定義複合建置
--include-build
命令列引數將執行的建置轉換為複合建置,將包含建置中的相依性取代到執行的建置中。
例如,從 my-app
執行的 ./gradlew run --include-build ../my-utils
的輸出
$ ./gradlew --include-build ../my-utils run > Task :app:processResources NO-SOURCE > Task :my-utils:string-utils:compileJava > Task :my-utils:string-utils:processResources NO-SOURCE > Task :my-utils:string-utils:classes > Task :my-utils:string-utils:jar > Task :my-utils:number-utils:compileJava > Task :my-utils:number-utils:processResources NO-SOURCE > Task :my-utils:number-utils:classes > Task :my-utils:number-utils:jar > Task :app:compileJava > Task :app:classes > Task :app:run The answer is 42 BUILD SUCCESSFUL in 0s 6 actionable tasks: 6 executed
透過設定檔定義複合建置
可以使用 Settings.includeBuild(java.lang.Object) 在 settings.gradle(.kts)
檔案中宣告包含建置,使上述配置持久化。
設定檔可用於同時新增子專案和包含建置。
包含建置是依位置新增的
includeBuild("my-utils")
在範例中,settings.gradle(.kts) 檔案組合了原本獨立的建置
rootProject.name = "my-composite"
includeBuild("my-app")
includeBuild("my-utils")
rootProject.name = 'my-composite'
includeBuild 'my-app'
includeBuild 'my-utils'
若要從 my-composite
執行 my-app
建置中的 run
任務,請執行 ./gradlew my-app:app:run
。
您可以選擇在 my-composite
中定義一個依賴於 my-app:app:run
的 run
任務,以便您可以執行 ./gradlew run
tasks.register("run") {
dependsOn(gradle.includedBuild("my-app").task(":app:run"))
}
tasks.register('run') {
dependsOn gradle.includedBuild('my-app').task(':app:run')
}
包含定義 Gradle 外掛的建置
包含建置的一個特殊情況是定義 Gradle 外掛的建置。
這些建置應使用設定檔的 pluginManagement {}
區塊內的 includeBuild
語句包含。
使用此機制,包含建置也可以貢獻一個設定外掛,該外掛可以應用於設定檔本身
pluginManagement {
includeBuild("../url-verifier-plugin")
}
pluginManagement {
includeBuild '../url-verifier-plugin'
}
包含建置的限制
大多數建置都可以包含在複合建置中,包括其他複合建置。但有一些限制。
在常規建置中,Gradle 確保每個專案都有唯一的專案路徑。這使得專案可識別和可定址,而不會發生衝突。
在複合建置中,Gradle 會為包含建置中的每個專案新增額外的限定條件,以避免專案路徑衝突。用於識別複合建置中專案的完整路徑稱為建置樹狀路徑。它由包含建置的建置路徑和專案的專案路徑組成。
預設情況下,建置路徑和專案路徑是從磁碟上的目錄名稱和結構派生的。由於包含建置可以位於磁碟上的任何位置,因此它們的建置路徑由包含目錄的名稱決定。這有時可能導致衝突。
總之,包含建置必須滿足以下要求
-
每個包含建置都必須具有唯一的建置路徑。
-
每個包含建置路徑都不得與主建置的任何專案路徑衝突。
這些條件保證即使在複合建置中,每個專案也可以被唯一識別。
如果發生衝突,解決衝突的方法是變更包含建置的建置名稱
includeBuild("some-included-build") {
name = "other-name"
}
當複合建置包含在另一個複合建置中時,兩個建置都具有相同的父級。換句話說,巢狀複合建置結構會被展平。 |
與複合建置互動
與複合建置互動通常與常規多專案建置類似。可以執行任務、執行測試,並且可以將建置匯入到 IDE 中。
執行任務
可以從命令列或 IDE 執行包含建置中的任務,方式與常規多專案建置中的任務相同。執行任務將導致任務相依性被執行,以及從其他包含建置建置相依性成品所需的任務。
您可以使用完整路徑呼叫包含建置中的任務,例如,:included-build-name:project-name:taskName
。專案和任務名稱可以縮寫。
$ ./gradlew :included-build:subproject-a:compileJava > Task :included-build:subproject-a:compileJava $ ./gradlew :i-b:sA:cJ > Task :included-build:subproject-a:compileJava
若要從命令列排除任務,您需要提供任務的完整路徑。
包含建置任務會自動執行以產生所需的相依性成品,或者包含建置可以宣告對包含建置中任務的相依性。 |
匯入到 IDE
複合建置最有用的功能之一是 IDE 整合。
匯入複合建置允許輕鬆地將來自不同 Gradle 建置的來源一起開發。對於每個包含建置,每個子專案都作為 IntelliJ IDEA 模組或 Eclipse 專案包含。相依性來源已配置,提供跨建置導航和重構。
宣告由包含建置取代的相依性
預設情況下,Gradle 將配置每個包含建置以確定它可以提供的相依性。執行此操作的演算法很簡單。Gradle 將檢查包含建置中專案的 group 和 name,並將專案相依性替換為任何與 ${project.group}:${project.name}
匹配的外部相依性。
預設情況下,不會為主建置註冊取代。 若要使主建置的(子)專案可透過 |
在某些情況下,Gradle 確定的預設取代不足或必須針對特定的複合建置進行更正。在這些情況下,可以明確宣告包含建置的取代。
例如,一個名為 anonymous-library
的單專案建置產生一個 Java 实用程式函式庫,但未宣告 group 屬性的值
plugins {
java
}
plugins {
id 'java'
}
當此建置包含在複合建置中時,它將嘗試取代相依性模組 undefined:anonymous-library
(undefined
是 project.group
的預設值,而 anonymous-library
是根專案名稱)。顯然,這在複合建置中沒有用。
若要在複合建置中使用未發布的函式庫,您可以明確宣告其提供的取代
includeBuild("anonymous-library") {
dependencySubstitution {
substitute(module("org.sample:number-utils")).using(project(":"))
}
}
includeBuild('anonymous-library') {
dependencySubstitution {
substitute module('org.sample:number-utils') using project(':')
}
}
透過此配置,my-app
複合建置將以對 anonymous-library
根專案的相依性取代對 org.sample:number-utils
的任何相依性。
停用配置的包含建置取代
如果您需要解析也作為包含建置一部分提供的模組的已發布版本,則可以在ResolutionStrategy 已解析的 Configuration 上停用包含建置取代規則。這是必要的,因為這些規則是全域應用於建置的,並且 Gradle 預設情況下在解析期間不考慮已發布的版本。
例如,我們建立一個單獨的 publishedRuntimeClasspath
配置,該配置解析為也存在於其中一個本地建置中的模組的已發布版本。這是透過停用全域相依性取代規則來完成的
configurations.create("publishedRuntimeClasspath") {
resolutionStrategy.useGlobalDependencySubstitutionRules = false
extendsFrom(configurations.runtimeClasspath.get())
isCanBeConsumed = false
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
}
configurations.create('publishedRuntimeClasspath') {
resolutionStrategy.useGlobalDependencySubstitutionRules = false
extendsFrom(configurations.runtimeClasspath)
canBeConsumed = false
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
}
一個用例是比較已發布和本地建置的 JAR 檔案。
必須宣告包含建置取代的情況
許多建置將自動作為包含建置運作,而無需宣告取代。以下是一些需要宣告取代的常見情況
-
當
archivesBaseName
屬性用於設定已發布成品的名稱時。 -
當發布了
default
以外的配置時。 -
當
MavenPom.addFilter()
用於發布與專案名稱不符的成品時。 -
當
maven-publish
或ivy-publish
外掛用於發布,且發布坐標與${project.group}:${project.name}
不符時。
複合建置取代無法運作的情況
即使明確宣告了相依性取代,某些建置在包含在複合建置中時也無法正確運作。此限制是因為取代的專案相依性將始終指向目標專案的 default
配置。任何時候專案的預設配置指定的成品和相依性與發布到儲存庫的內容不符時,複合建置都可能表現出不同的行為。
以下是一些已發布模組元數據可能與專案預設配置不同的情況
-
當發布了
default
以外的配置時。 -
當使用
maven-publish
或ivy-publish
外掛時。 -
當
POM
或ivy.xml
檔案在發布時被調整時。
使用這些功能的建置在包含在複合建置中時無法正常運作。
依賴於包含建置中的任務
雖然包含建置彼此隔離且無法宣告直接相依性,但複合建置可以宣告對其包含建置的任務相依性。包含建置是使用 Gradle.getIncludedBuilds() 或 Gradle.includedBuild(java.lang.String) 存取的,並且任務參考是透過 IncludedBuild.task(java.lang.String) 方法取得的。
使用這些 API,可以宣告對特定包含建置中任務的相依性
tasks.register("run") {
dependsOn(gradle.includedBuild("my-app").task(":app:run"))
}
tasks.register('run') {
dependsOn gradle.includedBuild('my-app').task(':app:run')
}
或者,您可以宣告對某些或所有包含建置中具有特定路徑的任務的相依性
tasks.register("publishDeps") {
dependsOn(gradle.includedBuilds.map { it.task(":publishMavenPublicationToMavenRepository") })
}
tasks.register('publishDeps') {
dependsOn gradle.includedBuilds*.task(':publishMavenPublicationToMavenRepository')
}
複合建置的限制
目前實作的限制包括
-
不支援發布內容與專案預設配置不符的包含建置。
請參閱 複合建置無法運作的情況。 -
如果多個複合建置包含相同的建置,則在平行執行時可能會發生衝突。
Gradle 不會在 Gradle 調用之間共享共享複合建置的專案鎖定,以防止並行執行。