Gradle in Action

去年在写android程序时接触到了gradle,但后来没有深入使用。这次做日志收集模块,发现公司的ivy还是有很多问题,例如版本控制需要自己去定义,否则容易出现包依赖中版本错误的问题。后来发现杭研那边和github上用maven比较多,而后续的开发也会更多的依赖于这边的工具了,所以也想试试maven。今天突然想起来这个号称下一代的构建工具,所以找了这本《Gradle in Action》来学习一下,以下是读书笔记,另外自己在用的时候也发现了些trick,写在这里可能会对以后再遇到有点帮助。

1. Introduce

讨论了project automation的重要性 project automation的好处:

1. 不用手工集成,手工集成即浪费时间又容易错误。
2. 可以重复build,这个是每天都需要执行的,最好简单到点一个按钮就build。
3. 让build更加简单,而不需要为特定的操作系统装上特定的IDE来做build的事。

type of project automation :

1. On-demand builds:例如开发者checkout代码后在本机build
2. Triggered builds:例如代码一旦checkin就自动build
3. Scheduled builds:例如在每天晚上12点构建一把

build的工具
一些来说build有以下的几步,一是源码编译;二是class copy;三是集成交付。java的build工具有下面一些:

1.Apache Ant:本身也是用java写的,配置文件是xml的。但ant本身不提供依赖管理,需要自己找额外的工具进行依赖管理 ,例如ivy。我司就一直是ant+ivy的开发模式。
2. Maven:这个相当于ant+ivy的功能,另外对依赖库支持比较好。包含了整个构建流程的步骤。而ant可能还需要自己去写。但是maven本身的可能不太适合项目需要,然而写自己的构建流程又过于复杂。需要学写maven's internal extenstion API。

针对上面的问题,ant+ivy需要全手写,另外包依赖如果不配置就直接找最新的让人忍不了。而maven自己定置又过于复杂,所以需要更新的构建工具了。

2. Gradle

好处先跳过,你可以理解为maven+ivy+grunt+ant的优点的合集,并且使用json来换掉了xml

2.1 安装

官网下载最新的包,我放在了/global/exec/zhaowei/software下面,然后将路径加在了~/.bash_profile中就可以了。

2.2 配置

这里有一个坑需要提一下,我司的/home/* , /global,以及常用的/disk是独立的,为了方便在任何一台机器上都可以访问/home和/global,所以这两个目录做的非本地的。gradle的默认本地仓库是/home/zhaowei/.gradle,没有也会为你生成一个。但即使你修改了权限为777,也会报这样的错(我查了一下搜索引擎,基本没有直接这样贴出来的,所以我贴一下完整的后续有人遇到了问题没准可以搜到,有帮助记得给我留个言^_^):

FAILURE: Build failed with an exception.
* What went wrong: A problem occurred configuring root project 'logcollector'.
Could not open buildscript class cache for build file '/disk1/zhaowei/gitlab/logcollector/build.gradle' (/home/zhaowei/.gradle/caches/2.3/scripts/build_1f97h4gton9n8ui1dfcn7qcjk/ProjectScript/buildscript).
java.io.IOException: No locks available

这个明显就是木有权限导致的了,所以只能修改本地仓库的设置。 我是在bin/gradle中修改的全局参数,windows下也可以在bin/gradle.bat中修改,我就不说了。 写成了这样:

  ...
  8 GRADLE_OPTS=-Dgradle.user.home=/disk1/zhaowei/gradle#我加的就是这一行撒~                                                
  9 # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
  10 DEFAULT_JVM_OPTS=""
  ...

然后就OK出现BUILD SUCCESSFUL了。麻烦之处就是,必须把这个目录写成本地的,这会导致每次在不同的机器上使用gradle,可能需要确定下/disk1/zhaowei/这个目录是不是有,没有的话可能会被gradle新建出来。

2.2 构建

  1. 构建java项目
    gradle支持插件来使用别人写好的一些工具进行构建 。例如最常用的一种java plugin:
    它配好了一些任务。默认源代码在 src/main/java 而测试源代码在src/test/java,资源目录是src/main/resources,测试资源是src/test/resources。输出目录是build 文件夹。需要的jar文件在build/libs下面。 主要使用的就是java平台的javac和jar工具。

2.3 依赖管理

官方指导手册

  1. 使用自己的mvn和ivy资料库
    对于我司的资料库,之前是ivy,引入方式:

      apply plugin:'java'
      repositories {
          maven {
                url "http://nexus.corp.youdao.com/nexus/content/groups/public/"
          }
          ivy {
                url "http://fountain.rd.netease.com/share/ivy/"
          }
      }
      dependencies {
          compile group: 'org.apache.mina', name: 'mina-core', version: '2.0.7'
          compile group: 'external', name: 'fastjson_ali', version: '1.1.41'
      }
    
  2. 一个下载jar到本地的task

 task downloadLib(type: Copy) {
    from configurations.compile
    into 'lib'
    rename('(.*)-[\\.0-9]+.so$', '$1.so')
}
rename是把有某些版本号的信息给去掉,因为有的项目so文件加了后缀就找不到。  

可以将依赖的jar包copy到lib目录下,如果根目录下没有lib目录,会新建一个。

  1. 包路径另外一个可能会遇到的问题是:查找ivy中包路径是否存的问题
    例如下面这个: Could not find toolbox:flagfilter:1.1.348840. Searched in the following locations: http://nexus.corp.youdao.com/nexus/content/groups/public/toolbox/flagfilter/1.1.348840/flagfilter-1.1.348840.pom http://nexus.corp.youdao.com/nexus/content/groups/public/toolbox/flagfilter/1.1.348840/flagfilter-1.1.348840.jar http://fountain.rd.netease.com/share/ivy/toolbox/flagfilter/1.1.348840/ivy-1.1.348840.xml http://fountain.rd.netease.com/share/ivy/toolbox/flagfilter/1.1.348840/flagfilter-1.1.348840.jar Required by: :liveserver2:unspecified
    这里我发现,gradle其实确实是去路径下去找了的,但是之前的ivy习惯是不加版本号在.jar前面的,例如*/flagfilter/1.1.348840/flagfilter-1.1.348840.jar其实是*/flagfilter/1.1.348840/flagfilter.jar,那这样需要修改寻找规则,以下是我的修改:

    9 ivy { 10 url "http://fountain.rd.netease.com/share/ivy/" 11 layout "pattern", { 12 artifact "[organisation]/[module]/[revision]/[artifact]-revision.[ext]" 13 artifact "[organisation]/[module]/[revision]/artifact.[ext]" 14 } 15 }

这样就把加版本号与不加的都会查一下了。另外还可以加别的路径:

    例如再加个: 
    artifact "[organisation]/[module]/[revision]/[type]/[artifact](-[classifier]).[ext]"
    就可以找这样的了:
    http://fountain.rd.netease.com/share/ivy/toolbox/flagfilter/1.1.348840/jar/flagfilter-1.1.348840.jar
    http://fountain.rd.netease.com/share/ivy/toolbox/flagfilter/1.1.348840/jar/flagfilter.jar
  1. 可能会遇到某些ivy项目上中,有conf = "default->dev"这样的配置,但是在中并没有default

    这个时候会报一些这样的错:

    "FAILURE: Build failed with an exception.
    What went wrong:
    Could not resolve all dependencies for configuration ':runtime'.
     Module version :liveserver2:unspecified, configuration 'runtime' declares a dependency on configuration 'default' which is not declared in the module descriptor for toolbox:outlog:2.0.496136.0
    

    看上去是outlog这个包没有default这样一个配置 这个问题需要使用configuration,例如cowork有conf dev,那么需要写:

       compile configuration: 'dev',group :'outfox',name :'cowork', version:'6.2.2.0'
    

    就会用dev的依赖去下载所有的依赖包。

5.如果刚修改了一个ivy目录下的jar名,立即再次构建会继续找不到这个jar
即使你在浏览器中把目录输入是能找到的,但是由于缓存问题,会说没有找到。我是放着不管,过了一天再构建,就可以找到了。主要还是没有阅读gradle的源码不知道逻辑。。。好淡腾~

6.如果出现了下载*-svn.info失败,有可能这个包已经在前面的依赖中了,直接删除这个依赖试一下。

7.查看依赖树
新旧包很可能会冲突,那么找冲突包的依赖就比较重要了

    gradle dependencies

会打印出依赖树,可以方便的查到哪两个依赖产生了冲突,然后就好了,例如:

    compile (configuration: 'api',group :'outfox',name :'odfs', version:'6.0.+'  ){
          exclude(module: 'spring')
     }

2.4 小细节

2.4.1 不将一些jar文件打包到war文件中

例如servlet等jar文件在开发环境中是必须使用的,但是不希望被打包到war中,那么可以

dependencies {
    providedRuntime 'javax.servlet:servlet-api:2.5'    }
2.4.2 修改源码路径

gradle 默认的路径是如下

    src/main/java       Production Java source
    src/main/resources      Production resources
    src/test/java       Test Java source
    src/test/resources      Test resources
    src/sourceSet/java      Java source for the given source set
    src/sourceSet/resources     Resources for the given source set

但我司一般是src/java,src/test,conf,如果不修改sourceSets是不能编译出class的,所以改为:

      7 sourceSets {
      8     main {
      9         java {
     10             srcDir 'src/java'
     11         }
     12     }
     13 }
2.4.3 删除缓存

我在使用ivy时,出现了上面提到的没有default的问题。我改好了,重新编译还是有这个问题。后来把缓存清理了就OK了。 例如我的缓存存在:/disk1/zhaowei/gradle/caches 直接删除,下次编译就好了。

2.4.4 重命名

在下载的时候,遇到了一个so的坑,也就是公司写解析userId的so文件加载规则不能加版本号(千万只羊驼在奔腾~,费了我几个小时以为是别的问题)。所以需要重命名:

136 task downloadLib(type: Copy) {
137     from configurations.compile
138     into 'lib'
139     rename('(.*)-[\\.0-9]+.so$', '$1.so')
140 }  

不得不说groovy真是方便,这要换了ant估计麻烦死。

2.4.5 task定义

记得加<<,否则每次执行,这个task都会被执行,例如上面的这个,应该改为:

136 task downloadLib(type: Copy) <<{
137     from configurations.compile
138     into 'lib'
139     rename('(.*)-[\\.0-9]+.so$', '$1.so')
140 }  

不得不说groovy真是方便,这要换了ant估计麻烦死。 加<<是在初始化阶段先不执行,否则不指定这个task也会执行一次,类似doLast的功能。

2.4.6 有先后关系的多个依赖
task serverDeploy(dependsOn: ['initLib','unzipDefinitionData','unzipWebPhraseData']) << {  
    stopServer()
    deleteServer()
    webappsCopy()
    startServer()
}
这里加单引号或者不加是一样的

参考资料

看官网,遇到问题google或者stackoverflow就可以了

  1. 一定要看的官网
  2. 具体实现可能需要用到的
  3. 官网的练习
comments powered by Disqus