此篇文章是基于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管理,你可以很清楚的了解到当前脚本的执行顺序,以及生命周期,利于维护,同时脚本的可读性也大大提升