Avoid Multi Module Builds With Gradle

When splitting a project internally you typically use a multi module build. Build tools like Maven or Gradle have built-in support for this. For simple use cases this makes things much more complicated. By using Gradle this can also be done within one simple project. This post shows how you can accomplish this.

A simple use case

Let’s say we have an application that needs to provide an external API. In this case it is required that a specific API JAR file is built that only contains the API classes.

Let’s assume that we already have a standard Java project configured in our “build.gradle” file:

apply plugin: 'java'

Split sources into multiple sourceSets

To be able to build an additional JAR file containing only the API parts, the easiest way is to split the sources into two sourceSets. This leads to a project structure like this:

+ src
  + api
    + java
  + main
    + java
    + resources

To make Gradle aware of the sourceSet and ensure that the output of the new “api” sourceSet is added to the classpath of the “main” sourceSet, we need to do the following in our “build.gradle” file:

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

Now we can split up our classes into API and implementation by putting them in the corresponding source folders.

Building the API JAR

To build an additional jar file that contains the API classes, we need to declare an additional JAR task. In addition we need to ensure that the API JAR is being built every time we run “gradle build” by adding a task dependency. To do those two things, we need to extend our “build.gradle” file by adding the following:

task apiJar(type: Jar) {
    appendix = "api"
    from sourceSets.api.output
}

build.dependsOn apiJar

Think about dependencies

Let’s think about dependencies in this scenario:

  • Dependencies that are needed by the “api” sourceSet are also needed by the “main” sourceSet
  • Dependencies needed by the “main” sourceSet” should not automatically be available for the “api” sourceSet to ensure that the API classes can only use what is intended to be used by them.
  • We don’t want to declare dependencies twice

To solve this, make the “main” sourceSet’s configurations extend from the “api” sourceSet’s configurations:

configurations {
    compile.extendsFrom apiCompile
    runtime.extendsFrom apiRuntime
}

Now you can comfortably declare dependencies:

dependencies {
    apiCompile 'joda-time:joda-time:2.4'
    compile 'org.springframework:spring-core:4.0.6.RELEASE'
}

Putting it all together

The complete build script looks like this:

apply plugin: 'java'

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

configurations {
    compile.extendsFrom apiCompile
    runtime.extendsFrom apiRuntime
}

repositories {
    mavenCentral()
}

dependencies {
    apiCompile 'joda-time:joda-time:2.4'
    compile 'org.springframework:spring-core:4.0.6.RELEASE'
}

jar {
    from sourceSets.api.output
}

task apiJar(type: Jar) {
    appendix = "api"
    from sourceSets.api.output
}

build.dependsOn apiJar
Short URL for this post: https://wp.me/p4nxik-2dn
This entry was posted in Build, config and deploy and tagged , , . Bookmark the permalink.

1 Response to Avoid Multi Module Builds With Gradle

  1. Pingback: IntelliJ 14 WebApp Config Problems | coding

Leave a Reply