Gradle 的增量构建是其性能优化的核心特性之一,它通过只处理变更的文件来显著提高构建速度。以下是 Gradle 增量构建的详细说明:
增量构建原理
增量构建基于任务的输入和输出声明,Gradle 会跟踪这些文件的变化,只有当输入文件发生变化时才重新执行任务。
工作流程
- 首次构建:执行任务并记录输入和输出的哈希值
- 后续构建:比较当前输入的哈希值与记录的哈希值
- 决策:
- 如果输入未变化且输出存在,跳过任务执行(UP-TO-DATE)
- 如果输入变化或输出不存在,重新执行任务
输入和输出声明
基本输入输出
groovytasks.register('processFiles') { // 输入文件 inputs.file('input.txt') inputs.dir('src/main/resources') // 输出目录 outputs.dir('build/processed') doLast { copy { from 'src/main/resources' into 'build/processed' } } }
多个输入输出
groovytasks.register('combineFiles') { inputs.files('file1.txt', 'file2.txt') inputs.files(fileTree('src') { include '**/*.java' }) outputs.file('build/combined.txt') outputs.dirs('build/classes', 'build/resources') doLast { // 处理文件 } }
属性输入
groovytasks.register('generateCode') { // 属性输入 inputs.property('version', project.version) inputs.property('target', 'production') outputs.dir('build/generated') doLast { // 根据属性生成代码 } }
内置任务的增量构建
Java 编译任务
groovy// Java 插件提供的编译任务自动支持增量构建 tasks.withType(JavaCompile).configureEach { options.incremental = true // 启用增量编译 }
测试任务
groovytest { // 测试任务自动支持增量构建 useJUnitPlatform() // 配置测试输入 testLogging { events 'passed', 'skipped', 'failed' } }
自定义增量任务
使用增量任务 API
groovyabstract class IncrementalTask extends DefaultTask { @Incremental @InputDirectory abstract DirectoryProperty getInputDirectory() @OutputDirectory abstract DirectoryProperty getOutputDirectory() @TaskAction void execute(InputChanges inputChanges) { if (inputChanges.incremental) { inputChanges.getFileChanges(inputDirectory).each { change -> if (change.fileType == FileType.FILE) { switch (change.changeType) { case ChangeType.ADDED: println "新增文件: ${change.file}" processFile(change.file) break case ChangeType.MODIFIED: println "修改文件: ${change.file}" processFile(change.file) break case ChangeType.REMOVED: println "删除文件: ${change.file}" removeOutput(change.file) break } } } } else { // 非增量构建,处理所有文件 println "执行全量构建" inputDirectory.get().asFile.eachFileRecurse { file -> if (file.isFile()) { processFile(file) } } } } void processFile(File file) { // 处理单个文件 } void removeOutput(File file) { // 删除对应的输出文件 } } // 注册任务 tasks.register('incrementalProcess', IncrementalTask) { inputDirectory.set(file('src/main/resources')) outputDirectory.set(file('build/processed')) }
增量构建配置
启用增量构建
groovy// gradle.properties org.gradle.caching=true org.gradle.parallel=true
任务级别配置
groovytasks.withType(JavaCompile).configureEach { options.incremental = true } tasks.withType(Test).configureEach { // 测试任务默认支持增量构建 }
构建缓存
本地构建缓存
groovy// gradle.properties org.gradle.caching=true
远程构建缓存
groovybuildCache { local { enabled = true } remote(HttpBuildCache) { url = 'https://cache.example.com/cache/' enabled = true push = true // 允许推送缓存 credentials { username = 'user' password = 'password' } } }
缓存任务输出
groovytasks.register('expensiveTask') { outputs.cacheIf { true } // 启用缓存 doLast { // 执行耗时操作 } }
增量构建最佳实践
1. 明确声明输入输出
groovytasks.register('customTask') { // 明确声明所有输入 inputs.files('config.xml', 'properties.json') inputs.property('env', System.getenv('ENV')) // 明确声明所有输出 outputs.dir('build/output') doLast { // 任务逻辑 } }
2. 使用文件树
groovytasks.register('processResources') { inputs.dir('src/main/resources').withPropertyName('resources') outputs.dir('build/resources').withPropertyName('output') doLast { copy { from inputs.dir into outputs.dir } } }
3. 避免不必要的输入
groovytasks.register('compileJava') { // 只包含必要的输入文件 inputs.files(fileTree('src/main/java') { include '**/*.java' exclude '**/generated/**' }) outputs.dir('build/classes') }
4. 使用属性输入
groovytasks.register('generateConfig') { inputs.property('database.url', project.findProperty('db.url')) inputs.property('database.username', project.findProperty('db.username')) outputs.file('build/config/application.properties') doLast { // 生成配置文件 } }
调试增量构建
查看任务状态
bash# 查看任务是否为 UP-TO-DATE ./gradlew build --info # 查看详细的增量构建信息 ./gradlew build --debug
强制重新执行任务
bash# 强制重新执行特定任务 ./gradlew clean build # 强制重新执行任务(不清理输出) ./gradlew build --rerun-tasks # 强制重新执行特定任务 ./gradlew :app:compileJava --rerun-tasks
分析构建性能
bash# 生成构建报告 ./gradlew build --scan # 查看任务执行时间 ./gradlew build --profile
常见问题和解决方案
1. 任务总是重新执行
问题:任务没有正确声明输入输出
解决方案:
groovytasks.register('problemTask') { // 确保所有输入都被声明 inputs.files('input.txt') inputs.property('version', project.version) // 确保所有输出都被声明 outputs.dir('build/output') }
2. 输出文件被外部修改
问题:输出文件被其他进程修改,导致缓存失效
解决方案:
groovytasks.register('sensitiveTask') { outputs.upToDateWhen { // 自定义 UP-TO-DATE 检查逻辑 true } }
3. 增量构建不生效
问题:任务不支持增量构建
解决方案:
groovy// 使用 @Incremental 注解 abstract class MyIncrementalTask extends DefaultTask { @Incremental @InputDirectory abstract DirectoryProperty getInputDir() @OutputDirectory abstract DirectoryProperty getOutputDir() }
性能优化建议
- 启用构建缓存:显著提高重复构建的速度
- 使用并行构建:利用多核 CPU 加速构建
- 优化任务依赖:减少不必要的任务执行
- 使用增量编译:Java 编译任务默认支持
- 避免在配置阶段执行耗时操作:将逻辑移到执行阶段
- 使用配置缓存:减少配置时间
- 合理使用增量任务 API:对于文件处理任务特别有效