5

Lets say I have the following sourceSets:

sourceSets {
    flavor1 {
        assets.srcDirs = ['repo-assets/flavor1']
        res.srcDirs = ['repo-res/flavor1']
    }
    flavor2 {
        assets.srcDirs = ['repo-assets/flavor2']
        res.srcDirs = ['repo-res/flavor2']
    }
    flavor3 {
        assets.srcDirs = ['repo-assets/flavor1']
        res.srcDirs = ['repo-res/flavor1']
    }
    flavor4 {
        assets.srcDirs = ['repo-assets/flavor2']
        res.srcDirs = ['repo-res/flavor2']
    }
}

If you notice flavor1 and flavor3 have same srcDirs and so does flavor2 and flavor4.


Trying Possibility#1

I was trying to figure out if there is a way to avoid the redundancy by using something like this:

sourceSets {
    flavor1, flavor3 {
        assets.srcDirs = ['repo-assets/flavor1']
        res.srcDirs = ['repo-res/flavor1']
    }
    flavor2, flavor4 {
        assets.srcDirs = ['repo-assets/flavor2']
        res.srcDirs = ['repo-res/flavor2']
    }
}

The above does not work (already tried). Looking for something similar so that i can just provide a common set of sourceDirs for a set of flavors. Anyone tried doing something similar and can provide some pointers?


Trying Possibility#2

Does the name of sourceSets need to be same as that of flavors?

Can i name the sourceSets separately and then map them to productFlavors like this?

productFlavors {
    flavor1 {
      sourceset = "src1"
    }
    flavor2 {
      sourceset = "src2"
    }
    flavor3 {
      sourceset = "src1"
    }
    flavor4 {
      sourceset = "src2"
    }
}

sourceSets {
    src1 {
    }
    src2 {
    }
}

Trying Possibility#3

Can the sourcesets be dynamically assigned via tasks somehow to achieve the same stuff?


UPDATE

Douglas's answer sort of helped me get very close to what i was looking for eventually (reducing the code in build.gradle). He used Possibility#3 above. Thanks Douglas! Any better alternative from bounty hunters is still welcome (something closer to possibilities #1 and #2 above). If nothing comes up the bounty is Douglas's already when the period ends as I've accepted his answer. But still will remain optimistic about finding a better alternative.

Community
  • 1
  • 1
Viral Patel
  • 32,418
  • 18
  • 82
  • 110
  • Could you maybe use two `flavorDimensions`? http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Multi-flavor-variants – david.mihola Mar 22 '16 at 07:34
  • thought of that but failing to figure out how it will help in this scenario – Viral Patel Mar 22 '16 at 09:52
  • What are the differences between flavor1 and flavor3 and between flavor2 and flavor4? From what we see above it looks like they're identical... – david.mihola Mar 22 '16 at 10:29
  • resources and assets are different (not minimal, they change the entire functionality as assets include the database) – Viral Patel Mar 22 '16 at 10:36
  • But these are different between all 4 flavors, right? That would mean that you really need one `flavorDimension`s with 4 flavors - and then introducing another `flavorDimension` would only make matters worse... – david.mihola Mar 22 '16 at 10:45
  • yes, that's what stopped me from thinking more about dimensions – Viral Patel Mar 22 '16 at 10:46
  • In that case, sorry for wasting your time - let's hope someone else has a better idea. – david.mihola Mar 22 '16 at 10:50
  • What about setting the root of sourceSets flavor3 to refer to flavor1? sourceSets.flavor3.root = sourceSets.flavor1 https://stackoverflow.com/questions/32229514/let-two-flavors-use-the-same-sourceset – Jeroen Mols Mar 24 '16 at 14:16
  • @jmols how can i use `.root` in this case? I have only `assets` and `res' to mention in the `sourceSets`. Can you help with an example of what you are thinking of that would help in this case? – Viral Patel Mar 25 '16 at 10:29
  • Out of curiosity: what is that 300 flavors? – TWiStErRob Mar 28 '16 at 23:51
  • i know it sounds strange. But i have variations based on change in resources and assets :) and have designed the project such that replacing these will give you a whole different app. so 300 is not the end of it. I'm probably going to have more than 1k by the end of year. – Viral Patel Mar 29 '16 at 04:11
  • Interesting administrative challenge in the Google Play Developer Console :) – TWiStErRob Mar 29 '16 at 10:43
  • its a pain, also think about other stores – Viral Patel Mar 29 '16 at 10:47

3 Answers3

9

You were also pretty close with your first possibility:

sourceSets {
    [flavor1, flavor3].each {
        it.assets.srcDirs = ['repo-assets/flavor1']
        it.res.srcDirs = ['repo-res/flavor1']
    }
    [flavor2, flavor4].each {
        it.assets.srcDirs = ['repo-assets/flavor2']
        it.res.srcDirs = ['repo-res/flavor2']
    }
}

The above doesn't look nice in IDEA editor, a lot of warnings are shown. You can set the type if you want to get code completion:

import com.android.build.gradle.api.AndroidSourceSet
android {
    sourceSets {
        [flavor2, flavor4].each { AndroidSourceSet ss ->
            ss.assets.srcDirs = ['repo-assets/flavor2']
            ss.res.srcDirs = ['repo-res/flavor2']
        }
    }
}

Another trick: this way the definition of the flavor is co-located with the source set listing.

android
    productFlavors {
        flavor1 {
            applicationId "flavor1.app.id"
        }
        flavor2 {
            applicationId "flavor2.app.id"
        }
        [flavor1, flavor2].each {
            sourceSets[it.name].assets.srcDirs = ['repo-assets/flavor1']
            sourceSets[it.name].res.srcDirs = ['repo-assets/flavor1']
        }
    }

Whichever way you go there's also a noteworthy thing about srcDirs, see source:

println assets.srcDirs // say it's [src/flavor/assets]
assets.srcDirs = ['dir1', 'dir2'] // overwrites existing directories: output would be [dir1, dir2]
assets.srcDirs 'dir1', 'dir2' // appends existing directories: output would be [src/flavor/assets, dir1, dir2]
assets.srcDir 'dir1' // appends only one dir, output would be [src/flavor/assets, dir1]
TWiStErRob
  • 44,762
  • 26
  • 170
  • 254
2

I think what @jmols is trying to say in comments is something like this:

Go from structure

repo-assets
    flavor1
    flavor2
repo-res
    flavor1
    flavor2

to

flavor1
    assets
    res
flavor2
    assets
    res

and use

sourceSets {
    //flavor1.setRoot('flavor1') // default
    //flavor2.setRoot('flavor2') // default
    flavor3.setRoot('flavor1')
    flavor4.setRoot('flavor2')
}

See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Project-Structure for setRoot and default structure. Here's the source code of setRoot, notice that it resets all customizations, that's why it's crucial to match the default structure. Its built-in call is located here.

TWiStErRob
  • 44,762
  • 26
  • 170
  • 254
1

You can define a function to copy flavors (outside of android {}):

def copyFlavor(flavordest, flavororig) {
    flavordest.resources.srcDirs = flavororig.resources.srcDirs
    flavordest.res.srcDirs = flavororig.res.srcDirs
    flavordest.aidl.srcDirs = flavororig.aidl.srcDirs
    flavordest.java.srcDirs = flavororig.java.srcDirs
    flavordest.assets.srcDirs = flavororig.assets.srcDirs
    flavordest.renderscript.srcDirs = flavororig.renderscript.srcDirs

    // if you don't need all of this, remove them. There are much more too, like `jni` and `jniLibs`
}

then call this function on source sets:

sourceSets {
    copyFlavor(flavor3, flavor1)  // flavor3 = flavor1
    copyFlavor(flavor4, flavor2)  // flavor4 = flavor2
}

Of course, it's only worth if you have many flavors, for only two, the overhead of writing a function is greater.

Edit: adding the project structure I used

To simplify and not need to specify flavor1 and flavor2 paths, I used the standard structure for flavors:

app/
    src/
        androidTest/
        flavor1/
            assets/
            res/
                values/strings.xml
        flavor2/
            assets/
            res/
                values/strings.xml
        main/
            assets/
            java/
            res/
                values/strings.xml
        test/

values.xml has a string app_name with content "SourceSets" for main, "SourceSets 1"for flavor1 and "SourceSets 2" for flavor2.

Just define flavor1 and flavor2 path the standard way and copy them later using the function provided.

Edit 2: Things I tried and probably some would work, but none did here (probably I made some stupid mistake from)

Things I tried and didn't work:

A:
1. flavor3.root = "flavor1"
2. flavor3.root = "flavor1/"
3. flavor3.setRoot("flavor1")
4. flavor3.setRoot("flavor1/")
These compiled, but the string resource I had defined to test didn't change.

B:
flavor3.root = flavor1.root
This doesn't even compile, .root is write-only.

C:
flavor3.root = flavor1
This also compiled, but had no effect. Notice it differs from A because in A, the right-hand-side is a string.

D:
I had the idea of [].each too, but when I tried it, I probably made some stupid mistake and it didn't work either:

    [flavor2, flavor4].each {
        it.res.srcDirs = ["flavor2/res"]
        it.resources.srcDirs = ["flavor2/res"]
        it.assets.srcDirs = ["flavor2/assets"]
    }

The last one I had some hope, but couldn't manage. Then I stuck to the copyFlavor function. All above I also tried with prepending src (e.g., src/flavor1), unsuccessful.

  • i do have a lot of flavors, around 300. This looks like something that will work for me. I'll give it a try and update in a few hours. – Viral Patel Mar 28 '16 at 06:13
  • 1
    I'll update the answer with the project structure I used in 20-30 minutes (I'm on mobile now). – Douglas Drumond Kayama Mar 28 '16 at 06:15
  • @AndroidMechanic Did it work? May I add anything to help? – Douglas Drumond Kayama Mar 28 '16 at 14:55
  • You can also do with variables, but it will be cumbersome in the same way as your original build.gradle. – Douglas Drumond Kayama Mar 28 '16 at 14:56
  • Haven't tried yet. Reached home a while ago. I'll try tonight and get back. But i am optimistic about this working out. :) Thanks a lot for the input ... :) – Viral Patel Mar 28 '16 at 14:57
  • Cheers! That worked! Thanks a lot. I'll accept the answer but will wait for the bounty award to automatically be awarded to you when the period ends. Meanwhile if someone comes up with a better answer I might need to change it. Hope that is OK with you. But, I highly doubt if anyone would (bounty is yours). But will still remain optimistic about it. Thanks again for the valuable input. – Viral Patel Mar 28 '16 at 18:37
  • btw, any clue on this one : http://stackoverflow.com/questions/36105494/localizing-string-resources-added-via-build-gradle-using-resvalue ? – Viral Patel Mar 28 '16 at 19:06
  • 1
    Cool! I have a personal project, learn Android hunting hard questions. It took me a while to figure out that solution, but it was worth. I'll check this other one as soon as I have some time. – Douglas Drumond Kayama Mar 28 '16 at 19:23
  • have another question to improvise this: mind taking a look? http://stackoverflow.com/questions/36291362/fetching-values-in-build-gradle-from-an-xml-or-csv – Viral Patel Mar 29 '16 at 17:41