本指南解释了如何由于 Gradle 激进的依赖解析而**防止意外的依赖版本**。

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

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

例如,如果 `commons-lang3` 的 `3.1` 和 `3.2` 版本都存在,Gradle 将选择 `3.2`,即使您的构建脚本明确声明了 `3.1`。如果您的构建使用了版本 `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`) 以在构建之间保持版本一致。