1. 程式人生 > >Gradle學習(二十)——多專案構建詳解

Gradle學習(二十)——多專案構建詳解

跨專案配置

雖然子專案之間可以完全隔離單獨配置,但是子專案直接有相同特徵的情況也是很常見的,多個專案共享配置是更好的選擇。

配置和執行

gradle學習-十八-構建的生命週期這一篇中我們已經講過Gradle構建過程中的各個階段,我們繼續擴充套件到多專案構建,來看看多專案構建中的配置階段和執行階段。這裡所說的配置指的就是build.gradle檔案的執行,這意味著下載所有使用apply plugin定義的外掛。預設情況下,一個任務執行之前所有的專案的配置都會被執行。也就是單個專案的單個任務被請求時,多專案構建中的所有專案都會首先執行配置。這樣做的目的是為了可以靈活的訪問和更改Gradle專案的任意模組。

按需配置

配置注入特性和訪問完整的專案模組都成為可能,因為在執行專案之前每一個專案都進行了配置。但是在一些超大的專案構建面前就顯得不那麼有效了,比如有數百多個層級巢狀的子專案,大型多專案構建在配置階段所花的時間就會有點讓人難以忍受了,伸縮性對於Gradle來說成為一個非常重要的需求,幸運的是這個需求在1.4版本中就完成了,叫做”按需配置”

按需配置模式僅僅試圖對和請求任務相關的專案進行配置,也就是說僅僅會執行組成本次構建的專案的build.gradle檔案。通過這種方式,可以極大的縮短超大專案的配置時間。長遠的看,這種模式會成為構建的預設模式也是唯一模式,不過按需配置特性還在孵化階段,並不能保證每次構建都正確執行,但是這個特性在解耦專案的多專案構建中執行的非常好。

按需配置模式下,工程按照如下進行配置:

  • 首先根專案總是會配置,通過這種方式典型的公共配置會被支援,比如allprojectssubprojects的指令碼塊
  • 在執行構建所在目錄對應專案也會被配置,但是隻有在Gradle不會明確執行任何任務的情況下,這樣按需配置時預設的任務就會被執行。
  • 支援標準的工程依賴,相關的專案都會被配置,如果A專案compile的classpath中依賴於B專案,那麼構建A專案,將會觸發兩個專案的配置
  • 支援通過任務路徑宣告的依賴,並且相關專案也會被配置。例如someTask.dependsOn(":someOtherProject:someOtherTask")
  • 通過任務路徑在命令列下或者Tooling API中執行任務,那麼相關的專案也會被配置,比如構建projectA:projectB:someTask將會引起projectB專案被配置

這個特性的啟用可以通過屬性檔案gradle.properties配置org.gradle.configureondemand=(true,false),也可以通過命令列選項--configure-on-demand, --no-configure-on-demand

公共行為定義

讓我們來看下project tree的例子,這有個多專案構建,根專案是water,子專案是bluewhale

構建佈局

── water\
│   ├── bluewhale\
│   ├── build.gradle
│   └── settings.gradle

settings.gradle

include 'bluewhale'

你會發現怎麼bluewhale專案沒有構建指令碼,在Gradle中構建指令碼是可選的。誠然對於單專案構建來說,沒有構建指令碼是沒有意義的,但是對於多專案來說就不一樣了,讓我們繼續看water專案的指令碼,並且執行:
build.gradle

Closure cl = { Task task ->
    println "I'm $task.project.name"
}

task hello {
    doLast(cl)
}

project('bluewhale') {
    task hello {
        doLast(cl)
    }
}

然後執行任務

± % gradle -q hello                                                   
I'm water
I'm bluewhale

Gradle允許你在任意指令碼中訪問多專案構建的任意專案,Project API提供了project()方法,可以傳入路徑,獲得路徑對應的project物件,從任意指令碼配置專案構建的能力,我們把它叫做跨專案配置,Gradle通過configuration injection注入實現這個功能

為每個專案新增相同的任務,這看起來很不方便,我們來改進一下,首先為我們的多專案構建新增一個krill的子專案

構建佈局

├── water/
│   ├── bluewhale/
│   ├── build.gradle
│   ├── krill/
│   ├── settings.gradle

settings.gradle

include 'bluewhale', 'krill'

在修改一下構建指令碼

allprojects {
    task hello {
        doLast { Task task ->
            println "I'm $task.project.name"
        }
    }
}

執行任務的輸出

± % gradle -q hello                                                   
I'm water
I'm bluewhale
I'm krill

Gradle的Project API提供了allprojects屬性,它會返回當前專案和在它之下的所有子專案,如果使用閉包呼叫allprojects,那麼閉包的statements會委託給與allprojects關聯的所有專案。

其他的構建系統都是通過繼承來實現公共行為的定義,當然Gradle也支援,我們之後再講,但是Gradle的configuration injection作為定義公共行為的通用方式,看起來很強力一些。

還有一種方式就是通過通用的外部指令碼來共享配置,我們之後也會講到

子專案配置

Gradle還提供了僅僅訪問子專案的屬性

定義公共行為

build.gradle

allprojects {
    task hello {
        doLast { Task task ->
            println "I'm $task.project.name"
        }
    }
}

subprojects {
    hello {
        doLast {
            println " - I depend on water"
        }
    }
}

執行任務結果如下:

± % gradle -q hello                                                    
I'm water
I'm bluewhale
 - I depend on water
I'm krill
 - I depend on water

你可能已經注意到有兩段程式碼使用到了hello任務,第一段是帶task關鍵字的表示建立hello任務並提供一些基本的配置,第二段不帶task關鍵字,表示在之前的基礎上附件上一些配置。你可也只建立一次任務,但是之後可以附加任意數量的配置

增加特定的行為

我們可以在公共的行為上再增加一些特定的行為,通常我們會把特定的行為房子對應專案的構建指令碼中,在那裡可以apply這些行為。但是也可以在根專案的構建指令碼中這麼做:
build.gradle

allprojects {
    task hello {
        doLast { Task task ->
            println "I'm $task.project.name"
        }
    }
}

subprojects {
    hello {
        doLast {
            println " - I depend on water"
        }
    }
}

project(':bluewhale').hello {
    doLast {
        println " - I'm the largest animal that has ever lived on this planet."
    }
}

執行任務的結果

± % gradle -q hello                                                    
I'm water
I'm bluewhale
 - I depend on water
 - I'm the largest animal that has ever lived on this planet.
I'm krill
 - I depend on water

我們再來試下常用的定義特性行為的方法,就是將特定行為定義在對應專案的構建指令碼中

構建佈局

water
├── krill/
│   └── build.gradle
├── bluewhale/
│   └── build.gradle
├── build.gradle
└── settings.gradle

settings.gradle

include 'bluewhale','krill'

build.gradle

allprojects {
    task hello {
        doLast { Task task ->
            println "I'm $task.project.name"
        }
    }
}

subprojects {
    hello {
        doLast {
            println " - I depend on water"
        }
    }
}

bluewhale/build.gradle

hello.doLast {
    println " - I'm the largest animal that has ever lived on this planet."
}

krill/build.gradle

hello.doLast {
    println "- The weight of my species in summer is twice as heavy as all human beings."
}

然後執行任務

± % gradle -q hello                                                    
I'm water
I'm bluewhale
 - I depend on water
 - I'm the largest animal that has ever lived on this planet.
I'm krill
 - I depend on water
 - The weight of my species in summer is twice as heavy as all human beings.

專案過濾

為了展示configuration injection的威力,我們再增加一個tropicalFish專案,然後通過water專案來增加更多的行為

更加名字過濾

構建佈局

water
├── tropicalFish/
├── krill/
│   └── build.gradle
├── bluewhale/
│   └── build.gradle
├── build.gradle
└── settings.gradle

settings.gradle

include 'bluewhale','krill','tropicalFish'

build.gradle

allprojects {
    task hello {
        doLast { Task task ->
            println "I'm $task.project.name"
        }
    }
}

subprojects {
    hello {
        doLast {
            println " - I depend on water"
        }
    }
}

configure(subprojects.findAll { it.name != 'tropicalFish' }) {
    hello.doLast {
        println " - I love to spend time in the arctic waters"
    }
}

然後執行任務

± % gradle -q hello                                                   
I'm water
I'm bluewhale
 - I depend on water
 - I love to spend time in the arctic waters
 - I'm the largest animal that has ever lived on this planet.
I'm krill
 - I depend on water
 - I love to spend time in the arctic waters
 - The weight of my species in summer is twice as heavy as all human beings.
I'm tropicalFish
 - I depend on water

configure()方法將會接受一個列表引數,並將配置應用到列表中的專案。

根據屬性過濾

除了用名字過濾,還可以通過屬性來過濾

構建佈局

water
├── krill/
│   └── build.gradle
├── tropicalFish/
│   └── build.gradle
├── bluewhale/
│   └── build.gradle
├── build.gradle
└── settings.gradle

settings.gradle

include 'bluewhale','krill','tropicalFish'

bluewhale/build.gradle

ext.arctic=true

hello.doLast {
    println " - I'm the largest animal that has ever lived on this planet."
}

krill/build.gradle

ext.arctic=true

hello.doLast {
    println "- The weight of my species in summer is twice as heavy as all human beings."
}

tropicalFish/build.gradle

tropicalFish=false

build.gradle

allprojects {
    task hello {
        doLast { Task task ->
            println "I'm $task.project.name"
        }
    }
}
subprojects {
    hello {
        doLast {
            println " - I depend on water"
        }
        afterEvaluate { Project project ->
            if (project.arctic) {
                doLast {
                    println " - I love to spend time in the arctic waters"
                }
            }
        }
    }
}

執行任務的結果

± % gradle -q hello                                                    
I'm water
I'm bluewhale
 - I depend on water
 - I'm the largest animal that has ever lived on this planet.
 - I love to spend time in the arctic waters
I'm krill
 - I depend on water
 - The weight of my species in summer is twice as heavy as all human beings.
 - I love to spend time in the arctic waters
I'm tropicalFish
 - I depend on water

在water專案的構建檔案中我們使用afterEvaluate通知,這意味著我們在所以的子專案完成賦值之後傳遞了一個閉包再次給它們賦值。由於arctic屬性在這些專案的構建指令碼之中,因此我們必須這麼做。

多專案構建的執行規則

如果我們從跟專案water專案去執行hello任務,這樣可以非常直觀的看到所有任務的hello任務都被執行了,但是如果我們在子目錄執行hello任務呢,比如bluewhale子專案,我們來看一下執行任務的結果

± % gradle -q hello                                                   
I'm bluewhale
 - I depend on water
 - I'm the largest animal that has ever lived on this planet.
 - I love to spend time in the arctic waters

背後的基本規則非常簡單,Gradle會從當前目錄往下看各個層級,如果遇到hello名字的任務就會執行。有一點非常重要,Gradle會賦值於多專案構建的每一個專案,並且建立每一個任務物件,然後更加任務名和當前目錄,去過濾那些應該被執行的任務。因為Gradle的跨專案特性,在任意任務執行之前所有的專案都應該被配置,我們之後再進行深入研究。我們先來繼續看海產的例子,我們再分別給bluewhalekrill專案增加一個任務

bluewhale/build.gradle

ext.arctic = true

hello.doLast {
    println " - I'm the largest animal that has ever lived on this planet."
}

task distanceToIceberg {
    doLast {
        println "20 nautical miles"
    }
}

krill/build.gradle

ext.arctic = true

hello.doLast {
    println " - The weight of my species in summer is twice as heavy as all human beings."
}

task distanceToIceberg {
    doLast {
        println "10 nautical miles"
    }
}

執行任務結果

± % gradle -q distanceToIceberg                                        
20 nautical miles
10 nautical miles

去掉-q看一下:

± % gradle distanceToIceberg                                           

> Task :bluewhale:distanceToIceberg
20 nautical miles

> Task :krill:distanceToIceberg
10 nautical miles


BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

執行環境是water專案中,但是water專案和tropicalFish專案根本就沒有這個任務,但是Gradle並不關心這些,它會從含有distanceToIceberg任務的專案中去執行構建。

通過絕對路徑去執行任務

我們之前看到,可以進入多專案構建的任意目錄來執行構建,它會從當前目錄開始的層級去匹配相同的任務名而執行構建。但是Gradle還提供一種方法,就是通過絕對路徑來執行任務

在tropicalFish目錄中執行任務的輸出:

± % gradle -q :hello :krill:hello hello                                
I'm water
I'm krill
 - I depend on water
 - The weight of my species in summer is twice as heavy as all human beings.
 - I love to spend time in the arctic waters
I'm bluewhale
 - I depend on water
 - I'm the largest animal that has ever lived on this planet.
 - I love to spend time in the arctic waters
I'm tropicalFish
 - I depend on water

通過結果我們知道我們執行了waterkrilltropicalFishhello任務。前兩個任務是通過絕對路徑指定的,最後一個任務是採用之前講到的名字匹配規則執行的。

專案和任務的路徑

專案的路徑有兩個部分:可選的冒號開始代表根專案,根專案是唯一不需要指定名字的專案,剩下的部分用冒號分割,後一個是前一個的子專案。

任務的路徑就是專案的路徑加上任務名,比如:bluewhale:hello

如果不帶冒號開始,那麼就可以理解為專案的相對路徑。

依賴性

我們之前的章節的專案非常特別,他們只有配置依賴,而沒有執行依賴,我們這節就來看下他們的區別

執行依賴

依賴性和執行順序

構建佈局

messages
├── consumer/
│   └── build.gradle
├── producer/
│   └── build.gradle
├── build.gradle
└── settings.gradle

build.gradle

ext.producerMessage = null

settings.gradle

include 'consumer', 'producer'

producer/build.gradle

task action {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = "Watch the order of execution."
    }
}

consumer/build.gradle

task action {
    doLast {
        println "Consuming message: ${rootProject.producerMessage}"
    }
}

執行任務的結果

± % gradle -q action                                                  
Consuming message: null
Producing message:

是不是有點懵,執行順序和我們預期的不一樣,我們試著通過hack的方式來修改一下,把producer的專案名修改成aProducer

構建佈局

messages
├── consumer/
│   └── build.gradle
├── aProducer/
│   └── build.gradle
├── build.gradle
└── settings.gradle

build.gradle

ext.producerMessage = null

settings.gradle

include 'consumer', 'aProducer'

aProducer/build.gradle

task action {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = "Watch the order of execution."
    }
}

consumer/build.gradle

task action {
    doLast {
        println "Consuming message: ${rootProject.producerMessage}"
    }
}

然後再執行任務:

± % gradle -q action                                                   
Producing message:
Consuming message: Watch the order of execution.

現在可以達到我們的預期效果了,但這種方式可不是依賴,它只是改變了執行順序,怎麼改變的留給讀者思考一下

跨工程依賴的本質

當然跨專案的依賴可不僅限於同名任務,讓我們來改下兩個子專案的任務的名字。
producer/build.gradle

task produce {
    doLast {
        println "Producing message:"
        rootProject.producerMessage = "Watch the order of execution."
    }
}

consumer/build.gradle

task consume(dependsOn:':producer:produce') {
    doLast {
        println "Consuming message: ${rootProject.producerMessage}"
    }
}

執行任務的結果:

± % gradle -q consume                                                  
Producing message:
Consuming message: Watch the order of execution.

配置時依賴

我們再給producer專案新增一個屬性,然後建立consumerproducer之間的配置時依賴。

producer/build.gradle

rootProject.producerMessage = "Watch the order of execution."

consumer/build.gradle

def message = rootProject.producerMessage

task consume {
    doLast {
        println "Consuming message: ${message}"
    }
}

執行任務如下:

± % gradle -q consume                                                  
Consuming message: null

預設的賦值順序是按專案的字母順序來的,因此consumer總是在producer之前被賦值,producerMessage總是在consumer讀取之後才會被賦值,Gradle針對這種情況也提供瞭解決方案。

consumer/build.gradle

evaluationDependsOn(":producer")

def message = rootProject.producerMessage

task consume {
    doLast {
        println "Consuming message: ${message}"
    }
}

執行任務如下:

± % gradle -q consume                                                  
Consuming message: Watch the order of execution.

使用在evaluationDependsOn方法會讓consumer被賦值之前先對producer進行賦值,這樣做這是為了講明白這種情況,但是實際操作中會直接去讀關鍵的屬性就可以了。

consumer/build.gradle

task consume {
    doLast {
        println "Consuming message: ${rootProject.producerMessage}"
    }
}

然後執行任務如下:

± % gradle -q consume                                                  
Consuming message: Watch the order of execution.

配置時依賴和執行時依賴是非常不同的,配置依賴發生在專案之間,而執行時依賴是發生在任務之間。還要注意即使構建的是從子專案開始,所有專案也會全部被配置,通常情況下都是先配置父專案再配置子專案。

如果你想改變這種順序,你可以使用evaluationDependsOnChildren()方法。

在相同的巢狀級別上,配置順序取決於專案名在字母數字上的順序,最常見的就是擁有相同生命週期的多專案構建,比如所以的專案都是java外掛的專案,他們根據這種順序自然而然的建立起了某種依賴,有時候依賴並不需要顯示的申明。

例項講解

讓我來看個典型的例項,一個根專案包含2個子web專案,根專案為這兩個web專案建立釋出包,在這個示例中我們僅僅建立一個構建檔案並且使用跨專案配置。

構建佈局

webDist
├── hello/
│   └── src/
│       └── main/
│           └── java/
│               └── com/
│                   └── sweetop/
│                       └── testgradle/
│                           └── HelloServlet.java
├── date/
│   └── src/
│       └── main/
│           └── java/
│               └── com/
│                   └── sweetop/
│                       └── testgradle/
│                           └── DateServlet.java
├── build.gradle
└── settings.gradle

settings.gradle

include 'date','hello'

build.gradle

allprojects {
    apply plugin: 'java'
    group 'com.sweetop.testgradle'
    version '1.0'
}

subprojects {
    apply plugin: 'war'
    repositories {
        jcenter()
    }
    dependencies {
        compile 'javax.servlet:servlet-api:2.5'
    }
}

task explodedDist(type: Copy) {
    subprojects {
        from tasks.withType(War)
    }
    into "$buildDir/explodedDist"
}

這種依賴關係很有趣,顯然datehellowebDist有配置時依賴,因為子專案的構建依賴都是webDist注入的。執行依賴又是另一面,webDist專案又要依賴於datehello專案的構建輸出。還有第三種依賴就是webDist配置依賴於datehello,因為它要知道archivePath的具體路徑,但是他是在執行時獲取這些資訊的,因此就不存在所謂的迴圈依賴了。

像這樣的依賴問題,在日常構建中都是比較常見的。

專案lib依賴

想象一下,如果一個專案在編譯路徑中需要另外一個專案生產的jar包,而且不僅需要jar還需要這種依賴傳遞關係,這種情況下怎麼辦。顯然這在java專案構建中經常會見到。Gradle也完美的解決了這種問題。

構建佈局

java
├── build.gradle
├── settings.gradle
├── shared/
│   └── src/
│       └── main/
│           └── java/
│               └── com/
│                   └── sweetop/
│                       └── testgradle/
│                           └── shared/
│                               └── Helper.java
├── api/
│   └── src/
│       └── main/
│           └── java/
│               └── com/
│                   └── sweetop/
│                       └── testgradle/
│                           ├── apiImpl/
│                           │   └── PersonImpl.java
│                           └── api/
│                               └── Person.java
└── services/
    └── personService/
        └── src/
            ├── test/
            │   └── java/
            │       └── services/
            │           └── PersonServiceTest.java
            └── main/
                └── java/
                    └── com/
                        └── sweetop/
                            └── testgradle/
                                └── services/
                                    └── PersonService.java

這裡有三個子專案shared,apipersonServicepersonService依賴於其他兩個子專案,api依賴於shared專案,services也是子專案,但是它只作為一個容器存在,既沒有構建指令碼,也沒有其他額外的注入。

settings.gradle

include 'api','shared','services:personService'

build.gradle

subprojects {
    apply plugin: 'java'
    group = "com.sweetop.testgradle"
    version = "1.0"
    repositories {
        jcenter()
    }
    dependencies {
        testCompile "junit:junit:4.12"
    }
}

project(":api") {
    dependencies {
        compile project(":shared")
    }
}

project(":services:personService") {
    dependencies {
        compile project(":shared"),project(":api")
    }
}

所有的構建邏輯都寫在根專案的build.gradle中,lib依賴是執行時依賴的一種特別方式,它可以首先構建其他專案,並且把生成的jar放在當前的classpath中,也可以傳遞依賴,將依賴的依賴的jar也放進來。在我們的例子中,他就會先構建shared專案,再構建api專案,最後才構建services專案。如果你進入到api目錄,執行gradle compile,這種依賴關係也會照樣進行,它會先構建shared專案,再構建api專案。

禁用專案之間的依賴

有時候你需要禁用掉這種依賴,你可以在命令列增加-a選項。

並行構建

隨著越來越多的cpu核數出現在開發者電腦和CI伺服器上,如何應用這些多核對Gradle來說變得很重要,並行構建主要想解決如下問題:

  • 減少多專案構建總的構建時間
  • 讓小專案的構建結果儘快返回,而不需要等其他專案構建完成

儘管通過Test.setMaxParallelForks(int)已經讓Gradle可以做到測試上的並行,但是這裡說的是專案構建過程中的並行,並行執行是個孵化中的特性,使用它之前我們來看下它是如何工作的。

並行構建可以讓解耦的專案在多專案構建中並行執行,雖然並行構建在配置階段不要求強制解耦,但是Gradle為完全解耦的專案提供了更為強大的一組特性,這些特性包括:

  • 按需配置
  • 並行配置
  • 配置重用
  • 專案級別的up-to-date檢查
  • 在構建專案依賴時優先使用以及構建好的工件

開啟並行構建的方式有兩種,一種是在命令列下使用--parallel選項,另外一種就是配置Gradle的屬性org.gradle.parallel=true,如果你不特別指定並行的執行緒數,那麼預設會以可以用的CPU核數作為執行緒數。每個wooker只專注的給定的專案去執行任務。任務依賴是同樣支援的,worker會先執行上游任務。

需要注意的是,根據字母數字順序的依賴這並行構建中就無法保證順序了,因為在並行模式中無法確定哪些任務先完成,因此你需要明確的宣告任務依賴和任務的輸入輸出,來避免任務的執行順序問題。

解耦專案

Gradle允許任何專案在執行階段和配置階段訪問其他專案,雖然為構建者提供了比較大的許可權和靈活性,但同時也限制了Gradle構建這些專案的靈活性。比如它會影響多專案並行構建,配置專案的單個子集和使用預先存在的工件來代替專案依賴的正確性。

如果兩個專案不直接訪問彼此的專案模型,那麼它們可以稱為解耦。解耦的專案僅僅可以通過宣告的依賴相互作用:專案依賴和任務依賴。任何其他方式的專案直接的互動都會被耦合(比如修改另一個專案的物件或者讀取另一個專案中的值)。配置階段進行耦合的結果就是如果執行configuration on demand那麼可以會有各種缺陷存在。在執行階段耦合的結果就是,如果執行並行構建,那麼因為有些任務執行的太遲,而達不到並行構建的效果。Gradle並不會去檢視檢測耦合並警告使用者,因為引入耦合的可能性太多了。

最普遍的耦合的方式就是配置注入,它可能並不是立即顯現,但是Gradle的一些特性比如關鍵字allprojectssubprojects會讓你的專案自動被耦合。這些關鍵字通常在根專案中的build.gradle檔案中出現,根專案除了一些常定義的配置以外也沒有別的作用,但是Gradle來說根專案也是一個完全的專案,並且通過allprojects耦合到所有子專案中,根專案和子專案的耦合並不會影響configuration on demand,但是在任意子專案中使用allprojectssubprojects都會影響。

這意味著使用任意形式的共享構建指令碼邏輯或者配置注入(allprojects, subprojects)都會引起專案的耦合,我們在擴充套件專案結構的概念並且使用解耦專案的特性時,我們還需要引入新的特性來解決常見用例(比如配置注入),使得它們不會被耦合。

為了更好的使用跨專案配置,而不引起並行和按需配置產生的問題,需要遵循以下規則:

  • 避免子專案的build.gradle引用其他子專案
  • 避免在執行階段更改其他專案的配置

多專案構建和測試

對於單個專案來說,java外掛的build任務通常用於編譯,測試和執行程式碼樣式檢查(如果使用了CodeQuality外掛)。而在多專案構建中,通常需要多個專案都做這些事情,buildNeededbuildDependents可以幫助做到這些。

在我們上一個例子中:services:personservice專案依賴於:api:shared專案,:api也依賴於:shared專案。

假設你在:api專案中做了一下更改,在執行clean任務後還沒有執行build任務,你想要構建必須支援的jar,但是僅僅在更改的專案上執行程式碼質量檢查和測試任務,那麼你可以使用build任務。

gradle :api:build

:shared:compileJava
:shared:processResources NO-SOURCE
:shared:classes
:shared:jar
:api:compileJava
:api:processResources NO-SOURCE
:api:classes
:api:jar
:api:assemble
:api:compileTestJava NO-SOURCE
:api:processTestResources NO-SOURCE
:api:testClasses UP-TO-DATE
:api:test NO-SOURCE
:api:check UP-TO-DATE
:api:build

BUILD SUCCESSFUL in 0s
4 actionable tasks: 4 executed

當你在一個標準的開發週期中工作時,你需要不斷重複:api專案,而你明確的知道只在這個專案中更改了檔案,你可能不想要承擔:shared:compile的開銷來檢視:shared專案更改了什麼,你可以選擇使用-a選項,它會用快取中的jars來解決專案的lib依賴問題而不會去再次構建依賴的專案,我們再構建一次:

gradle -a :api:build                                                 

:api:compileJava
:api:processResources NO-SOURCE
:api:classes
:api:jar
:api:assemble
:api:compileTestJava NO-SOURCE
:api:processTestResources NO-SOURCE
:api:testClasses UP-TO-DATE
:api:test NO-SOURCE
:api:check UP-TO-DATE
:api:build

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed

如果你是剛剛從版本控制系統中拿到了最新版本的原始碼,那麼你將不僅僅是想構建所依賴的專案,還想測試一下它們,buildNeeded將會測試所有testRuntime配置的lib依賴的專案。

gradle -a :api:buildNeeded                                                                                         !3626
:api:compileJava UP-TO-DATE
:api:processResources NO-SOURCE
:api:classes UP-TO-DATE
:api:jar UP-TO-DATE
:api:assemble UP-TO-DATE
:api:compileTestJava NO-SOURCE
:api:processTestResources NO-SOURCE
:api:testClasses UP-TO-DATE
:api:test NO-SOURCE
:api:check UP-TO-DATE
:api:build UP-TO-DATE
:shared:compileJava
:shared:processResources NO-SOURCE
:shared:classes
:shared:jar
:shared:assemble
:shared:compileTestJava NO-SOURCE
:shared:processTestResources NO-SOURCE
:shared:testClasses UP-TO-DATE
:shared:test NO-SOURCE
:shared:check UP-TO-DATE
:shared:build
:shared:buildNeeded
:api:buildNeeded

BUILD SUCCESSFUL in 1s
4 actionable tasks: 2 executed, 2 up-to-date

你可能重構了:api專案的某些部分,該專案應用到了其他專案中,如果你有一些型別方面的更改,那麼僅僅測試:api專案還是不夠的,你還要測試那些依賴於:api的專案,buildDependents將會測試所有在testRuntime配置中專案lib依賴於:api的專案。

gradle -a :api:buildDependents                                                                                     !3628
:api:compileJava
:api:processResources NO-SOURCE
:api:classes
:api:jar
:api:assemble
:api:compileTestJava NO-SOURCE
:api:processTestResources NO-SOURCE
:api:testClasses UP-TO-DATE
:api:test NO-SOURCE
:api:check UP-TO-DATE
:api:build
:services:personService:compileJava
:services:personService:processResources NO-SOURCE
:services:personService:classes
:services:personService:jar
:services:personService:assemble
:services:personService:compileTestJava
:services:personService:processTestResources NO-SOURCE
:services:personService:testClasses
:services:personService:test
:services:personService:check
:services:personService:build
:services:personService:buildDependents
:api:buildDependents

BUILD SUCCESSFUL in 2s
8 actionable tasks: 8 executed

最終,你可能需要構建和測試所有專案,那麼你在根專案中執行構建任務即可,它的所有子專案都會將執行同名的任務,因此你可以在根專案中使用gradle build來構建和測試所有專案。

多專案和buildSrc

在多專案中僅僅在根專案中有一個buildSrc目錄即可