1. 程式人生 > >Gradle之task的使用

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相似邏輯,使指令碼更加簡潔,執行任務incrementMajorVersionincrementMinorVersion之後,配置檔案中的主次版本號分別增加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的時候,雖然可能還是有很多配置操作不甚瞭解,但是可以讓自己站在一個較高的角度理解構建指令碼的邏輯,知道每一行程式碼是在幹什麼,這在編寫指令碼過程中還是很重要的,知其所以然,才能有更高的提升。