Gradle系列4--多模塊構建

可以作爲官方指南(以下簡稱"指南")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 框架測試類GreetingFormatterSpecsrc/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()
    }
}

切換到項目根目錄下執行greetertest任務:./gradlew :greeter:test

添加文檔

這部分直接參看指南Add documentation, 不想看也可以跳過

重構公共的構建腳本

此時您可能已經注意到在模塊greeting-librarygreeter構建腳本中都有通用腳本代碼。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 + ...)。
  • 在所有模塊中運行名字相似的任務。
  • 在特定模塊中運行任務而無需切換到該模塊目錄下。
  • 將通用的模塊設置重構到根項目中。
  • 從根項目中有選擇地配置模塊。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章