PageRenderTime 70ms CodeModel.GetById 18ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 1ms

/hudson-core/src/main/java/hudson/model/AbstractBuild.java

http://github.com/hudson/hudson
Java | 1140 lines | 620 code | 142 blank | 378 comment | 113 complexity | 7d9c0be91d36032b29de402a8e558589 MD5 | raw file
   1/*
   2 * The MIT License
   3 *
   4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Yahoo! Inc., CloudBees, Inc.
   5 *
   6 * Permission is hereby granted, free of charge, to any person obtaining a copy
   7 * of this software and associated documentation files (the "Software"), to deal
   8 * in the Software without restriction, including without limitation the rights
   9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 * copies of the Software, and to permit persons to whom the Software is
  11 * furnished to do so, subject to the following conditions:
  12 *
  13 * The above copyright notice and this permission notice shall be included in
  14 * all copies or substantial portions of the Software.
  15 *
  16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22 * THE SOFTWARE.
  23 */
  24package hudson.model;
  25
  26import hudson.AbortException;
  27import hudson.EnvVars;
  28import hudson.Functions;
  29import hudson.Launcher;
  30import hudson.Util;
  31import hudson.FilePath;
  32import hudson.console.AnnotatedLargeText;
  33import hudson.console.ExpandableDetailsNote;
  34import hudson.slaves.WorkspaceList;
  35import hudson.slaves.NodeProperty;
  36import hudson.slaves.WorkspaceList.Lease;
  37import hudson.matrix.MatrixConfiguration;
  38import hudson.model.Fingerprint.BuildPtr;
  39import hudson.model.Fingerprint.RangeSet;
  40import hudson.model.listeners.SCMListener;
  41import hudson.scm.ChangeLogParser;
  42import hudson.scm.ChangeLogSet;
  43import hudson.scm.ChangeLogSet.Entry;
  44import hudson.scm.SCM;
  45import hudson.scm.NullChangeLogParser;
  46import hudson.tasks.BuildStep;
  47import hudson.tasks.BuildWrapper;
  48import hudson.tasks.Builder;
  49import hudson.tasks.Fingerprinter.FingerprintAction;
  50import hudson.tasks.Publisher;
  51import hudson.tasks.BuildStepMonitor;
  52import hudson.tasks.BuildTrigger;
  53import hudson.tasks.test.AbstractTestResultAction;
  54import hudson.util.AdaptedIterator;
  55import hudson.util.Iterators;
  56import hudson.util.LogTaskListener;
  57import hudson.util.VariableResolver;
  58import org.kohsuke.stapler.Stapler;
  59import org.kohsuke.stapler.StaplerRequest;
  60import org.kohsuke.stapler.StaplerResponse;
  61import org.kohsuke.stapler.export.Exported;
  62import org.xml.sax.SAXException;
  63
  64import javax.servlet.ServletException;
  65import java.io.File;
  66import java.io.IOException;
  67import java.io.StringWriter;
  68import java.util.AbstractSet;
  69import java.util.ArrayList;
  70import java.util.Calendar;
  71import java.util.Collection;
  72import java.util.Collections;
  73import java.util.HashMap;
  74import java.util.HashSet;
  75import java.util.Iterator;
  76import java.util.List;
  77import java.util.Map;
  78import java.util.Set;
  79import java.util.logging.Level;
  80import java.util.logging.Logger;
  81
  82/**
  83 * Base implementation of {@link Run}s that build software.
  84 *
  85 * For now this is primarily the common part of {@link Build} and MavenBuild.
  86 *
  87 * @author Kohsuke Kawaguchi
  88 * @see AbstractProject
  89 */
  90public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Run<P,R> implements Queue.Executable {
  91
  92    /**
  93     * Set if we want the blame information to flow from upstream to downstream build.
  94     */
  95    private static final boolean upstreamCulprits = Boolean.getBoolean("hudson.upstreamCulprits");
  96
  97    /**
  98     * Name of the slave this project was built on.
  99     * Null or "" if built by the master. (null happens when we read old record that didn't have this information.)
 100     */
 101    private String builtOn;
 102
 103    /**
 104     * The file path on the node that performed a build. Kept as a string since {@link FilePath} is not serializable into XML.
 105     * @since 1.319
 106     */
 107    private String workspace;
 108
 109    /**
 110     * Version of Hudson that built this.
 111     */
 112    private String hudsonVersion;
 113
 114    /**
 115     * SCM used for this build.
 116     * Maybe null, for historical reason, in which case CVS is assumed.
 117     */
 118    private ChangeLogParser scm;
 119
 120    /**
 121     * Changes in this build.
 122     */
 123    private volatile transient ChangeLogSet<? extends Entry> changeSet;
 124
 125    /**
 126     * Cumulative list of people who contributed to the build problem.
 127     *
 128     * <p>
 129     * This is a list of {@link User#getId() user ids} who made a change
 130     * since the last non-broken build. Can be null (which should be
 131     * treated like empty set), because of the compatibility.
 132     *
 133     * <p>
 134     * This field is semi-final --- once set the value will never be modified.
 135     *
 136     * @since 1.137
 137     */
 138    private volatile Set<String> culprits;
 139
 140    /**
 141     * During the build this field remembers {@link BuildWrapper.Environment}s created by
 142     * {@link BuildWrapper}. This design is bit ugly but forced due to compatibility.
 143     */
 144    protected transient List<Environment> buildEnvironments;
 145
 146    protected AbstractBuild(P job) throws IOException {
 147        super(job);
 148    }
 149
 150    protected AbstractBuild(P job, Calendar timestamp) {
 151        super(job, timestamp);
 152    }
 153
 154    protected AbstractBuild(P project, File buildDir) throws IOException {
 155        super(project, buildDir);
 156    }
 157
 158    public final P getProject() {
 159        return getParent();
 160    }
 161
 162    /**
 163     * Returns a {@link Slave} on which this build was done.
 164     *
 165     * @return
 166     *      null, for example if the slave that this build run no longer exists.
 167     */
 168    public Node getBuiltOn() {
 169        if (builtOn==null || builtOn.equals(""))
 170            return Hudson.getInstance();
 171        else
 172            return Hudson.getInstance().getNode(builtOn);
 173    }
 174
 175    /**
 176     * Returns the name of the slave it was built on; null or "" if built by the master.
 177     * (null happens when we read old record that didn't have this information.)
 178     */
 179    @Exported(name="builtOn")
 180    public String getBuiltOnStr() {
 181        return builtOn;
 182    }
 183
 184    /**
 185     * Used to render the side panel "Back to project" link.
 186     *
 187     * <p>
 188     * In a rare situation where a build can be reached from multiple paths,
 189     * returning different URLs from this method based on situations might
 190     * be desirable.
 191     *
 192     * <p>
 193     * If you override this method, you'll most likely also want to override
 194     * {@link #getDisplayName()}.
 195     */
 196    public String getUpUrl() {
 197        return Functions.getNearestAncestorUrl(Stapler.getCurrentRequest(),getParent())+'/';
 198    }
 199
 200    /**
 201     * Gets the directory where this build is being built.
 202     *
 203     * <p>
 204     * Note to implementors: to control where the workspace is created, override
 205     * {@link AbstractRunner#decideWorkspace(Node,WorkspaceList)}.
 206     *
 207     * @return
 208     *      null if the workspace is on a slave that's not connected. Note that once the build is completed,
 209     *      the workspace may be used to build something else, so the value returned from this method may
 210     *      no longer show a workspace as it was used for this build.
 211     * @since 1.319
 212     */
 213    public final FilePath getWorkspace() {
 214        if (workspace==null) return null;
 215        Node n = getBuiltOn();
 216        if (n==null) return null;
 217        return n.createPath(workspace);
 218    }
 219
 220    /**
 221     * Normally, a workspace is assigned by {@link Runner}, but this lets you set the workspace in case
 222     * {@link AbstractBuild} is created without a build.
 223     */
 224    protected void setWorkspace(FilePath ws) {
 225        this.workspace = ws.getRemote();
 226    }
 227
 228    /**
 229     * Returns the root directory of the checked-out module.
 230     * <p>
 231     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
 232     * and so on exists.
 233     */
 234    public final FilePath getModuleRoot() {
 235        FilePath ws = getWorkspace();
 236        if (ws==null)    return null;
 237        return getParent().getScm().getModuleRoot(ws,this);
 238    }
 239
 240    /**
 241     * Returns the root directories of all checked-out modules.
 242     * <p>
 243     * Some SCMs support checking out multiple modules into the same workspace.
 244     * In these cases, the returned array will have a length greater than one.
 245     * @return The roots of all modules checked out from the SCM.
 246     */
 247    public FilePath[] getModuleRoots() {
 248        FilePath ws = getWorkspace();
 249        if (ws==null)    return null;
 250        return getParent().getScm().getModuleRoots(ws,this);
 251    }
 252
 253    /**
 254     * List of users who committed a change since the last non-broken build till now.
 255     *
 256     * <p>
 257     * This list at least always include people who made changes in this build, but
 258     * if the previous build was a failure it also includes the culprit list from there.
 259     * Culprits of unstable build are also included
 260     * see <a href="http://issues.hudson-ci.org/browse/HUDSON-4617">HUDSON-4617</a> for details
 261     * @return
 262     *      can be empty but never null.
 263     */
 264    @Exported
 265    public Set<User> getCulprits() {
 266        if (culprits==null) {
 267            Set<User> r = new HashSet<User>();
 268            R p = getPreviousCompletedBuild();
 269            if (p !=null && isBuilding()) {
 270                Result pr = p.getResult();
 271                if (pr!=null && pr.isWorseOrEqualTo(Result.UNSTABLE)) {
 272                    // we are still building, so this is just the current latest information,
 273                    // but we seems to be failing so far, so inherit culprits from the previous build.
 274                    // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record
 275                    // this information
 276                    r.addAll(p.getCulprits());
 277                }
 278            }
 279            for (Entry e : getChangeSet())
 280                r.add(e.getAuthor());
 281
 282            if (upstreamCulprits) {
 283                // If we have dependencies since the last successful build, add their authors to our list
 284                R previousBuild = getPreviousSuccessfulBuild();
 285                if (previousBuild != null) {
 286                    Map <AbstractProject,AbstractBuild.DependencyChange> depmap = getDependencyChanges(previousBuild);
 287                    for (AbstractBuild.DependencyChange dep : depmap.values()) {
 288                        for (AbstractBuild<?,?> b : dep.getBuilds()) {
 289                            for (Entry entry : b.getChangeSet()) {
 290                                r.add(entry.getAuthor());
 291                            }
 292                        }
 293                    }
 294                }
 295            }
 296
 297            return r;
 298        }
 299
 300        return new AbstractSet<User>() {
 301            public Iterator<User> iterator() {
 302                return new AdaptedIterator<String,User>(culprits.iterator()) {
 303                    protected User adapt(String id) {
 304                        return User.get(id);
 305                    }
 306                };
 307            }
 308
 309            public int size() {
 310                return culprits.size();
 311            }
 312        };
 313    }
 314
 315    /**
 316     * Returns true if this user has made a commit to this build.
 317     *
 318     * @since 1.191
 319     */
 320    public boolean hasParticipant(User user) {
 321        for (ChangeLogSet.Entry e : getChangeSet())
 322            if (e.getAuthor()==user)
 323                return true;
 324        return false;
 325    }
 326
 327    /**
 328     * Gets the version of Hudson that was used to build this job.
 329     *
 330     * @since 1.246
 331     */
 332    public String getHudsonVersion() {
 333        return hudsonVersion;
 334    }
 335
 336    @Override
 337    public synchronized void delete() throws IOException {
 338        // Need to check if deleting this build affects lastSuccessful/lastStable symlinks
 339        R lastSuccessful = getProject().getLastSuccessfulBuild(),
 340          lastStable = getProject().getLastStableBuild();
 341
 342        super.delete();
 343
 344        try {
 345            if (lastSuccessful == this)
 346                updateSymlink("lastSuccessful", getProject().getLastSuccessfulBuild());
 347            if (lastStable == this)
 348                updateSymlink("lastStable", getProject().getLastStableBuild());
 349        } catch (InterruptedException ex) {
 350            LOGGER.warning("Interrupted update of lastSuccessful/lastStable symlinks for "
 351                           + getProject().getDisplayName());
 352            // handle it later
 353            Thread.currentThread().interrupt();
 354        }
 355    }
 356
 357    private void updateSymlink(String name, AbstractBuild<?,?> newTarget) throws InterruptedException {
 358        if (newTarget != null)
 359            newTarget.createSymlink(new LogTaskListener(LOGGER, Level.WARNING), name);
 360        else
 361            new File(getProject().getBuildDir(), "../"+name).delete();
 362    }
 363
 364    private void createSymlink(TaskListener listener, String name) throws InterruptedException {
 365        Util.createSymlink(getProject().getBuildDir(),"builds/"+getId(),"../"+name,listener);
 366    }
 367
 368    protected abstract class AbstractRunner extends Runner {
 369        /**
 370         * Since configuration can be changed while a build is in progress,
 371         * create a launcher once and stick to it for the entire build duration.
 372         */
 373        protected Launcher launcher;
 374
 375        /**
 376         * Output/progress of this build goes here.
 377         */
 378        protected BuildListener listener;
 379
 380        /**
 381         * Returns the current {@link Node} on which we are buildling.
 382         */
 383        protected final Node getCurrentNode() {
 384            return Executor.currentExecutor().getOwner().getNode();
 385        }
 386
 387        /**
 388         * Allocates the workspace from {@link WorkspaceList}.
 389         *
 390         * @param n
 391         *      Passed in for the convenience. The node where the build is running.
 392         * @param wsl
 393         *      Passed in for the convenience. The returned path must be registered to this object.
 394         */
 395        protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws InterruptedException, IOException {
 396            // TODO: this cast is indicative of abstraction problem
 397            return wsl.allocate(n.getWorkspaceFor((TopLevelItem)getProject()));
 398        }
 399
 400        public Result run(BuildListener listener) throws Exception {
 401            Node node = getCurrentNode();
 402            assert builtOn==null;
 403            builtOn = node.getNodeName();
 404            hudsonVersion = Hudson.VERSION;
 405            this.listener = listener;
 406
 407            launcher = createLauncher(listener);
 408            if (!Hudson.getInstance().getNodes().isEmpty())
 409                listener.getLogger().println(node instanceof Hudson ? Messages.AbstractBuild_BuildingOnMaster() : Messages.AbstractBuild_BuildingRemotely(builtOn));
 410
 411            final Lease lease = decideWorkspace(node,Computer.currentComputer().getWorkspaceList());
 412
 413            try {
 414                workspace = lease.path.getRemote();
 415                node.getFileSystemProvisioner().prepareWorkspace(AbstractBuild.this,lease.path,listener);
 416
 417                if (project.isCleanWorkspaceRequired()) {
 418                    listener.getLogger().println("Cleaning the workspace because project is configured to clean the workspace before each build.");
 419                    if (!project.cleanWorkspace()){
 420                        listener.getLogger().println("Workspace cleaning was attempted but SCM blocked the cleaning.");
 421                    }
 422                }
 423                
 424                checkout(listener);
 425
 426                if (!preBuild(listener,project.getProperties()))
 427                    return Result.FAILURE;
 428
 429                Result result = doRun(listener);
 430
 431                Computer c = node.toComputer();
 432                if (c==null || c.isOffline()) {
 433                    // As can be seen in HUDSON-5073, when a build fails because of the slave connectivity problem,
 434                    // error message doesn't point users to the slave. So let's do it here.
 435                    listener.hyperlink("/computer/"+builtOn+"/log","Looks like the node went offline during the build. Check the slave log for the details.");
 436
 437                    // grab the end of the log file. This might not work very well if the slave already
 438                    // starts reconnecting. Fixing this requires a ring buffer in slave logs.
 439                    AnnotatedLargeText<Computer> log = c.getLogText();
 440                    StringWriter w = new StringWriter();
 441                    log.writeHtmlTo(Math.max(0,c.getLogFile().length()-10240),w);
 442
 443                    listener.getLogger().print(ExpandableDetailsNote.encodeTo("details",w.toString()));
 444                    listener.getLogger().println();
 445                }
 446
 447                // kill run-away processes that are left
 448                // use multiple environment variables so that people can escape this massacre by overriding an environment
 449                // variable for some processes
 450                launcher.kill(getCharacteristicEnvVars());
 451
 452                // this is ugly, but for historical reason, if non-null value is returned
 453                // it should become the final result.
 454                if (result==null)    result = getResult();
 455                if (result==null)    result = Result.SUCCESS;
 456
 457                return result;
 458            } finally {
 459                lease.release();
 460                this.listener = null;
 461            }
 462        }
 463
 464        /**
 465         * Creates a {@link Launcher} that this build will use. This can be overridden by derived types
 466         * to decorate the resulting {@link Launcher}.
 467         *
 468         * @param listener
 469         *      Always non-null. Connected to the main build output.
 470         */
 471        protected Launcher createLauncher(BuildListener listener) throws IOException, InterruptedException {
 472            Launcher l = getCurrentNode().createLauncher(listener);
 473
 474            if (project instanceof BuildableItemWithBuildWrappers) {
 475                BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
 476                for (BuildWrapper bw : biwbw.getBuildWrappersList())
 477                    l = bw.decorateLauncher(AbstractBuild.this,l,listener);
 478            }
 479
 480            buildEnvironments = new ArrayList<Environment>();
 481
 482            for (NodeProperty nodeProperty: Hudson.getInstance().getGlobalNodeProperties()) {
 483                Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
 484                if (environment != null) {
 485                    buildEnvironments.add(environment);
 486                }
 487            }
 488
 489            for (NodeProperty nodeProperty: Computer.currentComputer().getNode().getNodeProperties()) {
 490                Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
 491                if (environment != null) {
 492                    buildEnvironments.add(environment);
 493                }
 494            }
 495
 496            return l;
 497        }
 498
 499        private void checkout(BuildListener listener) throws Exception {
 500            for (int retryCount = project.getScmCheckoutRetryCount(); ; retryCount--) {
 501                // for historical reasons, null in the scm field means CVS, so we need to explicitly set this to something
 502                // in case check out fails and leaves a broken changelog.xml behind.
 503                // see http://www.nabble.com/CVSChangeLogSet.parse-yields-SAXParseExceptions-when-parsing-bad-*AccuRev*-changelog.xml-files-td22213663.html
 504                AbstractBuild.this.scm = new NullChangeLogParser();
 505
 506                try {
 507                    if (project.checkout(AbstractBuild.this, launcher, listener,
 508                        new File(getRootDir(), "changelog.xml"))) {
 509                        // check out succeeded
 510                        SCM scm = project.getScm();
 511
 512                        AbstractBuild.this.scm = scm.createChangeLogParser();
 513                        AbstractBuild.this.changeSet = AbstractBuild.this.calcChangeSet();
 514
 515                        for (SCMListener l : Hudson.getInstance().getSCMListeners()) {
 516                            l.onChangeLogParsed(AbstractBuild.this, listener, changeSet);
 517                        }
 518                        return;
 519                    }
 520                } catch (AbortException e) {
 521                    listener.error(e.getMessage());
 522                } catch (IOException e) {
 523                    // checkout error not yet reported
 524                    e.printStackTrace(listener.getLogger());
 525                }
 526                // all attempts failed
 527                if (retryCount == 0){
 528                    throw new RunnerAbortedException();
 529                }
 530                listener.getLogger().println("Retrying after 10 seconds");
 531                Thread.sleep(10000);
 532            }
 533        }
 534
 535        /**
 536         * The portion of a build that is specific to a subclass of {@link AbstractBuild}
 537         * goes here.
 538         *
 539         * @return
 540         *      null to continue the build normally (that means the doRun method
 541         *      itself run successfully)
 542         *      Return a non-null value to abort the build right there with the specified result code.
 543         */
 544        protected abstract Result doRun(BuildListener listener) throws Exception, RunnerAbortedException;
 545
 546        /**
 547         * @see #post(BuildListener)
 548         */
 549        protected abstract void post2(BuildListener listener) throws Exception;
 550
 551        public final void post(BuildListener listener) throws Exception {
 552            try {
 553                post2(listener);
 554                //Resolve issue with invalid symlinks for maven (see http://issues.hudson-ci.org/browse/HUDSON-8340)
 555                if (getResult().isBetterOrEqualTo(Result.UNSTABLE))
 556                    createSymlink(listener, "lastSuccessful");
 557
 558                if (getResult().isBetterOrEqualTo(Result.SUCCESS))
 559                    createSymlink(listener, "lastStable");
 560            } finally {
 561                // update the culprit list
 562                HashSet<String> r = new HashSet<String>();
 563                for (User u : getCulprits())
 564                    r.add(u.getId());
 565                culprits = r;
 566                CheckPoint.CULPRITS_DETERMINED.report();
 567            }
 568        }
 569
 570        public void cleanUp(BuildListener listener) throws Exception {
 571            BuildTrigger.execute(AbstractBuild.this, listener);
 572            buildEnvironments = null;
 573        }
 574
 575        /**
 576         * @deprecated as of 1.356
 577         *      Use {@link #performAllBuildSteps(BuildListener, Map, boolean)}
 578         */
 579        protected final void performAllBuildStep(BuildListener listener, Map<?,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
 580            performAllBuildSteps(listener,buildSteps.values(),phase);
 581        }
 582
 583        protected final boolean performAllBuildSteps(BuildListener listener, Map<?,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
 584            return performAllBuildSteps(listener,buildSteps.values(),phase);
 585        }
 586
 587        /**
 588         * @deprecated as of 1.356
 589         *      Use {@link #performAllBuildSteps(BuildListener, Iterable, boolean)}
 590         */
 591        protected final void performAllBuildStep(BuildListener listener, Iterable<? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
 592            performAllBuildSteps(listener,buildSteps,phase);
 593        }
 594
 595        /**
 596         * Runs all the given build steps, even if one of them fail.
 597         *
 598         * @param phase
 599         *      true for the post build processing, and false for the final "run after finished" execution.
 600         */
 601        protected final boolean performAllBuildSteps(BuildListener listener, Iterable<? extends BuildStep> buildSteps,
 602                                                     boolean phase) throws InterruptedException, IOException {
 603            boolean r = true;
 604            for (BuildStep bs : buildSteps) {
 605                if (bs instanceof Publisher && ((Publisher) bs).needsToRun(getResult()) &&
 606                    ((((Publisher) bs).needsToRunAfterFinalized()) ^ phase)) {
 607                    try {
 608                        r &= perform(bs, listener);
 609                    } catch (Exception e) {
 610                        String msg = "Publisher " + bs.getClass().getName() + " aborted due to exception";
 611                        e.printStackTrace(listener.error(msg));
 612                        LOGGER.log(Level.WARNING, msg, e);
 613                        setResult(Result.FAILURE);
 614                    }
 615                }
 616            }
 617            return r;
 618        }
 619
 620        /**
 621         * Calls a build step.
 622         */
 623        protected final boolean perform(BuildStep bs, BuildListener listener) throws InterruptedException, IOException {
 624            BuildStepMonitor mon;
 625            try {
 626                mon = bs.getRequiredMonitorService();
 627            } catch (AbstractMethodError e) {
 628                mon = BuildStepMonitor.BUILD;
 629            }
 630            return mon.perform(bs, AbstractBuild.this, launcher, listener);
 631        }
 632
 633        protected final boolean preBuild(BuildListener listener,Map<?,? extends BuildStep> steps) {
 634            return preBuild(listener,steps.values());
 635        }
 636
 637        protected final boolean preBuild(BuildListener listener,Collection<? extends BuildStep> steps) {
 638            return preBuild(listener,(Iterable<? extends BuildStep>)steps);
 639        }
 640
 641        protected final boolean preBuild(BuildListener listener,Iterable<? extends BuildStep> steps) {
 642            for (BuildStep bs : steps)
 643                if (!bs.prebuild(AbstractBuild.this,listener))
 644                    return false;
 645            return true;
 646        }
 647    }
 648
 649    /**
 650     * Gets the changes incorporated into this build.
 651     *
 652     * @return never null.
 653     */
 654    @Exported
 655    public ChangeLogSet<? extends Entry> getChangeSet() {
 656        if (scm==null) {
 657            // for historical reason, null means CVS.
 658            try {
 659                Class<?> c = Hudson.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.scm.CVSChangeLogParser");
 660                scm = (ChangeLogParser)c.newInstance();
 661            } catch (ClassNotFoundException e) {
 662                // if CVS isn't available, fall back to something non-null.
 663                scm = new NullChangeLogParser();
 664            } catch (InstantiationException e) {
 665                scm = new NullChangeLogParser();
 666                throw (Error)new InstantiationError().initCause(e);
 667            } catch (IllegalAccessException e) {
 668                scm = new NullChangeLogParser();
 669                throw (Error)new IllegalAccessError().initCause(e);
 670            }
 671        }
 672
 673        if (changeSet==null) // cached value
 674            try {
 675                changeSet = calcChangeSet();
 676            } finally {
 677                // defensive check. if the calculation fails (such as through an exception),
 678                // set a dummy value so that it'll work the next time. the exception will
 679                // be still reported, giving the plugin developer an opportunity to fix it.
 680                if (changeSet==null)
 681                    changeSet=ChangeLogSet.createEmpty(this);
 682            }
 683        return changeSet;
 684    }
 685
 686    /**
 687     * Returns true if the changelog is already computed.
 688     */
 689    public boolean hasChangeSetComputed() {
 690        File changelogFile = new File(getRootDir(), "changelog.xml");
 691        return changelogFile.exists();
 692    }
 693
 694    private ChangeLogSet<? extends Entry> calcChangeSet() {
 695        File changelogFile = new File(getRootDir(), "changelog.xml");
 696        if (!changelogFile.exists())
 697            return ChangeLogSet.createEmpty(this);
 698
 699        try {
 700            return scm.parse(this,changelogFile);
 701        } catch (IOException e) {
 702            e.printStackTrace();
 703        } catch (SAXException e) {
 704            e.printStackTrace();
 705        }
 706        return ChangeLogSet.createEmpty(this);
 707    }
 708
 709    @Override
 710    public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
 711        EnvVars env = super.getEnvironment(log);
 712        FilePath ws = getWorkspace();
 713        if (ws!=null)   // if this is done very early on in the build, workspace may not be decided yet. see HUDSON-3997
 714            env.put("WORKSPACE", ws.getRemote());
 715        // servlet container may have set CLASSPATH in its launch script,
 716        // so don't let that inherit to the new child process.
 717        // see http://www.nabble.com/Run-Job-with-JDK-1.4.2-tf4468601.html
 718        env.put("CLASSPATH","");
 719
 720        JDK jdk = project.getJDK();
 721        if (jdk != null) {
 722            Computer computer = Computer.currentComputer();
 723            if (computer != null) { // just in case were not in a build
 724                jdk = jdk.forNode(computer.getNode(), log);
 725            }
 726            jdk.buildEnvVars(env);
 727        }
 728        project.getScm().buildEnvVars(this,env);
 729
 730        if (buildEnvironments!=null)
 731            for (Environment e : buildEnvironments)
 732                e.buildEnvVars(env);
 733
 734        for (EnvironmentContributingAction a : Util.filter(getActions(),EnvironmentContributingAction.class))
 735            a.buildEnvVars(this,env);
 736
 737        EnvVars.resolve(env);
 738
 739        return env;
 740    }
 741
 742    public Calendar due() {
 743        return getTimestamp();
 744    }
 745
 746    /**
 747     * Builds up a set of variable names that contain sensitive values that
 748     * should not be exposed. The expection is that this set is populated with
 749     * keys returned by {@link #getBuildVariables()} that should have their
 750     * values masked for display purposes.
 751     *
 752     * @since 1.378
 753     */
 754    public Set<String> getSensitiveBuildVariables() {
 755        Set<String> s = new HashSet<String>();
 756
 757        ParametersAction parameters = getAction(ParametersAction.class);
 758        if (parameters != null) {
 759            for (ParameterValue p : parameters) {
 760                if (p.isSensitive()) {
 761                    s.add(p.getName());
 762                }
 763            }
 764        }
 765
 766        // Allow BuildWrappers to determine if any of their data is sensitive
 767        if (project instanceof BuildableItemWithBuildWrappers) {
 768            for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList()) {
 769                bw.makeSensitiveBuildVariables(this, s);
 770            }
 771        }
 772        
 773        return s;
 774    }
 775
 776    /**
 777     * Provides additional variables and their values to {@link Builder}s.
 778     *
 779     * <p>
 780     * This mechanism is used by {@link MatrixConfiguration} to pass
 781     * the configuration values to the current build. It is up to
 782     * {@link Builder}s to decide whether it wants to recognize the values
 783     * or how to use them.
 784     *
 785     * <p>
 786     * This also includes build parameters if a build is parameterized.
 787     *
 788     * @return
 789     *      The returned map is mutable so that subtypes can put more values.
 790     */
 791    public Map<String,String> getBuildVariables() {
 792        Map<String,String> r = new HashMap<String, String>();
 793
 794        ParametersAction parameters = getAction(ParametersAction.class);
 795        if (parameters!=null) {
 796            // this is a rather round about way of doing this...
 797            for (ParameterValue p : parameters) {
 798                String v = p.createVariableResolver(this).resolve(p.getName());
 799                if (v!=null) r.put(p.getName(),v);
 800            }
 801        }
 802        
 803        customizeBuildVariables(r);
 804
 805        // allow the BuildWrappers to contribute additional build variables
 806        if (project instanceof BuildableItemWithBuildWrappers) {
 807            for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList())
 808                bw.makeBuildVariables(this,r);
 809        }
 810
 811        return r;
 812    }
 813    
 814    /**
 815     * @since 2.1.0
 816     */
 817    protected void customizeBuildVariables(final Map<String, String> vars) {
 818        // nop
 819    }
 820
 821
 822    /**
 823     * Creates {@link VariableResolver} backed by {@link #getBuildVariables()}.
 824     */
 825    public final VariableResolver<String> getBuildVariableResolver() {
 826        return new VariableResolver.ByMap<String>(getBuildVariables());
 827    }
 828
 829    /**
 830     * Gets {@link AbstractTestResultAction} associated with this build if any.
 831     */
 832    public AbstractTestResultAction getTestResultAction() {
 833        return getAction(AbstractTestResultAction.class);
 834    }
 835
 836    /**
 837     * Invoked by {@link Executor} to performs a build.
 838     */
 839    public abstract void run();
 840
 841//
 842//
 843// fingerprint related stuff
 844//
 845//
 846
 847    @Override
 848    public String getWhyKeepLog() {
 849        // if any of the downstream project is configured with 'keep dependency component',
 850        // we need to keep this log
 851        OUTER:
 852        for (AbstractProject<?,?> p : getParent().getDownstreamProjects()) {
 853            if (!p.isKeepDependencies()) continue;
 854
 855            AbstractBuild<?,?> fb = p.getFirstBuild();
 856            if (fb==null)        continue; // no active record
 857
 858            // is there any active build that depends on us?
 859            for (int i : getDownstreamRelationship(p).listNumbersReverse()) {
 860                // TODO: this is essentially a "find intersection between two sparse sequences"
 861                // and we should be able to do much better.
 862
 863                if (i<fb.getNumber())
 864                    continue OUTER; // all the other records are younger than the first record, so pointless to search.
 865
 866                AbstractBuild<?,?> b = p.getBuildByNumber(i);
 867                if (b!=null)
 868                    return Messages.AbstractBuild_KeptBecause(b);
 869            }
 870        }
 871
 872        return super.getWhyKeepLog();
 873    }
 874
 875    /**
 876     * Gets the dependency relationship from this build (as the source)
 877     * and that project (as the sink.)
 878     *
 879     * @return
 880     *      range of build numbers that represent which downstream builds are using this build.
 881     *      The range will be empty if no build of that project matches this, but it'll never be null.
 882     */
 883    public RangeSet getDownstreamRelationship(AbstractProject that) {
 884        RangeSet rs = new RangeSet();
 885
 886        FingerprintAction f = getAction(FingerprintAction.class);
 887        if (f==null)     return rs;
 888
 889        // look for fingerprints that point to this build as the source, and merge them all
 890        for (Fingerprint e : f.getFingerprints().values()) {
 891
 892            if (upstreamCulprits) {
 893                // With upstreamCulprits, we allow downstream relationships
 894                // from intermediate jobs
 895                rs.add(e.getRangeSet(that));
 896            } else {
 897                BuildPtr o = e.getOriginal();
 898                if (o!=null && o.is(this))
 899                    rs.add(e.getRangeSet(that));
 900            }
 901        }
 902
 903        return rs;
 904    }
 905
 906    /**
 907     * Works like {@link #getDownstreamRelationship(AbstractProject)} but returns
 908     * the actual build objects, in ascending order.
 909     * @since 1.150
 910     */
 911    public Iterable<AbstractBuild<?,?>> getDownstreamBuilds(final AbstractProject<?,?> that) {
 912        final Iterable<Integer> nums = getDownstreamRelationship(that).listNumbers();
 913
 914        return new Iterable<AbstractBuild<?, ?>>() {
 915            public Iterator<AbstractBuild<?, ?>> iterator() {
 916                return Iterators.removeNull(
 917                    new AdaptedIterator<Integer,AbstractBuild<?,?>>(nums) {
 918                        protected AbstractBuild<?, ?> adapt(Integer item) {
 919                            return that.getBuildByNumber(item);
 920                        }
 921                    });
 922            }
 923        };
 924    }
 925
 926    /**
 927     * Gets the dependency relationship from this build (as the sink)
 928     * and that project (as the source.)
 929     *
 930     * @return
 931     *      Build number of the upstream build that feed into this build,
 932     *      or -1 if no record is available.
 933     */
 934    public int getUpstreamRelationship(AbstractProject that) {
 935        FingerprintAction f = getAction(FingerprintAction.class);
 936        if (f==null)     return -1;
 937
 938        int n = -1;
 939
 940        // look for fingerprints that point to the given project as the source, and merge them all
 941        for (Fingerprint e : f.getFingerprints().values()) {
 942            if (upstreamCulprits) {
 943                // With upstreamCulprits, we allow upstream relationships
 944                // from intermediate jobs
 945                Fingerprint.RangeSet rangeset = e.getRangeSet(that);
 946                if (!rangeset.isEmpty()) {
 947                    n = Math.max(n, rangeset.listNumbersReverse().iterator().next());
 948                }
 949            } else {
 950                BuildPtr o = e.getOriginal();
 951                if (o!=null && o.belongsTo(that))
 952                    n = Math.max(n,o.getNumber());
 953            }
 954        }
 955
 956        return n;
 957    }
 958
 959    /**
 960     * Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the
 961     * actual build object.
 962     *
 963     * @return
 964     *      null if no such upstream build was found, or it was found but the
 965     *      build record is already lost.
 966     */
 967    public AbstractBuild<?,?> getUpstreamRelationshipBuild(AbstractProject<?,?> that) {
 968        int n = getUpstreamRelationship(that);
 969        if (n==-1)   return null;
 970        return that.getBuildByNumber(n);
 971    }
 972
 973    /**
 974     * Gets the downstream builds of this build, which are the builds of the
 975     * downstream projects that use artifacts of this build.
 976     *
 977     * @return
 978     *      For each project with fingerprinting enabled, returns the range
 979     *      of builds (which can be empty if no build uses the artifact from this build.)
 980     */
 981    public Map<AbstractProject,RangeSet> getDownstreamBuilds() {
 982        Map<AbstractProject,RangeSet> r = new HashMap<AbstractProject,RangeSet>();
 983        for (AbstractProject p : getParent().getDownstreamProjects()) {
 984            if (p.isFingerprintConfigured())
 985                r.put(p,getDownstreamRelationship(p));
 986        }
 987        return r;
 988    }
 989
 990    /**
 991     * Gets the upstream builds of this build, which are the builds of the
 992     * upstream projects whose artifacts feed into this build.
 993     *
 994     * @see #getTransitiveUpstreamBuilds()
 995     */
 996    public Map<AbstractProject,Integer> getUpstreamBuilds() {
 997        return _getUpstreamBuilds(getParent().getUpstreamProjects());
 998    }
 999
1000    /**
1001     * Works like {@link #getUpstreamBuilds()}  but also includes all the transitive
1002     * dependencies as well.
1003     */
1004    public Map<AbstractProject,Integer> getTransitiveUpstreamBuilds() {
1005        return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects());
1006    }
1007
1008    private Map<AbstractProject, Integer> _getUpstreamBuilds(Collection<AbstractProject> projects) {
1009        Map<AbstractProject,Integer> r = new HashMap<AbstractProject,Integer>();
1010        for (AbstractProject p : projects) {
1011            int n = getUpstreamRelationship(p);
1012            if (n>=0)
1013                r.put(p,n);
1014        }
1015        return r;
1016    }
1017
1018    /**
1019     * Gets the changes in the dependency between the given build and this build.
1020     */
1021    public Map<AbstractProject,DependencyChange> getDependencyChanges(AbstractBuild from) {
1022        if (from==null)             return Collections.emptyMap(); // make it easy to call this from views
1023        FingerprintAction n = this.getAction(FingerprintAction.class);
1024        FingerprintAction o = from.getAction(FingerprintAction.class);
1025        if (n==null || o==null)     return Collections.emptyMap();
1026
1027        Map<AbstractProject,Integer> ndep = n.getDependencies();
1028        Map<AbstractProject,Integer> odep = o.getDependencies();
1029
1030        Map<AbstractProject,DependencyChange> r = new HashMap<AbstractProject,DependencyChange>();
1031
1032        for (Map.Entry<AbstractProject,Integer> entry : odep.entrySet()) {
1033            AbstractProject p = entry.getKey();
1034            Integer oldNumber = entry.getValue();
1035            Integer newNumber = ndep.get(p);
1036            if (newNumber!=null && oldNumber.compareTo(newNumber)<0) {
1037                r.put(p,new DependencyChange(p,oldNumber,newNumber));
1038            }
1039        }
1040
1041        return r;
1042    }
1043
1044    /**
1045     * Represents a change in the dependency.
1046     */
1047    public static final class DependencyChange {
1048        /**
1049         * The dependency project.
1050         */
1051        //TODO: review and check whether we can do it private
1052        public final AbstractProject project;
1053        /**
1054         * Version of the dependency project used in the previous build.
1055         */
1056        //TODO: review and check whether we can do it private
1057        public final int fromId;
1058        /**
1059         * {@link Build} object for {@link #fromId}. Can be null if the log is gone.
1060         */
1061        //TODO: review and check whether we can do it private
1062        public final AbstractBuild from;
1063        /**
1064         * Version of the dependency project used in this build.
1065         */
1066        //TODO: review and check whether we can do it private
1067        public final int toId;
1068        //TODO: review and check whether we can do it private
1069        public final AbstractBuild to;
1070
1071        public DependencyChange(AbstractProject<?,?> project, int fromId, int toId) {
1072            this.project = project;
1073            this.fromId = fromId;
1074            this.toId = toId;
1075            this.from = project.getBuildByNumber(fromId);
1076            this.to = project.getBuildByNumber(toId);
1077        }
1078
1079        public AbstractProject getProject() {
1080            return project;
1081        }
1082
1083        public int getFromId() {
1084            return fromId;
1085        }
1086
1087        public AbstractBuild getFrom() {
1088            return from;
1089        }
1090
1091        public int getToId() {
1092            return toId;
1093        }
1094
1095        public AbstractBuild getTo() {
1096            return to;
1097        }
1098        /**
1099         * Gets the {@link AbstractBuild} objects (fromId,toId].
1100         * <p>
1101         * This method returns all such available builds in the ascending order
1102         * of IDs, but due to log rotations, some builds may be already unavailable.
1103         */
1104        public List<AbstractBuild> getBuilds() {
1105            List<AbstractBuild> r = new ArrayList<AbstractBuild>();
1106
1107            AbstractBuild<?,?> b = (AbstractBuild)project.getNearestBuild(fromId);
1108            if (b!=null && b.getNumber()==fromId)
1109                b = b.getNextBuild(); // fromId exclusive
1110
1111            while (b!=null && b.getNumber()<=toId) {
1112                r.add(b);
1113                b = b.getNextBuild();
1114            }
1115
1116            return r;
1117        }
1118    }
1119
1120    //
1121    // web methods
1122    //
1123
1124    /**
1125     * Stops this build if it's still going.
1126     *
1127     * If we use this/executor/stop URL, it causes 404 if the build is already killed,
1128     * as {@link #getExecutor()} returns null.
1129     */
1130    public synchronized void doStop(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1131        Executor e = getExecutor();
1132        if (e!=null)
1133            e.doStop(req,rsp);
1134        else
1135            // nothing is building
1136            rsp.forwardToPreviousPage(req);
1137    }
1138
1139    private static final Logger LOGGER = Logger.getLogger(AbstractBuild.class.getName());
1140}