Is there a way to configure separate serviceCredentialsFile for each Android build variant with Firebase App Distribution?
Asked Answered
C

2

7

We have several Firebase projects which share the same code base via build types and flavors. We aim to use the app distribution via Gradle and authenticate using service account credentials.

In the docs, it is shown that firebaseAppDistribution block can be used to configure the parameters and the service credential file path is one of them. Since each variant is a Firebase project and each project has its own service credentials, as far as I can understand we need to point to separate service credential file paths in the Gradle configuration.

I've tried to update the file path with a gradle task depending on the variant but couldn't make it work. The current build file looks like:

...

apply plugin: 'com.google.firebase.appdistribution'

class StringExtension {
  String value

  StringExtension(String value) {
    this.value = value
  }

  public void setValue(String value) {
    this.value = value
  }

  public String getValue() {
    return value
  }
}

android {

  ...

  productFlavors.whenObjectAdded {
    flavor -> flavor.extensions.create("service_key_prefix", StringExtension, '')
  }

  productFlavors {

    flavor1 {
      ...
      service_key_prefix.value = "flavor1"
    }

    flavor2 {
      ...
      service_key_prefix.value = "flavor2"
    }
  }

  buildTypes {

    debug {
      firebaseAppDistribution {
        releaseNotesFile = file("internal_release_notes.txt").path
        groupsFile = file("group_aliases_debug_fb.txt").path
      }
    }

    release {
      firebaseAppDistribution {
        releaseNotesFile = file("release_notes.txt").path
        groupsFile = file("group_aliases_prod_fb.txt").path
      }
    }
  }
}

...

android.applicationVariants.all { variant ->

  task("firebaseCredentials${variant.name.capitalize()}", overwrite: true) {
    variant.productFlavors.each { flavor ->

      doLast {
        firebaseAppDistribution {

          def serviceKeyFile = file(
              "../${flavor.service_key_prefix.value}-${variant.buildType.name}-service-key.json")
          if (serviceKeyFile != null) {
            serviceCredentialsFile = serviceKeyFile.path
          }
        }
      }
    }
  }
}

android.applicationVariants.all { variant ->
  def distTask = tasks.named("appDistributionUpload${variant.name.capitalize()}")
  def configTask = tasks.named("firebaseCredentials${variant.name.capitalize()}")
  distTask.configure {
    dependsOn(configTask)
  }
}

apply plugin: 'com.google.gms.google-services'

The tasks seem to run correctly but I guess the file path is not updated because it still gives the following error when I run appDistributionUpload :

Could not find credentials. To authenticate, you have a few options:

  1. Set the serviceCredentialsFile property in your gradle plugin

  2. Set a refresh token with the FIREBASE_TOKEN environment variable

  3. Log in with the Firebase CLI

  4. Set service credentials with the GOOGLE_APPLICATION_CREDENTIALS environment variable

Any ideas on how to achieve such distribution configuration?

Cellaret answered 2/1, 2020 at 9:10 Comment(0)
C
4

After contacting the support team, I've learned that this configuration is not available yet out of the box and exists as a feature request as of now.

Here is the workaround from the Firebase support team, which modifies the related environment variable in memory at the beginning of each upload task:

android {

  applicationVariants.all { variant ->

    final uploadTaskName = "appDistributionUpload${variant.name.capitalize()}"
    final uploadTask = project.tasks.findByName(uploadTaskName)

    if (uploadTask != null) {
      uploadTask.doFirst {
        resetCredentialsCache()
        final value = "$projectDir/src/${variant.name}/app-distribution-key.json"
        setEnvInMemory('GOOGLE_APPLICATION_CREDENTIALS', value)
      }
    }
  }
}

private static void setEnvInMemory(String name, String val) {
  // There is no way to dynamically set environment params, but we can update it in memory
  final env = System.getenv()
  final field = env.getClass().getDeclaredField("m")
  field.setAccessible(true)
  field.get(env).put(name, val)
}

private static void resetCredentialsCache() {
  // Google caches credentials provided by environment param, we are going to reset it
  final providerClass = Class.forName(
      'com.google.firebase.appdistribution.buildtools.reloc.com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider')
  final providerCtor = providerClass.getDeclaredConstructor()
  providerCtor.setAccessible(true)
  final provider = providerCtor.newInstance()

  final credsClass = Class.forName(
      'com.google.firebase.appdistribution.buildtools.reloc.com.google.api.client.googleapis.auth.oauth2.GoogleCredential')
  final field = credsClass.getDeclaredField('defaultCredentialProvider')
  field.setAccessible(true)
  field.set(null, provider)
}

EDIT: The above solution works only for app distribution plugin version 1.3.1.

I didn't contact the support further and used GOOGLE_APPLICATION_CREDENTIALS environment variable on Jenkins CI, as explained here

Cellaret answered 18/2, 2020 at 19:18 Comment(4)
This seems broken, cannot find the class. I assume Google changed the source in the 2 years that have passed com.google.firebase.appdistribution.buildtools.reloc.com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider I'm using com.google.firebase:firebase-appdistribution-gradle:1.4.1 Hmm this package does not contain the buildtools package. I wonder where they are...Attack
I've been using this for few months now, not two years. I've just checked my setup. I am using "com.google.firebase:firebase-appdistribution-gradle:1.3.1". That might be the difference. I will check it with the latest release of the library and update the answer if necessary. It might worth checking the change log of the library to see whether they have changed anything with those classes or they have released a feature for the problem.Cellaret
Thx Emre, I now tried the 1.3.1, and then it could find the build-tools class. However it still couldn't find my credentials, for some reason, though the path it prints is valid (though with '/' on on win). I'll just go with '1.4.1' version and run use a gradle-property to select credentials, and then run one release at a time.Attack
@Attack I've checked it recently and confirmed that it doesn't work after the plugin version 1.3.1. I've edited the answer, if it helps you further. Couldn't find a way to tackle it with Gradle on my own.Cellaret
K
1

Is there really a need to define different serivceAccount files for each Variant? Or is it just enough to have seperate files for each flavor/buildType?

Beacuse the buildType and productFlavors are ReadOnly when accessed via android.applicationVariants, which will be executed after evaluation Phase. But you can directly set the value for BuildType or Flavor while in configuration phase of gradle.

Currently this is the only way i got it working for me.

BuildTypes:

android.buildTypes.all{
    File serviceAccountFile = file("/path/to/file-${it.name}.json")
    if (serviceAccountFile.exists()) {
        it.firebaseAppDistribution {
            serviceCredentialsFile = serviceAccountFile.path
        }
    }
}

Flavors:

android.productFlavors.all{
    File serviceAccountFile = file("/path/to/file-${it.name}.json")
    if (serviceAccountFile.exists()) {
        it.firebaseAppDistribution {
            serviceCredentialsFile = serviceAccountFile.path
        }
    }
}
Kilah answered 24/1, 2020 at 7:47 Comment(7)
Well, each variant is a project in Firebase and each project must have separate credential files, no? At least it was my understanding while reading the documents or using the console.Cellaret
there are different solutions: 1. Everything in one Project (go to project settings and add every variant as an app) 2. Add one Service account to all other cloud console projects and just use this one account file 3. One project per flavor and define the credentials in the flavor configuration of your buildscript blockKilah
I will see if the second option is viable for us.Cellaret
Seems that second option is not possible at all. Couldn't find a way via the console and also contacted Firebase support about it. Every project has different service account.Cellaret
you can include the email address of the one service account into the cloud console iam of all other projects and need to set the role Firebase Quality Admin. i just did this in a few projects and it worksKilah
im curious, why do you need a seperate service account per build variant? isnt it enough to have a different service account per flavor and add all the build types as different apps to the same firebase project?Kilah
Thanks, I actually couldn't share service accounts and even asked Firebase support whether it is possible or not. Their information aligned with my experience. About the need; Each project is a different application basically with different configuration needs. As far as I know, applications under the same project share same resources, which prevents me from putting the applications under the same project per flavor.Cellaret

© 2022 - 2025 — McMap. All rights reserved.