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

  • 构建脚本中声明的版本实际上对应于正在解析的版本

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

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

版本冲突失败

每当 Gradle 在依赖关系图中的两个不同版本中找到相同的模块时,就会出现版本冲突。默认情况下,Gradle 执行乐观升级,这意味着如果在图中找到版本1.1和,我们将解析为最高版本。但是,由于传递依赖,很容易忽略某些依赖项的升级。在上面的示例中,如果构建脚本中使用了一个版本并且传递了一个版本,那么您可以在不实际注意到的情况下使用。1.31.31.11.31.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.release... 1.+

  • 或使用更改版本(快照、内容更改的固定版本……​)

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

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 库依赖于以下库:

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 生态系统中声明一致的解决方案

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

为了使这更容易,Gradle 提供了一种使用扩展为 Java 生态系统配置一致分辨率的方法java

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

请参阅Java 插件扩展文档以获取更多配置选项。