Skip to content

Fix #13068: make PluginDependenciesResolver methods default - #13075

Merged
ascheman merged 2 commits into
apache:masterfrom
aschemaven:bugfix/13068-plugin-dependencies-resolver-defaults
Sep 8, 2026
Merged

Fix #13068: make PluginDependenciesResolver methods default#13075
ascheman merged 2 commits into
apache:masterfrom
aschemaven:bugfix/13068-plugin-dependencies-resolver-defaults

Conversation

@ascheman

@ascheman ascheman commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Forward port of 349395c, merged into maven-4.0.x via #13069.

Closes #13068.

resolveCoreExtensionAndFlatten and resolvePluginAndFlatten were added to the internal PluginDependenciesResolver as abstract methods. That happened on both lines — #12329 here, #12335 on maven-4.0.x — but the fix so far landed only on maven-4.0.x, so 4.1.0 would reintroduce the identical regression.

Implementations that live out of tree are compiled against one Maven and run on another, so the first plugin resolution dies with AbstractMethodError. IntelliJ IDEA, Eclipse m2e and NetBeans all override this component; IntelliJ implements the interface directly and therefore breaks. Christofer Dutz confirmed on Apache PLC4X that a build carrying this change imports cleanly where stock rc-6 fails.

What differs from the maven-4.0.x commit

The production change and the unit test are byte-identical. Only the IT needed adjusting, because master has moved on in three ways:

  • No TestSuiteOrdering entry. That class now orders by name pattern (MavenITgh<number>) instead of keeping an explicit list, so MavenITgh13068… is picked up automatically. The file is untouched here.
  • IT harness. AbstractMavenIntegrationTestCase no longer takes a version range, extractResources returns Path, and newVerifier(String) is deprecated in favour of newVerifier(Path). The IT was rewritten to the current idiom.
  • ITs run under failsafe here, not surefire.

Verification

Run locally against this branch:

mvn verify -Papache-release -Dgpg.skip=true     BUILD SUCCESS   (0 javadoc findings)
unit  PluginDependenciesResolverDefaultMethodsTest   Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
IT    MavenITgh13068LegacyPluginDependenciesResolverTest
      failsafe:integration-test @ core-it-suite
      Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
      [gh-13068] legacy PluginDependenciesResolver installed
      [gh-13068] resolvePlugin reached for maven-clean-plugin

The two marker lines are asserted by the test, so it cannot pass without the extension actually displacing the default component. On maven-4.0.x the same IT was verified red without the change, failing with AbstractMethodError at DefaultMavenPluginManager.createPluginRealm.

Note

The resolveCoreExtensionAndFlatten default is best effort — a dedicated implementation additionally reads the extension's artifact descriptor to apply relocations and run MavenPluginDependenciesValidator. Same caveat as on the 4.0.x PR, where @gnodet approved it as is.

resolveCoreExtensionAndFlatten and resolvePluginAndFlatten were added as
abstract methods in 4.0.0-rc-6, forward-porting the Maven 3.10.0 changes
(apache#12335). The interface is documented as internal, but it is the only
hook Maven offers for influencing plugin resolution, and every major
Java IDE overrides it: IntelliJ IDEA, Eclipse m2e and NetBeans. Those
implementations live out of tree and are compiled against one Maven
while running on another, so adding abstract methods turns plugin
resolution into an AbstractMethodError.

Both methods now default to the pre-existing resolvePlugin, which
DefaultPluginDependenciesResolver already treats as an alias -- there,
resolvePlugin delegates to resolvePluginAndFlatten. resolvePlugin itself
deliberately stays abstract: a default there would let an implementation
overriding neither method recurse infinitely.

The default for resolveCoreExtensionAndFlatten is best effort. A
dedicated implementation additionally reads the extension's artifact
descriptor to apply relocations and run MavenPluginDependenciesValidator.

Verified against the class shipped in IntelliJ IDEA 2026.2.2, which
implements resolvePluginAndFlatten but not resolveCoreExtensionAndFlatten
and therefore still fails on stock rc-6.

Adds a unit test pinning the compatibility contract and a core IT that
builds an extension against maven-core 4.0.0-rc-5 -- the last release
before the methods existed -- and runs a build through it.

Verified by Christofer Dutz (@chrisdutz) on Apache PLC4X: a build
carrying this change imports cleanly in IntelliJ IDEA, where stock rc-6
fails.

Forward port of 349395c from maven-4.0.x. The breaking change was
ported to both lines (apache#12329 master, apache#12335 maven-4.0.x); the fix so far
only to maven-4.0.x, so 4.1.0 would reintroduce the regression.

Two adjustments were needed for master, both in the IT only:

- no TestSuiteOrdering entry: that class now orders by name pattern
  (MavenITgh<number>) instead of keeping an explicit list
- the IT harness moved on: AbstractMavenIntegrationTestCase no longer
  takes a version range, extractResources returns Path, and
  newVerifier(String) is deprecated in favour of newVerifier(Path)

The production change and the unit test are byte-identical to
maven-4.0.x.

Closes apache#13068

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2HnhKt7MJWYsSSVUmPrvU
@ascheman ascheman added this to the 4.1.0 milestone Sep 8, 2026
@ascheman ascheman added the bug Something isn't working label Sep 8, 2026

@gnodet gnodet 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 fix is correct. Two abstract methods made default — the minimal change needed to restore backward compatibility with every out-of-tree implementation of PluginDependenciesResolver. CI is green across all platforms (JDK 17/21/25, Linux/macOS/Windows). The implementation is byte-identical to the maven-4.0.x commit.

One low-severity gap in the unit test: both delegation tests pass null as the session argument, then either assert assertNull(resolver.session) (which is trivially true) or don't check session at all. The test therefore doesn't actually exercise that the session is forwarded through the default method chain — it would pass even if the default method dropped the session on the floor. Flagging it as a nit; it doesn't affect correctness of the production fix, only test coverage depth.

This review was generated by an AI agent, Hermès, on behalf of @gnodet.

assertSame(artifact, resolver.pluginArtifact);
assertSame(filter, resolver.dependencyFilter);
assertSame(repositories, resolver.repositories);
assertNull(resolver.session);

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.

🔍 Nit — trivial session assertion: null is passed as the session argument and this assertion checks that null comes out the other side — which is guaranteed regardless of whether the default method actually forwards the argument. To make this test meaningful, pass a non-null mock or stub session and assert assertSame:

Suggested change
assertNull(resolver.session);
assertNull(resolver.session);

Suggested improvement (cannot be a one-click suggestion due to the required change in the call site at line 104):

// line 103-110
RepositorySystemSession session = mock(RepositorySystemSession.class);
assertSame(RESULT, resolver.resolvePluginAndFlatten(plugin, artifact, filter, repositories, session));

assertSame(plugin, resolver.plugin);
assertSame(artifact, resolver.pluginArtifact);
assertSame(filter, resolver.dependencyFilter);
assertSame(repositories, resolver.repositories);
assertSame(session, resolver.session);

(Add import static org.mockito.Mockito.mock; or use Mockito.mock(RepositorySystemSession.class) — the project already has Mockito on the test classpath.)

assertSame(plugin, resolver.plugin);
assertNull(resolver.pluginArtifact, "the extension's main artifact is resolved from the plugin GAV");
assertSame(filter, resolver.dependencyFilter);
assertSame(repositories, resolver.repositories);

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.

🔍 Nit — session forwarding not tested: resolveCoreExtensionAndFlatten is called with null for the session, but the test has no assertion that the session is passed through to resolvePlugin. A non-null session should be passed and verified with assertSame(session, resolver.session) — same pattern as the sibling test.

The delegation tests passed null as the session and then asserted
assertNull on it, which is trivially true -- they would have passed even
if the default method dropped the session. The core-extension test did
not check the session at all.

Both now pass a mocked RepositorySystemSession and assert it arrives at
resolvePlugin. Verified red by making the default method pass null:
"expected: <Mock for RepositorySystemSession> but was: <null>".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2HnhKt7MJWYsSSVUmPrvU

@gnodet gnodet 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.

Re-review after second commit ("Address review: assert the session is forwarded", 62967e1).

Both nits from the first pass are resolved:

  • resolvePluginAndFlattenDelegatesToResolvePlugin: now uses mock(RepositorySystemSession.class) and asserts assertSame(session, resolver.session) — the delegation is actually exercised.
  • resolveCoreExtensionAndFlattenDelegatesToResolvePlugin: same pattern, line 135 now asserts session forwarding.

The fix itself is solid: two methods converted from abstract to default, Javadoc correctly documents the delegation chain and the circular-recursion trap for any implementation that tries to delegate resolvePlugin back to resolvePluginAndFlatten without overriding the latter. The newerMethodsAreDefaultMethods reflection test is a clean regression guard against future accidental reintroduction of abstract. The IT is faithful to current master conventions (newVerifier(Path), no version range constructor, failsafe) and the extension POM correctly compiles against 4.0.0-rc-5 to reproduce the binary-compat scenario.

No outstanding findings. Ready to merge.

This review was generated by an AI agent, Hermès, on behalf of @gnodet.

@ascheman
ascheman merged commit e42bc4b into apache:master Sep 8, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PluginDependenciesResolver: abstract methods added in 4.0.0-rc-6 break IDE embedders with AbstractMethodError

2 participants