gnodet commented on issue #12302:
URL: https://github.com/apache/maven/issues/12302#issuecomment-4738643062
## Analysis: Two distinct root causes in the reported regressions
After tracing through the code on the current `4.0.x` branch, I can confirm
these are two separate bugs that both surface because
`TransitiveDependencyManager` is now the default.
---
### Category 1 — Scope derivation / provided-scope exclusion
**Affected projects:** `hadoop-api-shim`, `flink-connector-hive`
**Pattern:** A `provided`-scope direct dep's transitive `compile` deps are
missing from the compile classpath.
**Confirmed moving parts:**
1. **`ScopeDependencySelector.legacy()` explicitly excludes `provided` and
`test` at depth >= 2 during collection**
(`MavenSessionBuilderSupplier.java:102-105`). Any transitive dependency whose
scope resolves to `provided` before the selector evaluates it gets dropped from
the collected graph.
2. **`DependencyScope.PROVIDED.isTransitive() = false`**
(`DependencyScope.java:77`). Maven 4's scope manager registers `provided` as
non-transitive. In `Maven4ScopeManagerConfiguration`, `provided` falls into
`nonTransitiveDependencyScopes`, which in `RS_MAIN_COMPILE` (ELIMINATE mode)
means *its own transitive deps are not expanded onto the classpath*. A compile
dep of a `provided` dep ends up as the `provided` dep's transitive dep —
eligible for elimination.
3. **`ManagedScopeDeriver` narrows scope in `ConflictResolver`:**
parent=`provided` × child=`compile` → derived=`provided` (narrowest wins,
`ManagedScopeDeriver.java:74-81`). After the graph transformer runs, that
compile transitive dep is now `provided`-scoped in the resolved graph, and its
transitive closure is eliminated via (2).
**The TransitiveDependencyManager angle:**
`TransitiveDependencyManager.isInheritedDerived()` returns `depth == 0` (only
root-level depMgmt contributes managed scope), while `ClassicDependencyManager`
effectively does the same due to its depth=1 hop. The scope narrowing therefore
affects both managers equally at the collection phase. The regression likely
comes from a *combination* of the new `ScopeDependencySelector.legacy()`
(excluding provided from transitive collection) and the non-transitive scope
semantics introduced with Maven 4's resolver integration, which were previously
not applied in Maven 3 personality builds.
**Fix direction:** One of:
- Treat `provided`-scope deps' compile children as `compile` rather than
narrowing them during graph transformation (adjust `ManagedScopeDeriver` scope
table).
- Do not include `provided` in the `ScopeDependencySelector`'s excluded list
for transitive dep collection (at least in Maven 3 personality mode).
- Revisit whether `DependencyScope.PROVIDED.isTransitive() = false` is
semantically correct for backward compatibility — Maven 3 treated transitive
compile deps of provided deps as accessible at compile time.
---
### Category 2 — Version downgrade via transitive dependencyManagement
**Affected projects:** `guacamole-client`, `logging-log4j-samples`,
`netbeans-html4j`
**Pattern:** Enforcer rules (`RequireUpperBoundDeps`, `BannedDependencies`)
fail because a version that should be stable is silently downgraded by a
managed version in a transitive POM.
**Root cause — confirmed from code:**
| | `ClassicDependencyManager` | `TransitiveDependencyManager` |
|---|---|---|
| `deriveUntil` | `2` (+ depth=1 hop skips collection entirely) |
`Integer.MAX_VALUE` |
| Version management sources | **Root depMgmt only** | **Every POM in the
graph** |
`ClassicDependencyManager` (`ClassicDependencyManager.java:87,138`): the
explicit depth=1 hop calls `newInstance` directly without processing
`context.getManagedDependencies()`, and `deriveUntil=2` means depth >= 2
returns `this` unchanged. Only the root project's `<dependencyManagement>`
contributes managed versions.
`TransitiveDependencyManager` (`TransitiveDependencyManager.java:94`):
`deriveUntil = Integer.MAX_VALUE` means versions are collected from **every POM
encountered** (`AbstractDependencyManager.deriveChildManager:329-334`). When an
intermediate BOM/POM at depth N has a `<dependencyManagement>` entry with a
lower version of artifact X, that version gets recorded. If X was not managed
closer to the root, this lower version is applied — a version downgrade
invisible to the user.
The "nearer wins" rule (`getManagedVersion` walks the `path` from root
outward, returning the first match) does protect explicitly root-managed
versions. But any coordinate that was only implicitly resolved (not in the
root's depMgmt) can now be silently overridden by a deep transitive POM's
depMgmt.
**Fix direction:** Apply `TransitiveDependencyManager`'s full-depth
collection only for Maven 4 POMs (or only when the root explicitly opts in).
For Maven 3-personality builds, revert to `ClassicDependencyManager` depth
semantics. Alternatively, collect from all depths but only apply the collected
version if no version has already been established by a higher-priority
(nearer-to-root) source.
---
### Separate fixes are needed
These are independent bugs with different fix surfaces:
- **Category 1** → scope narrowing + `ScopeDependencySelector` + `provided`
non-transitive semantics.
- **Category 2** → `deriveUntil` depth in `TransitiveDependencyManager`.
Fixing Category 2 alone (e.g., restoring `ClassicDependencyManager` as the
default for Maven 3 personality) would not fix Category 1, which is about how
provided-scope deps' children are treated during collection and classpath
resolution in Maven 4's new scope model.
**Relevant source locations (`tmp-resolver` subtree on branch `4.0.x`):**
- `AbstractDependencyManager.java` — `deriveChildManager`,
`manageDependency`, `getManagedVersion`
- `TransitiveDependencyManager.java:94` — `deriveUntil = MAX_VALUE,
applyFrom = 2`
- `ClassicDependencyManager.java:87,138` — `deriveUntil = 2`, depth=1 hop
- `ScopeDependencySelector.java` (impl) — `legacy()` factory excluding
`provided`+`test`
- `ManagedScopeDeriver.java:74-81` — narrowest-wins scope derivation
- `MavenSessionBuilderSupplier.java:89-106` — `TransitiveDependencyManager`
wired as default
- `DependencyScope.java:77` — `PROVIDED("provided", false)` (non-transitive
flag)
--
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]