依赖版本对齐允许属于同一逻辑组(平台)的不同模块在依赖关系图中具有相同的版本。
处理不一致的模块版本
Gradle 支持对齐属于同一“平台”的模块的版本。例如,通常最好组件的 API 和实现模块使用相同的版本。但是,由于传递依赖解析的问题,属于同一平台的不同模块最终可能会使用不同的版本。例如,您的项目可能依赖于 jackson-databind
和 vert.x
库,如下所示
dependencies {
// a dependency on Jackson Databind
implementation("com.fasterxml.jackson.core:jackson-databind:2.8.9")
// and a dependency on vert.x
implementation("io.vertx:vertx-core:3.5.3")
}
dependencies {
// a dependency on Jackson Databind
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.9'
// and a dependency on vert.x
implementation 'io.vertx:vertx-core:3.5.3'
}
由于 vert.x
依赖于 jackson-core
,我们实际上会解析以下依赖版本
-
jackson-core
版本2.9.5
(由vertx-core
引入) -
jackson-databind
版本2.9.5
(通过冲突解决) -
jackson-annotation
版本2.9.0
(jackson-databind:2.9.5
的依赖)
很容易最终得到一组不能很好地协同工作的版本。为了解决这个问题,Gradle 支持依赖版本对齐,这由平台的概念支持。平台代表一组“可以很好地协同工作”的模块。要么是因为它们实际上是作为一个整体发布的(当平台的成员之一发布时,所有其他模块也以相同的版本发布),要么是因为有人测试了这些模块并表明它们可以很好地协同工作(通常是 Spring Platform)。
使用 Gradle 原生对齐版本
Gradle 原生支持对齐 Gradle 生成的模块。这是 依赖约束 传递性的直接结果。因此,如果您有一个多项目构建,并且希望消费者获得所有模块的相同版本,Gradle 提供了一种使用 Java Platform 插件 的简单方法。
例如,如果您的项目包含 3 个模块
-
lib
-
utils
-
core
,依赖于lib
和utils
以及声明以下依赖的消费者
-
core
版本 1.0 -
lib
版本 1.1
那么默认情况下,解析会选择 core:1.0
和 lib:1.1
,因为 lib
不依赖于 core
。我们可以通过在我们的项目中添加一个新模块,即平台,来解决这个问题,该平台将对项目的所有模块添加约束
plugins {
`java-platform`
}
dependencies {
// The platform declares constraints on all components that
// require alignment
constraints {
api(project(":core"))
api(project(":lib"))
api(project(":utils"))
}
}
plugins {
id 'java-platform'
}
dependencies {
// The platform declares constraints on all components that
// require alignment
constraints {
api(project(":core"))
api(project(":lib"))
api(project(":utils"))
}
}
完成此操作后,我们需要确保所有模块现在都依赖于平台,如下所示
dependencies {
// Each project has a dependency on the platform
api(platform(project(":platform")))
// And any additional dependency required
implementation(project(":lib"))
implementation(project(":utils"))
}
dependencies {
// Each project has a dependency on the platform
api(platform(project(":platform")))
// And any additional dependency required
implementation(project(":lib"))
implementation(project(":utils"))
}
重要的是,平台包含对所有组件的约束,而且每个组件都依赖于该平台。通过这样做,每当 Gradle 在图上向平台的模块添加依赖时,它也会包含对平台其他模块的约束。这意味着如果我们看到另一个属于同一平台的模块,我们将自动升级到相同的版本。
在我们的示例中,这意味着我们首先看到 core:1.0
,它引入了一个平台 1.0
,该平台对 lib:1.0
和 lib:1.0
具有约束。然后我们添加 lib:1.1
,它依赖于 platform:1.1
。通过冲突解决,我们选择 1.1
平台,该平台对 core:1.1
具有约束。然后我们在 core:1.0
和 core:1.1
之间进行冲突解决,这意味着 core
和 lib
现在已正确对齐。
仅当您使用 Gradle 模块元数据时,此行为才对已发布的组件强制执行。 |
对齐未使用 Gradle 发布的模块的版本
每当发布者不使用 Gradle 时,就像我们的 Jackson 示例一样,我们可以向 Gradle 解释说,所有 Jackson 模块“属于”同一平台,并受益于与原生对齐相同的行为。有两种选项可以表达一组模块属于平台
-
平台以 BOM 的形式发布,并且可以使用:例如,
com.fasterxml.jackson:jackson-bom
可以用作平台。在这种情况下,Gradle 缺少的信息是,如果使用了平台的成员之一,则应将平台添加到依赖项中。 -
无法使用现有平台。相反,应该由 Gradle 创建一个虚拟平台:在这种情况下,Gradle 根据所有使用的成员自行构建平台。
为了向 Gradle 提供缺失的信息,您可以定义 组件元数据规则,如下所述。
使用已发布的 BOM 对齐模块版本
abstract class JacksonBomAlignmentRule: ComponentMetadataRule {
override fun execute(ctx: ComponentMetadataContext) {
ctx.details.run {
if (id.group.startsWith("com.fasterxml.jackson")) {
// declare that Jackson modules belong to the platform defined by the Jackson BOM
belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
}
}
}
}
abstract class JacksonBomAlignmentRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext ctx) {
ctx.details.with {
if (id.group.startsWith("com.fasterxml.jackson")) {
// declare that Jackson modules belong to the platform defined by the Jackson BOM
belongsTo("com.fasterxml.jackson:jackson-bom:${id.version}", false)
}
}
}
}
通过将 belongsTo
与 false
(非虚拟)一起使用,我们声明所有模块都属于同一个已发布平台。在这种情况下,平台是 com.fasterxml.jackson:jackson-bom
,Gradle 将像查找任何其他模块一样在声明的仓库中查找它。
dependencies {
components.all<JacksonBomAlignmentRule>()
}
dependencies {
components.all(JacksonBomAlignmentRule)
}
使用该规则,上述示例中的版本将与 com.fasterxml.jackson:jackson-bom
的选定版本定义的版本对齐。在这种情况下,com.fasterxml.jackson:jackson-bom:2.9.5
将被选为 2.9.5
,因为 2.9.5
是所选模块的最高版本。在该 BOM 中,定义了以下版本并将使用:jackson-core:2.9.5
、jackson-databind:2.9.5
和 jackson-annotation:2.9.0
。此处较低版本的 jackson-annotation
可能是期望的结果,因为它符合 BOM 的建议。
自 Gradle 6.1 以来,此行为一直可靠地工作。实际上,它类似于使用 withDependencies 向平台的所有成员添加平台依赖的 组件元数据规则。 |
对齐没有已发布平台的模块的版本
abstract class JacksonAlignmentRule: ComponentMetadataRule {
override fun execute(ctx: ComponentMetadataContext) {
ctx.details.run {
if (id.group.startsWith("com.fasterxml.jackson")) {
// declare that Jackson modules all belong to the Jackson virtual platform
belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
}
}
}
}
abstract class JacksonAlignmentRule implements ComponentMetadataRule {
void execute(ComponentMetadataContext ctx) {
ctx.details.with {
if (id.group.startsWith("com.fasterxml.jackson")) {
// declare that Jackson modules all belong to the Jackson virtual platform
belongsTo("com.fasterxml.jackson:jackson-virtual-platform:${id.version}")
}
}
}
}
通过使用不带进一步参数的 belongsTo
关键字(平台是虚拟的),我们声明所有模块都属于同一个虚拟平台,引擎会对其进行特殊处理。虚拟平台不会从仓库中检索。标识符,在本例中为 com.fasterxml.jackson:jackson-virtual-platform
,是您作为构建作者自己定义的。“平台的内容”然后由 Gradle 通过收集所有指向同一虚拟平台的 belongsTo
语句动态创建。
dependencies {
components.all<JacksonAlignmentRule>()
}
dependencies {
components.all(JacksonAlignmentRule)
}
使用该规则,上述示例中的所有版本都将对齐到 2.9.5
。在这种情况下,还将采用 jackson-annotation:2.9.5
,因为这是我们定义本地虚拟平台的方式。
对于已发布平台和虚拟平台,Gradle 都允许您通过指定对平台的强制依赖来覆盖平台自身的版本选择
dependencies {
// Forcefully downgrade the virtual Jackson platform to 2.8.9
implementation(enforcedPlatform("com.fasterxml.jackson:jackson-virtual-platform:2.8.9"))
}
dependencies {
// Forcefully downgrade the virtual Jackson platform to 2.8.9
implementation enforcedPlatform('com.fasterxml.jackson:jackson-virtual-platform:2.8.9')
}