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 2026/09/10 07:50:59 UTC
(maven-pmd-plugin) branch master updated: Use targetJdk to find Java platform classes from toolchain for type resolution (#726)
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
The following commit(s) were added to refs/heads/master by this push:
new 7e1fbfe Use targetJdk to find Java platform classes from toolchain for type resolution (#726)
7e1fbfe is described below
commit 7e1fbfedfa7683a1875366524609e06a997aab38
Author: Andreas Dangel <ad...@apache.org>
AuthorDate: Thu Sep 10 09:50:54 2026 +0200
Use targetJdk to find Java platform classes from toolchain for type resolution (#726)
This change improves the correctness of PMD violations: For type resolution, PMD loads the classes to see the available type hierarchy, methods, return types etc. The project's dependencies are already provided to PMD by the plugin with the aux classpath. But the Java platform classes where still resolved from the current Java runtime. This can lead for specific cases in either false positives or false negatives. Examples are: `ExecutorService` is `AutoCloseable` since Java 19. If mav [...]
Historically, PMD 6.x loaded the types via reflection from a custom classloader - so it was not able to load classes from newer runtimes then it was running at. Hence the solution with the toolchains (MPMD-304 #277). With PMD 7, the classes are loaded via ASM, which allows to decouple the Java version used for executing PMD (runtime) from the Java version of the project under analysis (targetJdk). Since PMD 7, PMD running under Java 8 can analyze e.g. Java 25 projects. But one crucial [...]
To resolve Java platform classes, PMD by default used the current runtime. With the next PMD version 7.27.0, we'll log a warning, if this happens.
This change prepares for that and tries to add the correct Java platform classes to the aux classpath for PMD by using an appropriate toolchain (selected by targetJdk). It will add either rt.jar or jrt-fs.jar (depending on the Java version) to the aux classpath automatically.
Note: This change is independent of the PMD version. Providing the jrt-fs.jar or rt.jar on the aux classpath was supported since PMD 7.0.0, but just not used. The only change in PMD 7.27.0 in that regard is, that a warning is logged, if neither jrt-fs.jar or rt.jar is found on the aux classpath.
---
.../invoker.properties | 20 ++++
src/it/auxclasspath-with-java-runtime-11/pom.xml | 56 +++++++++
.../src/main/java/App.java | 23 ++++
.../verify.groovy | 34 ++++++
.../invoker.properties | 20 ++++
src/it/auxclasspath-with-java-runtime-8/pom.xml | 57 +++++++++
.../src/main/java/App.java | 23 ++++
.../auxclasspath-with-java-runtime-8/verify.groovy | 34 ++++++
.../invoker.properties | 20 ++++
.../auxclasspath-with-java-runtime-missing/pom.xml | 56 +++++++++
.../src/main/java/App.java | 23 ++++
.../toolchains.xml | 25 ++++
.../verify.groovy | 35 ++++++
.../plugins/pmd/AggregatorPmdNoForkReport.java | 6 +-
.../maven/plugins/pmd/AggregatorPmdReport.java | 6 +-
.../org/apache/maven/plugins/pmd/PmdReport.java | 127 ++++++++++++++++++++-
.../maven/plugins/pmd/exec/ServiceExecutor.java | 5 +-
src/site/markdown/examples/targetJdk.md.vm | 29 ++++-
src/site/markdown/faq.md | 10 ++
src/site/markdown/index.md.vm | 4 +
20 files changed, 603 insertions(+), 10 deletions(-)
diff --git a/src/it/auxclasspath-with-java-runtime-11/invoker.properties b/src/it/auxclasspath-with-java-runtime-11/invoker.properties
new file mode 100644
index 0000000..617c24a
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-11/invoker.properties
@@ -0,0 +1,20 @@
+# 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.toolchain.jdk.version = 11
+invoker.goals = clean verify
+invoker.debug = true
diff --git a/src/it/auxclasspath-with-java-runtime-11/pom.xml b/src/it/auxclasspath-with-java-runtime-11/pom.xml
new file mode 100644
index 0000000..e5254aa
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-11/pom.xml
@@ -0,0 +1,56 @@
+<?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>auxclasspath-with-java-runtime-11</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version>11</java.version>
+ <maven.compiler.release>${java.version}</maven.compiler.release>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>@project.groupId@</groupId>
+ <artifactId>@project.artifactId@</artifactId>
+ <version>@project.version@</version>
+ <configuration>
+ <targetJdk>${java.version}</targetJdk>
+ <linkXRef>false</linkXRef>
+ <typeResolution>true</typeResolution>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/auxclasspath-with-java-runtime-11/src/main/java/App.java b/src/it/auxclasspath-with-java-runtime-11/src/main/java/App.java
new file mode 100644
index 0000000..ce723a3
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-11/src/main/java/App.java
@@ -0,0 +1,23 @@
+/*
+ * 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 App
+{
+
+}
diff --git a/src/it/auxclasspath-with-java-runtime-11/verify.groovy b/src/it/auxclasspath-with-java-runtime-11/verify.groovy
new file mode 100644
index 0000000..21af998
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-11/verify.groovy
@@ -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.
+ */
+
+File buildLog = new File( basedir, 'build.log' )
+assert buildLog.exists()
+
+def auxclasspathLog = ''
+for ( String line : buildLog.readLines() ) {
+ if ( line.contains( 'aux classpath' ) ) {
+ auxclasspathLog += line + '\n';
+ }
+}
+
+// convert windows path names
+auxclasspathLog = auxclasspathLog.replaceAll('\\\\', '/')
+
+assert 1 == auxclasspathLog.count( '[DEBUG] Using aux classpath:' )
+assert 1 == auxclasspathLog.count( 'lib/jrt-fs.jar to aux classpath' )
diff --git a/src/it/auxclasspath-with-java-runtime-8/invoker.properties b/src/it/auxclasspath-with-java-runtime-8/invoker.properties
new file mode 100644
index 0000000..c5abad7
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-8/invoker.properties
@@ -0,0 +1,20 @@
+# 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.toolchain.jdk.version = 8
+invoker.goals = clean verify
+invoker.debug = true
diff --git a/src/it/auxclasspath-with-java-runtime-8/pom.xml b/src/it/auxclasspath-with-java-runtime-8/pom.xml
new file mode 100644
index 0000000..ea33ca1
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-8/pom.xml
@@ -0,0 +1,57 @@
+<?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>auxclasspath-with-java-runtime-8</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version>8</java.version>
+ <maven.compiler.source>${java.version}</maven.compiler.source>
+ <maven.compiler.target>${java.version}</maven.compiler.target>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>@project.groupId@</groupId>
+ <artifactId>@project.artifactId@</artifactId>
+ <version>@project.version@</version>
+ <configuration>
+ <targetJdk>${java.version}</targetJdk>
+ <linkXRef>false</linkXRef>
+ <typeResolution>true</typeResolution>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/auxclasspath-with-java-runtime-8/src/main/java/App.java b/src/it/auxclasspath-with-java-runtime-8/src/main/java/App.java
new file mode 100644
index 0000000..ce723a3
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-8/src/main/java/App.java
@@ -0,0 +1,23 @@
+/*
+ * 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 App
+{
+
+}
diff --git a/src/it/auxclasspath-with-java-runtime-8/verify.groovy b/src/it/auxclasspath-with-java-runtime-8/verify.groovy
new file mode 100644
index 0000000..d1bb8b7
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-8/verify.groovy
@@ -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.
+ */
+
+File buildLog = new File( basedir, 'build.log' )
+assert buildLog.exists()
+
+def auxclasspathLog = ''
+for ( String line : buildLog.readLines() ) {
+ if ( line.contains( 'aux classpath' ) ) {
+ auxclasspathLog += line + '\n';
+ }
+}
+
+// convert windows path names
+auxclasspathLog = auxclasspathLog.replaceAll('\\\\', '/')
+
+assert 1 == auxclasspathLog.count( '[DEBUG] Using aux classpath:' )
+assert 1 == auxclasspathLog.count( 'lib/rt.jar to aux classpath' )
diff --git a/src/it/auxclasspath-with-java-runtime-missing/invoker.properties b/src/it/auxclasspath-with-java-runtime-missing/invoker.properties
new file mode 100644
index 0000000..24f9e95
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-missing/invoker.properties
@@ -0,0 +1,20 @@
+# 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.java.version = 17+
+invoker.goals = --toolchains toolchains.xml clean verify
+invoker.debug = true
diff --git a/src/it/auxclasspath-with-java-runtime-missing/pom.xml b/src/it/auxclasspath-with-java-runtime-missing/pom.xml
new file mode 100644
index 0000000..6922cc0
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-missing/pom.xml
@@ -0,0 +1,56 @@
+<?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>auxclasspath-with-java-runtime-missing</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <java.version>11</java.version>
+ <maven.compiler.release>${java.version}</maven.compiler.release>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>@project.groupId@</groupId>
+ <artifactId>@project.artifactId@</artifactId>
+ <version>@project.version@</version>
+ <configuration>
+ <targetJdk>${java.version}</targetJdk>
+ <linkXRef>false</linkXRef>
+ <typeResolution>true</typeResolution>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/src/it/auxclasspath-with-java-runtime-missing/src/main/java/App.java b/src/it/auxclasspath-with-java-runtime-missing/src/main/java/App.java
new file mode 100644
index 0000000..ce723a3
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-missing/src/main/java/App.java
@@ -0,0 +1,23 @@
+/*
+ * 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 App
+{
+
+}
diff --git a/src/it/auxclasspath-with-java-runtime-missing/toolchains.xml b/src/it/auxclasspath-with-java-runtime-missing/toolchains.xml
new file mode 100644
index 0000000..8a659f1
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-missing/toolchains.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+
+<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 https://maven.apache.org/xsd/toolchains-1.1.0.xsd">
+ <!-- Deliberately empty to test PMD execution, when there is no matching toolchain for targetJdk -->
+</toolchains>
diff --git a/src/it/auxclasspath-with-java-runtime-missing/verify.groovy b/src/it/auxclasspath-with-java-runtime-missing/verify.groovy
new file mode 100644
index 0000000..643a7a6
--- /dev/null
+++ b/src/it/auxclasspath-with-java-runtime-missing/verify.groovy
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+File buildLog = new File( basedir, 'build.log' )
+assert buildLog.exists()
+
+def auxclasspathLog = ''
+for ( String line : buildLog.readLines() ) {
+ if ( line.contains( 'aux classpath' ) ) {
+ auxclasspathLog += line + '\n';
+ }
+}
+
+// convert windows path names
+auxclasspathLog = auxclasspathLog.replaceAll('\\\\', '/')
+
+assert 1 == auxclasspathLog.count( '[DEBUG] Using aux classpath:' )
+assert 1 == auxclasspathLog.count( '[WARNING] Adding current java runtime' )
+assert 1 == auxclasspathLog.count( 'lib/jrt-fs.jar to aux classpath' )
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java
index c95e09a..2462526 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdNoForkReport.java
@@ -25,6 +25,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.pmd.exec.PmdServiceExecutor;
+import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.resource.ResourceManager;
@@ -46,7 +47,8 @@ public class AggregatorPmdNoForkReport extends AggregatorPmdReport {
ResourceManager locator,
ConfigurationService configurationService,
I18N i18n,
- PmdServiceExecutor serviceExecutor) {
- super(locator, configurationService, i18n, serviceExecutor);
+ PmdServiceExecutor serviceExecutor,
+ ToolchainManager toolchainManager) {
+ super(locator, configurationService, i18n, serviceExecutor, toolchainManager);
}
}
diff --git a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java
index 02f7726..ba9f13c 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/AggregatorPmdReport.java
@@ -25,6 +25,7 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.pmd.exec.PmdServiceExecutor;
+import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.resource.ResourceManager;
@@ -43,8 +44,9 @@ public class AggregatorPmdReport extends PmdReport {
ResourceManager locator,
ConfigurationService configurationService,
I18N i18n,
- PmdServiceExecutor serviceExecutor) {
- super(locator, configurationService, i18n, serviceExecutor);
+ PmdServiceExecutor serviceExecutor,
+ ToolchainManager toolchainManager) {
+ super(locator, configurationService, i18n, serviceExecutor, toolchainManager);
}
@Override
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 87e6a9f..bd5c2b3 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/PmdReport.java
@@ -22,11 +22,17 @@ import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.stream.Stream;
import net.sourceforge.pmd.renderers.Renderer;
import org.apache.maven.plugins.annotations.Mojo;
@@ -38,6 +44,8 @@ import org.apache.maven.plugins.pmd.exec.PmdResult;
import org.apache.maven.plugins.pmd.exec.PmdServiceExecutor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.MavenReportException;
+import org.apache.maven.toolchain.Toolchain;
+import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.resource.ResourceManager;
import org.codehaus.plexus.resource.loader.FileResourceCreationException;
@@ -65,6 +73,11 @@ public class PmdReport extends AbstractPmdReport {
* <p>You can override the default PMD version by specifying PMD as a dependency,
* see <a href="examples/upgrading-PMD-at-runtime.html">Upgrading PMD at Runtime</a>.</p>
*
+ * <p>The {@code targetJdk} version is used to resolve java platform classes from a configured
+ * toolchain for type resolution, see <a href="examples/targetJdk.html">Target JDK and Toolchains</a>
+ * and <a href="faq.html#typeresolution_aggregate">Why do I get sometimes false positive and/or false negative
+ * violations?</a>.</p>
+ *
* <p>
* <b>Note:</b> this parameter is only used if the language parameter is set to <code>java</code>.
* </p>
@@ -112,6 +125,7 @@ public class PmdReport extends AbstractPmdReport {
* feature.
*
* @since 3.0
+ * @see #targetJdk
*/
@Parameter(property = "pmd.typeResolution", defaultValue = "true")
private boolean typeResolution;
@@ -247,6 +261,8 @@ public class PmdReport extends AbstractPmdReport {
private final ConfigurationService configurationService;
+ private final ToolchainManager toolchainManager;
+
/**
* Contains the result of the last PMD execution.
* It might be <code>null</code> which means, that PMD
@@ -259,11 +275,13 @@ public class PmdReport extends AbstractPmdReport {
ResourceManager locator,
ConfigurationService configurationService,
I18N i18n,
- PmdServiceExecutor serviceExecutor) {
+ PmdServiceExecutor serviceExecutor,
+ ToolchainManager toolchainManager) {
this.locator = locator;
this.configurationService = configurationService;
this.i18n = i18n;
this.serviceExecutor = serviceExecutor;
+ this.toolchainManager = toolchainManager;
}
/**
@@ -284,7 +302,7 @@ public class PmdReport extends AbstractPmdReport {
/**
* @param locale the locale
- * @param key the key to search for
+ * @param key the key to search for
* @return the text appropriate for the locale
*/
protected String getI18nString(Locale locale, String key) {
@@ -496,7 +514,7 @@ public class PmdReport extends AbstractPmdReport {
/**
* Convenience method to get the location of the specified file name.
*
- * @param name the name of the file whose location is to be resolved
+ * @param name the name of the file whose location is to be resolved
* @param position position in the list of rulesets (1-based)
* @return a String that contains the absolute file name of the file
*/
@@ -543,20 +561,123 @@ public class PmdReport extends AbstractPmdReport {
// Add the dependencies as last entries
classpath.addAll(dependencies);
+ classpath.removeIf(this::emptyOrNotExisting);
getLog().debug("Using aggregated aux classpath: " + classpath);
} else {
classpath.addAll(
includeTests ? project.getTestClasspathElements() : project.getCompileClasspathElements());
+ classpath.removeIf(this::emptyOrNotExisting);
getLog().debug("Using aux classpath: " + classpath);
}
+
+ classpath.addAll(determineJavaRuntimeClasses());
+
return String.join(File.pathSeparator, classpath);
} catch (Exception e) {
throw new MavenReportException(e.getMessage(), e);
}
}
+ private boolean emptyOrNotExisting(String entry) {
+ Path path = Paths.get(entry);
+ if (Files.isDirectory(path)) {
+ boolean emptyDirectory = false;
+ try (Stream<Path> stream = Files.list(path)) {
+ emptyDirectory = !stream.findAny().isPresent();
+ } catch (IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ if (emptyDirectory) {
+ getLog().debug("Ignoring empty directory for aux classpath: " + path);
+ return true;
+ }
+ } else if (!Files.exists(path)) {
+ getLog().debug("Ignoring non-existing entry for aux classpath: " + path);
+ return true;
+ }
+ return false;
+ }
+
+ private List<String> determineJavaRuntimeClasses() {
+ if (!"java".equals(language) && null != language) {
+ return Collections.emptyList();
+ }
+ if (targetJdk == null) {
+ return Collections.emptyList();
+ }
+
+ Toolchain toolchain = serviceExecutor.getToolchain(getJdkToolchain());
+ if (toolchain == null) {
+ List<Toolchain> toolchains =
+ toolchainManager.getToolchains(session, "jdk", Collections.singletonMap("version", targetJdk));
+ if (toolchains != null && !toolchains.isEmpty()) {
+ if (toolchains.size() > 1) {
+ getLog().warn("More than one toolchain found for jdk " + targetJdk + ": " + toolchains
+ + " - Using the first toolchain. Consider using <jdkToolchain> configuration parameter.");
+ }
+ toolchain = toolchains.get(0);
+ }
+ }
+ if (toolchain != null) {
+ String javaPath = toolchain.findTool("java");
+
+ if (javaPath != null) {
+ Path javaHome = Paths.get(javaPath).resolve("../..").normalize();
+ Path runtimeJar = findJavaRuntimeJar(javaHome);
+ if (runtimeJar != null) {
+ getLog().debug("Adding " + runtimeJar + " to aux classpath");
+ return Collections.singletonList(runtimeJar.toString());
+ }
+
+ getLog().warn("Couldn't find java runtime classes in " + javaHome
+ + ". Please make sure the java installation exists.");
+ } else {
+ getLog().warn("Could't find java executable for " + toolchain
+ + ". Please double check your toolchains.xml for outdated entries.");
+ }
+ }
+
+ // Always apply fallback (in case of no or invalid toolchain) to avoid an additional warning
+ // from PMD itself.
+ //
+ // The fallback is to use to current runtime - this might be the wrong java version for targetJdk,
+ // but preserves old (pre 3.29.0) behavior.
+ Path javaHome = Paths.get(System.getProperty("java.home"));
+ Path runtimeJar = findJavaRuntimeJar(javaHome);
+ if (runtimeJar != null) {
+ getLog().warn(
+ "Adding current java runtime classes from " + runtimeJar + " to aux classpath."
+ + " Please make sure to configure a toolchain for java version " + targetJdk
+ + " in your toolchains.xml. See also <https://maven.apache.org/plugins/maven-pmd-plugin/examples/targetJdk.html>.");
+ return Collections.singletonList(runtimeJar.toString());
+ }
+ return Collections.emptyList();
+ }
+
+ private static Path findJavaRuntimeJar(Path javaHome) {
+ // Java 11+
+ Path jrtFsJar = javaHome.resolve("lib/jrt-fs.jar");
+ if (Files.isRegularFile(jrtFsJar)) {
+ return jrtFsJar;
+ }
+
+ // Java 8 (javaHome is jre subdirectory)
+ Path rtJar = javaHome.resolve("lib/rt.jar"); // Java 8
+ if (Files.isRegularFile(rtJar)) {
+ return rtJar;
+ }
+
+ // Java 8 (javaHome is top-level directory)
+ Path rtJar2 = javaHome.resolve("jre/lib/rt.jar"); // Java 8
+ if (Files.isRegularFile(rtJar2)) {
+ return rtJar2;
+ }
+
+ return null;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/main/java/org/apache/maven/plugins/pmd/exec/ServiceExecutor.java b/src/main/java/org/apache/maven/plugins/pmd/exec/ServiceExecutor.java
index 3881f82..76e03e3 100644
--- a/src/main/java/org/apache/maven/plugins/pmd/exec/ServiceExecutor.java
+++ b/src/main/java/org/apache/maven/plugins/pmd/exec/ServiceExecutor.java
@@ -45,13 +45,16 @@ abstract class ServiceExecutor {
this.sessionProvider = sessionProvider;
}
- protected final Toolchain getToolchain(Map<String, String> jdkToolchain) {
+ public final Toolchain getToolchain(Map<String, String> jdkToolchain) {
Toolchain tc = null;
if (jdkToolchain != null) {
List<Toolchain> tcs = toolchainManager.getToolchains(sessionProvider.get(), "jdk", jdkToolchain);
if (tcs != null && !tcs.isEmpty()) {
tc = tcs.get(0);
+ if (tcs.size() > 1) {
+ LOG.warn("More than one toolchain found: {} - Using the first toolchain.", tcs);
+ }
}
}
diff --git a/src/site/markdown/examples/targetJdk.md.vm b/src/site/markdown/examples/targetJdk.md.vm
index 3b204db..07c8aa8 100644
--- a/src/site/markdown/examples/targetJdk.md.vm
+++ b/src/site/markdown/examples/targetJdk.md.vm
@@ -38,7 +38,7 @@ There is a configuration element in the PMD plugin that lets you set which [targ
<artifactId>maven-pmd-plugin</artifactId>
<version>${project.version}</version>
<configuration>
- <targetJdk>1.6</targetJdk>
+ <targetJdk>11</targetJdk>
</configuration>
</plugin>
</plugins>
@@ -47,9 +47,34 @@ There is a configuration element in the PMD plugin that lets you set which [targ
</project>
```
+Since version 3.29.0, the plugin automatically uses the value of `targetJdk` to resolve the Java platform classes via a matching toolchain. For [type resolution](../pmd-mojo.html#typeResolution), PMD reads the class files of both dependencies and the Java platform. If the wrong Java platform is selected, this might result in false positive and/or false negative violations. It is therefore important to have a toolchain configured with a version matching targetJdk.
+
+Example `${user.home}/.m2/toolchains.xml`:
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<toolchains>
+ <!-- JDK toolchains -->
+ <toolchain>
+ <type>jdk</type>
+ <provides>
+ <version>11</version>
+ <vendor>openjdk</vendor>
+ </provides>
+ <configuration>
+ <jdkHome>/path/to/jdk/11</jdkHome>
+ </configuration>
+ </toolchain>
+</toolchains>
+```
+
+See also [Guide to Using Toolchains](/guides/mini/guide-using-toolchains.html).
+
# Using Maven Toolchains
-Since version 3.14.0 of the PMD plugin, toolchains are supported. This helps if the build system is running a different JDK than being used for compiling. PMD reads the class files for [type resolution](../pmd-mojo.html#typeResolution) and this fails with ClassFormatErrors if the JDK version is incorrect.
+Since version 3.14.0 of the PMD plugin, toolchains are supported for executing PMD. This helps if the build system is running a different JDK than being used for compiling.
+
+Since version 3.29.0 of the PMD plugin, `targetJdk` is used to resolve the JDK for PMD, so that it doesn't matter anymore, which JDK version is used to run Maven and PMD.
To set this up, refer to the [Guide to Using Toolchains](/guides/mini/guide-using-toolchains.html), which makes use of the [Maven Toolchains Plugin](/plugins/maven-toolchains-plugin/).
diff --git a/src/site/markdown/faq.md b/src/site/markdown/faq.md
index fa60836..e576ff5 100644
--- a/src/site/markdown/faq.md
+++ b/src/site/markdown/faq.md
@@ -92,6 +92,16 @@ by matching the exact types of method parameters or variables.
However, this requires that the project is built first so that not only the project's dependencies can be used
for type resolution, but also the project's classes as well.
+It is sometimes also necessary that PMD is aware of the correct Java version, since the Java API itself
+changes between versions. For example, `java.util.concurrent.ExecutorService` has been made `AutoCloseable`
+since Java 19 - the rule [CloseResource](https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#closeresource)
+can only detect this correctly if PMD resolves types against that Java version or later.
+Since version 3.29.0, the plugin automatically adds the Java platform classes to the auxclasspath,
+so that they are also available for type resolution.. The property [`targetJdk`](pmd-mojo.html#targetJdk)
+determines the Java version to use, and a corresponding toolchain is selected. If none is available,
+a warning is issued and the current Java runtime is used instead, which may lead to false positive or
+false negative findings. See also [Target JDK and Toolchains](examples/targetJdk.html).
+
When using the property [aggregate](pmd-mojo.html#aggregate), this is problematic: With aggregate=true, PMD is
executed at the root of a multi-module project *before the individual modules are built*. Then the types of
the individual projects are not available, which might lead to false positive findings e.g. for the rule
diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm
index 5890e1b..28ef966 100644
--- a/src/site/markdown/index.md.vm
+++ b/src/site/markdown/index.md.vm
@@ -59,6 +59,10 @@ If you feel like the plugin is missing a feature or has a defect, you can file a
Upgrading Notes
---------------
+#[[### 3.29.0]]#
+
+- Starting with Maven PMD Plugin 3.29.0, the plugin automatically uses the value of `targetJdk` to resolve the Java platform classes via a matching toolchain. For [type resolution](pmd-mojo.html#typeResolution), PMD reads the class files of both dependencies and the Java platform. If the wrong Java platform is selected, this might result in false positive and/or false negative violations. It is therefore important to have a toolchain configured with a version matching targetJdk. See [Tar [...]
+
#[[### 3.22.0]]#
- Starting with Maven PMD Plugin 3.22.0, the plugin requires PMD version 7.0.0 or higher. PMD 7.0.0 switched to SLF4J and since Maven 3.1.0+ SLF4J is the default logging API. Logs from PMD are now always shown and cannot be disabled at runtime after maven has started. The property `showPmdLog` makes no sense anymore and is deprecated now. See [Maven Logging](https://maven.apache.org/maven-logging.html) for how to configure logging. For disabling PMD logs, you'd need to start maven with ` [...]