可以作爲官方指南(以下簡稱"指南")Creating Multi-project Builds的一個翻譯版本,與指南不同和參考的地方會做聲明。
首先說下這裏的“工程”,有兩個含義: 根工程(root project)和子工程(subproject)。根工程用於全局的管理,子工程作爲根工程的一個模塊(module). 爲了簡明, 不引起混淆情況下,文中"項目"指根工程, "模塊"指子工程。
Gradle中統一使用Project 來實例化對應的構建腳本(build.gradle). 可以簡單的將build.gradle所在的目錄稱爲一個project, 如果這個project又是用於管理其他project的就稱爲root project, 而其餘的都是subproject. 模塊化是gradle構建的一個重要功能, "模塊"也是集成開發環境中提供的稱呼, 比如 IntelliJ IDEA裏面就是"New Module". 實在理不清就直接用英文root/sub project來代替,不需要翻譯了, 擼起袖子敲上一個demo可能就明白了.
多模塊的構建有助於項目的模塊化, 可以使你專注於大項目中的某個區域,而Gradle負責項目的依賴。這個多模塊demo同時作爲gradle命令的一個練習.
環境
列舉我的開發環境, 以本機環境自行微調,不細表. jdk1.8, gradle-4.6-all, Linux系統. 另外markdown的原因無法提供指南中groovy和kotlin兩個版本, 這裏用groovy(應該是目前流行的方式), 所以kotlin環境也沒必要列出
創建項目
首先爲新項目創建一個文件夾,並將Gradle Wrapper添加到項目中(對比指南)。
$ mkdir creating-multi-project-builds
$ cd creating-multi-project-builds/
$ gradle wrapper --gradle-version 4.6 --distribution-type all #指定已有版本,免得下載
$ ./gradlew init #初始化項目
執行後項目結構:
creating-multi-project-builds
├── build.gradle #當前項目的配置腳本, 頂級構建腳本
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar #Gradle Wrapper執行的jar
│ └── gradle-wrapper.properties #Gradle Wrapper配置屬性
├── gradlew #Unix系統 Gradle Wrapper執行腳本
├── gradlew.bat #Windows系統 Gradle Wrapper執行腳本
└── settings.gradle #Gradle構建設置
可以刪除 build.gradle和settings.gradle中的註釋.
配置
build.gradle
allprojects {
repositories {
jcenter()
}
}
allprojects
塊用於添加根工程和所有子工程的配置, 類似的subprojects
塊用於添加所有子工程(模塊)的配置. 可以在根工程中任意的使用這兩個塊. 現在通過subproject
來配置所有模塊的版本, 在頂級build.gradle中加入:
subprojects {
version = '1.0'
}
添加一個Groovy庫模塊
$ mkdir greeting-library
$ cd greeting-library
$ ../gradlew init --type groovy-library
保留build.gradle和src目錄, 其餘文件(夾)全部刪除, 添加include 'greeting-library'
到項目頂級settings.gradle中, 刪掉不必要的註釋. 這樣不需要手動創建約定的源碼目錄.
修改 greeting-library/build.gradle
plugins {
id 'groovy'
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.13'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4',{
exclude module:'groovy-all'
}
}
repositories {
jcenter()
}
settings.gradle
include 'greeting-library'
最後, 在 greeting-library
中創建包目錄 greeter
$ mkdir -p src/main/groovy/greeter
$ mkdir -p src/test/groovy/greeter
添加一個 GreetingFormatter
類到src/main/groovy/greeter
.
greeting-library/src/main/groovy/greeter/GreetingFormatter.groovy
package greeter
import groovy.transform.CompileStatic
@CompileStatic
class GreetingFormatter {
static String greeting(final String name) {
"Hello, ${name.capitalize()}"
}
}
添加Spock 框架測試類GreetingFormatterSpec
到src/test/groovy/greeter
.
創建greeting-library/src/test/groovy/greeter/GreetingFormatterSpec.groovy
package greeter
import spock.lang.Specification
class GreetingFormatterSpec extends Specification {
def 'Creating a greeting'() {
expect: 'The greeting to be correctly capitalized'
GreetingFormatter.greeting('gradlephant') == 'Hello, Gradlephant'
}
}
從根工程目錄中執行 ./gradlew build
. 單個模塊並不能真正構建多模塊下面添加一個將使用這個庫的模塊.
添加Java應用模塊
$ mkdir greeter
$ cd greeter
$ ../gradlew init --type java-application
$ rm -r gradle* sett* src/*/*/*.java #刪除多餘的文件(夾)
$ mkdir -p src/main/java/greeter src/test/java/greeter
添加include 'greeter'
到頂級settings.gradle中(另起一行)
創建java文件greeter/src/main/java/greeter/Greeter.java
package greeter;
public class Greeter {
public static void main(String[] args) {
final String output = GreetingFormatter.greeting(args[0]);
System.out.println(output);
}
}
目標是java應用,需要告訴Gradle那個類作爲入口點. 修改當前模塊build.gradle,將一個擁有main
方法的java類名設置給mainClassName
:
mainClassName = 'greeter.Greeter'
項目根目錄執行 ./gradlew build
:
/xxx/creating-multi-project-builds/greeter/src/main/java/greeter/Greeter.java:5: 錯誤: 找不到符號
final String output = GreetingFormatter.greeting(args[0]);
^
符號: 變量 GreetingFormatter
位置: 類 Greeter
1 個錯誤
FAILURE: Build failed with an exception.
構建失敗這是因爲greeter
模塊不知道去哪裏找greeting-library
。創建模塊不會自動在其他模塊中可用——這樣會使得項目變得脆弱(任何改動都會波及整個項目)。Gradle具有特定語法將一個模塊鏈接到另一個模塊的依賴項。編輯greeter/build.gradle添加:
dependencies {
compile project(':greeting-library')
}
執行 ./gradlew build --console verbose
將會看到以下輸出:
$ ./gradlew build --console verbose
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
> Task :greeting-library:compileJava NO-SOURCE
> Task :greeting-library:compileGroovy
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
> Task :greeting-library:processResources NO-SOURCE
> Task :greeting-library:classes
> Task :greeting-library:jar
> Task :greeter:compileJava
> Task :greeter:processResources NO-SOURCE
> Task :greeter:classes
> Task :greeter:jar
> Task :greeter:startScripts
> Task :greeter:distTar
> Task :greeter:distZip
> Task :greeter:assemble
> Task :greeter:compileTestJava NO-SOURCE
> Task :greeter:processTestResources NO-SOURCE
> Task :greeter:testClasses UP-TO-DATE
> Task :greeter:test NO-SOURCE
> Task :greeter:check UP-TO-DATE
> Task :greeter:build
> Task :greeting-library:assemble
> Task :greeting-library:compileTestJava NO-SOURCE
> Task :greeting-library:compileTestGroovy
> Task :greeting-library:processTestResources NO-SOURCE
> Task :greeting-library:testClasses
> Task :greeting-library:test
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
> Task :greeting-library:check
> Task :greeting-library:build
我用的Gradle版本執行
./gradlew build
輸出沒有指南的詳細,-h
查看幫助中的--console
如下描述--console Specifies which type of console output to generate. Values are 'plain', 'auto' (default), 'rich' or 'verbose'.
因此採用
--console verbose
版本輸出來展示詳細的構建執行時序
請注意每個模塊如何在輸出中作爲前綴,以便您知道正在執行哪個模塊的任務。另外Gradle在移動到另一個模塊之前不會處理所有任務。
添加測試以確保應用程序本身的代碼有效。由於Spock 框架也是測試Java代碼的流行方法,因此首先將Groovy插件添加到greeter
模塊的構建腳本中來創建測試。這需要groovy
插件, 並且已經包含了java
插件,因此您可以將java
替換成groovy
:
greeter/build.gradle
plugins {
id 'groovy'
}
dependencies {
//將junit替換成下面
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
創建測試類greeter/src/test/groovy/greeter/GreeterSpec.groovy
package greeter
import spock.lang.Specification
class GreeterSpec extends Specification {
def 'Calling the entry point'() {
setup: 'Re-route standard out'
def buf = new ByteArrayOutputStream(1024)
System.out = new PrintStream(buf)
when: 'The entrypoint is executed'
Greeter.main('gradlephant')
then: 'The correct greeting is output'
buf.toString() == "Hello, Gradlephant\n".denormalize()
}
}
切換到項目根目錄下執行greeter
的test
任務:./gradlew :greeter:test
添加文檔
這部分直接參看指南Add documentation, 不想看也可以跳過
重構公共的構建腳本
此時您可能已經注意到在模塊greeting-library
和greeter
構建腳本中都有通用腳本代碼。Gradle的一個關鍵特性是能夠在根項目中放置這樣的公共構建腳本代碼。編輯根目錄下的build.gradle增加:
configure(subprojects.findAll { it.name == 'greeter' || it.name == 'greeting-library' }) {
apply plugin: 'groovy'
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4', {
exclude module: 'groovy-all'
}
}
}
刪除greeting-library下已在頂級build.gradle中的所有配置:
plugins {
id 'groovy'
}
dependencies {
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4',{
exclude module:'groovy-all'
}
}
repositories {
jcenter()
}
此時greeting-library/build.gradle只剩下
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.13'
}
對於greeter類似的刪除重複代碼, 在根目錄重新運行./gradlew clean build
以確保它仍然有效。此外, 項目還可以在IntelliJ IDEA等支持gradle構建的IDE中打開來進行開發。
總結
按照上面的步驟進行操作, 您已經掌握了
- 通過組合多個模塊來創建模塊化的項目。
- 讓一個模塊消費另一個模塊的產品。
- 輕鬆使用多語言項目(Java + Groovy + ...)。
- 在所有模塊中運行名字相似的任務。
- 在特定模塊中運行任務而無需切換到該模塊目錄下。
- 將通用的模塊設置重構到根項目中。
- 從根項目中有選擇地配置模塊。