Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jpms): add module-info.java descriptors #556

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

sgammon
Copy link

@sgammon sgammon commented Mar 13, 2024

Summary

This PR offers a minimal changeset to safely ship a module-info.java descriptor for the org.reactivestreams API artifact. The JAR is shipped as an MRJAR, with a module-info.class descriptor located in META-INF/versions/9.

This keeps the JAR compatible with JDK 6, while supporting full modularity for newer versions of Java.

Based on the current structure of the codebase, I tried to keep this PR as minimal as possible. Unfortunately, Gradle 6 does not yet have the release flag for the Java compiler, so I've upgraded Gradle to 7.x. The latest version is 7.6.4. Other than this and the module-info.java itself, I've added a task to validate the multi-release JAR as a module.

The tests pass and everything seems fine from local, of course, but this should probably be tested with downstream artifacts. I can put together a small integration test harness which uses these JPMS-enabled artifacts with popular Reactive Streams projects to make sure there is a clear picture of impacts.

Fixes and closes #531

Build tooling

The Gradle upgrade clears the way for some other changes, which are more optional/unrelated; for example, using Gradle Toolchains to enforce JDK 6 compatibility. I'm not sure if the Reactive Stream authors/maintainers want these changes, so I have split them off into another PR, which is a superset of this one: #557

Both PRs enable JPMS support, and only one or the other should be merged (if any). That PR has its own explanation about the changes enclosed.

JAR Structure

After applying this PR, the JAR structure for the api artifact is:

Screenshot 2024-03-12 at 6 26 01 PM

Care has been taken not to duplicate classes in the Java 9 class root, and to preserve the FlowAdapters.

JAR manifest:

Screenshot 2024-03-12 at 6 25 19 PM

OSGi manifest (Java 9+):

Screenshot 2024-03-12 at 6 26 34 PM

Changelog

  • feat(jpms): add module-info.java to api
  • chore: adjust builds where needed to release as MRJAR artifacts
  • chore: adjust BND tools to not interfere with MRJAR classes
  • chore: upgrade gradle → 7.6.4
  • chore: apply updates to groovy dsl for publishing with gradle v7

- feat(jpms): add `module-info.java` to `api`
- chore: adjust builds where needed to release as MRJAR artifacts
- chore: adjust BND tools to not interfere with MRJAR classes
- chore: upgrade gradle → `7.6.4`
- chore: apply updates to groovy dsl for publishing with gradle v7

Signed-off-by: Sam Gammon <[email protected]>
Comment on lines +16 to +20
if (isJdk9OrGreater) java9 {
java {
srcDirs = ['src/main/java', 'src/main/java9']
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now a second source set, instead of an additional directory in the main source set. This allows a second compile task to target a different JVM bytecode level.

Comment on lines +23 to 25
configurations {
mrjarArtifact
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MRJAR artifact is exported from the project, so that tests can depend on it. This is important to accurately test JPMS, otherwise Gradle will put these classes on the classpath downstream, which runs tests without modular encapsulation.

Comment on lines +28 to +33
if (isJdk9OrGreater) into('META-INF/versions/9') {
from(sourceSets.java9.output) {
include '**/FlowAdapters*'
include '**/module-info.class'
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amends the JAR with the META-INF/versions/9 root, from the output of the sourceSets.java9 compile. include is used here to avoid duplicating classes which reside in the root of the module, at JDK 6, which are identical anyway.

Comment on lines -27 to +43
'Export-Package': 'org.reactivestreams.*',
'Automatic-Module-Name': 'org.reactivestreams',
'Bundle-SymbolicName': 'org.reactivestreams.reactive-streams'
'Export-Package': 'org.reactivestreams.*,!META-INF.*',
'Multi-Release': true,
'Bundle-SymbolicName': 'org.reactivestreams.reactive-streams',
'-fixupmessages': '^Classes found in the wrong directory: .*'
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Export-Package has added !META-INF.* to avoid processing the Java 9 class root; Multi-Release was added, to replace Automatic-Module-Name. -fixupmessages avoids a BND warning about classes in the Java 9 root.

Comment on lines +54 to +61
if (isJdk9OrGreater) {
compileJava9Java {
group = "build"
description = "Compile Java 9 classes for MR JAR"

sourceCompatibility = 9
targetCompatibility = 9
}
Copy link
Author

@sgammon sgammon Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compile task; in this PR, the task is conditional based on whether the build-time JVM is at least Java 9. The existing build behavior is preserved in this way.

In the other PR, this is addressed by Gradle Toolchains, so the build task is not conditional and is always expected to run at JDK 9 or greater during build time.

Comment on lines +8 to +9
module org.reactivestreams {
exports org.reactivestreams;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module name matches the previous Automatic-Module-Name. The exported package is the only package in the JAR, so export-related risk is minimal. If it needs to be an open module let me know.

Comment on lines +14 to +18
def sonatypeRepo = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
def sonatypeSnapshots = 'https://oss.sonatype.org/content/repositories/snapshots/'

def distributionRepository = properties["distributionRepository"] ?: sonatypeRepo
def snapshotsRepository = properties["snapshotsRepository"] ?: sonatypeSnapshots
Copy link
Author

@sgammon sgammon Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I included a small unrelated change which allows parameterization of the deploy repositories. This will allow me to easily test integration of this JPMS support with downstream libraries. It should be completely inert to this codebase.

The username and password for releases are still required, but only when the deployment repo is Sonatype.

Comment on lines -56 to 66
apply plugin: "maven"
apply plugin: "signing"
apply plugin: "publishing"
apply plugin: "maven-publish"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small build cleanup because Gradle 7.x eliminates the maven plugin in favor of publishing. Other than DSL updates, the logic is unchanged.

Comment on lines +21 to +27
compileJava {
options.release = 9
}

compileTestJava {
options.release = 9
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always tests at --release=9, because Flow isn't available until then.

@@ -1,7 +1,7 @@
description = 'reactive-streams-tck'
dependencies {
api group: 'org.testng', name: 'testng', version:'7.3.0'
api project(':reactive-streams')
api project(path: ':reactive-streams', configuration: 'mrjarArtifact')
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wires together that the TCK depends on the MRJAR, rather than the unwrapped classes.

@sgammon
Copy link
Author

sgammon commented Mar 13, 2024

cc / @viktorklang, @ktoso This is ready for review. Let me know if you guys are open to this 😁

sgammon added a commit to elide-dev/jpms that referenced this pull request Mar 13, 2024
- chore: add `org.reactivestreams` submodule
- chore: pin to reactive-streams/reactive-streams-jvm#556
- chore: sync repository
- chore: add to version catalog

Relates-To: #1
Signed-off-by: Sam Gammon <[email protected]>
sgammon added a commit to elide-dev/jpms that referenced this pull request Mar 13, 2024
- chore: add `org.reactivestreams` submodule
- chore: pin to reactive-streams/reactive-streams-jvm#556
- chore: sync repository
- chore: add to version catalog

Relates-To: #1
Signed-off-by: Sam Gammon <[email protected]>
sgammon added a commit to elide-dev/jpms that referenced this pull request Mar 13, 2024
- chore: add `org.reactivestreams` submodule
- chore: pin to reactive-streams/reactive-streams-jvm#556
- chore: sync repository
- chore: add to version catalog

Relates-To: #1

Signed-off-by: Sam Gammon <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider adding a full Java module descriptor
1 participant