| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.maven.plugins.pmd; |
| |
| 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 net.sourceforge.pmd.PMDVersion; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.model.ReportPlugin; |
| import org.apache.maven.model.Reporting; |
| 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; |
| import org.codehaus.plexus.util.PathTool; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * Base class for the PMD reports. |
| * |
| * @author <a href="mailto:brett@apache.org">Brett Porter</a> |
| * @version $Id$ |
| */ |
| public abstract class AbstractPmdReport extends AbstractMavenReport { |
| // ---------------------------------------------------------------------- |
| // Configurables |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * The output directory for the intermediate XML report. |
| */ |
| @Parameter(property = "project.build.directory", required = true) |
| protected File targetDirectory; |
| |
| /** |
| * 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:aggregator-check, pmd:cpd-check, pmd:aggregator-cpd-check). |
| */ |
| @Parameter(property = "format", defaultValue = "xml") |
| protected String format = "xml"; |
| |
| /** |
| * Link the violation line numbers to the source xref. Links will be created automatically if the jxr plugin is |
| * being used. |
| */ |
| @Parameter(property = "linkXRef", defaultValue = "true") |
| private boolean linkXRef; |
| |
| /** |
| * Location of the Xrefs to link to. |
| */ |
| @Parameter(defaultValue = "${project.reporting.outputDirectory}/xref") |
| private File xrefLocation; |
| |
| /** |
| * Location of the Test Xrefs to link to. |
| */ |
| @Parameter(defaultValue = "${project.reporting.outputDirectory}/xref-test") |
| private File xrefTestLocation; |
| |
| /** |
| * A list of files to exclude from checking. Can contain Ant-style wildcards and double wildcards. Note that these |
| * exclusion patterns only operate on the path of a source file relative to its source root directory. In other |
| * words, files are excluded based on their package and/or class name. If you want to exclude entire source root |
| * directories, use the parameter <code>excludeRoots</code> instead. |
| * |
| * @since 2.2 |
| */ |
| @Parameter |
| private List<String> excludes; |
| |
| /** |
| * A list of files to include from checking. Can contain Ant-style wildcards and double wildcards. Defaults to |
| * **\/*.java. |
| * |
| * @since 2.2 |
| */ |
| @Parameter |
| private List<String> includes; |
| |
| /** |
| * Specifies the location of the source directories to be used for PMD. |
| * Defaults to <code>project.compileSourceRoots</code>. |
| * @since 3.7 |
| */ |
| @Parameter(defaultValue = "${project.compileSourceRoots}") |
| private List<String> compileSourceRoots; |
| |
| /** |
| * The directories containing the test-sources to be used for PMD. |
| * Defaults to <code>project.testCompileSourceRoots</code> |
| * @since 3.7 |
| */ |
| @Parameter(defaultValue = "${project.testCompileSourceRoots}") |
| private List<String> testSourceRoots; |
| |
| /** |
| * The project source directories that should be excluded. |
| * |
| * @since 2.2 |
| */ |
| @Parameter |
| private File[] excludeRoots; |
| |
| /** |
| * Run PMD on the tests. |
| * |
| * @since 2.2 |
| */ |
| @Parameter(defaultValue = "false") |
| protected boolean includeTests; |
| |
| /** |
| * 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; |
| |
| /** |
| * Whether to include the xml files generated by PMD/CPD in the site. |
| * |
| * @since 3.0 |
| */ |
| @Parameter(defaultValue = "false") |
| protected boolean includeXmlInSite; |
| |
| /** |
| * Skip the PMD/CPD report generation if there are no violations or duplications found. Defaults to |
| * <code>false</code>. |
| * |
| * <p>Note: the default value was changed from <code>true</code> to <code>false</code> with version 3.13.0. |
| * |
| * @since 3.1 |
| */ |
| @Parameter(defaultValue = "false") |
| protected boolean skipEmptyReport; |
| |
| /** |
| * File that lists classes and rules to be excluded from failures. |
| * For PMD, this is a properties file. For CPD, this |
| * is a text file that contains comma-separated lists of classes |
| * that are allowed to duplicate. |
| * |
| * @since 3.7 |
| */ |
| @Parameter(property = "pmd.excludeFromFailureFile", defaultValue = "") |
| protected String excludeFromFailureFile; |
| |
| /** |
| * Redirect PMD log into maven log out. |
| * When enabled, the PMD log output is redirected to maven, so that |
| * it is visible in the console together with all the other log output. |
| * Also, if maven is started with the debug flag (<code>-X</code> or <code>--debug</code>), |
| * the PMD logger is also configured for debug. |
| * |
| * @since 3.9.0 |
| */ |
| @Parameter(defaultValue = "true", property = "pmd.showPmdLog") |
| protected boolean showPmdLog = true; |
| |
| /** |
| * <p> |
| * Allow for configuration of the jvm used to run PMD via maven toolchains. |
| * This permits a configuration where the project is built with one jvm and PMD is executed with another. |
| * This overrules the toolchain selected by the maven-toolchain-plugin. |
| * </p> |
| * |
| * <p>Examples:</p> |
| * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html"> |
| * Guide to Toolchains</a> for more info) |
| * |
| * <pre> |
| * {@code |
| * <configuration> |
| * ... |
| * <jdkToolchain> |
| * <version>1.11</version> |
| * </jdkToolchain> |
| * </configuration> |
| * |
| * <configuration> |
| * ... |
| * <jdkToolchain> |
| * <version>1.8</version> |
| * <vendor>zulu</vendor> |
| * </jdkToolchain> |
| * </configuration> |
| * } |
| * </pre> |
| * |
| * <strong>note:</strong> requires at least Maven 3.3.1 |
| * |
| * @since 3.14.0 |
| */ |
| @Parameter |
| private Map<String, String> jdkToolchain; |
| |
| // ---------------------------------------------------------------------- |
| // Read-only parameters |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * The projects in the reactor for aggregation report. |
| */ |
| @Parameter(property = "reactorProjects", readonly = true) |
| protected List<MavenProject> reactorProjects; |
| |
| /** |
| * The current build session instance. This is used for |
| * toolchain manager API calls and for dependency resolver API calls. |
| */ |
| @Parameter(defaultValue = "${session}", required = true, readonly = true) |
| protected MavenSession session; |
| |
| @Component |
| private ToolchainManager toolchainManager; |
| |
| /** The files that are being analyzed. */ |
| protected Map<File, PmdFileInfo> filesToProcess; |
| |
| @Override |
| protected MavenProject getProject() { |
| return project; |
| } |
| |
| protected String constructXRefLocation(boolean test) { |
| String location = null; |
| if (linkXRef) { |
| File xrefLoc = test ? xrefTestLocation : xrefLocation; |
| |
| String relativePath = |
| PathTool.getRelativePath(outputDirectory.getAbsolutePath(), xrefLoc.getAbsolutePath()); |
| if (relativePath == null || relativePath.isEmpty()) { |
| relativePath = "."; |
| } |
| relativePath = relativePath + "/" + xrefLoc.getName(); |
| if (xrefLoc.exists()) { |
| // XRef was already generated by manual execution of a lifecycle binding |
| location = relativePath; |
| } else { |
| // Not yet generated - check if the report is on its way |
| Reporting reporting = project.getModel().getReporting(); |
| List<ReportPlugin> reportPlugins = |
| reporting != null ? reporting.getPlugins() : Collections.<ReportPlugin>emptyList(); |
| for (ReportPlugin plugin : reportPlugins) { |
| String artifactId = plugin.getArtifactId(); |
| if ("maven-jxr-plugin".equals(artifactId) || "jxr-maven-plugin".equals(artifactId)) { |
| location = relativePath; |
| } |
| } |
| } |
| |
| if (location == null) { |
| getLog().warn("Unable to locate Source XRef to link to - DISABLED"); |
| } |
| } |
| return location; |
| } |
| |
| /** |
| * Convenience method to get the list of files where the PMD tool will be executed |
| * |
| * @return a List of the files where the PMD tool will be executed |
| * @throws IOException If an I/O error occurs during construction of the |
| * canonical pathnames of the files |
| */ |
| protected Map<File, PmdFileInfo> getFilesToProcess() throws IOException { |
| if (aggregate && !project.isExecutionRoot()) { |
| return Collections.emptyMap(); |
| } |
| |
| if (excludeRoots == null) { |
| excludeRoots = new File[0]; |
| } |
| |
| Collection<File> excludeRootFiles = new HashSet<>(excludeRoots.length); |
| |
| for (File file : excludeRoots) { |
| if (file.isDirectory()) { |
| excludeRootFiles.add(file); |
| } |
| } |
| |
| List<PmdFileInfo> directories = new ArrayList<>(); |
| |
| if (null == compileSourceRoots) { |
| compileSourceRoots = project.getCompileSourceRoots(); |
| } |
| if (compileSourceRoots != null) { |
| for (String root : compileSourceRoots) { |
| File sroot = new File(root); |
| if (sroot.exists()) { |
| String sourceXref = constructXRefLocation(false); |
| directories.add(new PmdFileInfo(project, sroot, sourceXref)); |
| } |
| } |
| } |
| |
| if (null == testSourceRoots) { |
| testSourceRoots = project.getTestCompileSourceRoots(); |
| } |
| if (includeTests && testSourceRoots != null) { |
| for (String root : testSourceRoots) { |
| File sroot = new File(root); |
| if (sroot.exists()) { |
| String testXref = constructXRefLocation(true); |
| directories.add(new PmdFileInfo(project, sroot, testXref)); |
| } |
| } |
| } |
| if (isAggregator()) { |
| for (MavenProject localProject : getAggregatedProjects()) { |
| List<String> localCompileSourceRoots = localProject.getCompileSourceRoots(); |
| for (String root : localCompileSourceRoots) { |
| File sroot = new File(root); |
| if (sroot.exists()) { |
| String sourceXref = constructXRefLocation(false); |
| directories.add(new PmdFileInfo(localProject, sroot, sourceXref)); |
| } |
| } |
| if (includeTests) { |
| List<String> localTestCompileSourceRoots = localProject.getTestCompileSourceRoots(); |
| for (String root : localTestCompileSourceRoots) { |
| File sroot = new File(root); |
| if (sroot.exists()) { |
| String testXref = constructXRefLocation(true); |
| directories.add(new PmdFileInfo(localProject, sroot, testXref)); |
| } |
| } |
| } |
| } |
| } |
| |
| String excluding = getExcludes(); |
| getLog().debug("Exclusions: " + excluding); |
| String including = getIncludes(); |
| getLog().debug("Inclusions: " + including); |
| |
| Map<File, PmdFileInfo> files = new TreeMap<>(); |
| |
| for (PmdFileInfo finfo : directories) { |
| getLog().debug("Searching for files in directory " |
| + finfo.getSourceDirectory().toString()); |
| File sourceDirectory = finfo.getSourceDirectory(); |
| if (sourceDirectory.isDirectory() && !isDirectoryExcluded(excludeRootFiles, sourceDirectory)) { |
| List<File> newfiles = FileUtils.getFiles(sourceDirectory, including, excluding); |
| for (File newfile : newfiles) { |
| files.put(newfile.getCanonicalFile(), finfo); |
| } |
| } |
| } |
| |
| return files; |
| } |
| |
| private boolean isDirectoryExcluded(Collection<File> excludeRootFiles, File sourceDirectoryToCheck) { |
| boolean returnVal = false; |
| for (File excludeDir : excludeRootFiles) { |
| try { |
| if (sourceDirectoryToCheck |
| .getCanonicalFile() |
| .toPath() |
| .startsWith(excludeDir.getCanonicalFile().toPath())) { |
| getLog().debug("Directory " + sourceDirectoryToCheck.getAbsolutePath() |
| + " has been excluded as it matches excludeRoot " |
| + excludeDir.getAbsolutePath()); |
| returnVal = true; |
| break; |
| } |
| } catch (IOException e) { |
| getLog().warn("Error while checking " + sourceDirectoryToCheck + " whether it should be excluded.", e); |
| } |
| } |
| return returnVal; |
| } |
| |
| /** |
| * Gets the comma separated list of effective include patterns. |
| * |
| * @return The comma separated list of effective include patterns, never <code>null</code>. |
| */ |
| private String getIncludes() { |
| Collection<String> patterns = new LinkedHashSet<>(); |
| if (includes != null) { |
| patterns.addAll(includes); |
| } |
| if (patterns.isEmpty()) { |
| patterns.add("**/*.java"); |
| } |
| return StringUtils.join(patterns.iterator(), ","); |
| } |
| |
| /** |
| * Gets the comma separated list of effective exclude patterns. |
| * |
| * @return The comma separated list of effective exclude patterns, never <code>null</code>. |
| */ |
| private String getExcludes() { |
| Collection<String> patterns = new LinkedHashSet<>(FileUtils.getDefaultExcludesAsList()); |
| if (excludes != null) { |
| patterns.addAll(excludes); |
| } |
| return StringUtils.join(patterns.iterator(), ","); |
| } |
| |
| protected boolean isXml() { |
| return "xml".equals(format); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean canGenerateReport() { |
| if (aggregate && !project.isExecutionRoot()) { |
| return false; |
| } |
| |
| if (!isAggregator() && "pom".equalsIgnoreCase(project.getPackaging())) { |
| return false; |
| } |
| |
| // if format is XML, we need to output it even if the file list is empty |
| // so the "check" goals can check for failures |
| if (isXml()) { |
| return true; |
| } |
| try { |
| filesToProcess = getFilesToProcess(); |
| if (filesToProcess.isEmpty()) { |
| return false; |
| } |
| } catch (IOException e) { |
| getLog().error(e); |
| } |
| return true; |
| } |
| |
| protected String determineCurrentRootLogLevel() { |
| String logLevel = System.getProperty("org.slf4j.simpleLogger.defaultLogLevel"); |
| if (logLevel == null) { |
| logLevel = System.getProperty("maven.logging.root.level"); |
| } |
| if (logLevel == null) { |
| // TODO: logback level |
| logLevel = "info"; |
| } |
| return logLevel; |
| } |
| |
| static String getPmdVersion() { |
| return PMDVersion.VERSION; |
| } |
| |
| // TODO remove the part with ToolchainManager lookup once we depend on |
| // 3.0.9 (have it as prerequisite). Define as regular component field then. |
| protected final Toolchain getToolchain() { |
| Toolchain tc = null; |
| |
| if (jdkToolchain != null) { |
| // Maven 3.3.1 has plugin execution scoped Toolchain Support |
| try { |
| Method getToolchainsMethod = toolchainManager |
| .getClass() |
| .getMethod("getToolchains", MavenSession.class, String.class, Map.class); |
| |
| @SuppressWarnings("unchecked") |
| List<Toolchain> tcs = |
| (List<Toolchain>) getToolchainsMethod.invoke(toolchainManager, session, "jdk", jdkToolchain); |
| |
| if (tcs != null && !tcs.isEmpty()) { |
| tc = tcs.get(0); |
| } |
| } catch (NoSuchMethodException |
| | SecurityException |
| | IllegalAccessException |
| | IllegalArgumentException |
| | InvocationTargetException e) { |
| // ignore |
| } |
| } |
| |
| if (tc == null) { |
| tc = toolchainManager.getToolchainFromBuildContext("jdk", session); |
| } |
| |
| return tc; |
| } |
| |
| 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; |
| } |
| } |