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
Pingback: IntelliJ 14 WebApp Config Problems | coding