03.16 Gradle學習記錄010 多工程構建 part2

詳細學習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
  • 如果重構了被依賴工程,那麼需要測試所有依賴這個工程的其他工程
gradle :被重構工程名:buildDependents
  • 如果工程依賴的其他工程可能已經被重構
gradle :依賴工程名:buildNeeded
  • 如果是單工程構建,這個工程被部分改變了,只想構建被改變的部分
gradle :工程名:build

一般情況下,都會使用gradle build,因為它最安全。當然,速度也最慢。java工程一般執行了這個命令後,會在build文件夾下生成jar或者war文件。取決於構建腳本的指定。


分享到:


相關文章: