5月27日 23:19

How does Gradle's incremental build work? How to optimize build performance?

Gradle's incremental build is one of its core performance optimization features. It significantly improves build speed by only processing changed files. Here's a detailed explanation of Gradle incremental builds:

Incremental Build Principles

Incremental builds are based on task input and output declarations. Gradle tracks changes to these files and only re-executes tasks when input files have changed.

Workflow

  1. First build: Execute task and record hash values of inputs and outputs
  2. Subsequent builds: Compare current input hash values with recorded hash values
  3. Decision:
    • If inputs unchanged and outputs exist, skip task execution (UP-TO-DATE)
    • If inputs changed or outputs don't exist, re-execute task

Input and Output Declarations

Basic Inputs and Outputs

groovy
tasks.register('processFiles') { // Input files inputs.file('input.txt') inputs.dir('src/main/resources') // Output directory outputs.dir('build/processed') doLast { copy { from 'src/main/resources' into 'build/processed' } } }

Multiple Inputs and Outputs

groovy
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 { // Process files } }

Property Inputs

groovy
tasks.register('generateCode') { // Property inputs inputs.property('version', project.version) inputs.property('target', 'production') outputs.dir('build/generated') doLast { // Generate code based on properties } }

Incremental Builds for Built-in Tasks

Java Compilation Tasks

groovy
// Compilation tasks provided by Java plugin automatically support incremental builds tasks.withType(JavaCompile).configureEach { options.incremental = true // Enable incremental compilation }

Test Tasks

groovy
test { // Test tasks automatically support incremental builds useJUnitPlatform() // Configure test inputs testLogging { events 'passed', 'skipped', 'failed' } }

Custom Incremental Tasks

Using Incremental Task API

groovy
abstract 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 "Added file: ${change.file}" processFile(change.file) break case ChangeType.MODIFIED: println "Modified file: ${change.file}" processFile(change.file) break case ChangeType.REMOVED: println "Removed file: ${change.file}" removeOutput(change.file) break } } } } else { // Non-incremental build, process all files println "Executing full build" inputDirectory.get().asFile.eachFileRecurse { file -> if (file.isFile()) { processFile(file) } } } } void processFile(File file) { // Process single file } void removeOutput(File file) { // Remove corresponding output file } } // Register task tasks.register('incrementalProcess', IncrementalTask) { inputDirectory.set(file('src/main/resources')) outputDirectory.set(file('build/processed')) }

Incremental Build Configuration

Enable Incremental Build

groovy
// gradle.properties org.gradle.caching=true org.gradle.parallel=true

Task-level Configuration

groovy
tasks.withType(JavaCompile).configureEach { options.incremental = true } tasks.withType(Test).configureEach { // Test tasks support incremental builds by default }

Build Cache

Local Build Cache

groovy
// gradle.properties org.gradle.caching=true

Remote Build Cache

groovy
buildCache { local { enabled = true } remote(HttpBuildCache) { url = 'https://cache.example.com/cache/' enabled = true push = true // Allow pushing to cache credentials { username = 'user' password = 'password' } } }

Cache Task Outputs

groovy
tasks.register('expensiveTask') { outputs.cacheIf { true } // Enable caching doLast { // Execute expensive operations } }

Incremental Build Best Practices

1. Clearly Declare Inputs and Outputs

groovy
tasks.register('customTask') { // Clearly declare all inputs inputs.files('config.xml', 'properties.json') inputs.property('env', System.getenv('ENV')) // Clearly declare all outputs outputs.dir('build/output') doLast { // Task logic } }

2. Use File Trees

groovy
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. Avoid Unnecessary Inputs

groovy
tasks.register('compileJava') { // Only include necessary input files inputs.files(fileTree('src/main/java') { include '**/*.java' exclude '**/generated/**' }) outputs.dir('build/classes') }

4. Use Property Inputs

groovy
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 { // Generate configuration file } }

Debugging Incremental Builds

View Task Status

bash
# View if task is UP-TO-DATE ./gradlew build --info # View detailed incremental build information ./gradlew build --debug

Force Re-execution of Tasks

bash
# Force re-execution of specific task ./gradlew clean build # Force re-execution of tasks (without cleaning output) ./gradlew build --rerun-tasks # Force re-execution of specific task ./gradlew :app:compileJava --rerun-tasks

Analyze Build Performance

bash
# Generate build report ./gradlew build --scan # View task execution time ./gradlew build --profile

Common Issues and Solutions

1. Task Always Re-executes

Problem: Task doesn't correctly declare inputs and outputs

Solution:

groovy
tasks.register('problemTask') { // Ensure all inputs are declared inputs.files('input.txt') inputs.property('version', project.version) // Ensure all outputs are declared outputs.dir('build/output') }

2. Output Files Modified Externally

Problem: Output files modified by other processes, causing cache invalidation

Solution:

groovy
tasks.register('sensitiveTask') { outputs.upToDateWhen { // Custom UP-TO-DATE check logic true } }

3. Incremental Build Not Working

Problem: Task doesn't support incremental builds

Solution:

groovy
// Use @Incremental annotation abstract class MyIncrementalTask extends DefaultTask { @Incremental @InputDirectory abstract DirectoryProperty getInputDir() @OutputDirectory abstract DirectoryProperty getOutputDir() }

Performance Optimization Recommendations

  1. Enable build cache: Significantly improve speed of repeated builds
  2. Use parallel builds: Leverage multi-core CPUs to accelerate builds
  3. Optimize task dependencies: Reduce unnecessary task executions
  4. Use incremental compilation: Java compilation tasks support by default
  5. Avoid time-consuming operations in configuration phase: Move logic to execution phase
  6. Use configuration cache: Reduce configuration time
  7. Reasonably use incremental task API: Particularly effective for file processing tasks
标签:Gradle