#1274 ant integration broken with pmd-5.2.0
Separate PMDTask and PMDTaskImpl and set the context classloader when executing PMD For CPD, the validation of the language is deferred, until the context class loader has been switched
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,6 @@ public class SourceLanguage {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "<language name=\"" + this.name + "\" version=\"" + this.version + "\" />";
|
||||
return "<sourceLanguage name=\"" + this.name + "\" version=\"" + this.version + "\" />";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,279 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
package net.sourceforge.pmd.ant.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.PMDConfiguration;
|
||||
import net.sourceforge.pmd.Report;
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.RulePriority;
|
||||
import net.sourceforge.pmd.RuleSet;
|
||||
import net.sourceforge.pmd.RuleSetFactory;
|
||||
import net.sourceforge.pmd.RuleSetNotFoundException;
|
||||
import net.sourceforge.pmd.RuleSets;
|
||||
import net.sourceforge.pmd.ant.Formatter;
|
||||
import net.sourceforge.pmd.ant.PMDTask;
|
||||
import net.sourceforge.pmd.ant.SourceLanguage;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.renderers.AbstractRenderer;
|
||||
import net.sourceforge.pmd.renderers.Renderer;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
import net.sourceforge.pmd.util.datasource.DataSource;
|
||||
import net.sourceforge.pmd.util.datasource.FileDataSource;
|
||||
import net.sourceforge.pmd.util.log.AntLogHandler;
|
||||
import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.tools.ant.AntClassLoader;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
import org.apache.tools.ant.types.Path;
|
||||
|
||||
public class PMDTaskImpl {
|
||||
|
||||
private Path classpath;
|
||||
private Path auxClasspath;
|
||||
private final List<Formatter> formatters = new ArrayList<Formatter>();
|
||||
private final List<FileSet> filesets = new ArrayList<FileSet>();
|
||||
private final PMDConfiguration configuration = new PMDConfiguration();
|
||||
private boolean failOnError;
|
||||
private boolean failOnRuleViolation;
|
||||
private int maxRuleViolations = 0;
|
||||
private String failuresPropertyName;
|
||||
private Project project;
|
||||
|
||||
public PMDTaskImpl(PMDTask task) {
|
||||
configuration.setReportShortNames(task.isShortFilenames());
|
||||
configuration.setSuppressMarker(task.getSuppressMarker());
|
||||
this.failOnError = task.isFailOnError();
|
||||
this.failOnRuleViolation = task.isFailOnRuleViolation();
|
||||
this.maxRuleViolations = task.getMaxRuleViolations();
|
||||
if (this.maxRuleViolations > 0) {
|
||||
this.failOnRuleViolation = true;
|
||||
}
|
||||
configuration.setRuleSets(task.getRulesetFiles());
|
||||
if (task.getEncoding() != null) {
|
||||
configuration.setSourceEncoding(task.getEncoding());
|
||||
}
|
||||
configuration.setThreads(task.getThreads());
|
||||
this.failuresPropertyName = task.getFailuresPropertyName();
|
||||
configuration.setMinimumPriority(RulePriority.valueOf(task.getMinimumPriority()));
|
||||
|
||||
SourceLanguage version = task.getSourceLanguage();
|
||||
if (version != null) {
|
||||
LanguageVersion languageVersion = LanguageRegistry.findLanguageVersionByTerseName(version.getName() + " "
|
||||
+ version.getVersion());
|
||||
if (languageVersion == null) {
|
||||
throw new BuildException("The following language is not supported:" + version + ".");
|
||||
}
|
||||
configuration.setDefaultLanguageVersion(languageVersion);
|
||||
}
|
||||
|
||||
classpath = task.getClasspath();
|
||||
auxClasspath = task.getAuxClasspath();
|
||||
|
||||
filesets.addAll(task.getFilesets());
|
||||
formatters.addAll(task.getFormatters());
|
||||
|
||||
project = task.getProject();
|
||||
}
|
||||
|
||||
private void doTask() {
|
||||
setupClassLoader();
|
||||
|
||||
// Setup RuleSetFactory and validate RuleSets
|
||||
RuleSetFactory ruleSetFactory = new RuleSetFactory();
|
||||
ruleSetFactory.setClassLoader(configuration.getClassLoader());
|
||||
try {
|
||||
// This is just used to validate and display rules. Each thread will
|
||||
// create its own ruleset
|
||||
ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
|
||||
ruleSetFactory.setWarnDeprecated(true);
|
||||
String ruleSets = configuration.getRuleSets();
|
||||
if (StringUtil.isNotEmpty(ruleSets)) {
|
||||
// Substitute env variables/properties
|
||||
configuration.setRuleSets(project.replaceProperties(ruleSets));
|
||||
}
|
||||
RuleSets rules = ruleSetFactory.createRuleSets(configuration.getRuleSets());
|
||||
ruleSetFactory.setWarnDeprecated(false);
|
||||
logRulesUsed(rules);
|
||||
} catch (RuleSetNotFoundException e) {
|
||||
throw new BuildException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (configuration.getSuppressMarker() != null) {
|
||||
project.log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE);
|
||||
}
|
||||
|
||||
// Start the Formatters
|
||||
for (Formatter formatter : formatters) {
|
||||
project.log("Sending a report to " + formatter, Project.MSG_VERBOSE);
|
||||
formatter.start(project.getBaseDir().toString());
|
||||
}
|
||||
|
||||
// log("Setting Language Version " + languageVersion.getShortName(),
|
||||
// Project.MSG_VERBOSE);
|
||||
|
||||
// TODO Do we really need all this in a loop over each FileSet? Seems
|
||||
// like a lot of redundancy
|
||||
RuleContext ctx = new RuleContext();
|
||||
Report errorReport = new Report();
|
||||
final AtomicInteger reportSize = new AtomicInteger();
|
||||
final String separator = System.getProperty("file.separator");
|
||||
|
||||
for (FileSet fs : filesets) {
|
||||
List<DataSource> files = new LinkedList<DataSource>();
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(project);
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
for (String srcFile : srcFiles) {
|
||||
File file = new File(ds.getBasedir() + separator + srcFile);
|
||||
files.add(new FileDataSource(file));
|
||||
}
|
||||
|
||||
final String inputPaths = ds.getBasedir().getPath();
|
||||
configuration.setInputPaths(inputPaths);
|
||||
|
||||
Renderer logRenderer = new AbstractRenderer("log", "Logging renderer") {
|
||||
public void start() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
public void startFileAnalysis(DataSource dataSource) {
|
||||
project.log("Processing file " + dataSource.getNiceFileName(false, inputPaths), Project.MSG_VERBOSE);
|
||||
}
|
||||
|
||||
public void renderFileReport(Report r) {
|
||||
int size = r.size();
|
||||
if (size > 0) {
|
||||
reportSize.addAndGet(size);
|
||||
}
|
||||
}
|
||||
|
||||
public void end() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
public String defaultFileExtension() {
|
||||
return null;
|
||||
} // not relevant
|
||||
};
|
||||
List<Renderer> renderers = new LinkedList<Renderer>();
|
||||
renderers.add(logRenderer);
|
||||
for (Formatter formatter : formatters) {
|
||||
renderers.add(formatter.getRenderer());
|
||||
}
|
||||
try {
|
||||
PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers);
|
||||
} catch (RuntimeException pmde) {
|
||||
handleError(ctx, errorReport, pmde);
|
||||
}
|
||||
}
|
||||
|
||||
int problemCount = reportSize.get();
|
||||
project.log(problemCount + " problems found", Project.MSG_VERBOSE);
|
||||
|
||||
for (Formatter formatter : formatters) {
|
||||
formatter.end(errorReport);
|
||||
}
|
||||
|
||||
if (failuresPropertyName != null && problemCount > 0) {
|
||||
project.setProperty(failuresPropertyName, String.valueOf(problemCount));
|
||||
project.log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE);
|
||||
}
|
||||
|
||||
if (failOnRuleViolation && problemCount > maxRuleViolations) {
|
||||
throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(RuleContext ctx, Report errorReport, RuntimeException pmde) {
|
||||
|
||||
pmde.printStackTrace();
|
||||
project.log(pmde.toString(), Project.MSG_VERBOSE);
|
||||
|
||||
Throwable cause = pmde.getCause();
|
||||
|
||||
if (cause != null) {
|
||||
StringWriter strWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(strWriter);
|
||||
cause.printStackTrace(printWriter);
|
||||
project.log(strWriter.toString(), Project.MSG_VERBOSE);
|
||||
IOUtils.closeQuietly(printWriter);
|
||||
|
||||
if (StringUtil.isNotEmpty(cause.getMessage())) {
|
||||
project.log(cause.getMessage(), Project.MSG_VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
if (failOnError) {
|
||||
throw new BuildException(pmde);
|
||||
}
|
||||
errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename()));
|
||||
}
|
||||
|
||||
private void setupClassLoader() {
|
||||
|
||||
if (classpath == null) {
|
||||
project.log("Using the normal ClassLoader", Project.MSG_VERBOSE);
|
||||
} else {
|
||||
project.log("Using the AntClassLoader", Project.MSG_VERBOSE);
|
||||
// must be true, otherwise you'll get ClassCastExceptions as classes
|
||||
// are loaded twice
|
||||
// and exist in multiple class loaders
|
||||
boolean parentFirst = true;
|
||||
configuration.setClassLoader(new AntClassLoader(Thread.currentThread().getContextClassLoader(), project,
|
||||
classpath, parentFirst));
|
||||
}
|
||||
try {
|
||||
/*
|
||||
* 'basedir' is added to the path to make sure that relative paths
|
||||
* such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
|
||||
* work when ant is invoked from a different directory using "-f"
|
||||
*/
|
||||
configuration.prependClasspath(project.getBaseDir().toString());
|
||||
if (auxClasspath != null) {
|
||||
project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
|
||||
configuration.prependClasspath(auxClasspath.toString());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException(ioe.getMessage(), ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public void execute() throws BuildException {
|
||||
final Handler antLogHandler = new AntLogHandler(project);
|
||||
final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST, antLogHandler);
|
||||
try {
|
||||
doTask();
|
||||
} finally {
|
||||
logManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void logRulesUsed(RuleSets rules) {
|
||||
project.log("Using these rulesets: " + configuration.getRuleSets(), Project.MSG_VERBOSE);
|
||||
|
||||
RuleSet[] ruleSets = rules.getAllRuleSets();
|
||||
for (RuleSet ruleSet : ruleSets) {
|
||||
for (Rule rule : ruleSet.getRules()) {
|
||||
project.log("Using rule " + rule.getName(), Project.MSG_VERBOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ package net.sourceforge.pmd.cpd;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -53,6 +54,9 @@ public class CPDTask extends Task {
|
||||
private List<FileSet> filesets = new ArrayList<FileSet>();
|
||||
|
||||
public void execute() throws BuildException {
|
||||
ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(CPDTask.class.getClassLoader());
|
||||
|
||||
try {
|
||||
validateFields();
|
||||
|
||||
@ -82,6 +86,8 @@ public class CPDTask extends Task {
|
||||
re.printStackTrace();
|
||||
log(re.toString(), Project.MSG_ERR);
|
||||
throw new BuildException("ReportException during task execution", re);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(oldClassloader);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,9 +152,16 @@ public class CPDTask extends Task {
|
||||
private void validateFields() throws BuildException {
|
||||
if (minimumTokenCount == 0) {
|
||||
throw new BuildException("minimumTokenCount is required and must be greater than zero");
|
||||
} else if (filesets.isEmpty()) {
|
||||
}
|
||||
|
||||
if (filesets.isEmpty()) {
|
||||
throw new BuildException("Must include at least one FileSet");
|
||||
}
|
||||
|
||||
if (!Arrays.asList(LanguageFactory.supportedLanguages).contains(language)) {
|
||||
throw new BuildException("Language " + language + " is not supported. Available languages: "
|
||||
+ Arrays.toString(LanguageFactory.supportedLanguages));
|
||||
}
|
||||
}
|
||||
|
||||
public void addFileset(FileSet set) {
|
||||
@ -184,15 +197,15 @@ public class CPDTask extends Task {
|
||||
}
|
||||
|
||||
public void setFormat(FormatAttribute formatAttribute) {
|
||||
format = formatAttribute.getValue();
|
||||
this.format = formatAttribute.getValue();
|
||||
}
|
||||
|
||||
public void setLanguage(LanguageAttribute languageAttribute) {
|
||||
language = languageAttribute.getValue();
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public void setEncoding(String encodingValue) {
|
||||
encoding = encodingValue;
|
||||
public void setEncoding(String encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public static class FormatAttribute extends EnumeratedAttribute {
|
||||
@ -201,10 +214,4 @@ public class CPDTask extends Task {
|
||||
return FORMATS;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LanguageAttribute extends EnumeratedAttribute {
|
||||
public String[] getValues() {
|
||||
return LanguageFactory.supportedLanguages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,7 @@ import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
/**
|
||||
* AntLogHandler sends log messages to an Ant Task, so the regular Ant logging
|
||||
@ -21,12 +19,12 @@ import org.apache.tools.ant.Task;
|
||||
* @author Wouter Zelle
|
||||
*/
|
||||
public class AntLogHandler extends Handler {
|
||||
private Task antTask;
|
||||
private Project project;
|
||||
|
||||
private static final Formatter FORMATTER = new PmdLogFormatter();
|
||||
|
||||
public AntLogHandler(Task antTask) {
|
||||
this.antTask = antTask;
|
||||
public AntLogHandler(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public void publish(LogRecord logRecord) {
|
||||
@ -47,12 +45,12 @@ public class AntLogHandler extends Handler {
|
||||
throw new IllegalStateException("Unknown logging level"); //shouldn't get ALL or NONE
|
||||
}
|
||||
|
||||
antTask.log(FORMATTER.format(logRecord), antLevel);
|
||||
project.log(FORMATTER.format(logRecord), antLevel);
|
||||
if (logRecord.getThrown() != null) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(stringWriter, true);
|
||||
logRecord.getThrown().printStackTrace(printWriter);
|
||||
antTask.log(stringWriter.toString(), antLevel);
|
||||
project.log(stringWriter.toString(), antLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,6 @@ public class PMDTaskTest extends BuildFileTest {
|
||||
|
||||
@Test
|
||||
public void testInvalidLanguageVersion() {
|
||||
expectBuildExceptionContaining("testInvalidLanguageVersion", "Fail requested.", "The following language is not supported:<language name=\"java\" version=\"42\" />.");
|
||||
expectBuildExceptionContaining("testInvalidLanguageVersion", "Fail requested.", "The following language is not supported:<sourceLanguage name=\"java\" version=\"42\" />.");
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<target name="all" depends="testBasic"/>
|
||||
|
||||
<target name="testBasic">
|
||||
<cpd minimumTokenCount="20" outputFile="${pmd.home}/target/cpd.ant.tests">
|
||||
<cpd minimumTokenCount="20" outputFile="${pmd.home}/target/cpd.ant.tests" language="cpddummy">
|
||||
<fileset dir="${pmd.home}/src/main/">
|
||||
<include name="**/*.java"/>
|
||||
</fileset>
|
||||
|
@ -17,3 +17,4 @@
|
||||
* [#1263](https://sourceforge.net/p/pmd/bugs/1263/): PMD reports CheckResultSet violation in completely unrelated source files.
|
||||
* [#1272](https://sourceforge.net/p/pmd/bugs/1272/): varargs in methods are causing IndexOutOfBoundException when trying to process files
|
||||
* [#1273](https://sourceforge.net/p/pmd/bugs/1273/): CheckResultSet false positive in try-with-resources nested in if
|
||||
* [#1274](https://sourceforge.net/p/pmd/bugs/1274/): ant integration broken with pmd-5.2.0
|
||||
|
Reference in New Issue
Block a user