前言
構(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ǔ)
- Gradle有兩個最基本的概念:project和task洞斯。Gradle里面的所有東西都基于這兩個概念。project通常指一個項(xiàng)目惧财,而task指構(gòu)建過程中的任務(wù)巡扇。一次構(gòu)建可以有1到n個project,每個project有1到n個task垮衷。
- 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)健冤议。
- Gradle在依賴配置上面斟薇,和Maven一樣,支持傳遞性依賴恕酸,然后和Maven不同的是堪滨,它還支持排除傳遞性依賴以及關(guān)閉傳遞性依賴。
- 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插件吧君编。
- 使用Java plugin,只需要在build.gradle中加入這句話:
apply plugin: 'java'
-
了解或設(shè)置Java project布局绍昂。Gradle和Maven一樣啦粹,采用了“約定優(yōu)于配置”的方式對Java project布局,并且布局方式是和Maven一樣的窘游,此外唠椭,Gradle還可以方便的自定義布局。在Gradle中忍饰,一般把這些目錄叫做source set贪嫂。看下官方的答案:
Alt description
這里要注意艾蓝,每個plugin的source set可能都不一樣力崇。
同樣的斗塘,Java plugin還定義好了一堆task,讓我們可以直接使用亮靴,比如:clean馍盟、test、build等等茧吊。這些task都是圍繞著Java plugin的構(gòu)建生命周期的:
圖中每一塊都是一個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)系圖:
可以看到赋焕,基本和Maven是一樣的。其實(shí)Gradle里面這些依賴(scope)都是通過configuration來實(shí)現(xiàn)的仰楚,這里就不細(xì)說隆判,有興趣的可以研究一下官方資料。
關(guān)于“約定優(yōu)于配置”僧界,還有很多東西侨嘀,這里不細(xì)說,官方doc已經(jīng)說的很詳細(xì)了捂襟。
Gradle 其它不錯的特性
- 所有聲明都是一等公民
- 多project構(gòu)建
- 引用外部/通用構(gòu)建腳本
- Gradle wrapper
小結(jié)
- Gradle非常簡潔咬腕,項(xiàng)目本身的配置代碼非常少。
- Gradle在外部project構(gòu)建也支持很好葬荷,整體構(gòu)建簡單涨共,并且通過公用外部構(gòu)建腳本纽帖,讓配置內(nèi)容盡量沒有冗余。
- Gradle很靈活举反,可以方面的增加和修改構(gòu)建過程懊直。而Maven卻需要開發(fā)插件來支持。
- 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插件里是報錯的漠畜,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)來支撐〗孤模現(xiàn)代構(gòu)建系統(tǒng)Gradle提供了強(qiáng)大的功能、簡潔的語法雏逾、靈活的配置嘉裤,能適應(yīng)各種復(fù)雜的構(gòu)建環(huán)境。利用多project構(gòu)建栖博,讓整個系統(tǒng)模塊化屑宠,管理更高效。