Skip to content

[MSHADE-265] - Add module-info merging support - #829

Open
wilx wants to merge 4 commits into
apache:masterfrom
wilx:issue-629-module-info-merging
Open

[MSHADE-265] - Add module-info merging support#829
wilx wants to merge 4 commits into
apache:masterfrom
wilx:issue-629-module-info-merging

Conversation

@wilx

@wilx wilx commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Add opt-in merging of Java module descriptors with:

<moduleInfoMode>merge</moduleInfoMode>

The default remains discard, preserving the plugin's existing behavior.

The merge follows a source-amalgamation model: the primary artifact supplies the
module identity, while the shaded contents are treated as if their sources had
been relocated and compiled into that module. By default, the primary module's
exports and opens remain its public boundary. Users can opt into the effective
boundary of embedded modules or add explicit exports, opens, requirements, and
service uses where source-level intent cannot be inferred safely.

The processor:

  • removes requirements on modules absorbed into the shaded artifact;
  • uses only each release view's configured embedded identity, preserving external
    requirements that collide with inactive descriptor or filename-derived names;
  • preserves open-module semantics by emitting no explicit opens for an open primary
    and materializing retained packages from embedded open modules when boundary
    merging is requested;
  • infers external reads and service declarations from embedded explicit and
    automatic modules;
  • applies configured relocations to packages, classes, providers, and services;
  • analyzes automatic-module bytecode and service configuration, failing with
    actionable guidance when dynamic service use cannot be inferred;
  • selects automatic-module analysis from the same filtered, release-aware
    descriptor view used for merging;
  • resolves platform ownership against ct.sym and jmods from a selected JDK,
    with optional toolchain requirements for release-aware analysis; and
  • fails when filtering or minimization would leave an invalid module descriptor.

Multi-release inputs are merged as cumulative release views. The implementation
rebuilds ModulePackages, validates the invariants required of versioned module
descriptors, and raises the modular floor when a provider or public-boundary
change cannot be represented at an earlier release. Earlier releases then retain
an automatic-module bridge with a stable Automatic-Module-Name.

Manifest processing now also accepts reactor output directories. Maven can expose
a reactor dependency as target/classes when separate lifecycle tasks such as
package test are requested. The manifest pre-scan reads a directory manifest
when present, instead of attempting to open the directory as a JAR.

Documentation covers the defaults, public-boundary modes, explicit overrides,
toolchain selection, dynamic service use, and modular-floor behavior.

Fixes #629.

Fixes #581.

Fixes #588.

JIRA: MSHADE-265,
MSHADE-215

Validation

  • mvn test: 104 tests run, zero failures; one skipped.
  • JDK 8 mvn test: 104 tests run, zero failures; 14 modular-runtime tests skipped.
  • mvn -Prun-its verify: 86 integration projects passed, one skipped by its
    JRE-version condition.
  • The focused module-info-merge project passed when the built plugin was run
    with Maven 3.6.3, current Maven 3.x, and Maven 4.
  • The focused MSHADE-215 project fails with Shade 3.6.2 and passes with the
    branch build under Maven 3.6.3, current Maven 3.x, and Maven 4.
  • The original MSHADE-215 reproducer failed under Wine 9 with a Windows Temurin
    21 JVM and Maven 3.9.16; the branch build completed successfully in the same
    environment.
  • mvn site passed.
  • The generated modular JAR passed jar --validate, jar --describe-module,
    and jdeps --check.

Following this checklist to help us incorporate your
contribution quickly and easily:

  • Make sure there is a JIRA issue filed
    for the change (usually before you start working on it). Trivial changes like typos do not
    require a JIRA issue. Your pull request should address just this issue, without
    pulling in other changes.
  • Each commit in the pull request should have a meaningful subject line and body.
  • Format the pull request title like [MSHADE-XXX] - Fixes bug in ApproximateQuantiles,
    where you replace MSHADE-XXX with the appropriate JIRA issue. Best practice
    is to use the JIRA issue title in the pull request title and in the first line of the
    commit message.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Run mvn clean verify to make sure basic checks pass. A more thorough check will
    be performed on your pull request automatically.
  • You have run the integration tests successfully (mvn -Prun-its clean verify).

If your pull request is about ~20 lines of code you don't need to sign an
Individual Contributor License Agreement if you are unsure
please ask on the developers list.

To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.

@wilx
wilx marked this pull request as ready for review July 30, 2026 18:24
@wilx
wilx force-pushed the issue-629-module-info-merging branch 2 times, most recently from 6cbc929 to 38ac043 Compare July 30, 2026 19:38
@wilx
wilx marked this pull request as draft July 30, 2026 20:39
@wilx
wilx force-pushed the issue-629-module-info-merging branch 4 times, most recently from 90dc6e4 to 4cb31a5 Compare July 30, 2026 22:25
@wilx
wilx marked this pull request as ready for review July 30, 2026 22:29
@wilx
wilx force-pushed the issue-629-module-info-merging branch 2 times, most recently from ea84628 to 82abdc8 Compare July 31, 2026 12:47
@wilx
wilx force-pushed the issue-629-module-info-merging branch from 82abdc8 to b3653b4 Compare August 7, 2026 16:13
@wilx

wilx commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased.

@wilx
wilx force-pushed the issue-629-module-info-merging branch from b3653b4 to a6a69e6 Compare August 11, 2026 16:59
@wilx

wilx commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Rebased.

@wilx
wilx force-pushed the issue-629-module-info-merging branch from a6a69e6 to 4569d75 Compare August 11, 2026 17:11
jos.putNextEntry(jarEntry);
outputManifest.write(jos);
} finally {
forceMultiRelease = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This worries me a bit. It suggests there's a specific order to call methods, and thread safety might be in play. Maybe forceMultiRelease and forceAutomaticModuleName should be arguments instead of fields. Not sure, but worth digging into

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have made a change to limit the fields change only around the call to modifyOutputStream.

Threading should not be a problem. The transformers should be per-mojo invocation and within the invocation it is all a single thread. Unless the plugin itself spawns some threads.

@elharo elharo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The more I look at this the more unsure I am. The PR might be 100% correct and exactly what we need to do, but I don't know. What does shading a modular application even mean? Can we make shading work with JPMS, or is this something we fundamentally should not do?

Others might understand the issues involved here better than me.

manifest = jarFile.getManifest();
}
}
if (manifest != null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this might be able to be extracted to a private method and then pushed into the try blocks

@wilx wilx Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This does not look like an improvement to me.

/**
* Controls how module descriptors are handled while shading.
*/
public enum ModuleInfoMode {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do enums have to be public?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes.

*/
public enum ModuleInfoMode {
/** Discard module descriptors, retaining the historical behavior. */
discard,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These are usually UPPER CASE

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

# Merging Java Module Descriptors

By default, the plugin discards every root and versioned `module-info.class` from the inputs. This preserves the
historical behavior: an unmodified descriptor would describe the original artifact rather than the contents of the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

would describe --> describes

```

Merge mode models the shaded JAR as if the retained, relocated source files had been compiled together as one module.
The primary artifact remains authoritative for the module name, version, main class, and, by default, the public

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this right? Shouldn't a shaded jar change the module name? One purpose of shading is to allow multiple versions to exists in the classpath at the same time.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, if you enable the modules merging in your plugin configuration then you are agreeing to have a module. It needs a name. Changing it does not make much sense to me. Users of your project either want to use the original JAR or the shaded JAR, not both at the same time. So having the same module name as the original JAR does not look like a problem.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't like moving a direct negative, but no. Using both the original jar and the shaded jar at the same time is exactly why projects shade. They need to have both in the classpath, typically because some code need a fork or a different version and some code needs the original. Or maybe they need a very specific version and want to make sure the wrong version doesn't get added to the classpath. I suppose they use two different versions in this case but they still have the same module name unless this is changed.

@wilx wilx Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My usual use case is something like Netty being shaded into some sort of connector library JAR under a different package so that the connector does not depend on Netty version in dependencies. Renaming the packages in the normal JAR and distributing that, if I understand you correctly, sound unusual to me. But I accept that I don't know all the people's use cases.

I will add a way to change the module name, if possible.

@elharo elharo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

JPMS is complex so perhaps there's something I'm misunderstanding here, but for now I think this is something we should not do. I'm not saying this PR is bad or can be fixed to be accepted. I'm saying this is a feature we should not have, and further work is unlikely to be accepted absent a clear and compelling explanation of why this should be done.

@wilx

wilx commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

JPMS is complex so perhaps there's something I'm misunderstanding here, but for now I think this is something we should not do. I'm not saying this PR is bad or can be fixed to be accepted. I'm saying this is a feature we should not have, and further work is unlikely to be accepted absent a clear and compelling explanation of why this should be done.

We can discuss the exact function of the merging. But it should be merge eventually because people asked for it.

The mental model for how this works is in the OP. It works as if you pooled all the partial sources into one module, made some necessary adjustments, and compiled it as a single module.

@wilx

wilx commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

I have made the change to allow changing the resulting module name.

@elharo

elharo commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

"But it should be merge eventually because people asked for it." Um, no. People ask for features that are bad ideas all the time. It looks to me like this is one of those times.

wilx added 4 commits September 4, 2026 22:22
Add opt-in merging for root and multi-release module descriptors while preserving the primary module boundary. Cover relocation, services, modular-floor handling, filtering failures, Maven 3/4 compatibility, and reactor output directories during manifest processing.

Keep the PR-owned tests aligned with the JUnit 5 migration on master.

Fixes apache#629.

Fixes apache#581.

Fixes apache#588.
@wilx
wilx force-pushed the issue-629-module-info-merging branch from 4a39b98 to 0792783 Compare September 5, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants