diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 81bb3c7258046..d8c95cb4dd15a 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -31,6 +31,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.Scanner; @@ -49,6 +50,7 @@ import org.apache.maven.execution.MavenSession; import org.apache.maven.model.BuildBase; import org.apache.maven.model.Dependency; +import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.model.Profile; @@ -715,6 +717,9 @@ private void executeGoal(PluginExec pluginExec, String goal, Map } private List readAnnotationProcessors(Xpp3Dom pluginConfig) { + if (pluginConfig == null) { + return Collections.emptyList(); + } Xpp3Dom annotationProcessors = pluginConfig.getChild("annotationProcessors"); if (annotationProcessors == null) { return Collections.emptyList(); @@ -731,6 +736,9 @@ private List readAnnotationProcessors(Xpp3Dom pluginConfig) { } private Set readAnnotationProcessorPaths(Xpp3Dom pluginConfig) throws MojoExecutionException { + if (pluginConfig == null) { + return Collections.emptySet(); + } Xpp3Dom annotationProcessorPaths = pluginConfig.getChild("annotationProcessorPaths"); if (annotationProcessorPaths == null) { return Collections.emptySet(); @@ -739,6 +747,10 @@ private Set readAnnotationProcessorPaths(Xpp3Dom pluginConfig) throws Mojo Set elements = new LinkedHashSet<>(); try { List dependencies = convertToDependencies(paths); + // NOTE: The Maven Compiler Plugin also supports a flag (disabled by default) for applying managed dependencies to + // the dependencies of the APT plugins (not them directly), which we don't support yet here + // you can find the implementation at https://github.com/apache/maven-compiler-plugin/pull/180/files#diff-d4bac42d8f4c68d397ddbaa05c1cbbed7984ef6dc0bb9ea60739df78997e99eeR1610 + // when/if we need it CollectRequest collectRequest = new CollectRequest(dependencies, Collections.emptyList(), project.getRemoteProjectRepositories()); DependencyRequest dependencyRequest = new DependencyRequest(); @@ -756,25 +768,66 @@ private Set readAnnotationProcessorPaths(Xpp3Dom pluginConfig) throws Mojo } } - private List convertToDependencies(Xpp3Dom[] paths) { + private List convertToDependencies(Xpp3Dom[] paths) throws MojoExecutionException { List dependencies = new ArrayList<>(); for (Xpp3Dom path : paths) { String type = getValue(path, "type", "jar"); ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(type); + // WATCH OUT: this constructor turns any null values into empty strings org.eclipse.aether.artifact.Artifact artifact = new DefaultArtifact( getValue(path, "groupId", null), getValue(path, "artifactId", null), getValue(path, "classifier", null), handler.getExtension(), getValue(path, "version", null)); + if (toNullIfEmpty(artifact.getVersion()) == null) { + artifact = artifact.setVersion(getAnnotationProcessorPathVersion(artifact)); + } Set exclusions = convertToAetherExclusions(path.getChild("exclusions")); dependencies.add(new org.eclipse.aether.graph.Dependency(artifact, JavaScopes.RUNTIME, false, exclusions)); } return dependencies; } + private String getAnnotationProcessorPathVersion(org.eclipse.aether.artifact.Artifact annotationProcessorPath) + throws MojoExecutionException { + List managedDependencies = getProjectManagedDependencies(); + return findManagedVersion(annotationProcessorPath, managedDependencies) + .orElseThrow(() -> new MojoExecutionException(String.format( + "Cannot find version for annotation processor path '%s'. The version needs to be either" + + " provided directly in the plugin configuration or via dependency management.", + annotationProcessorPath))); + } + + private Optional findManagedVersion( + org.eclipse.aether.artifact.Artifact artifact, List managedDependencies) { + // here, Dependency uses null, while artifact uses empty strings + return managedDependencies.stream() + .filter(dep -> Objects.equals(dep.getGroupId(), artifact.getGroupId()) + && Objects.equals(dep.getArtifactId(), artifact.getArtifactId()) + && Objects.equals(dep.getClassifier(), toNullIfEmpty(artifact.getClassifier())) + && Objects.equals(dep.getType(), toNullIfEmpty(artifact.getExtension()))) + .findAny() + .map(org.apache.maven.model.Dependency::getVersion); + } + + private String toNullIfEmpty(String value) { + if (value != null && value.isBlank()) + return null; + return value; + } + + private List getProjectManagedDependencies() { + DependencyManagement dependencyManagement = project.getDependencyManagement(); + if (dependencyManagement == null || dependencyManagement.getDependencies() == null) { + return Collections.emptyList(); + } + return dependencyManagement.getDependencies(); + } + private String getValue(Xpp3Dom path, String element, String defaultValue) { Xpp3Dom child = path.getChild(element); + // don't bother filtering empty strings or null values, DefaultArtifact will turn nulls into empty strings if (child == null) { return defaultValue; } @@ -1505,7 +1558,6 @@ private void setAnnotationProcessorFlags(MavenDevModeLauncher.Builder builder) { break; } } - if (compilerMavenPlugin == null) { return; }