How to turn a plugin that is not a module into one that is both a module and a Multi-Release JAR File
I have authored two plugins so far. The JarExec plugin and mrJar. They've both already had the mrJar treatment. The gradle-modules-plugin is precisely the type of project for which mrJar was created. That is because: (A) It is a plugin. (B) It is not a module.
The gradle-modules-plugin is a popular tool that build authors use to create modules. Interestingly, however, the gradle-modules-plugin itself is not a module. As ironic as that might be, the discussion of whether or not a Gradle plugin should be a module is not the focus of this demonstration.
So, I want to be clear that the context of this demonstration is specifically this: With the help of mrJar, it is technically possible for any plugin to be a module.
This project uses the code in @paulbakker's and @sandermak's gradle-modules-plugin repository to demonstrate the usage and functionality of the mrJar plugin. It demonstrates the following:
- mrJar's support for transforming a project that is not a module, into a project that is a module
- mrJar's support for creating Modular Multi-Release JAR Files
- mrJar's ability to augment other plugins with functionality they don't themselves implement; and not conflict with those plugins even though they share similar functionality that is also implemented in mrJar
So mrJardularity is a melding of mrJar with those guys' Java 9 Modularity.
This repository contains a copy of code that implements a Gradle plugin. The original code was originally authored by @paulbakker and @sandermak (links above). The code in this repo has been refactored, relatively minimally, to demonstrate functionality of a different plugin authored by me, lingocoder.
These are the steps I took to turn the gradle-modules-plugin into a plugin that is a module:
-
Clone the original gradle-modules-plugin repo (link above) or download it as a zip
-
Run
gradle check
to run the tests and verify the original project builds successfullya. Observe that, before being given the mrJar treatment, the original gradle-modules-plugin that was assembled by Gradle's built-in archiver, is only an automatic module:
jar -f build/libs/moduleplugin-1.5.1-SNAPSHOT.jar No module descriptor found. Derived automatic module. [email protected] automatic requires java.base mandated contains org.javamodularity.moduleplugin contains org.javamodularity.moduleplugin.extensions contains org.javamodularity.moduleplugin.internal contains org.javamodularity.moduleplugin.shadow.javaparser contains org.javamodularity.moduleplugin.shadow.javaparser.ast ...
-
Apply the mrJar plugin in the
plugins{ }
block of the project'sbuild.gradle
:
a.id 'com.lingocoder.mrjar' version '0.0.10'
-
Configure the properties of mrJar's
mrjar
extension:mrjar{ releases = [JavaVersion.VERSION_1_9,JavaVersion.VERSION_12] packages =['org.javamodularity.moduleplugin.extensions', 'org.javamodularity.moduleplugin.internal', 'org.javamodularity.moduleplugin.tasks'] }
-
cd
into the root folder of the project and initialize the project for mrJar processing, with itsmrinit
task:a.
gradlew mrinit
-
Copy classes from the main source set to their corresponding release-specific packages you just created in Step 3+4
• I, more or less randomly, chose the minimal set of gradle-modules-plugin classes sufficient to demonstrate mrJar's MRJAR File creation functionality. Whether or not @paulbakker and @sandermak would spin off those particular classes to target particular releases is, once again, off-topic for a demo.
• Similarly, the functionality of the particular classes I chose arbitrarily for this demo, is not important in the context of this particular demo.
a) make appropriate release-specific changes to the respective source files
- since this demo is about applying mrJar — and not about coding best practices — I made the simplest release-specific changes to the code that sprang to mind. In a real-world project, you would make real-world changes in this step. -
Create
module-info.java
files in the base directory of the per-release source sets created abovea. add
requires
,exports
,uses
, etc. as appropriate for your module -
Assemble the MRJAR File artifact
a. For the particular use case of the original gradle-modules-plugin project, that project is scripted to publish its artifact to a local Maven repository. So I ran Gradle's
publish
task:gradlew publish
b. There are bound to be JPMS read access-related errors in this step, which you will need to solve before the modular MRJAR artifact can be built successfully. Solving those kinds of problems is out of scope for this kind of demo. Specific solutions would depend on the specific details of a specific project. The problems I encountered at this step for the original gradle-modules-plugin were, luckily, solved by Gradle's dependency management capabilities. See this repo's refactored build script for the details.
c. After each iteration of solving module-graph resolution-related issues, loop back to Step 8a until
gradle publish
reports:BUILD SUCCESSFUL in N seconds
-
Confirm that mrJar did indeed turn what was before an automatic module plugin into a plugin that is now actually a fully-fledged module itself; and one that supports multiple different releases to boot1:
jar -f consuming/maven-repo/org/javamodularity/j9dularity/1.5.1-mrjar/j9dularity-1.5.1-mrjar.jar --describe-module releases: 9 12 java9.modularity jar:file:///.../consuming/maven-repo/org/javamodularity/j9dularity/1.5.1-mrjar/j9dularity-1.5.1-mrjar.jar/!module-info.class exports org.javamodularity.moduleplugin exports org.javamodularity.moduleplugin.extensions exports org.javamodularity.moduleplugin.internal exports org.javamodularity.moduleplugin.tasks requires com.github.javaparser.core requires com.github.javaparser.symbolsolver.core requires gradle.api requires java.base mandated
-
Run the tests again to confirm the plugin is still good to go after some pretty major refactoring:
gradlew check ... BUILD SUCCESSFUL in 32m 53s 10 actionable tasks: 10 executed
Follow those steps and you too can turn any Java-based library project (plugin or not) into an MRJAR File that is also a bona fide JPMS module.
__
1 — Notice I changed the name of the plugin in this demo's refactored build script from the original „modulesPlugin“ to „j9dularity“. The intention of that change is to make it super obvious that the code and artifacts produced by the refactored build scripts in this demo repo are definitely not to be confused with those of the original.