2

I am trying to build an Android project which has 6 build flavors. Each has just 1 resource file that is unique to it. The Ant build copies the file from res/configs/ to res/raw/ for each flavor. I did not want to break that build and I could not figure out how to get the Gradle Android build to work with this folder structure. So I added a copy phase to get a common resource folder and then one for each flavor with just that 1 file.

task cleanExtra(type: Delete) {
    delete 'res-common'
    delete 'res-dev'
    delete 'res-qa'
    delete 'res-sb'
    delete 'res-test'
    delete 'res-stage'
    delete 'res-prod'
}
task setUpSharedResources(type: Copy) {
    from 'res'
    into 'res-common'
    exclude '**/environment.properties'
}
task setUpDevResources(type: Copy ) {
    from 'configs/dev/'
    into 'res-dev/raw/'
}
task setUpQaResources(type: Copy ) {
    from 'configs/qa/'
    into 'res-qa/raw/'
}
task setUpSbResources(type: Copy ) {
    from 'configs/sb/'
    into 'res-sb/raw/'
}
task setUpTestResources(type: Copy ) {
    from 'configs/test/'
    into 'res-test/raw/'
}
task setUpStageResources(type: Copy ) {
    from 'configs/stage/'
    into 'res-stage/raw/'
}
task setUpProdResources(type: Copy ) {
    from 'configs/prod/'
    into 'res-prod/raw/'
}

Then set up the flavor copy tasks to run before preBuild

preBuild.dependsOn setUpDevResources
preBuild.dependsOn setUpQaResources
preBuild.dependsOn setUpSbResources
preBuild.dependsOn setUpTestResources
preBuild.dependsOn setUpStageResources
preBuild.dependsOn setUpProdResources

Then the common copy tasks depend on the flavor ones so it happens first

setUpDevResources.dependsOn   setUpSharedResources
setUpQaResources.dependsOn    setUpSharedResources
setUpSbResources.dependsOn    setUpSharedResources
setUpTestResources.dependsOn  setUpSharedResources
setUpStageResources.dependsOn setUpSharedResources
setUpProdResources.dependsOn  setUpSharedResources
clean.dependsOn(cleanExtra)

With these new res folders created I can set up my android source sets and product flavors like so

productFlavors {
    dev  {}
    prod {}
    qa   {} 
    sb   {}
    stage{}
 // test {}
}

sourceSets {
    main {
        manifest.srcFile 'AndroidManifest.xml'
        java.srcDirs         = ['.apt_generated','src']
        resources.srcDirs    = ['.apt_generated','src']
        aidl.srcDirs         = ['.apt_generated','src']
        renderscript.srcDirs = ['.apt_generated','src']
        assets.srcDirs       = ['assets']
    }
    dev {
        res.srcDirs          = ['res-common','res-dev']
    }
    prod {
        res.srcDirs          = ['res-common','res-prod']
    }
    qa {
        res.srcDirs          = ['res-common','res-qa']
    }
    sb {
        res.srcDirs          = ['res-common','res-sb']
    }
    stage {
        res.srcDirs          = ['res-common','res-stage']
    }
    test {
        res.srcDirs          = ['res-common','res-test']
    }

    debug.setRoot('build-types/debug')
    release.setRoot('build-types/release')
}

Although this seems to work, its got some problems. The copy tasks are not tied to the flavors well. I just hard code the folder names. It seems like there should be some way of doing that with a loop. Or even better not doing the copy task at all and just telling Gradle which files to use neatly.

Another thing I am not sure about. I did not create a test flavor because gradle complained that that one already existed. Im not sure if there is a problem with just hijacking it for a different purpose.

I get the impression that I am forcing Gradle to build all the resources for all 6 flavors. Is there a way to solve this problem cleaner or faster is my question?

* Follow Up * I am realizing that I could avoid the copy phase with proper resource merging. I had some problems with that which is why I did the file copy stuff. When I have the file in both of the resource folders I get this error

/Users/mkluver/Documents/OH-android/oep/res/raw/environment_override.properties:
Error: Duplicate resources: /Users/mkluver/Documents/OH-android/oep/res/raw/environment_override.properties:raw/environment_override,
/Users/mkluver/Documents/OH-android/oep/res-dev/raw/environment_override.properties:raw/environment_override

I am guessing the resource merging failed because these are not XML resources but properties files. formatted with lines like.

#This is a comment
server.url=http://www.theserver.com/api
server.version=5.4

How can I tell gradle how to merge these sorts of files?

Marc
  • 1,159
  • 17
  • 31

2 Answers2

28

This is overkill.

all you need is:

src/main/res

This is your common resources for all your flavors

src/dev/res

This is the resources specific to the dev flavor.

src/prod/res

This is the resources specific to the prod flavor.

etc...

Note: if you have a resource both in src/main/res and src/<flavor>/res, the one in the flavor res folder will win.

Each flavor will actually get the combined resources of its own res folder and the one in src/main/res

There's no need to manually copy things, it's all done for you. One of the advantages also is that the copying/merging of the resource folders will be done incrementally which will be faster and more efficient.

It seems you are using res instead of src/main/res so maybe you are using the old legacy folder structure. This is not a problem, you can keep remapping the folders, but only specify one of them:

android {
    sourceSets {
        main {
            res.srcDirs    = ['res']
        }
        dev {
            res.srcDirs    = ['res-dev']
        }
        prod {
            res.srcDirs    = ['res-prod']
        }
        etc...
    }
}

Note sure what is going with your 'test' flavor. Is it really a different flavor for your build or is it your test apk?

Edit: For your resource merging issue: it's not a merge. A merge is between different sourceSets. So src/main/raw/foo.prop gets overridden by src/debug/raw/foo.prop. However, in your case, think, you have the same resources in 2 source folders that are set on the same sourcesets. This is because you set on each sourceSets both the common res folder and the flavor specific res.

When you let the merger do its thing, there's a clear overriding order. Flavors override main source sets and build types override flavors.

When you have two res folders associated with the same flavor, we cannot know which one to choose.

Xavier Ducrohet
  • 28,383
  • 5
  • 88
  • 64
  • The test flavor is not a test apk, its really a different flavor. We have a legacy build that works in ANT and Eclipse. I would prefer not to move any files around, so that I can leave the ANT build in a working state without modifications. About "Note: if you have a resource both ...the flavor res folder will win." I was having trouble with that, I'll update my question since code is hard to read in comments. – Marc Jan 07 '14 at 17:37
  • I added a comment about your merging issue. is your 'test' flavor meant to run test against one of the other flavors? I'm not sure this would work. – Xavier Ducrohet Jan 08 '14 at 05:20
  • 2
    No the test is just the name of a regular flavor. Its not meant to run tests against another flavor. I am worried this will cause problems and maybe I need to name this flavor something else. – Marc Jan 08 '14 at 17:19
  • 1
    Thank you, Xavier. The build script is much cleaner now. I removed most of the copies and fixed my source sets as you suggested. – Marc Jan 10 '14 at 17:50
  • As I mentioned in my answer here, you might want to also create a dummy manifest file for each flavor so that the lint checker doesn't flag either folder: https://stackoverflow.com/a/63224862/5916188 – Sam Aug 03 '20 at 07:09
  • Say I have a buildType named release and a flavor named staging. Should I use the folder `src/stagingRelease/res` if I want to add files specifically to a certain flavor? Or only `stc/staging/res`? – Jaap Weijland Dec 28 '20 at 15:22
0

I have just found a perfect solution for this problem, check it in my answer here How to exclude res folder from gradle build flavours?

lxknvlk
  • 2,744
  • 1
  • 27
  • 32