Creating 2 new tasks for submitting build to Crashalytics Beta

Hi there,

I’m trying to automate my Android project distribution and I’m using Crashalytics Beta for that. From their support page it is a pretty easy task:

f you want to distribute your app via Gradle, make sure you’re on version 1.11.4 or higher of the Gradle tool and run the following command:

gradle assembleRelease crashlyticsUploadDistributionRelease

Groups:

ext.betaDistributionGroupAliases="my-best-testers"

So basically we need 2 similar tasks (e.g. buildAndDistributeToTeam and buildAndDistributeToCustomer) where we’ll set different group for crashlytics (groups are created on their server). In each task we need to perform:

  1. clean
  2. assembleRelease
  3. set group either to Developer-Distribution or Customer-Distribution
  4. run crashlyticsUploadDistributionRelease

I want to do it using different tasks because then I can select task on http://ship.io CI. I have 2 jobs there and each job will build different task.

I hope my setup is clear. So what we do:

  1. we have 2 custom build types:
buildTypes {
        customerRelease {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main

            ext.betaDistributionGroupAliases = "Customer-Distribution"
        }

        developerRelease {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main

            ext.betaDistributionGroupAliases = "Developer-Distribution"
        }
    }

and then we have:

task buildAndDistributeToCustomer(dependsOn: clean, assembleRelease, crashlyticsUploadDistributionRelease) << {
    println "Built and Sent to customer"
}

task buildAndDistributeToTeam(dependsOn: clean, assembleRelease, crashlyticsUploadDistributionRelease) << {
    println "Built and Sent to team"
}

tasks.whenTaskAdded { task ->
    if (task.name == 'buildAndDistributeToCustomer') {
        task.dependsOn customerRelease
    }
    if (task.name == 'buildAndDistributeToTeam') {
        task.dependsOn developerRelease
    }
}

If I’m trying to build one of my custom tasks I have:

FAILURE: Build failed with an exception.

* Where:
Build file '/b/d20150703-570-a606v4/my-project-android/app/build.gradle' line: 80

* What went wrong:
A problem occurred evaluating project ':app'.
> Could not find property 'crashlyticsUploadDistributionRelease' on project ':app'.

Line 80 is the line where I define task buildAndDistributeToCustomer

Do you have any ideas how to make it work?

Without your custom tasks, are you able to run the crashlyticsUploadDistributionRelease as mentioned in the instructions?

It’s clear what you’re trying to do, but it’s not clear if you applied the crashlytics plugin and got that working first. If you don’t have the plugin applied, you don’t have the crashlyticsUploadDistributionRelease task, which would be a pretty good reason it Could not find property 'crashlyticsUploadDistributionRelease' on project ':app'.

On top of @jjustinic answer, if you have the plugin applied (which is probably the case), the only reason for this failure is that the task ‘crashlyticsUploadDistributionRelease’ is not created when configuring your custom tasks.

This can come from several reasons

  • you apply the plugin after this task declaration in your build.gradle file (which is highly unlikely :slight_smile: , but I have to say it anyway)
  • crashlytics plugin uses the new module based configuration, and a Rule Source (@Mutate, @Finalize) on the task container, in order to create their ‘crashlyticsUploadDistributionRelease’ task. Rules are applied after build script parsing.
    I don’t think the crashlytics source code can be found online to check, but if this is the case, you have to play with RuleSource (which is a good thing to start doing anyway :slight_smile: )

You can create a @Finalize rule on the task container, to create your custom tasks, sth like

@Finalize
void createCustomTasks(Taskcontainer tasks){
Task task = tasks.create(‘buildAndDistributeToCustomer’)
task.dependsOn(clean, assembleRelease, crashlyticsUploadDistributionRelease)
task.doLast{
println “Built and Sent to customer”
}
}

This @Finalize rule is guaranteed to being called last on the task container, and therefore the crashlytics tasks will be there already.
This has the drawback on finalizing the task container just to add a custom task, which will not be recommended in the future.

Otherwise you can try calling dependsOn on string rather than task object
task buildAndDistributeToCustomer(dependsOn: clean, assembleRelease, ‘crashlyticsUploadDistributionRelease’) << {
println “Built and Sent to customer”
}
This will not fail the build if called before the task exists, and normally would still create the tasks wiring.

Note that in a future release, Gradle will allow to use RuleSource with better constructs, and you will be able to express things such as :
my buildAndDistributeToCustomer custom task depends on the ‘crashlyticsUploadDistributionRelease’ task
without Finalizing the task container (which is a rather drastic solution to this small problem)

Thanks a lot for replies.

  1. I do apply crashalitycs (Fabric) plugin
  2. I’ve tried to place crashlyticsUploadDistributionRelease inside dependsOn: in task declaration before posting here
  3. After @Francois_Guillot reply I’ve tried to put in in single quotes inside dependsOn
  4. I’ve tried @Finalize solution but it seems my gradle version doesn’t support it. It says Finalize class not found

If I delete custom tasks I can successfully run crashlyticsUploadDistributionRelease from the command line using gradle assembleRelease crashlyticsUploadDistributionRelease. But because I don’t have control on the command line on ship.io I need to do that using custom tasks.

I decided to post full build.gradle (sorry that didn’t do that in the first place). It looks like that right now:

buildscript {
    repositories {
        jcenter()
        maven { url 'https://maven.fabric.io/public' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.0.+'
        classpath 'io.fabric.tools:gradle:1.+'
    }
}

apply plugin: 'com.android.application'
apply plugin: 'io.fabric'

repositories {
    jcenter()
    maven { url 'https://maven.fabric.io/public' }
}

android {
    compileSdkVersion 22
    buildToolsVersion "21.1.2"

    signingConfigs {
        main {
            keyAlias 'mykeystore'
            keyPassword 'mykeystore'
            storeFile file('mykeystore.jks')
            storePassword 'mykeystore'
        }
    }

    defaultConfig {
        applicationId "com.mycompany.myapp"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_7
            targetCompatibility JavaVersion.VERSION_1_7
        }
    }

    buildTypes {
        customerRelease {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main

            ext.betaDistributionGroupAliases = "Customer-Distribution"
        }
        developerRelease {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main

            ext.betaDistributionGroupAliases = "Developer-Distribution"
        }
    }

    lintOptions {
        disable 'InvalidPackage'
    }
}


dependencies {
    compile('com.crashlytics.sdk.android:crashlytics:2.4.0@aar') {
        transitive = true;
    }
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:recyclerview-v7:22.2.0'
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.squareup.okhttp:okhttp:2.4.0'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.makeramen:dragsortadapter:1.3.0'
    compile 'com.jakewharton:butterknife:6.1.0'
    compile 'uk.co.chrisjenx:calligraphy:2.1.0'
    compile 'com.github.rey5137:material:1.1.1'
}

tasks.whenTaskAdded { task ->
    if (task.name == 'buildAndDistributeToCustomer') {
        task.dependsOn customerRelease
    }
    if (task.name == 'buildAndDistributeToTeam') {
        task.dependsOn developerRelease
    }
}

task buildAndDistributeToCustomer(dependsOn: clean, assembleRelease, crashlyticsUploadDistributionRelease) << {
    println "Built and Sent to customer"
}

task buildAndDistributeToTeam(dependsOn: clean, assembleRelease, crashlyticsUploadDistributionRelease) << {
    println "Built and Sent to team"
}

Maybe crashlytics need to wait for the parsing of your build.gradle, to know your Android buildTypes and flavors, to create the corresponding task.
This actually makes sense.
In the new rule based way, this will probably be done with a RuleSource taking the android extension as rule input (whenever this will be possible with the new mechanism)

For now, they might use a project.afterEvaluate{ } block, to ensure the android {} extensino block is fully completed before creating their tasks.

So, you can try

afterEvaluate {
 buildAndDistributeToCustomer.dependsOn crashlyticsUploadDistributionRelease
buildAndDistributeToTeam.dependsOn crashlyticsUploadDistributionRelease
}

If this doesn’t work, I’m out of idea. You could ask the crashlytics guys how and when they create their tasks, and report this info here. This might give us more insight.

Thanks.
I’ve added this section, now end of my file looks like:

task buildAndDistributeToCustomer(dependsOn: clean, assembleRelease) << {
    println "Built and Sent to customer"
}

task buildAndDistributeToTeam(dependsOn: clean, assembleRelease) << {
    println "Built and Sent to team"
}

tasks.whenTaskAdded { task ->
    if (task.name == 'buildAndDistributeToCustomer') {
        task.dependsOn customerRelease
    }
    if (task.name == 'buildAndDistributeToTeam') {
        task.dependsOn developerRelease
    }
}

afterEvaluate {
    buildAndDistributeToCustomer.dependsOn crashlyticsUploadDistributionRelease
    buildAndDistributeToTeam.dependsOn crashlyticsUploadDistributionRelease
}

And I have Gradle error:

Error:(86, 0) Gradle DSL method not found: 'buildAndDistributeToCustomer()'
Possible causes:
 - The project 'MyProject' may be using a version of Gradle that does not contain the method.
 - The build file may be missing a Gradle plugin.

line 86 is the first line of listing I posted in this comment where I declare task buildAndDistributeToCustomer

task buildAndDistributeToCustomer(dependsOn: [clean, assembleRelease]) << {
    println "Built and Sent to customer"
}

or

task buildAndDistributeToCustomer {
  dependsOn clean, assembleRelease
  doLast {
    println "Built and Sent to customer"
  }
}

Thanks a lot! We made a huge progress!
With few more modifications I got it working (it builds successfully on the remote build server). Current build.grade at the end of the post.

However there are 2 issues left:

  • it builds all 4 targets: debug, release, customerRelease, developerRelease
  • nothing actually is submitted to the Crashlytics Beta.

It seems like buildTypes (customerRelease and developerRelease) are not applied to the corresponding tasks. There are 2 things that makes me think like that

  • app isn’t submitted (probably group betaDistributionGroup isn’t set) when I do remote build
  • app build fails locally on app submission to Crashalytics saying that app isn’t signed. So I think that string signingConfig signingConfigs.main is never called. I can build it remotely because there server signs apk itself with keys generated on the server.
buildscript {
    repositories {
        jcenter()
        maven { url 'https://maven.fabric.io/public' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
        classpath 'io.fabric.tools:gradle:1.+'
    }
}
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'

repositories {
    jcenter()
    maven { url 'https://maven.fabric.io/public' }
}


android {
    compileSdkVersion 22
    buildToolsVersion "21.1.2"

    signingConfigs {
        main {
            keyAlias 'mykeystore'
            keyPassword 'mykeystore'
            storeFile file('mykeystore.jks')
            storePassword 'mykeystore'
        }
    }

    defaultConfig {
        applicationId "com.mycompany.myapp"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_7
            targetCompatibility JavaVersion.VERSION_1_7
        }
    }

    buildTypes {
        customerRelease {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main

            ext.betaDistributionGroupAliases = "customer-distribution"
        }

        developerRelease {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.main

            ext.betaDistributionGroupAliases = "developer-distribution"
        }
    }

    lintOptions {
        disable 'InvalidPackage'
    }
}


dependencies {
    compile('com.crashlytics.sdk.android:crashlytics:2.4.0@aar') {
        transitive = true;
    }
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:recyclerview-v7:22.2.0'
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.squareup.okhttp:okhttp:2.4.0'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.makeramen:dragsortadapter:1.3.0'
    compile 'com.jakewharton:butterknife:7.0.1'
    compile 'uk.co.chrisjenx:calligraphy:2.1.0'
    compile 'com.github.rey5137:material:1.1.1'
    compile 'com.google.guava:guava:18.0'
}

task buildAndDistributeToCustomer(dependsOn: [assembleRelease]) << {
    println "Built and Sent to customer"
}

task buildAndDistributeToTeam(dependsOn: [assembleRelease]) << {
    println "Built and Sent to team"
}

tasks.whenTaskAdded { task ->
    if (task.name == 'buildAndDistributeToCustomer') {
        task.dependsOn customerRelease
    }
    if (task.name == 'buildAndDistributeToTeam') {
        task.dependsOn developerRelease
    }
}

afterEvaluate {
    buildAndDistributeToCustomer.dependsOn crashlyticsUploadDistributionRelease
    buildAndDistributeToTeam.dependsOn crashlyticsUploadDistributionRelease
}

I’m not familiar with the android plugin, but if you want to build only a single buildType, you should depend on assembleBuildType task (see here), e.g. assembleCustomerRelease, and assembleDeveloperRelease
You cannot depend on a build type, this does not make sense. A task depends on another task.

For the crashlytics submission, maybe running the build with debug output would help to understand why it is not submitted