此篇文章是基于Dreamforce数星期的Gradle使用体验后总结出来的

如果不知道Gradle为何物的,可以移步至此:Gradle入门

为什么会用Gradle

其实这个也是我最先使用Gradle这个脚本的的疑问,说实话,dreamforce是带着疑问去编写这个脚本的,幸运的是, gradle并不是一个非主流的脚本,他结合并保持了动态脚本语言的特色和书写方式,而dreamforce有着JAVA和Python的基础能很顺利的上手这门脚本语言。

gradle上面支持Groovy语法,也就是说也支持着原生的JAVA语法,如果你实在不清楚groovy的API,可以干脆直接写JAVA代码,这是完全支持的。gradle与Maven可以完美的结合,gradle也可以完美的支持ANT脚本,shell脚本..etc..

经过了数天的脚本编写后,Dreamforce发现Gradle确实比较实用,扩展性相当不错,甚至可以书写出非常良好可维护的代码结构,而这是ANT这些脚本很难做到的。(你见过面向对象的ANT么?可是你可以写面向对象的Gradle)

 

About Maven

在Gradle的管方文档上面,你可以很简单的使用的Maven,代码如下:

repositories {
 mavenCentral() }
 dependencies {
 compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
 testCompile group: 'junit', name: 'junit', version: '4.+' }

只需要在命令行使用:gradle build就可以将相关依赖下载下来,但是在实际项目中,这是有风险的。

首先,我们不能人为的控制依赖下载的触发条件,其次dependencies用Hard Code的方式书写也不太合理,同时这些dependencies也不能得到一个合理的管理,在后续的脚本中如果重用也很难。

为了解决这个问题,我们引入Task概念和Properties来进行管理

configurations {
 required_component
 }

增加一个 required_component的配置变量集,此变量集主要用来存储maven的依赖

 

task preCheck <<{
if (!file("gradle.properties").exists()) {
 logger.error('gradle.properties is not exists , please create this file first and then build again')
 }
 if (!project.hasProperty('customer_dependencies')){
 logger.error('Please indicate the dependencies')
 }
 else{
 dependencies {
 required_component(customer_dependencies)
 }
 }
 }

 

此段代码用一个preCheck来控制denpendencies的注入,当gradle.properties存在且有denpendencies属性时,对会进行denpendencies的注入,此时Maven才会有相应的依赖关系。

代码中的logger.error()方法可以暂时忽略,后面会专门讲述Gradle的日志问题

 

def tempFolderName = "tempFolder"
task downloadDependencies << {
 dependencies
 copy{
 from configurations.required_component
 into tempFolderName
 }
 }

 

此段代码是下载Maven的依赖,并将依赖移动”tempFolderName”这个文件夹里,denpendencies是Gradle下载Maven依赖的API,可以直接调用。

附gradle.properties:

required_component=org.hibernate:hibernate-core:3.6.7.Final,junit:junit:4.+

File IO

使用Gradle并无法避免的大量进行IO操作,这里将会列出我常用且推荐的IO操作方式:

列举出当前的目录(可过滤)

tempDir.traverse(
 nameFilter:~/.*\.zip/,
 maxDepth:0
 ){
 println it
 }

此段代码是匹配出tempDir下所有ZIP文件,将打印出文件名,匹配深度为0,意味着不会进行子文件的递归。

文件复制移动:

copy{
 from tempA
 into tempB
 }

将tempA移动到tempB

解压文件:

 

task unzipProjectTemp << {
tempDir.traverse(
 nameFilter:~/.*\.zip/,
 maxDepth:0
 ){
 if(it.path.indexOf(name)>-1){
 ant.unzip(  src:it,
 dest:srcDist,
 overwrite:"false" )
 }
 }
 }

 

此段代码首先是匹配出tempDir目录下的所有ZIP文件, it.path.indexOf(name)>-1 是名称匹配,在解压时使用ANT进行解压到srcDist目录(如果配有ant_home,将会使用用户配置的ANT,否则使用Gradle自带的ANT版本)

删除文件及目录

task clean << {
 if(tempDir.exists()){
 tempDir.deleteDir()
 }
 if(gradleTemp.exists()){
 gradleTemp.deleteDir()
 }
 }

如果存在上面的目录则删除

if(file("log.txt").exists()){
 file("log.txt").delete()
 }

如果log.txt存在则删除

 

Gradle日志处理

gradle官网推荐的做法是使用LOG4j,但是说实话对于如此小的脚本使用Log4j真的就太重量级了。所以Dreamforce自己写了一个简单的日志处理功能

一个简单的日志至少应该能区分INFO和ERROR两个信息级别,不仅可以在控制台显示信息,也可以将日志输出到文件中去

 

import java.util.List;
 import java.util.ArrayList;
 public class GradleLog {
 private List messages = new ArrayList();
 def logFile = "log.txt" //定义日志文件名
 private boolean enable = false; //定义一个日志开关
 public GradleLog(boolean enable){
 this.enable = enable
 }
 public boolean isEnable() {
 return enable;
 }
 public List getMessages() {
 return messages;
 }
 public void info(String message){
 getMessages().add(message)
 println message//打印当前消息
 output(message) //输出消息
 }
 public void error(String message){
 getMessages().add(message)
 output(message)
 output("Build Failed")
 throw new GradleException(message) //Error级别默认抛异常
 }
 private void output(String message){
 if(isEnable()){ //开关开启状态才会输出日志
 def file = new File(logFile)
 if(!file.exists()){
 file.createNewFile(); //日志文件如果不存在,则新建一个
 }
 def ln = System.getProperty('line.separator') //获取当前系统分隔符
 file << message+ln //将消息追加到日志文件中去,并追加一个换行分隔符
 }
}
}

 

上面这代段码就是一个简单的Gradle日志类,这个对象是可以直接写到Gradle上面去的。

 

GradleLog logger = new GradleLog(true)//If needn't this log , please set the value to 'false'
logger.error('gradle.properties is not exists , please create this file first and then build again')
logger.info("     --Unzip from "+ it.path +" to current directory")

 

使用方式如上,当然,ERROR级别是默认抛异常,有特殊需要的话可以进行更为细节的封装。

 

Pipline式的Task管理

我看过很多网上的Gradle代码,其实都没有进行一个方法的合理管制,这种容易造成代码失控,当脚本写的越来越多的时候,你无法避免的会出现一些BUG,而将Task在第一时间进行管理是一个很好的开始。

建议所有Task采用闭包,这样Gradle无法自动的去调用Task,你可以自主的进行Task的管理。

task download<< {
 dependencies
 copy{
 from configurations.required_component
 into tempFolderName
 }
 }

如上所示,这是一个闭包形式的Task,这种Task将不会自动运行。而这也是Dreamforce极为推荐的方式

task download {
 //...
 }

而如上所示,就是一个非闭包的Task,一旦Gradle进行运行,此Task就会自动执行,显然不满足我们的Task管理需求。

接下来就是如何使用Pipline的方式进行Task管理

 

task start << {
 if(file("log.txt").exists()){
 file("log.txt").delete()
 }
 logger.info "#################################"
 logger.info "#                                                                                             #"
 logger.info "#                                                                                             #"
 logger.info "#                         Assemble Build                                    #"
 logger.info "#                                                                                             #"
 logger.info "#                                                                                             #"
 logger.info "#################################"
 logger.info "The Artifactory Url is :" + mavenUrl
 def pipeline = [preCheck,downloadModel,unzipProjectTemp,unzipACTComponent,clean]
 def curse = 0
while(piplineFlag){
 println "### "+pipeline[curse]+"..."
 pipeline[curse].execute()
 curse++
 }
}

Pipline里指定的是Task的集合,将所有要运行的Task按照顺序放入Pipeline里,当然也可以将此pipeline放到Properties进行注入管理。

然后进行迭代执行,这里一定要注意,迭代执行的前提是piplineFlag为真,这是一个全局变量,同时Task如果出错变会设此标志为假,那么Pipeline将不会继续向下执行。同时最后一个Pipeline要将此值设为False

通过Pipeline方式的Task管理,你可以很清楚的了解到当前脚本的执行顺序,以及生命周期,利于维护,同时脚本的可读性也大大提升

 

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: