依赖锁定机制
源:Gradle 官方文档 - Dependency Locking
依赖锁定确保每次构建使用完全相同的依赖版本,即使依赖声明中使用了动态版本或范围,实现可重现的构建。
依赖锁定作用
为什么需要锁定
问题场景:
kotlin
dependencies {
implementation("com.example:library:1.+") // 动态版本
}风险:
- 不同时间构建可能使用不同版本
- CI 和本地构建结果不一致
- 难以复现问题
- 意外引入破坏性变更
解决方案:依赖锁定
锁定的优势
可重现构建:
- 确保版本一致
- CI/CD 稳定
- 问题可复现
安全性:
- 防止供应链攻击
- 审计依赖变更
- 控制升级时机
团队协作:
- 统一依赖版本
- 避免"在我机器上能跑"
- 便于 Code Review
启用依赖锁定
基本启用
kotlin
// build.gradle.kts
dependencyLocking {
lockAllConfigurations()
}锁定特定配置
kotlin
configurations {
runtimeClasspath {
resolutionStrategy.activateDependencyLocking()
}
compileClasspath {
resolutionStrategy.activateDependencyLocking()
}
}锁定模式
严格模式:
kotlin
dependencyLocking {
lockMode.set(LockMode.STRICT)
}宽松模式(默认):
kotlin
dependencyLocking {
lockMode.set(LockMode.LENIENT)
}区别:
- STRICT:必须锁定所有配置
- LENIENT:仅锁定激活的配置
生成锁文件
首次生成
bash
# 生成所有配置的锁文件
./gradlew dependencies --write-locks
# 仅生成特定模块
./gradlew :app:dependencies --write-locks生成位置:
gradle/
└── dependency-locks/
├── compileClasspath.lockfile
├── runtimeClasspath.lockfile
├── debugCompileClasspath.lockfile
└── debugRuntimeClasspath.lockfile锁文件格式
# compileClasspath.lockfile
androidx.core:core-ktx:1.15.0=compileClasspath
com.squareup.retrofit2:retrofit:2.11.0=compileClasspath
com.squareup.okhttp3:okhttp:4.12.0=compileClasspath,runtimeClasspath
empty=annotationProcessor更新锁文件
更新所有依赖
bash
# 更新所有锁文件
./gradlew dependencies --write-locks
# 更新特定模块
./gradlew :app:dependencies --write-locks更新特定依赖
bash
# 更新单个依赖
./gradlew dependencies --update-locks com.example:library
# 更新多个依赖
./gradlew dependencies --update-locks com.example:lib-a,com.example:lib-b更新特定组
bash
# 更新整个 group
./gradlew dependencies --update-locks androidx.compose:*验证锁文件
构建时验证
bash
# 普通构建会自动验证
./gradlew build验证失败:
Dependency lock state for configuration 'compileClasspath' is out of date:
- Did not resolve 'com.example:library:1.1.0' which is part of the lock state严格验证
kotlin
dependencyLocking {
lockMode.set(LockMode.STRICT)
}CI/CD 集成
GitHub Actions
yaml
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
- name: Verify Dependency Locks
run: ./gradlew build --no-daemon
- name: Check for lock changes
run: |
if ! git diff --exit-code gradle/dependency-locks/; then
echo "Lock files have changed!"
exit 1
fi更新策略
PR 中更新:
yaml
- name: Update Dependencies
if: github.event_name == 'pull_request'
run: ./gradlew dependencies --write-locks
- name: Commit Lock Changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "Update dependency locks"
file_pattern: gradle/dependency-locks/*.lockfile实战案例
案例1:多模块项目
根项目配置:
kotlin
// build.gradle.kts (project)
subprojects {
apply(plugin = "base")
dependencyLocking {
lockAllConfigurations()
}
}生成所有模块的锁:
bash
./gradlew allDependencies --write-locks案例2:Android 项目
kotlin
// app/build.gradle.kts
dependencyLocking {
lockAllConfigurations()
lockMode.set(LockMode.STRICT)
}生成 Android 配置锁:
bash
./gradlew :app:dependencies --write-locks锁文件:
gradle/dependency-locks/
├── debugCompileClasspath.lockfile
├── debugRuntimeClasspath.lockfile
├── releaseCompileClasspath.lockfile
└── releaseRuntimeClasspath.lockfile案例3:定期更新依赖
创建更新脚本:
bash
#!/bin/bash
# update-dependencies.sh
echo "Updating all dependencies..."
./gradlew dependencies --write-locks
echo "Running tests..."
./gradlew test
if [ $? -eq 0 ]; then
echo "Tests passed. Committing lock files..."
git add gradle/dependency-locks/
git commit -m "Update dependency locks"
else
echo "Tests failed. Rolling back..."
git checkout gradle/dependency-locks/
fi最佳实践
提交锁文件到版本控制:
gitignore
# .gitignore
# 不要忽略锁文件!
# gradle/dependency-locks/定期更新:
- 每周或每月更新一次
- 测试后再提交
- Code Review 时检查变更
CI 验证:
- 确保锁文件最新
- 失败时阻止合并
- 自动化检查
文档化更新原因:
bash
git commit -m "Update dependency locks
- Retrofit 2.10.0 -> 2.11.0 (bug fixes)
- OkHttp 4.11.0 -> 4.12.0 (security patch)
"使用严格模式:
kotlin
dependencyLocking {
lockMode.set(LockMode.STRICT)
}忽略不需要锁定的配置:
kotlin
configurations.matching {
it.name.endsWith("Metadata") || it.name.contains("Test")
}.configureEach {
resolutionStrategy.deactivateDependencyLocking()
}常见问题
锁文件冲突
问题:多人修改依赖导致锁文件冲突
解决:
bash
# 重新生成锁文件
./gradlew dependencies --write-locks
# 测试
./gradlew build
# 提交
git add gradle/dependency-locks/
git commit -m "Resolve lock file conflicts"版本不匹配
问题:锁文件中的版本与依赖声明不一致
原因:
- 依赖已更新但未更新锁
- 手动修改了锁文件
解决:
bash
./gradlew dependencies --write-locks构建失败
问题:锁定后构建失败
原因:锁文件损坏或不完整
解决:
bash
# 删除旧锁文件
rm -rf gradle/dependency-locks/
# 重新生成
./gradlew dependencies --write-locks与其他机制配合
与 Version Catalog
toml
# gradle/libs.versions.toml
[versions]
retrofit = "2.11.0"
[libraries]
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }kotlin
dependencies {
implementation(libs.retrofit)
}
dependencyLocking {
lockAllConfigurations()
}优势:
- Version Catalog 管理声明版本
- 锁文件锁定实际解析版本
与依赖约束
kotlin
dependencies {
constraints {
implementation("com.example:library:2.0")
}
}
dependencyLocking {
lockAllConfigurations()
}配合使用:
- 约束推荐版本
- 锁文件固定版本
工具支持
查看锁文件差异:
bash
# 查看锁文件变更
git diff gradle/dependency-locks/验证锁文件:
bash
# 验证所有锁文件
./gradlew buildEnvironment生成报告:
bash
# 生成依赖报告
./gradlew htmlDependencyReport