diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index c8257bc8752..e796e62ade3 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -25,7 +25,10 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.table.MavenMetadataFailures; -import org.openrewrite.maven.tree.*; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; +import org.openrewrite.maven.tree.Scope; import org.openrewrite.semver.Semver; import org.openrewrite.xml.tree.Xml; @@ -180,17 +183,17 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { acc.usingType = true; JavaProject javaProject = sourceFile.getMarkers().findFirst(JavaProject.class).orElse(null); JavaSourceSet javaSourceSet = sourceFile.getMarkers().findFirst(JavaSourceSet.class).orElse(null); - if(javaProject != null && javaSourceSet != null) { + if (javaProject != null && javaSourceSet != null) { acc.scopeByProject.compute(javaProject, (jp, scope) -> "compile".equals(scope) ? scope /* a `compile` scope dependency will also be available in test source set */ : "test".equals(javaSourceSet.getName()) ? "test" : "compile" ); } } - } else if(tree instanceof Xml.Document) { + } else if (tree instanceof Xml.Document) { Xml.Document doc = (Xml.Document) tree; MavenResolutionResult mrr = doc.getMarkers().findFirst(MavenResolutionResult.class).orElse(null); - if(mrr == null) { + if (mrr == null) { return sourceFile; } acc.pomsDefinedInCurrentRepository.add(mrr.getPom().getGav()); @@ -235,7 +238,10 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { } } - if(onlyIfUsing == null && getResolutionResult().getParent() != null && acc.pomsDefinedInCurrentRepository.contains(getResolutionResult().getParent().getPom().getGav())) { + if (onlyIfUsing == null && isSubprojectOfParentInRepository(acc)) { + return maven; + } + if (isAggregatorNotUsedAsParent()) { return maven; } @@ -243,6 +249,29 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { groupId, artifactId, version, versionPattern, resolvedScope, releasesOnly, type, classifier, optional, familyPatternCompiled, metadataFailures).visitNonNull(document, ctx); } + + private boolean isSubprojectOfParentInRepository(Scanned acc) { + return getResolutionResult().getParent() != null && + acc.pomsDefinedInCurrentRepository.contains(getResolutionResult().getParent().getPom().getGav()); + } + + private boolean isAggregatorNotUsedAsParent() { + List subprojects = getResolutionResult().getPom().getSubprojects(); + if (subprojects.isEmpty()) { + return false; + } + List modules = getResolutionResult().getModules(); + if (modules.isEmpty()) { + return true; + } + for (MavenResolutionResult child : modules) { + if (subprojects.contains(child.getPom().getGav().getArtifactId())) { + return false; + } + } + return true; + } + }); } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/RawPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/RawPom.java index 4ae0fc5b3da..0ea795ee8ea 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/internal/RawPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/internal/RawPom.java @@ -25,6 +25,7 @@ import lombok.experimental.NonFinal; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.maven.tree.*; @@ -118,6 +119,12 @@ public class RawPom { @Nullable Profiles profiles; + @Nullable + Modules modules; + + @Nullable + SubProjects subprojects; + public static RawPom parse(InputStream inputStream, @Nullable String snapshotVersion) { try { RawPom pom = MavenXmlMapper.readMapper().readValue(inputStream, RawPom.class); @@ -217,6 +224,32 @@ public Profiles(@JacksonXmlProperty(localName = "profile") List profile } } + @Getter + public static class Modules { + private final List modules; + + public Modules() { + this.modules = emptyList(); + } + + public Modules(@JacksonXmlProperty(localName = "module") List modules) { + this.modules = modules; + } + } + + @Getter + public static class SubProjects { + private final List subprojects; + + public SubProjects() { + this.subprojects = emptyList(); + } + + public SubProjects(@JacksonXmlProperty(localName = "subproject") List subprojects) { + this.subprojects = subprojects; + } + } + @FieldDefaults(level = AccessLevel.PRIVATE) @Data public static class Build { @@ -346,9 +379,9 @@ public static class Profile { } public @Nullable String getVersion() { - if(version == null) { - if(currentVersion == null) { - if(parent == null) { + if (version == null) { + if (currentVersion == null) { + if (parent == null) { return null; } else { return parent.getVersion(); @@ -382,8 +415,9 @@ public Pom toPom(@Nullable Path inputPath, @Nullable MavenRepository repo) { .packaging(packaging) .properties(getProperties() == null ? emptyMap() : getProperties()) .licenses(mapLicenses(getLicenses())) - .profiles(mapProfiles(getProfiles())); - if(StringUtils.isBlank(pomVersion)) { + .profiles(mapProfiles(getProfiles())) + .subprojects(mapSubProjects(getModules(), getSubprojects())); + if (StringUtils.isBlank(pomVersion)) { builder.dependencies(mapRequestedDependencies(getDependencies())) .dependencyManagement(mapDependencyManagement(getDependencyManagement())) .repositories(mapRepositories(getRepositories())) @@ -547,4 +581,17 @@ private List mapPluginExecutions(@N return executions; } + private List mapSubProjects(@Nullable Modules modules, @Nullable SubProjects subprojects) { + if (modules == null && subprojects != null) { + return subprojects.getSubprojects(); + } + if (subprojects == null && modules != null) { + return modules.getModules(); + } + if (modules != null && subprojects != null) { + return ListUtils.concatAll(modules.getModules(), subprojects.getSubprojects()); + } + return emptyList(); + } + } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java index 2530580ec43..4dd92feb8f4 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/MavenResolutionResult.java @@ -209,6 +209,10 @@ public boolean parentPomIsProjectPom() { .anyMatch(gav -> gav.equals(parentGav)); } + public boolean isMultiModulePom() { + return !getPom().getSubprojects().isEmpty(); + } + private Map getProjectPomsRecursive(Map projectPoms) { projectPoms.put(requireNonNull(pom.getRequested().getSourcePath()), pom.getRequested()); if (parent != null) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java index e5af8d7bb32..fabcfa52229 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/Pom.java @@ -115,6 +115,9 @@ public static int getModelVersion() { @Builder.Default List pluginManagement = emptyList(); + @Builder.Default + List subprojects = emptyList(); + public String getGroupId() { return gav.getGroupId(); } @@ -184,7 +187,8 @@ public ResolvedPom resolve(Iterable activeProfiles, repositories, dependencies, plugins, - pluginManagement) + pluginManagement, + subprojects) .resolve(ctx, downloader); } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java index e00559f9eb7..0604c7b47e6 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/tree/ResolvedPom.java @@ -69,11 +69,11 @@ public class ResolvedPom { Iterable activeProfiles; public ResolvedPom(Pom requested, Iterable activeProfiles) { - this(requested, activeProfiles, emptyMap(), emptyList(), null, emptyList(), emptyList(), emptyList(), emptyList()); + this(requested, activeProfiles, emptyMap(), emptyList(), null, emptyList(), emptyList(), emptyList(), emptyList(), emptyList()); } @JsonCreator - ResolvedPom(Pom requested, Iterable activeProfiles, Map properties, List dependencyManagement, @Nullable List initialRepositories, List repositories, List requestedDependencies, List plugins, List pluginManagement) { + ResolvedPom(Pom requested, Iterable activeProfiles, Map properties, List dependencyManagement, @Nullable List initialRepositories, List repositories, List requestedDependencies, List plugins, List pluginManagement, List subprojects) { this.requested = requested; this.activeProfiles = activeProfiles; this.properties = properties; @@ -83,6 +83,7 @@ public ResolvedPom(Pom requested, Iterable activeProfiles) { this.requestedDependencies = requestedDependencies; this.plugins = plugins; this.pluginManagement = pluginManagement; + this.subprojects = subprojects; } @NonFinal @@ -113,6 +114,10 @@ public ResolvedPom(Pom requested, Iterable activeProfiles) { @Builder.Default List pluginManagement = emptyList(); + @NonFinal + @Builder.Default + List subprojects = emptyList(); + /** * Deduplicate dependencies and dependency management dependencies @@ -172,6 +177,7 @@ public ResolvedPom resolve(ExecutionContext ctx, MavenPomDownloader downloader) emptyList(), emptyList(), emptyList(), + emptyList(), emptyList() ).resolver(ctx, downloader).resolve(); @@ -926,7 +932,7 @@ public List resolveDependencies(Scope scope, Map @@ -1152,35 +1146,35 @@ void noCompileScopeDependency() { org.springframework.samples spring-petclinic 2.7.3 - + org.springframework.boot spring-boot-starter-parent 3.0.5 petclinic - + 5.0.0 - + 17 UTF-8 UTF-8 - + 5.1.3 4.7.0 - + 0.8.8 - + - + org.springframework.boot spring-boot-starter-data-jpa - + """, """ @@ -1189,28 +1183,28 @@ void noCompileScopeDependency() { org.springframework.samples spring-petclinic 2.7.3 - + org.springframework.boot spring-boot-starter-parent 3.0.5 petclinic - + 5.0.0 - + 17 UTF-8 UTF-8 - + 5.1.3 4.7.0 - + 0.8.8 - + - + jakarta.xml.bind @@ -1221,7 +1215,7 @@ void noCompileScopeDependency() { spring-boot-starter-data-jpa - + """ ) @@ -1464,6 +1458,197 @@ class Foo {} ); } + @Test + void addDependencyToParentPomWhenAggregatingPomIsNotParent() { + rewriteRun( + spec -> spec.recipe(new AddDependency( + "org.hamcrest", + "hamcrest-junit", + "2.0.0.0", + null, + "test", + null, + null, + null, + null, + null, + null, + true)), + mavenProject("my-app-aggregate", + pomXml( + """ + + com.mycompany.app + my-app-aggregate + 1 + + project-parent + project-child + + + """ + ) + ), + mavenProject("project-parent", + pomXml( + """ + + com.mycompany.app + my-app-parent + 1 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + """, + """ + + com.mycompany.app + my-app-parent + 1 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + + """ + ) + ), + mavenProject("my-app-child", + pomXml( + """ + + com.mycompany.app + my-app-child + 1 + + com.mycompany.app + my-app-parent + 1 + + + """ + ) + ) + ); + } + + @Test + void addDependencyToAggregatingPomAndParentPomWhenAggregatingPomIsParent() { + rewriteRun( + spec -> spec.recipe(new AddDependency( + "org.hamcrest", + "hamcrest-junit", + "2.0.0.0", + null, + "test", + null, + null, + null, + null, + null, + null, + true)), + mavenProject("my-app-aggregate", + pomXml( + """ + + com.mycompany.app + my-app-aggregate + 1 + + my-app-parent + my-app-child + + + """, + """ + + com.mycompany.app + my-app-aggregate + 1 + + my-app-parent + my-app-child + + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + + """ + ) + ), + mavenProject("project-parent", + pomXml( + """ + + com.mycompany.app + my-app-parent + 1 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + """, + """ + + com.mycompany.app + my-app-parent + 1 + + org.springframework.boot + spring-boot-starter-parent + 3.1.5 + + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + + """ + ) + ), + mavenProject("my-app-child", + pomXml( + """ + + com.mycompany.app + my-app-child + 1 + + com.mycompany.app + my-app-aggregate + 1 + + + """ + ) + ) + ); + } + private AddDependency addDependency(@SuppressWarnings("SameParameterValue") String gav) { return addDependency(gav, null, null, null); } diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java index 8f69e5e1bc2..f197c65d4a7 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/internal/RawPomTest.java @@ -81,6 +81,41 @@ void repositoriesSerializationAndDeserialization() { assertThat(pom.getRepositories().getRepositories()).hasSize(1); } + @Test + void modulesAndSubProjects() { + RawPom pom = RawPom.parse( + //language=xml + new ByteArrayInputStream(""" + + 4.0.0 + + com.mycompany.app + my-app + 1 + + + my-module + my-other-module + + + + my-subproject + my-other-subproject + + + """.getBytes()), + null + ); + + assertThat(pom).isNotNull(); + //noinspection DataFlowIssue + assertThat(pom.getSubprojects()).isNotNull(); + //noinspection DataFlowIssue + assertThat(pom.getModules()).isNotNull(); + assertThat(pom.getSubprojects().getSubprojects()).hasSize(2); + assertThat(pom.getModules().getModules()).hasSize(2); + } + @Test void serializePluginFlags() { RawPom pom = RawPom.parse(