diff --git a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java index b7cc1d90811b..b8ca5288843c 100644 --- a/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java +++ b/compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java @@ -22,12 +22,16 @@ import javax.inject.Named; import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; import org.apache.maven.model.building.ArtifactModelSource; import org.apache.maven.model.building.DefaultModelBuildingRequest; @@ -262,7 +266,7 @@ private Model loadPom( RequestTraceHelper.interpretTrace(false, request.getTrace())); } } - model = modelResult.getEffectiveModel(); + model = stripInheritedDependencyManagement(modelResult); } catch (ModelBuildingException e) { for (ModelProblem problem : e.getProblems()) { if (problem.getException() instanceof UnresolvableModelException unresolvableModelException) { @@ -300,6 +304,54 @@ private boolean withinSameGav(Artifact a1, Artifact a2) { && Objects.equals(a1.getVersion(), a2.getVersion()); } + /** + * Strips parent-inherited {@code } entries from the effective model. + * Only entries directly declared in the POM (or from its own BOM imports) are kept. + * This prevents {@code TransitiveDependencyManager} from propagating parent-inherited + * management rules through the transitive dependency graph (gh-12302). + */ + private Model stripInheritedDependencyManagement(ModelBuildingResult modelResult) { + Model model = modelResult.getEffectiveModel(); + DependencyManagement effectiveDm = model.getDependencyManagement(); + if (effectiveDm == null || effectiveDm.getDependencies().isEmpty()) { + return model; + } + + // Collect depMgmt declared by parent POMs in the lineage + Map parentManagedVersions = new HashMap<>(); + List modelIds = modelResult.getModelIds(); + for (int i = 1; i < modelIds.size(); i++) { + Model rawParent = modelResult.getRawModel(modelIds.get(i)); + if (rawParent != null && rawParent.getDependencyManagement() != null) { + for (Dependency d : rawParent.getDependencyManagement().getDependencies()) { + parentManagedVersions.putIfAbsent(d.getManagementKey(), d.getVersion()); + } + } + } + + if (parentManagedVersions.isEmpty()) { + return model; + } + + List ownDeps = new ArrayList<>(); + for (Dependency d : effectiveDm.getDependencies()) { + String parentVersion = parentManagedVersions.get(d.getManagementKey()); + if (parentVersion == null || !parentVersion.equals(d.getVersion())) { + ownDeps.add(d); + } + } + + if (ownDeps.size() == effectiveDm.getDependencies().size()) { + return model; + } + + Model result = model.clone(); + DependencyManagement newDm = new DependencyManagement(); + newDm.setDependencies(ownDeps); + result.setDependencyManagement(newDm); + return result; + } + private Properties toProperties(Map dominant, Map recessive) { Properties props = new Properties(); if (recessive != null) { diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java index 88d168cccf84..de880843a4a7 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultArtifactDescriptorReader.java @@ -18,6 +18,7 @@ */ package org.apache.maven.impl.resolver; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -28,6 +29,8 @@ import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Singleton; +import org.apache.maven.api.model.Dependency; +import org.apache.maven.api.model.DependencyManagement; import org.apache.maven.api.model.Model; import org.apache.maven.api.services.ModelBuilder; import org.apache.maven.api.services.ModelBuilderException; @@ -242,7 +245,7 @@ private Model loadPom( RequestTraceHelper.interpretTrace(false, request.getTrace())); } } - model = modelResult.getEffectiveModel(); + model = stripInheritedDependencyManagement(modelResult); } catch (ModelBuilderException e) { for (ModelProblem problem : e.getResult().getProblemCollector().problems().toList()) { @@ -281,6 +284,45 @@ private boolean withinSameGav(Artifact a1, Artifact a2) { && Objects.equals(a1.getVersion(), a2.getVersion()); } + /** + * Strips parent-inherited {@code } entries from the effective model. + * Only entries directly declared in the POM (or from its own BOM imports) are kept. + * This prevents {@code TransitiveDependencyManager} from propagating parent-inherited + * management rules through the transitive dependency graph (gh-12302). + */ + private Model stripInheritedDependencyManagement(ModelBuilderResult modelResult) { + Model model = modelResult.getEffectiveModel(); + DependencyManagement effectiveDm = model.getDependencyManagement(); + if (effectiveDm == null || effectiveDm.getDependencies().isEmpty()) { + return model; + } + + Model parentModel = modelResult.getParentModel(); + DependencyManagement parentDm = parentModel != null ? parentModel.getDependencyManagement() : null; + if (parentDm == null || parentDm.getDependencies().isEmpty()) { + return model; + } + + Map parentManagedVersions = new HashMap<>(); + for (Dependency d : parentDm.getDependencies()) { + parentManagedVersions.put(d.getManagementKey(), d.getVersion()); + } + + List ownDeps = new ArrayList<>(); + for (Dependency d : effectiveDm.getDependencies()) { + String parentVersion = parentManagedVersions.get(d.getManagementKey()); + if (parentVersion == null || !parentVersion.equals(d.getVersion())) { + ownDeps.add(d); + } + } + + if (ownDeps.size() == effectiveDm.getDependencies().size()) { + return model; + } + + return model.withDependencyManagement(effectiveDm.withDependencies(ownDeps)); + } + private Map toProperties(Map dominant, Map recessive) { Map props = new HashMap<>(); if (recessive != null) { diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12302TransitiveDepMgmtVersionDowngradeTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12302TransitiveDepMgmtVersionDowngradeTest.java new file mode 100644 index 000000000000..6ba89c4df6df --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh12302TransitiveDepMgmtVersionDowngradeTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.it; + +import java.io.File; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Integration test for gh-12302. + * + *

Verifies that {@code TransitiveDependencyManager} does not silently downgrade + * dependency versions when an intermediate POM's {@code } + * declares a lower version of a transitive dependency. + * + *

Dependency graph: + *

+ *   root (test project)
+ *     └── module-a:1.0 (parent = parent-a:1.0)
+ *           └── module-b:1.0
+ *                 └── lib-c:2.0
+ *
+ *   parent-a has <dependencyManagement> managing lib-c to 1.0
+ * 
+ * + *

Expected: lib-c resolves to 2.0 (declared by module-b). + *
Actual (bug): lib-c is downgraded to 1.0 by parent-a's dependencyManagement + * because {@code TransitiveDependencyManager} has {@code deriveUntil = Integer.MAX_VALUE}, + * collecting managed versions from every POM in the graph. + */ +public class MavenITgh12302TransitiveDepMgmtVersionDowngradeTest extends AbstractMavenIntegrationTestCase { + + MavenITgh12302TransitiveDepMgmtVersionDowngradeTest() { + super("[4.0.0-rc-3,)"); + } + + @Test + public void testTransitiveDependencyManagerDoesNotDowngradeVersions() throws Exception { + File testDir = extractResources("/gh-12302-transitive-dep-mgmt-version-downgrade"); + + Verifier verifier = newVerifier(testDir.getAbsolutePath()); + verifier.setAutoclean(false); + verifier.deleteDirectory("target"); + verifier.deleteArtifacts("org.apache.maven.its.gh12302"); + verifier.filterFile("settings-template.xml", "settings.xml"); + verifier.addCliArgument("--settings"); + verifier.addCliArgument("settings.xml"); + verifier.addCliArgument("validate"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + List classpath = verifier.loadLines("target/classpath.txt"); + + // lib-c should resolve to 2.0 (as declared by module-b), not 1.0 + // (managed by parent-a's dependencyManagement). + // With the TransitiveDependencyManager bug, lib-c gets downgraded to 1.0. + assertTrue( + classpath.contains("lib-c-2.0.jar"), + "lib-c should be version 2.0 (declared by module-b), not downgraded: " + classpath); + assertFalse( + classpath.contains("lib-c-1.0.jar"), + "lib-c should NOT be downgraded to 1.0 by parent-a's dependencyManagement: " + classpath); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/pom.xml b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/pom.xml new file mode 100644 index 000000000000..eb2f79c4a8e1 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + org.apache.maven.its.gh12302 + test + 1.0 + jar + + Maven Integration Test :: gh-12302 + Verify that TransitiveDependencyManager does not downgrade dependency versions + when an intermediate POM's dependencyManagement declares a lower version. + + + + org.apache.maven.its.gh12302 + module-a + 1.0 + + + + + + + org.apache.maven.its.plugins + maven-it-plugin-dependency-resolution + 2.1-SNAPSHOT + + target/classpath.txt + 1 + + + + resolve + + compile + + validate + + + + + + diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/.gitattributes b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/.gitattributes new file mode 100644 index 000000000000..6d6420ce595a --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/.gitattributes @@ -0,0 +1,2 @@ +*.pom text eol=lf +maven-metadata.xml text eol=lf diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/1.0/lib-c-1.0.jar b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/1.0/lib-c-1.0.jar new file mode 100644 index 000000000000..ee3ce6555d83 Binary files /dev/null and b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/1.0/lib-c-1.0.jar differ diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/1.0/lib-c-1.0.pom b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/1.0/lib-c-1.0.pom new file mode 100644 index 000000000000..c969e383a7f1 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/1.0/lib-c-1.0.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + + org.apache.maven.its.gh12302 + lib-c + 1.0 + jar + diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/2.0/lib-c-2.0.jar b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/2.0/lib-c-2.0.jar new file mode 100644 index 000000000000..ee3ce6555d83 Binary files /dev/null and b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/2.0/lib-c-2.0.jar differ diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/2.0/lib-c-2.0.pom b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/2.0/lib-c-2.0.pom new file mode 100644 index 000000000000..5aa08a8cbc19 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/lib-c/2.0/lib-c-2.0.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + + org.apache.maven.its.gh12302 + lib-c + 2.0 + jar + diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-a/1.0/module-a-1.0.jar b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-a/1.0/module-a-1.0.jar new file mode 100644 index 000000000000..ee3ce6555d83 Binary files /dev/null and b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-a/1.0/module-a-1.0.jar differ diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-a/1.0/module-a-1.0.pom b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-a/1.0/module-a-1.0.pom new file mode 100644 index 000000000000..5c8d0bf728ce --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-a/1.0/module-a-1.0.pom @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.apache.maven.its.gh12302 + parent-a + 1.0 + + + module-a + 1.0 + jar + + + + org.apache.maven.its.gh12302 + module-b + 1.0 + + + diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-b/1.0/module-b-1.0.jar b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-b/1.0/module-b-1.0.jar new file mode 100644 index 000000000000..ee3ce6555d83 Binary files /dev/null and b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-b/1.0/module-b-1.0.jar differ diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-b/1.0/module-b-1.0.pom b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-b/1.0/module-b-1.0.pom new file mode 100644 index 000000000000..40e8134d7e78 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/module-b/1.0/module-b-1.0.pom @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.apache.maven.its.gh12302 + module-b + 1.0 + jar + + + + org.apache.maven.its.gh12302 + lib-c + 2.0 + + + diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/parent-a/1.0/parent-a-1.0.pom b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/parent-a/1.0/parent-a-1.0.pom new file mode 100644 index 000000000000..9d0c192914b0 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/repo/org/apache/maven/its/gh12302/parent-a/1.0/parent-a-1.0.pom @@ -0,0 +1,19 @@ + + + 4.0.0 + + org.apache.maven.its.gh12302 + parent-a + 1.0 + pom + + + + + org.apache.maven.its.gh12302 + lib-c + 1.0 + + + + diff --git a/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/settings-template.xml b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/settings-template.xml new file mode 100644 index 000000000000..c25336565b85 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-12302-transitive-dep-mgmt-version-downgrade/settings-template.xml @@ -0,0 +1,41 @@ + + + + + + maven-core-it-repo + + + maven-core-it + @baseurl@/repo + + ignore + + + false + + + + + + + maven-core-it-repo + +