使用Gradle 6避免依赖地狱的简要介绍

368 阅读7分钟

依赖地狱是许多团队的一个大问题。项目和它的依赖图越大,维护它就越难。现有的依赖管理工具提供的解决方案不足以有效地处理这个问题。

Gradle 6旨在提供可操作的工具,帮助处理这类问题,使依赖性管理更可维护和可靠。

以一个真实世界项目的匿名依赖关系图为例:

image.png

该图中有数百个不同的库,有些是内部库,有些是OSS库。其中一部分模块每周要发布几个版本。在实践中,对于这种规模的图,你没有办法避免典型的问题,比如:

image.png

image.png

在构建和测试你的产品时,依赖问题会导致很多问题,每天都要弄清楚是什么导致了回归,为什么项目突然不构建了,或者是哪个依赖对另一个依赖的升级负责,这都是非常具有挑战性的。

如果你很幸运,你会得到一个编译时的错误,但常见的是只在执行测试时或甚至在生产运行时发生问题。 在所有这些情况下,错误往往很难追溯到源头,因为它是在构建工具中的依赖性解析成功后出现的。 所以从依赖性管理的角度来看,一切都很正确,而事实上却不是这样。

造成这种不匹配的原因是,解析依赖关系的引擎没有足够的信息来检测——如果可能的话,自动修复——一个问题。为了给引擎提供更多的信息,模块需要携带更多的元数据。好消息是,这正是Gradle 6的重点所在

Gradle 6的依赖性管理介绍

Gradle 6向前迈进了一步,是依赖性管理新时代的推动者。在Gradle Module Metadata的帮助下,Gradle现在支持更丰富、更智能的依赖性声明模型,使构建工具能够做出更好的决定,使构建更可靠,并降低维护依赖性图的成本。

在依赖性管理中看到的很多问题通常是消费者(如你构建的应用程序)和生产者(如你使用的库/依赖)之间的分歧造成的,因为没有足够的信息让依赖性管理引擎做出正确的决定。

库(如Guava)或框架作者(如Spring Boot,或内部框架)能够以更丰富的方式表达需求,使他们的用户面临更少的依赖管理问题,这一点至关重要。 他们应该能够表达诸如 "如果你不知道使用哪个版本,就使用这个版本",或 "如果你使用这个功能,那么你也需要那些额外的依赖"。 这些是Gradle 6提供的众多选项中的一部分。

一个典型的依赖声明是以group,artifactversion (也称为GAV坐标,如com.google.guava:guava:25.1 )来表达的。让我们先关注一下version 部分。如果你看到25.1 ,这意味着什么?

  • 在你写代码的时候,它是最新的版本吗?
  • 是你从StackOverflow上复制和粘贴的一个版本,并且它能工作
  • 25.0,它可以工作吗?
  • 升级到26.0可以吗?

缺乏与单一版本声明相关的语义的一个直接后果是,我们很可能会进行乐观的升级。 我们假设,因为它在25.1 ,所以升级到26.0应该没有问题。在实践中,这很好,这也是Gradle多年来使用的策略。

然而,在有些情况下,乐观的升级会中断:

  • 主要版本的升级(破坏了二进制兼容性)
  • 漏洞(你真的不应该包括1.6 ,因为它有一个CVE)
  • 退步(在1.6 中有一个bug )
  • 库属于一个更大的模块集,需要共享相同的版本(例如Jackson Core, Databind, Annotations, ...)
  • ...

作为一个例子,Gradle 6为你提供了以更丰富的模型来表达事物的能力:

  • 你需要这个依赖关系严格在[1.0, 2.0[ 范围内(因为它遵循语义上的版本划分)
  • 在这个范围内,你更喜欢 1.5 (因为这是你已经测试过的)。
  • 而你拒绝1.6 ,因为你知道它有一个直接影响到你的错误。
dependencies {
    implementation("org.sample:sample") {
        version {
            strictly("[1.0, 2.0[")
            prefer("1.5")
            reject("1.6")
        }
    }
}

这意味着,如果没有其他人关心,引擎会选择1.5 。如果另一个依赖关系需要1.7 ,我们知道我们可以安全地升级到1.7 。然而,如果另一个依赖关系需要2.1 ,我们现在可以失败的构建,因为两个模块不一致。

此外,还有一些生产者不知道的关于依赖关系的信息,因为这些信息在库发布后会发生变化:发现的错误、漏洞、不正确的反式依赖关系等等......这些信息可以随时作为额外的输入推送给依赖关系管理引擎!

值得注意的是,Gradle提供的改进不仅仅是针对消费者。作为一个库的作者,你在表达你所生产的东西的方式上比以往任何时候都更灵活:不同的模块,它们的版本应该是一致的,有可选功能的库,一个关于依赖版本的建议平台,不同版本的运行时间的不同二进制文件等等

Gradle提供这些功能已经有几个版本了。然而它们的使用主要限于多项目设置。在Gradle 6中,所有这些工具现在都可以通过Gradle Module Metadata在发布的模块中支持它们,对库的作者和消费者都是可用的。它使需求的表达更加清晰,并使引擎能够计算出最佳的解决方案。

Gradle模块元数据

由于Gradle依赖模型比其他构建工具(Ant+Ivy、Maven、Bazel......)更丰富,我们需要一种元数据格式,以便为发布在Maven Central、Artifactory或Nexus等二进制仓库的库实现所有这些功能。 这种元数据格式基本上是Gradle模型的序列化。 你可以在我们的专门博文中了解更多信息。

在Gradle 6.0中,Gradle模块元数据的发布是默认启用的。

作为一个库的作者,你不应该担心使用Gradle的特定功能:在所有情况下,发布Maven或Ivy元数据仍然是可能的,我们尽最大努力将Gradle的特定概念映射到这些格式。如果不可能的话,这只意味着有些功能只对Gradle用户开放,但通常Maven用户与现在相比不会有任何损失。

在实践中

最后但并非最不重要的是,对于Gradle 6,我们大幅重写了用户指南中的依赖管理文档部分,使其更加以用例为中心。

在接下来的几周里,我们将发表一些博文,更详细地介绍不同的用例。特别是我们将解释你能用Gradle 6做什么,包括:

Gradle 6是向更好的依赖管理迈出的重要一步,但发展不会就此停止:我们知道我们还有很多工作要做,我们会处理你的反馈。