'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:

  1. Maven
    • E.g ThreeTen-Extra (their approach boils down to: first compile everything with --release 9, and then compile everything except module-info.java with --release 8).
  2. 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).

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 for main
  • set proper --module-path for mainModuleInfo


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:

  1. disabling org.javamodularity.moduleplugin
  2. removing the custom source set (it wasn't necessary)
  3. adding a custom compileModuleInfoJava task and setting its --module-path to the classpath of the compileJava 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 is 0x35 ? v.53), while other classes are in JDK 8 format (8th byte is 0x34 ? 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