Defining Java Jigsaw modules (JPMS)
The issue
The Jar files of the framework are not Jigsaw modules. Therefore, they are considered as "automatic" modules if put in the module path, or "unnamed" ones if put in the classpath instead.
When relying on Gradle, the Jar files are considered as "unnamed" ones, thus leading to troubles when trying to define our own modules depending on the Cloud framework (i.e. by describing the module through a module-info.java file). Here is a quote from the Gradle's documentation:
You probably want to use external libraries, like OSS libraries from Maven Central, in your modular Java project. Some libraries, in their newer versions, are already full modules with a module descriptor. For example, com.google.code.gson:gson:2.8.9 that has the module name com.google.gson.
Others, like org.apache.commons:commons-lang3:3.10, may not offer a full module descriptor but will at least contain an Automatic-Module-Name entry in their manifest file to define the module’s name (org.apache.commons.lang3 in the example). Such modules, that only have a name as module description, are called automatic module that export all their packages and can read all modules on the module path.
A third case are traditional libraries that provide no module information at all — for example commons-cli:commons-cli:1.4. Gradle puts such libraries on the classpath instead of the module path. The classpath is then treated as one module (the so called unnamed module) by Java.
It is worth to mention the fact an "application" module (i.e. a Jar file containing a module descriptor) can't depend on "unnamed" ones. This means that when defining a dependency to the Cloud framework in a module-info.java like that:
module my.module.name {
requires cloud.annotations;
}
We end up with the following Java error when trying to build the project with Gradle:
module-info.java:2: error: module not found: cloud.annotations requires cloud.annotations;
Even if an IDE like IntelliJ would not comply on its side since handling things differently. Thus, this proves that the framework's Jar files have not been included in the module path by Gradle.
Alternatives considered
One possibility is to adjust the Gradle's behavior at compilation time by adding the classpath in the module path, but that's not something that trivial to do nor convenient.
Relying on a Gradle plugin like extra-java-module-info may be the way to go even if not ideal. That's even more true in my case where I only have the issue with the Cloud framework.
Proposed solutions
Defining an automatic module name
One way to easily solve the issue with Gradle may be by simply defining the Automatic-Module-Name Jar manifest attribute:
jar {
manifest {
attributes("Automatic-Module-Name" to "cloud.annotations")
}
}
Which should lead to the following content in the MANIFEST.MF Jar's file:
Manifest-Version: 1.0
Automatic-Module-Name: cloud.annotations
Instead of the following content as we have for the version 2.0.0:
Manifest-Version: 1.0
However, it would be important to understand why the Gradle team decided to not automatically convert unnamed modules to automatic ones (reference). Especially:
but at most such that declare an automatic module name in the manifest which should be a sign that the library author at least thought about it and at least made it JPMS compatible.
Even if it seems we don't have risks regarding split-package issues:
loicd@DESKTOP-781OBR8 MINGW64 ~/Downloads
$ jar --file=cloud-core-2.0.0.jar --describe-module
No module descriptor found. Derived automatic module.
[email protected] automatic
requires java.base mandated
contains org.incendo.cloud
contains org.incendo.cloud.annotation.specifier
contains org.incendo.cloud.bean
contains org.incendo.cloud.caption
contains org.incendo.cloud.component
contains org.incendo.cloud.component.preprocessor
contains org.incendo.cloud.context
contains org.incendo.cloud.description
contains org.incendo.cloud.exception
contains org.incendo.cloud.exception.handling
contains org.incendo.cloud.exception.parsing
contains org.incendo.cloud.execution
contains org.incendo.cloud.execution.postprocessor
contains org.incendo.cloud.execution.preprocessor
contains org.incendo.cloud.help
contains org.incendo.cloud.help.result
contains org.incendo.cloud.injection
contains org.incendo.cloud.internal
contains org.incendo.cloud.key
contains org.incendo.cloud.meta
contains org.incendo.cloud.parser
contains org.incendo.cloud.parser.aggregate
contains org.incendo.cloud.parser.flag
contains org.incendo.cloud.parser.standard
contains org.incendo.cloud.permission
contains org.incendo.cloud.setting
contains org.incendo.cloud.state
contains org.incendo.cloud.suggestion
contains org.incendo.cloud.syntax
contains org.incendo.cloud.type
contains org.incendo.cloud.type.range
contains org.incendo.cloud.type.tuple
contains org.incendo.cloud.util
contains org.incendo.cloud.util.annotation
loicd@DESKTOP-781OBR8 MINGW64 ~/Downloads
$ jar --file=cloud-annotations-2.0.0.jar --describe-module
No module descriptor found. Derived automatic module.
[email protected] automatic
requires java.base mandated
provides javax.annotation.processing.Processor with org.incendo.cloud.annotations.processing.CommandContainerProcessor org.incendo.cloud.annotations.processing.CommandMethodProcessor
contains org.incendo.cloud.annotations
contains org.incendo.cloud.annotations.assembler
contains org.incendo.cloud.annotations.descriptor
contains org.incendo.cloud.annotations.exception
contains org.incendo.cloud.annotations.extractor
contains org.incendo.cloud.annotations.injection
contains org.incendo.cloud.annotations.method
contains org.incendo.cloud.annotations.parser
contains org.incendo.cloud.annotations.processing
contains org.incendo.cloud.annotations.string
contains org.incendo.cloud.annotations.suggestion
It may still preferable to define module descriptors (module-info.java files).
Defining module descriptors
This task may be less trivial and require more efforts.
Learning more about JPMS is required. A great guide on this front may be the Baeldung one.
I'm ok to open a pull request. I'm just waiting feedback before going further.
After a discussion with Citymonstret on Discord, he said being open for such change as long as it doesn't impact negatively the project's maintainability. The Java Jigsaw project has the goal of improving software maintainability by providing better encapsulation capabilities. Therefore, going toward this direction would be beneficial for the project, thus is aligned with what Citymonstret want.
For the time being, I opened two PRs proposing the easier and less risky solution to implement:
- https://github.com/Incendo/cloud/pull/787
- https://github.com/Incendo/cloud-minecraft/pull/106
Basically, we just add a manifest attribute in Jar files: the Automatic-Module-Name one. Details are provided in PRs' descriptions.
Can you have a look on these two PRs please?
Created PRs:
- https://github.com/Incendo/cloud/pull/787
- https://github.com/Incendo/cloud-minecraft/pull/106
- https://github.com/Incendo/cloud-translations/pull/46
- https://github.com/Incendo/cloud-processors/pull/48
- https://github.com/Incendo/cloud-discord/pull/89
- https://github.com/Incendo/cloud-spring/pull/32
- https://github.com/Incendo/cloud-cli/pull/2
Please, any chance to get these PRs merged and to publish new versions of Cloud modules?
Hello,
Any news on this? Any chance to merge these PRs and release a new version of the framework please? 🙏
@jpenilla made tests with NeoForge since he had a doubt about compatibility. Tests were ok.
The doubt was legitim because of the custom runtime classpath modules registering logic on which the mod loader relies on in order to overcome the JPMS's current limitations. Fortunately, the Automatic-Module-Name manifest attribute is well taken into account without issue.
So... It really seems merging these PRs is ok. Changes are really minimal while avoiding issues to consumers relying on both Gradle and JPMS at the same time. Do we need to check anything else? What can eventually drag you from moving forward?
If you need me to do anything or any adjustement, don't hesitate.
Hello!
Here a new attempt to raise awareness on this topic.