灵活强大的构建系统Gradle
前言
構(gòu)建,軟件生命周期中重要的一環(huán),在現(xiàn)代軟件開(kāi)發(fā)過(guò)程中,起著越來(lái)越重要的作用。過(guò)去在Java或類Java的世界里,Ant、Maven再熟悉不過(guò)了,Maven憑借其強(qiáng)大的依賴配置戰(zhàn)勝Ant,基本上成為了Java構(gòu)建的標(biāo)準(zhǔn)。而在現(xiàn)代,系統(tǒng)日益復(fù)雜,構(gòu)建的靈活性要求越來(lái)越高,比如:構(gòu)建過(guò)程中需要打包上傳到服務(wù)器,Maven無(wú)法很好地支持這種復(fù)雜的系統(tǒng)構(gòu)建,所以,我選擇了Gradle,一個(gè)基于Groovy,更靈活更強(qiáng)大的構(gòu)建系統(tǒng),能幫助我們構(gòu)建更復(fù)雜的項(xiàng)目。
為什么選擇Gradle
從框架方向來(lái)看:
從語(yǔ)言特性來(lái)看:
Gradle在開(kāi)源項(xiàng)目中的使用
現(xiàn)在使用Gradle構(gòu)建的開(kāi)源項(xiàng)目很多,我有過(guò)接觸的比如:Grails, Griffon, Groovy, Hibernate, Spring
還有很多其它開(kāi)源項(xiàng)目也都在用Gradle,比如Tapestry,Qi4J,Netflix下所有開(kāi)源項(xiàng)目(python、c++、html等除外)等等。
Gradle在企業(yè)中的使用
現(xiàn)在使用Gradle來(lái)做構(gòu)建體系的公司也越來(lái)越多,linkedin就很早開(kāi)始切換到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沒(méi)什么兩樣。我這里安裝的Gradle版本是1.10,詳細(xì)信息見(jiàn)下:
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_64Gradle的Features很多,官網(wǎng)doc介紹很詳細(xì),我這里就不多說(shuō)。下面簡(jiǎn)單介紹一下Gradle構(gòu)建相關(guān)的東西。
Gradle基礎(chǔ)
1,Gradle有兩個(gè)最基本的概念:project和task。Gradle里面的所有東西都基于這兩個(gè)概念。project通常指一個(gè)項(xiàng)目,而task指構(gòu)建過(guò)程中的任務(wù)。一次構(gòu)建可以有1到n個(gè)project,每個(gè)project有1到n個(gè)task。 2,Gradle有一個(gè)類似Maven中pom.xml的配置文件:build.gradle。功能也基本一樣,負(fù)責(zé)當(dāng)前project的構(gòu)建定義。看一個(gè)build.gradle的簡(jiǎn)單例子:
bob [10:46] ? pwd /Users/bob/framework/gradle-1.10/samples/userguide/tutorial/hello // 在你安裝的gradle根目錄下有對(duì)應(yīng)的samples目錄,里面有很多例子bob [10:46] ? cat build.gradle task hello {doLast {println 'Hello world!'} }文件中定義了一個(gè)task:hello,task的內(nèi)容是 “println ‘Hello world!’“,我們來(lái)執(zhí)行一下:
bob [10:49] ? gradle -q hello Hello world!可以看到,輸出了”Hello world!“,這里-q的意思是quiet模式,只輸出構(gòu)建中的必要信息。
gradle里可以定義多個(gè)task,task之間也可以有依賴關(guān)系,還可以定義默認(rèn)task,看一個(gè)例子: 帶有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)沒(méi)有task指定時(shí),則會(huì)執(zhí)行默認(rèn)的task。
Gradle依賴
Gradle和Maven在依賴管理上幾乎差不多,核心的概念是一樣的,只不過(guò)Gradle語(yǔ)法更精簡(jiǎn),并且多了一些更靈活的自定義配置。我們先看一個(gè)例子,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,那勢(shì)必會(huì)大大減少配置文件代碼塊,并有更強(qiáng)的可讀性,也就意味著系統(tǒng)更加穩(wěn)健。 1,Gradle在依賴配置上面,和Maven一樣,支持傳遞性依賴,然后和Maven不同的是,它還支持排除傳遞性依賴以及關(guān)閉傳遞性依賴。 2,Gradle的依賴scope,也基本和Maven一樣,不過(guò)它是通過(guò)配置來(lái)定義,plugin來(lái)支撐和加強(qiáng)的,所以除了基本的compile、runtime等scope外,Gradle還可以自定義出很多配置,針對(duì)不同的配置寫(xiě)不同的task來(lái)完成更復(fù)雜更靈活的構(gòu)建任務(wù)。
依賴相關(guān)的倉(cāng)庫(kù)配置很靈活,支持多種repository,看下面repository定義例子:
bob [11:07] ? cat userguide/artifacts/defineRepository/build.gradlerepositories {mavenCentral() // 定義倉(cāng)庫(kù)為maven中心倉(cāng)庫(kù) } repositories {jcenter() // 定義倉(cāng)庫(kù)為jcenter倉(cāng)庫(kù) } repositories {maven {url "http://repo.mycompany.com/maven2" // 定義依賴包協(xié)議是maven,地址是公司的倉(cāng)庫(kù)地址} } repositories { // 定義本地倉(cāng)庫(kù)目錄flatDir {dirs 'lib'} } repositories { // 定義ivy協(xié)議類型的倉(cāng)庫(kù)ivy {url "http://repo.mycompany.com/repo"} }可以看到,對(duì)于常用的maven、ivy、local以及jcenter的repository都有支持,語(yǔ)法很簡(jiǎn)單。而且還可以通過(guò)編寫(xiě)task來(lái)支持更復(fù)雜的repository,更多詳情可以查看安裝包里的對(duì)應(yīng)目錄下文件查看。
Gradle構(gòu)建
和Maven一樣,Gradle也是通過(guò)artifact來(lái)打包構(gòu)建的。得益于上述的Gradle本身的特性,artifact在Gradle里實(shí)現(xiàn)得更靈活一些。看一個(gè)例子:
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來(lái)完成artifact task myTask(type: MyTaskType) {destFile = file('build/somefile.txt') } artifacts {archives(myTask.destFile) {name 'my-artifact'type 'text'builtBy myTask} }## 根據(jù)自定義task來(lái)完成artifact task generate(type: MyTaskType) {destFile = file('build/somefile.txt') } artifacts {archives file: generate.destFile, name: 'my-artifact', type: 'text', builtBy: generate }這樣就簡(jiǎn)單地定義了好幾種artifact生成的定義,根據(jù)不同的場(chǎng)景需求,生成文本文件、jar包或者zip,還可以再上傳到服務(wù)器上。 一般情況下,常用的插件,比如說(shuō)”Java plugin”都默認(rèn)定義了”jar”這樣的artifact task,所以一般不需要額外開(kāi)發(fā)。但是,針對(duì)于一些復(fù)雜情況,或者在plugin基礎(chǔ)上增強(qiáng)的話,自定義artifact task還是非常有用的。
Gradle構(gòu)建的項(xiàng)目,發(fā)布到倉(cāng)庫(kù)中,也非常容易:
apply plugin: 'maven'uploadArchives {repositories {ivy {credentials {username "username"password "pw"}url "http://repo.mycompany.com"}} }Gradle 插件
上面簡(jiǎn)介介紹了一下Gradle的一些概念和配置,要用到項(xiàng)目中run起來(lái),現(xiàn)在還還要一步,就是本節(jié)介紹的Gradle插件。Gradle現(xiàn)在已經(jīng)支持很多插件,這給開(kāi)發(fā)者帶來(lái)極大的便利,先說(shuō)說(shuō)Java插件吧。 1,使用Java plugin,只需要在build.gradle中加入這句話:
apply plugin: 'java'2,了解或設(shè)置Java project布局。Gradle和Maven一樣,采用了“約定優(yōu)于配置”的方式對(duì)Java project布局,并且布局方式是和Maven一樣的,此外,Gradle還可以方便的自定義布局。在Gradle中,一般把這些目錄叫做source set。看下官方的答案:
這里要注意,每個(gè)plugin的source set可能都不一樣。
同樣的,Java plugin還定義好了一堆task,讓我們可以直接使用,比如:clean、test、build等等。這些task都是圍繞著Java plugin的構(gòu)建生命周期的:
圖中每一塊都是一個(gè)task,箭頭表示task執(zhí)行順序/依賴,比如執(zhí)行task jar,那么必須先執(zhí)行task compileJava和task processResources。另外可以看到,Gradle的Java plugin構(gòu)建生命周期比較復(fù)雜,但是也表明了更加靈活,而且,在項(xiàng)目中,一般只使用其中常用的幾個(gè):clean test check build 等等。
gradle構(gòu)建過(guò)程中,所有的依賴都表現(xiàn)為配置,比如說(shuō)系統(tǒng)運(yùn)行時(shí)的依賴是runtime,gradle里有一個(gè)依賴配置叫runtime,那么系統(tǒng)運(yùn)行時(shí)會(huì)加載這個(gè)依賴配置以及它的相關(guān)依賴。這里說(shuō)的有點(diǎn)繞,可以簡(jiǎn)單理解依賴和maven類似,只不過(guò)gradle用configuration實(shí)現(xiàn),所以更靈活,有更多選擇。下圖是依賴配置關(guān)系圖以及和task調(diào)用的關(guān)系圖:
可以看到,基本和Maven是一樣的。其實(shí)Gradle里面這些依賴(scope)都是通過(guò)configuration來(lái)實(shí)現(xiàn)的,這里就不細(xì)說(shuō),有興趣的可以研究一下官方資料。
關(guān)于“約定優(yōu)于配置”,還有很多東西,這里不細(xì)說(shuō),官方doc已經(jīng)說(shuō)的很詳細(xì)了。
Gradle 其它不錯(cuò)的特性
1,所有聲明都是一等公民 2,多project構(gòu)建 3,引用外部/通用構(gòu)建腳本 4,Gradle wrapper
小結(jié)
1,Gradle非常簡(jiǎn)潔,項(xiàng)目本身的配置代碼非常少。 2,Gradle在外部project構(gòu)建也支持很好,整體構(gòu)建簡(jiǎn)單,并且通過(guò)公用外部構(gòu)建腳本,讓配置內(nèi)容盡量沒(méi)有冗余。 3,Gradle很靈活,可以方面的增加和修改構(gòu)建過(guò)程。而Maven卻需要開(kāi)發(fā)插件來(lái)支持。 4,Gradle是基于Groovy的,也就是說(shuō)配置中可以編寫(xiě)自定義代碼,能適應(yīng)更復(fù)雜的場(chǎng)景,能完成更強(qiáng)大的功能,比如說(shuō):自動(dòng)上傳、分發(fā)、部署等等。
項(xiàng)目實(shí)戰(zhàn)
Gradle介紹了那么多,可以看出,gradle是非常靈活的,可以適應(yīng)各種復(fù)雜環(huán)境。建議各位從架構(gòu)角度考慮gradle構(gòu)建,而不僅僅把它當(dāng)作一個(gè)構(gòu)建工具。下面來(lái)說(shuō)說(shuō)我們實(shí)際項(xiàng)目中的Gradle改造工作。
背景:
我們的項(xiàng)目經(jīng)過(guò)一個(gè)半Q的迅速發(fā)展,整個(gè)項(xiàng)目已經(jīng)由1個(gè)簡(jiǎn)易后臺(tái)變成4個(gè)系統(tǒng)+若干腳本任務(wù)了,項(xiàng)目中存在很多冗余代碼和重復(fù)配置。我們使用上面介紹的方法對(duì)項(xiàng)目進(jìn)行了改造,以解決這兩個(gè)問(wèn)題。
步驟:
要解決冗余代碼和通用配置的問(wèn)題,最簡(jiǎn)單的做法就是抽取出共同部分,作為其它所有項(xiàng)目的parent/common項(xiàng)目。方法:
1,使用git submodule
將所有系統(tǒng)中公共的類庫(kù)和通用的配置,放到獨(dú)立的倉(cāng)庫(kù)Common中。因?yàn)槲覀冇胓it來(lái)管理代碼,而git本身提倡多branch,多倉(cāng)庫(kù),所以采用git submodule方式,其它項(xiàng)目需要添加Common這個(gè)submodule:
git submodule add yourGitRepo deps/Common最后的”deps/Common”是自定義的,意思就是在當(dāng)前的deps目錄下用Common名字來(lái)當(dāng)作submodule的clone。
如果你clone別的帶有submodule的項(xiàng)目時(shí),默認(rèn)情況下,當(dāng)前的project并不會(huì)把submodule的代碼都clone下來(lái),可以執(zhí)行:
git submodule foreach git pull以下這段一般大家經(jīng)常會(huì)遇到: 當(dāng)你clone項(xiàng)目時(shí),submodule會(huì)以最新的master分支上的commit id作為本次的tag下載,類似一個(gè)副本,因?yàn)橐话愦蠹叶际怯胹ubmodule,而不是修改它。所以當(dāng)你的submodule需要更新的時(shí)候,需要先執(zhí)行這段代碼:
git submodule foreach git checkout master讓submodule切換到master分支了,然后就可以用上面的submodule pull來(lái)更新了。
2,gradle構(gòu)建:
鑒于上文對(duì)gradle優(yōu)點(diǎn)的描述,我們采用gradle來(lái)構(gòu)建。我們的項(xiàng)目最初都是基于maven來(lái)構(gòu)建的,從maven切換到gradle很簡(jiǎn)單,在項(xiàng)目根目錄下,先執(zhí)行(假設(shè)你的機(jī)器已經(jīng)安裝了gradle環(huán)境,一般負(fù)責(zé)構(gòu)建的人首次需要安裝,開(kāi)發(fā)人員可以不安裝):
gradle init wrapper這樣,就會(huì)自動(dòng)生成相關(guān)的gradlew,build.gradle,settings.gradle等文件和相關(guān)目錄,并會(huì)自動(dòng)下載對(duì)應(yīng)版本的gradle binary包(所以以后不需要安裝)。Gradle會(huì)自動(dòng)識(shí)別Maven里的配置,并相應(yīng)的導(dǎo)入進(jìn)來(lái),有少量部分配置可能需要修改。
注:在已有的gradle項(xiàng)目里,盡量使用生成的gradlew這個(gè)wrapper,因?yàn)樗鼤?huì)自動(dòng)下載對(duì)應(yīng)版本的Gradle,也就是說(shuō)團(tuán)隊(duì)合作的其他人開(kāi)發(fā)機(jī)上是不需要手動(dòng)安裝Gradle的,并且wrapper也讓大家的Gradle版本一致,避免問(wèn)題。
3,gradle腳本修改
上面執(zhí)行完之后,環(huán)境已經(jīng)準(zhǔn)備好了,現(xiàn)在要做的就是修改構(gòu)建腳本: 因?yàn)橐呀?jīng)通過(guò)git submodule把公共項(xiàng)目放到獨(dú)立目錄(deps/Common)了,并且它本身也是獨(dú)立可構(gòu)建的項(xiàng)目,那么也就是說(shuō)當(dāng)前有兩個(gè)項(xiàng)目了,一個(gè)是當(dāng)前project,一個(gè)是Common項(xiàng)目,要做的就是告訴gradle,要多項(xiàng)目構(gòu)建,編輯settings.gradle,增加項(xiàng)目配置:
include "deps:Common"以上就是把Common引入到當(dāng)前項(xiàng)目了。 根據(jù)項(xiàng)目的不同,然后對(duì)應(yīng)修改build.gradle,就大功告成了。看一個(gè)例子:
// 這一段主要是把公共庫(kù)Common的構(gòu)建腳本引入,因?yàn)橐话銜?huì)有通用的配置在里面 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 // 啟動(dòng)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 {// 依賴公共庫(kù)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 這個(gè)conf在java插件里是報(bào)錯(cuò)的,war里是正確的providedCompile 'javax.servlet.jsp:jsp-api:2.1'... }我們?cè)賮?lái)簡(jiǎn)單看下公共項(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"""// 定義依賴倉(cāng)庫(kù) 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 = truesourceSets = [sourceSets.main] } findbugs {ignoreFailures = truesourceSets = [sourceSets.main] } pmd {ruleSets = ["basic", "braces", "design"]ignoreFailures = truesourceSets = [sourceSets.main] } jacocoTestReport {reports {xml.enabled truehtml.enabled truecsv.enabled false}sourceSets sourceSets.main } tasks.withType(Compile) {options.encoding = "UTF-8" } test {useTestNG()jacoco {excludes = ["org.*"]} }這樣,就可以在公共項(xiàng)目里配置好一堆基礎(chǔ)的task,dependencies等等,而使用這個(gè)公共項(xiàng)目的其它項(xiàng)目則可以直接使用,無(wú)需再額外配置。
4,run
腳本修改完了,就可以開(kāi)始構(gòu)建了(不需要安裝gradle,直接使用生成的gradlew就行):
./gradlew build// 基于profile構(gòu)建 ./gradlew -Pprofile=dev build常用構(gòu)建命令: clean:清除之前的構(gòu)建 test:執(zhí)行測(cè)試 compileJava:編譯java check:在test之后做一個(gè)check,一般代碼檢查插件,都是在這個(gè)階段做的 build:構(gòu)建打包
總結(jié)
隨著公司業(yè)務(wù)的發(fā)展,軟件系統(tǒng)變得日益復(fù)雜和龐大,這就要求有更靈活、更高效的構(gòu)建系統(tǒng)來(lái)支撐。現(xiàn)代構(gòu)建系統(tǒng)Gradle提供了強(qiáng)大的功能、簡(jiǎn)潔的語(yǔ)法、靈活的配置,能適應(yīng)各種復(fù)雜的構(gòu)建環(huán)境。利用多project構(gòu)建,讓整個(gè)系統(tǒng)模塊化,管理更高效。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的灵活强大的构建系统Gradle的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 美团Android自动化之旅—适配渠道包
- 下一篇: 机器学习在美团配送系统的实践:用技术还原