Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
*/
package org.openrewrite.maven;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.maven.trait.MavenPlugin;
import org.openrewrite.maven.tree.MavenResolutionResult;
import org.openrewrite.maven.tree.Plugin;
import org.openrewrite.maven.tree.Pom;
import org.openrewrite.maven.tree.ResolvedPom;
import org.openrewrite.semver.Semver;
import org.openrewrite.semver.VersionComparator;
import org.openrewrite.xml.XmlIsoVisitor;
Expand Down Expand Up @@ -94,6 +98,13 @@ public static class Scanned {
*/
Set<Path> aggregatorPaths = new HashSet<>();

/**
* Paths of POMs where the annotation processor is already present in the effective POM
* (via a parent POM outside the reactor), but not in the POM's own XML.
* These POMs should be skipped to avoid redundant configuration.
*/
Set<Path> alreadyConfiguredInEffectivePomPaths = new HashSet<>();

/**
* Get the actual orphan paths (candidates minus aggregator-only POMs).
* A true orphan has no parent in reactor and is not an aggregator-only POM.
Expand Down Expand Up @@ -121,7 +132,20 @@ public TreeVisitor<?, ExecutionContext> getScanner(Scanned acc) {
@Override
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
MavenResolutionResult mrr = getResolutionResult();
Path sourcePath = mrr.getPom().getRequested().getSourcePath();
ResolvedPom resolvedPom = mrr.getPom();
Path sourcePath = resolvedPom.getRequested().getSourcePath();

// Check if the annotation processor is already in the effective POM (merged from parent POMs)
// but NOT in the current POM's own XML. In that case, skip this POM to avoid adding
// redundant configuration that duplicates what the parent already provides.
boolean inEffectivePom = hasAnnotationProcessor(resolvedPom.getPlugins()) ||
hasAnnotationProcessor(resolvedPom.getPluginManagement());
Pom requestedPom = resolvedPom.getRequested();
boolean inCurrentPomXml = hasAnnotationProcessor(requestedPom.getPlugins()) ||
hasAnnotationProcessor(requestedPom.getPluginManagement());
if (sourcePath != null && inEffectivePom && !inCurrentPomXml) {
acc.alreadyConfiguredInEffectivePomPaths.add(sourcePath);
}

if (mrr.parentPomIsProjectPom()) {
// This module has a parent within the reactor
Expand Down Expand Up @@ -177,6 +201,12 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Scanned acc) {
return tree;
}

// Skip POMs where the annotation processor is already configured via a parent POM
// outside the reactor (present in effective POM but not in own XML)
if (acc.alreadyConfiguredInEffectivePomPaths.contains(sourcePath)) {
return tree;
}

// First, ensure the plugin exists - use the source path as file pattern
tree = new AddPluginVisitor(isParent,
MAVEN_COMPILER_PLUGIN_GROUP_ID, MAVEN_COMPILER_PLUGIN_ARTIFACT_ID, null,
Expand Down Expand Up @@ -254,4 +284,33 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) {
}
};
}

private boolean hasAnnotationProcessor(List<Plugin> plugins) {
for (Plugin plugin : plugins) {
if (!MAVEN_COMPILER_PLUGIN_GROUP_ID.equals(plugin.getGroupId()) ||
!MAVEN_COMPILER_PLUGIN_ARTIFACT_ID.equals(plugin.getArtifactId())) {
continue;
}
JsonNode config = plugin.getConfiguration();
if (config == null || config.isMissingNode()) {
continue;
}
JsonNode paths = config.path("annotationProcessorPaths").path("path");
if (paths.isArray()) {
for (JsonNode path : paths) {
if (isMatchingProcessor(path)) {
return true;
}
}
} else if (!paths.isMissingNode() && isMatchingProcessor(paths)) {
return true;
}
}
return false;
}

private boolean isMatchingProcessor(JsonNode path) {
return groupId.equals(path.path("groupId").asText("")) &&
artifactId.equals(path.path("artifactId").asText(""));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,89 @@ void addToBuildPluginsWhenInBothLocations() {
}
}

@Nested
class EffectivePomCheck {

@Test
void doesNotAddWhenAlreadyInEffectivePomViaAncestor() {
// 3-level hierarchy: grandparent has the processor in pluginManagement,
// intermediate parent inherits it via effective POM but has no own XML config.
// Expected: intermediate parent is NOT modified (annotation processor already in effective POM)
rewriteRun(
pomXml(
// Grandparent already has the processor configured in pluginManagement
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>grandparent</artifactId>
<version>1</version>
<packaging>pom</packaging>
<modules>
<module>parent-module</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
"""
),
mavenProject("parent-module",
pomXml(
// Intermediate parent: inherits the processor from grandparent via effective POM,
// but does NOT have it in its own XML. Should not be modified.
"""
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>grandparent</artifactId>
<version>1</version>
</parent>
<artifactId>parent-module</artifactId>
<packaging>pom</packaging>
<modules>
<module>child-module</module>
</modules>
</project>
"""
)
),
mavenProject("child-module",
pomXml(
"""
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mycompany.app</groupId>
<artifactId>parent-module</artifactId>
<version>1</version>
</parent>
<artifactId>child-module</artifactId>
</project>
"""
)
)
);
}
}

@Nested
class CombinedAggregatorAndParent {

Expand Down