Skip to content

Commit

Permalink
[FIXED JENKINS-23097] Set description during build as a build step.
Browse files Browse the repository at this point in the history
This change allows the description of a build to be set as a part of
building. Setting the description early is useful if you have time
consuming builds.

Common description setter functionality is extracted to a helper
class in order to avoid code copy when implementing the new builder.
The common helper class also properly closes a reader after parsing a
log file.

The test builder is moved to a separate class in order to avoid code
copy when implementing more test classes. The test builder is now
compatible with jenkins-core 1.414 or later by defining a Descriptor,
mandatory since JENKINS-9687.

The parent version is bumped to 1.480.3 in order to compile against
Java 7.
  • Loading branch information
Jonas Kåveby committed May 19, 2014
1 parent 823db6b commit 43a98d9
Show file tree
Hide file tree
Showing 15 changed files with 403 additions and 134 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.398</version>
<version>1.480.3</version>
</parent>

<artifactId>description-setter</artifactId>
Expand Down
@@ -0,0 +1,79 @@
package hudson.plugins.descriptionsetter;

import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

/**
* The DescriptionSetterBuilder allows the description of a build to be set as a
* part of building. Setting the description early is useful if you have time
* consuming builds.
*
*/
public class DescriptionSetterBuilder extends Builder {

private final String regexp;
private final String description;

@DataBoundConstructor
public DescriptionSetterBuilder(String regexp, String description) {
this.regexp = regexp;
this.description = Util.fixEmptyAndTrim(description);
}

@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) throws InterruptedException {

return DescriptionSetterHelper.setDescription(build, listener, regexp,
description);
}

@Extension
public static final class DescriptorImpl extends
BuildStepDescriptor<Builder> {

public DescriptorImpl() {
super(DescriptionSetterBuilder.class);
}

@Override
public String getDisplayName() {
return Messages.DescriptionSetter_DisplayName();
}

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}

@Override
public Builder newInstance(StaplerRequest req, JSONObject formData)
throws FormException {
return req.bindJSON(DescriptionSetterBuilder.class, formData);
}
}

@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}

public String getRegexp() {
return regexp;
}

public String getDescription() {
return description;
}

}
@@ -0,0 +1,128 @@
package hudson.plugins.descriptionsetter;

import hudson.model.BuildListener;
import hudson.model.AbstractBuild;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Helper class that performs common functionality used by both
* DescriptionSetterBuilder and DescriptionSetterPublisher.
*
*/
public class DescriptionSetterHelper {

private static final String LOG_PREFIX = "[description-setter]";

/**
* Sets the description on the given build based on the specified regular
* expression and description.
*
* @param build the build whose description to set.
* @param listener the build listener to report events to.
* @param regexp the regular expression to apply to the build log.
* @param description the description to set.
* @return true, regardless of if the regular expression matched and a
* description could be set or not.
* @throws InterruptedException if the build is interrupted by the user.
*/
public static boolean setDescription(AbstractBuild<?, ?> build,
BuildListener listener, String regexp, String description)
throws InterruptedException {

try {
Matcher matcher;
String result = null;

matcher = parseLog(build.getLogFile(), regexp);
if (matcher != null) {
result = getExpandedDescription(matcher, description);
result = build.getEnvironment(listener).expand(result);
} else {
if (result == null && regexp == null && description != null) {
result = description;
}
}

if (result == null) {
listener.getLogger().println(
LOG_PREFIX + " Could not determine description.");
return true;
}

result = urlify(result);

build.addAction(new DescriptionSetterAction(result));
build.setDescription(result);
listener.getLogger().println(LOG_PREFIX + " Description set: " + result);
} catch (IOException e) {
e.printStackTrace(listener.error(LOG_PREFIX
+ " Error while parsing logs for description-setter"));
}

return true;
}

private static 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 = null;
try {
reader = new BufferedReader(new FileReader(logFile));
while ((line = reader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
return matcher;
}
}
} finally {
if (reader != null) {
reader.close();
}
}
return null;
}

private static 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) == null ? "" : matcher.group(i));
}
return result;
}

private static String urlify(String text) {
try {
new URL(text);
return String.format("<a href=\"%s\">%s</a>", text, text);
} catch (MalformedURLException e) {
return text;
}
}
}
Expand Up @@ -5,33 +5,31 @@
import hudson.Util;
import hudson.matrix.MatrixAggregatable;
import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixRun;
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.model.AbstractBuild;
import hudson.model.AbstractProject;
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;

/**
* The DescriptionSetterPublisher allows the description of a build to be set as
* a post-build action, after the build has completed.
*
*/
public class DescriptionSetterPublisher extends Recorder implements
MatrixAggregatable {

Expand Down Expand Up @@ -67,71 +65,11 @@ public BuildStepMonitor getRequiredMonitorService() {
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;
boolean useUnstable = (regexpForFailed != null || descriptionForFailed != null)
&& build.getResult().isWorseThan(Result.UNSTABLE);
return DescriptionSetterHelper.setDescription(build, listener,
useUnstable ? regexpForFailed : regexp,
useUnstable ? descriptionForFailed : description);
}

private Object readResolve() throws ObjectStreamException {
Expand All @@ -143,33 +81,6 @@ private Object readResolve() throws ObjectStreamException {
}
}

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) == null ? "" : 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> {
Expand Down
@@ -0,0 +1,9 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Regular expression}" field="regexp">
<f:textbox />
</f:entry>
<f:entry title="${%Description}" field="description">
<f:textbox />
</f:entry>
</j:jelly>
@@ -0,0 +1,2 @@
Regular\ expression=\u6b63\u898f\u8868\u73fe
Description=\u8aac\u660e
@@ -0,0 +1,7 @@
<div>
The description to set on the build.
<ul>
<li>If a regular expression is configured, every instance of \n will be replaced with the n-th group of the regular expression match.</li>
<li>If the description is empty, the first group selected by the regular expression will be used as description.</li>
<li>If no regular expression is configured, the description is taken verbatim.</li>
</div>

0 comments on commit 43a98d9

Please sign in to comment.