7

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).

croc
  • 1,416
  • 1
  • 18
  • 24
  • Have you tried making the 'library' project a normal project. The dependency management of gradle means you can use any project pretty much like a library project. – Saad Farooq Oct 05 '13 at 05:29
  • I have, but then I had other problems with the build. I think the problem was, that other libraries, built as normal projects, produced APKs that the build system (or I) didn't know how to include into the main project/APK. I might try that again though, see if I can approach in a different way. – croc Oct 05 '13 at 11:09
  • Is it possible for you to share code for that set up ? – Saad Farooq Oct 05 '13 at 20:27
  • For what set up exactly would you like me to share the code? The one where libraries are build as projects? – croc Oct 06 '13 at 23:46
  • Sorry for late response... I meant the code for where you are using a standard project dependency instead of a library dependency. I think it's clear that this won't be possible with a library. I've done something similar with `ant` but will have to look into a gradle implementation. – Saad Farooq Oct 12 '13 at 03:30
  • When I was trying that solution all I did was replace `apply plugin: 'android-library'` with `apply plugin: 'android'` in my lib project. The build didn't work, of course, so I didn't explore that option any further. – croc Oct 18 '13 at 11:45
  • Ah Ok... I might try to do that for my project at some point... hope I remember to put it here if it works – Saad Farooq Oct 19 '13 at 07:13
  • Visit this thread: http://stackoverflow.com/questions/24860659/multi-flavor-app-based-on-multi-flavor-library-in-android-gradle/24910671#24910671 – Ali Aug 01 '14 at 21:18

4 Answers4

6

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.

croc
  • 1,416
  • 1
  • 18
  • 24
  • @ croc Thanks for posting this. :D Makes it easier. – Varun Oct 18 '13 at 18:02
  • I've updated the answer a bit to include changes possible with the new 0.9.2 Gradle plugin. Suggestions/comments welcome. – croc Mar 28 '14 at 19:53
3

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

Varun
  • 33,833
  • 4
  • 49
  • 42
  • Please see my edit, I've expanded my question. Your suggestion is unfortunately not enough to bring me to a working solution. Could you provide me with a working example of build.gradle scripts? – croc Oct 02 '13 at 13:54
  • @croc I don't think that you can modify the `srcDirs` of your libraryproject like this.. The sources are set when `gradle` loads up and prepares the tasks. As much as I know, this can be resolved if you write some custom tasks in `build.gradle` to set the `srcDirs` for your library project from there. I will give it a try later on after work and post here if I get it to work. meanwhile you can let us all know if you make any progress. – Varun Oct 02 '13 at 22:29
  • Will do, thanks :) I was hoping Xavier (Android SDK Tech Lead) would look into this as well, because I'm pretty sure this is something people will need and/or expect from Android-Gradle build system (even though it isn't being designed that way). – croc Oct 03 '13 at 08:53
  • 1
    @croc Have a look here. https://groups.google.com/forum/#!topic/adt-dev/wjH5M7dPo-0 Looks like someone else has already brought this to the adt-dev team. Hopefully this will be supported at a later point in future. – Varun Oct 04 '13 at 06:00
  • Thanks! However I'd still like to see if there are any possible workarounds for this limitation. If there really aren't, I'll mark your answer as the accepted one. – croc Oct 04 '13 at 19:40
  • 1
    @croc I have put uploaded some code to GitHub. https://github.com/varunkochar/Trying-Android-Gradle/tree/master/FakeLibraryProductFlavors. Its a bad workaround that works. Requires calling additional gradle task before compiling the app code. – Varun Oct 05 '13 at 00:46
  • Well, at least it works :P Though I'd like to make it so, that you only need to call one task to build the whole thing and I already have an idea how to do that. However, I'd just like to know, if there is a way to get the current build variant (name) at any point of the build process (would make my idea easier and nicer to implement). @Varun BTW, could you edit your answer to include your GitHub link so that it's better visible to others that may not read all these comments? – croc Oct 05 '13 at 23:14
  • sure. I would like to make it work through a single task too. Updated the answer. I know how that we can iterate over all variants, but did not find a way to know the current variant names. If variant name was known before dependencies were prepared we could very well switch the sourcesets similar to the way you posted in the question. – Varun Oct 06 '13 at 17:11
  • I marked your answer as the accepted one. I'll probably edit my question to include your solution combined with my, so that only one task would need to be called to build the desired build variant (if my solution works at all; I think it should though). – croc Oct 06 '13 at 23:44
  • 1
    @croc Points I have plenty. :) Just wanted to help solve this because many other developers might be facing this. You probably could've waited before marking my answer as accepted as someone else might come up with a better approach. – Varun Oct 07 '13 at 15:42
  • 1
    I know :P I have thought about that but since you helped me and the bounty was on, I thought it only fair to give you the bounty as well :) As for the answer, if anyone will come up with a better solution, I can still accept that one later. – croc Oct 08 '13 at 06:03
  • I have posted my solution and marked that as the answer because that's what I'm using now. Feel free to review it and post suggestions if you have any. – croc Oct 18 '13 at 11:42
3

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 {
        }
    }
}
shaktiman_droid
  • 2,368
  • 1
  • 17
  • 32
0

Product flavors are now possible in android studio 0.5 (requires gradle plugin 0.9)

So you can effectively write this DSL now:

android {
    buildTypes {
        debug {
        }
        release {
        }
    }
    signingConfigs {
        debug {
        }
    }
}

See: http://tools.android.com/tech-docs/new-build-system/migrating_to_09 And http://tools.android.com/recent/androidstudio050released

redDragonzz
  • 1,543
  • 2
  • 15
  • 33