You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ad...@apache.org on 2021/09/05 18:25:51 UTC

[maven-pmd-plugin] 01/02: [MPMD-283] Create a real aggregate goal

This is an automated email from the ASF dual-hosted git repository.

adangel pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-pmd-plugin.git

commit 5ae53f135b46102e7b749c834173cdc97678f834
Author: Andreas Dangel <ad...@apache.org>
AuthorDate: Sun Sep 5 20:02:32 2021 +0200

    [MPMD-283] Create a real aggregate goal
    
    Closes #45
---
 src/it/MPMD-283-aggregated-pmd/invoker.properties  |  19 ++
 src/it/MPMD-283-aggregated-pmd/module-a/pom.xml    |  46 +++++
 .../module-a/src/main/java/module/a/IModuleA.java  |  24 +++
 .../module-a/src/main/java/module/a/ModuleA.java   |  24 +++
 src/it/MPMD-283-aggregated-pmd/module-b/pom.xml    |  60 ++++++
 .../module-b/src/main/java/module/b/ModuleB.java   |  54 +++++
 src/it/MPMD-283-aggregated-pmd/pom.xml             | 107 ++++++++++
 src/it/MPMD-283-aggregated-pmd/ruleset.xml         |  32 +++
 src/it/MPMD-283-aggregated-pmd/verify.groovy       |  61 ++++++
 src/it/multi-module/pom.xml                        |   8 +
 .../maven/plugins/pmd/AbstractPmdReport.java       |  76 ++++++-
 .../plugins/pmd/AbstractPmdViolationCheckMojo.java |  11 +-
 .../maven/plugins/pmd/AggregatorCpdReport.java     |  41 ++++
 .../pmd/AggregatorCpdViolationCheckMojo.java       |  40 ++++
 .../plugins/pmd/AggregatorPmdNoForkReport.java     |  37 ++++
 .../maven/plugins/pmd/AggregatorPmdReport.java     |  43 ++++
 .../pmd/AggregatorPmdViolationCheckMojo.java       |  40 ++++
 .../org/apache/maven/plugins/pmd/CpdReport.java    |   9 +-
 .../maven/plugins/pmd/CpdViolationCheckMojo.java   |   2 +-
 .../org/apache/maven/plugins/pmd/PmdReport.java    |  12 +-
 .../maven/plugins/pmd/PmdViolationCheckMojo.java   |   2 +-
 src/site/apt/examples/aggregate.apt.vm             | 219 +++++++++++++++++++++
 src/site/apt/examples/javascriptReport.apt.vm      |   7 +
 src/site/apt/examples/jspReport.apt.vm             |   7 +
 src/site/apt/examples/multi-module-config.apt.vm   |   7 +
 src/site/apt/examples/removeReport.apt.vm          |  10 +-
 src/site/apt/index.apt.vm                          |  32 ++-
 src/site/apt/usage.apt.vm                          |  18 +-
 src/site/fml/faq.fml                               |   6 +
 src/site/site.xml                                  |   1 +
 30 files changed, 1029 insertions(+), 26 deletions(-)

diff --git a/src/it/MPMD-283-aggregated-pmd/invoker.properties b/src/it/MPMD-283-aggregated-pmd/invoker.properties
new file mode 100644
index 0000000..8faff53
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/invoker.properties
@@ -0,0 +1,19 @@
+# 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 = clean verify site
+invoker.maven.version = 3+
diff --git a/src/it/MPMD-283-aggregated-pmd/module-a/pom.xml b/src/it/MPMD-283-aggregated-pmd/module-a/pom.xml
new file mode 100644
index 0000000..0a62abe
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/module-a/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.plugins.pmd.it</groupId>
+    <artifactId>MPMD-283-aggregated-pmd</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>MPMD-283-aggregated-pmd-module-a</artifactId>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>@project.groupId@</groupId>
+        <artifactId>@project.artifactId@</artifactId>
+        <configuration>
+          <rulesets>
+            <ruleset>../ruleset.xml</ruleset>
+          </rulesets>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/src/it/MPMD-283-aggregated-pmd/module-a/src/main/java/module/a/IModuleA.java b/src/it/MPMD-283-aggregated-pmd/module-a/src/main/java/module/a/IModuleA.java
new file mode 100644
index 0000000..83cfecf
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/module-a/src/main/java/module/a/IModuleA.java
@@ -0,0 +1,24 @@
+package module.a;
+
+/*
+ * 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.
+ */
+
+public interface IModuleA
+{
+}
\ No newline at end of file
diff --git a/src/it/MPMD-283-aggregated-pmd/module-a/src/main/java/module/a/ModuleA.java b/src/it/MPMD-283-aggregated-pmd/module-a/src/main/java/module/a/ModuleA.java
new file mode 100644
index 0000000..9d5d21d
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/module-a/src/main/java/module/a/ModuleA.java
@@ -0,0 +1,24 @@
+package module.a;
+
+/*
+ * 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.
+ */
+
+public class ModuleA implements IModuleA
+{
+}
\ No newline at end of file
diff --git a/src/it/MPMD-283-aggregated-pmd/module-b/pom.xml b/src/it/MPMD-283-aggregated-pmd/module-b/pom.xml
new file mode 100644
index 0000000..1833b2d
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/module-b/pom.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.maven.plugins.pmd.it</groupId>
+    <artifactId>MPMD-283-aggregated-pmd</artifactId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>MPMD-283-aggregated-pmd-module-b</artifactId>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>@project.groupId@</groupId>
+        <artifactId>@project.artifactId@</artifactId>
+        <configuration>
+          <rulesets>
+            <ruleset>../ruleset.xml</ruleset>
+          </rulesets>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>MPMD-283-aggregated-pmd-module-a</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math</artifactId>
+      <version>2.2</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/src/it/MPMD-283-aggregated-pmd/module-b/src/main/java/module/b/ModuleB.java b/src/it/MPMD-283-aggregated-pmd/module-b/src/main/java/module/b/ModuleB.java
new file mode 100644
index 0000000..0689d93
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/module-b/src/main/java/module/b/ModuleB.java
@@ -0,0 +1,54 @@
+package module.b;
+
+/*
+ * 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.
+ */
+
+import module.a.IModuleA;
+import module.a.ModuleA;
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.FieldElement;
+
+public class ModuleB
+{
+    public static void main( String[] args )
+    {
+        ModuleA m = new ModuleA();
+        doSomething( m );
+    }
+
+    // this method will be detected as being unsued,
+    // if typeresolution is not setup correctly: module a needs
+    // to be on PMD's auxclasspath, so that PMD knows, that ModuleA
+    // implements IModuleA
+    private static void doSomething( IModuleA module )
+    {
+        System.out.println( module );
+    }
+
+    public static void aPublicMethod()
+    {
+        Complex u = new Complex(1, 1);
+        aPrivateMethod( u );
+    }
+
+    private static void aPrivateMethod( FieldElement<Complex> u )
+    {
+        System.out.println( "aPrivateMethod: " + u );
+    }
+}
\ No newline at end of file
diff --git a/src/it/MPMD-283-aggregated-pmd/pom.xml b/src/it/MPMD-283-aggregated-pmd/pom.xml
new file mode 100644
index 0000000..2d1e909
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.plugins.pmd.it</groupId>
+  <artifactId>MPMD-283-aggregated-pmd</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+  </properties>
+
+  <modules>
+    <module>module-a</module>
+    <module>module-b</module>
+  </modules>
+
+  <build>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-site-plugin</artifactId>
+          <version>@sitePluginVersion@</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>@project.groupId@</groupId>
+        <artifactId>@project.artifactId@</artifactId>
+        <version>@project.version@</version>
+        <configuration>
+          <typeResolution>true</typeResolution>
+          <failurePriority>1</failurePriority>
+          <linkXRef>false</linkXRef>
+          <rulesets>
+            <ruleset>ruleset.xml</ruleset>
+          </rulesets>
+        </configuration>
+        <executions>
+          <execution>
+            <inherited>false</inherited> <!-- don't run aggregate in child modules -->
+            <goals>
+              <goal>aggregate-pmd-check</goal>
+              <goal>aggregate-cpd-check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>@project.groupId@</groupId>
+        <artifactId>@project.artifactId@</artifactId>
+        <version>@project.version@</version>
+        <configuration>
+          <typeResolution>true</typeResolution>
+          <rulesets>
+            <ruleset>ruleset.xml</ruleset>
+          </rulesets>
+          <skipEmptyReport>false</skipEmptyReport>
+          <linkXRef>false</linkXRef>
+        </configuration>
+        <reportSets>
+          <reportSet><!-- default reportSet - this is empty to prevent creating reports in child modules -->
+            <reports></reports>
+          </reportSet>
+          <reportSet><!-- aggregate reportSet, to define in poms having modules -->
+            <id>aggregate</id>
+            <inherited>false</inherited> <!-- don't run aggregate in child modules -->
+            <reports>
+              <report>aggregate-pmd</report>
+              <report>aggregate-cpd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
diff --git a/src/it/MPMD-283-aggregated-pmd/ruleset.xml b/src/it/MPMD-283-aggregated-pmd/ruleset.xml
new file mode 100644
index 0000000..71feeca
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/ruleset.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<ruleset name="Custom ruleset"
+    xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
+    <description>Custom Ruleset for test case MPMD-283</description>
+
+    <rule ref="category/java/bestpractices.xml/UnusedPrivateMethod">
+        <priority>5</priority>
+    </rule>
+
+</ruleset>
diff --git a/src/it/MPMD-283-aggregated-pmd/verify.groovy b/src/it/MPMD-283-aggregated-pmd/verify.groovy
new file mode 100644
index 0000000..f6d1d28
--- /dev/null
+++ b/src/it/MPMD-283-aggregated-pmd/verify.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+def paths = [
+    "target/site/pmd.html",
+    "target/site/cpd.html",
+    "target/pmd.xml",
+    "target/cpd.xml",
+]
+
+// aggregated reports must exist
+for ( String path : paths )
+{
+    File file = new File( basedir, path )
+    System.out.println( "Checking for existence of " + file )
+    if ( !file.isFile() )
+    {
+        throw new RuntimeException( "Missing: " + file )
+    }
+}
+
+// double check violations: these violations only appear, if type resolution didn't work
+// in case the modules have not been compiled before
+File pmdXml = new File( basedir, "target/pmd.xml" )
+assert !pmdXml.text.contains( "Avoid unused private methods such as 'doSomething(IModuleA)'." )
+assert !pmdXml.text.contains( "Avoid unused private methods such as 'aPrivateMethod(FieldElement)'." )
+
+File pmdHtml = new File( basedir, "target/site/pmd.html" )
+assert !pmdHtml.text.contains( "Avoid unused private methods such as 'doSomething(IModuleA)'." )
+assert !pmdHtml.text.contains( "Avoid unused private methods such as 'aPrivateMethod(FieldElement)'." )
+
+// no individual module reports
+def modules = [ "module-a", "module-b" ]
+for ( String module : modules )
+{
+  for ( String path : paths )
+  {
+    File file = new File( basedir, "${module}/${path}" )
+    System.out.println( "Checking for absence of " + file )
+    if ( file.exists() )
+    {
+        throw new RuntimeException( "Banned: " + file )
+    }
+  }
+}
diff --git a/src/it/multi-module/pom.xml b/src/it/multi-module/pom.xml
index c7b6279..cde2108 100644
--- a/src/it/multi-module/pom.xml
+++ b/src/it/multi-module/pom.xml
@@ -71,6 +71,14 @@ under the License.
         <configuration>
           <skipPmdError>false</skipPmdError>
         </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>pmd</report>
+              <report>cpd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
       </plugin>
     </plugins>
   </reporting>
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java b/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java
index ab8550d..7bdd483 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdReport.java
@@ -23,13 +23,17 @@ import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 
 import org.apache.maven.doxia.siterenderer.Renderer;
@@ -40,6 +44,7 @@ import org.apache.maven.plugins.annotations.Component;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.reporting.AbstractMavenReport;
+import org.apache.maven.reporting.MavenReportException;
 import org.apache.maven.toolchain.Toolchain;
 import org.apache.maven.toolchain.ToolchainManager;
 import org.codehaus.plexus.util.FileUtils;
@@ -80,7 +85,7 @@ public abstract class AbstractPmdReport
      * Set the output format type, in addition to the HTML report. Must be one of: "none", "csv", "xml", "txt" or the
      * full class name of the PMD renderer to use. See the net.sourceforge.pmd.renderers package javadoc for available
      * renderers. XML is produced in any case, since this format is needed
-     * for the check goals (pmd:check, pmd:cpd-check).
+     * for the check goals (pmd:check, pmd:aggregator-check, pmd:cpd-check, pmd:aggregator-cpd-check).
      */
     @Parameter( property = "format", defaultValue = "xml" )
     protected String format = "xml";
@@ -160,8 +165,11 @@ public abstract class AbstractPmdReport
      * Whether to build an aggregated report at the root, or build individual reports.
      *
      * @since 2.2
+     * @deprecated since 3.15.0 Use the goals <code>pmd:aggregate-pmd</code> and <code>pmd:aggregate-cpd</code>
+     * instead.
      */
     @Parameter( property = "aggregate", defaultValue = "false" )
+    @Deprecated
     protected boolean aggregate;
 
     /**
@@ -421,9 +429,9 @@ public abstract class AbstractPmdReport
                 }
             }
         }
-        if ( aggregate )
+        if ( isAggregator() )
         {
-            for ( MavenProject localProject : reactorProjects )
+            for ( MavenProject localProject : getAggregatedProjects() )
             {
                 List<String> localCompileSourceRoots = localProject.getCompileSourceRoots();
                 for ( String root : localCompileSourceRoots )
@@ -551,7 +559,7 @@ public abstract class AbstractPmdReport
             return false;
         }
 
-        if ( "pom".equals( project.getPackaging() ) && !aggregate )
+        if ( !isAggregator() && "pom".equalsIgnoreCase( project.getPackaging() ) )
         {
             return false;
         }
@@ -662,4 +670,62 @@ public abstract class AbstractPmdReport
 
         return tc;
     }
-}
\ No newline at end of file
+
+    protected boolean isAggregator()
+    {
+        // returning here aggregate for backwards compatibility
+        return aggregate;
+    }
+
+    // Note: same logic as in m-javadoc-p (MJAVADOC-134)
+    protected Collection<MavenProject> getAggregatedProjects()
+    {
+        Map<Path, MavenProject> reactorProjectsMap = new HashMap<>();
+        for ( MavenProject reactorProject : this.reactorProjects )
+        {
+            reactorProjectsMap.put( reactorProject.getBasedir().toPath(), reactorProject );
+        }
+
+        return modulesForAggregatedProject( project, reactorProjectsMap );
+    }
+
+    /**
+     * Recursively add the modules of the aggregatedProject to the set of aggregatedModules.
+     * 
+     * @param aggregatedProject the project being aggregated
+     * @param reactorProjectsMap map of (still) available reactor projects 
+     * @throws MavenReportException if any
+     */
+    private Set<MavenProject> modulesForAggregatedProject( MavenProject aggregatedProject,
+                                                           Map<Path, MavenProject> reactorProjectsMap )
+    {
+        // Maven does not supply an easy way to get the projects representing
+        // the modules of a project. So we will get the paths to the base
+        // directories of the modules from the project and compare with the
+        // base directories of the projects in the reactor.
+
+        if ( aggregatedProject.getModules().isEmpty() )
+        {
+            return Collections.singleton( aggregatedProject );
+        }
+
+        List<Path> modulePaths = new LinkedList<Path>();
+        for ( String module :  aggregatedProject.getModules() )
+        {
+            modulePaths.add( new File( aggregatedProject.getBasedir(), module ).toPath() );
+        }
+
+        Set<MavenProject> aggregatedModules = new LinkedHashSet<>();
+
+        for ( Path modulePath : modulePaths )
+        {
+            MavenProject module = reactorProjectsMap.remove( modulePath );
+            if ( module != null )
+            {
+                aggregatedModules.addAll( modulesForAggregatedProject( module, reactorProjectsMap ) );
+            }
+        }
+
+        return aggregatedModules;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdViolationCheckMojo.java b/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdViolationCheckMojo.java
index 6f0b3ea..88bdaba 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdViolationCheckMojo.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/AbstractPmdViolationCheckMojo.java
@@ -60,8 +60,11 @@ public abstract class AbstractPmdViolationCheckMojo<D>
      * Whether to build an aggregated report at the root, or build individual reports.
      *
      * @since 2.2
+     * @deprecated since 3.15.0 Use the goal <code>pmd:aggregate-check</code> or
+     * <code>pmd:aggregate-cpd-check</code> instead.
      */
     @Parameter( property = "aggregate", defaultValue = "false" )
+    @Deprecated
     protected boolean aggregate;
 
     /**
@@ -129,7 +132,7 @@ public abstract class AbstractPmdViolationCheckMojo<D>
             return;
         }
 
-        if ( "pom".equals( project.getPackaging() ) && !aggregate )
+        if ( !isAggregator() && "pom".equalsIgnoreCase( project.getPackaging() ) )
         {
             return;
         }
@@ -317,4 +320,10 @@ public abstract class AbstractPmdViolationCheckMojo<D>
     {
         return maxAllowedViolations;
     }
+
+    protected boolean isAggregator()
+    {
+        // returning here aggregate for backwards compatibility
+        return aggregate;
+    }
 }
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorCpdReport.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorCpdReport.java
new file mode 100644
index 0000000..e3aa906
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorCpdReport.java
@@ -0,0 +1,41 @@
+package org.apache.maven.plugins.pmd;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugins.annotations.Mojo;
+
+/**
+ * Creates a report for PMD's Copy/Paste Detector (CPD) tool in an <b>aggregator</b> project.
+ * It can also generate a cpd results file in any of these formats: xml, csv or txt.
+ *
+ * <p>See <a href="https://pmd.github.io/latest/pmd_userdocs_cpd.html">Finding duplicated code</a>
+ * for more details.
+ *
+ * @since 3.15.0
+ */
+@Mojo( name = "aggregate-cpd", aggregator = true, threadSafe = true )
+public class AggregatorCpdReport extends CpdReport
+{
+    @Override
+    protected boolean isAggregator()
+    {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorCpdViolationCheckMojo.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorCpdViolationCheckMojo.java
new file mode 100644
index 0000000..25bbb78
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorCpdViolationCheckMojo.java
@@ -0,0 +1,40 @@
+package org.apache.maven.plugins.pmd;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugins.annotations.Execute;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+
+/**
+ * Fail the build in an <b>aggregator</b> project if there were any CPD violations in the source code.
+ *
+ * @since 3.15.0
+ */
+@Mojo( name = "aggregate-cpd-check", defaultPhase = LifecyclePhase.VERIFY, threadSafe = true, aggregator = true )
+@Execute( goal = "aggregate-cpd" )
+public class AggregatorCpdViolationCheckMojo extends CpdViolationCheckMojo
+{
+    @Override
+    protected boolean isAggregator()
+    {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java
new file mode 100644
index 0000000..216ebae
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java
@@ -0,0 +1,37 @@
+package org.apache.maven.plugins.pmd;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugins.annotations.Execute;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+
+/**
+ * Creates a PMD site report in an <b>aggregator</b> project without forking the <code>test-compile</code> phase again.
+ * 
+ * @since 3.15.0
+ */
+@Mojo( name = "aggregate-pmd-no-fork", aggregator = true, threadSafe = true,
+    requiresDependencyResolution = ResolutionScope.TEST )
+@Execute( phase = LifecyclePhase.NONE )
+public class AggregatorPmdNoForkReport extends AggregatorPmdReport
+{
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java
new file mode 100644
index 0000000..3495241
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java
@@ -0,0 +1,43 @@
+package org.apache.maven.plugins.pmd;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugins.annotations.Execute;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+
+/**
+ * Creates a PMD site report in an <b>aggregator</b> project based on the rulesets and configuration set in the plugin.
+ * It can also generate a pmd output file aside from the site report in any of the following formats: xml, csv or txt.
+ * 
+ * @since 3.15.0
+ */
+@Mojo( name = "aggregate-pmd", aggregator = true, threadSafe = true,
+    requiresDependencyResolution = ResolutionScope.TEST )
+@Execute( phase = LifecyclePhase.TEST_COMPILE )
+public class AggregatorPmdReport extends PmdReport
+{
+    @Override
+    protected boolean isAggregator()
+    {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdViolationCheckMojo.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdViolationCheckMojo.java
new file mode 100644
index 0000000..6fdcab3
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdViolationCheckMojo.java
@@ -0,0 +1,40 @@
+package org.apache.maven.plugins.pmd;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.plugins.annotations.Execute;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+
+/**
+ * Fails the build in an <b>aggregator</b> project if there were any PMD violations in the source code.
+ *
+ * @since 3.15.0
+ */
+@Mojo( name = "aggregate-pmd-check", defaultPhase = LifecyclePhase.VERIFY, aggregator = true, threadSafe = true )
+@Execute( goal = "aggregate-pmd" )
+public class AggregatorPmdViolationCheckMojo extends PmdViolationCheckMojo
+{
+    @Override
+    protected boolean isAggregator()
+    {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java b/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
index 1af30da..8964e60 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/CpdReport.java
@@ -41,8 +41,10 @@ import net.sourceforge.pmd.cpd.JavaTokenizer;
 import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
 
 /**
- * Creates a report for PMD's CPD tool. See
- * <a href="https://pmd.github.io/latest/pmd_userdocs_cpd.html">Finding duplicated code</a>
+ * Creates a report for PMD's Copy/Paste Detector (CPD) tool.
+ * It can also generate a cpd results file in any of these formats: xml, csv or txt.
+ *
+ * <p>See <a href="https://pmd.github.io/latest/pmd_userdocs_cpd.html">Finding duplicated code</a>
  * for more details.
  *
  * @author Mike Perham
@@ -265,7 +267,8 @@ public class CpdReport
 
     private void generateMavenSiteReport( Locale locale )
     {
-        CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ), aggregate );
+        CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ),
+                isAggregator() );
         gen.generate( cpdResult.getDuplications() );
     }
 
diff --git a/src/main/java/org/apache/maven/plugins/pmd/CpdViolationCheckMojo.java b/src/main/java/org/apache/maven/plugins/pmd/CpdViolationCheckMojo.java
index 56bd004..9fab222 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/CpdViolationCheckMojo.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/CpdViolationCheckMojo.java
@@ -37,7 +37,7 @@ import org.apache.maven.plugins.annotations.Parameter;
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 /**
- * Fail the build if there were any CPD violations in the source code.
+ * Fails the build if there were any CPD violations in the source code.
  *
  * @version $Id$
  * @since 2.0
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
index 6becd50..04483da 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
@@ -57,7 +57,8 @@ import org.codehaus.plexus.util.StringUtils;
 import net.sourceforge.pmd.renderers.Renderer;
 
 /**
- * Creates a PMD report.
+ * Creates a PMD site report based on the rulesets and configuration set in the plugin.
+ * It can also generate a pmd output file aside from the site report in any of the following formats: xml, csv or txt.
  *
  * @author Brett Porter
  * @version $Id$
@@ -489,7 +490,8 @@ public class PmdReport
         throws MavenReportException
     {
         Sink sink = getSink();
-        PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ), aggregate );
+        PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ),
+                isAggregator() );
         doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
         doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
         doxiaRenderer.setFiles( filesToProcess );
@@ -549,7 +551,7 @@ public class PmdReport
         try
         {
             List<String> classpath = new ArrayList<>();
-            if ( aggregate )
+            if ( isAggregator() )
             {
                 List<String> dependencies = new ArrayList<>();
 
@@ -557,7 +559,7 @@ public class PmdReport
                 // if module a depends on module b and both are in the reactor
                 // then we don't want to resolve the dependency as an artifact.
                 List<String> exclusionPatterns = new ArrayList<>();
-                for ( MavenProject localProject : reactorProjects )
+                for ( MavenProject localProject : getAggregatedProjects() )
                 {
                     exclusionPatterns.add( localProject.getGroupId() + ":" + localProject.getArtifactId() );
                 }
@@ -567,7 +569,7 @@ public class PmdReport
                                      : ScopeFilter.including( "compile", "provided" )
                 ) );
 
-                for ( MavenProject localProject : reactorProjects )
+                for ( MavenProject localProject : getAggregatedProjects() )
                 {
                     ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(
                             session.getProjectBuildingRequest() );
diff --git a/src/main/java/org/apache/maven/plugins/pmd/PmdViolationCheckMojo.java b/src/main/java/org/apache/maven/plugins/pmd/PmdViolationCheckMojo.java
index 3f6200e..8ff7e62 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdViolationCheckMojo.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdViolationCheckMojo.java
@@ -39,7 +39,7 @@ import org.codehaus.plexus.util.StringUtils;
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 
 /**
- * Fail the build if there were any PMD violations in the source code.
+ * Fails the build if there were any PMD violations in the source code.
  *
  * @version $Id$
  * @since 2.0
diff --git a/src/site/apt/examples/aggregate.apt.vm b/src/site/apt/examples/aggregate.apt.vm
new file mode 100644
index 0000000..7b1bcd2
--- /dev/null
+++ b/src/site/apt/examples/aggregate.apt.vm
@@ -0,0 +1,219 @@
+ ------
+ Aggregating PMD reports for Multi-Module-Projects
+ ------
+ Andreas Dangel
+ ------
+ 2021-09-03
+ ------
+
+~~ 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/doxia/references/apt-format.html
+
+Aggregating PMD reports for Multi-Module-Projects
+
+ For example, consider the following directory structure:
+
++-----+
+
+Project
+  |-- pom.xml
+  |-- Module1
+  |   `-- pom.xml
+  |   `-- Module 2
+  |       `-- pom.xml
+  |   `-- Module 3
+  |       `-- pom.xml
+  |-- Module4
+  |   `-- pom.xml
+  `-- Module5
+    `-- pom.xml
+
++-----+
+
+ Since 3.15.0 the <<<aggregate>>> has changed a little bit. It'll generate aggregated 
+ reports at every level.
+ To get only an aggregated project at root level, you need to configure the pom like:
+ 
++-----+
+<project>
+  ...
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>${project.version}</version>
+        <reportSets>
+          <reportSet>
+            <id>aggregate</id>
+            <inherited>false</inherited> <!-- don't run aggregate in child modules -->
+            <reports>
+              <report>aggregate-pmd</report>
+              <report>aggregate-cpd</report>
+            </reports>
+          </reportSet>
+          <reportSet>
+            <id>default</id>
+            <reports>
+              <report>pmd</report>
+              <report>cpd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+    </plugins>
+    ...
+  </reporting>
+  ...
+</project>
++-----+
+
+* Using The <<<aggregate>>> Goals
+
+ The {{{../pmd-mojo.html#aggregate}\<aggregate/\>}} parameter doesn't make sure, that the project is
+ compiled before executing PMD which might lead to wrong results. Therefore the report goals
+ <<<aggregate-pmd>>> and <<<aggregate-cpd>>> have been introduced. You could define these goals in the
+ \<build/\> element (using the \<execution/\> tag) or \<reporting/\> element (using the \<reportSet/\> tag) as shown
+ below.
+
++-----+
+<project>
+  ...
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <!-- Default configuration for all reports -->
+          ...
+        </configuration>
+        <executions>
+          <execution>
+            <id>aggregate</id>
+            <goals>
+              <goal>aggregate-pmd</goal>
+              <goal>aggregate-cpd</goal>
+            </goals>
+            <phase>site</phase>
+            <configuration>
+              <!-- Specific configuration for the aggregate report -->
+              ...
+            </configuration>
+          </execution>
+          ...
+        </executions>
+      </plugin>
+      ...
+    </plugins>
+  </build>
+  ...
+</project>
++-----+
+
++-----+
+<project>
+  ...
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>${project.version}</version>
+        <configuration>
+          <!-- Default configuration for all reports -->
+          ...
+        </configuration>
+        <reportSets>
+          <reportSet>
+            <id>non-aggregate</id>
+            <configuration>
+              <!-- Specific configuration for the non aggregate report -->
+              ...
+            </configuration>
+            <reports>
+              <report>pmd</report>
+              <report>cpd</report>
+            </reports>
+          </reportSet>
+          <reportSet>
+            <id>aggregate</id>
+            <configuration>
+              <!-- Specific configuration for the aggregate report -->
+              ...
+            </configuration>
+            <reports>
+              <report>aggregate-pmd</report>
+              <report>aggregate-cpd</report>
+            </reports>
+          </reportSet>
+          ...
+        </reportSets>
+      </plugin>
+      ...
+    </plugins>
+  </reporting>
+  ...
+</project>
++-----+
+
+* Generate aggregate PMD without duplicate execution of phase test-compile
+
+ * The standard goal <<<aggregate-pmd>>> invokes separate lifecyle <<<test-compile>>>.
+
+ * In a CI environment you now might execute something like <<<mvn clean deploy site site-deploy>>>.
+
+ * During <<<site>>> build the standard reports will trigger <<<test-compile>>> again,
+ depending on your build this may take some time, because
+ stuff like <<<enforcer>>> or generating stubs from a WDSL will be invoked again, which may lead
+ to longer build times.
+
+ * As of version 3.15.0 a new report is defined, <<<aggregate-pmd-no-fork>>>
+ which will not trigger above phases a second time.
+
+ * Note: This is only a problem for PMD report. CPD does not invoke a separate lifecycle.
+
+ * Configure this in your <<<reporting>>> section as follows:
+
++-----+
+<project>
+  ...
+  <reporting>
+    <excludeDefaults>true</excludeDefaults>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>${project.version}</version>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>aggregate-pmd-no-fork</report>
+              <report>aggregate-cpd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+    </plugins>
+  </reporting>
+  ...
+</project>
++-----+
diff --git a/src/site/apt/examples/javascriptReport.apt.vm b/src/site/apt/examples/javascriptReport.apt.vm
index 5159d64..3883059 100644
--- a/src/site/apt/examples/javascriptReport.apt.vm
+++ b/src/site/apt/examples/javascriptReport.apt.vm
@@ -62,6 +62,13 @@ Analyzing JavaScript Code
             <compileSourceRoot>${basedir}/src/main/javascript</compileSourceRoot>
           </compileSourceRoots>
         </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>pmd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
       </plugin>
     </plugins>
   </reporting>
diff --git a/src/site/apt/examples/jspReport.apt.vm b/src/site/apt/examples/jspReport.apt.vm
index c1802c8..a75a44d 100644
--- a/src/site/apt/examples/jspReport.apt.vm
+++ b/src/site/apt/examples/jspReport.apt.vm
@@ -65,6 +65,13 @@ Analyzing JSP Code
             <compileSourceRoot>${basedir}/src/main/webapp</compileSourceRoot>
           </compileSourceRoots>
         </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>pmd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
       </plugin>
     </plugins>
   </reporting>
diff --git a/src/site/apt/examples/multi-module-config.apt.vm b/src/site/apt/examples/multi-module-config.apt.vm
index 1af734a..19e933c 100644
--- a/src/site/apt/examples/multi-module-config.apt.vm
+++ b/src/site/apt/examples/multi-module-config.apt.vm
@@ -182,6 +182,13 @@ whizbang
             <ruleset>whizbang/pmd-ruleset.xml</ruleset>
           </rulesets>
         </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>pmd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
       </plugin>
     </plugins>
   </reporting>
diff --git a/src/site/apt/examples/removeReport.apt.vm b/src/site/apt/examples/removeReport.apt.vm
index a1570fe..ff1afa7 100644
--- a/src/site/apt/examples/removeReport.apt.vm
+++ b/src/site/apt/examples/removeReport.apt.vm
@@ -3,7 +3,7 @@
  ------
  Dennis Lundberg
  ------
- 2007-12-28
+ 2021-09-03
  ------
 
  ~~ Licensed to the Apache Software Foundation (ASF) under one
@@ -28,8 +28,9 @@
 
 Remove Report
 
- You may want to generate only one of the reports, but not the other.  To do
- this you can use the <<<\<reportSets\>>>> feature within your POM.  Below is
+ You may want to generate only one of the reports, but not the other. Since version 3.15.0 separate
+ aggregate reports have been added, which would duplicate the reports. To select only the reports you want
+ you can use the <<<\<reportSets\>>>> feature within your POM.  Below is
  the default configuration for the plugin.  To disable one of the reports,
  just copy the <<<\<reportSets\>>>> element below to your POM and remove the
  <<<\<report\>>>> you don't want to generate.
@@ -47,6 +48,9 @@ Remove Report
             <reports>
               <report>pmd</report>
               <report>cpd</report>
+              <report>aggregate-pmd</report>
+              <report>aggregate-pmd-no-fork</report>
+              <report>aggregate-cpd</report>
             </reports>
           </reportSet>
         </reportSets>
diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm
index 613bd3d..a6dea66 100644
--- a/src/site/apt/index.apt.vm
+++ b/src/site/apt/index.apt.vm
@@ -37,19 +37,37 @@ ${project.name}
 
 * Goals Overview
 
-  This plugin has 4 goals:
+  This plugin has the following goals:
 
   * {{{./pmd-mojo.html}pmd:pmd}} creates a PMD site report based on the rulesets and configuration set in the plugin.
     It can also generate a pmd output file aside from the site report in any of the following formats: xml, csv or txt.
 
-  * {{{./cpd-mojo.html}pmd:cpd}} generates a report for PMD's Copy/Paste Detector (CPD) tool.  It can also
+  * {{{./aggregate-pmd-mojo.html}pmd:aggregate-pmd}} creates a PMD site report in an <<aggregator>> project
+    based on the rulesets and configuration set in the plugin. It can also generate a pmd output file aside from
+    the site report in any of the following formats: xml, csv or txt.
+
+  * {{{./aggregate-pmd-no-fork-mojo.html}pmd:aggregate-pmd-no-fork}} creates a PMD site report in an <<aggregator>>
+    project without forking the <<<test-compile>>> phase again.
+
+  * {{{./cpd-mojo.html}pmd:cpd}} creates a report for PMD's Copy/Paste Detector (CPD) tool. It can also
     generate a cpd results file in any of these formats: xml, csv or txt.
 
-  * {{{./check-mojo.html}pmd:check}} verifies that the PMD report is empty and fails the build if it is not.
-    This goal is executed by default when <<<pmd:pmd>>> is executed.
+  * {{{./aggregate-cpd-mojo}pmd:aggregate-cpd}} creates a report for PMD's Copy/Paste Detector (CPD) tool
+    in an <<aggregator>> project. It can also generate a cpd results file in any of these formats: xml, csv or txt.
+
+  * {{{./check-mojo.html}pmd:check}} fails the build if there were any PMD violations in the source code.
+    This goal invokes automatically <<<pmd:pmd>>> prior to executing itself.
 
-  * {{{./cpd-check-mojo.html}pmd:cpd-check}} verifies that the CPD report is empty and fails the build if it is not.
-    This goal is executed by default when <<<pmd:cpd>>> is executed.
+  * {{{./aggregate-pmd-check-mojo.html}pmd:aggregate-pmd-check}} fails the build in an <<aggregator>> project if there
+    were any PMD violations in the source code.
+    This goal invokes automatically <<<pmd:aggregate-pmd>>> prior to executing itself.
+
+  * {{{./cpd-check-mojo.html}pmd:cpd-check}} fails the build if there were any CPD violations in the source code.
+    This goal invokes automatically <<<pmd:cpd>>> prior to executing itself.
+
+  * {{{./aggregate-cpd-check-mojo.html}pmd:aggregate-cpd-check}} fails the build in an <<aggregator>> project
+    if there were any CPD violations in the source code.
+    This goal invokes automatically <<<pmd:aggregate-cpd>>> prior to executing itself.
 
 * Usage
 
@@ -86,6 +104,8 @@ ${project.name}
 
   * {{{./examples/multi-module-config.html}Multimodule Configuration}}
 
+  * {{{./examples/aggregate.html}Aggregating PMD reports for Multi-Module-Projects}}
+
   * {{{./examples/removeReport.html}Remove Report}}
 
   * {{{./examples/targetJdk.html}Target JDK and Toolchains}}
diff --git a/src/site/apt/usage.apt.vm b/src/site/apt/usage.apt.vm
index 6bcfe52..6b91235 100644
--- a/src/site/apt/usage.apt.vm
+++ b/src/site/apt/usage.apt.vm
@@ -81,7 +81,7 @@ Configuration
   You can configure the minimum code size which trips the CPD.  The default of <<<100>>> tokens corresponds
   to approximately 5-10 lines of code.
 
-  Since PMD parses the Java source, it needs to know which Java version to use. CPD's default is <<<1.9>>>.
+  Since PMD parses the Java source, it needs to know which Java version to use.
   The following is a possible configuration:
 
 +-----+
@@ -106,6 +106,22 @@ Configuration
             <excludeRoot>target/generated-sources/stubs</excludeRoot>
           </excludeRoots>
         </configuration>
+        <reportSets>
+          <reportSet><!-- by default, id = "default" -->
+            <reports><!-- select non-aggregate reports -->
+              <report>pmd</report>
+              <report>cpd</report>
+            </reports>
+          </reportSet>
+          <reportSet><!-- aggregate reportSet, to define in poms having modules -->
+            <id>aggregate</id>
+            <inherited>false</inherited> <!-- don't run aggregate in child modules -->
+            <reports>
+              <report>aggregate-pmd</report>
+              <report>aggregate-cpd</report>
+            </reports>
+          </reportSet>
+        </reportSets>
       </plugin>
     </plugins>
   </reporting>
diff --git a/src/site/fml/faq.fml b/src/site/fml/faq.fml
index 658a154..bcef70a 100644
--- a/src/site/fml/faq.fml
+++ b/src/site/fml/faq.fml
@@ -126,6 +126,12 @@ under the License.
           First pass will compile the projects (e.g. <code>mvn clean package</code>) and the second pass
           will execute PMD without clean via the verify phase (e.g. <code>mvn verify</code>).
         </p>
+        <p>
+          Since version 3.15.0 the new goal <a href="aggregate-pmd-mojo.html">aggregate-pmd</a> can be used
+          which allows to run everything with only one maven call. However, this goal invokes the lifecycle
+          <em>test-compile</em> before executing itself, which might lead to duplicated execution of some
+          plugins.
+        </p>
       </answer>
     </faq>
   </part>
diff --git a/src/site/site.xml b/src/site/site.xml
index d3d61c3..7df2790 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -35,6 +35,7 @@ under the License.
     <menu name="Examples">
       <item name="Upgrading PMD at Runtime" href="examples/upgrading-PMD-at-runtime.html"/>
       <item name="Multimodule Configuration" href="examples/multi-module-config.html"/>
+      <item name="Aggregating PMD reports" href="examples/aggregate.html"/>
       <item name="Remove Report" href="examples/removeReport.html"/>
       <item name="Target JDK and Toolchains" href="examples/targetJdk.html"/>
       <item name="Using Rule Sets" href="examples/usingRuleSets.html"/>