虽然本站已经多次发布过关于Gradle的新闻,但从未系统的发布过关于Gradle使用的帖子,本系列的用意就是要补上这个空缺。
对于Gradle的各种好处和优点,稍微对Groovy社区现状有些了解的,基本上已经知道了个大概,这里便不再赘述。对于那些不太了解Gradle的读者,只要记住一句话就够了:Gradle就是可以使用Groovy来书写构建脚本的构建系统,支持依赖管理和多项目,类似Maven,但比之简单轻便。
作为系列开篇,我们将给出一个Groovy工程的Gradle模板,同时还会介绍一个古怪的问题及其解决办法,这一问题对于习惯使用IDE创建Groovy类的同学估计不会碰到。但大家就当是看新鲜吧,万一今后碰到类似的问题,也知道如何解决,以及原因是什么。
与其去学习那么多的Gradle相关的概念,不妨先看看一个使用Gradle的典型的Groovy工程是个什么样子:
usePlugin 'groovy'
repositories {
flatDir dirs: "lib"
}
dependencies {
groovy ':groovy-all:1.7.0'
compile fileTree(dir: 'lib', includes: ['*.jar'])
runtime fileTree(dir: 'lib', includes: ['*.jar'])
}
// usage: gradle -i run
task run(dependsOn: classes) << {
captureStandardOutput(LogLevel.INFO)
ant.java(
classname: 'fox.gem.GroovyClass',
fork: true,
classpath: "${sourceSets.main.runtimeClasspath.asPath}"
)
}
以上是Gradle的构建文件build.gradle中的内容,是不是比Ant的build.xml要简单太多了?这就是“惯例”的力量。由此,我们可以了解Gradle的第一个特点:“CoC(惯例优于配置)”。只要按照Gradle的惯例,那么可以最大限度的减少build文件的内容。不消说,惯例当然也是可以修改的,否则早就有人对其进行口诛笔伐了。至于如何修改,那是后话。
在这一节,我们所需了解的惯例就是Gradle对于工程目录布局的要求,其结构:
- src/main/java,java类目录
- src/main/groovy,groovy类/文件目录
- src/main/resources,源代码所用的资源目录
- src/test/java,java测试类目录
- src/test/groovy,groovy测试类目录
- src/test/resources,测试代码所用的资源目录
- build/classes,编译后的类目录
- build/libs,生成的jar所在目录
本文所用的目录结构便是按此结构创建的。接下来,看第一句话:usePlugin 'groovy'。这似乎意味着Gradle内部有一个插件系统,的确如此。插件架构是Gradle的第二个特点,因为我们创建的是Groovy工程,自然就要用到“groovy”插件。上面的目录结构中关于groovy的部分,实际就是这个插件的“惯例”。每个插件都会提供相关的任务和惯例,这一点请查阅相应的插件文档。
接下来的两部分:repositories和dependencies,都是用于依赖管理的。前者指出依赖所在的位置,后者指出依赖的东西。在这里之所以设置成这样,主要是我直到现在都是习惯把相关依赖手动的复制到工程下的lib,然后在配置文件中直接指明“*.jar”。主要的原因还在于网络,另外就是尚在小规模使用。对于有条件且网络条件不成问题的使用者,建议在公司内建一个私服,然后指出相应的依赖。对此,我没有太多的经验。但是参照网络上关于Maven使用的一些这方面的资料,应该还是有指导意义的。
我相信,以上这种“repositories和dependencies”的使用,对于偏爱Ant的读者是一个最自然的过渡了。这部分的内容非常明了,只对其中的“groovy ':groovy-all:1.7.0'”做个说明。这句话主要是为Gradle的groovy插件服务的,让它知道目前使用的groovy的lib,其写法和Maven的很类似,其格式为:“组:名字:版本”,对于咱们这种完全使用文件目录的形式,只需要使用后两个即可,它们会被用来组合成jar的文件名。以上就是在工程的lib下查找“groovy-all-1.7.0.jar”。
自此,你已经可以使用Gradle的任务来编译和测试工程了,最常用的命令:
- classes:编译源代码
- jar:源代码打包
- testClasses:编译测试代码
- test:运行单元测试
- javadoc/groovydoc:生成文档
- build:以上的一条龙服务
可是,如何运行我的类呢?很遗憾,目前Gradle中并没有直接提供相应的手段,得要借助ant。好消息是,在Gradle 0.9中将增加运行java类的支持,不过该版本目前并没有正式发布。
运行程序,得要自己动手写Gradle的Task了。注意上述build.gradle的最后一部分,完全是groovy代码,而且有些地方还很象ant,如那个dependsOn属性。这便是Gradle第三个,也是最振奋人心的特性:在构建脚本中完全支持Groovy。Gradle的每个任务就是一个闭包,就这么简单。其中的ant变量,就是Groovy的AntBuilder。利用Ant的java Target,我们可以很轻易的运行一个Java类。对于这段脚本,另一个值得注意的便是:captureStandardOutput(LogLevel.INFO),它的作用是捕获stdout。为什么要这样?这是因为在ant.java中,我们使用了fork=true,这表明程序运行在另一个jvm中,程序的输出(println),不会显示到我们目前的jvm中。如果去掉这句话,在目前的jvm中完全可以看到我们java类的输出。光指定捕获还不够,在运行任务时,还需要使用-i开关,即“gradle -i run”,这样我们才能看到我们想要的东西。但不幸的是,这会引入很多噪音,真是“事事两难全”!
注意,根据不同的Log级别,使用的开关是不一样的,-i对应的是INFO。关于这一点请参考文档。
在本篇的最后,我要说说我遇到的古怪问题。
事情是这样的,因为嫌Eclipse太慢,我现在倾向于用文本编辑器+构建工具来书写程序。目前使用Grails时,基本都是如此。有次,在(非Grails)工程开始创建基本环境时,为了试一试配置没问题,我直接就用UltraEdit创建的一个Groovy类,然后保存。其中,在保存时除了修改文件名,未修改任何其它设置。现在,运行“gradle build”。你猜怎么着,在命令行中输出:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
...\GroovyClass.groovy: 6: Invalid duplicate class definition of class GroovyClass :
...\GroovyClass.groovy contains at least two definitions of the class GroovyClass.
……
出于保密原因,以上对类的名称进行了修改。但大意是说在我的那个文件中定义了两个类。古怪的问题!明明只有一个类文件,一个类定义。
本着“有问题找Google”的精神,了解到了一些关于Groovy编译方面的知识:如果有语句出现全局的class{…}之外,那么groovyc就会把这个groovy文件视为一个脚本。此时,编译结果将是以该groovy文件名为类名的类(继承Script类)+其内部定义的类。这样,由于外部的文件名和内部中的类定义,自然就产生了两个类定义的问题。这说明,如果要在这样书写文件,那么在该groovy文件中定义的class的名字不能和groovy文件本身同名。
可是,我完全没有在class之外再写任何内容呀。在再三检查之后,我基本确认了没有这个可能。难道有什么不可见字符?开启UE的16进制模式之后,果然在文件第一行的开头发现了一个乱七八糟的东西。突然记起,UE保存功能是可以选择格式的。于是,新建一个文件,复制原来的内容,保存时特意看了看格式。果然,编码赫然显示为“UTF-8”,于是选择“ANSI/ASCII”保存。运行“gradle build”,成功。
为什么在用Grails的时候没有碰到呢?或许跟习惯有关。大部分Grails的文件都可以使用Grails的命令产生一个样板,对于实在没有的,基本也是顺手复制,然后再改。因此,这类问题基本可以杜绝。偏偏这次是从头到尾使用UE去完成的,结果就中彩了。
或许这是UE自己的Bug,我没有试过其它的编辑器,如EditPlus。但如果你碰到了类似的问题,期望本文对你有一点帮助。

我也遇到过,呵呵
我在使用Grails1.3.0升级旧程序时也遇到了这个问题,的确是UTF-8格式的问题,在UE中另存为时选择格式UTF-8无BOM的就可以了。
在Grails.1.2.2前都没有遇到这个问题,难道是Groovy1.7的Bug?
另外,能不能介绍下如何在Grails工程中使用Gradle? Grails1.3.0下有个gradlew.bat,但具体情况还没有研究
gradle是grails构建未来的方向
gradle是grails构建未来的方向,目前已经出现了相应的插件。你的提议非常好,我会花时间研究一下,然后写点东西向各位报告。
我遇到过
这个文件编码的问题我碰到过,utf8编码的文件似乎有一种特殊的格式,一种以BOM码开头的文件,就是你碰到的,它有固定的头三个字节表示这是一个utf8编码的文本文件,groovy的编译器似乎一直都不认这种格式。很多文本编辑器都可以选择用两种不一样的方式保存utf8编码的文件。