Gradle 1.12用户指南翻译——第五十章. 依赖管理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见:http://blog.csdn.net/column/details/gradle-translation.html翻译项目请关注Github上的地址:https://github.com/msdx/gradledoc本文翻译所在分支:https://github.com/msdx/gradledoc/tree/1.12。
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见:
http://blog.csdn.net/column/details/gradle-translation.html
翻译项目请关注Github上的地址:
https://github.com/msdx/gradledoc
本文翻译所在分支:
https://github.com/msdx/gradledoc/tree/1.12。
直接浏览双语版的文档请访问:
http://gradledoc.qiniudn.com/1.12/userguide/userguide.html。
另外,Android 手机用户可通过我写的一个程序浏览文档,带缓存功能的,目前0.2.1版本兼容 Android 2.2以上系统,项目地址如下:
https://github.com/msdx/gradle-doc-apk
翻译不易,转载请注明本文在CSDN博客上的出处:
http://blog.csdn.net/maosidiaoxian/article/details/53616326

关于我对Gradle的翻译,以Github上的项目及http://gradledoc.qiniudn.com 上的文档为准。如发现翻译有误的地方,将首先在以上两个地方更新。因时间精力问题,博客中发表的译文基本不会同步修改。


第五十章. 依赖管理

50.1. 介绍

依赖管理是每个构建的关键特性,并且 Gradle 强调提供最好的依赖管理,容易理解以及使用各种各样的方法来兼容。如果您熟悉使用 Maven 或 Ivy 的方法,那么你会很高兴地知道,Gradle 完全兼容这两种方法,除此之外,它还有足够的灵活性,以支持完全自定义的方法。

这里是 Gradle 支持的依赖管理的主要亮点:

  • 依赖管理传递: Gradle 赋予你对你的项目依赖项树的完全控制。

  • 对非管理依赖的支持:如果你的依赖只是版本控制下或共享的驱动器中的文件,Gradle 也提供了强大的功能以支持这种情况。

  • 支持依赖定义的自定义:Gradle 的模块依赖给你在构建脚本中描述依赖项层次结构的能力。

  • 一个完全可自定义的依赖解析的方法:Gradle 为你提供了自定义解析规则的能力,让你可以轻松替换依赖。

  • 完全兼容 Maven 和 Ivy :如果你已经在 Maven POM 文件或 Ivy 文件中定义了依赖,Gradle 提供了一系列受欢迎的构建工具可以进行无缝集成。

  • 与现有依赖管理基础结构的集成:Gradle 兼容 Maven 和 Ivy 仓库。如果你使用 Archiva, Nexus,或者是 Artifactory, Gradle 100% 兼容所有仓库的格式。

成千上万的开源组件互相依赖,每个组件都有一系列的版本及不兼容性,随着构建变得越来越复杂,依赖管理常常出现各种问题。当一个构建的依赖树变得笨拙时,你的构建工具不应该强迫你对依赖管理采取单一、不灵活的做法。一个正确的构建系统必须被设计得很灵活,而 Gradle 可以应付任何情况。

50.1.1. 为了迁移的灵活的依赖管理

依赖管理在从一个构建系统迁移到另一个的过程中尤其具有挑战性。如果你从一个像 Ant 或 Maven 这样的工具迁移到 Gradle,你可能会面临一些困难的情况。例如,一个常见的模式是,有一个 Ant 项目,而它保存在文件系统中的一些 jar 文件缺少版本号。其他的构建系统在迁移之前需要对这种方法进行大量的替换。使用 Gradle,你可以让你的新构建适配任何现有的依赖来源或依赖元数据。这使得增量迁移到 Gradle 比其他的要容易得多。在大多数的大项目中,构建迁移和对发展过程的任何更改都是增量的,因为大多数组织不能负担得起停止一切事情并迁移到依赖管理的构建工具的想法。

即使你的项目使用一个自定义的依赖管理系统,或者是一些像 Eclipse 作为依赖管理的主数据的 .classpath 文件,那么可以很容易地写一个 Gradle 插件在 Gradle 中使用此数据。出于迁移的目的,这是在 Gradle 中的一种常用技术。(但是,一旦你已经迁移,远离 .classpath 文件,直接使用 Gradle 的依赖管理功能会是一个好主意。)

50.1.2. 依赖管理和 Java

令人啼笑皆非的是,以其丰富的开源组件库著称的语言,Java 竟然没有库或者版本的概念。在 Java 中,没有标准的方法来告诉 JVM 你使用的是 3.0.5 版本的 Hibernate,也没有标准的方法来表明 foo-1.0.jar 依赖于 bar-2.0.jar。这导致了外部的解决方案通常都会基于构建工具。目前最受欢迎的解决方案是 Maven 和 Ivy。Maven 提供了一个完整的构建系统,而 Ivy 则完全着眼于依赖管理。

这两种工具都依赖于描述符 XML 文件,包含有关具体某个 jar 的依赖的信息。这两者都使用了存储库,在存储库中实际的 jar 文件和它们的描述文件都放在一起,而且这两者通过窗口或其他的方式都提供了 jar 版本冲突的解决方案。这两者都已成为解决依赖冲突的标准,而 Gradle 最初使用的是 Ivy 引擎的依赖管理。Gradle 已经取代了 Ivy 上的直接依赖,而采用了原生的 Gradle 依赖解决引擎,它支持许多的依赖解决方案的方法,包括 POM 文件和 Ivy 描述文件。

50.2. 依赖管理的最佳实践

由于 Gradle 对依赖管理有强烈的主张,该工具使你可以选择两个选项之间:遵循推荐的最佳实践或支持任何你能想到的类型的模式。本节概述 Gradle 项目推荐的管理依赖的最佳实践。

不管用什么语言,对每个项目而言,适当的依赖管理是很重要的。从一个由 Java 编写的依赖了数以百计开源库的复杂企业应用,再到依赖少量库的最简单的 Clojure 应用,依赖管理的办法大不相同,并且可以依赖于目标的技术、应用程序部署的方法和项目的性质。项目捆绑作为可重用的库,比起企业应用集成到更大的软件和基础设施的系统中,可能有不同的要求。尽管这一要求变化很大,Gradle 项目建议所有项目都遵循这套核心规则:

50.2.1. 在文件名中包含版本号(版本化 jar 文件)

在文件名中库的版本必须是容易辨认的。Jar 的版本通常是在清单文件中,当你要检查一个项目时它并不显而易见。如果有人让你看 20 个 jar 文件,你更喜欢哪一种?一个文件命名为 beanutils-beanutils-1.3.jar 的集合,还是文件命名为 spring.jar 的集合?如果依赖的文件名称带有版本号,那么将更容易快速确定依赖的版本。

如果版本不清楚,你可能引入一些很难找到的微妙的错误。例如可能有一个项目使用 Hibernate 2.5。想一下一个开发人员决定在她的机器安装 3.0.5 的版本,以修复一个关键的安全 bug,但她忘记通知其他团队这个变化。她可能成功地解决了这个安全 bug,但她也可能有引入一些 bug 到代码库中,因为项目用到了 Hibernate 现在弃用的功能。一周后在集成的机器上可能会有一个异常,而这个异常无法在任何人的机器上复现。然后多个开发人员花了数天的时间在这个问题上,终于意识到如果他们知道 Hibernate 已经从 2.5 升级到 3.0.5,这个错误会很容易发现。

在 jar 文件中包含版本号提高了在你的项目中的表现,并且让它们更易于维护。这种做法也减少了潜在的错误。

50.2.2. 管理传递依赖

传递依赖管理是一种技术,让你的项目能够依赖那些反过来依赖其他库的库。这种递归模式传递依赖的结果是,在一个依赖树中,会包含你的项目的第一级依赖、第二级依赖,等等。如果你不把你的依赖作为层次结构树的第一级和第二级依赖的模型,那么在对未组织的依赖进行混乱的组装之后,就会很容易地失去控制。考虑 Gradle 项目本身,虽然 Gradle 仅有几个直接的第一级依赖,当 编译 Gradle 时在它的类路径上会需要超过一百个的依赖。在更大的规模上,使用 Spring,Hibernate和其他的库,旁边数百或数千个内部项目的企业应用,也有非常大的依赖树。

当这些大的依赖树需要更改时,你经常得解决一些依赖的版本冲突。比如说一个开源库需要一个日志库的一个版本,而另一个库需要日志库的另一个版本。Gradle 和其他的构建工具都能够处理这种关系树和解决冲突问题,但不同的是,Gradle 让你可以控制传递依赖和冲突的解决。

虽然你可以尝试手动管理这个问题,你很快就会发现这种方法不能扩展。如果你想要摆脱第一级的依赖,你不能真正确定还有哪些 jar 文件你是需要删除的。第一级依赖的依赖项也可能是第一级依赖本身,或者也可能是另一个第一级依赖的传递依赖。如果你尝试自己管理传递依赖,最终的结果是你的构建会变得很脆弱:没有人敢去改变你的依赖,因为破坏构建的风险太高。项目的类路径会变得一片狼藉,并且,如果类路径出现问题时,那简直就是人间地狱。

注: 在一个项目中,我们发现在类路径中有一个神秘、LDAP 相关的 jar 包。没有代码引用这个 jar 包,这个 jar 包也与该项目没有任何连接。没人能弄清楚这个 jar 包是干什么用的,直到它被从构建中移除,然后应用在试图向 LDAP 进行身份验证的时候,遇到了很大的性能问题。这个神秘的 jar 包是一个必需传递的,第四级的依赖,很容易被忽略,因为没有人会费心去使用托管的传递依赖。

Gradle 向你提供了不同的方式来表达第一级的和传递的依赖。通过 Gradle 你可以混合使用和适配一些方法;例如,你可以在 SCM 中存储你的 jar 包,而不需要 XML 描述符文件,并且仍然使用传递依赖管理。

50.2.3. 解决版本冲突的问题

相同的 jar 包的冲突版本应该被检测到,并且要么解决,要么抛出异常。如果你不使用传递依赖管理,版本冲突没被发现,那么在类路径中无法预测的顺序,将导致不知道会使用哪一个版本的依赖。对于许多开发人员都会更改依赖的大型项目,成功的构建将会少之又少,因为依赖的顺序可能会直接影响到构建是否成功(或者在产品中是否会出现一个 bug)。

如果你还没有处理过在类路径中 jar 包版本冲突的麻烦,这里有一个小趣闻等着你。在一个有30个子模块的大型项目中,向子项目添加的一个依赖改变了类路径的顺序, Spring 2.5 与老的 2.4 版本的顺序被交换。虽然可以继续构建,开发者已经开始注意到在生产中出现了各种令人惊讶(和惊人可怕)的 bug。然而,更糟糕的是,无意降低版本的 Spring 向系统引入了几个安全漏洞,现在需要在整个组织中进行全面的安全审核。

总之,版本冲突是很不好的,你应该管理你的传递依赖,以避免它们。你也可能想要了解版本冲突用到的地方,并且在你的整个组织中统一一个指定版本的依赖。通过一个好的冲突报告工具,像 Gradle,这些信息可以用于与整个组织沟通,并在一个单一的版本上实现标准化。如果你觉得你不会发生版本冲突,再想想。 不同的第一级依赖,依赖于一系列不同的重叠版本的其他依赖,这种情况是很常见的,而 JVM 还不能提供简单的方法,使得能在类路径中让相同的 jar 包可以有不同的版本(请参阅第 50.1.2 节,“依赖管理和 Java”)。

Gradle 提供了以下的冲突解决策略:

  • Newest:使用最新版本的依赖这是 Gradle 的默认策略,只要版本都能向后兼容,往往是合适的选择。
  • Fail:一个版本冲突将导致构建失败。这种策略强制在构建脚本中明确地解决所有的版本冲突。有关如何显式选择一个特定版本的详细信息,请参阅ResolutionStrategy 

虽然上面介绍的策略通常足够解决大部分的冲突,但是 Gradle 也提供更细粒度的机制,以解决版本冲突:

  • 强制配置第一级依赖。如果冲突中的依赖已经是第一级的依赖,那么这种方法会很有用。请参阅DependencyHandler中的示例。
  • 强制配置任何依赖项(不管是否可传递)。如果冲突中的依赖是传递依赖,那么这种方法会很有用。它也可以用于强制第一级依赖的版本。请参阅ResolutionStrategy中的示例。
  • 依赖解析规则是一个在 Gradle 1.4 引进的孵化中的功能,让你可以对特定的依赖细粒度地控制所选定的版本。

为了解决版本冲突问题,报告依赖关系图也是很有帮助的。这些报告是依赖管理的另一个功能。

50.2.4. 使用动态版本和变化的模块

有许多情况,是你想要使用一个特定依赖的最新版本,或者是某个版本范围内的最新版。这可以是在开发中需要,或者你可能正在开发一个库,它被设计为使用一个范围内的依赖版本。你可以通过使用动态版本很容易地依赖这些不断变化的依赖。一个动态的版本可以是一个版本范围(例如2.+),也可以是表示可用的最新版本的占位符(例如latest.integration)。

另外,你请求的模块随着时间推移,即使是同一版本,有时也可能改变了。这种变化模块的类型的一个例子是 MavenSNAPSHOT模块,它总是指向最新发布的构件。换句话说,一个标准的 Maven snapshot 是一个这样的模块,它永远不会不变,可以说,它是“不断变化的模块”。

动态版本变化模块的主要区别是,当你解析一个动态版本时,你会得到真正的、 静态的版本作为模块名称。当你解析一个变化模块时,这个 artifacts 使用你请求的版本进行命名,但下层的 artifacts 可能随时会有变化。

默认情况下,Gradle 对动态版本和变化模块的缓存时间是24小时。你可以使用命令行选项重写默认的缓存模式。你可以通过resolution strategy修改你的构建的缓存到期时间(见第 50.9.3 节,“调整控制依赖缓存”)。

50.3. 依赖配置

在 Gradle 中,依赖被分组到配置中。配置有一个名字和许多属性,并且它们能够互相继承。许多 Gradle 插件会向你的 project 添加预定义的配置。例如,Java 插件会添加一些配置来表示它所需要的不同的类路径。详细信息请参阅第 23.5 节,“依赖管理” 。当然,你可以添加自定义配置到这上面。关于自定义配置,有许多的用例。这是非常方便的,例如添加依赖时不需要构建或测试你的软件(比如,将会与发布的软件一起的额外的 JDBC 驱动程序)。

一个项目的配置被一个 configurations 对象所管理。你传给这个 configurations 对象的闭包会通过它对应的 API 被应用。要了解更多关于此 API 的内容,可以看看ConfigurationContainer

如果要定义配置:

示例 50.1. 配置的定义

build.gradle

configurations {
    compile
}

如果要访问配置:

示例 50.2. 访问配置

build.gradle

println configurations.compile.name
println configurations['compile'].name

配置一个配置:

示例 50.3. 配置一个配置

build.gradle

configurations {
    compile {
        description = 'compile classpath'
        transitive = true
    }
    runtime {
        extendsFrom compile
    }
}
configurations.compile {
    description = 'compile classpath'
}

50.4. 如何声明依赖

你可以声明几种不同类型的依赖:

表 50.1. 依赖类型

类型 描述
外部模块依赖 对一些仓库中的外部模块的依赖
项目依赖 在同一个构建中对另一个项目的依赖
文件依赖 对本地文件系统中的一些文件的依赖
客户端模块依赖 对外部模块的依赖,该块部模块的 artifacts 存储于一些仓库中,但是模块的元数据由本地构建指定。当你想要重写模块的元数据时,你可以使用这种类型的依赖。
Gradle API 依赖 对当前的 Gradle 版本的 API 的依赖当你正在开发自定义的 Gradle 插件和任务类型时,你可以使用这种类型的依赖。
本地的 Groovy 依赖 对当前的 Gradle 所使用的 Groovy 版本的依赖。当你正在开发自定义的 Gradle 插件和任务类型时,你可以使用这种类型的依赖。

50.4.1. 外部模块依赖

外部模块依赖是最常见的依赖。它们引用外部仓库中的模块。

示例 50.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
    }
}

有关更多的例子和完整的参考,请参阅DependencyHandler 

Gradle 为模块依赖提供了不同的标记法。有 string 标记法和 map 标记法。模块依赖有一个 API,用于进行进一步的配置。要了解所有该 API 的内容,可以参阅ExternalModuleDependency 。该 API 提供了一些属性和配置方法。通过 string 标记法,你可以定义一个属性的子集。而通过使用 map 标记法,你可以定义所有的属性。要访问完整的 API,使用 map 或 string 标记法,你可以把单个的依赖与一个闭包一起指定给一个配置。

如果你定义了一个模块依赖,Gradle 会在仓库中查找相应的模块描述符文件(pom.xmlivy.xml)。如果存在此类模块描述符文件,它会进行分析,并下载此模块的 artifacts (例如hibernate-3.0.5.jar)以及其依赖项(例如 cglib)。如果不存在这样的模块描述符文件,Gradle 会查找一个hibernate-3.0.5.jar 文件。在 Maven 中,一个模块仅能有一个 artifact。在 Gradle 和 Ivy 中,一个模块可以具有多个 artifacts。每个 artifact 可以有一组不同的依赖。

50.4.1.1. 依赖有多个 artifacts 的模块

正如前面提到的,一个 Maven 模块都只有一个 artifact。因此,当你的项目依赖于一个 Maven 模块时,这个模块的 artifact 是哪一个会很明显。而使用 Gradle 或Ivy的话,情况就不同了。Ivy 的依赖描述符( ivy.xml)可以定义多个 artifact。有关更多的信息,请参阅 ivy.xml引用的Ivy。在 Gradle 中,当你声明一个对 Ivy 模块的依赖时,实际上你是在那个模块的  default 配置上声明了一个依赖。所以实际上,你依赖的 artifacts 集(通常是一些 jar 包) 是与该模块的  default 配置相关联的 artifacts 集。以下是一些比较重要的情况:
  • 一个模块的 default 配置包含了不希望有的 artifacts。一个依赖只是声明了所需的 artifacts ,而不是依赖整个配置。
  • 所需的 artifact 属于一个非 default的配置。这个配置被显式地命名为这个依赖声明的一部分。
还有一些其他的情况,需要调整依赖声明。请参阅  DependencyHandler 的例子和声明依赖的完整参考。

50.4.1.2. Artifact only notation

如上所述,如果找不到模块的描述符文件,默认情况下 Gradle 会下载一个与模块的名称相同的 jar 文件。但有时候,即使存储库包含了模块描述符,而你只是想下载 artifact jar而不下载它的依赖项。[14] 而有时候你想要从一个仓库中下载一个 zip,而它没有模块描述符。Gradle 提供了一个 artifact only notation 用于这些案例情况——只是对你想要下载的扩展前加个 '@' 标志:

示例 505. Artifact 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'
}

一个 artifact only notation 创建了一个模块依赖,它只下载指定扩展名的 artifact 文件。现有的模块描述符将被忽略。

50.4.1.3. Classifiers

Maven 依赖管理有 classifier 的概念。[15] 而 Gradle 支持这一点。如果你想从一个 Maven 仓库中获取 classified 依赖项,你可以这样写:

示例 50.6. 使用 classifier 的依赖

build.gradle

compile "org.gradle.test.classifiers:service:1.0:jdk15@jar"
    otherConf group: 'org.gradle.test.classifiers', name: 'service', version: '1.0', classifier: 'jdk14'

如上面的第一行所示,classifiers 可以与artifact only notation 一起使用。

它可以轻松地遍历一个配置的依赖 artifacts:

示例 50.7. 遍历一个配置

build.gradle

task listJars << {
    configurations.compile.each { File file -> println file.name }
}

gradle -q listJars的输出结果

> 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

50.4.2. 客户端模块依赖

客户端模块依赖允许直接在构建脚本中声明传递依赖。它们是外部库的模块描述符的替代者。

示例 50.8. 客户端模块依赖 - 传递依赖

build.gradle

dependencies {
    runtime module("org.codehaus.groovy:groovy-all:2.2.0") {
        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 描述符来找出它的依赖,而是从构建文件中获取信息。一个客户端模块的依赖可以是正常的模块依赖,或者是 artifact 依赖项或是另一个客户端模块。可以看一看 API 文档: ClientModule

在当前版本客户端模块有一个缺陷。假设你的项目是一个库,你想要这个库上传到你公司的 Maven 或Ivy 仓库。Gradle 会将你的项目的 jar 包以及这仆依赖的 XML 描述文件上传到公司仓库。如果你使用了客户端模块,在 XML 描述符文件中的依赖声明就会不正确。我们将在未来版本的 Gradle 修正这一点。

50.4.3. 项目依赖

对于多项目构建,Gradle 能区分外部依赖与作为多项目构建的一部分的某个项目上的依赖。对于后者,你可以声明项目依赖

50.9. 项目依赖

build.gradle

dependencies {
    compile project(':shared')
}

详细信息请参阅ProjectDependency的 API 文档

多项目构建将在第 56 章,多项目生成中进行详述。

50.4.4. 文件依赖

文件依赖允许你直接将一组文件添加到配置中,而不用先将它们添加到存储库。这将会非常有用,比如你无法,或者是不想要把某些文件放到仓库。或者是你如果不想使用任何仓库来存储你的依赖。

如果想添加一些文件作为配置的依赖,你只需要传一个文件集合作为依赖:

示例 50.10. 文件依赖

build.gradle

dependencies {
    runtime files('libs/a.jar', 'libs/b.jar')
    runtime fileTree(dir: 'libs', include: '*.jar')
}

文件依赖项不会包含在你的项目的发布的依赖描述中。然而,文件依赖会被包含在同一个构建的传递项目依赖里。这意味着它们不能在当前的构建外使用,但它们可以在同一个构建中使用。

你可以声明哪些任务将产生作为文件依赖的文件。例如,你可以在通过构建生成文件的时候这样做。

示例 50.11. 生成文件依赖

build.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 -q list的输出结果

> gradle -q list
compiling classes
classpath = [classes]

50.4.5. Gradle API 依赖

你可以通过使用DependencyHandler.gradleApi()方法,来声明一个当前的 Gradle 版本的 API 上的依赖。当你在开发自定义 Gradle 任务或插件时将会很有用。

示例 50.12. Gradle API 依赖

build.gradle

dependencies {
    compile gradleApi()
}

50.4.6. 本地 Groovy 依赖

可以通过使用DependencyHandler.localGroovy()方法,来声明对与 Gradle 一起发布的 Groovy 的依赖。当你在开发自定义 Gradle 任务或在 Groovy 中的插件时将会很有用。

示例 50.13. Gradle 的 Groovy 依赖

build.gradle

dependencies {
    compile localGroovy()
}

50.4.7. 排除传递依赖

通过配置或者是通过依赖,你可以排除一个传递依赖:

示例 50.14. 排除传递依赖

build.gradle

configurations {
    compile.exclude module: 'commons'
    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
}

dependencies {
    compile("org.gradle.test.excludes:api:1.0") {
        exclude module: 'shared'
    }
}

如果你为一个特定的配置定义一个 exclude,则解析此配置或任何继承的配置时,对于所有的依赖,所排除的传递依赖将会被过滤掉。如果你想要从你的所有配置中排除传递依赖,你可以用简明的方式,使用 Groovy 的 spread-dot 运算符来表示,如这个例子所示例。在定义一个 exclude 时,你可以只指定 organization 或者 module 名称,或者是两者都指定。可以看看 Dependency 和 Configuration 的 API 文档。

不是每个传递依赖都可以被排除 — — 一些传递依赖可能是应用程序能正确运行的必要条件。一般来说,可以被排除的传递依赖,在运行时并不需要,或者是保证在目标环境或平台上可用。

你应排除每个依赖或每个配置吗?事实证明,在大多数情况下你想要排除每一个配置。下面是为什么可能想要排除传递依赖的一些原因。记住,对于其中一些用例,有着比排除更好的解决方案!

  • 由于许可证的原因而让依赖不受欢迎。
  • 在任何的远程仓库中这个依赖都不可用。
  • 在运行时不需要这个依赖。
  • 这个依赖有一个版本与所需要的版本冲突。使用这个案例,请参考第 50.2.3 节,“解决版本冲突”和关于ResolutionStrategy文档,以了解这个问题潜在的更好的解决方案。

基本上,在大多数情况下对每一个配置都是排除传递依赖。这种依赖声明的方式更加明确。它也更准确,因为每个依赖排除规则并不能保证给定的传递依赖不会显示在配置中。例如,某些其他的依赖,并没有任何排除规则,可能会带上那个多余的传递依赖。

其他相关依赖关系排除的示例,可以参考 ModuleDependencyDependencyHandler

50.4.8. 可选属性

一个依赖的所有属性都是可选的,除了 name。这取决于仓库类型,这些信息实际上需要用于在仓库中找到这个依赖。请参阅 50.6 节,“仓库”。例如,如果你使用 Maven 仓库,你需要定义group,name 和 version。如果你使用文件系统仓库,你可能只需要 name 或 name 和 version。

示例 50.15. 依赖的可选属性

build.gradle

dependencies {
    runtime ":junit:4.10", ":testng"
    runtime name: 'testng' 
}

你也可以向一个配置指定依赖 notations 的集合或数组:

示例 50.16. 依赖的集合和数组

build.gradle

List groovy = ["org.codehaus.groovy:groovy-all:2.2.0@jar",
               "commons-cli:commons-cli:1.0@jar",
               "org.apache.ant:ant:1.9.3@jar"]
List hibernate = ['org.hibernate:hibernate:3.0.5@jar', 'somegroup:someorg:1.0@jar']
dependencies {
    runtime groovy, hibernate
}

50.4.9. 依赖配置

在 Gradle ,一个依赖可以有不同的配置 (就像你的项目可以有不同的配置)。如果你不显式指定任何东西,Gradle 会使用依赖的默认配置。对于 Maven 存储库的依赖,至少默认配置是唯一可用的一个。如果你使用 Ivy 存储库,并且想要为你的依赖定义一个非默认配置,就要使用 map 标记法并且声明:

示例 50.17. 依赖配置

build.gradle

dependencies {
    runtime group: 'org.somegroup', name: 'somedependency', version: '1.0', configuration: 'someConfiguration'
}

同样的项目依赖,你需要声明:

示例 50.18. 项目的依赖配置

build.gradle

dependencies {
    compile project(path: ':api', configuration: 'spi')
}

50.4.10. 依赖报告

你可以从命令行生成依赖报告 (参阅 第11.6.4节,“列出项目依赖”)。通过使用 Project report 插件(参阅 第 41 章, Project Report 插件),在构建中可以创建一个这样的报告。

从 Gradle 1.2 起,有一个新的编程 API 用于访问解析的依赖信息。依赖报告(见前面一段)正是使用此 API。这个 API 可以让你查看解析的依赖图,并提供有关依赖的信息。在未来的版本,这个API 将提供更多有关解析结果的详细信息。关于这个 API 的更多信息,请参考ResolvableDependencies.getResolutionResult()上的文档。ResolutionResult API的可能用法:

  • 创建高级的依赖报告,以适应你的用例。
  • 启用使构建逻辑基于依赖树的内容来决定。

50.5. 使用依赖

下面的示例我们使用以下依赖设置:

示例 50.19. Configuration.copy

build.gradle

configurations {
    sealife
    alllife
}

dependencies {
    sealife "sea.mammals:orca:1.0", "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
    alllife configurations.sealife
    alllife "air.birds:albatros:1.0"
}

这些依赖有以下的传递依赖:

shark-1.0 -> seal-2.0, tuna-1.0

orca-1.0 -> seal-1.0

tuna-1.0 -> herring-1.0

你可以使用配置来访问它们的声明依赖或其中一个子集:

示例 50.20. 访问声明依赖

build.gradle

task dependencies << {
    configurations.alllife.dependencies.each { dep -> println dep.name }
    println()
    configurations.alllife.allDependencies.each { dep -> println dep.name }
    println()
    configurations.alllife.allDependencies.findAll { dep -> dep.name != 'orca' }.each { dep -> println dep.name }
}

gradle -q dependencies的输出结果

> gradle -q dependencies
albatros

albatros
orca
shark
tuna

albatros
shark
tuna

dependencies返回只明确属于配置的依赖。allDependencies包括了扩展配置的依赖。

要获得配置依赖的library文件,你可以这样:

示例 50.21. Configuration.files

build.gradle

task allFiles << {
    configurations.sealife.files.each { file ->
        println file.name
    }
}

gradle -q allFiles的输出结果

> gradle -q allFiles
orca-1.0.jar
shark-1.0.jar
tuna-1.0.jar
herring-1.0.jar
seal-2.0.jar

有时你想要配置依赖的某个子集(例如单个依赖)的 library 文件。

示例 50.22. 指定的 Configuration.files

build.gradle

task files << {
    configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
        println file.name
    }
}

gradle -q files的输出结果

> gradle -q files
orca-1.0.jar
seal-2.0.jar

Configuration.files方法总是获取整个配置的所有 artifacts。然后,由指定的依赖筛选获取的文件。正如你在示例中所看到的,传递依赖都被包括在内。

你还可以复制配置。你可以选择指定只复制原始配置里的一个子集的依赖。复制的方法有两种。copy方法只复制明确属于配置的依赖。copyRecursive方法将复制所有依赖,包括扩展配置的依赖。

示例 50.23. Configuration.copy

build.gradle

task copy << {
    configurations.alllife.copyRecursive { dep -> dep.name != 'orca' }.allDependencies.each { dep ->
        println dep.name
    }
    println()
    configurations.alllife.copy().allDependencies.each { dep ->
        println dep.name
    }
}

gradle -q copy 的输出结果

> gradle -q copy
albatros
shark
tuna

albatros

要重点注意,复制的配置所返回的文件,往往并不总是与原始配置的依赖子集所返回的文件一样。在子集的依赖和不属于子集的依赖之间,存在版本冲突的情况下,解析的结果可能会有所不同。

示例 50.24. Configuration.copy 与 Configuration.files

build.gradle

task copyVsFiles << {
    configurations.sealife.copyRecursive { dep -> dep.name == 'orca' }.each { file ->
        println file.name
    }
    println()
    configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
        println file.name
    }
}

gradle -q copyVsFiles的输出结果

> gradle -q copyVsFiles
orca-1.0.jar
seal-1.0.jar

orca-1.0.jar
seal-2.0.jar

在上面的例子中 orca 依赖 seal-1.0 而 shark 依赖于 seal-2.0。原始配置因此有版本冲突,所以被解析为更新的 seal-2.0 版本。files 方法因此返回 seal-2.0 作为 orca的传递依赖。复制的配置只有 orca 一个依赖,因此没有版本冲突,seal-1.0 作为传递依赖被返回。

一旦一个配置被解析,那它就是不可变的了。如果去修改它的状态,或者是它的某个依赖的状态,将会引发一个异常。你可以永远复制一个解析了的配置。这个复制的配置处于未解析的状态,并且可以被刷新解析。

想了解更多关于这个配置类的API,可以参阅它的 API 文档:Configuration.

50.6. 仓库

Gradle 仓库管理,基于 Apache Ivy,为你提供了有关仓库布局和获取策略的许多自由。另外,Gradle 提供了各种方便的方法来添加预配置的仓库。

您可以配置任意数量的仓库,每一个都会被Gradle 独立处理。如果 Gradle 在特定仓库中查找模块描述符,它将尝试从同一仓库中下载该模块的所有 artifacts。虽然模块的元数据和模块 artifacts 必须位于同一仓库,但也有可能单个仓库有多个 Url,给多个位置去搜索元数据文件和 jar 文件。

有几种不同类型的存储库可以声明:

表 50.2. 仓库类型

类型 描述
Maven 中央仓库 一个会在 Maven 中央仓中查找依赖的预配置仓库。
Maven JCenter 仓库 一个会在 Bintray 的 Jcenter 查找依赖的预配置仓库。
Maven 本地仓库 一个会在本地 Maven 仓库中查找依赖的预配置仓库。
Maven 仓库 一个 Maven 仓库。可以位于本地文件系统上,或在某个远程的位置。
Ivy 仓库 一个 Ivy 仓库可以位于本地文件系统上,或在某个远程的位置。
Flat 目录仓库 一个在本地文件系统上的简单的仓库。不支持任何元数据格式。

50.6.1. Maven 中央仓库

若要添加中央 Maven 2 仓库(http://repo1.maven.org/maven2),只需添加下面的代码到你的构建脚本中:

示例 50.25. 添加 Maven 中央仓库

build.gradle

repositories {
    mavenCentral()
}

现在 Gradle 将会在此仓库中查找你的依赖。

50.6.2. Maven JCenter 仓库

BintrayJCenter 是所有流行的 Maven OSS artifacts 的up-to-date 集合,包括直接发布到 Bintray 的 artifacts。

若要添加 JCenter Maven 仓库(http://jcenter.bintray.com),只需添加下面的内容到你的构建脚本中:

示例 50.26. 添加 Bintray Jcenter Maven 仓库

build.gradle

repositories {
    jcenter()
}

现在 Gradle 将会在 JCenter 仓库中查找你的依赖。

50.6.3. 本地 Maven 仓库

你可以这样把本地的 Maven 缓存作为仓库使用:

示例 50.27. 添加本地 Maven 缓存作为仓库:

build.gradle

repositories {
    mavenLocal()
}

Gradle 使用与 Maven 相同的逻辑来标识你本地的 Maven 缓存的位置。如果在settings.xml中定义一个本地仓库的位置,那么这个位置将会被使用。在USER_HOME/.m2 的settings.xml 比在 M2_HOME/confsettings.xml优先。如果没有settings.xml可用,Gradle 将使用默认的位置USER_HOME/.m2/repository

50.6.4. Maven 仓库

要添加一个自定义的 Maven 仓库,你可以如下操作:

示例 50.28. 添加一个自定义的 Maven 仓库

build.gradle

repositories {
    maven {
        url "http://repo.mycompany.com/maven2"
    }
}

有时候,一个仓库会出现 POM 文件发布在一个地方,而 JAR 文件和其他构件发布在另一个地方。要定义一个这样的仓库,你可以这样:

示例 50.29. 为 JAR 文件添加额外的 Maven 仓库

build.gradle

repositories {
    maven {
        // Look for POMs and artifacts, such as JARs, here
        url "http://repo2.mycompany.com/maven2"
        // Look for artifacts here if not found at the above location
        artifactUrls "http://repo.mycompany.com/jars"
        artifactUrls "http://repo.mycompany.com/jars2"
    }
}

Gradle 将会在第一个URL中查找 POM 和 JAR 文件。如果那里找不到 JAR,就会用 artifact URLs 来查找 JAR 文件。

50.6.4.1. 访问密码保护的 Maven 仓库

要访问一个使用基本的身份验证的 Maven 仓库,当你在定义该仓库时要指定使用的用户名和密码:

示例 50.30. 访问密码保护的 Maven 仓库

build.gradle

repositories {
    maven {
        credentials {
            username 'user'
            password 'password'
        }
        url "http://repo.mycompany.com/maven2"
    }
}

最好是把你的用户名和密码写在gradle.properties中,而不是直接写在构建文件中。

50.6.5. 平面的目录仓库

如果你想要把一个(平面)文件系统目录作为仓库使用,只需输入:

示例 50.31. 平面仓库的解决

build.gradle

repositories {
    flatDir {
        dirs 'lib'
    }
    flatDir {
        dirs 'lib1', 'lib2'
    }
}

这将会添加一些用于查找依赖的仓库,它会在一个或多个目录中寻找。如果你只使用平面目录解析器,那么你不需要去设置一个依赖的所有属性。请参阅 第50.4.8 节,“可选属性”

50.6.6. Ivy 存储库

使用一个标准布局的 Ivy 存储库:

示例 50.32. Ivy存储库

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "maven"
    }
}

详细信息请参阅IvyArtifactRepository 

50.6.6.1. 为一个 Ivy 存储库定义自定义的模式

若要定义非标准布局的 Ivy 仓库,你可以定义一个仓库模式布局:

示例 50.33. Ivy 仓库模式布局

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "pattern", {
            artifact "[module]/[revision]/[type]/[artifact].[ext]"
        }
    }
}

50.6.6.2. 使用 Maven 兼容布局的 Ivy 仓库

作为可选的功能,一个使用模式布局的仓库可以有它自己的以Maven 风格奠定的“组织”部分,该部分使用斜杠替换点作为分隔符。例如,组织my.company将表示为my/company

示例50.34. 使用 Maven 兼容布局的 Ivy 仓库

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "pattern", {
            artifact "[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
            m2compatible = true
        }
    }
}

50.6.6.3. 为一个 Ivy 仓库定义不同的 artifact 和 Ivy 文件位置

若要定义一个从不同的位置获取 Ivy 文件和 artifacts 的 Ivy 仓库,您可以使用模式布局,每个单独的模式用于定位到 Ivy 文件和 artifacts:

示例 50.35. 自定义模式的 Ivy 仓库

build.gradle

repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        layout "pattern", {
            artifact "3rd-party-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
            artifact "company-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
            ivy "ivy-files/[organisation]/[module]/[revision]/ivy.xml"
        }
    }
}

每个artifactivy都指定了一个仓库,添加一个额外的的模式来使用。这些模式以定义它们的顺序来使用。

50.6.6.4. 访问密码保护的 Ivy 仓库

要访问一个使用基本的身份验证的 Ivy 仓库,当你在定义该仓库时要指定使用的用户名和密码:

示例 50.36. Ivy存储库

build.gradle

repositories {
    ivy {
        url 'http://repo.mycompany.com'
        credentials {
            username 'user'
            password 'password'
        }
    }
}

50.6.7. 使用仓库

若要访问一个仓库:

示例 50.37. 访问一个仓库:

build.gradle

println repositories.localRepository.name
    println repositories['localRepository'].name

要配置一个仓库:

示例 50.38. 仓库配置

build.gradle

repositories {
    flatDir {
        name 'localRepository'
    }
}
repositories {
    localRepository {
        dirs 'lib'
    }
}
repositories.localRepository {
    dirs 'lib'
}

50.6.8. 更多关于 Ivy 解析器的信息

Gradle由于 Ivy 在它的 hood 之下,对仓库非常灵活。

  • 对于与仓库通信的协议,有很多的选项(比如文件系统,http, ssh……)

  • 每个仓库都可以有其自己的布局。

比方说,你可以声明一个junit:junit:3.8.2库的依赖。现在 Gradle 是如何发现它在存储库中的?某种程度上依赖信息必须映射到一个路径上。相比于固定路径的 Maven,使用 Gradle 你可以定义一个模式,该模式定义了路径的样子。这里有一些例子:[16]

// Maven2 layout (if a repository is marked as Maven2 compatible, the organization (group) is split into subfolders according to the dots.)
someroot/[organisation]/[module]/[revision]/[module]-[revision].[ext]

// Typical layout for an Ivy repository (the organization is not split into subfolder)
someroot/[organisation]/[module]/[revision]/[type]s/[artifact].[ext]

// Simple layout (the organization is not used, no nested folders.)
someroot/[artifact]-[revision].[ext]

要添加任何一种仓库 (你可以很简单地编写你自己的) ,你可以:

示例 50.39. 自定义仓库的定义

build.gradle

repositories {
    ivy {
        ivyPattern "$projectDir/repo/[organisation]/[module]-ivy-[revision].xml"
        artifactPattern "$projectDir/repo/[organisation]/[module]-[revision](-[classifier]).[ext]"
    }
}

其中由 Ivy (也因此由 Gradle )提供解析器的,它的概述可以在这里找到。通过Gradle,你只是不用通过XML来配置它们,而是通过它们的API。

50.7. 依赖解析的工作原理

Gradle 将获取你的依赖声明和仓库定义,并通过一个称为依赖项解析的过程尝试下载所有依赖项。下面是这个过程的工作原理的简要概述。

  • 给出所需的依赖项,Gradle 首先尝试为该依赖解析模块。每个仓库按顺序进行检查,首先查找指示该模块存在的模块描述符文件(POM 或 Ivy 文件)。如果没有找到模块描述符,Gradle 将搜索表示模块存在于存储库中的主要模块 artifact文件。

    • 如果依赖被声明为一个动态版本(像1.+),Gradle 将会把它解析到在仓库中的最新的可用的静态版本(如1.2)。对于 Maven 仓库,是通过maven metadata.xml文件来实现,而对于 Ivy 存储库,则是通过目录列表。

    • 如果模块描述符是一个具有的父 POM 文件声明的 POM 文件,Gradle 将以递归方式尝试为该 POM 文件解析每个父模块。

  • 一旦已为该模块检查了每个仓库,Gradle 将会选择使用“最好”的一个。它使用以下标准来完成:

    • 对于动态版本,“高”的静态版本优于“低”的版本。
    • 由模块描述符文件(Ivy或 POM 文件)声明的文件优于只有一个artifact 文件的模块。
    • 前面的仓库的模块优于后面的仓库的模块。

    当依赖由一个静态版本来声明,并且在仓库中找到模块描述符文件时,将不会再继续搜索后面的仓库,这个过程的其余部分是短路的。

  • 然后这个模块的所有artifact将从上面的过程所选择的 同一个仓库 中请求。

50.8. 微调依赖解析过程

在大多数情况下,Gradle 的默认依赖管理将会在你的构建中按你所想的解析依赖关系。然而,在某些情况下,会有必要调整依赖解析,以确保你的构建能得到正确的依赖关系。

有许多种方式都可以影响到Gradle解析依赖。

50.8.1. 强制一个特定模块的版本

强制一个模块的版本,是告诉 Gradle 对于指定的依赖(不管是否传递依赖),始终使用一个特定的版本,而覆盖在发布的模块描述符中所指定的任何版本。当解决版本冲突时,这可能非常有用——有关详细信息请参阅第 50.2.3 节,“解决版本冲突”

强制版本也可以用于处理传递依赖所带来的流氓元数据。如果传递依赖有质量较差的元数据,导致了依赖解析时的问题时,你可以强制 Gradle 对于这一依赖使用一个较新的,固定的版本。有关示例,请参见ResolutionStrategy。请注意,“依赖解析规则”(见下文),提供了一种更强大的机制,来取代一个损坏的模块依赖。请参阅50.8.2.3 节,“黑名单替换指定版本”

50.8.2. 使用依赖解析规则

一个依赖解析规则为每一个解析依赖执行,并提供功能强大的 api 用于在解析依赖之前处理这个依赖的请求。此功能还在孵化中,但目前提供了对于一个请求的依赖更改组、 名称及版本的功能,允许在解析过程中,把一个依赖替换为另一个完全不同的模块。

依赖解析规则提供了一种非常强大的方式来控制依赖解析过程,并可以用于实现在依赖管理中各种高级模式的排序。下面将概述其中的某些模式。更多的信息和代码示例请参阅ResolutionStrategy

50.8.2.1. 模块化可发布的单位

通常一个组织会使用一个版本发布一组库;这些库将在一起构建,测试以及发布。这些库形成一个“可发布的单位”,被设计并打算作为一个整体使用。而一起使用来自不同的可发布单位的库,也不会有意义。

但传递依赖解析会很容易破坏这种协议。举个例子:

  • module-a 依赖于 releasable-unit:part-one:1.0
  • module-a 依赖于 releasable-unit:part-one:1.0

一个依赖于module-amodule-b的构建,将在可发布单位内获得这个库的不同版本。

依赖解析规则使你能够在构建中强制指定可发布的单位。想象一下,一个可发布的单位,由有“org.gradle” 组的所有库定义。我们可以强制所有这些库使用一致的版本:

示例 50.40. 强制一个组的所有库使用一致的版本

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.gradle') {
            details.useVersion '1.4'
        }
    }
}

50.8.2.2. 实现一个自定义的版本方案

在一些企业的环境中,可以声明在 gradle 构建中的模块版本列表,是在外部维护和审核的。依赖解析规则提供了这种模式的整洁的实现:

  • 在构建脚本中,开发人员使用模块组与名称声明依赖,但使用占位符版本,例如:“default”。
  • 这个“default”版本通过一个依赖解析规则被解析为一个特定的版本,这个规则将在一个核准模块的公司中查找该版本。

该规则实现可以整齐地封装在一个公司的插件中,并在组织内和所有构建共享。

示例 50.41. 使用自定义的版本方案

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.version == 'default') {
            def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
            details.useVersion version
        }
    }
}

def findDefaultVersionInCatalog(String group, String name) {
    //some custom logic that resolves the default version into a specific version
    "1.0"
}

50.8.2.3. 黑名单替换特定版本

依赖解析规则提供了一个机制,用于把一个依赖的指定版本列入黑名单,并提供一个替代的版本。如果某个依赖的版本坏了,并且不应该被使用,这个机制将非常有用。一个依赖解析规则将会使这个版本被替换为一个已知的好的版本。一个坏的模块的例子是,在一个库上声明的一个依赖无法在任何公共仓库中找到,但为什么不能使用一个特定的模块版本,而更希望要另一个版本,还有很多其他原因。

在下面的示例中,想象版本1.2.1包含了重要的修复程序,并应始终优先于1.2使用。提供的规则将强制执行:在任何时间遇到了1.2版本,都将会替换为1.2.1。注意,这与上面描述的强制使用一个版本不同,这个模块的其他版本将不受影响。这意味着,如果这个版本也因依赖传递被获取到,“使用最新”的冲突解决策略仍然会选择 1.3 版本。

示例 50.42. 黑名单替换特定版本

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.group == 'org.software' && details.requested.name == 'some-library' && details.requested.version == '1.2') {
            //prefer different version which contains some necessary fixes
            details.useVersion '1.2.1'
        }
    }
}

50.8.2.4. 使用一个兼容模块替换一个依赖模块

有时一个完全不同的模块可以作为请求的模块依赖的替代者。示例包括,使用“groovy”来代替“groovy-all”,或者使用“log4j-over-slf4j”来代替“log4j”。从 Gradle 1.5 开始,你可以使用依赖解析规则来进行这些替换:

示例 50.43. 在解析中更改依赖组及名称

build.gradle

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.name == 'groovy-all') {
            //prefer 'groovy' over 'groovy-all':
            details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
        }
        if (details.requested.name == 'log4j') {
            //prefer 'log4j-over-slf4j' over 'log4j', with fixed version:
            details.useTarget "org.slf4j:log4j-over-slf4j:1.7.5"
        }
    }
}

50.8.3. 启用 Ivy 动态解析模式

Gradle 的Ivy 仓库实现支持相当于 Ivy 的动态解析的模式。通常情况下,Gradle 将rev属性用于在ivy.xml文件中包含的每个依赖定义。在动态解析模式中,Gradle 将优先使用revConstraint属性来代替rev属性,用于一个给定的依赖定义。如果不存在revConstraint属性,则使用rev属性。

若要启用动态解析模式,你需要在仓库定义进行合适的设置。下面展示了几个例子。注意,动态解析模式只对 Gradle 的 Ivy 仓库有效。它不能用于 Maven 仓库,或自定义的 Ivy DependencyResolver实现。

示例 50.44. 启用动态解析模式

build.gradle

// Can enable dynamic resolve mode when you define the repository
repositories {
    ivy {
        url "http://repo.mycompany.com/repo"
        resolve.dynamicMode = true
    }
}

// Can use a rule instead to enable (or disable) dynamic resolve mode for all repositories
repositories.withType(IvyArtifactRepository) {
    resolve.dynamicMode = true
}

50.8.4. 组件元数据规则

每个模块(也称为组件)都有相关的元数据上,像它的组,名称,版本,依赖,等等。这个元数据通常来源于模块的描述符。元数据规则允许模块的元数据在构建脚本中被操纵。它们在模块描述符下载之后,被所有候选版本之间选择之前生效。这使得元数据成为自定义依赖解析的另一种手段。

其中一个 Gradle 能理解的模块元数据是模块的状态模式。这一概念,也可以从 Ivy 中,随着时间推移,一个模块发展的成熟水平可以得知。默认状态模式,按状态的成熟程度排序,分别是integrationmilestonerelease。除了状态模式以外,模块也有一个(当前) 的状态,这个状态必须是它的状态模式中的值之一。如果没有在(Ivy)描述符中指定,Ivy 模块和 Maven snapshot模块的状态默认为integration ,不是snapshot 的 Maven 模块则默认为release

一个模块的状态以及状态模式,会在解析 latest 版本选择器时考虑到。具体而言,latest.someStatus 将会解析成有着someStatus 或更成熟状态的最高的模块版本。例如,在适当的位置使用默认的状态模式,latest.integration将选择最高的模块版本,不论其状态(因为integration是成熟度最低的状态),而latest.release将选择release状态的最高模块版本。这里是在代码中的表现:

示例 50.45,“Latest”版本选择器

build.gradle

dependencies {
    config1 "sea.fish:tuna:latest.integration"
    config2 "sea.fish:tuna:latest.release"
}

task listFish << {
    configurations.config1.each { println it.name }
    println()
    configurations.config2.each { println it.name}
}

gradle -q listFish的输出结果

> gradle -q listFish
tuna-1.5.jar

tuna-1.4.jar

下一个示例演示了基于在一个模块的元数据规则中声明的自定义状态模式的latest选择器:

示例 50.46. 自定义状态模式

build.gradle

dependencies {
    config3 "air.birds:albatros:latest.silver"
    components {
        eachComponent { ComponentMetadataDetails details ->
            if (details.id.group == "air.birds") {
                details.statusScheme = ["bronze", "silver", "gold", "platinum"]
            }
        }
    }
}

task listBirds << {
    configurations.config3.each { println it.name }
}

gradle -q listBirds的输出结果

> gradle -q listBirds
albatros-2.0.jar

50.9. 依赖缓存

Gradle 包含了一个高度复杂的依赖缓存机制,该机制力求减少依赖解析中的远程请求,同时努力保证依赖解析结果的正确及可再生性。

Gradle 依赖缓存包含 2 个主要类型的存储:

  • 一个基于文件的下载构件存储,包括像jars这样的二进制文件,以及像 POM 文件以及 Ivy 文件这样的原始下载的元数据。下载的构件的存储路径包括了 SHA1 校验和,意味着 2 个具有相同名称但内容不同的构件可以很容易地被缓存。

  • 解析的模块元数据的二进制存储,包括解析动态版本、 模块描述符和构件的结果。

从下载的构件的存储中分享出缓存的元数据,将允许我们使用缓存做一些非常有用的东西,而如果使用一个透明的只有文件的缓存布局则会很困难。

Gradle 缓存中不允许本地缓存隐藏问题和,以及一直是许多构建工具的挑战的创造神秘及难以调试的行为。这一新行为通过带宽和存储空间的有效途径被实现。在此过程中,Gradle 实现了可靠和可重复的的企业构建。

50.9.1. Gradle 依赖缓存的主要特点

50.9.1.1. 单独的元数据缓存

Gradle 以二进制格式在元数据缓存中保留了依赖解析的各方面的记录。存储在元数据缓存中的信息包括:

  • 解析一个动态版本(例如1.+)到一个具体的版本(例如1.2)的结果。
  • 对于特定模块的解析后的模块元数据,包括模块构件和模块依赖。
  • 对于特定构件的解析后的构件元数据,包括指向下载的构件工件的信息。
  • 在特定的仓库中缺少的特定模块或工件,避免反复地尝试访问不存在的资源。

每个元数据缓存中的条目包括了一条存储库中提供的信息,以及可用于缓存到期的时间戳的记录。

50.9.1.2. 仓库缓存是独立的

如上文所述,每个仓库是一个单独的元数据缓存。一个仓库由它的 URL、类型和布局来区分。如果一个模块或构件以之前没有从这个存储库中解析过,Gradle 将尝试在这个存储库中解析这个模块。这将始终涉及到存储库中的远程查找,然而,在许多情况下都没有下载的需要(见第 50.9.1.3 节,“构件重用”下文)、。

如果所需的构件,在构建所指定的任何仓库中都没有找到的话,依赖解析将会失败,无论本地缓存是否从一个其他的仓库中取回这个构件。仓库独立允许构建之间用一种先进方法的方法彼此隔离,以前没有构建工具能做到这样。这是一个关键的功能,能在任何环境中创建可靠,重复性好的构建。

50.9.1.3. 构件重用

在下载构件前,Gradle 试图通过下载与该项目关联的sha文件以确定所需的构件的校验和。如果校验和可以获取到,并且如果已经存在具有相同 id 和校验和的构件,则不会再去下载这个构件。如果校验和不能从远程服务器检索,构件将被下载(并且忽略它所匹配的现有的构件)。

同时考虑构件从不同的仓库下载,Gradle 还将试图重用本地 Maven 库中发现的构件。如果一个候选的构件已经通过 Maven 下载,并且如果它可以和远程服务器定义的校验和匹配,Gradle 将使用这个构件。

50.9.1.4. 基于校验和的存储

不同的仓库提供不同的二进制构件以响应相同的构件标识符是可能的。这种常见的情况是 Maven 的快照构件,但任何构件也都可以不改变它的标识符而重新发布。通过基于其 SHA1 校验和缓存构件,Gradle 是能够保持同一构件的多个版本。这意味着,当对一个存储库解析时,Gradle 将永远不会覆盖从一个不同的仓库中缓存的构件文件。这不需要让一个单独的文件在每一个仓库中存储就可以做到。

50.9.1.5. 缓存锁

Gradle 依赖缓存使用基于文件的锁来确保,它可以安全地通过多个 Gradle 进程并行使用。每当二进制元数据存储区正在读取或写入时,锁都会被持有,但会在慢的操作比如下载远程工作时释放。

50.9.2. 重写缓存的命令行选项

50.9.2.1. 离线

--offline 命令行开机告诉 Gradle 总是从缓存中使用依赖模块,无论它们是否被再次检查。在使用离线运行时,Gradle 将不会尝试访问网络来执行依赖解析。如果所需的模块在依赖缓存中不存在,构建执行将会失败。

50.9.2.2. 刷新

有时,Gradle 依赖缓存可能与已配置的仓库的实际状态不同步。也许一个存储库的最初配置不正确,或许是一个“无改变”的模块被不正确地发布。要刷新依赖缓存中的所有依赖项,请使用--refresh-dependencies命令行选项。

--refresh-dependencies 选项告诉 Gradle 在解析模块和构件时忽略所有缓存条目。对所有已配置的仓库执行新的解析,通过重新计算动态版本,模块刷新,以及下载构件。然而,在再次下载之前Gradle 可能将会检查前一个下载的构件是否有效。这是通过比较发布在仓库中的 SHA1 值和现在已经下载好的工件的 SHA1 值来完成的。

50.9.3. 依赖缓存的微调控制

你可以在一个配置中通过使用ResolutionStrategy对缓存的某些方面进行微调。

默认情况下,Gradle 缓存动态版本的时间为 24 小时。如果要改变Gradle对解析一个动态版本的缓存时间,可以使用:

示例 50.47. 动态版本缓存控制

build.gradle

configurations.all {
    resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
}

默认情况下,Gradle 的变化模块将缓存 24 小时。要修改 Gradle 对变化模块的元数据和构件的缓存时间,请使用:

示例 50.48. 变化模块的缓存控制

build.gradle

configurations.all {
    resolutionStrategy.cacheChangingModulesFor 4, 'hours'
}

更多详细信息请参阅ResolutionStrategy的 API 文档。

50.10. 传递依赖管理的策略

许多项目依赖于Maven 中央仓库。这不是没有问题的。

  • Maven 中央存储库可能会下线,或者响应时间很长。

  • 许多项目的 POM 文件会有错误的信息(比如,commons-httpclient-3.0的POM文件声明了 JUnit 是运行时依赖)。

  • 对于许多项目而言,可能不是只有正确的一组依赖(因POM格式的影响会多或少)。

如果您的项目依赖于 Maven 中央仓,你很可能需要额外的自定义仓库,因为:

  • 你可能需要还没有上传到Maven 中央仓的依赖。

  • 你想要正确地处理 Maven 中央仓 POM 文件中错误的元数据。

  • 你不想曝光给想要对你的项目进行构建的人,Maven 中央仓停机或者有时候响应时间太长。

想在设置一个自定义的仓库并不算什么。[17]但想让这个仓库保持最新的状态,可能会很乏味。对一个新的版本,你总是要创建新的 XML 描述符和目录。你的自定义存仓库是另一个基础结构元素,它可能有停机时间并且需要更新。要启用历史版本,你需要保留所有过去的库,并且需要备份。它是一个间接层。你还要查找另外一个信息源。尽管这一切真的不是大问题,但累加起来就有影响了。仓库管理器,比如像 Artifactory 或 Nexus 则会使这些工作变得轻松。但是比如开源项目通常没有主机用于这些产品。这种状况通过一些新的服务也改变了,比如Bintray ,它可以让开发者使用自助服务的仓库平台托管和分发他们发布的二进制文件。Bintray 还支持共享经过他们审核的构件,通过JCenter公共仓库,为所有普通的OSS java 构件提供一个单一的解析地址(见第 50.6.2 节,“Maven JCenter 存储库”)。

这也是为什么一些项目更愿意把他们的库存储于他们的版本控件系统的原因。这种做法 Gradle 也完全支持。库可以存在一个平面目录中,而没有任何 XML 模块描述符文件。然而 Gradle 能提供完整的传递依赖管理。您可以使用客户端模块依赖,或者是工件依赖来表达依赖关系,后者的情况中第一级依赖是没有传递依赖的。人们可以从 svn 检出这样一个项目,并且具体必要的一切,来构建它。

如果您是使用像 Git 一样的分布式版本控制系统,由于人们会检出整个历史,你可能不想使用版本控制系统来保存这些库。但即使是这样, Gradle 的灵活性也可以使你的生活更轻松。例如你可以使用一个共享平面目录,而不包括如上所述可以有完全的传递依赖管理的XML 描述符。

你也可以使用混合策略。如果你主要关心的是 POM 文件和维护的自定义 XML 描述符中的元数据不正确,客户端模块提供了一种替代方案。但你当然也可以仍然使用 Maven2 仓库和你自定义的存储库,作为只放 jars和依然使用传递依赖管理的仓库。或者,你可以只为元数字不正确的 POMs 提供客户端模块。对于这些jar 和不正确的 POMs,你依然要使用远程仓库。

50.10.1. 隐式传递依赖

还有另外一种方法,用于处理没有XML 描述符文件的传递依赖。你可以使用 Gradle 来做,但我们不推荐它。我们提到它是为了完整性,以及和其他构建工具进行比较。

诀窍是只使用构件依赖,并在列表中对它们进行分组。对于这种方法,你将用某种方式表达,你第一级的依赖项和传递依赖是什么(见第 50.4.8 条,“可选属性”)。但缺点是,对于 Gradle 依赖管理而言,所有的依赖都被认为是第一级依赖。依赖报告不会显示你真正的依赖关系图,并且compile任务会使用所有的依赖,而不只是第一级依赖。总之,比起使用客户端模块,你的构建不再那么容易维护和可靠。而你不会有其他收获。



[14Gradle 支持部分多项目构建(参见 第 56 章, 多项目构建)。

[16http://ant.apache.org/ivy/history/latest-milestone/concept.html,你可以了解到更多关于 ivy 模式的内容。

[17如果你想要从 Maven 中央仓库停机保护你的项目不变得更加复杂时,你可能需要设置一个仓库代理。在企业环境中,这是相当普遍的。而对于一个开放源码项目而言,看起来则有点小题大做了。


目录
相关文章
|
安全 Cloud Native Go
解决Gradle依赖问题:不安全协议的处理策略Could not resolve all dependencies for configuration ‘:detachedConfiguration9
解决Gradle依赖问题:不安全协议的处理策略Could not resolve all dependencies for configuration ‘:detachedConfiguration9
1035 0
|
JavaScript Java Maven
|
Java Android开发
Android如何通过Gradle发布Android依赖库(aar)到 jitpack 公共仓库
Android如何通过Gradle发布Android依赖库(aar)到 jitpack 公共仓库
621 0
|
Java API Android开发
Gradle 依赖关系中 compile和 implementation的区别
将在一个项目中展示implementation,api以及compile之间的差异。 假设我有一个包含三个Gradle模块的项目: • app(Android应用) • my-android-library(Android库) • my-java-library(Java库) app具有my-android-library与依赖。my-android-library具有my-java-library依赖。
594 0
|
Java Spring
一行解决IDEA中gradle下载依赖jar包慢问题(适用于各操作系统)
一行解决IDEA中gradle下载依赖jar包慢问题(适用于各操作系统)
1545 0
一行解决IDEA中gradle下载依赖jar包慢问题(适用于各操作系统)
|
28天前
|
Java Spring
Gradle 如何确保依赖的版本兼容性
Gradle 通过依赖管理机制确保版本兼容性,使用声明式配置定义项目依赖,自动解析和下载所需库。它支持版本范围和动态版本,通过冲突解决策略选择最佳版本,保证构建的一致性和可靠性。
|
5月前
|
Java 数据库连接 数据库
在Maven或者Gradle构建配置中添加了必要的依赖
在Maven或者Gradle构建配置中添加了必要的依赖
61 1
|
6月前
|
Java 关系型数据库 MySQL
Gradle中的依赖Dependencies说明与使用总结
Gradle中的依赖Dependencies说明与使用总结
419 0
|
Java Maven Android开发
android之gradle配置仓库与引入依赖
android之gradle配置仓库与引入依赖
607 0
gradle依赖冲突的解决方式
gradle依赖冲突的解决方式
126 0