乐闻世界logo
搜索文章和话题

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

2月21日 18:10

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