AbstractBuild.java 51.3 KB
Newer Older
K
kohsuke 已提交
1 2
/*
 * The MIT License
3
 *
4
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc., CloudBees, Inc.
5
 *
K
kohsuke 已提交
6 7 8 9 10 11
 * 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:
12
 *
K
kohsuke 已提交
13 14
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
15
 *
K
kohsuke 已提交
16 17 18 19 20 21 22 23
 * 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.
 */
24 25
package hudson.model;

K
Kohsuke Kawaguchi 已提交
26
import com.google.common.collect.ImmutableList;
27
import com.google.common.collect.ImmutableSortedSet;
28
import hudson.AbortException;
29
import hudson.EnvVars;
30
import hudson.FilePath;
31
import hudson.Functions;
32
import hudson.Launcher;
33 34
import hudson.console.AnnotatedLargeText;
import hudson.console.ExpandableDetailsNote;
35
import hudson.console.ModelHyperlinkNote;
36
import hudson.matrix.MatrixConfiguration;
37 38
import hudson.model.Fingerprint.BuildPtr;
import hudson.model.Fingerprint.RangeSet;
39
import hudson.model.labels.LabelAtom;
40
import hudson.model.listeners.RunListener;
K
kohsuke 已提交
41
import hudson.model.listeners.SCMListener;
42 43 44
import hudson.scm.ChangeLogParser;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
45
import hudson.scm.NullChangeLogParser;
46 47 48 49
import hudson.scm.SCM;
import hudson.slaves.NodeProperty;
import hudson.slaves.WorkspaceList;
import hudson.slaves.WorkspaceList.Lease;
50
import hudson.tasks.BuildStep;
51 52
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildTrigger;
53
import hudson.tasks.BuildWrapper;
54 55
import hudson.tasks.Builder;
import hudson.tasks.Fingerprinter.FingerprintAction;
56
import hudson.tasks.Publisher;
57
import hudson.tasks.test.AbstractTestResultAction;
58
import hudson.tasks.test.AggregatedTestResultAction;
59
import hudson.util.*;
60
import jenkins.model.Jenkins;
61
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
62
import jenkins.model.lazy.BuildReference;
63
import org.kohsuke.stapler.HttpResponse;
64
import org.kohsuke.stapler.Stapler;
65 66
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
67
import org.kohsuke.stapler.export.Exported;
68
import org.xml.sax.SAXException;
69

70
import javax.servlet.ServletException;
71 72
import java.io.File;
import java.io.IOException;
73
import java.io.InterruptedIOException;
74
import java.io.StringWriter;
75
import java.lang.ref.WeakReference;
76
import java.text.MessageFormat;
77 78 79 80 81 82 83 84 85 86 87
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
88 89
import java.util.logging.Level;
import java.util.logging.Logger;
90
import javax.annotation.CheckForNull;
J
Jesse Glick 已提交
91
import org.kohsuke.stapler.interceptor.RequirePOST;
K
kohsuke 已提交
92

93 94
import static java.util.logging.Level.WARNING;

95 96 97
/**
 * Base implementation of {@link Run}s that build software.
 *
98
 * For now this is primarily the common part of {@link Build} and MavenBuild.
99 100 101 102
 *
 * @author Kohsuke Kawaguchi
 * @see AbstractProject
 */
103
public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Run<P,R> implements Queue.Executable {
104

105 106 107 108 109
    /**
     * Set if we want the blame information to flow from upstream to downstream build.
     */
    private static final boolean upstreamCulprits = Boolean.getBoolean("hudson.upstreamCulprits");

110
    /**
K
kohsuke 已提交
111
     * Name of the slave this project was built on.
K
kohsuke 已提交
112
     * Null or "" if built by the master. (null happens when we read old record that didn't have this information.)
113 114 115
     */
    private String builtOn;

K
kohsuke 已提交
116 117
    /**
     * The file path on the node that performed a build. Kept as a string since {@link FilePath} is not serializable into XML.
K
kohsuke 已提交
118
     * @since 1.319
K
kohsuke 已提交
119 120 121
     */
    private String workspace;

122 123 124 125 126
    /**
     * Version of Hudson that built this.
     */
    private String hudsonVersion;

127 128 129 130 131 132 133 134 135
    /**
     * SCM used for this build.
     * Maybe null, for historical reason, in which case CVS is assumed.
     */
    private ChangeLogParser scm;

    /**
     * Changes in this build.
     */
J
John Pederzolli 已提交
136
    private volatile transient WeakReference<ChangeLogSet<? extends Entry>> changeSet;
137

138 139 140 141 142 143 144 145 146 147 148 149 150 151
    /**
     * Cumulative list of people who contributed to the build problem.
     *
     * <p>
     * This is a list of {@link User#getId() user ids} who made a change
     * since the last non-broken build. Can be null (which should be
     * treated like empty set), because of the compatibility.
     *
     * <p>
     * This field is semi-final --- once set the value will never be modified.
     *
     * @since 1.137
     */
    private volatile Set<String> culprits;
152

K
kohsuke 已提交
153
    /**
154
     * During the build this field remembers {@link hudson.tasks.BuildWrapper.Environment}s created by
K
kohsuke 已提交
155 156
     * {@link BuildWrapper}. This design is bit ugly but forced due to compatibility.
     */
157
    protected transient List<Environment> buildEnvironments;
158

159 160 161 162 163 164
    /**
     * Pointers to form bi-directional link between adjacent {@link AbstractBuild}s.
     *
     * <p>
     * Unlike {@link Run}, {@link AbstractBuild}s do lazy-loading, so we don't use
     * {@link Run#previousBuild} and {@link Run#nextBuild}, and instead use these
165
     * fields and point to {@link #selfReference} (or {@link #none}) of adjacent builds.
166 167 168
     */
    private volatile transient BuildReference<R> previousBuild, nextBuild;

169 170 171
    @SuppressWarnings({"unchecked", "rawtypes"}) private static final BuildReference NONE = new BuildReference("NONE", null);
    @SuppressWarnings("unchecked") private BuildReference<R> none() {return NONE;}

172 173 174 175
    /*package*/ final transient BuildReference<R> selfReference = new BuildReference<R>(getId(),_this());



176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    protected AbstractBuild(P job) throws IOException {
        super(job);
    }

    protected AbstractBuild(P job, Calendar timestamp) {
        super(job, timestamp);
    }

    protected AbstractBuild(P project, File buildDir) throws IOException {
        super(project, buildDir);
    }

    public final P getProject() {
        return getParent();
    }

192 193 194 195 196 197
    @Override
    void dropLinks() {
        super.dropLinks();

        if(nextBuild!=null) {
            AbstractBuild nb = nextBuild.get();
198
            if (nb!=null) {
199
                nb.previousBuild = previousBuild;
200
            }
201 202 203 204 205 206
        }
        if(previousBuild!=null) {
            AbstractBuild pb = previousBuild.get();
            if (pb!=null)   pb.nextBuild = nextBuild;
        }
    }
207 208 209

    @Override
    public R getPreviousBuild() {
210 211 212 213 214
        while (true) {
            BuildReference<R> r = previousBuild;    // capture the value once

            if (r==null) {
                // having two neighbors pointing to each other is important to make RunMap.removeValue work
J
Jesse Glick 已提交
215 216
                P _parent = getParent();
                if (_parent == null) {
217
                    throw new IllegalStateException("no parent for " + number + " in " + workspace);
J
Jesse Glick 已提交
218 219
                }
                R pb = _parent._getRuns().search(number-1, Direction.DESC);
220 221 222 223 224
                if (pb!=null) {
                    ((AbstractBuild)pb).nextBuild = selfReference;   // establish bi-di link
                    this.previousBuild = pb.selfReference;
                    return pb;
                } else {
225
                    this.previousBuild = none();
226 227 228
                    return null;
                }
            }
229
            if (r==none())
230 231 232 233 234 235 236
                return null;

            R referent = r.get();
            if (referent!=null) return referent;

            // the reference points to a GC-ed object, drop the reference and do it again
            this.previousBuild = null;
237 238 239 240 241
        }
    }

    @Override
    public R getNextBuild() {
242 243 244 245 246 247 248 249 250 251 252
        while (true) {
            BuildReference<R> r = nextBuild;    // capture the value once

            if (r==null) {
                // having two neighbors pointing to each other is important to make RunMap.removeValue work
                R nb = getParent().builds.search(number+1, Direction.ASC);
                if (nb!=null) {
                    ((AbstractBuild)nb).previousBuild = selfReference;   // establish bi-di link
                    this.nextBuild = nb.selfReference;
                    return nb;
                } else {
253
                    this.nextBuild = none();
254 255 256
                    return null;
                }
            }
257
            if (r==none())
258 259 260 261 262 263 264
                return null;

            R referent = r.get();
            if (referent!=null) return referent;

            // the reference points to a GC-ed object, drop the reference and do it again
            this.nextBuild = null;
265 266 267
        }
    }

268 269
    /**
     * Returns a {@link Slave} on which this build was done.
K
kohsuke 已提交
270 271
     *
     * @return
M
mindless 已提交
272
     *      null, for example if the slave that this build run no longer exists.
273
     */
274
    public @CheckForNull Node getBuiltOn() {
275
        if (builtOn==null || builtOn.equals(""))
276
            return Jenkins.getInstance();
277
        else
278
            return Jenkins.getInstance().getNode(builtOn);
279 280 281
    }

    /**
M
mindless 已提交
282 283
     * Returns the name of the slave it was built on; null or "" if built by the master.
     * (null happens when we read old record that didn't have this information.)
284
     */
K
kohsuke 已提交
285
    @Exported(name="builtOn")
286 287 288 289
    public String getBuiltOnStr() {
        return builtOn;
    }

290
    /**
291 292 293 294
     * Allows subtypes to set the value of {@link #builtOn}.
     * This is used for those implementations where an {@link AbstractBuild} is made 'built' without
     * actually running its {@link #run()} method.
     *
295 296
     * @since 1.429
     */
297
    protected void setBuiltOnStr( String builtOn ) {
298 299 300
        this.builtOn = builtOn;
    }

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    /**
     * Gets the nearest ancestor {@link AbstractBuild} that belongs to
     * {@linkplain AbstractProject#getRootProject() the root project of getProject()} that
     * dominates/governs/encompasses this build.
     *
     * <p>
     * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs,
     * and still in some of them, builds of child projects are related/tied to that of the parent project.
     * In such a case, this method returns the governing build.
     *
     * @return never null. In the worst case the build dominates itself.
     * @since 1.421
     * @see AbstractProject#getRootProject()
     */
    public AbstractBuild<?,?> getRootBuild() {
        return this;
    }

K
kohsuke 已提交
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
    /**
     * Used to render the side panel "Back to project" link.
     *
     * <p>
     * In a rare situation where a build can be reached from multiple paths,
     * returning different URLs from this method based on situations might
     * be desirable.
     *
     * <p>
     * If you override this method, you'll most likely also want to override
     * {@link #getDisplayName()}.
     */
    public String getUpUrl() {
        return Functions.getNearestAncestorUrl(Stapler.getCurrentRequest(),getParent())+'/';
    }

K
kohsuke 已提交
335 336 337 338 339
    /**
     * Gets the directory where this build is being built.
     *
     * <p>
     * Note to implementors: to control where the workspace is created, override
340
     * {@link AbstractBuildExecution#decideWorkspace(Node,WorkspaceList)}.
K
kohsuke 已提交
341 342 343 344 345
     *
     * @return
     *      null if the workspace is on a slave that's not connected. Note that once the build is completed,
     *      the workspace may be used to build something else, so the value returned from this method may
     *      no longer show a workspace as it was used for this build.
K
kohsuke 已提交
346
     * @since 1.319
K
kohsuke 已提交
347
     */
348
    public final @CheckForNull FilePath getWorkspace() {
349
        if (workspace==null) return null;
K
kohsuke 已提交
350
        Node n = getBuiltOn();
351
        if (n==null) return null;
K
kohsuke 已提交
352 353 354
        return n.createPath(workspace);
    }

355
    /**
356
     * Normally, a workspace is assigned by {@link hudson.model.Run.RunExecution}, but this lets you set the workspace in case
357 358 359 360 361 362
     * {@link AbstractBuild} is created without a build.
     */
    protected void setWorkspace(FilePath ws) {
        this.workspace = ws.getRemote();
    }

K
kohsuke 已提交
363 364 365 366 367 368 369 370
    /**
     * Returns the root directory of the checked-out module.
     * <p>
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
     */
    public final FilePath getModuleRoot() {
        FilePath ws = getWorkspace();
371
        if (ws==null)    return null;
372
        return getParent().getScm().getModuleRoot(ws, this);
K
kohsuke 已提交
373 374 375 376 377 378 379 380 381 382 383
    }

    /**
     * Returns the root directories of all checked-out modules.
     * <p>
     * Some SCMs support checking out multiple modules into the same workspace.
     * In these cases, the returned array will have a length greater than one.
     * @return The roots of all modules checked out from the SCM.
     */
    public FilePath[] getModuleRoots() {
        FilePath ws = getWorkspace();
384
        if (ws==null)    return null;
385
        return getParent().getScm().getModuleRoots(ws, this);
K
kohsuke 已提交
386 387
    }

388 389 390 391
    /**
     * List of users who committed a change since the last non-broken build till now.
     *
     * <p>
K
kohsuke 已提交
392 393
     * This list at least always include people who made changes in this build, but
     * if the previous build was a failure it also includes the culprit list from there.
394 395 396 397 398 399
     *
     * @return
     *      can be empty but never null.
     */
    @Exported
    public Set<User> getCulprits() {
400
        if (culprits==null) {
401
            Set<User> r = new HashSet<User>();
402
            R p = getPreviousCompletedBuild();
403
            if (p !=null && isBuilding()) {
K
kohsuke 已提交
404
                Result pr = p.getResult();
K
Kohsuke Kawaguchi 已提交
405
                if (pr!=null && pr.isWorseThan(Result.SUCCESS)) {
K
kohsuke 已提交
406 407 408 409 410 411
                    // we are still building, so this is just the current latest information,
                    // but we seems to be failing so far, so inherit culprits from the previous build.
                    // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record
                    // this information
                    r.addAll(p.getCulprits());
                }
412
            }
413
            for (Entry e : getChangeSet())
414
                r.add(e.getAuthor());
415 416 417 418

            if (upstreamCulprits) {
                // If we have dependencies since the last successful build, add their authors to our list
                if (getPreviousNotFailedBuild() != null) {
K
Kohsuke Kawaguchi 已提交
419 420
                    Map <AbstractProject,DependencyChange> depmap = getDependencyChanges(getPreviousSuccessfulBuild());
                    for (DependencyChange dep : depmap.values()) {
421 422 423 424 425 426 427 428 429
                        for (AbstractBuild<?,?> b : dep.getBuilds()) {
                            for (Entry entry : b.getChangeSet()) {
                                r.add(entry.getAuthor());
                            }
                        }
                    }
                }
            }

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
            return r;
        }

        return new AbstractSet<User>() {
            public Iterator<User> iterator() {
                return new AdaptedIterator<String,User>(culprits.iterator()) {
                    protected User adapt(String id) {
                        return User.get(id);
                    }
                };
            }

            public int size() {
                return culprits.size();
            }
        };
    }

448 449 450 451 452 453 454
    /**
     * Returns true if this user has made a commit to this build.
     *
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
        for (ChangeLogSet.Entry e : getChangeSet())
455 456 457
            try{
                if (e.getAuthor()==user)
                    return true;
458 459
            } catch (RuntimeException re) { 
                LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + getParent().getDisplayName() + ", " + getDisplayName(), re);
460
            }
461 462 463
        return false;
    }

K
kohsuke 已提交
464 465 466 467 468 469 470 471 472
    /**
     * Gets the version of Hudson that was used to build this job.
     *
     * @since 1.246
     */
    public String getHudsonVersion() {
        return hudsonVersion;
    }

473 474
    /**
     * @deprecated as of 1.467
475
     *      Please use {@link hudson.model.Run.RunExecution}
476 477 478 479 480 481 482 483 484 485 486
     */
    public abstract class AbstractRunner extends AbstractBuildExecution {

    }

    public abstract class AbstractBuildExecution extends Runner {
        /*
            Some plugins might depend on this instance castable to Runner, so we need to use
            deprecated class here.
         */

487 488
        /**
         * Since configuration can be changed while a build is in progress,
489
         * create a launcher once and stick to it for the entire build duration.
490 491 492
         */
        protected Launcher launcher;

493 494 495 496 497
        /**
         * Output/progress of this build goes here.
         */
        protected BuildListener listener;

K
Kohsuke Kawaguchi 已提交
498 499 500 501 502
        /**
         * Lease of the workspace.
         */
        private Lease lease;

K
kohsuke 已提交
503
        /**
K
typo  
Kohsuke Kawaguchi 已提交
504
         * Returns the current {@link Node} on which we are building.
K
kohsuke 已提交
505 506 507 508 509
         */
        protected final Node getCurrentNode() {
            return Executor.currentExecutor().getOwner().getNode();
        }

510 511 512 513 514 515 516 517
        public Launcher getLauncher() {
            return launcher;
        }

        public BuildListener getListener() {
            return listener;
        }

K
kohsuke 已提交
518 519 520 521 522 523 524 525
        /**
         * Allocates the workspace from {@link WorkspaceList}.
         *
         * @param n
         *      Passed in for the convenience. The node where the build is running.
         * @param wsl
         *      Passed in for the convenience. The returned path must be registered to this object.
         */
526
        protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws InterruptedException, IOException {
527 528 529 530 531
            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)));
            }
K
kohsuke 已提交
532
            // TODO: this cast is indicative of abstraction problem
533
            return wsl.allocate(n.getWorkspaceFor((TopLevelItem)getProject()), getBuild());
K
kohsuke 已提交
534 535
        }

536
        public Result run(BuildListener listener) throws Exception {
537
            Node node = getCurrentNode();
538 539
            assert builtOn==null;
            builtOn = node.getNodeName();
540
            hudsonVersion = Jenkins.VERSION;
541
            this.listener = listener;
542

543
            launcher = createLauncher(listener);
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
            if (!Jenkins.getInstance().getNodes().isEmpty()) {
                if (node instanceof Jenkins) {
                    listener.getLogger().print(Messages.AbstractBuild_BuildingOnMaster());
                } else {
                    listener.getLogger().print(Messages.AbstractBuild_BuildingRemotely(ModelHyperlinkNote.encodeTo("/computer/" + builtOn, builtOn)));
                    Set<LabelAtom> assignedLabels = new HashSet<LabelAtom>(node.getAssignedLabels());
                    assignedLabels.remove(node.getSelfLabel());
                    if (!assignedLabels.isEmpty()) {
                        boolean first = true;
                        for (LabelAtom label : assignedLabels) {
                            if (first) {
                                listener.getLogger().print(" (");
                                first = false;
                            } else {
                                listener.getLogger().print(' ');
                            }
                            listener.getLogger().print(label.getName());
                        }
                        listener.getLogger().print(')');
                    }
                }
            } else {
                listener.getLogger().print(Messages.AbstractBuild_Building());
            }
568
            
K
Kohsuke Kawaguchi 已提交
569
            lease = decideWorkspace(node, Computer.currentComputer().getWorkspaceList());
K
kohsuke 已提交
570

K
Kohsuke Kawaguchi 已提交
571 572 573
            workspace = lease.path.getRemote();
            listener.getLogger().println(Messages.AbstractBuild_BuildingInWorkspace(workspace));
            node.getFileSystemProvisioner().prepareWorkspace(AbstractBuild.this,lease.path,listener);
574

K
Kohsuke Kawaguchi 已提交
575 576 577
            for (WorkspaceListener wl : WorkspaceListener.all()) {
                wl.beforeUse(AbstractBuild.this, lease.path, listener);
            }
578

K
Kohsuke Kawaguchi 已提交
579 580
            getProject().getScmCheckoutStrategy().preCheckout(AbstractBuild.this, launcher, this.listener);
            getProject().getScmCheckoutStrategy().checkout(this);
581

K
Kohsuke Kawaguchi 已提交
582 583
            if (!preBuild(listener,project.getProperties()))
                return Result.FAILURE;
584

K
Kohsuke Kawaguchi 已提交
585
            Result result = doRun(listener);
586

K
Kohsuke Kawaguchi 已提交
587 588 589 590 591
            Computer c = node.toComputer();
            if (c==null || c.isOffline()) {
                // As can be seen in HUDSON-5073, when a build fails because of the slave connectivity problem,
                // error message doesn't point users to the slave. So let's do it here.
                listener.hyperlink("/computer/"+builtOn+"/log","Looks like the node went offline during the build. Check the slave log for the details.");
D
Dave Brosius 已提交
592

K
Kohsuke Kawaguchi 已提交
593 594 595 596 597 598 599 600 601
                if (c != null) {
                    // grab the end of the log file. This might not work very well if the slave already
                    // starts reconnecting. Fixing this requires a ring buffer in slave logs.
                    AnnotatedLargeText<Computer> log = c.getLogText();
                    StringWriter w = new StringWriter();
                    log.writeHtmlTo(Math.max(0,c.getLogFile().length()-10240),w);

                    listener.getLogger().print(ExpandableDetailsNote.encodeTo("details",w.toString()));
                    listener.getLogger().println();
602
                }
K
Kohsuke Kawaguchi 已提交
603
            }
604

K
Kohsuke Kawaguchi 已提交
605 606 607 608
            // kill run-away processes that are left
            // use multiple environment variables so that people can escape this massacre by overriding an environment
            // variable for some processes
            launcher.kill(getCharacteristicEnvVars());
609

K
Kohsuke Kawaguchi 已提交
610 611 612 613
            // this is ugly, but for historical reason, if non-null value is returned
            // it should become the final result.
            if (result==null)    result = getResult();
            if (result==null)    result = Result.SUCCESS;
614

K
Kohsuke Kawaguchi 已提交
615
            return result;
616 617
        }

618 619 620 621 622
        /**
         * Creates a {@link Launcher} that this build will use. This can be overridden by derived types
         * to decorate the resulting {@link Launcher}.
         *
         * @param listener
623
         *      Always non-null. Connected to the main build output.
624 625
         */
        protected Launcher createLauncher(BuildListener listener) throws IOException, InterruptedException {
626 627 628 629
            Launcher l = getCurrentNode().createLauncher(listener);

            if (project instanceof BuildableItemWithBuildWrappers) {
                BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
630
                for (BuildWrapper bw : biwbw.getBuildWrappersList())
631 632 633 634 635
                    l = bw.decorateLauncher(AbstractBuild.this,l,listener);
            }

            buildEnvironments = new ArrayList<Environment>();

636 637 638 639 640 641 642
            for (RunListener rl: RunListener.all()) {
                Environment environment = rl.setUpEnvironment(AbstractBuild.this, l, listener);
                if (environment != null) {
                    buildEnvironments.add(environment);
                }
            }

643
            for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) {
644 645 646 647 648 649 650 651 652 653 654 655 656 657
                Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
                if (environment != null) {
                    buildEnvironments.add(environment);
                }
            }

            for (NodeProperty nodeProperty: Computer.currentComputer().getNode().getNodeProperties()) {
                Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
                if (environment != null) {
                    buildEnvironments.add(environment);
                }
            }

            return l;
658 659
        }

660 661 662
        public void defaultCheckout() throws IOException, InterruptedException {
            AbstractBuild<?,?> build = AbstractBuild.this;
            AbstractProject<?, ?> project = build.getProject();
663

664 665 666 667 668 669 670
            for (int retryCount=project.getScmCheckoutRetryCount(); ; retryCount--) {
                // for historical reasons, null in the scm field means CVS, so we need to explicitly set this to something
                // in case check out fails and leaves a broken changelog.xml behind.
                // see http://www.nabble.com/CVSChangeLogSet.parse-yields-SAXParseExceptions-when-parsing-bad-*AccuRev*-changelog.xml-files-td22213663.html
                build.scm = NullChangeLogParser.INSTANCE;

                try {
K
Kohsuke Kawaguchi 已提交
671
                    if (project.checkout(build, launcher,listener,new File(build.getRootDir(),"changelog.xml"))) {
672 673 674 675 676 677 678
                        // check out succeeded
                        SCM scm = project.getScm();

                        build.scm = scm.createChangeLogParser();
                        build.changeSet = new WeakReference<ChangeLogSet<? extends Entry>>(build.calcChangeSet());

                        for (SCMListener l : Jenkins.getInstance().getSCMListeners())
679 680 681
                            try {
                                l.onChangeLogParsed(build,listener,build.getChangeSet());
                            } catch (Exception e) {
682
                                throw new IOException("Failed to parse changelog",e);
683
                            }
684 685 686 687

                        // Get a chance to do something after checkout and changelog is done
                        scm.postCheckout( build, launcher, build.getWorkspace(), listener );

688 689 690 691 692 693 694 695 696
                        return;
                    }
                } catch (AbortException e) {
                    listener.error(e.getMessage());
                } catch (InterruptedIOException e) {
                    throw (InterruptedException)new InterruptedException().initCause(e);
                } catch (IOException e) {
                    // checkout error not yet reported
                    e.printStackTrace(listener.getLogger());
697
                }
698 699 700 701 702 703 704

                if (retryCount == 0)   // all attempts failed
                    throw new RunnerAbortedException();

                listener.getLogger().println("Retrying after 10 seconds");
                Thread.sleep(10000);
            }
705 706
        }

K
kohsuke 已提交
707 708 709 710 711 712 713 714 715
        /**
         * The portion of a build that is specific to a subclass of {@link AbstractBuild}
         * goes here.
         *
         * @return
         *      null to continue the build normally (that means the doRun method
         *      itself run successfully)
         *      Return a non-null value to abort the build right there with the specified result code.
         */
K
kohsuke 已提交
716
        protected abstract Result doRun(BuildListener listener) throws Exception, RunnerAbortedException;
717 718 719 720 721 722 723 724 725 726 727 728 729 730

        /**
         * @see #post(BuildListener)
         */
        protected abstract void post2(BuildListener listener) throws Exception;

        public final void post(BuildListener listener) throws Exception {
            try {
                post2(listener);
            } finally {
                // update the culprit list
                HashSet<String> r = new HashSet<String>();
                for (User u : getCulprits())
                    r.add(u.getId());
731
                culprits = ImmutableSortedSet.copyOf(r);
K
kohsuke 已提交
732
                CheckPoint.CULPRITS_DETERMINED.report();
733 734
            }
        }
735 736

        public void cleanUp(BuildListener listener) throws Exception {
K
Kohsuke Kawaguchi 已提交
737 738 739 740
            if (lease!=null) {
                lease.release();
                lease = null;
            }
741 742
            BuildTrigger.execute(AbstractBuild.this, listener);
            buildEnvironments = null;
743
        }
744

745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
        /**
         * @deprecated as of 1.356
         *      Use {@link #performAllBuildSteps(BuildListener, Map, boolean)}
         */
        protected final void performAllBuildStep(BuildListener listener, Map<?,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
            performAllBuildSteps(listener,buildSteps.values(),phase);
        }

        protected final boolean performAllBuildSteps(BuildListener listener, Map<?,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
            return performAllBuildSteps(listener,buildSteps.values(),phase);
        }

        /**
         * @deprecated as of 1.356
         *      Use {@link #performAllBuildSteps(BuildListener, Iterable, boolean)}
         */
        protected final void performAllBuildStep(BuildListener listener, Iterable<? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
            performAllBuildSteps(listener,buildSteps,phase);
763 764 765 766 767 768 769 770
        }

        /**
         * Runs all the given build steps, even if one of them fail.
         *
         * @param phase
         *      true for the post build processing, and false for the final "run after finished" execution.
         */
771
        protected final boolean performAllBuildSteps(BuildListener listener, Iterable<? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
772
            boolean r = true;
773 774
            for (BuildStep bs : buildSteps) {
                if ((bs instanceof Publisher && ((Publisher)bs).needsToRunAfterFinalized()) ^ phase)
775
                    try {
776 777 778 779
                        if (!perform(bs,listener)) {
                            LOGGER.fine(MessageFormat.format("{0} : {1} failed", AbstractBuild.this.toString(), bs));
                            r = false;
                        }
780
                    } catch (Exception e) {
781 782
                        String msg = "Publisher " + bs.getClass().getName() + " aborted due to exception";
                        e.printStackTrace(listener.error(msg));
783
                        LOGGER.log(WARNING, msg, e);
784
                        setResult(Result.FAILURE);
785
                    }
K
kohsuke 已提交
786
            }
787
            return r;
K
kohsuke 已提交
788 789 790 791 792 793 794 795 796 797 798
        }

        /**
         * Calls a build step.
         */
        protected final boolean perform(BuildStep bs, BuildListener listener) throws InterruptedException, IOException {
            BuildStepMonitor mon;
            try {
                mon = bs.getRequiredMonitorService();
            } catch (AbstractMethodError e) {
                mon = BuildStepMonitor.BUILD;
799
            }
800
            Result oldResult = AbstractBuild.this.getResult();
801 802 803
            for (BuildStepListener bsl : BuildStepListener.all()) {
                bsl.started(AbstractBuild.this, bs, listener);
            }
804
            boolean canContinue = mon.perform(bs, AbstractBuild.this, launcher, listener);
805 806 807
            for (BuildStepListener bsl : BuildStepListener.all()) {
                bsl.finished(AbstractBuild.this, bs, listener, canContinue);
            }
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
            Result newResult = AbstractBuild.this.getResult();
            if (newResult != oldResult) {
                String buildStepName = getBuildStepName(bs);
                listener.getLogger().format("Build step '%s' changed build result to %s%n", buildStepName, newResult);
            }
            if (!canContinue) {
                String buildStepName = getBuildStepName(bs);
                listener.getLogger().format("Build step '%s' marked build as failure%n", buildStepName);
            }
            return canContinue;
        }

        private String getBuildStepName(BuildStep bs) {
            if (bs instanceof Describable<?>) {
                return ((Describable<?>) bs).getDescriptor().getDisplayName();
            } else {
                return bs.getClass().getSimpleName();
            }
826
        }
827 828

        protected final boolean preBuild(BuildListener listener,Map<?,? extends BuildStep> steps) {
829 830 831 832
            return preBuild(listener,steps.values());
        }

        protected final boolean preBuild(BuildListener listener,Collection<? extends BuildStep> steps) {
833 834 835 836
            return preBuild(listener,(Iterable<? extends BuildStep>)steps);
        }

        protected final boolean preBuild(BuildListener listener,Iterable<? extends BuildStep> steps) {
837
            for (BuildStep bs : steps)
838 839
                if (!bs.prebuild(AbstractBuild.this,listener)) {
                    LOGGER.fine(MessageFormat.format("{0} : {1} failed", AbstractBuild.this.toString(), bs));
840
                    return false;
841
                }
842 843
            return true;
        }
844
    }
K
kohsuke 已提交
845

846 847 848 849 850 851 852 853 854 855 856 857 858 859
    /**
     * get the fingerprints associated with this build
     *
     * @return never null
     */
    @Exported(name = "fingerprint", inline = true, visibility = -1)
    public Collection<Fingerprint> getBuildFingerprints() {
        FingerprintAction fingerprintAction = getAction(FingerprintAction.class);
        if (fingerprintAction != null) {
            return fingerprintAction.getFingerprints().values();
        }
        return Collections.<Fingerprint>emptyList();
    }

860 861 862 863 864
	/*
     * No need to to lock the entire AbstractBuild on change set calculcation
     */
    private transient Object changeSetLock = new Object();
    
865 866 867 868 869
    /**
     * Gets the changes incorporated into this build.
     *
     * @return never null.
     */
K
kohsuke 已提交
870
    @Exported
871
    public ChangeLogSet<? extends Entry> getChangeSet() {
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
        synchronized (changeSetLock) {
            if (scm==null) {
                // for historical reason, null means CVS.
                try {
                    Class<?> c = Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.scm.CVSChangeLogParser");
                    scm = (ChangeLogParser)c.newInstance();
                } catch (ClassNotFoundException e) {
                    // if CVS isn't available, fall back to something non-null.
                    scm = NullChangeLogParser.INSTANCE;
                } catch (InstantiationException e) {
                    scm = NullChangeLogParser.INSTANCE;
                    throw (Error)new InstantiationError().initCause(e);
                } catch (IllegalAccessException e) {
                    scm = NullChangeLogParser.INSTANCE;
                    throw (Error)new IllegalAccessError().initCause(e);
                }
888 889
            }
        }
890

891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
        ChangeLogSet<? extends Entry> cs = null;
        if (changeSet!=null)
            cs = changeSet.get();

        if (cs==null)
            cs = calcChangeSet();

        // defensive check. if the calculation fails (such as through an exception),
        // set a dummy value so that it'll work the next time. the exception will
        // be still reported, giving the plugin developer an opportunity to fix it.
        if (cs==null)
            cs = ChangeLogSet.createEmpty(this);

        changeSet = new WeakReference<ChangeLogSet<? extends Entry>>(cs);
        return cs;
906 907 908 909 910 911 912 913 914 915 916 917
    }

    /**
     * Returns true if the changelog is already computed.
     */
    public boolean hasChangeSetComputed() {
        File changelogFile = new File(getRootDir(), "changelog.xml");
        return changelogFile.exists();
    }

    private ChangeLogSet<? extends Entry> calcChangeSet() {
        File changelogFile = new File(getRootDir(), "changelog.xml");
918
        if (!changelogFile.exists())
919
            return ChangeLogSet.createEmpty(this);
920 921 922 923

        try {
            return scm.parse(this,changelogFile);
        } catch (IOException e) {
924
            LOGGER.log(WARNING, "Failed to parse "+changelogFile,e);
925
        } catch (SAXException e) {
926
            LOGGER.log(WARNING, "Failed to parse "+changelogFile,e);
927
        }
928
        return ChangeLogSet.createEmpty(this);
929 930 931
    }

    @Override
K
kohsuke 已提交
932 933
    public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
        EnvVars env = super.getEnvironment(log);
934 935 936
        FilePath ws = getWorkspace();
        if (ws!=null)   // if this is done very early on in the build, workspace may not be decided yet. see HUDSON-3997
            env.put("WORKSPACE", ws.getRemote());
937

938
        project.getScm().buildEnvVars(this,env);
939

940 941 942
        if (buildEnvironments!=null)
            for (Environment e : buildEnvironments)
                e.buildEnvVars(env);
943

944
        for (EnvironmentContributingAction a : getActions(EnvironmentContributingAction.class))
945 946
            a.buildEnvVars(this,env);

947
        EnvVars.resolve(env);
948

949 950
        return env;
    }
K
kohsuke 已提交
951

K
Kohsuke Kawaguchi 已提交
952 953
    /**
     * During the build, expose the environments contributed by {@link BuildWrapper}s and others.
954 955 956 957 958 959 960
     * 
     * <p>
     * Since 1.444, executor thread that's doing the build can access mutable underlying list,
     * which allows the caller to add/remove environments. The recommended way of adding
     * environment is through {@link BuildWrapper}, but this might be handy for build steps
     * who wants to expose additional environment variables to the rest of the build.
     * 
K
Kohsuke Kawaguchi 已提交
961 962 963
     * @return can be empty list, but never null. Immutable.
     * @since 1.437
     */
964
    public EnvironmentList getEnvironments() {
965 966 967 968 969 970
        Executor e = Executor.currentExecutor();
        if (e!=null && e.getCurrentExecutable()==this) {
            if (buildEnvironments==null)    buildEnvironments = new ArrayList<Environment>();
            return new EnvironmentList(buildEnvironments); 
        }
        
971
        return new EnvironmentList(buildEnvironments==null ? Collections.<Environment>emptyList() : ImmutableList.copyOf(buildEnvironments));
K
Kohsuke Kawaguchi 已提交
972 973
    }

974
    public Calendar due() {
975
        return getTimestamp();
976
    }
977
      
978
    @SuppressWarnings("deprecation")
979 980 981
    public List<Action> getPersistentActions(){
        return super.getActions();
    }
982

983 984
    /**
     * Builds up a set of variable names that contain sensitive values that
A
alanharder 已提交
985
     * should not be exposed. The expectation is that this set is populated with
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
     * keys returned by {@link #getBuildVariables()} that should have their
     * values masked for display purposes.
     *
     * @since 1.378
     */
    public Set<String> getSensitiveBuildVariables() {
        Set<String> s = new HashSet<String>();

        ParametersAction parameters = getAction(ParametersAction.class);
        if (parameters != null) {
            for (ParameterValue p : parameters) {
                if (p.isSensitive()) {
                    s.add(p.getName());
                }
            }
        }

        // Allow BuildWrappers to determine if any of their data is sensitive
        if (project instanceof BuildableItemWithBuildWrappers) {
            for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList()) {
                bw.makeSensitiveBuildVariables(this, s);
            }
        }
        
        return s;
    }

1013 1014 1015 1016 1017 1018
    /**
     * Provides additional variables and their values to {@link Builder}s.
     *
     * <p>
     * This mechanism is used by {@link MatrixConfiguration} to pass
     * the configuration values to the current build. It is up to
J
Jesse Glick 已提交
1019
     * {@link Builder}s to decide whether they want to recognize the values
1020 1021
     * or how to use them.
     *
1022 1023 1024 1025 1026
     * <p>
     * This also includes build parameters if a build is parameterized.
     *
     * @return
     *      The returned map is mutable so that subtypes can put more values.
1027 1028
     */
    public Map<String,String> getBuildVariables() {
1029 1030 1031
        Map<String,String> r = new HashMap<String, String>();

        ParametersAction parameters = getAction(ParametersAction.class);
1032
        if (parameters!=null) {
1033 1034 1035
            // this is a rather round about way of doing this...
            for (ParameterValue p : parameters) {
                String v = p.createVariableResolver(this).resolve(p.getName());
1036
                if (v!=null) r.put(p.getName(),v);
1037 1038
            }
        }
1039 1040 1041 1042 1043 1044 1045

        // allow the BuildWrappers to contribute additional build variables
        if (project instanceof BuildableItemWithBuildWrappers) {
            for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList())
                bw.makeBuildVariables(this,r);
        }

1046 1047 1048
        for (BuildVariableContributor bvc : BuildVariableContributor.all())
            bvc.buildVariablesFor(this,r);

1049
        return r;
1050
    }
1051 1052 1053 1054 1055 1056 1057 1058

    /**
     * Creates {@link VariableResolver} backed by {@link #getBuildVariables()}.
     */
    public final VariableResolver<String> getBuildVariableResolver() {
        return new VariableResolver.ByMap<String>(getBuildVariables());
    }

1059 1060 1061
    /**
     * Gets {@link AbstractTestResultAction} associated with this build if any.
     */
1062 1063 1064
    public AbstractTestResultAction getTestResultAction() {
        return getAction(AbstractTestResultAction.class);
    }
1065

1066 1067 1068 1069 1070 1071 1072
    /**
     * Gets {@link AggregatedTestResultAction} associated with this build if any.
     */
    public AggregatedTestResultAction getAggregatedTestResultAction() {
        return getAction(AggregatedTestResultAction.class);
    }

K
kohsuke 已提交
1073 1074 1075 1076 1077
    /**
     * Invoked by {@link Executor} to performs a build.
     */
    public abstract void run();

1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
//
//
// fingerprint related stuff
//
//

    @Override
    public String getWhyKeepLog() {
        // if any of the downstream project is configured with 'keep dependency component',
        // we need to keep this log
K
kohsuke 已提交
1088
        OUTER:
K
kohsuke 已提交
1089
        for (AbstractProject<?,?> p : getParent().getDownstreamProjects()) {
1090
            if (!p.isKeepDependencies()) continue;
K
kohsuke 已提交
1091

K
kohsuke 已提交
1092
            AbstractBuild<?,?> fb = p.getFirstBuild();
1093
            if (fb==null)        continue; // no active record
K
kohsuke 已提交
1094 1095

            // is there any active build that depends on us?
1096
            for (int i : getDownstreamRelationship(p).listNumbersReverse()) {
K
kohsuke 已提交
1097 1098 1099
                // TODO: this is essentially a "find intersection between two sparse sequences"
                // and we should be able to do much better.

1100
                if (i<fb.getNumber())
K
kohsuke 已提交
1101 1102
                    continue OUTER; // all the other records are younger than the first record, so pointless to search.

K
kohsuke 已提交
1103
                AbstractBuild<?,?> b = p.getBuildByNumber(i);
1104
                if (b!=null)
K
kohsuke 已提交
1105
                    return Messages.AbstractBuild_KeptBecause(b);
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
            }
        }

        return super.getWhyKeepLog();
    }

    /**
     * Gets the dependency relationship from this build (as the source)
     * and that project (as the sink.)
     *
     * @return
     *      range of build numbers that represent which downstream builds are using this build.
1118
     *      The range will be empty if no build of that project matches this (or there is no {@link FingerprintAction}), but it'll never be null.
1119
     */
1120 1121 1122 1123
    public RangeSet getDownstreamRelationship(AbstractProject that) {
        RangeSet rs = new RangeSet();

        FingerprintAction f = getAction(FingerprintAction.class);
1124
        if (f==null)     return rs;
1125 1126 1127

        // look for fingerprints that point to this build as the source, and merge them all
        for (Fingerprint e : f.getFingerprints().values()) {
1128 1129 1130 1131

            if (upstreamCulprits) {
                // With upstreamCulprits, we allow downstream relationships
                // from intermediate jobs
1132
                rs.add(e.getRangeSet(that));
1133 1134
            } else {
                BuildPtr o = e.getOriginal();
1135
                if (o!=null && o.is(this))
1136 1137
                    rs.add(e.getRangeSet(that));
            }
1138 1139 1140 1141
        }

        return rs;
    }
1142

K
kohsuke 已提交
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
    /**
     * Works like {@link #getDownstreamRelationship(AbstractProject)} but returns
     * the actual build objects, in ascending order.
     * @since 1.150
     */
    public Iterable<AbstractBuild<?,?>> getDownstreamBuilds(final AbstractProject<?,?> that) {
        final Iterable<Integer> nums = getDownstreamRelationship(that).listNumbers();

        return new Iterable<AbstractBuild<?, ?>>() {
            public Iterator<AbstractBuild<?, ?>> iterator() {
1153 1154 1155 1156 1157 1158
                return Iterators.removeNull(
                    new AdaptedIterator<Integer,AbstractBuild<?,?>>(nums) {
                        protected AbstractBuild<?, ?> adapt(Integer item) {
                            return that.getBuildByNumber(item);
                        }
                    });
K
kohsuke 已提交
1159 1160 1161 1162
            }
        };
    }

1163 1164 1165 1166 1167 1168
    /**
     * Gets the dependency relationship from this build (as the sink)
     * and that project (as the source.)
     *
     * @return
     *      Build number of the upstream build that feed into this build,
1169
     *      or -1 if no record is available (for example if there is no {@link FingerprintAction}, even if there is an {@link Cause.UpstreamCause}).
1170
     */
1171 1172
    public int getUpstreamRelationship(AbstractProject that) {
        FingerprintAction f = getAction(FingerprintAction.class);
1173
        if (f==null)     return -1;
1174 1175 1176 1177 1178

        int n = -1;

        // look for fingerprints that point to the given project as the source, and merge them all
        for (Fingerprint e : f.getFingerprints().values()) {
1179 1180 1181 1182
            if (upstreamCulprits) {
                // With upstreamCulprits, we allow upstream relationships
                // from intermediate jobs
                Fingerprint.RangeSet rangeset = e.getRangeSet(that);
1183
                if (!rangeset.isEmpty()) {
1184 1185 1186 1187
                    n = Math.max(n, rangeset.listNumbersReverse().iterator().next());
                }
            } else {
                BuildPtr o = e.getOriginal();
1188
                if (o!=null && o.belongsTo(that))
1189 1190
                    n = Math.max(n,o.getNumber());
            }
1191 1192 1193 1194
        }

        return n;
    }
1195

K
kohsuke 已提交
1196 1197 1198 1199 1200 1201 1202 1203
    /**
     * Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the
     * actual build object.
     *
     * @return
     *      null if no such upstream build was found, or it was found but the
     *      build record is already lost.
     */
1204
    public AbstractBuild<?,?> getUpstreamRelationshipBuild(AbstractProject<?,?> that) {
K
kohsuke 已提交
1205
        int n = getUpstreamRelationship(that);
1206
        if (n==-1)   return null;
K
kohsuke 已提交
1207 1208 1209
        return that.getBuildByNumber(n);
    }

1210 1211 1212 1213 1214 1215
    /**
     * Gets the downstream builds of this build, which are the builds of the
     * downstream projects that use artifacts of this build.
     *
     * @return
     *      For each project with fingerprinting enabled, returns the range
1216
     *      of builds (which can be empty if no build uses the artifact from this build or downstream is not {@link AbstractProject#isFingerprintConfigured}.)
1217 1218 1219 1220
     */
    public Map<AbstractProject,RangeSet> getDownstreamBuilds() {
        Map<AbstractProject,RangeSet> r = new HashMap<AbstractProject,RangeSet>();
        for (AbstractProject p : getParent().getDownstreamProjects()) {
1221
            if (p.isFingerprintConfigured())
1222 1223 1224 1225
                r.put(p,getDownstreamRelationship(p));
        }
        return r;
    }
1226

1227 1228 1229
    /**
     * Gets the upstream builds of this build, which are the builds of the
     * upstream projects whose artifacts feed into this build.
1230
     * @return empty if there is no {@link FingerprintAction} (even if there is an {@link Cause.UpstreamCause})
K
kohsuke 已提交
1231
     * @see #getTransitiveUpstreamBuilds()
1232 1233
     */
    public Map<AbstractProject,Integer> getUpstreamBuilds() {
K
kohsuke 已提交
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245
        return _getUpstreamBuilds(getParent().getUpstreamProjects());
    }

    /**
     * Works like {@link #getUpstreamBuilds()}  but also includes all the transitive
     * dependencies as well.
     */
    public Map<AbstractProject,Integer> getTransitiveUpstreamBuilds() {
        return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects());
    }

    private Map<AbstractProject, Integer> _getUpstreamBuilds(Collection<AbstractProject> projects) {
1246
        Map<AbstractProject,Integer> r = new HashMap<AbstractProject,Integer>();
K
kohsuke 已提交
1247
        for (AbstractProject p : projects) {
1248
            int n = getUpstreamRelationship(p);
1249
            if (n>=0)
1250 1251 1252 1253 1254
                r.put(p,n);
        }
        return r;
    }

1255 1256
    /**
     * Gets the changes in the dependency between the given build and this build.
1257
     * @return empty if there is no {@link FingerprintAction}
1258 1259
     */
    public Map<AbstractProject,DependencyChange> getDependencyChanges(AbstractBuild from) {
1260
        if (from==null)             return Collections.emptyMap(); // make it easy to call this from views
1261 1262
        FingerprintAction n = this.getAction(FingerprintAction.class);
        FingerprintAction o = from.getAction(FingerprintAction.class);
1263
        if (n==null || o==null)     return Collections.emptyMap();
1264

1265 1266
        Map<AbstractProject,Integer> ndep = n.getDependencies(true);
        Map<AbstractProject,Integer> odep = o.getDependencies(true);
1267 1268 1269 1270 1271 1272 1273

        Map<AbstractProject,DependencyChange> r = new HashMap<AbstractProject,DependencyChange>();

        for (Map.Entry<AbstractProject,Integer> entry : odep.entrySet()) {
            AbstractProject p = entry.getKey();
            Integer oldNumber = entry.getValue();
            Integer newNumber = ndep.get(p);
1274
            if (newNumber!=null && oldNumber.compareTo(newNumber)<0) {
1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311
                r.put(p,new DependencyChange(p,oldNumber,newNumber));
            }
        }

        return r;
    }

    /**
     * Represents a change in the dependency.
     */
    public static final class DependencyChange {
        /**
         * The dependency project.
         */
        public final AbstractProject project;
        /**
         * Version of the dependency project used in the previous build.
         */
        public final int fromId;
        /**
         * {@link Build} object for {@link #fromId}. Can be null if the log is gone.
         */
        public final AbstractBuild from;
        /**
         * Version of the dependency project used in this build.
         */
        public final int toId;

        public final AbstractBuild to;

        public DependencyChange(AbstractProject<?,?> project, int fromId, int toId) {
            this.project = project;
            this.fromId = fromId;
            this.toId = toId;
            this.from = project.getBuildByNumber(fromId);
            this.to = project.getBuildByNumber(toId);
        }
1312 1313 1314 1315 1316

        /**
         * Gets the {@link AbstractBuild} objects (fromId,toId].
         * <p>
         * This method returns all such available builds in the ascending order
1317
         * of IDs, but due to log rotations, some builds may be already unavailable.
1318 1319 1320 1321 1322
         */
        public List<AbstractBuild> getBuilds() {
            List<AbstractBuild> r = new ArrayList<AbstractBuild>();

            AbstractBuild<?,?> b = (AbstractBuild)project.getNearestBuild(fromId);
1323
            if (b!=null && b.getNumber()==fromId)
1324 1325
                b = b.getNextBuild(); // fromId exclusive

1326
            while (b!=null && b.getNumber()<=toId) {
1327 1328 1329 1330 1331 1332
                r.add(b);
                b = b.getNextBuild();
            }

            return r;
        }
1333
    }
1334

1335 1336 1337 1338
    //
    // web methods
    //

1339 1340 1341 1342 1343 1344 1345 1346
    /**
     * @deprecated as of 1.489
     *      Use {@link #doStop()}
     */
    public void doStop(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        doStop().generateResponse(req,rsp,this);
    }

K
kohsuke 已提交
1347 1348 1349 1350 1351
    /**
     * Stops this build if it's still going.
     *
     * If we use this/executor/stop URL, it causes 404 if the build is already killed,
     * as {@link #getExecutor()} returns null.
C
Christoph Kutzinski 已提交
1352 1353
     * 
     * @since 1.489
K
kohsuke 已提交
1354
     */
J
Jesse Glick 已提交
1355
    @RequirePOST
1356
    public synchronized HttpResponse doStop() throws IOException, ServletException {
K
kohsuke 已提交
1357
        Executor e = getExecutor();
1358 1359
        if (e==null)
            e = getOneOffExecutor();
1360
        if (e!=null)
1361
            return e.doStop();
K
kohsuke 已提交
1362 1363
        else
            // nothing is building
1364
            return HttpResponses.forwardToPreviousPage();
K
kohsuke 已提交
1365
    }
1366 1367

    private static final Logger LOGGER = Logger.getLogger(AbstractBuild.class.getName());
1368
}
1369 1370