Grouping few of many sourceSets having exactly same configuration
Asked Answered
R

3

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.

Rabiah answered 20/3, 2016 at 9:36 Comment(13)
Could you maybe use two flavorDimensions? tools.android.com/tech-docs/new-build-system/…Extravagance
thought of that but failing to figure out how it will help in this scenarioRabiah
What are the differences between flavor1 and flavor3 and between flavor2 and flavor4? From what we see above it looks like they're identical...Extravagance
resources and assets are different (not minimal, they change the entire functionality as assets include the database)Rabiah
But these are different between all 4 flavors, right? That would mean that you really need one flavorDimensions with 4 flavors - and then introducing another flavorDimension would only make matters worse...Extravagance
yes, that's what stopped me from thinking more about dimensionsRabiah
In that case, sorry for wasting your time - let's hope someone else has a better idea.Extravagance
What about setting the root of sourceSets flavor3 to refer to flavor1? sourceSets.flavor3.root = sourceSets.flavor1 #32230014Werby
@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?Rabiah
Out of curiosity: what is that 300 flavors?Holothurian
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.Rabiah
Interesting administrative challenge in the Google Play Developer Console :)Holothurian
its a pain, also think about other storesRabiah
H
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]
Holothurian answered 28/3, 2016 at 23:51 Comment(4)
That was neat! I liked itPaulapauldron
I was looking the docs for Groovy, I came to edit my answer to add exactly that.Paulapauldron
OMG! where were you till now? :) Let me give this a try. Thanks a lot in advance....Rabiah
have another question to improvise this: mind taking a look? #36291862Rabiah
H
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.

Holothurian answered 28/3, 2016 at 23:32 Comment(0)
P
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.

Paulapauldron answered 28/3, 2016 at 4:18 Comment(9)
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.Rabiah
I'll update the answer with the project structure I used in 20-30 minutes (I'm on mobile now).Paulapauldron
@AndroidMechanic Did it work? May I add anything to help?Paulapauldron
You can also do with variables, but it will be cumbersome in the same way as your original build.gradle.Paulapauldron
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 ... :)Rabiah
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.Rabiah
btw, any clue on this one : #36105994 ?Rabiah
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.Paulapauldron
have another question to improvise this: mind taking a look? #36291862Rabiah

© 2022 - 2024 — McMap. All rights reserved.