Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gradle Android dependency product flavors

I'd like to know if there is a way for a root project to define/inject some properties in it's dependencies. More specifically, the problem I'm having is that a library project has to know whether to take "free" or "pro" java sources and other resources before assemble/compile task is run. Kind of like specifying product flavors for library projects (that are inherited from it's parent project), but that isn't supported by the Android plugin for Gradle. Changing the library project structure, i.e. creating "free" and "pro" libs is not an option.

Edit: The best I've managed to achieve so far is something like this:

root: build.gradle

android {
    ...

    productFlavors {
        free, pro
    }

    sourceSets {
        free {
            project(':..:lib') {
                groupFreePro = 'free'
                // java.srcDirs = ['src', 'free/src']
             }
        }

        pro {
            project(':..:lib') {
                groupFreePro = 'pro'
                // java.srcDirs = ['src', 'pro/src']
            }
        }
    ...
    }
}

library: gradle.build

android {
    ...
    sourceSets {
        main {
                  java.srcDirs = [groupFreePro + '/src']
                  res.srcDirs = [groupFreePro + '/res']
             }
        }
    ...
    }
}

That way I inject the groupFreePro variable into the lib project. But there is a problem with this approach:

By the time, when the lib project get's to it's android -> sourceSets task the groupFreePro is always set to "pro". I presume that's because all the sourceSets at the root project are read (and not just the one variant that I want to build with; "free" for example) and thus the last set/task always overrides any previously set values of groupFreePro.

If I try to set the value of groupFreePro any other way it either gets overriden (like in the above case), or I don't know the appropriate task/time/place where I should call this variable injection stuff to set the variable to the desired value. Uncommenting java.srcDirs in root project doesn't help either.

I tried solving these problems on my own, but I'm really new to Gradle and also lack of proper documentation (at least for the Android part) leaves me guessing what to do most of the time so I do a lot of trial and error (but now I'm kind of stuck).

like image 466
croc Avatar asked Sep 25 '13 19:09

croc


3 Answers

This is how I've solved the problem for now. It's not a perfect solution, but it's good enough for now.

Update!

I've updated the answer to include the latest 0.9.2 Gradle plugin and it's new(est) features (mostly just updated the library build scripts).

root: gradle.build

// global variables
ext {
    // can be set to default values or blank
    groupFreePro = "free"
}

// start parameters
println "Start parametes: tasks = " + gradle.startParameter.getTaskNames()

gradle.startParameter.getTaskNames().each { task ->
    if (task.contains("Free") || task.contains("F")) {
        groupFreePro = "free"
    } else if (task.contains("Pro") || task.contains("P")) {
        groupFreePro = "pro"
    }
    println "groupFreePro = " + groupFreePro
}

android {
...
}

The task.contains("F") is there to handle the abbreviated versions or running tasks (if we wanted to run the script as gradle aFD).

The global variables under ext can be set to default values. In that case, even if you run the script without the "Free/Pro" in the task name, it should work just fine. The downside of default values is that the build may not crash if not set up properly (if you want the build to work only if "Free/Pro" in the task name is specified).

library: gradle.build

android {
...
    defaultPublishConfig groupFreePro + groupDebugRelease.capitalize()

    productFlavors {
        free
        pro
    }

    ...
    sourceSets {
        main {
            java.srcDirs = ['/src']
            res.srcDirs = ['/res']
        }

        free {
            java.srcDirs = ["free/src"]
            res.srcDirs = ["free/res"]
        }

        pro {
            java.srcDirs = ["pro/src"]
            res.srcDirs = ["pro/res"]
        }
    }
    ...
}

dependencies {
    freeCompile fileTree(dir: 'free/lib', include: '*.jar')
}

Update:

The library now contains defaultPublishConfig so that I don't need to specify
        java.srcDirs = ["src", groupFreePro + "/src"]
        res.srcDirs = [groupFreePro + "/res"]

any more, as well as custom flavour compiling now be used i.e. flavor1Compile (in the dependencies block).

The option of writing compile project(path: ':project', configuration: 'flavor1Debug') in the dependencies block doesn't really work for us because you have to pass these options through dependencies and if you have multiple flavour groups/dimensions this means that more or less all of the flavour combinations have to be handled in "non-last" dependencies (i.e. dependencies that have other dependencies (that have multiple flavours)) as well.



The println lines are just to see and make sure the right params are passed.

The upside of this solution (compared to Varun's) is that you need to run just one (original) task. That also means that it works (or at least it should) with Android Studio without any problems.

The downside of this solution is that it doesn't work if you wanted to build all variants using gradle assemble command (or alike) that is missing the Free part of the task. I guess that could also be handled but I'm not doing that, because the current solution is good enough for me at the moment (though if I improve the current solution, I'll probably update this answer as well).

There are other solutions possible by using gradle.taskGraph.whenReady but I don't know how to properly set srcDirs (of dependencies in particular). Suggestions welcome.

like image 195
croc Avatar answered Oct 17 '22 04:10

croc


This feature is now available after Version 0.9 of the Gradle plugin for Android.

Take a look here: http://tools.android.com/tech-docs/new-build-system/migrating_to_09

Copy pasting here:

Libraries

The DSL for the library projects is now the same as for the application projects. This means you can create more build types, and create flavors.
- You can create/configure more build types, in the buildTypes { ... } container.
- You can create product flavors using the productFlavors { ... } container.
- You can create signingConfigs using the signingConfigs { ... } container.

For example if you have in your library:

android {
    debug {
    }
    release {
    }
    debugSigningConfig {
    }
}

You would replace it with:

android {
    buildTypes {
        debug {
        }
        release {
        }
    }
    signingConfigs {
        debug {
        }
    }
}
like image 30
Jigar Avatar answered Oct 17 '22 03:10

Jigar


Here is the LibraryVariant and here is the ApkVariant

Looking at the above DSL it does not look like multiple productFlavors is supported for the LibraryVariant types..

If your free/pro are not going to change much you can create a aar each for pro and free and use them as dependencies as needed by your app.

UPDATE:

I have some code at github. It works but requires calling an additional task before the actual build/assemble task is called on the app. https://github.com/varunkochar/Trying-Android-Gradle/tree/master/FakeLibraryProductFlavors

UPDATE:

With the latest android gradle plugin v0.9.0, the LibraryProject now also supports the same DSL as an ApplicationProject. So, you can use the latest version and use the inbuilt ability of library projects to build with custom flavors. Source: http://tools.android.com/tech-docs/new-build-system

like image 28
Varun Avatar answered Oct 17 '22 03:10

Varun