Build types support in Java

Continuing the discussion from Does the upcoming variants implementation match that of Android's gradle plugin?:

So I’ve started the above topic almost a year ago in search for support like the Android gradle plugin has for build types and variants, but for other languages and java web apps in particular. Fast forward 1 year, I am not able to use the build types with a java web app, like mentioned here https://docs.gradle.org/current/userguide/native_software.html#buildTypes

The compiler spits out:

The following model rules could not be applied due to unbound inputs and/or subjects:
buildTypes { … } @ build.gradle line 50, column 5
subject:
- buildTypes Object []
[
] - indicates that a model item could not be found for the path or type.

I am trying to have 2 different build types, main and debug with:
src/main/resources/log4j.properties
src/debug/resources/log4j.properties

When I build the debug type, I want to have the associated properties file packaged. Bare in mind that all java files reside inside the main type, not duplicated in the debug one.

You might find the nebula facet plugin useful

Eg:

apply plugin: 'java' 
apply plugin: 'nebula.facet'

facets {
    debug
}

Indeed, it does sound useful. But as far as I can see, it builds on the SourceSets feature of Gradle. It still generates separate debug and main classes and resources, it doesn’t merge them. And speaking of SourceSets, what difference do they have compared to build types and flavors?

I would’ve wanted a built-in way of achieving this, if possible, especially since build types and flavors seem to be the exact thing that would do this. Or am I mistaken?

So far I’ve only managed to have a debug and main SourceSets, but they don’t merge java or resource files. And even if they would merge, the WAR plugin still seeks inside the src/main folder for classes and resources, it doesn’t take into account the different debug/main SourceSets. Any ideas?

The facet sourceSet will inherit from the main sourceSet so should achieve the merge / override behaviour you require.

It should then be simple enough to create an extra artifact (jar/war) from the facet. This artifact will likely have a ‘debug’ classifier

The problem is that it just doesn’t work. Neither using the default SourceSets implemention like below, nor the Nebula plugin, merges the java/resources. It just spits out the debug java/resources inside the build/classes/debug and build/resources/debug folders, and the main set inside the build/classes/main and build/resources/main folders. It doesn’t merge them inside the debug folders.

The Nebula plugin’s version using SourceSets that I’ve also tried:

sourceSets {
    debug {
        java {
            compileClasspath += main.output
            runtimeClasspath += main.output
        }
    }
}

Yes, as I understand, the facet plugin only adds the sourceSet for the facet. It doesn’t add a debugJavaCompile or debugWar tasks. You’ll need to add these yourself and wire them into the DAG

task debugCompileJava(type: JavaCompile) { ... } 
task debugWar(type: War) { ... } 
task debugProcessResources(type: ProcessResources) { ... } 
assemble.dependsOn debugWar
classes.dependsOn debugCompileJava
classes.dependsOn debugProcessResources

I believe this flavour support will become a core concept in the java plugin in future. For the time being you’ll need to roll your own unfortunately

The compileDebugJava and processDebugResources are already being called, that is not the problem. The problem is overriding the java/resources from the main set, like I’ve already said. Anyhow, I’ve finally been able to achieve this by setting the output directory for classes and resources on each set. Also, by setting the from variable on the WAR task config, you can create the archive according to the source set that is being built. This is pretty much how the Android plugin does it. The only problem that is still present is that Intellij IDEA complains about duplicate class files inside the debug and main sets for classes with the same name.

Now, the only issue that I have left is: why have both the SourceSets and the Build Types & Flavors implementations inside Gradle? What’s the difference between them? It’s confusing to have both. And why don’t Build Types work in Java? @luke_daley if this is doable with SourceSets, which have been available for years, why haven’t you told me so a year ago…?

Code below for posterity:

apply plugin: 'war'

sourceSets {
    main {
        output.resourcesDir = 'build/resources'
        output.classesDir   = 'build/classes'
    }
    debug {
        java {
            compileClasspath += main.output
            runtimeClasspath += main.output
        }
        output.resourcesDir = 'build/resources'
        output.classesDir   = 'build/classes'
    }
}

task assembleDebugWar(type: War) {
    from sourceSets.debug.output
    archiveName "ROOT.war"
}

task assembleReleaseWar(type: War) {
    from sourceSets.main.output
    archiveName "ROOT.war"
}

That config looks error prone to me, having two tasks writing to the same output directory is likely to cause issues (eg up to date chevks). IMHO it’s best to keep the output directories separate (and instead merge in the war task)

Also, do you really want the same classname in two facets/flavours? If using dependency injection this might be better handled with two different implementations of a single interface + different IOC config for each flavour

It’s hardly error prone, considering the main set is being compiled first every time. Unless you bring an exact example of what would go wrong, this is not an issue.

If you’d have read the initial thread, you’d understand why I need the separate class scheme on java classes. The approach you mention is not only unusable, it’s overkill.

If we are to continue this conversation, please, bring exact examples like I did and more concrete explanations, as currently most of them were out of context with what I wrote and it’s very difficult to follow them and/or not useful.

Given your build script above, there will be these two tasks

  1. classes - Assembles main classes
  2. debugClasses - Assembles debug classes

But you have configured both to use the same directories. Because of this, gradle will NEVER think that the “classes” task is up to date thus incremental builds will not work as expected.

Actually the java classes have no issues:

:compileJava UP-TO-DATE
:processResources
:classes
:compileDebugJava UP-TO-DATE
:processDebugResources
:debugClasses
:assembleDebugWar

Furthermore, if I move the files that need to be overridden from the main set to a release SourceSet, then even the resources are up to date:

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileDebugJava UP-TO-DATE
:processDebugResources UP-TO-DATE
:debugClasses UP-TO-DATE
:assembleDebugWar