package hudson.plugins.descriptionsetter; import hudson.Extension; import hudson.Launcher; import hudson.Util; import hudson.matrix.MatrixAggregatable; import hudson.matrix.MatrixAggregator; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixProject; import hudson.matrix.MatrixRun; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Result; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.ObjectStreamException; import java.net.MalformedURLException; import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; public class DescriptionSetterPublisher extends Recorder implements MatrixAggregatable { private final String regexp; private final String regexpForFailed; private final String description; private final String descriptionForFailed; private final boolean setForMatrix; @Deprecated private transient boolean setForFailed = false; @Deprecated private transient boolean explicitNotRegexp = false; @DataBoundConstructor public DescriptionSetterPublisher(String regexp, String regexpForFailed, String description, String descriptionForFailed, boolean setForMatrix) { this.regexp = regexp; this.regexpForFailed = regexpForFailed; this.description = Util.fixEmptyAndTrim(description); this.descriptionForFailed = Util.fixEmptyAndTrim(descriptionForFailed); this.setForMatrix = setForMatrix; } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException { try { Matcher matcher; String result = null; boolean useUnstable = (regexpForFailed != null || descriptionForFailed != null) && build.getResult().isWorseThan(Result.UNSTABLE); matcher = parseLog(build.getLogFile(), useUnstable ? regexpForFailed : regexp); if (matcher != null) { result = getExpandedDescription(matcher, useUnstable ? descriptionForFailed : description); result = build.getEnvironment(listener).expand(result); } else { if (useUnstable) { if (result == null && regexpForFailed == null && descriptionForFailed != null) { result = descriptionForFailed; } } else { if (result == null && regexp == null && description != null) { result = description; } } } if (result == null) { listener .getLogger() .println( "[description-setter] Could not determine description."); return true; } result = urlify(result); build.addAction(new DescriptionSetterAction(result)); listener.getLogger().println("Description set: " + result); build.setDescription(result); } catch (IOException e) { e.printStackTrace(listener .error("error while parsing logs for description-setter")); } return true; } private Matcher parseLog(File logFile, String regexp) throws IOException, InterruptedException { if (regexp == null) { return null; } // Assume default encoding and text files String line; Pattern pattern = Pattern.compile(regexp); BufferedReader reader = new BufferedReader(new FileReader(logFile)); while ((line = reader.readLine()) != null) { Matcher matcher = pattern.matcher(line); if (matcher.find()) { return matcher; } } return null; } private Object readResolve() throws ObjectStreamException { if (explicitNotRegexp) { return new DescriptionSetterPublisher(null, null, regexp, setForFailed ? regexpForFailed : null, false); } else { return this; } } private String getExpandedDescription(Matcher matcher, String description) { String result = description; if (result == null) { if (matcher.groupCount() == 0) { result = "\\0"; } else { result = "\\1"; } } // Expand all groups: 1..Count, as well as 0 for the entire pattern for (int i = matcher.groupCount(); i >= 0; i--) { result = result.replace("\\" + i, matcher.group(i)); } return result; } private String urlify(String text) { try { new URL(text); return String.format("<a href=\"%s\">%s</a>", text, text); } catch (MalformedURLException e) { return text; } } @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> { public DescriptorImpl() { super(DescriptionSetterPublisher.class); } @Override public String getDisplayName() { return "Set build description"; } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } @Override public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { return req.bindJSON(DescriptionSetterPublisher.class, formData); } public boolean isMatrixProject(AbstractProject project) { return project instanceof MatrixProject; } } @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl) super.getDescriptor(); } @Deprecated public boolean isExplicitNotRegexp() { return explicitNotRegexp; } public String getRegexp() { return regexp; } @Deprecated public boolean isSetForFailed() { return setForFailed; } public String getRegexpForFailed() { return regexpForFailed; } public String getDescription() { return description; } public String getDescriptionForFailed() { return descriptionForFailed; } public MatrixAggregator createAggregator(final MatrixBuild build, Launcher launcher, final BuildListener listener) { if (!isSetForMatrix()) { return null; } return new MatrixAggregator(build, launcher, listener) { @Override public boolean endRun(MatrixRun run) throws InterruptedException, IOException { if (build.getDescription() == null && run.getDescription() != null) { build.setDescription(run.getDescription()); } return true; } }; } public boolean isSetForMatrix() { return setForMatrix; } }