在某些情况下,您可能希望完全控制依赖项图。特别是,您可能希望确保

  • 构建脚本中声明的版本实际上与解析的版本相对应

  • 或确保依赖项解析随着时间的推移是可重现的

Gradle 提供了通过配置解析策略来执行此操作的方法。

版本冲突时失败

只要 Gradle 在依赖项图中找到相同模块的两个不同版本,就会发生版本冲突。默认情况下,Gradle 执行乐观升级,这意味着如果在图中找到版本1.11.3,我们将解析为最高版本1.3。但是,由于传递依赖项,很容易错过一些依赖项被升级。在上面的示例中,如果1.1是构建脚本中使用的版本,而1.3是传递引入的版本,则可以使用1.3而没有实际注意到。

为了确保您了解此类升级,Gradle 提供了一种可以在配置的解析策略中激活的模式。想象以下依赖项声明

build.gradle.kts
dependencies {
    implementation("org.apache.commons:commons-lang3:3.0")
    // the following dependency brings lang3 3.8.1 transitively
    implementation("com.opencsv:opencsv:4.6")
}
build.gradle
dependencies {
    implementation 'org.apache.commons:commons-lang3:3.0'
    // the following dependency brings lang3 3.8.1 transitively
    implementation 'com.opencsv:opencsv:4.6'
}

然后默认情况下 Gradle 会升级commons-lang3,但可以失败构建

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnVersionConflict()
    }
}

确保解析结果可重复

在某些情况下,依赖项解析可能会随着时间推移而变得不稳定。也就是说,如果您在日期 D 构建,那么在日期 D+x 构建可能会得到不同的解析结果。

这在以下情况下可能发生

  • 使用动态依赖项版本(版本范围、latest.release1.+ 等)

  • 或使用更改版本(SNAPSHOT、内容不断变化的固定版本等)

处理动态版本的推荐方法是使用依赖项锁定。但是,也可以完全阻止使用动态版本,这是一种替代策略

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnDynamicVersions()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnDynamicVersions()
    }
}

同样,可以通过激活此标志来阻止使用更改版本

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnChangingVersions()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnChangingVersions()
    }
}

在发布时,最好在更改版本时失败。

最终,可以使用单个调用将动态版本失败和更改版本失败结合起来

build.gradle.kts
configurations.all {
    resolutionStrategy {
        failOnNonReproducibleResolution()
    }
}
build.gradle
configurations.all {
    resolutionStrategy {
        failOnNonReproducibleResolution()
    }
}

获取一致的依赖项解析结果

依赖项解析一致性是一个孵化功能

人们普遍误以为应用程序只有一个依赖项图。实际上,Gradle 在构建过程中会解析多个不同的依赖项图,即使是在单个项目中也是如此。例如,在编译时使用的依赖项图与在运行时使用的依赖项图不同。通常,运行时的依赖项图是编译依赖项的超集(规则有一些例外,例如,某些依赖项在运行时二进制文件中重新打包)。

Gradle 独立地解析这些依赖项图。这意味着,例如在 Java 生态系统中,"编译类路径"的解析不会影响"运行时类路径"的解析。类似地,测试依赖项最终可能会提升生产依赖项的版本,在执行测试时会导致一些令人惊讶的结果。

通过启用依赖项解析一致性,可以缓解这些令人惊讶的行为。

启用项目本地依赖项解析一致性

例如,假设您的 Java 库依赖于以下库

示例 6. 一级依赖项
build.gradle.kts
dependencies {
    implementation("org.codehaus.groovy:groovy:3.0.1")
    runtimeOnly("io.vertx:vertx-lang-groovy:3.9.4")
}
build.gradle
dependencies {
    implementation 'org.codehaus.groovy:groovy:3.0.1'
    runtimeOnly 'io.vertx:vertx-lang-groovy:3.9.4'
}

然后解析 compileClasspath 配置将按预期将 groovy 库解析为版本 3.0.1。但是,解析 runtimeClasspath 配置将返回 groovy 3.0.2

原因是 vertx 的传递依赖项(它是 runtimeOnly 依赖项)带来了更高版本的 groovy。通常情况下,这不是问题,但这也意味着您将在运行时使用的 Groovy 库版本将与您用于编译的版本不同。

为了避免这种情况,Gradle 提供了一个 API 来解释配置应该如何一致地解析。

声明配置之间的解析一致性

在上面的示例中,我们可以声明在运行时,我们希望与编译时使用相同的公共依赖项版本,方法是声明“运行时类路径”应该与“编译类路径”一致。

build.gradle.kts
configurations {
    runtimeClasspath.get().shouldResolveConsistentlyWith(compileClasspath.get())
}
build.gradle
configurations {
    runtimeClasspath.shouldResolveConsistentlyWith(compileClasspath)
}

因此,runtimeClasspathcompileClasspath 都将解析 Groovy 3.0.1。

这种关系是有方向的,这意味着如果需要解析 runtimeClasspath 配置,Gradle 将首先解析 compileClasspath,然后将解析结果作为 严格约束 “注入”到 runtimeClasspath 中。

如果由于某种原因,两个图的版本无法“对齐”,则解析将失败并提示操作。

在 Java 生态系统中声明一致的解析

上面的 runtimeClasspathcompileClasspath 示例在 Java 生态系统中很常见。但是,仅仅声明这两个配置之间的一致性通常是不够的。例如,您很可能希望测试运行时类路径运行时类路径一致。

为了简化操作,Gradle 提供了一种使用 java 扩展配置 Java 生态系统中一致解析的方法。

build.gradle.kts
java {
    consistentResolution {
        useCompileClasspathVersions()
    }
}
build.gradle
java {
    consistentResolution {
        useCompileClasspathVersions()
    }
}

有关更多配置选项,请参阅 Java 插件扩展文档