Migrate Gradle Groovy to Kotlin building WAR, JAR & EAR

Have been building & deploying my application as an EAR (JAR & WAR) using Gradle Groovy:

task deleteFiles(type: Delete) {
    project.logger.lifecycle(">>>>> 2 deleteFiles")

    delete fileTree("./build/libs") {
        include "**/NOTiFYmoto*.?ar"
    }

    delete fileTree("./src/main/application") {
        include "**/NOTiFYmoto*.war"
        include "**/NOTiFYmoto*.jar"
        include "**/NOTiFYmoto*.ear"
    }
}

task NOTiFYmotoWAR(type: War) {
    project.logger.lifecycle(">>>>> 3 NOTiFYmotoWAR")

    dependsOn deleteFiles

    archiveFileName = "NOTiFYmotoWAR.war"

    rootSpec.exclude("**/dto/*")
    rootSpec.exclude("**/ean/*")
    rootSpec.exclude("**/ejb/*")
    rootSpec.exclude("**/entity/*")
    rootSpec.exclude("**/filter/*")
    rootSpec.exclude("**/gson/*")

    rootSpec.exclude("**/showwcase/*")

    rootSpec.exclude("**/morphia-*.jar")
    rootSpec.exclude("**/mongo*.jar")
    rootSpec.exclude("**/bson*.jar")
    rootSpec.exclude("**/surefire*.jar")

    rootSpec.exclude("**/primefaces/**")
    rootSpec.exclude("**/controller/**")

    rootSpec.exclude("**/webservices/*")

    rootSpec.exclude("**/checkstyle*.jar")
    rootSpec.exclude("**/classgraph*.jar")
    rootSpec.exclude("**/kotlin*.jar")

    rootSpec.exclude("**/NOTiFYmoto*.jar")
    rootSpec.exclude("**/NOTiFYmoto*.war")
}

task NOTiFYmotoJAR(type: Jar) {
    project.logger.lifecycle(">>>>> 4 NOTiFYmotoJAR")

    dependsOn NOTiFYmotoWAR

    archiveFileName = "NOTiFYmotoJAR.jar"

    from("./src/main/java") {
        include "META-INF/**"
    }

    // Exclude
    rootSpec.exclude("**/jsf/SliderViewBean.class")
    rootSpec.exclude("**/push/PushBean.class")

    from("./build/classes/java/main") {
        include "*/**"
    }

    // Kotlin
    from("./build/classes/kotlin/main") {
        include "*/**"
    }
}

task copyNOTiFYmotoWAR(type: Copy) {
    project.logger.lifecycle(">>>>> 5 copyNOTiFYmotoWAR")

    dependsOn NOTiFYmotoJAR

    from file("./build/libs/NOTiFYmotoWAR.war")
    into file("./src/main/application")
}

task copyNOTiFYmotoJAR(type: Copy) {
    project.logger.lifecycle(">>>>> 6 copyNOTiFYmotoJAR")

    dependsOn copyNOTiFYmotoWAR

    from file("./build/libs/NOTiFYmotoJAR.jar")
    into file("./src/main/application")
}

task NOTiFYmotoEAR(type: Ear) {
    project.logger.lifecycle(">>>>> 7 NOTiFYmotoEAR")

    apply plugin: "ear"
    
    dependsOn copyNOTiFYmotoJAR

    archiveFileName = "NOTiFYmoto.ear"

    manifest {
        from("./src/main/resources/META-INF/MANIFEST.MF")
    }

    exclude "**/*.class"
    exclude "**/asm-Java.jar"
    exclude "**/jboss-deployment-structure.xml"

    dependencies {
        earlib group: "com.google.code.gson", name: "gson", version: "2.10", ext: "jar"
        earlib group: "org.apache.httpcomponents", name: "httpclient", version: "4.5.13", ext: "jar"
        earlib group: "org.apache.httpcomponents", name: "httpcore", version: "4.4.14", ext: "jar"
        earlib group: "org.jetbrains.kotlin", name: "kotlin-stdlib", version: "1.8.21", ext: "jar"

        earlib group: "dev.morphia.morphia", name: "morphia-core", version: "2.3.0", ext: "jar"

        earlib group: "org.mongodb", name: "mongodb-driver-core", version: "4.9.1", ext: "jar"
        earlib group: "org.mongodb", name: "mongodb-driver-sync", version: "4.9.1", ext: "jar"
        earlib group: "org.mongodb", name: "bson", version: "4.9.1", ext: "jar"

        earlib group: "net.bytebuddy", name: "byte-buddy", version: "1.14.4", ext: "jar"

        earlib group: "io.github.classgraph", name: "classgraph", version: "4.8.157"
    }
}

My WAR, JAR builds as an EAR & deploys on WildFly 28.x & runs.

NOTiFYmoto.ear
NOTiFYmoto.jar
NOTiFYmoto.wa

I’ve migrate to Gradle Kotlin DSL. It’s now unable to create the complete EAR (JAR & WAR):

tasks.create<Delete>("deleteFiles") {
    project.logger.lifecycle(">>>>> 2 deleteFiles")

    delete {
        fileTree("./build/libs")

        file("**/NOTiFYmoto*.?ar")
    }

    delete {
        fileTree("./src/main/application")

        files("**/NOTiFYmoto*.war")
        files("**/NOTiFYmoto*.jar")
        files("**/NOTiFYmoto*.ear")
    }
}

tasks.register<War>("NOTiFYmotoWAR") {

    project.logger.lifecycle(">>>>> 3 NOTiFYmotoWAR")

    dependsOn("deleteFiles")

    archiveFileName.set("NOTiFYmotoWAR.war")

    rootSpec.exclude("**/dto/*")
    rootSpec.exclude("**/ean/*")
    rootSpec.exclude("**/ejb/*")
    rootSpec.exclude("**/entity/*")
    rootSpec.exclude("**/filter/*")
    rootSpec.exclude("**/gson/*")

    rootSpec.exclude("**/showwcase/*")

    rootSpec.exclude("**/morphia-*.jar")
    rootSpec.exclude("**/mongo*.jar")
    rootSpec.exclude("**/bson*.jar")
    rootSpec.exclude("**/surefire*.jar")
    rootSpec.exclude("**/*primefaces*/**")
    rootSpec.exclude("**/controller/**")
    rootSpec.exclude("**/webservices/*")
    rootSpec.exclude("**/checkstyle*.jar")
    rootSpec.exclude("**/classgraph*.jar")
    rootSpec.exclude("**/kotlin*.jar")
    rootSpec.exclude("**/NOTiFYmoto*.jar")
    rootSpec.exclude("**/NOTiFYmoto*.war")

}

tasks.register<Jar>("NOTiFYmotoJAR") {

    project.logger.lifecycle(">>>>> 4 NOTiFYmotoJAR")

    dependsOn("NOTiFYmotoWAR")

    archiveFileName.set("NOTiFYmotoJAR.jar")

    from("./src/main/java") {
        //"META-INF/**"
        fileTree("META-INF/**")
    }

    // Exclude
    rootSpec.exclude("**/jsf/SliderViewBean.class")
    rootSpec.exclude("**/push/PushBean.class")

    from("./build/classes/java/main") {
        //include "*/**"
        fileTree("*/**")
    }

    // Kotlin
    from("./build/classes/kotlin/main") {
        //include "*/**"
        fileTree("*/**")
    }
}

tasks.register<War>("copyNOTiFYmotoWAR") {
    project.logger.lifecycle(">>>>> 5 copyNOTiFYmotoWAR")

    dependsOn("NOTiFYmotoJAR")

    from(File(buildDir, "./build/libs/NOTiFYmotoWAR.war"))
    into(File(buildDir, "./src/main/application"))
}

tasks.register<Jar>("copyNOTiFYmotoJAR") {
    project.logger.lifecycle(">>>>> 6 copyNOTiFYmotoJAR")

    dependsOn("copyNOTiFYmotoWAR")

    from(File(buildDir, "./build/libs/NOTiFYmotoJAR.jar"))
    into(File(buildDir, "./src/main/application"))
}

tasks.register<Ear>("NOTiFYmotoEAR") {
    project.logger.lifecycle(">>>>> 7 NOTiFYmotoEAR")

    apply(plugin = "ear")

    dependsOn("copyNOTiFYmotoJAR")

    archiveFileName.set("NOTiFYmoto.ear")

    manifest {
        from("./src/main/resources/META-INF/MANIFEST.MF")
    }

    exclude("**/*.class")
    exclude("**/asm-Java.jar")
    exclude("**/jboss-deployment-structure.xml")

    dependencies {
        earlib(group = "gson", name = "gson", version = "2.10", ext = "jar")
        earlib(group = "gson", name = "gson", version = "2.10", ext = "jar")

        earlib(group = "httpcomponents", name = "httpclient", version = "4.5.14", ext = "jar")
        earlib(group = "httpcomponents", name = "httpcore", version = "4.4.16", ext = "jar")
        earlib(group = "kotlin", name = "kotlin-stdlib", version = "1.8.10", ext = "jar")
        earlib(group = "morphia", name = "morphia-core", version = "2.3.0", ext = "jar")
        earlib(group = "mongodb", name = "mongodb-driver-core", version = "4.9.1", ext = "jar")
        earlib(group = "mongodb", name = "mongodb-driver-sync", version = "4.9.1", ext = "jar")
        earlib(group = "mongodb", name = "bson", version = "4.9.1", ext = "jar")
        earlib(group = "classgraph", name = "classgraph", version = "4.8.158", ext = "jar")

    }
}

I always creates:

NOTiFYmoto.ear
NOTiFYmoto.jar
NOTiFYmoto.war

NOTiFYmotoJAR.jar
NOTiFYmotoWAR.war

The EAR won’t deploy the combined ‘NOTiFYmotoJAR.jar & NOTiFYmotoWAR.war’ with:

./gradlew build clean 
>>>>> 2 deleteFiles
>>>>> 3 NOTiFYmotoWAR
>>>>> 4 NOTiFYmotoJAR
>>>>> 5 copyNOTiFYmotoWAR
>>>>> 6 copyNOTiFYmotoJAR
>>>>> 7 NOTiFYmotoEAR

./gradlew NOTiFYmotoEAR
>>>>> 2 deleteFiles
>>>>> 7 NOTiFYmotoEAR
>>>>> 6 copyNOTiFYmotoJAR
>>>>> 5 copyNOTiFYmotoWAR
>>>>> 4 NOTiFYmotoJAR
>>>>> 3 NOTiFYmotoWAR

Gives:

Ear File - 6.1 MB
Java JAR file - 2 KB
War File - 311.6 MB

Java JAR file - 111 KB
War File - 220.2 MB

Won’t combine an EAR of ‘Java JAR’ & ‘Java JAR file’?

task NOTiFYmotoEAR(type: Ear) {

Some notes:

  • your logger calls do not tell you when the tasks are executed, but when they are configured as you do it in the configuration phase, not the execution phase
  • do not use task.create... but task.register... or val taskName by task.registering... or you don’t benefit from task configuration avoidance
  • while the whole deleteFiles task looks suspicious and should most probably removed completely, it does not do any work now anyway. Calling files or fileTree within delete { ... } has no effect. Instead you would want delete(fileTree(...) { include ... }) to have it like in your Groovy version
  • any explicit dependsOn (except if a lifecycle task is on the left-hand side) is a code smell and should be avoided as far as possible, instead you should properly wire task outputs to task inputs and get the necessary task dependencies automatically
  • you do not need rootSpec. prefix, the tasks themselves are copy specs and forward what you configure to the root spec automatically, using it explicitly is just noise
  • it is preferable to not store resources in src/main/java/{META-INF,...} but conventionally in src/main/resources/{META-INF,...}, then they are automatically packed into the jar without further configuration if you use the normal jar task instead of creating your own
  • you seem to only have excludes on your war task, so I’m not sure why actually anything is packed into it unless you did not show the complete build script
  • fileTree within from(...) { ... } as above with delete { ... } simply has no effect at all
  • why don’t you simply use the standard jar task and configure it like you need, then you would not need all those explicit froms that are bad anyway as you should not configure paths, but outputs to inputs
  • you don’t need to use file(...) for the from and into calls if you work with paths at all which you shouldn’t for most things shown, a given string is automatically relative to the project directory
  • the from you configure for your “copy war” task is build/build/... and the into is build/src/... which is probably not intended and couldn’t happen if you would use the task that produces that file as from instead
  • your “copy war” task depends on your “jar” task
  • your “copy jar” task depends on your “war” task
  • from within manifest { ... } again is not doing what you expect it to and instead just includes the given file into your ear
  • using apply and dependencies within the ear task configuration only causes, that the configuration is only done if the ear task is configured, but actually it belongs to the top level, not inside that block
  • do not use the legacy apply to apply plugins, but use the plugins { ... } block

Once you fixed all that, it probably starts behaving as you would like it to. :slight_smile:

Thanks all your suggestions. It appears a lot of effort migrating from Groovy to Kotlin. Albeit my Gradle Groovy EAR appears to have worked for years.

I have looked at ‘Gradle Kotlinize’ but wouldn’t convert anything. Are you aware of any tools? TIA.

Even your Groovy version has many of the points I listed, they are not at all Kotlin DSL specific.
That it works for years is probably more luck than anything else and only if you happen to execute things in the right order.
It’s like with every other Maven project that only builds properly and sanely if you always call clean along with the actual task. :smiley:

And no, I’m not aware of any good automagic tool that can convert one turing-complete language into another.

If you are happy with all the bad things you do, then just fix the things you ported over wrongly like the no-effect calls I mentioned and the wrong paths I mentioned.