依赖关系版本对齐允许依赖于同一逻辑组( 平台 )的不同模块在依赖关系图中具有相同的版本.

Handling inconsistent module versions

Gradle支持对齐属于同一"平台"的模块的版本. 例如,通常最好是组件的API和实现模块使用相同的版本. 但是,由于传递依赖解决方案的博弈,属于同一平台的不同模块最终可能使用不同的版本. 例如,您的项目可能依赖于jackson-databindvert.x库,如下所示:

示例1.声明依赖关系
build.gradle
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'
}
build.gradle.kts
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.0jackson-databind:2.9.5依赖性jackson-databind:2.9.5

最终会出现一组不能很好地协同工作的版本,这很容易. 为了解决此问题,Gradle支持依赖项版本对齐,平台概念支持此功能. 平台代表了一组"一起工作"的模块. 或者是因为它们实际上是作为一个整体发布的(当平台的一个成员发布时,所有其他模块也都以相同的版本发布),或者是因为有人测试了模块并指出它们可以很好地协同工作(通常是Spring)平台).

Aligning versions natively with Gradle

Gradle本地支持Gradle生产的模块的对齐. 这是依赖关系约束的可传递的直接结果. 因此,如果您具有多项目构建,并且希望使用者获得所有模块的相同版本,那么Gradle提供了一种使用Java Platform Plugin进行此操作的简单方法.

例如,如果您的项目包含3个模块:

  • lib

  • utils

  • core ,取决于libutils

并声明以下依赖项的使用者:

  • core版本1.0

  • lib版本1.1

那么默认情况下,解析将选择core:1.0lib:1.1 ,因为lib不依赖core . 我们可以通过在项目中添加一个新的模块platform来解决此问题,该模块将对您项目的所有模块添加约束:

例子2.平台模块
build.gradle
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"))
    }
}
build.gradle.kts
plugins {
    `java-platform`
}

dependencies {
    // The platform declares constraints on all components that
    // require alignment
    constraints {
        api(project(":core"))
        api(project(":lib"))
        api(project(":utils"))
    }
}

完成此操作后,我们需要确保所有模块现在都依赖于该平台 ,如下所示:

例子3.声明对平台的依赖
build.gradle
dependencies {
    // Each project has a dependency on the platform
    api(platform(project(":platform")))

    // And any additional dependency required
    implementation(project(":lib"))
    implementation(project(":utils"))
}
build.gradle.kts
dependencies {
    // Each project has a dependency on the platform
    api(platform(project(":platform")))

    // And any additional dependency required
    implementation(project(":lib"))
    implementation(project(":utils"))
}

平台对所有组件都包含约束很重要,而且每个组件都对平台具有依赖关系很重要. 这样,每当摇篮将增加一个依赖关系图上的平台的模块,它还将包括对平台的其他模块的约束. 这意味着,如果看到属于同一平台的另一个模块,我们将自动升级到相同版本.

In our example, it means that we first see core:1.0, which brings a platform 1.0 with constraints on lib:1.0 and lib:1.0. Then we add lib:1.1 which has a dependency on platform:1.1. By conflict resolution, we select the 1.1 platform, which has a constraint on core:1.1. Then we conflict resolve between core:1.0 and core:1.1, which means that core and lib are now aligned properly.

仅当您使用Gradle Module元数据时,才对发布的组件强制执行此行为.

Aligning versions of modules not published with Gradle

每当发布者不使用Gradle时(例如在我们的Jackson例子中),我们都可以向Gradle解释说,所有Jackson模块都"属于"相同的平台,并且受益于与本机对齐方式相同的行为:

例子4.依赖版本对齐规则
build.gradle
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-platform:${id.version}")
            }
        }
    }
}
build.gradle.kts
open 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-platform:${id.version}")
            }
        }
    }
}

通过使用belongsTo关键字,我们声明所有模块都属于同一个虚拟平台 ,该平台由引擎特别对待,特别是在对齐方面. 我们可以通过注册使用刚刚创建的规则:

例子5.使用依赖版本对齐规则
build.gradle
dependencies {
    components.all(JacksonAlignmentRule)
}
build.gradle.kts
dependencies {
    components.all(JacksonAlignmentRule::class.java)
}

然后,以上示例中的所有版本将与2.9.5保持一致. 但是,Gradle将允许您通过在Jackson平台上指定依赖项来覆盖该选择:

例子6.强制降级平台
build.gradle
dependencies {
    // Forcefully downgrade the Jackson platform to 2.8.9
    implementation enforcedPlatform('com.fasterxml.jackson:jackson-platform:2.8.9')
}
build.gradle.kts
dependencies {
    // Forcefully downgrade the Jackson platform to 2.8.9
    implementation(enforcedPlatform("com.fasterxml.jackson:jackson-platform:2.8.9"))
}

Virtual vs published platforms

由组件元数据规则定义的,未在存储库上发布belongsTo目标模块的平台称为虚拟平台. 引擎特别考虑了虚拟平台,它像已发布的模块一样参与依赖关系解析,但是会触发依赖关系版本对齐. 另一方面,我们可以找到在公共资源库上发布的"真实"平台. 典型示例包括BOM,例如Spring BOM. 它们的不同之处在于,已发布的平台可能引用的模块实际上是不同的模块. 例如,Spring BOM声明了对Spring以及Apache Groovy的依赖. 显然,这些东西的版本不同,因此在这种情况下对齐是没有意义的. 换句话说,如果发布了平台,则Gradle会信任其元数据,并且不会尝试对齐该平台的依赖版本.