Gradle的初探

Gradle对Maven的改进

聊了Maven的思路和优势,那Maven的缺点呢?这个我们和Gradle一起聊聊。Gradle就是在Maven的基础上进行的改进。优势主要体现在以下方面:

1.配置语言

Maven使用的是XML,受限于XML的表达能力以及XML本身的冗余,会使pom.xml文件显得冗长而笨重。而Gradle是基于Groovy定义的一种DSL语言,简洁并且表达能力强大。在Maven中,任何扩展都需要通过Maven插件实现,但Gradle的配置文件本身就是一种语言,可以直接依赖任意Java库,可以直接在build.gradle文件中像Ant一样定义task,比Ant的表达能力更强(Ant本身也是XML定义的)。

Gradle的配置文件中可以直接获取到Project对象以及环境变量,可以通过程序对build过程进行更细致的自定义控制,这个功能对于复杂的项目来说非常有用。

2.项目自包含(Self Provisioning Build Environment)

用户下载了一个Maven定义的项目,如果没用过Maven,还需要下载Maven工具包,了解Maven。但Gradle可以给项目生成一个 gradlew脚本,用户直接运行gradlew脚本即可,该脚本会自动检测本地是否已经有Gradle,没有则从网络下载,对用户透明(当然,国内网络下最好还是自己先下载好)。

对仓库的配置,Maven提供了一个本地的settings.xml配置文件,用于定义私有仓库以及仓库密码这样敏感的不应该放源码仓库里的文件。但这样带来的不便就是这些信息项目中没有自包含,所以Gradle干掉了这种本地配置的机制,所有的定义都在项目里。私有仓库密码这样的可以放在项目下的 gradle.properties文件里不提交上去,通过其他方式分享给内部成员。这点可能各有优劣。

3.任务依赖以及执行机制

Maven的构建生命周期的每一步都是预定义好的(参看前文),插件任务只能在预留的生命周期中的某个阶段切入,虽然Maven的生命周期阶段考虑很充分,但有时候也不能满足需求。Maven会严格按照生命周期的阶段从开始线性执行任务,而Gradle则使用了Directed Acyclic Graph来检测任务的依赖关系,决定哪些任务可以并行执行,这样使任务的定义以及执行都更灵活。

4.依赖管理更为灵活

Maven对依赖管理比较严格,依赖必须是源码仓库的坐标。虽然也支持system scope的本地路径配置,但还是有许多不方便之处(system scope的依赖,打包的时候不包含进来)。如果世界上所有的库都通过Maven发布,当然没有问题,但现实往往不是这样的。这里要吐槽一下国内的各大厂发布的sdk之类的库,几乎都不提供仓库地址,就给个压缩包放一堆jar包进来,让用户自己搞定依赖管理问题。而Gradle在这方面比较灵活,比如支持:

  1. compile fileTree(dir: 'libs', include: '*.jar'

这样的配置规则。

另外由于Gradle本身是一种语言,可以用编程的方式来管理依赖。比如大多数子项目都依赖某个库,除了个别几个,就可以这样写:

  1. configure(subprojects.findAll {it.name != 'xxx1’ && it.name != ‘xxx2’}) {  
  2. dependencies {  
  3. compile("com.google.guava:guava:18.0”)  
  4.  

5.子项目以及动态依赖机制

动态依赖主要是用来解决几个互相依赖的库都在快速开发期间的依赖问题,不能每次地层库修改发布新版本,上层库都要修改依赖配置文件,所以需要动态设置依赖最新版本。

Maven的解决方案是SNAPSHOT机制,子项目之间也是通过这个机制来实现依赖的。遇到的问题我们前面也分析了。

Gradle的虽然也兼容Maven仓库的SNAPSHOT机制,但它自己的版本管理机制上,并没有引入SNAPSHOT机制。它的依赖支持4.x,2.+这样的配置规则,实现动态依赖(注:Maven也支持类似的规则,参看 Dependency Version Requirement Specification)。而子项目之间的依赖采用特殊的依赖配置,和第三方库的配置规则有区别。它直接使用:

  1. compile project(“:subpoject-name”); 

这样的配置,无需配置版本号,明确指定是子项目,避免Maven的子项目依赖带来的版本号问题。子项目的配置中也不需要显示配置父项目,只需要父项目单向中配置子项目列表即可。

同时Gradle的release机制也更为灵活,支持release到各种仓库(包括Maven仓库),但不控制release过程中的版本号生成,修改源码仓库等步骤,留给用户自己通过手动或者CI工具,或者脚本去解决。

关于Gradle相对Maven的改进这里主要列举这几点,其他的可以参看Gradle官方的比较表格:maven_vs_gradle,这里不再详述。

Go语言的多项目以及依赖管理问题

最后再谈谈Go语言的多项目以及依赖管理问题。Go官方对这两方面并未做约定或者提供工具,于是只能各自想办法解决。多项目问题一般就是回归到了 Makefile+脚本的解决方案,比如kubernetes。依赖管理,开源社区多用Godeps,kubernetes用的也是这个。Godeps通过源码仓库路径以及源码tag来确定库的坐标,只管理依赖,有点像ivy,不关心构建过程。Godepes会将依赖库的依赖也添加到当前项目的依赖配置中,不是动态的依赖传递机制。没有scope,不区分是否是单元测试的依赖。一个仓库只支持一个配置,没有子项目概念,项目大了管理就比较复杂。另外它对传递依赖以及版本冲突的问题当前还是没有解决太好(有一些相关Issue)。

一个语言的多项目以及依赖管理方案对这个语言的生态发展有很大的影响,Java发展到现在,Maven以及Gradle功不可没,所以感觉Go官方应该对这两方面有所作为。Go语言迟迟没出依赖管理工具,个人觉得有几方面考虑:

1.Go尚未确定动态库的机制。编译型语言依赖最好也是二进制的,而不是源码。一方面可以加快编译速度,另外一方面也可以实现源码保护,方便分发以及代理缓存,让语言的适用范围更广。许多商业上的库是不方便提供源码的。所以依赖管理工具的实现需要动态库的机制。而动态库尚未确定的原因我觉得是Go 语言不想过早的引入二进制动态库的格式兼容问题,初期全部用源码是最省事的。

2.先让社区试试水,看看效果和反馈。

任何一个语言,发展到一定阶段都避不开依赖管理问题。前一段时间看到一篇写Go语言的文章,嘲讽Java的Maven构建个项目恨不能把半个互联网下载下来,我当时脑海中就浮现出长者的那句经典语录“图样图森破”。Go当前没遇到这些问题的原因只是Go还比较年轻,库还不够丰富,以及Go的很多项目还不够复杂。而像kubernetes这样的项目,当前依赖已经有226个了,构建一下,也快要下载半个Github了。所以个人觉得Go社区当前还是非常需要一个类似于Gradle的工具,来解决依赖管理,构建,多项目管理等问题。

猜你喜欢

转载自see-you-again.iteye.com/blog/2347598