Repository navigation
Fix #13726: remove the stale jansi export from extension.xml - #13727
arturobernalg wants to merge 4 commits into
Conversation
…sion descriptor JAnsi stopped being a maven-core dependency when it was migrated to JLine (Migrate from JAnsi to JLine, introduce MessageBuilderFactory, apache#11874), but maven-core/src/main/resources/META-INF/maven/extension.xml still declares org.fusesource.jansi:jansi as an exportedArtifact and org.fusesource.jansi.* as an exportedPackage. DefaultArtifactFilterManager builds its classpath exclusion filter from these declarations (via CoreExports), so any plugin that declares jansi as its own dependency silently has it stripped from its realm, even though maven-core does not actually provide it. This broke at least the jkube Kubernetes Maven plugin on 3.10.0. Added ExtensionDescriptorBuilderTest#testCoreExtensionDescriptorHasNoStaleExports, which loads maven-core's own production extension.xml from the classpath and asserts none of its exports mention jansi -- fails on the current descriptor, passes once the stale entries are removed. Full maven-core suite, checkstyle and spotless pass locally.
|
It was intentionally to support backward compatibility with plugins used |
gnodet-bot
left a comment
There was a problem hiding this comment.
The fix itself is correct: org.fusesource.jansi:jansi is not a maven-core dependency on 3.10.x (removed in #11874), so keeping it in exportedArtifacts and exportedPackages actively harms plugins that declare jansi themselves — DefaultArtifactFilterManager filters it from their realm even though Maven core does not provide it. Removing the stale entry is the right fix.
Re: @slawekjaranowski's backward-compatibility concern — the export was providing negative compatibility: plugins that genuinely need jansi were having it silently stripped. Removing the export restores their ability to bring their own jansi. Any plugin relying on Maven core to inject jansi into its realm was already broken on 3.10.x (since core doesn't ship it). So removing the export is strictly a fix, not a break.
One nit on the test Javadoc:
This review was generated by an AI agent, Hermès on behalf of @gnodet.
|
@slawekjaranowski thanks for the context. I checked how that intent holds up now:
|
gnodet-bot
left a comment
There was a problem hiding this comment.
Following up on my earlier review — the prior nit (wrong issue reference in the test Javadoc: MNG-8724 should be #13726) is addressed in commit 1de59523. All prior findings resolved.
The fix is correct and the test is well-designed: it loads maven-core's own production extension.xml via the classloader and asserts noneMatch(a -> a.contains("jansi")) on both exported artifacts and packages. Statically verified: the pre-fix descriptor contains org.fusesource.jansi:jansi → noneMatch returns false → assertTrue fails; post-fix → passes. The test correctly gates against future stale export regressions.
Note: master's impl/maven-core/src/main/resources/META-INF/maven/extension.xml also still exports org.fusesource.jansi:jansi and org.fusesource.jansi.Ansi — worth a separate follow-up if jansi is similarly not a maven-core dependency on 4.x.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
|
I have added a integration test for color output this test should be extended with execution:
add assertions if color output is generated |
|
We have in Maven core:
which replace original class from JAnsi by exporting it in extension.xml and forward call to existing already initialized JLine terminal I don't know if we can initialize two terminal one by JAnsi and one by JLine in the same process. |
@slawekjaranowski is right that removing the exportedPackage entry risks a real regression: maven-jline ships Ansi.java specifically to forward old JAnsi API calls to the already-initialized JLine terminal, for maven-shared-utils backward compatibility, and that forwarding only reaches plugin realms through this export. Checked plexus-classworlds: ClassRealm's SelfFirstStrategy/ ParentFirstStrategy call loadClassFromImport() first, and on ClassNotFoundException from the imported (Maven API) realm, fall back to loadClassFromSelf() -- the plugin's own classpath. So the exportedPackage entry only "wins" for org.fusesource.jansi.Ansi (the one class maven-jline actually ships); any other class in that package, e.g. AnsiConsole, which maven-jline does not provide, falls through to whatever the plugin brought itself. That import is safe by construction and was never the cause of apache#13726. The actual bug is exportedArtifact: DefaultArtifactFilterManager uses it as a dependency-resolution-time exclusion list (see DefaultClassRealmManager's providedArtifacts), stripping the real org.fusesource.jansi:jansi artifact out of a plugin's resolved classpath entirely -- so a plugin needing AnsiConsole never even gets its own copy added to its realm, regardless of the package import. That is the only entry this PR still removes. Narrowed the regression test to the exportedArtifacts list only, and updated its Javadoc to explain why exportedPackage is excluded from the assertion. Full maven-core suite, checkstyle and spotless pass locally.
|
@slawekjaranowski you were right, I was missing something. I checked I then checked how plexus-classworlds actually resolves an import: The actual bug is the Pushed 427555f: restored |
|
Maybe we can also add |
gnodet-bot
left a comment
There was a problem hiding this comment.
Previous findings are all addressed. The new commit (427555f) correctly restores the org.fusesource.jansi.* exportedPackage — the exportedPackage mechanism is a ClassRealm import with graceful fallback (CNFE → null → falls through to the plugin's own classpath), not the classpath-stripping exportedArtifact. Removing only the exportedArtifact is the right fix.
One minor inaccuracy in the new XML comment (inline).
This review was generated by an AI agent, Hermès on behalf of @gnodet.
@slawekjaranowski's suggestion: maven-jline ships exactly one class under org.fusesource.jansi (Ansi.java, the compat shim), so export that FQCN instead of the org.fusesource.jansi.* wildcard. Checked ClassRealm's Entry#matches(): a non-wildcard entry (no trailing ".*") matches only that exact class name, not the rest of the package, the same way existing entries like "org.apache.maven.artifact" already do elsewhere in this file. So this is strictly narrower than the wildcard, with identical behavior today (Ansi is the only class the shim provides) and no risk of silently claiming classes maven-jline doesn't actually have if the real jansi package grows new top-level classes later. Also dropped the SelfFirstStrategy mention in the comment per gnodet-bot's nit: the import-with-fallback (loadClassFromImport catching ClassNotFoundException, falling through to loadClassFromSelf) happens in both SelfFirstStrategy and ParentFirstStrategy, so citing one strategy implied the fallback was strategy-dependent when it isn't. Added a second assertion locking in that the exact-class export survives (not just the artifact removal), full suite + checkstyle + spotless pass locally.
gnodet-bot
left a comment
There was a problem hiding this comment.
Previous findings are all addressed in commit c2a917187.
- ✅ XML comment inaccuracy (SelfFirstStrategy): fixed — now correctly references
ClassRealm#loadClassFromImportas the fallback mechanism. - ✅ Export narrowed from
org.fusesource.jansi.*toorg.fusesource.jansi.Ansi(exact class) — this is the right scoping, since maven-jline only shipsAnsi.java; otherorg.fusesource.jansi.*classes fall back to the plugin's own classpath viaClassRealm#loadClassFromImport. - ✅ Test updated to assert both aspects of the fix: no jansi artifact export, and the exact-class package export stays.
Pre-fix test validation: testCoreExtensionDescriptorHasNoStaleArtifactExports was confirmed failing before the fix (static trace: getExportedArtifacts() contained org.fusesource.jansi:jansi → noneMatch → false; getExportedPackages() contained org.fusesource.jansi.* wildcard → contains("org.fusesource.jansi.Ansi") → false), passing after.
LGTM.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
|
Pushed c2a9171, applying both suggestions:
Added a second assertion so the test also locks in that the exact-class export stays. Full suite, checkstyle and spotless pass locally. |
|
@slawekjaranowski checked both branches for that -- |
|
@arturobernalg about add After it we allow plugins to use own JAnsi implentation. If plugin try to initialize JAnsi console by own way it can override statically shared sys out, err stream ... and finally break console in whole Maven process ... Maybe I miss in this case but it will be a plugin problem. @gnodet what do you think here? |
slawekjaranowski
left a comment
There was a problem hiding this comment.
looks ok, tested only with standard plugins like help, surefire - color output is ok
Not tested in case when plugin will call AnsiConsole.systemInstall(), AnsiConsole.systemUninstall()
JAnsi stopped being a maven-core dependency when it was migrated to JLine (Migrate from JAnsi to JLine, introduce MessageBuilderFactory, #11874), but maven-core/src/main/resources/META-INF/maven/extension.xml still declares org.fusesource.jansi:jansi as an exportedArtifact and org.fusesource.jansi.* as an exportedPackage.
DefaultArtifactFilterManager builds its classpath exclusion filter from these declarations (via CoreExports), so any plugin that declares jansi as its own dependency silently has it stripped from its realm, even though maven-core does not actually provide it. This broke at least the jkube Kubernetes Maven plugin on 3.10.0.
Added ExtensionDescriptorBuilderTest#testCoreExtensionDescriptorHasNoStaleExports, which loads maven-core's own production extension.xml from the classpath and asserts none of its exports mention jansi -- fails on the current descriptor, passes once the stale entries are removed. Full maven-core suite, checkstyle and spotless pass locally.
To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
check one of the following: