Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2015-04-29) 理解Gradle中的依赖

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

因为项目需要,这两天我对Gradle的依赖管理进行了一些了解,感觉收获很多,解决了我很多困惑已久的疑问。

Gradle的文档写得太好了,非常详细,尤其难得的是在过程中由浅入深,常常恰到好处的点到了读者的疑惑,例子也多,只要用心读一遍,各种疑惑都解决了。对比maven那简陋的文档,Gradle对用户太友好了。

花一天时间读完这几篇(有的有点长),基本上可以理解gradle的依赖管理了。这里记录一下曾经困扰我多时的问题。

为什么可以用compile, test来设置依赖

一个最简单的声明依赖的例子

apply plugin: 'java'

repositories {
    mavenCentral()
}

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

问题:为什么我们可以使用compiletestCompile,而不能写成别的?它们是两个方法吗?

答案是,这两个是gradle的java plugin中定义的configuration。除了它们以外,另外还有runtime, testRuntime, archives, default。(参考:https://gradle.org/docs/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management)

一个configuration可以看作是一堆依赖的集合,一个插件可以定义多个configuration,用于不同的功能,比如compile用于编译,runtime用于执行,testCompile用于编译测试。当插件定义他们以后,它们就像是一个个占位符,等着我们在自己的项目中,以上面例子的形式,给它们添加不同的依赖。

configuration之间是可以有继承关系的,比如runtime继承了compile,所以当我们给compile中添加了一些依赖后,runtime就可以自动获取它们,当然也可以添加额外的依赖。

其中的default略有特殊,会在后面提到。

所以,comiple, testCompile并不是已经定义好的方法,而是预定义的configuration名。每个插件所定义的configuration是不同的,所以当我们不知道该写什么时,应该去查看该插件的说明,而不是查看gradle自身。

而之所以用compilecompileTest这些名字,是因为它们是一些惯例(参考了maven时的dependency scope),每个插件还可以自己定义各种不同的名字。所以可以先猜测一下,但为了准确起见,还是要查看文档。

configuration: 'someConf'是什么意思

看到一个依赖的声明,里面包含了一个奇怪的configuration: 'someConf'

compile configuration: 'someConf', group: 'org.someOrg', name: 'someModule', version: '1.0'

不太理解它有什么用,直到理解了IvyMaven的区别,以及Gradle自己的处理以后,才知道是怎么回事。

要先从最老的Maven说起。Maven预定义了几个scope,比如compile/runtime/test等,用来管理不同的依赖集,供不同的操作使用。这些scope是定死的,不能修改。另外,maven一个模块只能发布一个成品文件(artifact,通常是jar),所以当我们使用一个由maven发布的依赖时,我们可以准确的拿到该成品文件,并且使用它的runtime scope中声明的依赖。

后来Ivy出现了,它认为Maven的做法太死板,进行了改进。Ivy提出了configuration的概念,跟Maven的dependency scope类似,但更加纯粹一些,仅用来表示一些依赖集合。Ivy没有预定义configuration,而是让用户自己定义并管理,同时还支持一个模块生成多个不同的成品文件,每个成品文件可以使用不同的configuration来获取依赖。这样做的好处是,可以通过细分configuration每个依赖集更加精减,这样用户在使用时可以指定某个特定的configuration来依赖真正需要的库。

所以在gradle中,当我们声明使用一个来自maven的依赖时,我们不需要指定任何configuration。只有当声明来自ivy的依赖时,我们才有必要在需要的时候,指定某个configuration,如果没有指定,则默认使用default confiuration.

那么问题来了,如果我对一个来自maven的依赖使用了configuration,会出现什么情况?会忽略?会报错?还是可以使用maven预设的dependency scope作为configuration?

经过尝试,发现这个规则其实是Gradle确定的。在https://github.com/gradle/gradle/blob/master/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java这个文件中,Gradle给来自maven的依赖预设了一堆configuration。如果我们指定它们,就不会报错,甚至还能取得不同的依赖,如果不在列表中,则会报错。需要注意的是,testVisibilityPRIVATE,我们可以使用它,但是并不会使用test scope中的依赖。

如何查看依赖,理解它的输出

参看博客:如何读懂gradle dependencies的输出

如何查看全部项目的依赖

如何在gradle项目中显示所有项目的依赖

如何查看某个版本的依赖是否有冲突

看这个文章就可以了,写得非常好:http://mrhaki.blogspot.jp/2014/08/gradle-goodness-getting-more-dependency.html

gradle是怎么解决依赖版本冲突的

当项目的依赖(不论是显式声明的,还是某个依赖声明的)有冲突时,gradle会以最大的版本号为准。

注意这里的做法跟maven不同,maven中是按显式声明的为准。比如我们在pom.xml中声明了使用abc 1.1,但某个依赖中使用了abc 1.2,maven会坚持使用我们声明的较老的版本abc 1.1

这两种做法,感觉gradle的更合理一些。因为maven的做法,会产生强迫某个依赖使用我们声明的较旧版本的某个库,导致一些奇怪的错误。

在下面的文章中,有一个很有趣的段子来调侃这件事:http://rafael.cordones.me/2013/05/06/transitive-dependency-mediation-in-maven-and-gradle/

comments powered by Disqus