Signing product flavors with gradle

AndroidGradleBuildbuild.gradleCode Signing

Android Problem Overview


I am tyring to migrate my projects to gradle. One of my projects has multiple product flavors and each one of them has to be signed with a different signingConfig in its release version. So this is what I tried so far:

buildscript {
    ...
}

apply plugin: 'android'

android {
    compileSdkVersion 17
    buildToolsVersion '17'

    signingConfigs {
        flavor1 {
            storeFile file("keystore")
            storePassword "secret"
            keyAlias "aliasForFlavor1"
            keyPassword "secretFlavor1"
        }

        flavor2 {
            storeFile file("keystore")
            storePassword "secret"
            keyAlias "aliasForFlavor2"
            keyPassword "secretFlavor2"
        }
    }

    productFlavors {
        flavor1 {
            signingConfig signingConfigs.flavor1
        }

        flavor1 {
            signingConfig signingConfigs.flavor2
        }
    }
}

dependencies {
    ...
}

When I run gradle build I get a groovy.lang.MissingFieldException and the following error message:

No such field: signingConfigs for class: com.android.build.gradle.internal.dsl.GroupableProductFlavorFactory

So I assume the productFlavors.* part of the Gradle script is not the right place to put code signing configurations.

Android Solutions


Solution 1 - Android

You can declare signing config for each flavor in buildType. Here is my gradle file for release signing flavors with different keystores.

android {
  signingConfigs {
    configFirst {
        keyAlias 'alias'
        keyPassword 'password'
        storeFile file('first.keystore')
        storePassword 'password'
    }

    configSecond {
        keyAlias 'alias'
        keyPassword 'password'
        storeFile file('second.keystore')
        storePassword 'password'
    }
  }

  compileSdkVersion 23
  buildToolsVersion "23.0.2"
  defaultConfig {
        minSdkVersion 14
        targetSdkVersion 23
  }

  productFlavors{
    flavor1 {
        applicationId "com.test.firstapp"
    }

    flavor2 {
        applicationId "com.test.secondapp"
    }
  }

  buildTypes {
    release {
        productFlavors.flavor1.signingConfig signingConfigs.configFirst
        productFlavors.flavor2.signingConfig signingConfigs.configSecond           
        
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'),
                'proguard-rules.pro'

    }
  }
}

buildTypes block should be placed after productFlavors block, I mean order is important.

Solution 2 - Android

Per the user guide, signingConfigs for flavors are supported.

The problem here has to do with the scope of the signingConfigs object. I just assigned it to a variable inside the productFlavors block, but outside the flavor1 flavor block to fix the issue:

productFlavors {
    def flavor1SigningVariable = signingConfigs.flavor1

    flavor1 {
        ...
        signingConfig flavor1SigningVariable
        ...
    }

Solution 3 - Android

The gradle plugin for android only supports signing per build type, not per flavor. The reason for this is that any given variant (build type + flavors) can only be signed by one key, but can be a combination of several flavor groups. For example your flavor groups could be cpu (x86/arm) and version (free/paid), that's four different variants right there.

The solution you're looking for is to create separate build types for your different release versions. For example, your build types might be debug, release, release-beta, like this:

...

android {

    ...

    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }

        release {
            signingConfig signingConfigs.release
        }

        release-beta {
            initWith release
            signingConfig signingConfigs.release-beta
        }
    }
}

The initWith above just tells gradle that release-beta should be a copy of the release build type, only signed with a different key.

Solution 4 - Android

Maybe another interesting solution with dynamic flavor signing configs and other advantages

  • Im fine with defining app id and app name of flavors in gradle (it is clear and just 2 lines for each flavor) but I do not want to define separate signing configs (gradle file would be too long when adding flavors)
  • I also do not want to have sensitive signing information placed in gradle because of commiting it
  • Bonus advantage is that debug build has another app id and app name.

.gitignore

...
/keystore.properties

.keystore.properties

storeFile=../mystore.jks
storePassword=...

keyAliasFlavor1=...
keyPasswordFlavor1=...

keyAliasFlavor2=...
keyPasswordFlavor2=...

app/build.gradle

def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(rootProject.file('keystore.properties')))

android {
    ...

    buildTypes {
        debug {
            ...
            manifestPlaceholders = [appNameSuffix: " Dev"]
            applicationIdSuffix ".dev"
        }
        release {
            ...
            manifestPlaceholders = [appNameSuffix: ""]
            productFlavors.all { flavor ->
                flavor.signingConfig = android.signingConfigs.create("${flavor.name}")
                flavor.signingConfig.storeFile = rootProject.file(keystoreProperties["storeFile"])
                flavor.signingConfig.storePassword = keystoreProperties["storePassword"]
                flavor.signingConfig.keyAlias = keystoreProperties["keyAlias${flavor.name}"]
                flavor.signingConfig.keyPassword = keystoreProperties["keyPassword${flavor.name}"]
            }
        }
    }

    productFlavors {
        Flavor1 {
            applicationId "..."
            manifestPlaceholders = [appNameBase: "MyApp 1"]
        }
        Flavor2 {
            applicationId "..."
            manifestPlaceholders = [appNameBase: "MyApp 2"]
        }
        // ... and many other flavors without taking care about signing configs
        // (just add two lines into keystore.properties for each new flavor)
    }
}

app/src/main/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    ...
    <application android:label="${appNameBase}${appNameSuffix}" ...>
        ...
    </application>
</manifest>

Solution 5 - Android

Split signing configs between Gms and Hms builds

Just for future ref if anyone has to split their signing configs between Gms and Hms builds.

This adds on the the answer listed here:
Maybe another interesting solution with dynamic flavor signing configs and other advantages


build.gradle

Option 1

gradle.startParameter.getTaskNames().each()

   def keystorePropertiesFile = rootProject.file("keystore.properties")
   def keystoreProperties = new Properties()
   keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

   signingConfigs {
        release {
            storeFile file(keystoreProperties["RELEASE_STORE_FILE"])
            storePassword keystoreProperties["RELEASE_STORE_PASSWORD"]
            keyAlias keystoreProperties["RELEASE_KEY_ALIAS"]
            keyPassword keystoreProperties["RELEASE_KEY_PASSWORD"]
        }
        releaseHms {
            storeFile file(keystoreProperties["RELEASE_HMS_STORE_FILE"])
            storePassword keystoreProperties["RELEASE_STORE_PASSWORD"]
            keyAlias keystoreProperties["RELEASE_KEY_ALIAS"]
            keyPassword keystoreProperties["RELEASE_KEY_PASSWORD"]
        }
        debug {
            // use default debug key to sign
        }
    }
    
   buildTypes {
        release {
            ...

            gradle.startParameter.getTaskNames().each { task ->
                if (task.toLowerCase().contains("gms")) {
                    signingConfig signingConfigs.release
                }
                if (task.toLowerCase().contains("hms") {
                    signingConfig signingConfigs.releaseHms
                }
            }
        }

        debug {
            ...

            gradle.startParameter.getTaskNames().each { task ->
                if (task.toLowerCase().contains("gms")) {
                    signingConfig signingConfigs.debug
                }
                if (task.toLowerCase().contains("hms") {
                    signingConfig signingConfigs.releaseHms
                }
            }

        flavorDimensions "serviceplatform"
        productFlavors {
        hms {
            dimension "serviceplatform"
            applicationIdSuffix ".huawei"
            versionNameSuffix "-huawei"
        }
        gms {
            dimension "serviceplatform"
            applicationIdSuffix ".android"
        }
     }

   sourceSets {
        main {
            res.srcDirs = [                    "src/main/res",                    "src/main/res/layout/toolbar",                    "src/main/res/layout/fragment",                    "src/main/res/layout/activity"            ]
        }

        gms {
            java.srcDir("src/gms/java")
        }
        hms {
            java.srcDir("src/hms/java")
        }
    }

Option 2

productFlavors.gms.signingConfig

Ensure your flavorDimensions is before buildTypes

flavorDimensions "serviceplatform"
     productFlavors {
        hms {
            ...
        }
        gms {
            ...
        }
    }

buildTypes {
        release {
            ...
            productFlavors.gms.signingConfig signingConfigs.release
            productFlavors.hms.signingConfig signingConfigs.releaseHms
        }

        debug {
            ...
            productFlavors.gms.signingConfig signingConfigs.debug
            productFlavors.hms.signingConfig signingConfigs.releaseHms
        }
    }

Solution 6 - Android

This is the Kotlin DSL equivalent of ashakirov's answer:

// See https://stackoverflow.com/q/60474010
fun getLocalProperty(key: String) = gradleLocalProperties(rootDir).getProperty(key)
fun String?.toFile() = file(this!!)
// Could also use System.getenv("VARIABLE_NAME") to get each variable individually
val environment: Map<String, String> = System.getenv()

android {
    signingConfigs {
        create("MyFirstConfig") {
            keyAlias = getLocalProperty("signing.keyAlias") ?: environment["SIGNING_KEY_ALIAS"]
            storeFile = (getLocalProperty("signing.storeFile") ?: environment["SIGNING_STORE_FILE"]).toFile()
            keyPassword = getLocalProperty("signing.keyPassword") ?: environment["SIGNING_KEY_PASSWORD"]
            storePassword = getLocalProperty("signing.storePassword") ?: environment["SIGNING_STORE_PASSWORD"]
            enableV1Signing = true
            enableV2Signing = true
        }
        create("MySecondConfig") {
            keyAlias = getLocalProperty("signing.keyAlias2") ?: environment["SIGNING_KEY_ALIAS2"]
            storeFile = (getLocalProperty("signing.storeFile2") ?: environment["SIGNING_STORE_FILE2"]).toFile()
            keyPassword = getLocalProperty("signing.keyPassword2") ?: environment["SIGNING_KEY_PASSWORD2"]
            storePassword = getLocalProperty("signing.storePassword2") ?: environment["SIGNING_STORE_PASSWORD2"]
            enableV1Signing = true
            enableV2Signing = true
        }
    }

    productFlavors {
        create("flavor1") {
            // ...
        }
        create("flavor2") {
            // ...
        }
    }

    buildTypes {
        getByName("release") { // OR simply  release {  in newer versions of Android Gradle Plugin (AGP)
            productFlavors["flavor1"].signingConfig = signingConfigs["MyFirstConfig"]
            productFlavors["flavor2"].signingConfig = signingConfigs["MySecondConfig"]
            // OR alternative notation
            // productFlavors {
            //     getByName("flavor1") {
            //         signingConfig = signingConfigs["MyFirstConfig"]
            //     }
            //     getByName("flavor2") {
            //         signingConfig = signingConfigs["MySecondConfig"]
            //     }
            // }
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser2473760View Question on Stackoverflow
Solution 1 - AndroidashakirovView Answer on Stackoverflow
Solution 2 - Androidc2knapsView Answer on Stackoverflow
Solution 3 - AndroidMarcus Forsell StahreView Answer on Stackoverflow
Solution 4 - AndroidmikepView Answer on Stackoverflow
Solution 5 - AndroidFrancoisView Answer on Stackoverflow
Solution 6 - AndroidMahozadView Answer on Stackoverflow