詳細學習Gradle的多工程構建。第二部分。該學習記錄基於Gradle官方網站資料。本篇參考鏈接如下:
https://docs.gradle.org/current/userguide/multi_project_builds.html
工程間的相互依賴
執行階段的依賴
這個示例展示了工程之間的相互影響。
根工程裡定義了一個變量。分別在子工程的任務的doLast閉包中對其進行賦值和輸出。根據子工程任務執行順序的不同,輸出結果也不同。
目錄結構
messages
├── build.gradle
├── consumer
│ └── build.gradle
├── producer
│ └── build.gradle
└── settings.gradle
根工程messages的build.gradle文件:
// 定義了一個變量。相當於全局變量。但是並沒有給它賦值
ext.producerMessage = nullge
根工程messages的settings.gradle文件:注意文件名字不要弄錯,否則會找不到子工程。
// 引入子工程consumer與producer
include 'consumer', 'producer'
子工程consumer的build.gradle文件:
task action {
doLast {
// 這裡輸出的是根工程中定義的變量
println("Consuming message: ${rootProject.producerMessage}")
}
}
子工程producer的build.gradle文件:
task action {
doLast {
println "Producing message:"
// 給根工程定義的變量賦值
rootProject.producerMessage = 'Watch the order of execution.'
}
}
輸出:
$ gradle action
> Task :consumer:action
Consuming message: null
> Task :producer:action
Producing message:
BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed
由輸出可知,consumer的任務先於producer執行。這是因為Gradle自動按照子工程名字的字母順序來執行他們的任務。如果把producer工程的名字更改為aproducer(同時要修改根工程settings.gradle內的子工程名),那麼輸出如下:
$ gradle action
> Task :aproducer:action
Producing message:
> Task :consumer:action
Consuming message: Watch the order of execution.
BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
那麼單獨執行consumer的action任務結果什麼呢
$ gradle :consumer:action
> Task :consumer:action
Consuming message: null
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
說明指定工程名的時候,這個工程不依賴的其他工程不會被構建執行。
一個模擬真實的多工程的java Web構建
這個示例展示的是通過根工程對子工程的war包生成和抽出。
WebDist
├── build.gradle
├── date
│ └── src
│ └── main
│ └── java
│ └── date
│ └── Library.java
├── hello
│ └── src
│ └── main
│ └── java
│ └── hello
│ └── Library.java
└── settings.gradle
根工程WebDist的settings.gradle文件:
// 聲明根工程名
rootProject.name = 'webDist'
// 引入子工程date和hello
include 'date', 'hello'
根工程WebDist的build.gradle文件:
// 包括根工程在內的所有工程都引入java插件,並且聲明group和version
allprojects {
apply plugin: 'java'
group = 'org.gradle.sample'
version = '1.0'
}
// 所有子工程引入war插件用於打包。
subprojects {
apply plugin: 'war'
// 聲明倉庫。需要的api會從倉庫中自動下載
repositories {
mavenCentral()
}
// 聲明需要依賴的api
dependencies {
compile "javax.servlet:servlet-api:2.5"
}
}
// 將達成war包的子工程拷貝到指定文件夾
task explodedDist(type: Copy) {
into "$buildDir/explodedDist"
// 這裡指定的是子工程的類型為War的任務的輸出結果是explodedDist任務的輸入
// 很拗口,但是很重要
subprojects {
from tasks.withType(War)
}
}
以上展示了一個比較複雜的依賴邏輯。子工程對根工程有配置階段依賴,因為子工程的配置都是在根工程中做的。
根工程又對子工程有執行階段依賴,因為根工程拷貝的war包是子工程war任務執行之後生成。
同時根工程對子工程也有配置階段依賴,因為根工程必須知道子工程生成war包的位置。但是,war包的位置並不是在配置階段告訴根工程的,而是在執行階段。這樣避免了循環依賴。
我的結論是某階段的依賴, 不一定在那個階段完成。後續學習應該有更深入的知識。
工程類庫的依賴
如果一個工程需要在其編譯路徑中引入另外一個工程提供的jar包,或者不僅僅是這個jar包,還有這個jar包本身需要的依賴,可以使用如下示例展示的方法。
注意如果安裝了完全版的Gradle,這個示例可以在C:\\Gradle\\samples\\\\userguide\\multiproject\\dependencies\\java文件夾中找到。
工程路徑:
java
├── api
│ └── src
│ ├── main
│ │ └── java
│ │ └── ...
│ └── test
│ └── java
│ └── ...
├── build.gradle
├── services
│ └── personService
│ └── src
│ ├── main
│ │ └── java
│ │ └── ...
│ └── test
│ └── java
│ └── ...
├── settings.gradle
└── shared
└── src
└── main
└── java
└── ...
personService工程對另外兩個工程有類庫依賴(lib dependency)。aip工程對shared工程有類庫依賴。services工程是一個空的子工程,只起到包裹personService工程的作用。表示工程的階層關係,需要用到冒號":"
根工程的build.gradle文件:
subprojects {
// 為所有子工程引入java插件
apply plugin: 'java'
group = 'org.gradle.sample'
version = '1.0'
// 指定倉庫
repositories {
mavenCentral()
}
// 指定測試依賴,依賴於junit4.12
dependencies {
testImplementation "junit:junit:4.12"
}
}
// api子工程
project(':api') {
// 明確指定api子工程依賴於shared子工程的實現
dependencies {
implementation project(':shared')
}
}
// services下的personService子工程
project(':services:personService') {
// 明確指定personService子工程以來於shared和api子工程的實現
dependencies {
implementation project(':shared'), project(':api')
}
}
上述的類庫依賴(lib dependency)是一種比較特殊的執行階段依賴。被依賴的工程首先會被build打包為jar,添加到依賴工程的classpath裡。當有依賴傳遞關係的時候,比如工程1依賴工程2,工程2又依賴工程3,那麼工程3會首先被build成jar包。
依賴於其他工程的任務輸出
示例展示了consumer工程依賴於producer工程的buildInfo的輸出。buildInfo生成一個property配置文件,放在特定的目錄下。然後producer的構建腳本把這個配置文件設定為sourceSets的一部分。
producer工程的build.gradle文件
// 這是一個BuildInfo類型的任務。生成一個配置文件。
task buildInfo(type: BuildInfo) {
version = project.version
outputFile = file("$buildDir/generated-resources/build-info.properties")
}
sourceSets {
main {
// 第一個參數指定文件夾,第二個參數指定文件的做成者,這裡是buildInfo任務
output.dir(buildInfo.outputFile.parentFile, builtBy: buildInfo)
}
}
※關於sourceSets現在還不太理解。之後會展開學習。
consumer工程的build.gradle文件:
// 指定依賴
dependencies {
// 利用runtimeOnly指定這個依賴只在執行時發生
runtimeOnly project(':producer')
}
工程的並行執行
在執行任務的時候使用 --parallel參數,會使工程的構建和執行變為並行。並行當然會提高運行速度,但是需要精心編寫構建腳本,防止他們的構建衝突。官網在這裡並沒有多做介紹,之後應該有詳細章節進行學習。
工程的解耦
其實這個概念也只是一個抽象的概念。一般理解為工程有依賴關係,或者互相使用對方的自定義參數等,都可以稱為這兩個工程是耦合的。意外的情況就是非耦合。使用allprojects屬性或者subprojects會是所有工程都變成耦合。耦合性在其他語言中也有相似概念。不做過多介紹。
多工程構建和對構建的測試
工程的構建有如下幾種形式
- 測試所有工程之間的依賴關係是否成立,並且構建所有工程
- 如果重構了被依賴工程,那麼需要測試所有依賴這個工程的其他工程
- 如果工程依賴的其他工程可能已經被重構
- 如果是單工程構建,這個工程被部分改變了,只想構建被改變的部分
一般情況下,都會使用gradle build,因為它最安全。當然,速度也最慢。java工程一般執行了這個命令後,會在build文件夾下生成jar或者war文件。取決於構建腳本的指定。
閱讀更多 一個經驗用十年的碼農 的文章