本章介绍了Gradle 内部依赖项解析的工作方式. 在介绍了如何声明存储库依赖项之后 ,有必要解释在依赖项解析期间这些声明如何组合在一起.

依赖关系解析是一个由两个阶段组成的过程,将重复执行两个阶段,直到完成依赖关系图为止:

  • 将新的依赖项添加到图中后,请执行冲突解决方案以确定应将哪个版本添加到图中.

  • 当特定的依赖关系(即具有版本的模块)被标识为图形的一部分时,请检索其元数据,以便可以依次添加其依赖关系.

以下部分将描述Gradle识别为冲突的内容以及如何自动解决冲突. 之后,将介绍元数据的检索,解释Gradle如何遵循依赖关系链接 .

How Gradle handles conflicts?

执行依赖关系解析时,Gradle处理两种类型的冲突:

Version conflicts

那是当两个或多个依赖项需要给定的依赖项但版本不同时.

Implementation conflicts

那就是当依赖图包含在Gradle术语中提供相同实现或功能的模块时.

以下各节将详细说明Gradle如何尝试解决这些冲突.

The dependency resolution process is highly customizable to meet enterprise requirements. For more information, see the chapter on Controlling transitive dependencies.

Version conflict resolution

当两个组件发生版本冲突:

  • 取决于同一个模块,假设com.google.guava:guava

  • 但是在不同的版本上,假设20.025.1-android

    • 我们的项目本身取决于com.google.guava:guava:20.0

    • 我们的项目还依赖com.google.inject:guice:4.2.2 ,而com.google.inject:guice:4.2.2本身依赖com.google.guava:guava:25.1-android

Resolution strategy

鉴于上述冲突,可以通过选择版本或通过失败的解决方案来解决. 处理依赖性管理的不同工具具有处理此类冲突的不同方法.

Apache Maven使用最接近的优先策略.

Maven将采用最短路径获取依赖项并使用该版本. 如果有多个相同长度的路径,则第一个获胜.

这意味着在上面的示例中, guava的版本将为20.0因为直接依赖性比guice依赖性更近 .

该方法的主要缺点是依赖于顺序. 在很大的图中保持顺序可能是一个挑战. 例如,如果新版本的依赖项最终以不同于先前版本的顺序拥有其自己的依赖项声明,该怎么办?

使用Maven,这可能会对已解决的版本产生不良影响.

Apache Ivy是一种非常灵活的依赖项管理工具. 它提供了自定义依赖关系解决方案(包括冲突解决方案)的可能性.

这种灵活性伴随着难以推理的代价.

Gradle将考虑所有请求的版本,无论它们出现在依赖关系图中的何处. 在这些版本中,它将选择最高的版本.

如您所见,Gradle支持丰富版本声明的概念,因此最高版本取决于版本声明的方式:

  • 如果不涉及范围,则将选择不被拒绝的最高版本.

    • 如果严格低于该版本,则选择将失败.

  • 如果涉及范围:

    • 如果有非范围版本落入指定范围内或高于其上限,则将选择该版本.

    • 如果只有范围,则将选择具有最高上限的范围的现有最高版本.

    • 如果严格低于该版本,则选择将失败.

请注意,在范围起作用的情况下,Gradle需要元数据来确定对于所考虑范围确实存在哪些版本. 这将导致对元数据的中间查找,如Gradle如何检索依​​赖元数据? .

Implementation conflict resolution

Gradle使用变体和功能来识别模块提供的功能 .

这是一个独特的功能,值得一章以了解其含义和功能.

两个模块之一发生冲突时:

  • 尝试选择不兼容的变体,

  • 声明相同的功能

选择候选人之间了解有关处理此类冲突的更多信息.

How Gradle retrieves dependency metadata?

Gradle需要有关依赖关系图中包含的模块的元数据. 该信息是两点所必需的:

  • 当声明的版本是动态的时,确定模块的现有版本.

  • 确定给定版本的模块依赖性.

Discovering versions

面对动态版本,Gradle需要确定具体的匹配版本:

  • 检查每个存储库,Gradle不会在第一个返回某些元数据的站点上停止. 定义多个时,将按照添加顺序对其进行检查.

  • 对于Maven存储库,Gradle将使用maven-metadata.xml来提供有关可用版本的信息.

  • 对于常春藤存储库,Gradle将诉诸目录列表.

此过程将生成候选版本列表,这些候选版本然后与表示的动态版本匹配. 此时,将恢复版本冲突解决 .

请注意,Gradle会缓存版本信息,有关更多信息,请参见控制动态版本缓存一节.

Obtaining module metadata

给定所需的依赖关系(带有版本),Gradle尝试通过搜索依赖关系指向的模块来解决依赖关系.

  • 依次检查每个存储库.

    • 根据存储库的类型,Gradle会查找描述模块的元数据文件( .module.pomivy.xml文件)或直接查找工件文件.

    • Modules that have a module metadata file (.module, .pom or ivy.xml file) are preferred over modules that have an artifact file only.

    • 一旦存储库返回元数据结果,以下存储库将被忽略.

  • 如果找到依赖项的元数据,则将对其进行检索和解析

    • 如果模块元数据是声明了父POM的POM文件,则Gradle将递归地尝试为POM解析每个父模块.

  • 然后,从上述过程中选择的同一存储库中请求模块的所有工件.

  • 然后,所有这些数据(包括存储库源和潜在的丢失)都存储在Dependency Cache中 .

上面的最后一点是使与Maven Local集成的问题. 由于它是Maven的缓存,因此有时会错过给定模块的某些工件. 如果Gradle从Maven Local采购了这样的模块,它将认为丢失的工件完全丢失.

Repository blacklisting

当Gradle无法从存储库检索信息时,它将在构建期间将其列入黑名单,并且所有依赖项解析都将失败.

最后一点对于可重复性很重要. 如果允许继续构建而忽略有问题的存储库,则一旦存储库重新联机,后续的构建可能会有不同的结果.

HTTP Retries

在将其列入黑名单之前,Gradle将尝试几次连接到给定的存储库. 如果连接失败,Gradle将重试某些可能会被瞬态发生的错误,从而增加每次重试之间的等待时间.

当由于永久错误或达到最大重试次数而无法联系存储库时,就会将其列入黑名单.

The Dependency Cache

Gradle包含一个高度复杂的依赖项缓存机制,该机制旨在最大程度地减少在依赖项解析中发出的远程请求的数量,同时努力确保依赖项解析的结果正确且可重现.

Gradle依赖项缓存由位于GRADLE_USER_HOME/caches下的两种存储类型组成:

  • 基于文件的下载工件的存储,包括二进制文件(如jars)以及原始下载的元数据(如POM文件和Ivy文件). 下载的工件的存储路径包括SHA1校验和,这意味着可以轻松地缓存2个名称相同但内容不同的工件.

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

Gradle缓存不允许本地缓存隐藏问题并创建其他神秘且难以调试的行为. Gradle专注于带宽和存储效率,可实现可靠且可复制的企业构建.

Separate metadata cache

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

  • 将动态版本(例如1.+ )解析为具体版本(例如1.2 )的结果.

  • 特定模块的已解析模块元数据,包括模块工件和模块依赖性.

  • 特定工件的已解析工件元数据,包括指向下载的工件文件的指针.

  • 由于没有在一个特定的存储库中的特定模块或工件,省去重复尝试访问不存在的资源.

元数据缓存中的每个条目都包括提供信息的存储库记录以及可用于缓存过期的时间戳.

Repository caches are independent

如上所述,对于每个存储库,都有一个单独的元数据缓存. 存储库由其URL,类型和布局标识. 如果以前没有从此存储库解析模块或工件,则Gradle将尝试根据存储库解析模块. 这将始终涉及对存储库的远程查找,但是在许多情况下, 不需要下载 .

如果所需的工件在构建指定的任何存储库中均不可用,则依赖关系解析将失败,即使本地缓存具有从其他存储库中检索到的该工件的副本,也是如此. 存储库独立性允许构建以以前没有构建工具完成的高级方式彼此隔离. 这是创建可在任何环境下可靠且可复制的内部版本的关键功能.

Artifact reuse

在下载工件之前,Gradle会尝试通过下载与该工件关联的sha文件来确定所需工件的校验和. 如果可以检索校验和,那么如果已经存在具有相同ID和校验和的工件,则不会下载工件. 如果无法从远程服务器检索校验和,则将下载工件(如果它与现有工件匹配,则将被忽略).

除了考虑从其他存储库下载的工件外,Gradle还将尝试重用在本地Maven存储库中找到的工件. 如果Maven已下载了候选工件,则Gradle将使用此工件,前提是可以对其进行验证以匹配远程服务器声明的校验和.

Checksum based storage

响应相同的工件标识符,不同的存储库可能会提供不同的二进制工件. Maven SNAPSHOT工件通常是这种情况,但对于在不更改其标识符的情况下重新发布的任何工件也是如此. 通过根据工件的SHA1校验和缓存工件,Gradle能够维护同一工件的多个版本. 这意味着在针对一个存储库进行解析时,Gradle绝不会覆盖来自其他存储库的缓存工件文件. 无需在每个存储库中单独存放工件文件即可完成此操作.

Cache Locking

Gradle依赖项缓存使用基于文件的锁定来确保多个Gradle进程可以安全地同时使用它. 每当读取或写入二进制元数据存储时,都会保留该锁,但是会为缓慢的操作(例如下载远程工件)而释放该锁.

仅当不同的Gradle进程可以一起通信时,才支持此并发访问. 对于容器化版本,通常不是这种情况 .

Cache Cleanup

Gradle跟踪访问依赖项缓存中的哪些工件. 使用此信息,定期(最多每24小时)扫描缓存,以查找未使用超过30天的工件. 然后删除过时的工件,以确保高速缓存不会无限期增长.

Accessing the resolution result programmatically

尽管大多数用户只需要访问文件的"固定列表",但是在某些情况下,在上进行推理并获得有关分辨率结果的更多信息可能会很有趣:

  • 用于工具集成,其中需要依赖图的模型

  • 用于生成依赖关系图的可视表示形式(图像, .dot文件等)的任务

  • 用于提供诊断的任务(类似于dependencyInsight任务)

  • 适用于需要在执行时执行依赖关系解析的任务(例如,按需下载文件)

对于这些用例,Gradle提供了惰性的,线程安全的API,可通过调用Configuration.getIncoming()方法进行访问:

  • 无论解析成功与否, ResolutionResult API都可以访问已解析的依赖关系图.

  • 工件API提供了对未转换但未转换的工件的简单访问,但是具有工件的延迟下载(它们只能按需下载).

  • 人工制品视图API提供了高级的,经过过滤的人工制品视图,可能已转换 .