package hudson.plugins.violations; import static hudson.model.Result.SUCCESS; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.Result; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.plugins.violations.ViolationsReport.TypeReport; import hudson.plugins.violations.hudson.ViolationsFreestyleDescriptor; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import java.io.File; import java.io.IOException; import java.util.Collection; import com.google.common.annotations.VisibleForTesting; /** * Generats HTML and XML reports from checkstyle, pmd and findbugs report xml * files. * * @author Peter Reilly */ public class ViolationsPublisher extends Recorder { private static final String VIOLATIONS = "violations"; private final ViolationsConfig config = new ViolationsConfig(); /** * Get the configuration object for this violations publisher. * * @return the config. */ public ViolationsConfig getConfig() { return config; } /** * Get a copy of the configuration. This is used to configure a new * publisher in the config.jelly script. * * @return a copy o the config. */ public ViolationsConfig getOldConfig() { return config.clone(); } /** * Called by hudson at the end of a build. * * @param build * the build * @param launcher * the launcher * @param listener * for reporting errors * @return true always. * @throws InterruptedException * if user cancels the operation * @throws IOException * if problem parsing the xml files */ @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { FilePath htmlPath = new FilePath(new File(build.getProject().getRootDir(), VIOLATIONS)); FilePath targetPath = new FilePath(new File(build.getRootDir(), VIOLATIONS)); FilePath workspace = build.getWorkspace(); build.getActions().add(createBuildAction(workspace, targetPath, htmlPath, config, build, listener)); return true; } @VisibleForTesting static ViolationsBuildAction createBuildAction(FilePath workspace, FilePath targetPath, FilePath htmlPath, ViolationsConfig config, AbstractBuild<?, ?> build, BuildListener listener) throws IOException, InterruptedException { ViolationsReport report = workspace.act(new ViolationsCollector(false, targetPath, htmlPath, config)); report.setConfig(config); report.setBuild(build); report.setBuildResult(); handleRatcheting(report.getBuild().getResult(), report.getTypeReports().values(), listener, config); return new ViolationsBuildAction(build, report); } /** * Perform ratcheting if enabled, i.e. lower the thresholds if the build is * stable and the current value is lower than the current threshold. */ static void handleRatcheting(Result result, Collection<TypeReport> typeReports, BuildListener listener, ViolationsConfig config) { if (!shouldDoRatcheting(config, result)) { return; } // adjust the single configs (if needed) for (TypeReport typeReport : typeReports) { TypeConfig typeConfig = config.getTypeConfigs().get(typeReport.getType()); int thresholdCount = typeReport.getNumber() + 1; if (config.isAutoUpdateUnstable() && thresholdCount < typeConfig.getUnstable()) { listener.getLogger().println( "Setting unstable value for " + typeConfig.getType() + " to " + thresholdCount); typeConfig.setUnstable(thresholdCount); } if (config.isAutoUpdateMax() && thresholdCount < typeConfig.getMax()) { listener.getLogger().println( "Setting max/stormy value for " + typeConfig.getType() + " to " + thresholdCount); typeConfig.setMax(thresholdCount); // fix the min value but don't use fix() because it changes max // (and not min) if (typeConfig.getMin() >= typeConfig.getMax()) { typeConfig.setMin(typeConfig.getMax() - 1); } } } } static boolean shouldDoRatcheting(ViolationsConfig config, Result result) { if (!config.isAutoUpdateMax() && !config.isAutoUpdateUnstable()) { return FALSE; } if (result != SUCCESS) { return FALSE; } return TRUE; } /** * Create a project action for a project. * * @param project * the project to create the action for. * @return the created violations project action. */ @Override public Action getProjectAction(AbstractProject<?, ?> project) { return new ViolationsProjectAction(project); } @Override public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } /** * Get the descriptor. * * @return the violations publisher descriptor. */ @Override public BuildStepDescriptor<Publisher> getDescriptor() { return DESCRIPTOR; } /** The descriptor for this publisher - used in project config page. */ @Extension public static final BuildStepDescriptor<Publisher> DESCRIPTOR = new ViolationsFreestyleDescriptor(); }