gnodet commented on PR #820:
URL:
https://github.com/apache/maven-shade-plugin/pull/820#issuecomment-5075398245
## Root Cause Analysis: Why Maven 4 Prunes Differently Than Maven 3.10
@cstamas @hboutemy — I dug into why Maven 3.10.0-RC1 (Resolver 2.0.20)
retains the duplicate `c` node under `b:alt` while Maven 4.0.0-rc-5 (same
Resolver 2.0.20) prunes it. The short answer: **it's the `DependencyManager`,
not the `ConflictResolver`.**
### The Configuration Difference
The pivotal config property is
`maven.resolver.dependencyManagerTransitivity`:
| Maven Version | Default Value | DependencyManager Used |
|---|---|---|
| **3.9.x** (Resolver 1.x) | n/a | `ClassicDependencyManager` |
| **3.10.0-rc-1** (Resolver 2.0.20) | `false` |
`ClassicDependencyManager(deriveUntil=2, applyFrom=2)` |
| **4.0.0-rc-5** (Resolver 2.0.20) | `true` |
`TransitiveDependencyManager(deriveUntil=MAX_VALUE, applyFrom=2)` |
Maven 3.10's `DefaultRepositorySystemSessionFactory` defaults
`dependencyManagerTransitivity` to `false`, keeping `ClassicDependencyManager`.
Maven 4's version defaults it to `!mavenMaven3Personality` = `true`, switching
to `TransitiveDependencyManager`.
### How This Causes the Bug
In `BfDependencyCollector.doRecurse()`, the collector builds a **pool cache
key** from `(artifact, repos, selector, manager, traverser, filter)`:
```java
Object key = args.pool.toKey(artifact, childRepos, childSelector,
childManager, childTraverser, childFilter);
List<DependencyNode> children = args.pool.getChildren(key);
if (children == null) {
boolean skipResolution = args.skipper.skipResolution(child,
parentContext.parents);
// ... resolve and cache
} else {
child.setChildren(children); // pool hit → skipper never consulted
}
```
**Maven 3.10 (`ClassicDependencyManager`):** At depth ≥ 2,
`deriveChildManager()` returns `this` (same instance). So the pool key for `c`
under `b` equals the pool key for `c` under `b:alt` → **cache hit**. The cached
children are reused directly; the GACE skipper is **never consulted**. Both `c`
nodes get populated subtrees, and `ConflictResolver` (even in default
`Verbosity.NONE`) sees both and retains both.
**Maven 4 (`TransitiveDependencyManager`):** At every depth,
`deriveChildManager()` creates a **new manager instance** incorporating managed
dependencies from the current node. The pool key for `c` under `b` ≠ pool key
for `c` under `b:alt` → **cache miss**. The GACE skipper **is consulted** on
the second occurrence and marks `c` under `b:alt` as `skippedAsDuplicate`. That
`c` node ends up with **no children**, and `ConflictResolver` (in
`Verbosity.NONE`) prunes it entirely as a conflict loser. The shade plugin's
exclusion walk never sees `c` under `b:alt`, so the exclusion is lost.
### Conclusion
This is a **deliberate Maven 4 design decision** —
`TransitiveDependencyManager` is the correct default for Maven 4's dependency
model (dependency management applies transitively at all depths). The different
pool-key behavior is a side effect, not a bug. The shade plugin genuinely needs
to see all transitive occurrences regardless of conflict resolution, and
`CONFIG_PROP_VERBOSE = STANDARD` is the documented mechanism for that use case.
This PR's approach is the correct fix on the plugin side.
---
_This analysis was generated by an AI agent and may contain inaccuracies.
Please verify before relying on it._
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]