本指南解释了如何防止由于 Gradle 的激进依赖解析而导致的意外依赖升级

为什么要防止意外的依赖升级?

在 Gradle 中管理依赖时,您可能会遇到传递性依赖导致依赖意外升级到更新版本的情况。这可能导致意外行为或兼容性问题。Gradle 默认执行乐观升级,这意味着当在依赖图中发现同一个依赖存在多个版本时,它会解析到可用的最高版本。

例如,如果 commons-lang33.13.2 版本同时存在,即使您的构建脚本显式声明了 3.1,Gradle 也会选择 3.2。如果您的构建使用了 3.1 版本中存在但 3.2 版本中没有的功能,或者您尚未更新构建以兼容 3.2 版本,您可能不希望 Gradle 的依赖解析过程进行这种激进的升级。

build.gradle.kts
dependencies {
    implementation("org.apache.commons:commons-lang3:3.2")

    constraints {
        implementation("org.apache.commons:commons-lang3:3.1") {
            because("Version 1.3 introduces breaking changes not yet handled")
        }
    }
}
build.gradle
dependencies {
    implementation("org.apache.commons:commons-lang3:3.2")

    constraints {
        implementation("org.apache.commons:commons-lang3:3.1") {
            because("Version 1.3 introduces breaking changes not yet handled")
        }
    }
}

运行 ./gradlew dependencies --configuration runtimeClasspath 会展示结果

dependencies.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_prevent_accidental_upgrades'
------------------------------------------------------------

runtimeClasspath - Compile classpath for source set 'main'.
+--- org.apache.commons:commons-lang3:3.2
\--- org.apache.commons:commons-lang3:3.1 -> 3.2 (c)

选项 1:强制执行严格的依赖解析

Gradle 提供了一个选项,可以在发生版本冲突时使构建失败,从而确保不会发生意外升级。

要启用此功能,请将您的构建配置为在版本冲突时失败

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

启用此设置后,如果一个依赖存在多个版本冲突,Gradle 将停止执行并报告错误,而不是自动升级到最高版本。

运行 ./gradlew dependencies --configuration runtimeClasspath 会展示失败情况

dependencies-fail.out
> Task :dependencies FAILED

------------------------------------------------------------
Root project 'how_to_prevent_accidental_upgrades'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':dependencies'.
> Could not resolve all dependencies for configuration ':runtimeClasspath'.
   > Conflict found for the following module:
       - org.apache.commons:commons-lang3 between versions 3.2 and 3.1

选项 2:使用依赖约束

如果您有多个依赖项使用了共享库,您可以使用 依赖约束 在所有模块中强制使用一致的版本。

build.gradle.kts
dependencies {
    implementation("org.apache.commons:commons-lang3")
    constraints {
        implementation("org.apache.commons:commons-lang3") {
            version {
                strictly("3.1")
            }
        }
    }
}
build.gradle
dependencies {
    implementation("org.apache.commons:commons-lang3")
    constraints {
        implementation("org.apache.commons:commons-lang3") {
            version {
                strictly("3.1")
            }
        }
    }
}

使用 strictly("3.1"),Gradle 确保没有其他版本可以覆盖指定的依赖。

运行 ./gradlew dependencies --configuration runtimeClasspath 会展示结果

dependencies-const.out
> Task :dependencies

------------------------------------------------------------
Root project 'how_to_prevent_accidental_upgrades'
------------------------------------------------------------

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.apache.commons:commons-lang3 -> 3.1
\--- org.apache.commons:commons-lang3:{strictly 3.1} -> 3.1 (c)

选项 3:将依赖锁定到特定版本

为了更稳健的解决方案,您可以使用 依赖锁定 来确保 Gradle 在不同构建中一致地解析到相同的依赖版本。

要启用依赖锁定

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

然后,生成一个锁定文件

./gradlew dependencies --write-locks

这会创建一个 gradle.lockfile 文件,其中存储了精确的依赖版本,除非显式更新,否则可防止未来的升级。

总结

Gradle 的乐观依赖解析可能会意外升级依赖,导致兼容性问题。

为了防止意外升级,您可以

  • 强制严格解析 (failOnVersionConflict)

  • 使用 strictly 关键字显式约束依赖

  • 使用依赖锁定 (activateDependencyLocking) 以在不同构建中保持版本一致。