From 3355bf815007d1fd0712cf61acd08960e963b4a1 Mon Sep 17 00:00:00 2001 From: Ashley Scopes <73482956+ascopes@users.noreply.github.com> Date: Sat, 20 Dec 2025 14:00:31 +0000 Subject: [PATCH 1/2] GH-944: Allow users to specify root paths to exclude from QDox scanning This enables users to work around issues such as https://github.com/paul-hammant/qdox/issues/219, https://github.com/paul-hammant/qdox/issues/1, etc that result in issues such as https://github.com/apache/maven-plugin-tools/issues/944 being raised. With this change, users can specify a set of excluded scan directories that source trees reside within to prevent maven-plugin-plugin from feeding them into the Java source analysis tooling, which allows unblocking builds that are completely blocked until such a time that issues such as https://github.com/paul-hammant/qdox/issues/287 are addressed. Given the long release span between releases of dependent libraries, developers will otherwise be blocked for long periods of time with unbuildable projects using the current tooling. --- .../invoker.properties | 18 +++ .../gh-944-exclude-source-directory/pom.xml | 108 ++++++++++++++++++ .../apache/maven/plugin/coreit/FirstMojo.java | 52 +++++++++ .../plugin/coreit/SomeGeneratedModel.java | 34 ++++++ .../plugin/DescriptorGeneratorMojo.java | 32 ++++++ ...avaAnnotationsMojoDescriptorExtractor.java | 30 ++++- .../plugin/DefaultPluginToolsRequest.java | 17 +++ .../tools/plugin/PluginToolsRequest.java | 18 +++ .../JavaJavadocMojoDescriptorExtractor.java | 21 +++- 9 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 maven-plugin-plugin/src/it/gh-944-exclude-source-directory/invoker.properties create mode 100644 maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml create mode 100644 maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java create mode 100644 maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/invoker.properties b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/invoker.properties new file mode 100644 index 000000000..c959b536b --- /dev/null +++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/invoker.properties @@ -0,0 +1,18 @@ +# 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. + +invoker.goals = process-classes diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml new file mode 100644 index 000000000..6276aca0c --- /dev/null +++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml @@ -0,0 +1,108 @@ + + + + + + 4.0.0 + + org.apache.maven.its.mplugin-382 + mplugin-382-exclude-provided-dependency + 1.0-SNAPSHOT + maven-plugin + + Maven Integration Test :: gh-944-exclude-source-directory + + Test plugin-plugin, plugin.xml descriptor - shouldn't contain explicitly excluded source directories + + + + UTF-8 + + + + + org.apache.maven + maven-plugin-api + @maven3Version@ + provided + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + @project.version@ + provided + + + + + org.immutables + value + 2.12.0 + provided + + + + org.jspecify + jspecify + 1.0.0 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + @compilerPluginVersion@ + + 1.8 + 1.8 + + + + org.immutables + value + 2.12.0 + + + + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + @project.version@ + + + + ${project.build.directory}/generated-sources/annotations + + prefix + + + + + + diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java new file mode 100644 index 000000000..e459b61c3 --- /dev/null +++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/FirstMojo.java @@ -0,0 +1,52 @@ +/* + * 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.plugin.coreit; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; + +/** + * Touches a test file. + * + * @since 1.2 + */ +@Mojo( + name = "first", + requiresDependencyResolution = ResolutionScope.TEST, + defaultPhase = LifecyclePhase.INTEGRATION_TEST) +public class FirstMojo extends AbstractMojo { + + /** + * @since 0.1 + * @deprecated As of 0.2 + */ + @Parameter(alias = "alias") + private String aliasedParam; + + @Parameter + private ModifiableSomeGeneratedModel foo; + + public void execute() throws MojoExecutionException { + // nothing + } +} diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java new file mode 100644 index 000000000..c5290edd5 --- /dev/null +++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/src/main/java/org/apache/maven/plugin/coreit/SomeGeneratedModel.java @@ -0,0 +1,34 @@ +/* + * 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.plugin.coreit; + +import java.util.List; + +import org.immutables.value.Value.Immutable; +import org.immutables.value.Value.Modifiable; +import org.jspecify.annotations.Nullable; + +@Immutable +@Modifiable +public interface SomeGeneratedModel { + // Triggers https://github.com/paul-hammant/qdox/issues/287 on the generated code when QDox tries + // to scan it. + @Nullable + List<@Nullable String> getThings(); +} diff --git a/maven-plugin-plugin/src/main/java/org/apache/maven/plugin/plugin/DescriptorGeneratorMojo.java b/maven-plugin-plugin/src/main/java/org/apache/maven/plugin/plugin/DescriptorGeneratorMojo.java index ffbb36448..84a177d75 100644 --- a/maven-plugin-plugin/src/main/java/org/apache/maven/plugin/plugin/DescriptorGeneratorMojo.java +++ b/maven-plugin-plugin/src/main/java/org/apache/maven/plugin/plugin/DescriptorGeneratorMojo.java @@ -130,6 +130,37 @@ public class DescriptorGeneratorMojo extends AbstractGeneratorMojo { @Parameter private Set extractors; + /** + * A set of root directories to exclude from being scanned, if the extractor scans source directories + * to obtain metadata. + * + *

Users can specify this to prevent certain generated source roots from being parsed by this plugin + * in the event that those source roots contain potentially malformed or incompatible code. + * + *

This is primarily designed to facilitate allowing this plugin to operate with generated sources + * that use annotations or documentation in an uncontrollable format that may conflict with the parsing + * rules we utilise. + * + *

Note that this only accepts source roots. It will not accept + * specific paths within a source root (e.g. specific packages). In this context, a source root + * would be considered to be a directory holding a full Java package structure which can be + * passed directly to {@code javac} for compilation, or {@code javadoc} for documentation. + * + *

As an example, the following configuration will prevent this goal scanning any + * generated sources from annotation processors: + * + *

{@code
+     *   
+     *     ${project.build.directory}/generated-sources/annotations
+     *     ${project.build.directory}/generated-test-sources/annotations
+     *   
+     * }
+ * + * @since TBC + */ + @Parameter + private Set excludedScanDirectories = Collections.emptySet(); + /** * By default, an exception is throw if no mojo descriptor is found. As the maven-plugin is defined in core, the * descriptor generator mojo is bound to generate-resources phase. @@ -351,6 +382,7 @@ public void generate() throws MojoExecutionException { request.setInternalJavadocVersion(internalJavadocVersion); request.setExternalJavadocBaseUrls(externalJavadocBaseUrls); request.setSettings(mavenSession.getSettings()); + request.setExcludedScanDirectories(excludedScanDirectories); mojoScanner.populatePluginDescriptor(request); request.setPluginDescriptor(extendPluginDescriptor(request)); diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java index 043473499..ae31e2207 100644 --- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java +++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java @@ -254,7 +254,7 @@ private JavaProjectBuilder scanJavadoc( JavaProjectBuilder builder = new JavaProjectBuilder(new SortedClassLibraryBuilder()); builder.setEncoding(request.getEncoding()); - extendJavaProjectBuilder(builder, request.getProject()); + extendJavaProjectBuilder(request, builder, request.getProject()); for (MojoAnnotatedClass mojoAnnotatedClass : mojoAnnotatedClasses) { if (Objects.equals( @@ -289,7 +289,7 @@ private JavaProjectBuilder scanJavadoc( } for (MavenProject mavenProject : mavenProjects) { - extendJavaProjectBuilder(builder, mavenProject); + extendJavaProjectBuilder(request, builder, mavenProject); } return builder; @@ -630,11 +630,19 @@ protected void extendJavaProjectBuilderWithSourcesJar( } } - private void extendJavaProjectBuilder(JavaProjectBuilder builder, final MavenProject project) { + private void extendJavaProjectBuilder( + PluginToolsRequest request, JavaProjectBuilder builder, final MavenProject project) { List sources = new ArrayList<>(); for (String source : project.getCompileSourceRoots()) { - sources.add(new File(source)); + File sourceFile = new File(source); + + // Allow users to exclude certain paths such as generated sources from being scanned, in the case that + // this may be problematic for them (e.g. using obscure unsupported syntax by the parser, comments that + // cannot be controlled, etc.) + if (!isExcludedDirectory(request.getExcludedScanDirectories(), sourceFile)) { + sources.add(sourceFile); + } } // TODO be more dynamic @@ -642,9 +650,23 @@ private void extendJavaProjectBuilder(JavaProjectBuilder builder, final MavenPro if (!project.getCompileSourceRoots().contains(generatedPlugin.getAbsolutePath()) && generatedPlugin.exists()) { sources.add(generatedPlugin); } + extendJavaProjectBuilder(builder, sources, project.getArtifacts()); } + private boolean isExcludedDirectory(Collection excludedDirectories, File sourceFile) { + for (File excludedScanDirectory : excludedDirectories) { + File candidateFile = sourceFile; + while (candidateFile != null) { + if (excludedScanDirectory.equals(candidateFile)) { + return true; + } + candidateFile = candidateFile.getParentFile(); + } + } + return false; + } + private void extendJavaProjectBuilder( JavaProjectBuilder builder, List sourceDirectories, Set artifacts) { diff --git a/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/DefaultPluginToolsRequest.java b/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/DefaultPluginToolsRequest.java index 1beb53791..6990fda0a 100644 --- a/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/DefaultPluginToolsRequest.java +++ b/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/DefaultPluginToolsRequest.java @@ -18,7 +18,9 @@ */ package org.apache.maven.tools.plugin; +import java.io.File; import java.net.URI; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -66,6 +68,8 @@ public class DefaultPluginToolsRequest implements PluginToolsRequest { private String mavenApiVersion; + private Collection excludedScanDirectories; + public DefaultPluginToolsRequest(MavenProject project, PluginDescriptor pluginDescriptor) { this.project = project; this.pluginDescriptor = pluginDescriptor; @@ -232,4 +236,17 @@ public PluginToolsRequest setUsedMavenApiVersion(String mavenApiVersion) { public String getUsedMavenApiVersion() { return mavenApiVersion; } + + @Override + public Collection getExcludedScanDirectories() { + if (excludedScanDirectories == null) { + excludedScanDirectories = new HashSet<>(); + } + return excludedScanDirectories; + } + + @Override + public void setExcludedScanDirectories(Collection excludedScanDirectories) { + this.excludedScanDirectories = excludedScanDirectories; + } } diff --git a/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/PluginToolsRequest.java b/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/PluginToolsRequest.java index 079b120d4..64f232d3e 100644 --- a/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/PluginToolsRequest.java +++ b/maven-plugin-tools-api/src/main/java/org/apache/maven/tools/plugin/PluginToolsRequest.java @@ -18,7 +18,9 @@ */ package org.apache.maven.tools.plugin; +import java.io.File; import java.net.URI; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -211,4 +213,20 @@ public interface PluginToolsRequest { * @since 3.8.0 */ String getUsedMavenApiVersion(); + + /** + * Get the collection of directories to exclude from scanning during the detection of sources. + * + * @return the directories to exclude from scanning during detection of sources. + * @since TBC + */ + Collection getExcludedScanDirectories(); + + /** + * Set the collection of directories to exclude from scanning during the detection of sources. + * + * @param excludedScanDirectories the directories to exclude from scanning during detection of sources. + * @since TBC + */ + void setExcludedScanDirectories(Collection excludedScanDirectories); } diff --git a/maven-plugin-tools-java/src/main/java/org/apache/maven/tools/plugin/extractor/javadoc/JavaJavadocMojoDescriptorExtractor.java b/maven-plugin-tools-java/src/main/java/org/apache/maven/tools/plugin/extractor/javadoc/JavaJavadocMojoDescriptorExtractor.java index 7bd772f68..a990b7c4f 100644 --- a/maven-plugin-tools-java/src/main/java/org/apache/maven/tools/plugin/extractor/javadoc/JavaJavadocMojoDescriptorExtractor.java +++ b/maven-plugin-tools-java/src/main/java/org/apache/maven/tools/plugin/extractor/javadoc/JavaJavadocMojoDescriptorExtractor.java @@ -548,7 +548,13 @@ protected Collection discoverClasses(final PluginToolsRequest request MavenProject project = request.getProject(); for (String source : project.getCompileSourceRoots()) { - builder.addSourceTree(new File(source)); + // Allow users to exclude certain paths such as generated sources from being scanned, in the case that + // this may be problematic for them (e.g. using obscure unsupported syntax by the parser, comments that + // cannot be controlled, etc.) + File sourceFile = new File(source); + if (!isExcludedDirectory(request.getExcludedScanDirectories(), sourceFile)) { + builder.addSourceTree(sourceFile); + } } // TODO be more dynamic @@ -573,4 +579,17 @@ protected void validate(MojoDescriptor mojoDescriptor) throws InvalidParameterEx } } } + + private boolean isExcludedDirectory(Collection excludedDirectories, File sourceFile) { + for (File excludedScanDirectory : excludedDirectories) { + File candidateFile = sourceFile; + while (candidateFile != null) { + if (excludedScanDirectory.equals(candidateFile)) { + return true; + } + candidateFile = candidateFile.getParentFile(); + } + } + return false; + } } From 64a95079c419fe4c7bc74d0f0c8e4479845bbd0d Mon Sep 17 00:00:00 2001 From: Ash <73482956+ascopes@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:04:58 +0000 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/it/gh-944-exclude-source-directory/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml index 6276aca0c..c59b6e3fc 100644 --- a/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml +++ b/maven-plugin-plugin/src/it/gh-944-exclude-source-directory/pom.xml @@ -22,8 +22,8 @@ under the License. 4.0.0 - org.apache.maven.its.mplugin-382 - mplugin-382-exclude-provided-dependency + org.apache.maven.its.gh-944-exclude-source-directory + gh-944-exclude-source-directory 1.0-SNAPSHOT maven-plugin