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