package hudson.model; import com.thoughtworks.xstream.XStream; import hudson.CloseProofOutputStream; import hudson.EnvVars; import hudson.ExtensionPoint; import hudson.FeedAdapter; import hudson.FilePath; import hudson.Util; import static hudson.Util.combine; import hudson.XmlFile; import hudson.security.Permission; import hudson.security.PermissionGroup; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixRun; import hudson.model.listeners.RunListener; import hudson.search.SearchIndexBuilder; import hudson.tasks.BuildStep; import hudson.tasks.LogRotator; import hudson.tasks.test.AbstractTestResultAction; import hudson.util.IOException2; import hudson.util.XStream2; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Writer; import java.text.ParseException; import java.text.SimpleDateFormat; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; /** * A particular execution of {@link Job}. * *
* Custom {@link Run} type is always used in conjunction with
* a custom {@link Job} type, so there's no separate registration
* mechanism for custom {@link Run} types.
*
* @author Kohsuke Kawaguchi
* @see RunListener
*/
@ExportedBean
public abstract class Run
* In earlier versions < 1.24, this number is not unique nor continuous,
* but going forward, it will, and this really replaces the build id.
*/
public /*final*/ int number;
/**
* Previous build. Can be null.
* These two fields are maintained and updated by {@link RunMap}.
*/
protected volatile transient RunT previousBuild;
/**
* Next build. Can be null.
*/
protected volatile transient RunT nextBuild;
/**
* When the build is scheduled.
*/
protected transient final Calendar timestamp;
/**
* The build result.
* This value may change while the state is in {@link State#BUILDING}.
*/
protected volatile Result result;
/**
* Human-readable description. Can be null.
*/
protected volatile String description;
/**
* The current build state.
*/
protected volatile transient State state;
private static enum State {
/**
* Build is created/queued but we haven't started building it.
*/
NOT_STARTED,
/**
* Build is in progress.
*/
BUILDING,
/**
* Build is completed now, and the status is determined,
* but log files are still being updated.
*/
POST_PRODUCTION,
/**
* Build is completed now, and log file is closed.
*/
COMPLETED
}
/**
* Number of milli-seconds it took to run this build.
*/
protected long duration;
/**
* Keeps this log entries.
*/
private boolean keepLog;
protected static final SimpleDateFormat ID_FORMATTER = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
/**
* Creates a new {@link Run}.
*/
protected Run(JobT job) throws IOException {
this(job, new GregorianCalendar());
this.number = project.assignBuildNumber();
}
/**
* Constructor for creating a {@link Run} object in
* an arbitrary state.
*/
protected Run(JobT job, Calendar timestamp) {
this.project = job;
this.timestamp = timestamp;
this.state = State.NOT_STARTED;
}
/**
* Loads a run from a log file.
*/
protected Run(JobT project, File buildDir) throws IOException {
this(project, new GregorianCalendar());
try {
this.timestamp.setTime(ID_FORMATTER.parse(buildDir.getName()));
} catch (ParseException e) {
throw new IOException2("Invalid directory name "+buildDir,e);
} catch (NumberFormatException e) {
throw new IOException2("Invalid directory name "+buildDir,e);
}
this.state = State.COMPLETED;
this.result = Result.FAILURE; // defensive measure. value should be overwritten by unmarshal, but just in case the saved data is inconsistent
getDataFile().unmarshal(this); // load the rest of the data
}
/**
* Ordering based on build numbers.
*/
public int compareTo(RunT that) {
return this.number - that.number;
}
/**
* Returns the build result.
*
*
* When a build is {@link #isBuilding() in progress}, this method
* may return null or a temporary intermediate result.
*/
@Exported
public Result getResult() {
return result;
}
public void setResult(Result r) {
// state can change only when we are building
assert state==State.BUILDING;
StackTraceElement caller = findCaller(Thread.currentThread().getStackTrace(),"setResult");
// result can only get worse
if(result==null) {
result = r;
LOGGER.fine(toString()+" : result is set to "+r+" by "+caller);
} else {
if(r.isWorseThan(result)) {
LOGGER.fine(toString()+" : result is set to "+r+" by "+caller);
result = r;
}
}
}
/**
* Gets the subset of {@link #getActions()} that consists of {@link BuildBadgeAction}s.
*/
public List
* The strange method name is so that we can access it from EL.
*/
public boolean getHasArtifacts() {
return !getArtifacts().isEmpty();
}
private void addArtifacts( File dir, String path, List
* This method is called after the status of the build is determined.
* This is a good opportunity to do notifications based on the result
* of the build. When this method is called, the build is not really
* finalized yet, and the build is still considered in progress --- for example,
* even if the build is successful, this build still won't be picked up
* by {@link Job#getLastSuccessfulBuild()}.
*/
void post( BuildListener listener ) throws Exception;
/**
* Performs final clean up action.
*
* This method is called after {@link #post(BuildListener)},
* after the build result is fully finalized. This is the point
* where the build is already considered completed.
*
* Among other things, this is often a necessary pre-condition
* before invoking other builds that depend on this build.
*/
void cleanUp(BuildListener listener) throws Exception;
}
/**
* Used in {@link Runner#run} to indicates that a fatal error in a build
* is reported to {@link BuildListener} and the build should be simply aborted
* without further recording a stack trace.
*/
public static final class RunnerAbortedException extends RuntimeException {}
protected final void run(Runner job) {
if(result!=null)
return; // already built.
BuildListener listener=null;
PrintStream log = null;
onStartBuilding();
try {
// to set the state to COMPLETE in the end, even if the thread dies abnormally.
// otherwise the queue state becomes inconsistent
long start = System.currentTimeMillis();
try {
try {
log = new PrintStream(new FileOutputStream(getLogFile()));
listener = new StreamBuildListener(new CloseProofOutputStream(log));
listener.started();
setResult(job.run(listener));
LOGGER.info(toString()+" main build action completed: "+result);
} catch (ThreadDeath t) {
throw t;
} catch( RunnerAbortedException e ) {
result = Result.FAILURE;
} catch( InterruptedException e) {
// aborted
result = Result.ABORTED;
listener.getLogger().println(Messages.Run_BuildAborted());
LOGGER.log(Level.INFO,toString()+" aborted",e);
} catch( Throwable e ) {
handleFatalBuildProblem(listener,e);
result = Result.FAILURE;
}
// even if the main build fails fatally, try to run post build processing
job.post(listener);
} catch (ThreadDeath t) {
throw t;
} catch( Throwable e ) {
handleFatalBuildProblem(listener,e);
result = Result.FAILURE;
} finally {
long end = System.currentTimeMillis();
duration = end-start;
// advance the state.
// the significance of doing this is that Hudson
// will now see this build as completed.
// things like triggering other builds requires this as pre-condition.
// see issue #980.
state = State.POST_PRODUCTION;
try {
job.cleanUp(listener);
} catch (Exception e) {
handleFatalBuildProblem(listener,e);
// too late to update the result now
}
RunListener.fireCompleted(this,listener);
if(listener!=null)
listener.finished(result);
if(log!=null)
log.close();
try {
save();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
getParent().logRotate();
} catch (IOException e) {
e.printStackTrace();
}
} finally {
onEndBuilding();
}
}
/**
* Handles a fatal build problem (exception) that occurred during the build.
*/
private void handleFatalBuildProblem(BuildListener listener, Throwable e) {
if(listener!=null) {
if(e instanceof IOException)
Util.displayIOException((IOException)e,listener);
Writer w = listener.fatalError(e.getMessage());
if(w!=null) {
try {
e.printStackTrace(new PrintWriter(w));
w.close();
} catch (IOException e1) {
// ignore
}
}
}
}
/**
* Called when a job started building.
*/
protected void onStartBuilding() {
state = State.BUILDING;
}
/**
* Called when a job finished building normally or abnormally.
*/
protected void onEndBuilding() {
state = State.COMPLETED;
if(result==null) {
// shouldn't happen, but be defensive until we figure out why
result = Result.FAILURE;
LOGGER.warning(toString()+": No build result is set, so marking as failure. This shouldn't happen");
}
}
/**
* Save the settings to a file.
*/
public synchronized void save() throws IOException {
getDataFile().write(this);
}
private XmlFile getDataFile() {
return new XmlFile(XSTREAM,new File(getRootDir(),"build.xml"));
}
/**
* Gets the log of the build as a string.
*
* @deprecated Use {@link #getLog(int)} instead as it avoids loading
* the whole log into memory unnecessarily.
*/
@Deprecated
public String getLog() throws IOException {
return Util.loadFile(getLogFile());
}
/**
* Gets the log of the build as a list of strings (one per log line).
* The number of lines returned is constrained by the maxLines parameter.
*
* @param maxLines The maximum number of log lines to return. If the log
* is bigger than this, only the most recent lines are returned.
* @return A list of log lines. Will have no more than maxLines elements.
* @throws IOException If there is a problem reading the log file.
*/
public List
* Used by {@link BuildStep}s that invoke external processes.
*
* On Windows systems, environment variables are case-preserving but
* comparison/query is case insensitive (IOW, you can set 'Path' to something
* and you get the same value by doing '%PATH%'.) So to implement this semantics
* the map returned from here is a {@link TreeMap} with a special comparator.
*
*/
public Map