Gradle介紹

前言

構(gòu)建,軟件生命周期中重要的一環(huán),在現(xiàn)代軟件開發(fā)過程中,起著越來越重要的作用。過去在Java或類Java的世界里,Ant、Maven再熟悉不過了,Maven憑借其強(qiáng)大的依賴配置戰(zhàn)勝Ant,基本上成為了Java構(gòu)建的標(biāo)準(zhǔn)。而在現(xiàn)代,系統(tǒng)日益復(fù)雜,構(gòu)建的靈活性要求越來越高,比如:構(gòu)建過程中需要打包上傳到服務(wù)器,Maven無法很好地支持這種復(fù)雜的系統(tǒng)構(gòu)建,所以,我選擇了Gradle,一個基于Groovy,更靈活更強(qiáng)大的構(gòu)建系統(tǒng),能幫助我們構(gòu)建更復(fù)雜的項(xiàng)目。

為什么選擇Gradle

從框架方向來看:

 1. Gradle是很成熟的技術(shù),可以處理大規(guī)模構(gòu)建     
 2. Gradle對多語言、多平臺有更natural的支持 
 3. Gradle關(guān)注在構(gòu)建效率上
 4. Gradle發(fā)布很頻繁,重要feature開發(fā)計(jì)劃透明化
 5. Gradle社區(qū)很活躍,并且增加迅速

從語言特性來看:

 1. 代碼很精簡
 2. Gradle基于Groovy,能完成復(fù)雜系統(tǒng)的構(gòu)建任務(wù)
 3. DSL比XML更簡潔高效

Gradle在開源項(xiàng)目中的使用

現(xiàn)在使用Gradle構(gòu)建的開源項(xiàng)目很多,我有過接觸的比如:Grails, Griffon, Groovy, Hibernate, Spring

還有很多其它開源項(xiàng)目也都在用Gradle,比如Tapestry,Qi4J,Netflix下所有開源項(xiàng)目(python、c++、html等除外)等等。

Gradle在企業(yè)中的使用

現(xiàn)在使用Gradle來做構(gòu)建體系的公司也越來越多,linkedin就很早開始切換到Gradle。

Gradle體驗(yàn)

Gradle的安裝非常方便,下載ZIP包,解壓到本地目錄,設(shè)置 GRADLE_HOME 環(huán)境變量并將 GRADLE_HOME/bin 加到 PATH 環(huán)境變量中,安裝就完成了。用戶可以運(yùn)行g(shù)radle -v命令驗(yàn)證安裝,這些初始的步驟和Maven沒什么兩樣。我這里安裝的Gradle版本是1.10,詳細(xì)信息見下:

bob [10:42]  ? gradle -v
Gradle 1.10
------------------------------------------------------------
Build time:   2013-12-17 09:28:15 UTC
Build number: none
Revision:     36ced393628875ff15575fa03d16c1349ffe8bb6
Groovy:       1.8.6
Ant:          Apache Ant(TM) version 1.9.2 compiled on July 8 2013
Ivy:          2.2.0
JVM:          1.7.0_45 (Oracle Corporation 24.45-b08)
OS:           Mac OS X 10.9.2 x86_64

Gradle的Features很多,官網(wǎng)doc介紹很詳細(xì),我這里就不多說。下面簡單介紹一下Gradle構(gòu)建相關(guān)的東西。

Gradle基礎(chǔ)

  1. Gradle有兩個最基本的概念:project和task。Gradle里面的所有東西都基于這兩個概念。project通常指一個項(xiàng)目,而task指構(gòu)建過程中的任務(wù)。一次構(gòu)建可以有1到n個project,每個project有1到n個task。
  2. Gradle有一個類似Maven中pom.xml的配置文件:build.gradle。功能也基本一樣,負(fù)責(zé)當(dāng)前project的構(gòu)建定義??匆粋€build.gradle的簡單例子:
    bob [10:46]  ? pwd
    /Users/bob/framework/gradle-1.10/samples/userguide/tutorial/hello
    // 在你安裝的gradle根目錄下有對應(yīng)的samples目錄,里面有很多例子
    
    bob [10:46]  ? cat build.gradle
    task hello {
        doLast {
            println 'Hello world!'
        }
    }

文件中定義了一個task:hello,task的內(nèi)容是 "println 'Hello world!'",我們來執(zhí)行一下:

bob [10:49]  ? gradle -q hello
Hello world!

可以看到,輸出了"Hello world!",這里-q的意思是quiet模式,只輸出構(gòu)建中的必要信息。

gradle里可以定義多個task,task之間也可以有依賴關(guān)系,還可以定義默認(rèn)task,看一個例子:
帶有task依賴關(guān)系:

bob [10:53]  ? cat userguide/tutorial/lazyDependsOn/build.gradle
task taskX(dependsOn: 'taskY') << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}

帶有默認(rèn)task例子:

bob [10:59]  ? cat userguide/tutorial/defaultTasks/build.gradle
defaultTasks 'clean', 'run'


task clean << {
    println 'Default Cleaning!'
}

task run << {
    println 'Default Running!'
}

task other << {
    println "I'm not a default task!"
}

看看執(zhí)行情況:

bob [10:59]  ? gradle -q
Default Cleaning!
Default Running!

bob [11:00]  ? gradle -q other
I'm not a default task!

默認(rèn)task,當(dāng)沒有task指定時,則會執(zhí)行默認(rèn)的task。

Gradle依賴

Gradle和Maven在依賴管理上幾乎差不多,核心的概念是一樣的,只不過Gradle語法更精簡,并且多了一些更靈活的自定義配置。我們先看一個例子,Maven的pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
</dependencies>

更換成Gradle腳本,結(jié)果是這樣:

dependencies {
    compile('org.springframework:spring-core:3.2.4.RELEASE')
    compile('org.springframework:spring-beans:3.2.4.RELEASE')
    compile('org.springframework:spring-context:3.2.4.RELEASE')
    testCompile('junit:junit:4.7')
}

代碼塊少了很多。試想,生產(chǎn)環(huán)境下的中、大型應(yīng)用如果用都用Gradle替換Maven,那勢必會大大減少配置文件代碼塊,并有更強(qiáng)的可讀性,也就意味著系統(tǒng)更加穩(wěn)健。

  1. Gradle在依賴配置上面,和Maven一樣,支持傳遞性依賴,然后和Maven不同的是,它還支持排除傳遞性依賴以及關(guān)閉傳遞性依賴。
  2. Gradle的依賴scope,也基本和Maven一樣,不過它是通過配置來定義,plugin來支撐和加強(qiáng)的,所以除了基本的compile、runtime等scope外,Gradle還可以自定義出很多配置,針對不同的配置寫不同的task來完成更復(fù)雜更靈活的構(gòu)建任務(wù)。

依賴相關(guān)的倉庫配置很靈活,支持多種repository,看下面repository定義例子:

bob [11:07]  ? cat userguide/artifacts/defineRepository/build.gradle

repositories {
    mavenCentral()          // 定義倉庫為maven中心倉庫
}
repositories {
    jcenter()               // 定義倉庫為jcenter倉庫
}
repositories {
    maven {
        url "http://repo.mycompany.com/maven2"      // 定義依賴包協(xié)議是maven,地址是公司的倉庫地址
    }
}
repositories {                              // 定義本地倉庫目錄
    flatDir {
        dirs 'lib'
    }
}
repositories {                              // 定義ivy協(xié)議類型的倉庫
    ivy {
        url "http://repo.mycompany.com/repo"
    }
}

可以看到,對于常用的maven、ivy、local以及jcenter的repository都有支持,語法很簡單。而且還可以通過編寫task來支持更復(fù)雜的repository,更多詳情可以查看安裝包里的對應(yīng)目錄下文件查看。

Gradle構(gòu)建

和Maven一樣,Gradle也是通過artifact來打包構(gòu)建的。得益于上述的Gradle本身的特性,artifact在Gradle里實(shí)現(xiàn)得更靈活一些??匆粋€例子:

bob [13:00]  ? cat userguide/artifacts/uploading/build.gradle

## jar類型的artifact
task myJar(type: Jar)
artifacts {
    archives myJar
}
## file類型的artifact
def someFile = file('build/somefile.txt')
artifacts {
    archives someFile
}

## 根據(jù)自定義task來完成artifact
task myTask(type:  MyTaskType) {
    destFile = file('build/somefile.txt')
}
artifacts {
    archives(myTask.destFile) {
        name 'my-artifact'
        type 'text'
        builtBy myTask
    }
}

## 根據(jù)自定義task來完成artifact
task generate(type:  MyTaskType) {
    destFile = file('build/somefile.txt')
}
artifacts {
    archives file: generate.destFile, name: 'my-artifact', type: 'text', builtBy: generate
}

這樣就簡單地定義了好幾種artifact生成的定義,根據(jù)不同的場景需求,生成文本文件、jar包或者zip,還可以再上傳到服務(wù)器上。

一般情況下,常用的插件,比如說"Java plugin"都默認(rèn)定義了"jar"這樣的artifact task,所以一般不需要額外開發(fā)。但是,針對于一些復(fù)雜情況,或者在plugin基礎(chǔ)上增強(qiáng)的話,自定義artifact task還是非常有用的。

Gradle構(gòu)建的項(xiàng)目,發(fā)布到倉庫中,也非常容易:

apply plugin: 'maven'

uploadArchives {
    repositories {
        ivy {
            credentials {
                username "username"
                password "pw"
            }
            url "http://repo.mycompany.com"
        }
    }
}

Gradle 插件

上面簡介介紹了一下Gradle的一些概念和配置,要用到項(xiàng)目中run起來,現(xiàn)在還還要一步,就是本節(jié)介紹的Gradle插件。Gradle現(xiàn)在已經(jīng)支持很多插件,這給開發(fā)者帶來極大的便利,先說說Java插件吧。

  1. 使用Java plugin,只需要在build.gradle中加入這句話:
    apply plugin: 'java'
    
  2. 了解或設(shè)置Java project布局。Gradle和Maven一樣,采用了“約定優(yōu)于配置”的方式對Java project布局,并且布局方式是和Maven一樣的,此外,Gradle還可以方便的自定義布局。在Gradle中,一般把這些目錄叫做source set??聪鹿俜降拇鸢福?/p>

    Alt description

這里要注意,每個plugin的source set可能都不一樣。

同樣的,Java plugin還定義好了一堆task,讓我們可以直接使用,比如:clean、test、build等等。這些task都是圍繞著Java plugin的構(gòu)建生命周期的:

Alt description

圖中每一塊都是一個task,箭頭表示task執(zhí)行順序/依賴,比如執(zhí)行task jar,那么必須先執(zhí)行task compileJava和task processResources。另外可以看到,Gradle的Java plugin構(gòu)建生命周期比較復(fù)雜,但是也表明了更加靈活,而且,在項(xiàng)目中,一般只使用其中常用的幾個:clean test check build 等等。

gradle構(gòu)建過程中,所有的依賴都表現(xiàn)為配置,比如說系統(tǒng)運(yùn)行時的依賴是runtime,gradle里有一個依賴配置叫runtime,那么系統(tǒng)運(yùn)行時會加載這個依賴配置以及它的相關(guān)依賴。這里說的有點(diǎn)繞,可以簡單理解依賴和maven類似,只不過gradle用configuration實(shí)現(xiàn),所以更靈活,有更多選擇。下圖是依賴配置關(guān)系圖以及和task調(diào)用的關(guān)系圖:

Alt description

可以看到,基本和Maven是一樣的。其實(shí)Gradle里面這些依賴(scope)都是通過configuration來實(shí)現(xiàn)的,這里就不細(xì)說,有興趣的可以研究一下官方資料。

關(guān)于“約定優(yōu)于配置”,還有很多東西,這里不細(xì)說,官方doc已經(jīng)說的很詳細(xì)了。

Gradle 其它不錯的特性

  1. 所有聲明都是一等公民
  2. 多project構(gòu)建
  3. 引用外部/通用構(gòu)建腳本
  4. Gradle wrapper

小結(jié)

  1. Gradle非常簡潔,項(xiàng)目本身的配置代碼非常少。
  2. Gradle在外部project構(gòu)建也支持很好,整體構(gòu)建簡單,并且通過公用外部構(gòu)建腳本,讓配置內(nèi)容盡量沒有冗余。
  3. Gradle很靈活,可以方面的增加和修改構(gòu)建過程。而Maven卻需要開發(fā)插件來支持。
  4. Gradle是基于Groovy的,也就是說配置中可以編寫自定義代碼,能適應(yīng)更復(fù)雜的場景,能完成更強(qiáng)大的功能,比如說:自動上傳、分發(fā)、部署等等。

項(xiàng)目實(shí)戰(zhàn)

Gradle介紹了那么多,可以看出,gradle是非常靈活的,可以適應(yīng)各種復(fù)雜環(huán)境。建議各位從架構(gòu)角度考慮gradle構(gòu)建,而不僅僅把它當(dāng)作一個構(gòu)建工具。下面來說說我們實(shí)際項(xiàng)目中的Gradle改造工作。

背景:

我們的項(xiàng)目經(jīng)過一個半Q的迅速發(fā)展,整個項(xiàng)目已經(jīng)由1個簡易后臺變成4個系統(tǒng)+若干腳本任務(wù)了,項(xiàng)目中存在很多冗余代碼和重復(fù)配置。我們使用上面介紹的方法對項(xiàng)目進(jìn)行了改造,以解決這兩個問題。

步驟:

要解決冗余代碼和通用配置的問題,最簡單的做法就是抽取出共同部分,作為其它所有項(xiàng)目的parent/common項(xiàng)目。方法:

1. 使用git submodule

將所有系統(tǒng)中公共的類庫和通用的配置,放到獨(dú)立的倉庫Common中。因?yàn)槲覀冇胓it來管理代碼,而git本身提倡多branch,多倉庫,所以采用git submodule方式,其它項(xiàng)目需要添加Common這個submodule:

git submodule add yourGitRepo deps/Common

最后的"deps/Common"是自定義的,意思就是在當(dāng)前的deps目錄下用Common名字來當(dāng)作submodule的clone。

如果你clone別的帶有submodule的項(xiàng)目時,默認(rèn)情況下,當(dāng)前的project并不會把submodule的代碼都clone下來,可以執(zhí)行:

git submodule foreach git pull

以下這段一般大家經(jīng)常會遇到:
當(dāng)你clone項(xiàng)目時,submodule會以最新的master分支上的commit id作為本次的tag下載,類似一個副本,因?yàn)橐话愦蠹叶际怯胹ubmodule,而不是修改它。所以當(dāng)你的submodule需要更新的時候,需要先執(zhí)行這段代碼:

git submodule foreach git checkout master

讓submodule切換到master分支了,然后就可以用上面的submodule pull來更新了。

2. gradle構(gòu)建:

鑒于上文對gradle優(yōu)點(diǎn)的描述,我們采用gradle來構(gòu)建。我們的項(xiàng)目最初都是基于maven來構(gòu)建的,從maven切換到gradle很簡單,在項(xiàng)目根目錄下,先執(zhí)行(假設(shè)你的機(jī)器已經(jīng)安裝了gradle環(huán)境,一般負(fù)責(zé)構(gòu)建的人首次需要安裝,開發(fā)人員可以不安裝):

gradle init wrapper

這樣,就會自動生成相關(guān)的gradlew,build.gradle,settings.gradle等文件和相關(guān)目錄,并會自動下載對應(yīng)版本的gradle binary包(所以以后不需要安裝)。Gradle會自動識別Maven里的配置,并相應(yīng)的導(dǎo)入進(jìn)來,有少量部分配置可能需要修改。

注:在已有的gradle項(xiàng)目里,盡量使用生成的gradlew這個wrapper,因?yàn)樗鼤詣酉螺d對應(yīng)版本的Gradle,也就是說團(tuán)隊(duì)合作的其他人開發(fā)機(jī)上是不需要手動安裝Gradle的,并且wrapper也讓大家的Gradle版本一致,避免問題。

3. gradle腳本修改

上面執(zhí)行完之后,環(huán)境已經(jīng)準(zhǔn)備好了,現(xiàn)在要做的就是修改構(gòu)建腳本:
因?yàn)橐呀?jīng)通過git submodule把公共項(xiàng)目放到獨(dú)立目錄(deps/Common)了,并且它本身也是獨(dú)立可構(gòu)建的項(xiàng)目,那么也就是說當(dāng)前有兩個項(xiàng)目了,一個是當(dāng)前project,一個是Common項(xiàng)目,要做的就是告訴gradle,要多項(xiàng)目構(gòu)建,編輯settings.gradle,增加項(xiàng)目配置:

include "deps:Common"

以上就是把Common引入到當(dāng)前項(xiàng)目了。
根據(jù)項(xiàng)目的不同,然后對應(yīng)修改build.gradle,就大功告成了??匆粋€例子:

// 這一段主要是把公共庫Common的構(gòu)建腳本引入,因?yàn)橐话銜型ㄓ玫呐渲迷诶锩?def userGradleScript = file("deps/Common/build.gradle")
if (userGradleScript.exists()) {
    apply from: userGradleScript
}
// 使用war插件,這樣就默認(rèn)引入了java插件
apply plugin: 'war'
// for jetty
apply plugin: 'jetty'
stopKey = 'yourStopKey' // 自定義的stopkey
stopPort = xxxx     // 停止端口
httpPort = xxxx     // 啟動http端口

// 項(xiàng)目屬性
group = 'yourApp'
version = '1.0.0'
description = """這里描述你的項(xiàng)目"""


// checkstyle config文件地址
checkstyle {
    configFile = file("deps/Common/config/checkstyle/checkstyle.xml")
}
// lib依賴
dependencies {
    // 依賴公共庫Common,compile是和maven里的compile scope一樣
    compile project(':deps:Common')
    compile 'commons-validator:commons-validator:1.4.0'
    compile('javax.servlet.jsp.jstl:jstl-api:1.2') {
        exclude(module: 'servlet-api')      // 防止版本沖突
    }
    compile 'javax.persistence:persistence-api:1.0.2'
    runtime 'mysql:mysql-connector-java:5.1.26'
    providedCompile 'org.apache.tomcat:tomcat-servlet-api:7.0.30'
    // providedCompile 這個conf在java插件里是報(bào)錯的,war里是正確的
    providedCompile 'javax.servlet.jsp:jsp-api:2.1'
    ...
}

我們再來簡單看下公共項(xiàng)目Common的構(gòu)建腳本:

// 定義一堆基礎(chǔ)插件
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: "jacoco"
apply plugin: 'checkstyle'
apply plugin: 'pmd'
apply plugin: 'findbugs'
apply plugin: 'eclipse'
apply plugin: 'idea'
// 定義項(xiàng)目屬性
group = 'Common'
version = '1.0.0'
description = """Giant common library"""

// 定義依賴倉庫
repositories {
    mavenCentral()
}
// project的額外屬性,這里用于定義profile屬性,模擬maven的profile
ext {
    if (project.hasProperty('profile')) {
        profile = project['profile']
    } else {
        profile = "dev"
    }
    println "profile:" + profile
}
// 額外增加source path
sourceSets {
    main {
        resources {
            srcDir "src/main/profiles/${profile}"
        }
    }
}
// project依賴
dependencies {
    compile 'ch.qos.logback:logback-core:1.0.13'
    compile 'ch.qos.logback:logback-classic:1.0.13'
    compile 'ch.qos.logback:logback-access:1.0.13'
    compile 'commons-io:commons-io:2.0.1'
    compile 'commons-lang:commons-lang:2.6'
    compile 'joda-time:joda-time:1.6.2'
    compile 'org.testng:testng:6.8.7'
    compile 'com.googlecode.jmockit:jmockit:1.5'
    ...
}
// task配置
checkstyle {
    ignoreFailures = true
    sourceSets = [sourceSets.main]
}
findbugs {
    ignoreFailures = true
    sourceSets = [sourceSets.main]
}
pmd {
    ruleSets = ["basic", "braces", "design"]
    ignoreFailures = true
    sourceSets = [sourceSets.main]
}
jacocoTestReport {
    reports {
        xml.enabled true
        html.enabled true
        csv.enabled false
    }
    sourceSets sourceSets.main
}
tasks.withType(Compile) {
    options.encoding = "UTF-8"
}
test {
    useTestNG()
    jacoco {
        excludes = ["org.*"]
    }
}

這樣,就可以在公共項(xiàng)目里配置好一堆基礎(chǔ)的task,dependencies等等,而使用這個公共項(xiàng)目的其它項(xiàng)目則可以直接使用,無需再額外配置。

4. run

腳本修改完了,就可以開始構(gòu)建了(不需要安裝gradle,直接使用生成的gradlew就行):

./gradlew build

// 基于profile構(gòu)建
./gradlew -Pprofile=dev build

常用構(gòu)建命令:

  • clean:清除之前的構(gòu)建
  • test:執(zhí)行測試
  • compileJava:編譯java
  • check:在test之后做一個check,一般代碼檢查插件,都是在這個階段做的
  • build:構(gòu)建打包

總結(jié)

隨著公司業(yè)務(wù)的發(fā)展,軟件系統(tǒng)變得日益復(fù)雜和龐大,這就要求有更靈活、更高效的構(gòu)建系統(tǒng)來支撐?,F(xiàn)代構(gòu)建系統(tǒng)Gradle提供了強(qiáng)大的功能、簡潔的語法、靈活的配置,能適應(yīng)各種復(fù)雜的構(gòu)建環(huán)境。利用多project構(gòu)建,讓整個系統(tǒng)模塊化,管理更高效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,273評論 6 342
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,048評論 25 709
  • 微博每隔一段時間就能爆出娛樂圈誰誰誰出軌了,然后原配、小三甚至是之前出過軌被唾罵的明星們紛紛進(jìn)了熱門頭...
    鱈零kelsey閱讀 318評論 0 0
  • 游綠城廣場,學(xué)瑞鵬精神 2017年7月5日肖靜 天氣相較于前幾日氣溫有所下降,今天,我們小紅旗實(shí)踐隊(duì)來到了綠城廣場...
    我想靜靜_24d6閱讀 343評論 0 0

友情鏈接更多精彩內(nèi)容