From c5b5bdc91a9e178875b8bc6cb61b9e2da4187d77 Mon Sep 17 00:00:00 2001 From: Ric Harris Date: Tue, 7 Apr 2026 16:48:25 +0100 Subject: [PATCH] Add control of PMD execution threads to plugin goal --- .../apache/maven/plugins/pmd/PmdReport.java | 45 +++++++++++++++++++ .../maven/plugins/pmd/exec/PmdExecutor.java | 3 ++ .../maven/plugins/pmd/exec/PmdRequest.java | 9 ++++ 3 files changed, 57 insertions(+) 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 7d9eb4a9..a0fb1cf3 100644 --- a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java +++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java @@ -219,6 +219,19 @@ public class PmdReport extends AbstractPmdReport { @Parameter(property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets") private File rulesetsTargetDirectory; + /** + * Controls the number of threads used by PMD Analysis for the given goal. + * This can be an integer, or a float (or int) followed by the letter `C`, eg `0.5C` or `1C`. + * In the latter case, the float will be multiplied by the number of cores of the host machine, and rounded down to an integer. + * If the specified number of threads is zero, then PMD will use the main thread for everything. If it is `n` > 0, + * PMD will spawn `n` separate analysis threads besides the main thread. + * The default behaviour is deferred to PMD and is currently 1C i.e. one thread per core on the host machine. + * + * @since 3.28.1 + */ + @Parameter(property = "pmd.executionThreads") + private String executionThreads; + /** * Used to locate configured rulesets. The rulesets could be on the plugin * classpath or in the local project file system. @@ -256,6 +269,7 @@ public PmdReport( /** * {@inheritDoc} */ + @Override public String getName(Locale locale) { return getI18nString(locale, "name"); } @@ -263,6 +277,7 @@ public String getName(Locale locale) { /** * {@inheritDoc} */ + @Override public String getDescription(Locale locale) { return getI18nString(locale, "description"); } @@ -371,11 +386,41 @@ private void executePmd() throws MavenReportException { request.setIncludeXmlInReports(includeXmlInReports); request.setReportOutputDirectory(getReportOutputDirectory().getAbsolutePath()); request.setJdkToolchain(getJdkToolchain()); + if (executionThreads != null) { + request.setExecutionThreads(numThreadsConverter(executionThreads)); + } getLog().info("PMD version: " + AbstractPmdReport.getPmdVersion()); pmdResult = serviceExecutor.execute(request); } + /** + * Essentially per org.apache.maven.cli.MavenCli.calculateDegreeOfConcurrency(String). + * @return the (integer) number of threads, where a suffix of C means that a multiple of available cores is calculated. + */ + private int numThreadsConverter(String executionThreadsString) { + boolean isCoreMultiplied = executionThreadsString.endsWith("C"); + if (isCoreMultiplied) { + executionThreadsString = + executionThreadsString.substring(0, executionThreadsString.length() - 1); // remove the C + try { + float f = Float.parseFloat(executionThreadsString); + if (f <= 0.0f) { + throw new IllegalArgumentException( + "Invalid threads core multiplier value: '" + f + "C'. Value must be positive."); + } + return (int) (f * Runtime.getRuntime().availableProcessors()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("'" + executionThreadsString + "' is not a float or integer"); + } + } + try { + return Integer.parseInt(executionThreadsString); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("'" + executionThreadsString + "' is not an integer"); + } + } + /** * Resolves the configured rulesets and copies them as files into the {@link #rulesetsTargetDirectory}. * diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java index dacde6e7..86df5ea1 100644 --- a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java +++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdExecutor.java @@ -173,6 +173,9 @@ public PmdResult run() throws MavenReportException { configuration.setRuleSets(request.getRulesets()); configuration.setMinimumPriority(RulePriority.valueOf(request.getMinimumPriority())); + if (request.getExecutionThreads() != null) { + configuration.setThreads(request.getExecutionThreads()); + } if (request.getBenchmarkOutputLocation() != null) { TimeTracker.startGlobalTracking(); } diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java index b923fada..42f49149 100644 --- a/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java +++ b/src/main/java/org/apache/maven/plugins/pmd/exec/PmdRequest.java @@ -58,6 +58,7 @@ public class PmdRequest implements Serializable { private String benchmarkOutputLocation; private boolean includeXmlInReports; private String reportOutputDirectory; + private Integer executionThreads; /** * Configure language and language version. @@ -225,4 +226,12 @@ public String getReportOutputDirectory() { public String getExcludeFromFailureFile() { return excludeFromFailureFile; } + + public Integer getExecutionThreads() { + return executionThreads; + } + + public void setExecutionThreads(Integer executionThreads) { + this.executionThreads = executionThreads; + } }