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]

Reply via email to