'Compile a JDK 8 project + a JDK 9 "module-info.java" in Gradle
I'm working on a Java library targeting JDK 8, and I'm building it in Gradle 5 using OpenJDK 11. In order to target JDK 8, I'm javac's --release
option.
However, I'd also like my library to be JPMS-compatible. In other words:
- I'd like to provide a
module-info.class
compiled with--release 9
(option 3 in Stephen Colebourne's scale), - while all the rest is compiled with
--release 8
.
MCVE
build.gradle:
plugins {
id 'java'
id 'org.javamodularity.moduleplugin' version '1.4.1' // *
}
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.6'
}
compileJava.options.compilerArgs.addAll(['--release', '9']) // **
* org.javamodularity.moduleplugin
sets --module-path
for compileJava
** there's no Gradle DSL for --release
yet: #2510
src/main/java/module-info.java:
module pl.tlinkowski.sample {
requires lombok;
exports pl.tlinkowski.sample;
}
src/main/java/pl/tlinkowski/sample/Sample.java:
package pl.tlinkowski.sample;
@lombok.Value
public class Sample {
int sample;
}
This MCVE compiles, but all the classes (instead of only module-info.class
) are in JDK 9 class format (v.53).
Other build tools
What I want to do is certainly possible in:
- Maven
- E.g ThreeTen-Extra (their approach boils down to: first compile everything with
--release 9
, and then compile everything exceptmodule-info.java
with--release 8
).
- E.g ThreeTen-Extra (their approach boils down to: first compile everything with
- Ant
- E.g. Lombok (their approach boils down to: have
module-info.java
in a separate "source set" - main source set is compiled with--release 8
, and "module info" source set is compiled with--release 9
).
- E.g. Lombok (their approach boils down to: have
What I tried
I liked Lombok's approach, so I manipulated the source sets in build.gradle
as follows:
sourceSets {
main { // all but module-info
java {
exclude 'module-info.java'
}
}
mainModuleInfo { // module-info only
java {
srcDirs = ['src/main/java']
outputDir = file("$buildDir/classes/java/main")
include 'module-info.java'
}
}
}
Then, I configured a task dependency and added proper --release
options to both compilation tasks:
classes.dependsOn mainModuleInfoClasses
compileJava.options.compilerArgs.addAll(['--release', '8'])
compileMainModuleInfoJava.options.compilerArgs.addAll(['--release', '9'])
If I compile now, I get:
error: package lombok does not exist
So I still don't know how to instruct org.javamodularity.moduleplugin
to:
- not use
--module-path
formain
- set proper
--module-path
formainModuleInfo
Solution 1:[1]
EDIT: This functionality is now supported by Gradle Modules Plugin since version 1.5.0.
Here's a working build.gradle
snippet:
plugins {
id 'java'
id 'org.javamodularity.moduleplugin' version '1.5.0'
}
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.6'
}
modularity.mixedJavaRelease 8
OK, I managed to get this working by:
- disabling
org.javamodularity.moduleplugin
- removing the custom source set (it wasn't necessary)
- adding a custom
compileModuleInfoJava
task and setting its--module-path
to the classpath of thecompileJava
task (inspired by this Gradle manual)
Here's the full source code of build.gradle
:
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.6'
}
compileJava {
exclude 'module-info.java'
options.compilerArgs = ['--release', '8']
}
task compileModuleInfoJava(type: JavaCompile) {
classpath = files() // empty
source = 'src/main/java/module-info.java'
destinationDir = compileJava.destinationDir // same dir to see classes compiled by compileJava
doFirst {
options.compilerArgs = [
'--release', '9',
'--module-path', compileJava.classpath.asPath,
]
}
}
compileModuleInfoJava.dependsOn compileJava
classes.dependsOn compileModuleInfoJava
Notes:
- it compiles ?
- I verified that
module-info.class
is in JDK 9 format (8th byte is0x35
? v.53), while other classes are in JDK 8 format (8th byte is0x34
? v.52) ? - however, disabling
org.javamodularity.moduleplugin
is unsatisfactory, because it means that tests will no longer run on module path, etc. ?
Solution 2:[2]
I have developed a Gradle plugin for this: https://github.com/Glavo/module-info-compiler
I have tried Gradle Modules Plugin, but there are still some troublesome problems, so I developed this plugin, a compiler specifically used to compile module-info.java
.
It is not implemented by calling javac. It is a complete compiler that can run above Java 8. It recognizes the syntax of module-info.java
and generates the corresponding module-info.class
file according to it.
It only checks the grammar, and does not actually check those packages, classes or modules, so it can work without configuration of any module path.
This Gradle plugin has processed everything for you. For a Java 8 project containing module-info.java
, you only need to do this:
plugins {
id("java")
id("org.glavo.compile-module-info-plugin") version "2.0"
}
tasks.compileJava {
options.release.set(8)
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Glavo |