/*
* The MIT License
*
* Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
* Red Hat, Inc., Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.matrix;
import hudson.AbortException;
import hudson.Functions;
import hudson.Util;
import hudson.console.ModelHyperlinkNote;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Executor;
import hudson.model.Fingerprint;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.util.HttpResponses;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.interceptor.RequirePOST;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import javax.servlet.ServletException;
/**
* Build of {@link MatrixProject}.
*
* @author Kohsuke Kawaguchi
*/
public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
private AxisList axes;
/**
* If non-null, the {@link MatrixBuild} originates from the given build number.
*/
private Integer baseBuild;
public MatrixBuild(MatrixProject job) throws IOException {
super(job);
}
public MatrixBuild(MatrixProject job, Calendar timestamp) {
super(job, timestamp);
}
public MatrixBuild(MatrixProject project, File buildDir) throws IOException {
super(project, buildDir);
}
public Object readResolve() {
// MatrixBuild.axes added in 1.285; default to parent axes for old data
if (axes==null)
axes = getParent().getAxes();
return this;
}
/**
* Deletes the build and all matrix configurations in this build when the button is pressed.
*/
@RequirePOST
public void doDoDeleteAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
checkPermission(DELETE);
// We should not simply delete the build if it has been explicitly
// marked to be preserved, or if the build should not be deleted
// due to dependencies!
String why = getWhyKeepLog();
if (why!=null) {
sendError(hudson.model.Messages.Run_UnableToDelete(toString(),why),req,rsp);
return;
}
List<MatrixRun> runs = getRuns();
for(MatrixRun run : runs){
why = run.getWhyKeepLog();
if (why!=null) {
sendError(hudson.model.Messages.Run_UnableToDelete(toString(),why),req,rsp);
return;
}
run.delete();
}
delete();
rsp.sendRedirect2(req.getContextPath()+'/' + getParent().getUrl());
}
/**
* Used by view to render a ball for {@link MatrixRun}.
*/
public final class RunPtr {
public final Combination combination;
private RunPtr(Combination c) { this.combination=c; }
public MatrixRun getRun() {
return MatrixBuild.this.getRun(combination);
}
/**
* Return the URL to the run that this pointer references.
*
* In the typical case, this creates {@linkplain #getShortUrl() a very short relative url}.
* If the referenced run is a nearest previous build, this method returns a longer URL to that exact build.
* {@link MatrixRun} which belongs to a given build {@link MatrixBuild}.
* If there is no run which belongs to the build, return url of run, which belongs to the nearest previous build.
*/
public String getNearestRunUrl() {
MatrixRun r = getRun();
if (r==null) return null;
if (getNumber()==r.getNumber())
return getShortUrl()+'/';
else
return Stapler.getCurrentRequest().getContextPath()+'/'+r.getUrl();
}
public String getShortUrl() {
return Util.rawEncode(combination.toString());
}
public String getTooltip() {
MatrixRun r = getRun();
if(r!=null) return r.getIconColor().getDescription();
Queue.Item item = Jenkins.getInstance().getQueue().getItem(getParent().getItem(combination));
if(item!=null)
return item.getWhy();
return null; // fall back
}
}
public Layouter<RunPtr> getLayouter() {
// axes can be null if build page is access right when build starts
return axes == null ? null : new Layouter<RunPtr>(axes) {
protected RunPtr getT(Combination c) {
return new RunPtr(c);
}
};
}
/**
* Sets the base build from which this build is derived.
* @since 1.416
*/
public void setBaseBuild(MatrixBuild baseBuild) {
this.baseBuild = (baseBuild==null || baseBuild==getPreviousBuild()) ? null : baseBuild.getNumber();
}
/**
* Returns the base {@link MatrixBuild} that this build originates from.
* <p>
* If this build is a partial build, unexecuted {@link MatrixRun}s are delegated to this build number.
*/
public MatrixBuild getBaseBuild() {
return baseBuild==null ? getPreviousBuild() : getParent().getBuildByNumber(baseBuild);
}
/**
* Gets the {@link MatrixRun} in this build that corresponds
* to the given combination.
*/
public MatrixRun getRun(Combination c) {
MatrixConfiguration config = getParent().getItem(c);
if(config==null) return null;
return getRunForConfiguration(config);
}
/**
* Returns all {@link MatrixRun}s for this {@link MatrixBuild}.
*/
@Exported
public List<MatrixRun> getRuns() {
List<MatrixRun> r = new ArrayList<MatrixRun>();
for(MatrixConfiguration c : getParent().getItems()) {
MatrixRun b = getRunForConfiguration(c);
if (b != null) r.add(b);
}
return r;
}
private MatrixRun getRunForConfiguration(MatrixConfiguration c) {
for (MatrixBuild b=this; b!=null; b=b.getBaseBuild()) {
MatrixRun r = c.getBuildByNumber(b.getNumber());
if (r!=null) return r;
}
return null;
}
/**
* Returns all {@link MatrixRun}s for exactly this {@link MatrixBuild}.
* <p>
* Unlike {@link #getRuns()}, this method excludes those runs
* that didn't run and got inherited.
* @since 1.413
*/
public List<MatrixRun> getExactRuns() {
List<MatrixRun> r = new ArrayList<MatrixRun>();
for(MatrixConfiguration c : getParent().getItems()) {
MatrixRun b = c.getBuildByNumber(getNumber());
if (b != null) r.add(b);
}
return r;
}
@Override
public String getWhyKeepLog() {
MatrixBuild b = getNextBuild();
if (b!=null && b.isPartial())
return b.getDisplayName()+" depends on this";
return super.getWhyKeepLog();
}
/**
* True if this build didn't do a full build and it is depending on the result of the previous build.
*/
public boolean isPartial() {
for(MatrixConfiguration c : getParent().getActiveConfigurations()) {
MatrixRun b = c.getNearestOldBuild(getNumber());
if (b != null && b.getNumber()!=getNumber())
return true;
}
return false;
}
@Override
public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) {
try {
MatrixRun item = getRun(Combination.fromString(token));
if(item!=null) {
if (item.getNumber()==this.getNumber())
return item;
else {
// redirect the user to the correct URL
String url = Functions.joinPath(item.getUrl(), req.getRestOfPath());
String qs = req.getQueryString();
if (qs!=null) url+='?'+qs;
throw HttpResponses.redirectViaContextPath(url);
}
}
} catch (IllegalArgumentException _) {
// failed to parse the token as Combination. Must be something else
}
return super.getDynamic(token,req,rsp);
}
@Override
public void run() {
run(new RunnerImpl());
}
@Override
public Fingerprint.RangeSet getDownstreamRelationship(AbstractProject that) {
Fingerprint.RangeSet rs = super.getDownstreamRelationship(that);
for(MatrixRun run : getRuns())
rs.add(run.getDownstreamRelationship(that));
return rs;
}
private class RunnerImpl extends AbstractRunner {
private final List<MatrixAggregator> aggregators = new ArrayList<MatrixAggregator>();
protected Result doRun(BuildListener listener) throws Exception {
MatrixProject p = getProject();
PrintStream logger = listener.getLogger();
// list up aggregators
listUpAggregators(listener, p.getPublishers().values());
listUpAggregators(listener, p.getProperties().values());
listUpAggregators(listener, p.getBuildWrappers().values());
axes = p.getAxes();
try {
return p.getExecutionStrategy().run(MatrixBuild.this, aggregators, listener);
} catch( InterruptedException e ) {
logger.println("Aborted");
Executor x = Executor.currentExecutor();
x.recordCauseOfInterruption(MatrixBuild.this, listener);
return x.abortResult();
} catch (AbortException e) {
logger.println(e.getMessage());
return Result.FAILURE;
} finally {
// if the build was aborted in the middle. Cancel all the configuration builds.
Queue q = Jenkins.getInstance().getQueue();
synchronized(q) {// avoid micro-locking in q.cancel.
final int n = getNumber();
for (MatrixConfiguration c : p.getActiveConfigurations()) {
if(q.cancel(c))
logger.println(Messages.MatrixBuild_Cancelled(ModelHyperlinkNote.encodeTo(c)));
MatrixRun b = c.getBuildByNumber(n);
if(b!=null && b.isBuilding()) {// executor can spend some time in post production state, so only cancel in-progress builds.
Executor exe = b.getExecutor();
if(exe!=null) {
logger.println(Messages.MatrixBuild_Interrupting(ModelHyperlinkNote.encodeTo(b)));
exe.interrupt();
}
}
}
}
}
}
private void listUpAggregators(BuildListener listener, Collection<?> values) {
for (Object v : values) {
if (v instanceof MatrixAggregatable) {
MatrixAggregatable ma = (MatrixAggregatable) v;
MatrixAggregator a = ma.createAggregator(MatrixBuild.this, launcher, listener);
if(a!=null)
aggregators.add(a);
}
}
}
public void post2(BuildListener listener) throws Exception {
for (MatrixAggregator a : aggregators)
a.endBuild();
}
}
}