Gradle supports multiple build variants and product flavors, which is particularly important for Android development and multi-environment deployment. Here's a detailed explanation of Gradle build variants:
Build Variant Concepts
Build Variants are a mechanism in Gradle for generating different versions of applications, allowing developers to generate multiple build outputs based on different configurations.
Android Build Variants
Basic Configuration
groovyandroid { // Build types buildTypes { debug { applicationIdSuffix ".debug" versionNameSuffix "-debug" debuggable true minifyEnabled false } release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } // Product flavors flavorDimensions "version", "environment" productFlavors { free { dimension "version" applicationId "com.example.app.free" versionName "1.0-free" } paid { dimension "version" applicationId "com.example.app.paid" versionName "1.0-paid" } dev { dimension "environment" buildConfigField "String", "API_URL", "\"https://dev.api.example.com\"" resValue "string", "app_name", "My App (Dev)" } staging { dimension "environment" buildConfigField "String", "API_URL", "\"https://staging.api.example.com\"" resValue "string", "app_name", "My App (Staging)" } prod { dimension "environment" buildConfigField "String", "API_URL", "\"https://api.example.com\"" resValue "string", "app_name", "My App" } } }
Build Variant Combinations
The above configuration will generate the following build variants:
- freeDevDebug
- freeDevRelease
- freeStagingDebug
- freeStagingRelease
- freeProdDebug
- freeProdRelease
- paidDevDebug
- paidDevRelease
- paidStagingDebug
- paidStagingRelease
- paidProdDebug
- paidProdRelease
Java/Kotlin Build Variants
Using Source Sets
groovy// build.gradle sourceSets { main { java { srcDirs 'src/main/java' } resources { srcDirs 'src/main/resources' } } // Custom source sets custom { java { srcDirs 'src/custom/java' } resources { srcDirs 'src/custom/resources' } } } // Configure source sets for specific build types android.sourceSets { debug { java.srcDirs 'src/debug/java' res.srcDirs 'src/debug/res' } release { java.srcDirs 'src/release/java' res.srcDirs 'src/release/res' } }
Using Task Variants
groovy// Create tasks for different environments tasks.register('buildDev') { group = 'build' description = 'Build for development environment' doLast { // Development environment build logic } } tasks.register('buildStaging') { group = 'build' description = 'Build for staging environment' doLast { // Staging environment build logic } } tasks.register('buildProd') { group = 'build' description = 'Build for production environment' doLast { // Production environment build logic } }
Multi-Environment Configuration
Using Configuration Files
groovy// build.gradle ext { environments = [ dev: [ apiUrl: 'https://dev.api.example.com', dbUrl: 'jdbc:mysql://dev-db.example.com:3306/mydb', enableDebug: true ], staging: [ apiUrl: 'https://staging.api.example.com', dbUrl: 'jdbc:mysql://staging-db.example.com:3306/mydb', enableDebug: false ], prod: [ apiUrl: 'https://api.example.com', dbUrl: 'jdbc:mysql://prod-db.example.com:3306/mydb', enableDebug: false ] ] } // Select configuration based on environment variable def environment = project.hasProperty('env') ? project.env : 'dev' def config = environments[environment] tasks.register('buildWithConfig') { doLast { println "Building for environment: ${environment}" println "API URL: ${config.apiUrl}" println "DB URL: ${config.dbUrl}" println "Debug enabled: ${config.enableDebug}" } }
Using Property Files
groovy// Create configuration task tasks.register('generateConfig') { def env = project.hasProperty('env') ? project.env : 'dev' def configFile = file("config/${env}.properties") inputs.file configFile outputs.dir('build/config') doLast { copy { from configFile into 'build/config' rename { 'application.properties' } } } } // Depend on configuration task tasks.named('processResources') { dependsOn 'generateConfig' from 'build/config' }
Dynamic Variant Generation
Generate Variants Based on Input
groovy// build.gradle def variants = ['variant1', 'variant2', 'variant3'] variants.each { variant -> tasks.register("build${variant.capitalize()}") { group = 'build' description = "Build ${variant}" doLast { println "Building ${variant}" // Variant-specific build logic } } } // Create aggregate task tasks.register('buildAllVariants') { group = 'build' description = 'Build all variants' dependsOn variants.collect { "build${it.capitalize()}" } }
Generate Variants Based on Configuration Files
groovy// variants.json [ { "name": "variant1", "version": "1.0.0", "features": ["feature1", "feature2"] }, { "name": "variant2", "version": "2.0.0", "features": ["feature1", "feature3"] } ] // build.gradle import groovy.json.JsonSlurper def variantsFile = file('variants.json') def variants = new JsonSlurper().parse(variantsFile) variants.each { variant -> tasks.register("build${variant.name.capitalize()}") { group = 'build' description = "Build ${variant.name} v${variant.version}" doLast { println "Building ${variant.name} version ${variant.version}" println "Features: ${variant.features.join(', ')}" } } }
Variant-Specific Dependencies
Configure Dependencies for Different Variants
groovy// Android project dependencies { implementation 'androidx.core:core-ktx:1.9.0' // Debug-specific dependencies debugImplementation 'com.facebook.stetho:stetho:1.6.0' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' // Release-specific dependencies releaseImplementation 'com.squareup.okhttp3:okhttp:4.10.0' // Product flavor-specific dependencies freeImplementation 'com.google.android.gms:play-services-ads:21.3.0' paidImplementation 'com.example:premium-features:1.0.0' // Build variant-specific dependencies freeDevImplementation 'com.example:dev-tools:1.0.0' paidProdImplementation 'com.example:prod-analytics:1.0.0' }
Java Project Variant Dependencies
groovyconfigurations { devImplementation stagingImplementation prodImplementation } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0' devImplementation 'org.springframework.boot:spring-boot-devtools:3.0.0' stagingImplementation 'org.springframework.boot:spring-boot-starter-actuator:3.0.0' prodImplementation 'org.springframework.boot:spring-boot-starter-security:3.0.0' } // Create tasks for different environments tasks.register('runDev', JavaExec) { group = 'application' classpath = sourceSets.main.runtimeClasspath + configurations.devImplementation mainClass = 'com.example.Application' args = ['--spring.profiles.active=dev'] } tasks.register('runStaging', JavaExec) { group = 'application' classpath = sourceSets.main.runtimeClasspath + configurations.stagingImplementation mainClass = 'com.example.Application' args = ['--spring.profiles.active=staging'] } tasks.register('runProd', JavaExec) { group = 'application' classpath = sourceSets.main.runtimeClasspath + configurations.prodImplementation mainClass = 'com.example.Application' args = ['--spring.profiles.active=prod'] }
Variant-Specific Resources
Android Resource Variants
shellapp/ ├── src/ │ ├── main/ │ │ ├── res/ │ │ │ ├── values/ │ │ │ │ └── strings.xml │ │ │ └── drawable/ │ │ │ └── icon.png │ ├── debug/ │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ ├── free/ │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── paid/ │ └── res/ │ └── values/ │ └── strings.xml
Java Resource Variants
groovysourceSets { main { resources { srcDirs 'src/main/resources' } } dev { resources { srcDirs 'src/dev/resources' } } staging { resources { srcDirs 'src/staging/resources' } } prod { resources { srcDirs 'src/prod/resources' } } } // Create JARs for different environments tasks.register('devJar', Jar) { group = 'build' from sourceSets.main.output from sourceSets.dev.output archiveFileName = 'app-dev.jar' } tasks.register('stagingJar', Jar) { group = 'build' from sourceSets.main.output from sourceSets.staging.output archiveFileName = 'app-staging.jar' } tasks.register('prodJar', Jar) { group = 'build' from sourceSets.main.output from sourceSets.prod.output archiveFileName = 'app-prod.jar' }
Best Practices
1. Plan Variant Dimensions Reasonably
groovy// Avoid too many variant combinations flavorDimensions "version" // Use only one dimension productFlavors { free { dimension "version" } paid { dimension "version" } }
2. Use Shared Configuration
groovy// Define shared configuration def commonConfig = { versionCode 1 versionName "1.0.0" minSdkVersion 21 targetSdkVersion 33 } android { defaultConfig commonConfig }
3. Use Variant Filters
groovyandroid { variantFilter { variant -> def names = variant.flavors*.name if (names.contains("paid") && names.contains("dev")) { variant.setIgnore(true) // Ignore paidDev variant } } }
4. Use Build Variant-Specific Tasks
groovyandroid.applicationVariants.all { variant -> def variantName = variant.name.capitalize() tasks.register("process${variantName}Resources") { doLast { println "Processing resources for ${variant.name}" } } }
5. Use Version Catalog for Variants
groovy// gradle/libs.versions.toml [versions] spring-boot = "3.0.0" [libraries] spring-boot-web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring-boot" } // Use in build.gradle dependencies { implementation libs.spring.boot.web }