diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedDependency.java index 8cef451465..d54da62f95 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddManagedDependency.java @@ -29,8 +29,11 @@ import org.openrewrite.xml.tree.Xml; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import static java.util.Objects.requireNonNull; @@ -182,6 +185,10 @@ public String getInstanceNameSuffix() { public static class Scanned { boolean usingType; List rootPoms = new ArrayList<>(); + // Populated by callers (e.g. UpgradeTransitiveDependencyVersion.getScanner) when + // addToRootPom=true and a transitive was found in a non-root pom under a reactor root. + // Keyed by the reactor root's GAV; values are the coordinates the writer should pin there. + Map> reactorRootTransitives = new HashMap<>(); } @Override diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersion.java index 38ecc66006..caf39b2ff1 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersion.java @@ -21,8 +21,10 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.maven.table.MavenMetadataFailures; +import org.openrewrite.maven.tree.GroupArtifact; import org.openrewrite.maven.tree.MavenResolutionResult; import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; import org.openrewrite.semver.Semver; import org.openrewrite.xml.tree.Xml; @@ -169,7 +171,37 @@ public AddManagedDependency.Scanned getInitialValue(ExecutionContext ctx) { @Override public TreeVisitor getScanner(AddManagedDependency.Scanned acc) { - return addManagedDependency().getScanner(acc); + TreeVisitor addManagedDependencyScanner = addManagedDependency().getScanner(acc); + if (!Boolean.TRUE.equals(addToRootPom)) { + return addManagedDependencyScanner; + } + // When adding to the reactor root, record for each root the coordinates any pom under it + // holds transitively. The visitor phase reads this map to write the pins in one place + // instead of the pom that happens to hold each transitive. + return new MavenIsoVisitor() { + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + addManagedDependencyScanner.visit(document, ctx); + MavenResolutionResult mrr = getResolutionResult(); + Set matchingDependencies = mrr.findDependencies(groupId, artifactId, null) + .stream() + .filter(ResolvedDependency::isTransitive) + .collect(toCollection(LinkedHashSet::new)); + if (matchingDependencies.isEmpty()) { + return document; + } + MavenResolutionResult current = mrr; + while (current.parentPomIsProjectPom()) { + current = current.getParent(); + } + ResolvedGroupArtifactVersion rootGav = current.getPom().getGav(); + Set coords = acc.reactorRootTransitives.computeIfAbsent(rootGav, k -> new LinkedHashSet<>()); + for (ResolvedDependency dep : matchingDependencies) { + coords.add(new GroupArtifact(dep.getGroupId(), dep.getArtifactId())); + } + return document; + } + }; } @Override @@ -177,6 +209,27 @@ public TreeVisitor getVisitor(AddManagedDependency.Scanned return new MavenIsoVisitor() { @Override public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + if (Boolean.TRUE.equals(addToRootPom)) { + // Only fire on the reactor root; write every coordinate recorded against it by + // the scanner. Skip non-root poms even if they hold the transitive - the point + // of addToRootPom is to centralize the pin. + MavenResolutionResult mrr = getResolutionResult(); + if (mrr.parentPomIsProjectPom()) { + return document; + } + Set coords = acc.reactorRootTransitives.get(mrr.getPom().getGav()); + if (coords == null || coords.isEmpty()) { + return document; + } + Xml.Document d = document; + for (GroupArtifact coord : coords) { + d = (Xml.Document) addManagedDependency(coord.getGroupId(), coord.getArtifactId()) + .getVisitor(acc) + .visitNonNull(d, ctx); + } + return d; + } + Set matchingDependencies = getResolutionResult().findDependencies(groupId, artifactId, null) .stream() .filter(ResolvedDependency::isTransitive) diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersionTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersionTest.java index e07e648ff0..8b2c8dcb91 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersionTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/UpgradeTransitiveDependencyVersionTest.java @@ -381,4 +381,71 @@ void doesNotAddManagedDependencyToChildModuleWhenInheritedFromParent() { ) ); } + + @Test + void addsManagedDependencyToReactorRootWhenAddToRootPomAndOnlyChildHoldsTheTransitive() { + rewriteRun( + spec -> spec.recipe(new UpgradeTransitiveDependencyVersion( + "com.fasterxml*", "jackson-core", "2.12.5", null, null, null, null, null, null, true, null)), + mavenProject("parent", + pomXml( + """ + + 4.0.0 + com.example + parent + 1.0.0 + pom + + child + + + """, + """ + + 4.0.0 + com.example + parent + 1.0.0 + pom + + child + + + + + com.fasterxml.jackson.core + jackson-core + 2.12.5 + + + + + """ + ), + mavenProject("child", + pomXml( + """ + + 4.0.0 + + com.example + parent + 1.0.0 + + child + + + org.openrewrite + rewrite-java + 7.0.0 + + + + """ + ) + ) + ) + ); + } }