This plugin adds support for building and testing multi-release jars with Gradle. It leverages the toolchain support to compile, test and run your application on the appropriate target JVMs.
This plugin has been successfully tested on Gradle 7.1.1, 7.2, 7.3-rc-1. Earlier releases are not supported.
Note
|
Before using multi-release jars, you should double check that they are the right solution for your problem. Multi-release jars are a good solution when you want to support different runtimes, all having the same set of dependencies. In general, this should be limited to cases where you want to provide a dedicated implementation of a specific class for a particular Java runtime version, without having to use reflection to load it dynamically. Other use cases should be taken with a grain of salt. See https://blog.gradle.org/mrjars |
Usage
plugins {
id 'me.champeau.mrjar' version "0.1"
}
plugins {
id("me.champeau.mrjar") version "0.1"
}
The plugin will expose an extension called multiRelease
which can be used to configure a number of source sets:
multiRelease {
targetVersions 8, 11
}
multiRelease {
targetVersions(8, 11)
}
The first version of the list is the version of the main source set. The code above will then automatically:
-
configure the main source set to use Java 8
-
create an additional source set
src/main/java11
for Java 11 specific classes -
create an additional source set
src/test/java11
for Java 11 specific test classes
The main source set will be compiled using a Java 8 toolchain, while the Java 11 source set will be compiled using a Java 11 toolchain. There are advantages of doing do:
-
JDK specific APIs are only visible to the appropriate source sets
-
each source set can use JDK specific language features
-
no need to use
bootclasspath
tricks
Organization of sources
The main source set, src/main/java
, is always compiled first.
You can see it as the "shared" source sets, where all the common classes live.
All additional source sets created by this plugin will automatically "see" the classes of the main source set.
For example, the following layout is valid:
src
|-- main
|-- java
|-- org/my/company/SharedClass.java
|-- org/my/company/Substituted.java
|-- java11
|-- org/my/company/Substituted.java
The Substituted
class requires SharedClass
at compile time, but you don’t have to duplicate the code: it will automatically be visible when Gradle will compile the Java 11 version.
Testing
One of the challenges of multi-release jars is testing. This plugin provides support for testing in a similar way it does for compilation:
-
for each target language version, a
src/test/javaXXX
source set is created -
each language specific source set can see the classes from the main source set
-
each source set can provide tests which are specific to the language version it is testing
-
each source set can provide test classes which override tests from the main test sources
For example, the following layout is permitted:
src
|-- test
|-- java
|-- org/my/company/CommonTests.java
|-- org/my/company/SubstitutedTests.java
|-- java11
|-- org/my/company/SubstitutedTests.java
|-- org/my/company/VersionSpecificTests.java
When executing the task java11Test
, the following test cases will be executed:
-
CommonTests
from the main tests -
SubstitutedTests
from thejava11
source set -
VersionSpecificTests
from thejava11
source set
Running on different JDKs
Eventually, if you also apply the application
plugin, this plugin will create additional run
tasks preconfigured for each language level.
For example, you select target languages 8, 11 and 17, the plugin will:
-
configure the
run
task to use Java 8 -
configure a
java11Run
task to run with Java 11 -
configure a
java17Run
task to run with Java 17
Version specific dependencies
You can have dependencies specific to a version of Java.
For example, for the java11
source set, it is possible to add a dependency only used to compile the sources found in src/test/java11
:
dependencies {
java11TestImplementation("my:library:1.0")
}
Warning
|
Source set specific dependencies on production code (tests are fine) are a strong indication that a multi-release jar is the wrong abstraction. By definition, a multi-release jar is a single deliverable targetting different platforms at runtime. Because you don’t know what the runtime will be, it means that all downstream consumers will bring the transitive dependencies even if they don’t need them. Therefore, if you have version specific dependencies, it’s a better idea to use Gradle’s variant-aware dependency management mechanism to provide different deliverables per release (eg. mylib-java8.jar and mylib-java11.jar ), because the consumers would only get the dependencies they care about.
|