鍍金池/ 教程/ 物聯(lián)網(wǎng)/ 管理任務(wù)
依賴管理實(shí)戰(zhàn)
配置遠(yuǎn)程倉庫
安裝Gradle
簡介
項(xiàng)目自動(dòng)化簡介
配置子項(xiàng)目
Gradle強(qiáng)大的特性
自動(dòng)化測試
簡介
掌握構(gòu)建生命周期
使用命令行操作
構(gòu)建Java項(xiàng)目
管理任務(wù)
構(gòu)建工具
連續(xù)傳遞的特性
用Gradle開發(fā)Web項(xiàng)目
聲明依賴
構(gòu)建塊
簡介
測試Java應(yīng)用
java構(gòu)建工具
為什么選擇Gradle
拆分項(xiàng)目文件
單元測試
多項(xiàng)目打包
Gradle 起步
介紹這個(gè)Gradle項(xiàng)目
簡介
自定義腳本
Gradle包裝器
簡要概述依賴管理
項(xiàng)目模塊化

管理任務(wù)

每個(gè)新創(chuàng)建的任務(wù)都是org.gradle.api.DefaultTask類型,org.gradle.api.Task的標(biāo)準(zhǔn)實(shí)現(xiàn),DefaultTask所有的域都是私有的,意味著他們只能通過setter和getter方法來訪問,慶幸的是Groovy提供了一些語法糖來允許你通過名字來使用域。

管理項(xiàng)目的版本

許多公司或者開源組織有他們自己的發(fā)布版本的措施,一般用主版本號和次版本號來表示,也會(huì)用SNAPSHOT來表示項(xiàng)目正在開發(fā)中,版本號是通過String類型來表示,如果你想準(zhǔn)確獲得項(xiàng)目的主版本號,那應(yīng)該怎么辦?使用正則表達(dá)式匹配點(diǎn)號然后過濾得到主版本號和次版本號?如果我們用一個(gè)類來表示是不是更簡單?

你可以很簡單的通過類的域來設(shè)置、查詢和修改你的版本號的某個(gè)部分,甚至你可以把版本信息直接保存在一個(gè)文件里,比如一個(gè)文件或者數(shù)據(jù)庫里,避免通過修改構(gòu)建腳本來更改版本號,如下圖所示:

http://wiki.jikexueyuan.com/project/gradleIn-action/images/dag26.png" alt="" />

通過編程來控制版本對于自動(dòng)化項(xiàng)目生命周期很有必要,比如:你的代碼通過了單元測試準(zhǔn)備交付了,當(dāng)前的版本是1.3-SNAPSHOT,在打包成war文件之前你想把它變成發(fā)布版本1.3然后自動(dòng)部署到服務(wù)器中,這些步驟可以劃分為多個(gè)任務(wù):一個(gè)用來修改項(xiàng)目的版本號另一個(gè)用于打包WAR文件。

聲明任務(wù)的動(dòng)作(actions)

動(dòng)作就是在你的任務(wù)中放置構(gòu)建邏輯的地方,Task接口提供了兩個(gè)方法來聲明任務(wù)的動(dòng)作:

doFirst和doLast,當(dāng)任務(wù)執(zhí)行的時(shí)候,定義在閉包里的動(dòng)作邏輯就按順序執(zhí)行。

接下來我們會(huì)寫一個(gè)簡單的任務(wù)printVersion,任務(wù)的作用就是打印項(xiàng)目的版本號,在任務(wù) 的最后一個(gè)動(dòng)作定義這個(gè)邏輯。

    version = '0.1-SNAPSHOT'

    task printVersion {
        doLast {
        println "Version: $version"
        }
    }

前面我們講過左移操作符是方法doLast的快捷鍵,他們的作用是一樣的,當(dāng)你執(zhí)行g(shù)radle printVersion,你應(yīng)該得到下面的輸出:

    gradle printVersion
    :printVersion
    Version: 0.1-SNAPSHOT

如果你用doFirst方法的話輸出的結(jié)果是一樣的:

    task printVersion {
        doFirst {
        println "Version: $version"
        }
    }

給已經(jīng)存在的任務(wù)添加動(dòng)作

到目前為止,你只是給printVersion這個(gè)任務(wù)添加了單個(gè)動(dòng)作,要么是第一個(gè)或者最后一個(gè),對于每個(gè)任務(wù)可以有多個(gè)動(dòng)作,實(shí)際上,當(dāng)任務(wù)創(chuàng)建的時(shí)候你可以添加任意多個(gè)動(dòng)作,每一個(gè)任務(wù)都有一個(gè)動(dòng)作清單,他們在運(yùn)行的時(shí)候是執(zhí)行的,接下來我們來修改之前的例子:

    task printVersion {
    //任務(wù)的初始聲明可以添加first和last動(dòng)作
        doFirst {
        println "Before reading the project version"
        }

        doLast {
        println "Version: $version"
        }
    }

//你可以在任務(wù)的動(dòng)作列表的最前面添加其他任務(wù),比如:

    printVersion.doFirst { println "First action" }

由此可知,我們可以添加額外的動(dòng)作給已經(jīng)存在的任務(wù),當(dāng)你想添加動(dòng)作的那個(gè)任務(wù)不是你自己寫的時(shí)候這會(huì)非常有用,你可以添加一些自定義的邏輯,比如你可以添加doFirst動(dòng)作到compile-Java任務(wù)來檢查項(xiàng)目是否包含至少一個(gè)source文件。

訪問任務(wù)屬性

接下來我們來改善一下輸出版本號的方法,Gradle提供一個(gè)基于SLF4J庫的日志實(shí)現(xiàn),除了實(shí)現(xiàn)了基本的日志級別(DEBUG, ERROR, INFO, TRACE, WARN))外,還添加了額外的級別,日志實(shí)例可以通過任務(wù)的方法來直接訪問,接下來,你將用QUIET級別打印項(xiàng)目的版本號:

    task printVersion << {
        logger.quiet "Version: $version"
    }

訪問任務(wù)的屬性是不是很容易?接下來我將給你展示兩個(gè)其他的屬性,group和description,兩個(gè)都是documentation任務(wù)的一部分,description屬性簡短的表示任務(wù)的目的,group表示任務(wù)的邏輯分組。

    task printVersion(group: 'versioning', description:     'Prints project version.') << {
        logger.quiet "Version: $version"
    }

你也可以通過setter方法來設(shè)置屬性:

    task printVersion {
        group = 'versioning'
        description = 'Prints project version.'
        doLast {
        logger.quiet "Version: $version"
        }
    }

當(dāng)你運(yùn)行g(shù)radle tasks,你會(huì)看到任務(wù)顯示在正確的分組里和它的描述信息:

    gradle tasks
    :tasks
    ...
    Versioning tasks
    ----------------
    printVersion - Prints project version.
    ...

定義任務(wù)依賴

dependsOn方法用來聲明一個(gè)任務(wù)依賴于一個(gè)或者多個(gè)任務(wù),接下來通過一個(gè)例子來講解運(yùn)用不同的方法來應(yīng)用依賴:

    task first << { println "first" }
    task second << { println "second" }

    //聲明多個(gè)依賴
    task printVersion(dependsOn: [second, first]) << {
    logger.quiet "Version: $version"
    }

    task third << { println "third" }
    //通過任務(wù)名稱來聲明依賴
    third.dependsOn('printVersion')

你可以通過命令行調(diào)用third任務(wù)來執(zhí)行這個(gè)任務(wù)依賴鏈:

    $ gradle -q third
    first
    second
    Version: 0.1-SNAPSHOT
    third

仔細(xì)看這個(gè)執(zhí)行順序,你有沒用發(fā)現(xiàn)printVersion聲明了對second和first任務(wù)的依賴,但是first在second任務(wù)前執(zhí)行了,Gradle里面任務(wù)的執(zhí)行順序并不是確定的。

任務(wù)依賴執(zhí)行順序

Gradle并不保證依賴的任務(wù)能夠按順序執(zhí)行,dependsOn方法只是定義這些任務(wù)應(yīng)該在這個(gè)任務(wù)之前執(zhí)行,但是這些依賴的任務(wù)具體怎么執(zhí)行它并不關(guān)心,如果你習(xí)慣用命令式的構(gòu)建工具來定義依賴(比如ant)這可能會(huì)難以理解。在Gradle里面,執(zhí)行順序是由任務(wù)的輸入輸出特性決定的,這樣做有很多優(yōu)點(diǎn),比如你想修改構(gòu)建邏輯的時(shí)候你不需要去了解整個(gè)任務(wù)依賴鏈,另一方面,因?yàn)槿蝿?wù)不是順序執(zhí)行的,就可以并發(fā)的執(zhí)行來提高性能。

終結(jié)者任務(wù)

在實(shí)際情況中,你可能需要在一個(gè)任務(wù)執(zhí)行之后進(jìn)行一些清理工作,一個(gè)典型的例子就是Web容器在部署應(yīng)用之后要進(jìn)行集成測試,Gradle提供了一個(gè)finalizer任務(wù)來實(shí)現(xiàn)這個(gè)功能,你可以用finalizedBy方法來結(jié)束一個(gè)指定的任務(wù):

    task first << { println "first" }
    task second << { println "second" }
    //聲明first結(jié)束后執(zhí)行second任務(wù)
    first.finalizedBy second

你會(huì)發(fā)現(xiàn)任務(wù)first結(jié)束后自動(dòng)觸發(fā)任務(wù)second:

    $ gradle -q first
    first
    second

添加隨意的代碼

接下來我們來學(xué)習(xí)怎么在build腳本中定義一些隨機(jī)的代碼,在實(shí)際情況下,如果你熟悉Groovy的語法你可以編寫一些類或者方法,接下來你將會(huì)創(chuàng)建一個(gè)表示版本的類,在Java中一個(gè)class遵循bean的約定(POJO),就是添加setter和getter方法來訪問類的域,到后面發(fā)現(xiàn)手工寫這些方法很煩人,Groovy有個(gè)對應(yīng)的概念叫POGO(plain-old Groovy object),他們的setter和getter方法在生成字節(jié)碼的時(shí)候自動(dòng)添加,因此運(yùn)行的時(shí)候可以直接訪問,看下面這個(gè)例子:

    version = new ProjectVersion(0, 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() {
            //只有release為false的時(shí)候才添加后綴SNAPSHOT
            "$major.$minor${release ? '' : '-SNAPSHOT'}"
        }
    }

當(dāng)運(yùn)行這個(gè)修改的腳本之后,你可以看到printVersion的輸出和之前一樣,但是你還是得手工修改build腳本來更改版本號,接下來你將學(xué)習(xí)如何把版本號存儲(chǔ)在一個(gè)文件里然后配置你的腳本去讀取這個(gè)配置。

任務(wù)的配置

在你寫代碼之前,你要新建一個(gè)屬性文件version.properties,內(nèi)容如下:

    major = 0
    minor = 1
    release = false

添加任務(wù)配置塊

接下來我們將聲明一個(gè)任務(wù)loadVersion來從屬性文件中讀取版本號并賦給ProjectVersion實(shí)例,第一眼看起來和其他定義的任務(wù)一樣,仔細(xì)一看你會(huì)注意到你沒有定義動(dòng)作或者使用左移操作符,在Gradle里稱之為任務(wù)配置塊(task configuration)。

    ext.versionFile = file('version.properties')
    //配置任務(wù)沒有左移操作符
    task loadVersion {
        project.version = readVersion()
    }

    ProjectVersion readVersion() {
        logger.quiet 'Reading the version file.'
        //如果文件不存在拋出異常
        if(!versionFile.exists()) {
            throw new GradleException("Required version file does not exist:$versionFile.canonicalPath")
        }

    Properties versionProps = new Properties()

    //groovy的file實(shí)現(xiàn)了添加方法通過新創(chuàng)建的流來讀取

    versionFile.withInputStream { stream ->
    versionProps.load(stream)
    }
    //在Groovy中如果這是最后一個(gè)語句你可以省略return關(guān)鍵字
    new ProjectVersion(versionProps.major.toInteger(),
     versionProps.minor.toInteger(), versionProps.release.toBoolean())
    }

接下來運(yùn)行printVersion,你會(huì)看到loadVersion任務(wù)先執(zhí)行了:

    $ gradle printVersion
    Reading the version file.
    :printVersion
    Version: 0.1-SNAPSHOT

你也許會(huì)很奇怪這個(gè)任務(wù)是怎么調(diào)用的,你沒有聲明依賴,也沒有在命令行中調(diào)用它。任務(wù)配置塊總是在任務(wù)動(dòng)作之前執(zhí)行的,理解這個(gè)行為的關(guān)鍵就是Gradle的構(gòu)建生命周期,我們來看下Gradle的構(gòu)建階段:

http://wiki.jikexueyuan.com/project/gradleIn-action/images/4-1.png" alt="" />

Gradle的構(gòu)建生命周期

無論你什么時(shí)候執(zhí)行一個(gè)gradle build,都會(huì)經(jīng)過三個(gè)不同的階段:初始化、配置和執(zhí)行。

在初始化階段,Gradle給你的項(xiàng)目創(chuàng)建一個(gè)Project實(shí)例,你的構(gòu)建腳本只定義了單個(gè)項(xiàng)目,在多項(xiàng)目構(gòu)建的上下文環(huán)境中,構(gòu)建的階段更為重要。根據(jù)你正在執(zhí)行的項(xiàng)目,Gradle找出這個(gè)項(xiàng)目的依賴。

下一個(gè)階段就是配置階段,Gradle構(gòu)建一些在構(gòu)建過程中需要的一些模型數(shù)據(jù),當(dāng)你的項(xiàng)目或者指定的任務(wù)需要一些配置的時(shí)候這個(gè)階段很有幫助。

記住不管你執(zhí)行哪個(gè)build哪怕是gradle tasks配置代碼都會(huì)執(zhí)行

在執(zhí)行階段任務(wù)按順序執(zhí)行,執(zhí)行順序是通過依賴關(guān)系決定的,標(biāo)記為up-to-date的任務(wù)會(huì)跳過,比如任務(wù)B依賴于任務(wù)A,當(dāng)你運(yùn)行g(shù)radle B的時(shí)候執(zhí)行順序?qū)⑹茿->B。

聲明任務(wù)的輸入和輸出

Gradle通過比較兩次build之間輸入和輸出有沒有變化來確定這個(gè)任務(wù)是否是最新的,如果從上一個(gè)執(zhí)行之后這個(gè)任務(wù)的輸入和輸出沒有發(fā)生改變這個(gè)任務(wù)就標(biāo)記為up-to-date,跳過這個(gè)任務(wù)。

http://wiki.jikexueyuan.com/project/gradleIn-action/images/4-2.png" alt="" />

輸入可以是一個(gè)目錄、一個(gè)或者多個(gè)文件或者隨機(jī)的屬性,任務(wù)的輸出可以是路徑或者文件,輸入和輸出在DefaultTask類中用域來表示。假設(shè)你想創(chuàng)建一個(gè)任務(wù)把項(xiàng)目的版本由SNAPSHOT改為release,下面的代碼定義一個(gè)新任務(wù)給release變量賦值為true,然后把改變寫入到文件中。

    task makeReleaseVersion(group: 'versioning', description: 'Makes project a release version.') << {
        version.release = true
        //ant的propertyfile任務(wù)提供很方便的方法來修改屬性文件
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type:'string',operation: '=', value: 'true')
        }
    }

運(yùn)行這個(gè)任務(wù)會(huì)修改版本屬性并寫入到文件中。

    $ gradle makeReleaseVersion
    :makeReleaseVersion

    $ gradle printVersion
    :printVersion
    Version: 0.1

編寫自定義的任務(wù)

makeReleaseVersion的邏輯比較簡單,你可能不用考慮代碼維護(hù)的問題,隨著構(gòu)建邏輯越來越復(fù)雜,你添加了越來越多的簡單的任務(wù),這時(shí)候你就有需要用類和方法來結(jié)構(gòu)化你的代碼,你可以把你編寫源代碼的那一套代碼實(shí)踐搬過來。

編寫自定義任務(wù)類

之前提到過,Gradle會(huì)給每一個(gè)任務(wù)創(chuàng)建一個(gè)DefaultTask類型的實(shí)例,當(dāng)你要?jiǎng)?chuàng)建一個(gè)自定義的任務(wù)時(shí),你需要?jiǎng)?chuàng)建一個(gè)繼承自DefaultTask的類,看看下面這個(gè)例子:

    class ReleaseVersionTask extends DefaultTask {
        //通過注解聲明任務(wù)的輸入和輸出    
        @Input Boolean release
        @OutputFile File destFile

        ReleaseVersionTask() {
            //在構(gòu)造器里設(shè)置任務(wù)的分組和描述
            group = 'versioning'
            description = 'Makes project a release version.'
        }
        //通過注解聲明要執(zhí)行的任務(wù)
        @TaskAction
        void start() {
            project.version.release = true
            ant.propertyfile(file: destFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
        }
    }

通過注解來表達(dá)輸入和輸出

任務(wù)輸入和輸出注解給你的實(shí)現(xiàn)添加了語法糖,他們和調(diào)用TasksInputs和TaskOutputs方法是一樣的效果,你一眼就知道任務(wù)期望什么樣的輸入數(shù)據(jù)以及會(huì)產(chǎn)生什么輸出。我們使用@Input注解來聲明輸入屬性release,用@OutputFile來定義輸出文件。

使用自定義的任務(wù)

上面我們實(shí)現(xiàn)了自定義的動(dòng)作方法,但是我們怎么使用這個(gè)方法,你需要在build腳本中創(chuàng)建一個(gè)ReleaseVersionTask類型的任務(wù),通過給屬性賦值來設(shè)定輸入和輸出:

    //定義一個(gè)ReleaseVersionTask類型的任務(wù)
    task makeReleaseVersion(type: ReleaseVersionTask) {
        //設(shè)定任務(wù)屬性
        release = version.release
        destFile = versionFile
    }

復(fù)用自定義的任務(wù)

假設(shè)你在另一個(gè)項(xiàng)目中想使用前面這個(gè)自定義的任務(wù),在另一個(gè)項(xiàng)目中需求又不太一樣,用來表示版本的POGO有不同的域,比如下面這個(gè):

    class ProjectVersion {
        Integer min
        Integer maj
        Boolean prodReady

        @Override
        String toString() {
        "$maj.$min${prodReady? '' : '-SNAPSHOT'}"
        }
    }

此外,你還想把版本文件名改為project-version.properties,需要怎么做才能復(fù)用上面那個(gè)自定義的任務(wù)呢?

    task makeReleaseVersion(type: ReleaseVersionTask) {
        release = version.prodReady
        //不同的版本文件
        destFile = file('project-version.properties')
    }

Gradle自帶的任務(wù)類型

Gradle自帶的任務(wù)類型繼承自DefaultTask,Gradle提供了很多自帶的任務(wù)類型,這里我只介紹兩個(gè),Zip和copy用在發(fā)布項(xiàng)目中。

//eg.使用任務(wù)類型來備份發(fā)布版本

    task createDistribution(type: Zip, dependsOn:   makeReleaseVersion) {
        //引用war任務(wù)的輸出
        from war.outputs.files
        //把所有文件放進(jìn)ZIP文件的src目錄
        from(sourceSets*.allSource) {
        into 'src'
        }
        //添加版本文件
        from(rootDir) {
        include versionFile.name
        }
    }

    task backupReleaseDistribution(type: Copy) {
            //引用createDistribution的輸出
            from createDistribution.outputs.files
            into "$buildDir/backup"
        }

        task release(dependsOn: backupReleaseDistribution)  << {
            logger.quiet 'Releasing the project...'
    }

任務(wù)依賴推導(dǎo)

你可能注意到上面通過dependsOn方法來顯示聲明兩個(gè)任務(wù)之間的依賴,可是,一些任務(wù)并不是直接依賴于其他任務(wù)(比如上面createDistribution依賴于war)。Gradle怎么知道在任務(wù)之前執(zhí)行哪個(gè)任務(wù)?通過使用一個(gè)任務(wù)的輸出作為另一個(gè)任務(wù)的輸入,依賴就推導(dǎo)出來了,結(jié)果依賴的任務(wù)自動(dòng)執(zhí)行了,我們來看一下完整的執(zhí)行圖:

    $ gradle release
    :makeReleaseVersion
    :compileJava
    :processResources UP-TO-DATE
    :classes
    :war
    :createDistribution
    :backupReleaseDistribution
    :release
    Releasing the project...

運(yùn)行build之后你可以在build/distribution目錄找到生成的ZIP文件,這是打包任務(wù)的默認(rèn)輸出目錄,下面這個(gè)圖是生成的目錄樹:

http://wiki.jikexueyuan.com/project/gradleIn-action/images/4-3.png" alt="" />

在buildSrc目錄創(chuàng)建代碼

在前面我們創(chuàng)建了兩個(gè)類,ProjectVersion和ReleaseVersionTask,這些類可以移動(dòng)到你項(xiàng)目的buildSrc目錄,buildSrc目錄是一個(gè)放置源代碼的可選目錄,你可以很容易的管理你的代碼。Gradle采用了標(biāo)準(zhǔn)的項(xiàng)目布局,java代碼在src/main/java目錄,Groovy代碼應(yīng)該在src/main/groovy目錄,在這些目錄的任何代碼都會(huì)自動(dòng)編譯然后放置到項(xiàng)目的classpath目錄。這里你是在處理class,你可以把他們放到指定的包里面,假如com.manning.gia,下面顯示了Groovy類在項(xiàng)目中的目錄結(jié)構(gòu):

http://wiki.jikexueyuan.com/project/gradleIn-action/images/4-4.png" alt="" />

不過要記住把這些類放在源代碼目錄需要額外的工作,這和在腳本文件中定義有點(diǎn)不一樣,你需要導(dǎo)入Gradle的API,看看下面這個(gè)例子:

    package com.manning.gia
    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.OutputFile
    import org.gradle.api.tasks.TaskAction

    class ReleaseVersionTask extends DefaultTask {
        (...)
    }

反過來,你的構(gòu)建腳本需要從buildSrc中導(dǎo)入編譯的classes(比如 com.manning.gia.ReleaseVersionTask),下面這個(gè)是編譯任務(wù)輸出:

    $ gradle makeReleaseVersion
    :buildSrc:compileJava UP-TO-DATE
    :buildSrc:compileGroovy
    :buildSrc:processResources UP-TO-DATE
    :buildSrc:classes
    :buildSrc:jar
    :buildSrc:assemble
    :buildSrc:compileTestJava UP-TO-DATE
    :buildSrc:compileTestGroovy UP-TO-DATE
    :buildSrc:processTestResources UP-TO-DATE
    :buildSrc:testClasses UP-TO-DATE
    :buildSrc:test
    :buildSrc:check
    :buildSrc:build
    :makeReleaseVersion UP-TO-DATE

到此為止你學(xué)習(xí)了簡單任務(wù)的創(chuàng)建,自定義的task類,指定Gradle API提供的task類型,查看了任務(wù)動(dòng)作和任務(wù)配置的區(qū)別,以及他們的使用情形,任務(wù)配置和任務(wù)動(dòng)作是在不同階段執(zhí)行的。