Creating a testJar with Kotlin and Gradle

We have a multi-project build with the following structure:
– mainProject/heldenleben/src/
– mainProject/eulenspiegel/src/

The files in mainProject/eulenspiegel/src/main are built with a standard Gradle task and consumed in mainProject/heldenleben/src/main.

However, let’s say we needed to take the file mainProject/eulenspiegel/src/test/testHelper.kt and make it available for use in mainProject/eulgenspiegel/test/violinTest.kt.

There are 3 elements that need to happen:
1. mainProject/eulenspiegel/src/test/testHelper.kt needs to be compiled to Java bytecode
2. mainProject/eulenspiegel/src/test/testHelper.kt needs to be packaged into a jar file
3. mainProject/heldenleben/src/test needs to find and use the file created in step 2

Let’s look at each step.

1. Compile to Java bytecode
Before files are packaged into jars, they first need to be compiled to bytecode. In our case, we want to compile mainProject/eulenspiegel/src/test/testHelper.kt. Compilation should produce the corresponding classfile at mainProject/eulenspiegel/build/classes/kotlin/test/testHelper.class.

One way to do this is manually, by running the pre-defined testClasses task. However, we can combine it with step 2!

2. Package into jar file
The following code sample written in Kotlin DSL and found in mainProject/eulenspiegel/build.gradle.kts, will compile our source code and package it into a jar file. Let’s step through it.

1 tasks.getByName("assemble").dependsOn("testJar")
3 tasks.register<Jar>("testJar") {
4     archiveFileName.set("eulenspiegel-testHelpers-$version.jar")
5     include("com/eulenspiegel/helpers/*") 
6     from(project.the<SourceSetContainer>()["test"].output)
7 }

Line 1 tells Gradle’s default assemble task to wait for this testJar task that we are defining on line 3. Without line 1, assemble will not fail but it will not produce the testJar output. (You can run the testJar task manually – find it under other/testJar – but then you’d have to add it everywhere assemble is used so much better to just make assemble depend on the testJar.)

Line 3 registers a task of type <Jar> with the name “testJar” – the name is a string and can be whatever you like.

Line 4 sets the name of the archive (jar) file that we are going to produce. $version is a variable created elsewhere in the build.gradle.kts.

Line 5 is optional – in this case, we only want to include a single file. Leave blank to include everything in mainProject/eulenspiegel/src/test.

Line 6 tells us to use the standard source set located at mainProject/eulenspiegel/src/test.

3. Tell mainProject/heldenleben to use our jar file
The relevant sections of mainProject/heldenleben/build.gradle.kts are below. Let’s step through them.

1 tasks.getByName("compileTestKotlin").dependsOn(":eulenspiegel:testJar")
3 repositories {
4     flatDir {
5         dirs("${buildDir.absolutePath.replace("heldenleben", "eulenspiegel")}/libs")
6     }
7 }
8 dependencies {
9     testImplementation("com.mainProject:eulenspiegel-testHelpers:$version")
10 }

Line 1: the standard compileTestKotlin task needs to wait for our testJar task to finish, or it will fail. (And beware – this is likely something that will be caught on CI because you’ll have a local file hanging around in mainProject/eulenspiegel/build/libs that mainProject/heldenleben can find.)

Line 3: by default, Gradle will look remotely for your dependencies. These lines tell it to look for local files.

Line 5: By default, Gradle will look in mainProject/heldenleben/build/libs for the jar file. We need to tell it to look in mainProject/eulenspiegel/build/libs instead.

Line 9: this is the line that actually creates the dependency!

Good luck with your Gradle builds and comment with your thoughts 🙂

1 Comment

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s