Skip to content

Feature request: Separate compilation of module-info.java #72

@tlinkowski

Description

@tlinkowski

Overview

I'd like to request a feature for library developers who need to target JDK 6-8 but who also want to provide a module-info.class (as per Option 3 by @jodastephen).

The proposed feature has two parts:

  1. Low-level API: separate compilation of module-info.java.
  2. High-level API: easy setting of javac --release option, separately for main Java code and for module-info.java.

Justification

General

Library maintainers need to target JDK 6-8 because these JDKs (especially JDK 8) are still very popular (taking ~95% share in 2018, and predicted to take ~90% in 2019).

Still, those maintainers could make the most of JPMS by:

  • providing module-info.class for consumers who put the library on module path,
  • compiling module-info.java against the remaining classes of this module and against other modules (which provides better encapsulation and prevents split packages).

So providing a module-info.class is like saying:

Hey, I'm JPMS-compatible! You can safely put me on the module path — I guarantee to have no split packages!

To sum up, I see this feature request as great means of promoting JPMS! Would you agree, @paulbakker, @sandermak?

Examples

Here are some popular libraries that provide module-info.class while targeting JDK 6-8, e.g.:

  • Maven:
  • Ant:
    • Lombok (JDK 9 module-info.class in root dir)

There are also some libraries that:

Vavr /ˈveɪ.vɚ/

Let's have a closer look at vavr's case:

  1. It's a quite popular library (3000+ Github stars) targeting JDK 8.
  2. Its creator, @danieldietrich, is (or was?) a believer in JPMS.
  3. However, as Multi-Release JAR that contains Java 8 binaries + Java 9 module-info.class vavr-io/vavr#2230 (comment) shows, he backed out of it (partly because the required Gradle configuration was too complex).
  4. I personally think it's a shame if such a popular library cannot be easily shipped with a module-info.class while still targeting JDK 8.

Proposed DSL

Note: Everything in this section (especially naming) is TBD.

Low-level API

Extra property on moduleOptions extension:

build.gradle
compileJava.moduleOptions.compileModuleInfoSeparately = true
build.gradle.kts
tasks {
  compileJava.moduleOptions.compileModuleInfoSeparately = true
}

High-level API

Special modularity extension with two functions:

  • for releasing to JDK 6-8:
    • mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease = 9)
  • for releasing to JDK 9+
    • standardJavaRelease(int mainJavaRelease)

For example, the most common configuration (JDK 8 + JDK 9 module-info.class) would be a one-liner:

build.gradle
modularity.mixedJavaRelease 8
build.gradle.kts
modularity.mixedJavaRelease(8)

Proposed behavior

module-info.java location

I propose to leave module-info.java in src/main/java (I don't see any need to put it in a separate source set directory).

module-info.class location

There are two places where module-info.class can end up:

  1. in the root output directory (natural; corresponds to module-info.java location)
  2. in META-INF/versions/9 (Multi-Release JAR, AKA MRJAR)

Having read a post on MRJARs by @melix, I'm rather suspicious of MRJARs, and so I prefer option 1.

On the other hand, @gunnarmorling claims that it's better to use option 2 because module-info.class "will be out of the way in most cases when being used on an older Java version". But I don't really know why, because as far as I understand, any tool that scans the classpath (like Guava's ClassPath) will return module-info.class no matter where it is (based on its .class extension). @gunnarmorling, would you care to give me a pointer here?

Alternatives

ModiTect

It's fair to mention ModiTect (by @gunnarmorling) and its Gradle plugin (by @siordache) here.

However, ModiTect does much more than I request here: ModiTect actually generates module-info.class from a special notation (there's even no module-info.java there edit: it can actually parse module-info.java). Personally, I find it too complex for my needs.

Badass-Jar plugin

@siordache also created a Gradle plugin that lets one "seamlessly create modular jars that target a Java release before 9".

It looks quite nice, however:

  • I'm not sure how it plays along with org.javamodularity.moduleplugin (and I'd like to use it for patching modules, testing on module-path, etc.)
  • in order to build the proper JAR and make sure your module-info.java is correct, you actually have to run the build twice (./gradlew jar and ./gradlew -PjavaCompatibility=9 jar) - too complex
  • it doesn't use javac's --release option, which (as opposed to using -source and -target) guarantees that only the right APIs are referenced (e.g. it throws when compiling optional.isEmpty() with --release 8)
  • it produces only Multi-Release JARs, and I'm somewhat skeptical about them (as I mentioned before)

Related StackOverflow questions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions