Gradle之task的使用
一、構建塊。
每一個Gradle構建指令碼中都會包含三個基本的構建塊,project、task和property。每一個構建都至少包含一個project,一個或者多個task,在多專案的構建中一個project可以依賴於其它的project,task之間也形成一個依賴關係圖來確保它們之間的執行順序,下圖顯示了這種依賴:
1、專案:project,一個專案代表一個正在構建的元件(比如一個JAR檔案),或者是一個想要完成的目標,如部署應用程式。每一個Gradle指令碼至少定義一個專案,當構建程序啟動之後,Gradle基於build.gradle檔案中的配置例項化org.geadle.api.project類,在構建指令碼中使用project變數引用該例項,通過project變數,你可以訪問Gradle的所有特性。下圖顯示了該API介面和重要的方法:
2、任務:task,task有一些重要的功能:任務動作(task action)和任務依賴(task dependency)。task action定義了任務執行時最小的工作單元,task dependency定義了task之間的依賴關係,例如在某個task執行之前要執行另外一個task,尤其是需要另一個task的輸出作為輸入的時候。task的API表示是org.gradle.api.task介面,結構如下圖:
3、屬性:property,每一個project和task都提供了可以通過getter和setter方法訪問其屬性,Gadle允許使用者擴充套件屬性自定義一些變數。
①Gradle屬性通過gradle.properties檔案中的宣告新增到專案中,在android專案構建中我們也經常把versionName、versionCode定義在該檔案中,在module中使用。
//gradle.properties指令碼:
exampleProp=myValue
someOtherProp=455
//build.gradle指令碼中可以直接使用上述定義的兩個變數:
assert project.exampleProp =='myValue'
/**直接引用gradle.properties檔案中定義的屬性 $someOtherProp */
task printGradleProperty << {
println"Second property: $someOtherProp"
}
②擴充套件屬性需要使用ext名稱空間:
project.ext.myProp ='myValue'
ext {
someOtherProp =123
}
/**斷言 如果判斷為false,則構建會失敗,使用ext名稱空間訪問屬性是可選的*/
assert myProp =='myValue1'
printlnproject.someOtherProp
ext.someOtherProp =567
二、task的使用,每一個task都是org.gradle.api.DefaultTask
型別,在構建指令碼中我們可以直接使用屬性名使用該類中的相關屬性,在底層Groovy會為你呼叫相關的getter和setter方法去訪問這些屬性。
1、為task新增動作(action):在構建指令碼中,我們可以給同一個task新增多個動作,每一個task保持了一個動作列表,在執行的時候,這些動作會按順序執行:
//build.gradle 指令碼
version = '0.1-SNAPSHOT'
task printVersion {
doFirst {
println "Before reading the project version"
}
doLast {
println "Version: $version"
}
}
/**先新增的doFirst動作後執行,先新增的doLast動作先執行*/
printVersion.doFirst { println "Second action" }
printVersion.doFirst { println "First action" }
printVersion.doLast { println "Not Last action" }
printVersion.doLast { println "Last action" }
//執行printVersion :
> gradle printVersion
> Task :printVersion
First action
Second action
Before reading the project version
Version: 0.1-SNAPSHOT
Not Last action
Last action
2、定義task之間的依賴:使用dependsOn
方法宣告依賴一個或者多個task,被依賴的任務要在該任務之前執行,但是被依賴的多個任務之間的執行順序是不保證的。
//build.gradle 指令碼:
version = '0.1-SNAPSHOT'
task first {
doLast {
println "first"
}
}
task second {
doLast {
println "second"
}
}
/**指定多個task依賴*/
task printVersion(dependsOn: [second, first]) {
doLast {
println "Version: $version"
}
}
task third {
doLast {
println "third"
}
}
/**dependsOn 新增任務之間的依賴,宣告依賴時直接按名稱引用task,被依賴的任務要先被執行*/
third.dependsOn('printVersion')
//執行指令碼:
> gradle third
> Task :first
first
> Task :second
second
> Task :printVersion
Version: 0.1-SNAPSHOT
> Task :third
third
3、終結器task:在某個task執行完成後會執行終結器task,即使該task執行失敗,終結器task也會被執行:
task first {
doLast {
println "first"
}
}
task second {
doLast {
println "second"
}
}
/**first 被 second終結*/
first.finalizedBy second
//執行 first任務:
> gradle first
> Task :first
first
> Task :second
second
4、自定義一個表示版本專案的類:我們可以按照Java的習慣來編寫構建指令碼,下面將自定義一個表示版本號的類(ProjectVersion
),該類儲存version.properties配置檔案裡的屬性,定義一個task:makeReleaseVersion
,該task動態的修改版本的屬性值,並將值寫入version.properties檔案中,所有操作都寫在註釋中:
//version.properties檔案中的配置:
major=1
minor=1
release=false
//build.gradle 構建指令碼:
/**Project 介面提供的file()方法,建立一個相對於專案目錄的一個java.io.File例項
* 這裡是讀取 version.properties 版本配置檔案*/
ext.versionFile = file('version.properties')
/**這是一個task配置塊:不同於之前的task,該task沒有定義動作(doLast(),doFirst()),
* task配置塊永遠在task動作之前執行*/
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
println 'Reading the version file.'
/**版本檔案不存在就丟擲異常*/
if (!versionFile.exists()) {
throw new GradleException("Required version file does not exist: $versionFile.canonicalPath")
}
Properties versionProps = new Properties()
/**Groovy通過該方法來講配置檔案中的屬性讀取到versionProps中*/
versionFile.withInputStream { stream ->
versionProps.load(stream)
}
/**在version.properties 檔案中定義了major minor release屬性*/
return new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}
task printVersion {
doLast {
println("Version: $version")
}
}
/** 利用ant task的 propertyfile 修改屬性檔案*/
task makeReleaseVersion(group: 'versioning', description: 'Makes project a release version.') {
doLast {
version.release = true
ant.propertyfile(file: versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}
class ProjectVersion {
Integer major
Integer minor
Boolean release
ProjectVersion(Integer major, Integer minor) {
this.major = major
this.minor = minor
this.release = Boolean.FALSE
}
ProjectVersion(Integer major, Integer minor, Boolean release) {
this(major, minor)
this.release = release
}
@Override
String toString() {
/**在release屬性為false的時候才加上-SNAPSHOT字尾,
* Gradle 總是會呼叫toString()方法來讀取version的值,所以我們要重寫該方法,以實現自定義的版本號表示*/
"$major.$minor${release ? '' : '-SNAPSHOT'}"
}
}
//執行printVersion任務:
> gradle printVersion
> Configure project : //這是在配置塊中輸出的
Reading the version file.
> Task :printVersion
Version: 1.1-SNAPSHOT //讀取的正是version.properties配置的相關屬性值
//先執行makeReleaseVersion 任務,再執行printVersion任務,version.properties中的release屬性被修改成true,輸出Version字尾被去掉:
> Task :printVersion
Version: 1.1
5、Gradle構建的生命週期:Gradle構建有3個生命週期:初始化、配置和執行,下圖分別顯示了這3個生命週期階段,並解釋了4中的構建指令碼的程式碼分別對應的是哪個階段: 在初始化階段,會為專案建立一個Project的例項;配置塊中的程式碼使在配置階段被執行,該階段適合為專案或者指定的task設定所需要的配置;含有action的task在執行階段被執行。
6、使用自定義的task實現對配置檔案(version.properties)的修改和增量式構建的支援
//version.properties 配置檔案:
major=0
minor=1
release=false
//build.gradle 指令碼:
/**這裡使用 project-version.properties 作為配置檔案,*/
ext.versionFile = file('version.properties')
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
println 'Reading the version file.'
if (!ext.versionFile.exists()) {
throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
}
Properties versionProps = new Properties()
versionFile.withInputStream { stream ->
versionProps.load(stream)
}
/**新建 ProjectVersion 物件,使用配置檔案的屬性值為物件賦值*/
return new ProjectVersion(min: versionProps.major.toInteger(), maj: versionProps.minor.toInteger(), prodReady: versionProps.release.toBoolean())
}
/**為自定義的ReleaseVersionTask 的引數release、destFile賦值*/
task makeReleaseVersion(type: ReleaseVersionTask) {
release = version.prodReady
destFile = new File('version.properties')
}
task printVersion {
doLast {
println("Version: $version")
}
}
/**自定義task繼承DefaultTask*/
class ReleaseVersionTask extends DefaultTask {
/**task的輸入輸出註解, @Input註解宣告task的輸入屬性:release
* OutputFile註解定義輸出檔案,
* 不使用註解也可以實現修改配置檔案屬性值的需求,因為會預設為屬性生成setter和getter方法,
* 但是無法做到增量式構建,使用註解,Gradle會比較兩次的input和output是否是最新的,
* 只有是最新的時候(比如手動修改了配置檔案),task才會被執行,達到增量式構建的效果*/
@Input
Boolean release
@OutputFile
File destFile
ReleaseVersionTask() {
/**設定task的屬性,描述性的引數*/
group = 'versioning'
description = 'Makes project a release version.'
}
/**執行makeReleaseVersion 任務時,會將兩個引數傳進來,然後執行被@TaskAction註解宣告的方法*/
@TaskAction
void start() {
project.version.prodReady = true
/**修改配置檔案的屬性值*/
ant.propertyfile(file: destFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}
class ProjectVersion {
Integer min
Integer maj
Boolean prodReady
/**此例中使用了 min: versionProps.major.toInteger() 的方式為物件賦值,也可以使用構造引數*/
@Override
String toString() {
"$maj.$min${prodReady ? '' : '-SNAPSHOT'}"
}
}
//直接執行printVersion任務,輸出如下:
> Task :printVersion
Version: 1.0-SNAPSHOT
//先執行makeReleaseVersion任務,然後執行printVersion任務,輸出如下:
> Task :printVersion
Version: 1.0
//多次執行makeReleaseVersion任務,gradle輸出資訊如下:up-to-date代表是最新的,也就是自定義task沒有被執行,因為多次執行makeReleaseVersion只有第一次需要執行
BUILD SUCCESSFUL in 2s
1 actionable task: 1 up-to-date
7、task規則:在許多時候,多個task可能做著很多類似的工作,下面以兩個task說明該問題並解決,一個task用來增加專案的主版本號,另一個task增加次版本號,task的命名方式一般是以“駝峰命名法”,如:incrementMajorVersion,這兩個task基本上是一樣的:
task incrementMajorVersion(group: 'versioning', description: 'Increments project major version.') {
doLast {
String currentVersion = version.toString()
++version.major
String newVersion = version.toString()
logger.info "Incrementing major project version: $currentVersion -> $newVersion"
ant.propertyfile(file: versionFile) {
entry(key: 'major', type: 'int', operation: '+', value: 1)
}
}
}
task incrementMinorVersion(group: 'versioning', description: 'Increments project minor version.') {
doLast {
String currentVersion = version.toString()
++version.minor
String newVersion = version.toString()
logger.info "Incrementing minor project version: $currentVersion -> $newVersion"
ant.propertyfile(file: versionFile) {
entry(key: 'minor', type: 'int', operation: '+', value: 1)
}
}
}
合併上述兩個task相似邏輯,使指令碼更加簡潔,執行任務incrementMajorVersion
和incrementMinorVersion
之後,配置檔案中的主次版本號分別增加1
apply plugin: 'war'
ext.versionFile = file('version.properties')
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
logger.quiet 'Reading the version file.'
if (!versionFile.exists()) {
throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
}
Properties versionProps = new Properties()
versionFile.withInputStream { stream ->
versionProps.load(stream)
}
new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}
/**通過新增task規則,合併相似的task。
* 合併增加主次版本號的task*/
tasks.addRule("Pattern: increment<Classifier>Version – Increments the project version classifier.") { String taskName ->
/**根據預定義的模式檢查task的名稱*/
if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
/**給符合命名模式的task動態的新增一個doLast方法*/
task(taskName) {
doLast {
/**從完整的task名稱中提取型別字串*/
String classifier = (taskName - 'increment' - 'Version').toLowerCase()
String currentVersion = version.toString()
switch (classifier) {
case 'major': ++version.major
break
case 'minor': ++version.minor
break
default: throw new GradleException("Invalid version type '$classifier. Allowed types: ['Major', 'Minor']")
}
String newVersion = version.toString()
println "Incrementing $classifier project version: $currentVersion -> $newVersion"
/**修改配置檔案裡相應屬性的值*/
ant.propertyfile(file: versionFile) {
entry(key: classifier, type: 'int', operation: '+', value: 1)
}
}
}
}
}
class ProjectVersion {
Integer major
Integer minor
Boolean release
ProjectVersion(Integer major, Integer minor) {
this.major = major
this.minor = minor
this.release = Boolean.FALSE
}
ProjectVersion(Integer major, Integer minor, Boolean release) {
this(major, minor)
this.release = release
}
@Override
String toString() {
"$major.$minor${release ? '' : '-SNAPSHOT'}"
}
}
三、監聽構建的生命週期 有時候在構建的特定的生命週期可能需要執行一些程式碼,生命週期事件可能發生在某一個構建階段之前、期間或者之後,我們可以使用生命週期鉤子(hook),在特定的生命週期執行一些想要的程式碼,下面是生命週期鉤子示例圖:
生成task執行圖是指完成了各個task之間依賴關係的確定,task之間的依賴關係被描述成一個有向無環圖。通過實現TaskExecutionGraphListener
介面,註冊監聽器,可以監聽執行圖事件,執行release
任務後,配置檔案被修改:
apply plugin: 'war'
ext.versionFile = file('version.properties')
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
logger.quiet 'Reading the version file.'
if (!versionFile.exists()) {
throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
}
Properties versionProps = new Properties()
versionFile.withInputStream { stream ->
versionProps.load(stream)
}
new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}
/**自定義一個監聽器*/
class ReleaseVersionListener implements TaskExecutionGraphListener {
final static String releaseTaskPath = ':release'
@Override
void graphPopulated(TaskExecutionGraph taskGraph) {
println("tasks : " + taskGraph.allTasks)
/**判斷執行的tasks 是不是包含 release task,只有執行的是release task的時候才會執行下面的操作*/
if (taskGraph.hasTask(releaseTaskPath)) {
List<Task> allTasks = taskGraph.allTasks
/**從執行圖中的一系列task過濾release task*/
Task releaseTask = allTasks.find { it.path == releaseTaskPath }
/**通過task拿到project,每一個task都知道所屬的project*/
Project project = releaseTask.project
if (!project.version.release) {
project.version.release = true
project.ant.propertyfile(file: project.versionFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}
}
}
/**新增一個task執行圖的監聽器*/
gradle.taskGraph.addTaskExecutionGraphListener(new ReleaseVersionListener())
task release {
doLast {
println 'Releasing the project...'
}
}
class ProjectVersion {
Integer major
Integer minor
Boolean release
ProjectVersion(Integer major, Integer minor) {
this.major = major
this.minor = minor
this.release = Boolean.FALSE
}
ProjectVersion(Integer major, Integer minor, Boolean release) {
this(major, minor)
this.release = release
}
@Override
String toString() {
"$major.$minor${release ? '' : '-SNAPSHOT'}"
}
}
好了,task的使用就到這吧,總結這些主要是可以讓自己以後在使用Gradle的時候,雖然可能還是有很多配置操作不甚瞭解,但是可以讓自己站在一個較高的角度理解構建指令碼的邏輯,知道每一行程式碼是在幹什麼,這在編寫指令碼過程中還是很重要的,知其所以然,才能有更高的提升。