5

I'm experimenting with gradle and trying to setup a system that builds different flavors (brands) of an application, which differ by configuration mainly. What I have so far are two versions of the build scripts - both not working.

Version 1
First flavor specific resource folder flavor-res is added to sourcesets, which achieves overwriting some default resources. A task rule defines tasks for each flavor, which should (ideally) trigger build of the whole jar.

This works fine and generates the required jar, for one flavor at a time, like

gradle clean flavorOne 

but the shadowJar task runs only once, if I do

gradle clean flavorOne flavorTwo

Stripped down Script:

sourceSets {
    main {
        ...
        resources {
            srcDirs = ['src/main/resources', "${project.buildDir}/flavor-res/"]
        }
    }
}

shadowJar { classifier = 'SNAPSHOT' }

tasks.addRule("Pattern: flavor<Name>") { String taskName ->
if (taskName.startsWith("flavor")) {

    String flavorName = (taskName - "flavor")
    String flavorOutDir = "${project.buildDir}/${flavorName}"

    // Set output folder and jar name 
    task("${taskName}Configure") {
        outputs.dir(flavorOutDir)

        doFirst {
            archivesBaseName = flavorName
            project.buildDir = flavorOutDir
        }
    }

    // Copy res to folder used in sourcesets
    task("${taskName}CopyResources") {
        mustRunAfter = ["${taskName}Configure"]
        outputs.dir("${project.buildDir}/flavor-res")

        doFirst {
            copy {
                from "flavors/${flavorName}/"
                into "${project.buildDir}/flavor-res/"
            }
        }
    }

    shadowJar.mustRunAfter = ["${taskName}Configure", "${taskName}CopyResources"]

    // Define task that depends on shadowJar
    task(taskName, dependsOn: ["${taskName}Configure", "${taskName}CopyResources", 
         shadowJar]) {
        println "Configuring ${taskName}"
    }

}

Sensing that it probably doesnt work because the change detection somehow doesnt work, I tried an alternative approach. Here is a simplified version of script

Version 2

Modified the rule to define a shadowJar dynamic task for each flavor.

/* Removed sourceSets in this version */

shadowJar { classifier = 'SNAPSHOT' }

tasks.addRule("Pattern: flavor<Name>") { String taskName ->
if (taskName.startsWith("flavor")) {

    String flavorName = (taskName - "flavor")
    String flavorOutDir = "${project.buildDir}/${flavorName}"

    // Set resources for main sourceset
    task("${taskName}Configure") {
        outputs.dir(flavorOutDir)

        doFirst {
            archivesBaseName = flavorName
            sourceSets.main.resources.srcDirs = ['src/main/resources', "${flavorOutDir}/flavor-res"]
            project.buildDir = flavorOutDir
        }
    }

    task("${taskName}CopyResources") {
        outputs.dir("${flavorOutDir}/flavor-res")
        dependsOn "${taskName}Configure"

        doFirst {
            copy {
                from "flavors/${flavorName}/"
                into "${project.buildDir}/flavor-res/"
            }
        }
    }

    // This should shadowJar for each flavor - but generate jars dont have the required artifacts.
    task ("${taskName}Build", type: ShadowJar) {

        from sourceSets.main.output
        configurations = [ configurations.runtime ] 
        classifier = 'SNAPSHOT'

        dependsOn "${taskName}CopyResources"
    }

    task(taskName, dependsOn: ["${taskName}Build"]) {
    }
}
 }

However, now, the generated jars are malformed. The first flavor gets just the artifacts for main, but no showed jars. The second jar has just the manifest and nothing else.

What would be the correct way of achieving that.

PS: No, its not an android application (flavor is just a synonym for a brand).

Battlefury
  • 247
  • 1
  • 4
  • 20

1 Answers1

0

I decided to recreate a flavor build script, because it can be simplified to what you have now. The ShadowJar task can handle copying all the classes and resources by itself, there is no need to define separate ones. I also took some default configuration that would have been applied to the shadowJar task and applied this to the custom ShadowJar tasks to get the same behavior.

I first build a quick test project structure which can be found here: Test Structure

Then I came up with the following script:


    import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

    plugins {
        id 'java'
        id "com.github.johnrengelman.shadow" version "2.0.4"
    }

    group 'your-group'
    version 'dev-SNAPSHOT'

    sourceCompatibility = 1.8

    repositories {
        mavenCentral()
    }

    dependencies {
        // Example dependency
        compile group: 'com.google.guava', name: 'guava', version: '19.0'
    }

    tasks.addRule("Pattern: flavor<Name>") { def taskName ->
        if (!taskName.startsWith("flavor")) {
            return
        }

        def flavorName = taskName - "flavor"
        // Define the shadow task
        def shadowTask = task ("${flavorName}ShadowJar", type: ShadowJar) {
            classifier = flavorName
            // Add our flavor resources, first to prioritize these resources
            from file("src/main/flavors/${flavorName}")
            // Include our project classes
            from project.sourceSets.main.output
            // Don't include duplicate resources, only the first ones added, in
            // this case the flavored resources will override the default ones
            duplicatesStrategy = DuplicatesStrategy.EXCLUDE
            // Some defaults taken from the default shadowJar task
            // https://github.com/johnrengelman/shadow/blob/master/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy#L48
            configurations = [ project.configurations.runtime ]
            manifest.inheritFrom project.tasks.jar.manifest
            exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
        }

        // Define the flavor task
        task ("${taskName}", dependsOn: shadowTask) {}
    }

Cybermaxke
  • 13
  • 1
  • 2
  • Multiple targets are built but only the first one gets to include the resources from the flavor folder. All the other targets dont copy/overwrite any resources. gradle flavorOne flavorTwo. Eventually, I need ability to do several things including modifying Source files and other stuff for each flavor. I don't understand why the first target always works, but not the later ones, except maybe that it has something to do with detecting when the targets are uptodate. – Battlefury Jul 06 '18 at 17:44