/* * The MIT License * * Copyright (c) 2004-2009, 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.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Executor; import hudson.model.Fingerprint; import hudson.model.Hudson; import hudson.model.JobProperty; import hudson.model.Node; import hudson.model.ParametersAction; import hudson.model.Queue; import hudson.model.Result; import hudson.model.Cause.UpstreamCause; import hudson.slaves.WorkspaceList; import hudson.slaves.WorkspaceList.Lease; import hudson.tasks.Publisher; 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.HashSet; import java.util.List; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; /** * Build of {@link MatrixProject}. * * @author Kohsuke Kawaguchi */ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> { private AxisList axes; 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; } /** * 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); } public String getShortUrl() { return Util.rawEncode(combination.toString()); } public String getTooltip() { MatrixRun r = getRun(); if(r!=null) return r.getIconColor().getDescription(); Queue.Item item = Hudson.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); } }; } /** * 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 config.getBuildByNumber(getNumber()); } /** * Returns all {@link MatrixRun}s for this {@link MatrixBuild}. */ public List<MatrixRun> getRuns() { 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 Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) { try { MatrixRun item = getRun(Combination.fromString(token)); if(item!=null) return item; } 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 for (Publisher pub : p.getPublishers().values()) { if (pub instanceof MatrixAggregatable) { MatrixAggregatable ma = (MatrixAggregatable) pub; MatrixAggregator a = ma.createAggregator(MatrixBuild.this, launcher, listener); if(a!=null) aggregators.add(a); } } //let properties do their job for (JobProperty prop : p.getProperties().values()) { if (prop instanceof MatrixAggregatable) { MatrixAggregatable ma = (MatrixAggregatable) prop; MatrixAggregator a = ma.createAggregator(MatrixBuild.this, launcher, listener); if(a!=null) aggregators.add(a); } } axes = p.getAxes(); Collection<MatrixConfiguration> activeConfigurations = p.getActiveConfigurations(); final int n = getNumber(); String touchStoneFilter = p.getTouchStoneCombinationFilter(); Collection<MatrixConfiguration> touchStoneConfigurations = new HashSet<MatrixConfiguration>(); Collection<MatrixConfiguration> delayedConfigurations = new HashSet<MatrixConfiguration>(); for (MatrixConfiguration c: activeConfigurations) { if (touchStoneFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), p.getTouchStoneCombinationFilter())) { touchStoneConfigurations.add(c); } else { delayedConfigurations.add(c); } } for (MatrixAggregator a : aggregators) if(!a.startBuild()) return Result.FAILURE; try { if(!p.isRunSequentially()) for(MatrixConfiguration c : touchStoneConfigurations) scheduleConfigurationBuild(logger, c); Result r = Result.SUCCESS; for (MatrixConfiguration c : touchStoneConfigurations) { if(p.isRunSequentially()) scheduleConfigurationBuild(logger, c); Result buildResult = waitForCompletion(listener, c); r = r.combine(buildResult); } if (p.getTouchStoneResultCondition() != null && r.isWorseThan(p.getTouchStoneResultCondition())) { logger.printf("Touchstone configurations resulted in %s, so aborting...\n", r); return r; } if(!p.isRunSequentially()) for(MatrixConfiguration c : delayedConfigurations) scheduleConfigurationBuild(logger, c); for (MatrixConfiguration c : delayedConfigurations) { if(p.isRunSequentially()) scheduleConfigurationBuild(logger, c); Result buildResult = waitForCompletion(listener, c); r = r.combine(buildResult); } return r; } catch( InterruptedException e ) { logger.println("Aborted"); return Result.ABORTED; } catch (AggregatorFailureException e) { return Result.FAILURE; } finally { // if the build was aborted in the middle. Cancel all the configuration builds. Queue q = Hudson.getInstance().getQueue(); synchronized(q) {// avoid micro-locking in q.cancel. for (MatrixConfiguration c : activeConfigurations) { if(q.cancel(c)) logger.println(Messages.MatrixBuild_Cancelled(c.getDisplayName())); MatrixRun b = c.getBuildByNumber(n); if(b!=null) { Executor exe = b.getExecutor(); if(exe!=null) { logger.println(Messages.MatrixBuild_Interrupting(b.getDisplayName())); exe.interrupt(); } } } } } } private Result waitForCompletion(BuildListener listener, MatrixConfiguration c) throws InterruptedException, IOException, AggregatorFailureException { String whyInQueue = ""; long startTime = System.currentTimeMillis(); // wait for the completion int appearsCancelledCount = 0; while(true) { MatrixRun b = c.getBuildByNumber(getNumber()); // two ways to get beyond this. one is that the build starts and gets done, // or the build gets cancelled before it even started. Result buildResult = null; if(b!=null && !b.isBuilding()) buildResult = b.getResult(); Queue.Item qi = c.getQueueItem(); if(b==null && qi==null) appearsCancelledCount++; else appearsCancelledCount = 0; if(appearsCancelledCount>=5) { // there's conceivably a race condition in computating b and qi, as their computation // are not synchronized. There are indeed several reports of Hudson incorrectly assuming // builds being cancelled. See // http://www.nabble.com/Master-slave-problem-tt14710987.html and also // http://www.nabble.com/Anyone-using-AccuRev-plugin--tt21634577.html#a21671389 // because of this, we really make sure that the build is cancelled by doing this 5 // times over 5 seconds listener.getLogger().println(Messages.MatrixBuild_AppearsCancelled(c.getDisplayName())); buildResult = Result.ABORTED; } if(buildResult!=null) { for (MatrixAggregator a : aggregators) if(!a.endRun(b)) throw new AggregatorFailureException(); return buildResult; } if(qi!=null) { // if the build seems to be stuck in the queue, display why String why = qi.getWhy(); if(!why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) { listener.getLogger().println(c.getDisplayName()+" is still in the queue: "+why); whyInQueue = why; } } Thread.sleep(1000); } } private void scheduleConfigurationBuild(PrintStream logger, MatrixConfiguration c) { logger.println(Messages.MatrixBuild_Triggering(c.getDisplayName())); c.scheduleBuild(getAction(ParametersAction.class), new UpstreamCause(MatrixBuild.this)); } public void post2(BuildListener listener) throws Exception { for (MatrixAggregator a : aggregators) a.endBuild(); } @Override protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws IOException, InterruptedException { String customWorkspace = getProject().getCustomWorkspace(); if (customWorkspace != null) { // we allow custom workspaces to be concurrently used between jobs. return Lease.createDummyLease(n.getRootPath().child(getEnvironment(listener).expand(customWorkspace))); } return super.decideWorkspace(n,wsl); } } /** * A private exception to help maintain the correct control flow after extracting the 'waitForCompletion' method */ private static class AggregatorFailureException extends Exception {} }