At Revinate, we use Gradle and Spring Boot to build our Java 8 applications. In one project, we have configuration files written in a custom Domain Specific Language (DSL) and need to transpile them to json as part of the build process. There is a class with a main method that performs this transpilation which we’d like to invoke as a Gradle build step.

The problem

We must transpile all the DSL files within the repository and package the resulting json files in the jar file during the build. However, we’re compiling the very same classes that are invoked to perform the transpilation at the same time.

The solution

After some research, we determined that we needed to insert our logic between when the Java classes are compiled but before they are zipped into the jar. Gradle exposes the phases of the build process, allowing pre and post hooks, which we use to transpile the DSL into json files on disk just before the package step occurs.

We run a Gradle task that executes the Java main method using the task type JavaExec. It’s a good candidate since it let’s us invoke the main method with arguments.

In build.gradle:

task runWriter(type: JavaExec) {
    ...
}

This must run after the compile but before the jar task. One way to cause that behavior is:

jar {
    doFirst {
        tasks.runWriter.execute()
    }
}

The documentation of the JavaExec task type describes how both a classpath and a main class are specified. The Main is easy but what classpath should we use? Thanks to Gradle, we have access to the runtime classpath property:

sourceSets.main.runtimeClasspath

This variable holds the classpath of the runtime and it’s exactly what we need to run the transpiler.

Here’s the code:

task runWriter(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath
    main      = 'com.revinate.myapp.MyMain'
}
jar {
    doFirst {
        tasks.runWriter.execute()
    }
}

A very easy and elegant solution thanks to Gradle!