51.1 介绍
每一个构建依赖管理是一个重要的特性,Gradle放置了一个强调提供一流的依赖管理同时便于理解和兼容各种各样的方法。如果你熟悉方法适用于Maven或者Ivy,你会惊喜的发现Gradle完全兼容两种方法,并且足够灵活去支持充分定制的方法。
这有一些亮高点关于Gradle支持依赖管理:
- 传递依赖管理 :Gradle给了你充分地控制你项目的依赖树。
- 支持不受管理的依赖:如果你的依赖是十分简便的文件在版本控制或者一个共享硬盘,Gradle提供了强大的功能去支持这些。
- 支持自定义的依赖定义:Gradle的模块定义给了你能力去描述依赖的层次结构在构建脚本中。
- 一个完全可定制的依赖解决方法:Gradle提供了你能力去定制解决规范让依赖替代更加容易。
- 完全兼容Maven和Ivy:如果你已经定义了依赖在一个Maven POM或者一个Ivy file中,Gradle提供了无缝集成一系列的受欢迎的构建工具。
整合现有依赖管理基础设施:Gradle是兼容Maven和IVy仓库的。如果你使用Archiva,Nexus或者Artifactory,Gradle是完全兼容所有的仓库格式。
成千上万的相互依存的开源软件都会有一系列版本的不兼容,依赖管理导致构建问题的习惯变得更加复杂。当一个构建依赖树变得更加笨拙,你的构建工具不应该阻止你去采用一个单一的,僵化的依赖管理方法。一个适当的构建系统可以设计的更为灵活,Gradle可以解决任何问题。
51.1.1 灵活的依赖管理迁移
依赖管理在从一个构建迁移系统迁移到另一个构建系统时可以变得十分具有挑战性。如果你是从一个工具像Ant或者Maven迁移至Gradle,你可能会面临一些困难的情况。例如,一个常见模式是一个Ant项目拥有一个version-less jar文件在文件系统中。其他的构建系统需要在迁移前有一个批量的替代方法。使用Gradle,你可以适应你的新构建在任何存在的依赖资源或者依赖数据。这个使得Gradle的增量迁移比替换要简单许多。在许多大型项目,构建迁移和任何发展进程的改变都是增量的,因为许多组织不能承受停止一切,迁移到构建工具的依赖管理的想法。
及时你的项目使用一个定制的依赖管理系统或者一些像Eclipse.classpath文件作为依赖管理的主数据,在Gradle中编写Gradle插件使用这些数据是非常简单的。对已迁移目标来说,这是一个常用的技术。(但是,一旦你开始迁移,从一个.claspath文件中移除出来,直接使用Gradle的依赖管理功能可能是一个好主意。)
51.1.2 依赖管理和Java
讽刺的是,在一个以丰富的开源组件库闻名的Java语言中没有库或者版本的概念。在Java中,没有标准的方法去告诉JVM,你在使用Hibernate3.0.5版本,同样也没有标准的方法说foo-1.0.jar依赖于bar-2.0.jar。这导致了外部解决方案通常依赖于构建工具。目前最受欢迎的是Maven和Ivy。Maven提供了一个完整的构建系统,但是IVY仅仅关注依赖管理。
这两种工具都依赖XML文件解析,包含了特定的jar依赖项的信息。这两者都使用仓库放置Jar和pom文件,同样都提供了解决Jar版本冲突的方法。
实际也都使用存储库的jar放置连同他们的描述符文件,和两者都提供解决冲突的jar版本以一种形式或另一个。都形成了解决依赖冲突的标准,Gradle最初使用Ivy来解决其依赖管理问题。Gradle已经取代了直接依赖IVY,而改为使用一个提供了一系列依赖解决方法包括pom和Ivy描述文件的Gradle依赖解决引擎。
51.2 依赖管理最佳实践
当Gradle有强烈的想法关于依赖管理,工具给了你一个选择在两个选项中:按照推荐的最佳实践或支持任何类型的模式你能想到的。本节概述了Gradle项目推荐的最佳实践管理依赖性。
不管是什么语言,恰当的依赖管理对于每个项目都是很重要的。从一个复杂的企业应用程序用Java编写依赖于数以百计的开源库,最简单的Clojure应用依赖于少数的库,依赖管理的方法十分广泛并且可以依赖于目标技术,应用程序部署的方法,项目的性质。项目打包为可重用的库与企业级应用相比可能拥有不同的需求,并且集成到一个更大上万软件系统和基础设施。尽管要求十分宽泛,但还是Gradle项目建议所有的项目遵循下面一些规则:
51.2.1.在文件名中加入版本号(jar版本)
一个库的版本号必须是文件名的一部分。jar的版本号经常在Manifest文件中,当你查看项目的时候,并不是可见的。如果有人问你查看20个Jar文件的集合,你更愿意哪样?一个集合名为commons-beanutils-1.3.jar啊哈四海一个集合名为spring.jar?如果一个人依赖的文件名有版本号你会很快速的判断你的依赖的版本。
如果版本号不太清楚,你可以介绍非常难找的微妙的错误。例如这里有个项目可能使用了Hibernate2.5。想想一个开发者决定去安装3.0.5版本的Hibernate在她的电脑上去检查一个重要的安全缺陷但是忘记去知会团队中的其他人。她可能成功查找到这个安全bug,但是她同样可能在一个代码库中介绍了细微的bug,使用了一个Hibernate中的now—deprecated特性。几周以后发生了一个异常,集成机器不能复制到其他机器上。多个开发者花费了好几天在这个问题上,仅仅最后意识到这个错误将轻易被发现如果他们知道Hibernate已经从2.5升级到了3.0.5。
版本号在Jar的名称中提高了项目的表达能力,并且使他们更加容易维护。这个经验同时减少了潜在的错误。
51.2.2.管理传递依赖
传递依赖管理是一个技巧,使您的项目依赖于库,反过来,依赖其他的库。这个传递依赖的递归模式形成了一个递归树包括你的项目的一级依赖性,二级依赖性,等等。如果你不模型化你的依赖为层次树的一级和二季依赖,很容易很快是取控制一个组装的非结构化依赖性。考虑到Gradle项目自身,Gradle只有几个直接的,一级依赖,当Gradle编译时需要超过百数的依赖在类路径中。在一个规模更大,企业级项目使用Spring,Hibernate,和其他的库,在成百上千的内部项目,可能导致很大的依赖关系树。
当这些大型的依赖树需要改变时,你将会经常解决依赖版本冲突问题。说一个开源库需要一个日志版本,其他开源库需要另外的版本。Gradle和其他构建工具全都有能力去解决冲突问题,但是Gradle不同的是控制它给你覆盖传递依赖和解决冲突的办法。
当你尝试手动管理这个问题的时候,你会很快发现这个方法没有规模。如果你想要拍出一级依赖你真的肯呢过不能确认其他的Jars你需要移除哪一个。一级依赖的依赖本身也可能是一个一级依赖,或者可能是另一个一级依赖的传递依赖。如果你尝试去管理自身的传递依赖,最后的结局是你的构建将会变得脆弱:没人敢去改变你的依赖因为打破构建的风险实在太高。项目类路径变得一团糟,而且,如果一个类路径出现了错误,那么麻烦就大了。
NOTE:在一个项目中,我们发现一个神秘的LDAP关联的jar在类路径中。没有代码关联这个jar,所以这里没有联通项目。没有人可以查找出这个jar是干什么的,直到把这个类从项目中移除后,应用在试图在LDAP进行验证
时承受了巨大的性能问题。这个神秘的jar文件是一个必要的四级传递依赖,这是容易被忽视的,因为没有人愿意使用传递依赖管理。
Gradle提供了你不同的方法去表现一级传递依赖。使用Gradle你可以混合并且匹配这个方法:例如,你可以储存你的Jars在一个SCM中不使用XML表示文件并且依然使用传递依赖管理。
51.2.3 解决版本冲突
相同jar的版本冲突应该被发现并且被解决或者导致一个异常。如果你不使用传递依赖管理,版本冲突是不被发现并且常见的类路径意外顺序会决定哪一个版本的依赖会赢。在一个大的项目,许多开发者改变了依赖,成功构建将会十分少并且顺序之间的依赖会直接影响一个构建是成功还是失败(或者一个bug在产品中是出现还是不出现)。
如果你还没有处理过在一个类路径中jar的版本冲突带来的问题,那么这里有个小故事等着你。在一个拥有30个子项目的大型项目中,添加一个依赖给一个子模块改变类路径的顺序,交换Spring 2.5到一个较老的版本2.4。当构建持续工作时,开发者会开始注意到各种奇怪的莫名其妙的问题在产品中。更糟糕的是,Spring的意外下调引发了一些安全漏洞问题在系统中,现在需要在整个组织中进行一个全面的安全审查。
总而言之,版本冲突是不好的,你应该管理你的传递依赖避免发生。你同样可能想要知道冲突的版本在哪里使用的并且固定在一个特别的版本依赖在你的组织中。使用一个号的冲突报告工具,像Gradle,这些信息可以被用来和整个组织进行沟通交流并且标准化一个单独的版本。*如果你觉得你不会遇到版本冲突的问题,再好好想想。*这是非常普遍的对于不同的一级依赖依赖于一系列的不同的重叠的其他依赖的版本,并且JVM并没有提供一个简单的方法去拥有相同JAR的不同版本在类路径中。(查看Section51.1.1,“依赖管理和JAVA”)
Gradle提供了以下的解决冲突策略:
- 最新:最新版本的依赖在使用。这个是Gradle的默认策略,并且这个经常是一个恰当的方法只要版本向后兼容。
- 失败:一个版本冲突导致了一次构建失败。这个策略要求所有的版本冲突要被明确地解决在构建脚本中。查看ResolutionStrategy查找关于如何明确选择版本的详细信息。
上面介绍的策略基本上解决了大部分的冲突,Gradle提供了更细粒化的机制去解决版本冲突问题:
- 被迫配置一个一级依赖。这个方法是很有效的如果这个依赖在冲突中已经是一个一级依赖。在DependencyHandler中查看例子。
- 强迫配置任何依赖(传递或不传递)。这个方法是很有效的如果这个依赖在冲突中是一个传递依赖。它同样可以被用来逼迫一级依赖的版本。在ResolutionStrategy中查看例子。
- 依赖解决规则是一个孵化中的特性在Gradle1.4中引入的,它给了你更细粒化的控制版本选择对于一个特定的依赖。
为了解决由于版本冲突引起的问题,依赖图的报告也是十分有帮助的。这类报告的另外一个特性是依赖管理。
51.2.4 使用动态版本和修改模块
这有很多种情况当你想要使用一个特定依赖的最新版本,或者一系列版本中最新的。这个可以成为一个需求在发展期间,或者你可能发展一个库,被设计用来和一系列依赖版本一起工作。你可以简单的依赖这些一致性概念依赖根据使用一个动态笨笨。一个动态版本可以成为即是一个版本范围(例如2.+)或者可以是一个占位符表示最新可用的版本。(例如,latest.integration)。
如其不然,有时候你请求的米快随着时间的推移在改变,即使是对相同的版本。一个例子关于这个类型的改变模块是一个Maven SNAPSHOT 模块,总是指向最新的块工件。换句话说,一个标准的Mavensnapshot是一个从不固步自封的模块,是一个改变的模块。
主要的区别在一个动态版本和一个改变模块是当你解决一个动态版本,你会得到真正的,静态版本的模块名称。当你解决一个概念模块,块工件被命名为你所请求的,但是潜在的工件可能随时间而改变。
默认地,Gradle在24H内缓存动态版本和改变模块。你可以重写默认缓存模块使用命令行选项。你可以使用解决策略改变缓存到期时间在你的构建中。(查看Section51.9.3 "Fine-tuned control over dependency caching").
51.3 依赖配置
在Gradle中,依赖被分组于配置中。配置有一个名称,一个号码关于其他属性,并且它们可以相互扩展。许多Gradle插件添加了预定义的配置在你的项目中。Java插件,例如,添加了一些配置去表现大量必要的类路径。查看Section23.5“Dependency”更详细的内容。当然你可以添加自定义配置。有许多用例为自定义配置。这是非常便利的例子添加依赖而不需要去构建或者测试你的软件。(例如,额外的JDBC驱动已经装载在你的分销中)。
一个项目的配置是可以被管理的通过一个configurations对象。这个你传递给配置对象的闭包是应用于它的API。去学习更多关于这个API的知识,你可以看看ConfigurationContainer。
定义一个configuration:
例51.1 定义一个configuration
build.gradle
configurations{ compile }
进入一个configuration:
例51.2 进入一个configuration
build.gradle
println configurations.compile.name println configurations['compile'].name
配置一个configuration:
例51.3 配置一个configuration
build.gradle
configurations { compile { description = 'compile classpath' transitive = true } runtime{ extendsFrom compile } } configurations.compile { description = 'compile classpath' }
51.4 如何描述你的依赖
有好几种不同的方式可以用来描述你的依赖:
Table51.1 依赖种类
External module dependency:
在某些仓库中,在一个额外的模块中的一个依赖。
Project dependency
在同样的构建中,在其他项目中的一个依赖。
File dependency
在本地文件系统中,在一组文件中的一个依赖
Client module dependency
在一个额外模块中的一个依赖,工件位于某些仓库中但是模块元数据是由本地构建制定的。你使用这些类型的依赖当你想要去重写模块的元数据。
Gradle API dependency
在当前Gradle版本中API中的一个依赖。你可以使用这种依赖当你在发展自定义Gradle插件和任务类型。
Local Groovy dependency
当前Gradle版本使用的Groovy版本中的一个依赖。你使用这种依赖当你在发展自定义Gradle插件和任务类型。
51.4.1 外部模块依赖
外部模块依赖是最常见的依赖。他们连接一个模块在一个外部的仓库。
例51.4.模块依赖
build.gradle
dependencies { runtime group: 'org.springframework', name: 'spring-core', version: '2.5' runtime 'org.springframework:spring-core:2.5', 'org.springframework:spring-aop:2.5' runtime( [group: 'org.springframework', name: 'spring-core', version: '2.5'], [group: 'org.springframework', name: 'spring-aop', version: '2.5'] ) runtime('org.hibernate:hibernate:3.0.5') { transitive = true } runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') { transitive = true } }
查看API中的DependencyHandler类查找更多地例子和一个完整的参考。
Gradle提供了不同的符号给模块依赖。有一个字符串符号,一个地图符号。一个模块依赖有一个API允许更长远的配置。查看ExternalModuleDependency去学习关于API更多。这个API提供了属性和配置方法。通过字符串地图,你可以定义一个属性的子集。使用地图符号你可以定义任何属性。为了获得完整的API,不管是根据地图符号还是字符串符号,你可以指定一个单一依赖给一个配置和一个闭包。
如果你定义了一个模块依赖,Gradle寻找一个模块描述文件(pom.xml或者ivy.xml)在仓库中。如果这样的一个模块描述文件存在的话,
这是解析并且这个模块的工件(例如hibernate-3.0.5.jar)同样它的依赖(例如cglib)是下载了的。如果没有这样的模块描述文件存在,Gradle查看一个文件叫做hibernate-3.0.5.jar区检索。在Maven中,一个模块可以有且仅有一个工件。在Gradle和ivy,一个模块可以拥有多重工件。每一个工件可以拥有不同数量的依赖。
51.4.1.1依赖拥有多重工件的模块
如前所述,一个Maven模块只有一个工件。因此,当你的项目依赖一个Maven模块,很明显它的工件是什么。使用Gradle或者Ivy,这个情况就不一样了。Ivy的依赖描述(ivy.xml)可以描述多重工件。对于更多地信息查看Ivy的ivy.xml文件。在Gradle中,当你描述一个依赖在一个Ivy模块时,你可以声明一个依赖的默认配置模块。所以实际的工件集(代表性地jars)你依赖的是和默认配置模块相关的工件设置。下面是一些当这个问题产生时的情况:
- 一个模块的默认配置包含不希望存在的工件。比起依赖整个的配置,依赖只是存在于希望声明的工件。
- 所需的工件不属于默认配置。配置是显式命名为依赖声明的一部分。
还有其他的情况,需要调整依赖声明。请查看DependencyHandler类在API文件中的例子和完整的参考声明依赖。
51.4.1.2 工件唯一符号
正如上面所说的,如果没有模块声明文件可以被找到,Gradle根据模块名称默认下载Jar。但是在某些时候,即使仓库包含了模块声明,你只是想要下载工件jar文件,不包括依赖。并且有时候你想要无下载一个zip文件从一个仓库,没有模块声明。Gradle提供了一个artifact only符号给这些使用用例,你想要下载的是根据一个简单的前缀符号“@”:
**例51.5Artifact only notation
**build.gradle
>
dependencies{
runtime “org.groovy:groovy:2.2.0@jar”
runtime group:’org.groovy’,name:’groovy’,version:’2.2.0’,ext:’jar’
}
一个工件唯一符号创建一个模块依赖只下载制定文件拓展名的工件。
51.4.1.3 分类器
Maven依赖管理有分类器的概念。G绕到了支持这个。从Maven仓库检索分类依赖,你可以写:
**例51.6. 有分类器的依赖
**build.gradle
compile “org.gradle.test.classfiers:service:1.0:jdk@jar”
otherConfgroup:’org.gradle.test.classfiers’,name:’service’,version:’1.0’,classfier:’jdk14’
在上面的第一行可以看到,分类器可以与工件一起使用符号。
很容易迭代依赖工件的配置:
例51.7迭代一个配置
build.gradle
task listJars << {
configurations.compile.each{File file -> println file.name}
}
gradle -q listJar输出:
gradle -q listJars
hibernate-core-3.6.7.Final.jar
antlr-2.7.6.jar
commons-collections-3.1.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-3.2.0.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
jta-1.1.jar
slf4j-api-1.6.1.jar
51.4.2 客户端模块依赖关系
客户端模块依赖允许你直接描述传递依赖在构建脚本中。在外部仓库中他们是一个模块声明的替代。
例51.8.客户端模块依赖-传递依赖
build.gradle
dependencies {
runtime module(“org.codehaus.groovy:groovy:2.3.6”) {
dependency(“commons-cli:commons-cli:1.0”) {
transitive = false
}
module(group: ‘org.apache.ant’, name: ‘ant’, version: ‘1.9.3’) {
dependencies “org.apache.ant:ant-launcher:1.9.3@jar”,
“org.apache.ant:ant-junit:1.9.3”
}
}
}
这里定义了一个在Groovy上的依赖。Groovy本身有依赖。但是Gradle还没有找到一个xml文件声明去计算出来但是得到了信息来源构建文件。一个客户端模型的依赖可以成为正常模块依赖或者工件依赖或者其他客户端依赖。同样可以在API中查看ClientModule类。
在当前版本客户端模块有一个限制。比方说你的项目是一个类,你想把这个类上传到你公司的maven或者ivy仓库中。Gradle发布了你的项目的jars在公司的仓库中,并且包含了XML声明文件。如果你使用客户端模块,在XML声明文件中的依赖描述是不正确的。我们会改进这些在未来发布的Gradle版本中。
51.4.3.项目依赖
Gradle区分外部依赖和依赖相同的多项目构建的一部分的项目。后者可以声明项目依赖项。
**例51.9项目依赖
**build.gradle
dependencies{
compile project(‘:shared’)
}
查看更多地功能请到API文档ProjectDependency.
多重项目构建讨论在Chapter57,多重项目构建
51.4.4 文件依赖
文件依赖允许你直接添加一系列文件去配置,不用将他们加进仓库中。这个很有用如果你不能,或者不想要,放置文件在仓库中。或者如果你不想要使用任何仓库去放置你的依赖,
为了添加文件作为一个配置的依赖,你可以简单的传递一个file collection作为一个依赖:
**例51.10.文件依赖
**build.gradle
dependencies{
runtime files(‘libs/a.jar’,’libs/b.jar’)
runtime fileTree(dir:’libs’,include:’*.jar’)
}
文件依赖没有被包含在发布色依赖声明在你的项目中。但是,文件依赖被包含在传递依赖在相同的构建中。这意味着它们不能够使用当前以外的构建,但他们可以使用相同的构建。
你可以您可以声明任务产生的文件文件的依赖。你可能会这样做,例如,生成的文件构建。
例51.11 生产文件依赖
builg=d.gradle
dependencies {
compile files(“$buildDir/classes”) {
builtBy ‘compile’
}
}
task compile << {
println 'compiling classes'
}
task list(dependsOn: configurations.compile) << {
println "classpath = ${configurations.compile.collect {File file -> file.name}}"
}
gradle -a list输出:
gradle q list
compiling classes
classpath = [classes]
例51.12 GradleAPI依赖
你可以声明一个依赖在现版本Gradle的API中的DependencyHandler.gradleApi()方法。当你在发展定制Gradle任务或者插件时是很有效的。
**例51.12 Gradle API 依赖
**build.gradle
dependencies{
compile gradleApi()
}
51.4.6 本地Groovy依赖
你可以声明一个依赖Groovy与Gradle利用分布式DependencyHandler.localGroovy()方法。当你发展一个自定义Gradle任务或者插件在Groovy中是非常有效的。
例51.13 Gradle的Groovy依赖
build.gradle
dependencies {
compile localGroovy()
}