File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\model\AbstractBuild.java.html

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 */
24 package hudson.model;
25
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 import hudson.Util;
34 import hudson.console.AnnotatedLargeText;
35 import hudson.console.ExpandableDetailsNote;
36 import hudson.console.ModelHyperlinkNote;
37 import hudson.matrix.MatrixConfiguration;
38 import hudson.model.Fingerprint.BuildPtr;
39 import hudson.model.Fingerprint.RangeSet;
40 import hudson.model.listeners.RunListener;
41 import hudson.model.listeners.SCMListener;
42 import hudson.scm.ChangeLogParser;
43 import hudson.scm.ChangeLogSet;
44 import hudson.scm.ChangeLogSet.Entry;
45 import hudson.scm.NullChangeLogParser;
46 import hudson.scm.SCM;
47 import hudson.slaves.NodeProperty;
48 import hudson.slaves.WorkspaceList;
49 import hudson.slaves.WorkspaceList.Lease;
50 import hudson.tasks.BuildStep;
51 import hudson.tasks.BuildStepMonitor;
52 import hudson.tasks.BuildTrigger;
53 import hudson.tasks.BuildWrapper;
54 import hudson.tasks.Builder;
55 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 import org.kohsuke.stapler.StaplerRequest;
66 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 import java.io.File;
72 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 import java.util.AbstractSet;
78 import java.util.ArrayList;
79 import java.util.Calendar;
80 import java.util.Collection;
81 import java.util.Collections;
82 import java.util.HashMap;
83 import java.util.HashSet;
84 import java.util.Iterator;
85 import java.util.List;
86 import java.util.Map;
87 import java.util.Set;
88 import java.util.logging.Level;
89 import java.util.logging.Logger;
90 import javax.annotation.CheckForNull;
91 import org.kohsuke.stapler.interceptor.RequirePOST;
92
93 /**
94 * Base implementation of {@link Run}s that build software.
95 *
96 * For now this is primarily the common part of {@link Build} and MavenBuild.
97 *
98 * @author Kohsuke Kawaguchi
99 * @see AbstractProject
100 */
101 public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Run<P,R> implements Queue.Executable {
102
103 /**
104 * Set if we want the blame information to flow from upstream to downstream build.
105 */
106 private static final boolean upstreamCulprits = Boolean.getBoolean("hudson.upstreamCulprits");
107
108 /**
109 * Name of the slave this project was built on.
110 * Null or "" if built by the master. (null happens when we read old record that didn't have this information.)
111 */
112 private String builtOn;
113
114 /**
115 * The file path on the node that performed a build. Kept as a string since {@link FilePath} is not serializable into XML.
116 * @since 1.319
117 */
118 private String workspace;
119
120 /**
121 * Version of Hudson that built this.
122 */
123 private String hudsonVersion;
124
125 /**
126 * SCM used for this build.
127 * Maybe null, for historical reason, in which case CVS is assumed.
128 */
129 private ChangeLogParser scm;
130
131 /**
132 * Changes in this build.
133 */
134 private volatile transient WeakReference<ChangeLogSet<? extends Entry>> changeSet;
135
136 /**
137 * Cumulative list of people who contributed to the build problem.
138 *
139 * <p>
140 * This is a list of {@link User#getId() user ids} who made a change
141 * since the last non-broken build. Can be null (which should be
142 * treated like empty set), because of the compatibility.
143 *
144 * <p>
145 * This field is semi-final --- once set the value will never be modified.
146 *
147 * @since 1.137
148 */
149 private volatile Set<String> culprits;
150
151 /**
152 * During the build this field remembers {@link BuildWrapper.Environment}s created by
153 * {@link BuildWrapper}. This design is bit ugly but forced due to compatibility.
154 */
155 protected transient List<Environment> buildEnvironments;
156
157 /**
158 * Pointers to form bi-directional link between adjacent {@link AbstractBuild}s.
159 *
160 * <p>
161 * Unlike {@link Run}, {@link AbstractBuild}s do lazy-loading, so we don't use
162 * {@link Run#previousBuild} and {@link Run#nextBuild}, and instead use these
163 * fields and point to {@link #selfReference} of adjacent builds.
164 */
165 private volatile transient BuildReference<R> previousBuild, nextBuild;
166
167 /*package*/ final transient BuildReference<R> selfReference = new BuildReference<R>(getId(),_this());
168
169
170
171 protected AbstractBuild(P job) throws IOException {
172 super(job);
173 }
174
175 protected AbstractBuild(P job, Calendar timestamp) {
176 super(job, timestamp);
177 }
178
179 protected AbstractBuild(P project, File buildDir) throws IOException {
180 super(project, buildDir);
181 }
182
183 public final P getProject() {
184 return getParent();
185 }
186
187 @Override
188 void dropLinks() {
189 super.dropLinks();
190
191 if(nextBuild!=null) {
192 AbstractBuild nb = nextBuild.get();
193 if (nb!=null) {
194 // remove the oldest build
195 if (previousBuild == selfReference)
196 nb.previousBuild = nextBuild;
197 else
198 nb.previousBuild = previousBuild;
199 }
200 }
201 if(previousBuild!=null) {
202 AbstractBuild pb = previousBuild.get();
203 if (pb!=null) pb.nextBuild = nextBuild;
204 }
205 }
206
207 @Override
208 public R getPreviousBuild() {
209 while (true) {
210 BuildReference<R> r = previousBuild; // capture the value once
211
212 if (r==null) {
213 // having two neighbors pointing to each other is important to make RunMap.removeValue work
214 R pb = getParent().builds.search(number-1, Direction.DESC);
215 if (pb!=null) {
216 ((AbstractBuild)pb).nextBuild = selfReference; // establish bi-di link
217 this.previousBuild = pb.selfReference;
218 return pb;
219 } else {
220 // this indicates that we know there's no previous build
221 // (as opposed to we don't know if/what our previous build is.
222 this.previousBuild = selfReference;
223 return null;
224 }
225 }
226 if (r==selfReference)
227 return null;
228
229 R referent = r.get();
230 if (referent!=null) return referent;
231
232 // the reference points to a GC-ed object, drop the reference and do it again
233 this.previousBuild = null;
234 }
235 }
236
237 @Override
238 public R getNextBuild() {
239 while (true) {
240 BuildReference<R> r = nextBuild; // capture the value once
241
242 if (r==null) {
243 // having two neighbors pointing to each other is important to make RunMap.removeValue work
244 R nb = getParent().builds.search(number+1, Direction.ASC);
245 if (nb!=null) {
246 ((AbstractBuild)nb).previousBuild = selfReference; // establish bi-di link
247 this.nextBuild = nb.selfReference;
248 return nb;
249 } else {
250 // this indicates that we know there's no next build
251 // (as opposed to we don't know if/what our next build is.
252 this.nextBuild = selfReference;
253 return null;
254 }
255 }
256 if (r==selfReference)
257 return null;
258
259 R referent = r.get();
260 if (referent!=null) return referent;
261
262 // the reference points to a GC-ed object, drop the reference and do it again
263 this.nextBuild = null;
264 }
265 }
266
267 /**
268 * Returns a {@link Slave} on which this build was done.
269 *
270 * @return
271 * null, for example if the slave that this build run no longer exists.
272 */
273 public @CheckForNull Node getBuiltOn() {
274 if (builtOn==null || builtOn.equals(""))
275 return Jenkins.getInstance();
276 else
277 return Jenkins.getInstance().getNode(builtOn);
278 }
279
280 /**
281 * Returns the name of the slave it was built on; null or "" if built by the master.
282 * (null happens when we read old record that didn't have this information.)
283 */
284 @Exported(name="builtOn")
285 public String getBuiltOnStr() {
286 return builtOn;
287 }
288
289 /**
290 * Allows subtypes to set the value of {@link #builtOn}.
291 * This is used for those implementations where an {@link AbstractBuild} is made 'built' without
292 * actually running its {@link #run()} method.
293 *
294 * @since 1.429
295 */
296 protected void setBuiltOnStr( String builtOn ) {
297 this.builtOn = builtOn;
298 }
299
300 /**
301 * Gets the nearest ancestor {@link AbstractBuild} that belongs to
302 * {@linkplain AbstractProject#getRootProject() the root project of getProject()} that
303 * dominates/governs/encompasses this build.
304 *
305 * <p>
306 * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs,
307 * and still in some of them, builds of child projects are related/tied to that of the parent project.
308 * In such a case, this method returns the governing build.
309 *
310 * @return never null. In the worst case the build dominates itself.
311 * @since 1.421
312 * @see AbstractProject#getRootProject()
313 */
314 public AbstractBuild<?,?> getRootBuild() {
315 return this;
316 }
317
318 /**
319 * Used to render the side panel "Back to project" link.
320 *
321 * <p>
322 * In a rare situation where a build can be reached from multiple paths,
323 * returning different URLs from this method based on situations might
324 * be desirable.
325 *
326 * <p>
327 * If you override this method, you'll most likely also want to override
328 * {@link #getDisplayName()}.
329 */
330 public String getUpUrl() {
331 return Functions.getNearestAncestorUrl(Stapler.getCurrentRequest(),getParent())+'/';
332 }
333
334 /**
335 * Gets the directory where this build is being built.
336 *
337 * <p>
338 * Note to implementors: to control where the workspace is created, override
339 * {@link AbstractBuildExecution#decideWorkspace(Node,WorkspaceList)}.
340 *
341 * @return
342 * null if the workspace is on a slave that's not connected. Note that once the build is completed,
343 * the workspace may be used to build something else, so the value returned from this method may
344 * no longer show a workspace as it was used for this build.
345 * @since 1.319
346 */
347 public final @CheckForNull FilePath getWorkspace() {
348 if (workspace==null) return null;
349 Node n = getBuiltOn();
350 if (n==null) return null;
351 return n.createPath(workspace);
352 }
353
354 /**
355 * Normally, a workspace is assigned by {@link RunExecution}, but this lets you set the workspace in case
356 * {@link AbstractBuild} is created without a build.
357 */
358 protected void setWorkspace(FilePath ws) {
359 this.workspace = ws.getRemote();
360 }
361
362 /**
363 * Returns the root directory of the checked-out module.
364 * <p>
365 * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
366 * and so on exists.
367 */
368 public final FilePath getModuleRoot() {
369 FilePath ws = getWorkspace();
370 if (ws==null) return null;
371 return getParent().getScm().getModuleRoot(ws,this);
372 }
373
374 /**
375 * Returns the root directories of all checked-out modules.
376 * <p>
377 * Some SCMs support checking out multiple modules into the same workspace.
378 * In these cases, the returned array will have a length greater than one.
379 * @return The roots of all modules checked out from the SCM.
380 */
381 public FilePath[] getModuleRoots() {
382 FilePath ws = getWorkspace();
383 if (ws==null) return null;
384 return getParent().getScm().getModuleRoots(ws, this);
385 }
386
387 /**
388 * List of users who committed a change since the last non-broken build till now.
389 *
390 * <p>
391 * This list at least always include people who made changes in this build, but
392 * if the previous build was a failure it also includes the culprit list from there.
393 *
394 * @return
395 * can be empty but never null.
396 */
397 @Exported
398 public Set<User> getCulprits() {
399 if (culprits==null) {
400 Set<User> r = new HashSet<User>();
401 R p = getPreviousCompletedBuild();
402 if (p !=null && isBuilding()) {
403 Result pr = p.getResult();
404 if (pr!=null && pr.isWorseThan(Result.SUCCESS)) {
405 // we are still building, so this is just the current latest information,
406 // but we seems to be failing so far, so inherit culprits from the previous build.
407 // isBuilding() check is to avoid recursion when loading data from old Hudson, which doesn't record
408 // this information
409 r.addAll(p.getCulprits());
410 }
411 }
412 for (Entry e : getChangeSet())
413 r.add(e.getAuthor());
414
415 if (upstreamCulprits) {
416 // If we have dependencies since the last successful build, add their authors to our list
417 if (getPreviousNotFailedBuild() != null) {
418 Map <AbstractProject,AbstractBuild.DependencyChange> depmap = getDependencyChanges(getPreviousSuccessfulBuild());
419 for (AbstractBuild.DependencyChange dep : depmap.values()) {
420 for (AbstractBuild<?,?> b : dep.getBuilds()) {
421 for (Entry entry : b.getChangeSet()) {
422 r.add(entry.getAuthor());
423 }
424 }
425 }
426 }
427 }
428
429 return r;
430 }
431
432 return new AbstractSet<User>() {
433 public Iterator<User> iterator() {
434 return new AdaptedIterator<String,User>(culprits.iterator()) {
435 protected User adapt(String id) {
436 return User.get(id);
437 }
438 };
439 }
440
441 public int size() {
442 return culprits.size();
443 }
444 };
445 }
446
447 /**
448 * Returns true if this user has made a commit to this build.
449 *
450 * @since 1.191
451 */
452 public boolean hasParticipant(User user) {
453 for (ChangeLogSet.Entry e : getChangeSet())
454 try{
455 if (e.getAuthor()==user)
456 return true;
457 } catch (RuntimeException re) {
458 LOGGER.log(Level.INFO, "Failed to determine author of changelog " + e.getCommitId() + "for " + getParent().getDisplayName() + ", " + getDisplayName(), re);
459 }
460 return false;
461 }
462
463 /**
464 * Gets the version of Hudson that was used to build this job.
465 *
466 * @since 1.246
467 */
468 public String getHudsonVersion() {
469 return hudsonVersion;
470 }
471
472 @Override
473 public synchronized void delete() throws IOException {
474 // Need to check if deleting this build affects lastSuccessful/lastStable symlinks
475 R lastSuccessful = getProject().getLastSuccessfulBuild(),
476 lastStable = getProject().getLastStableBuild();
477
478 super.delete();
479
480 try {
481 if (lastSuccessful == this)
482 updateSymlink("lastSuccessful", getProject().getLastSuccessfulBuild());
483 if (lastStable == this)
484 updateSymlink("lastStable", getProject().getLastStableBuild());
485 } catch (InterruptedException ex) {
486 LOGGER.warning("Interrupted update of lastSuccessful/lastStable symlinks for "
487 + getProject().getDisplayName());
488 // handle it later
489 Thread.currentThread().interrupt();
490 }
491 }
492
493 private void updateSymlink(String name, AbstractBuild<?,?> newTarget) throws InterruptedException {
494 if (newTarget != null)
495 newTarget.createSymlink(new LogTaskListener(LOGGER, Level.WARNING), name);
496 else
497 new File(getProject().getBuildDir(), "../"+name).delete();
498 }
499
500 private void createSymlink(TaskListener listener, String name) throws InterruptedException {
501 Util.createSymlink(getProject().getBuildDir(),"builds/"+getId(),"../"+name,listener);
502 }
503
504 /**
505 * @deprecated as of 1.467
506 * Please use {@link RunExecution}
507 */
508 public abstract class AbstractRunner extends AbstractBuildExecution {
509
510 }
511
512 public abstract class AbstractBuildExecution extends Runner {
513 /*
514 Some plugins might depend on this instance castable to Runner, so we need to use
515 deprecated class here.
516 */
517
518 /**
519 * Since configuration can be changed while a build is in progress,
520 * create a launcher once and stick to it for the entire build duration.
521 */
522 protected Launcher launcher;
523
524 /**
525 * Output/progress of this build goes here.
526 */
527 protected BuildListener listener;
528
529 /**
530 * Returns the current {@link Node} on which we are building.
531 */
532 protected final Node getCurrentNode() {
533 return Executor.currentExecutor().getOwner().getNode();
534 }
535
536 public Launcher getLauncher() {
537 return launcher;
538 }
539
540 public BuildListener getListener() {
541 return listener;
542 }
543
544 /**
545 * Allocates the workspace from {@link WorkspaceList}.
546 *
547 * @param n
548 * Passed in for the convenience. The node where the build is running.
549 * @param wsl
550 * Passed in for the convenience. The returned path must be registered to this object.
551 */
552 protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws InterruptedException, IOException {
553 String customWorkspace = getProject().getCustomWorkspace();
554 if (customWorkspace != null) {
555 // we allow custom workspaces to be concurrently used between jobs.
556 return Lease.createDummyLease(n.getRootPath().child(getEnvironment(listener).expand(customWorkspace)));
557 }
558 // TODO: this cast is indicative of abstraction problem
559 return wsl.allocate(n.getWorkspaceFor((TopLevelItem)getProject()), getBuild());
560 }
561
562 public Result run(BuildListener listener) throws Exception {
563 Node node = getCurrentNode();
564 assert builtOn==null;
565 builtOn = node.getNodeName();
566 hudsonVersion = Jenkins.VERSION;
567 this.listener = listener;
568
569 launcher = createLauncher(listener);
570 if (!Jenkins.getInstance().getNodes().isEmpty())
571 listener.getLogger().print(node instanceof Jenkins ? Messages.AbstractBuild_BuildingOnMaster() :
572 Messages.AbstractBuild_BuildingRemotely(ModelHyperlinkNote.encodeTo("/computer/" + builtOn, builtOn)));
573 else
574 listener.getLogger().print(Messages.AbstractBuild_Building());
575
576 final Lease lease = decideWorkspace(node,Computer.currentComputer().getWorkspaceList());
577
578 try {
579 workspace = lease.path.getRemote();
580 listener.getLogger().println(Messages.AbstractBuild_BuildingInWorkspace(workspace));
581 node.getFileSystemProvisioner().prepareWorkspace(AbstractBuild.this,lease.path,listener);
582
583 for (WorkspaceListener wl : WorkspaceListener.all()) {
584 wl.beforeUse(AbstractBuild.this, lease.path, listener);
585 }
586
587 getProject().getScmCheckoutStrategy().preCheckout(AbstractBuild.this, launcher, this.listener);
588 getProject().getScmCheckoutStrategy().checkout(this);
589
590 if (!preBuild(listener,project.getProperties()))
591 return Result.FAILURE;
592
593 Result result = doRun(listener);
594
595 Computer c = node.toComputer();
596 if (c==null || c.isOffline()) {
597 // As can be seen in HUDSON-5073, when a build fails because of the slave connectivity problem,
598 // error message doesn't point users to the slave. So let's do it here.
599 listener.hyperlink("/computer/"+builtOn+"/log","Looks like the node went offline during the build. Check the slave log for the details.");
600
601 if (c != null) {
602 // grab the end of the log file. This might not work very well if the slave already
603 // starts reconnecting. Fixing this requires a ring buffer in slave logs.
604 AnnotatedLargeText<Computer> log = c.getLogText();
605 StringWriter w = new StringWriter();
606 log.writeHtmlTo(Math.max(0,c.getLogFile().length()-10240),w);
607
608 listener.getLogger().print(ExpandableDetailsNote.encodeTo("details",w.toString()));
609 listener.getLogger().println();
610 }
611 }
612
613 // kill run-away processes that are left
614 // use multiple environment variables so that people can escape this massacre by overriding an environment
615 // variable for some processes
616 launcher.kill(getCharacteristicEnvVars());
617
618 // this is ugly, but for historical reason, if non-null value is returned
619 // it should become the final result.
620 if (result==null) result = getResult();
621 if (result==null) result = Result.SUCCESS;
622
623 return result;
624 } finally {
625 lease.release();
626 this.listener = null;
627 }
628 }
629
630 /**
631 * Creates a {@link Launcher} that this build will use. This can be overridden by derived types
632 * to decorate the resulting {@link Launcher}.
633 *
634 * @param listener
635 * Always non-null. Connected to the main build output.
636 */
637 protected Launcher createLauncher(BuildListener listener) throws IOException, InterruptedException {
638 Launcher l = getCurrentNode().createLauncher(listener);
639
640 if (project instanceof BuildableItemWithBuildWrappers) {
641 BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
642 for (BuildWrapper bw : biwbw.getBuildWrappersList())
643 l = bw.decorateLauncher(AbstractBuild.this,l,listener);
644 }
645
646 buildEnvironments = new ArrayList<Environment>();
647
648 for (RunListener rl: RunListener.all()) {
649 Environment environment = rl.setUpEnvironment(AbstractBuild.this, l, listener);
650 if (environment != null) {
651 buildEnvironments.add(environment);
652 }
653 }
654
655 for (NodeProperty nodeProperty: Jenkins.getInstance().getGlobalNodeProperties()) {
656 Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
657 if (environment != null) {
658 buildEnvironments.add(environment);
659 }
660 }
661
662 for (NodeProperty nodeProperty: Computer.currentComputer().getNode().getNodeProperties()) {
663 Environment environment = nodeProperty.setUp(AbstractBuild.this, l, listener);
664 if (environment != null) {
665 buildEnvironments.add(environment);
666 }
667 }
668
669 return l;
670 }
671
672 public void defaultCheckout() throws IOException, InterruptedException {
673 AbstractBuild<?,?> build = AbstractBuild.this;
674 AbstractProject<?, ?> project = build.getProject();
675
676 for (int retryCount=project.getScmCheckoutRetryCount(); ; retryCount--) {
677 // for historical reasons, null in the scm field means CVS, so we need to explicitly set this to something
678 // in case check out fails and leaves a broken changelog.xml behind.
679 // see http://www.nabble.com/CVSChangeLogSet.parse-yields-SAXParseExceptions-when-parsing-bad-*AccuRev*-changelog.xml-files-td22213663.html
680 build.scm = NullChangeLogParser.INSTANCE;
681
682 try {
683 if (project.checkout(build,launcher,listener,new File(build.getRootDir(),"changelog.xml"))) {
684 // check out succeeded
685 SCM scm = project.getScm();
686
687 build.scm = scm.createChangeLogParser();
688 build.changeSet = new WeakReference<ChangeLogSet<? extends Entry>>(build.calcChangeSet());
689
690 for (SCMListener l : Jenkins.getInstance().getSCMListeners())
691 try {
692 l.onChangeLogParsed(build,listener,build.getChangeSet());
693 } catch (Exception e) {
694 throw new IOException2("Failed to parse changelog",e);
695 }
696 return;
697 }
698 } catch (AbortException e) {
699 listener.error(e.getMessage());
700 } catch (InterruptedIOException e) {
701 throw (InterruptedException)new InterruptedException().initCause(e);
702 } catch (IOException e) {
703 // checkout error not yet reported
704 e.printStackTrace(listener.getLogger());
705 }
706
707 if (retryCount == 0) // all attempts failed
708 throw new RunnerAbortedException();
709
710 listener.getLogger().println("Retrying after 10 seconds");
711 Thread.sleep(10000);
712 }
713 }
714
715 /**
716 * The portion of a build that is specific to a subclass of {@link AbstractBuild}
717 * goes here.
718 *
719 * @return
720 * null to continue the build normally (that means the doRun method
721 * itself run successfully)
722 * Return a non-null value to abort the build right there with the specified result code.
723 */
724 protected abstract Result doRun(BuildListener listener) throws Exception, RunnerAbortedException;
725
726 /**
727 * @see #post(BuildListener)
728 */
729 protected abstract void post2(BuildListener listener) throws Exception;
730
731 public final void post(BuildListener listener) throws Exception {
732 try {
733 post2(listener);
734
735 if (result.isBetterOrEqualTo(Result.UNSTABLE))
736 createSymlink(listener, "lastSuccessful");
737
738 if (result.isBetterOrEqualTo(Result.SUCCESS))
739 createSymlink(listener, "lastStable");
740 } finally {
741 // update the culprit list
742 HashSet<String> r = new HashSet<String>();
743 for (User u : getCulprits())
744 r.add(u.getId());
745 culprits = ImmutableSortedSet.copyOf(r);
746 CheckPoint.CULPRITS_DETERMINED.report();
747 }
748 }
749
750 public void cleanUp(BuildListener listener) throws Exception {
751 BuildTrigger.execute(AbstractBuild.this, listener);
752 buildEnvironments = null;
753 }
754
755 /**
756 * @deprecated as of 1.356
757 * Use {@link #performAllBuildSteps(BuildListener, Map, boolean)}
758 */
759 protected final void performAllBuildStep(BuildListener listener, Map<?,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
760 performAllBuildSteps(listener,buildSteps.values(),phase);
761 }
762
763 protected final boolean performAllBuildSteps(BuildListener listener, Map<?,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
764 return performAllBuildSteps(listener,buildSteps.values(),phase);
765 }
766
767 /**
768 * @deprecated as of 1.356
769 * Use {@link #performAllBuildSteps(BuildListener, Iterable, boolean)}
770 */
771 protected final void performAllBuildStep(BuildListener listener, Iterable<? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
772 performAllBuildSteps(listener,buildSteps,phase);
773 }
774
775 /**
776 * Runs all the given build steps, even if one of them fail.
777 *
778 * @param phase
779 * true for the post build processing, and false for the final "run after finished" execution.
780 */
781 protected final boolean performAllBuildSteps(BuildListener listener, Iterable<? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
782 boolean r = true;
783 for (BuildStep bs : buildSteps) {
784 if ((bs instanceof Publisher && ((Publisher)bs).needsToRunAfterFinalized()) ^ phase)
785 try {
786 if (!perform(bs,listener)) {
787 LOGGER.fine(MessageFormat.format("{0} : {1} failed", AbstractBuild.this.toString(), bs));
788 r = false;
789 }
790 } catch (Exception e) {
791 String msg = "Publisher " + bs.getClass().getName() + " aborted due to exception";
792 e.printStackTrace(listener.error(msg));
793 LOGGER.log(Level.WARNING, msg, e);
794 setResult(Result.FAILURE);
795 }
796 }
797 return r;
798 }
799
800 /**
801 * Calls a build step.
802 */
803 protected final boolean perform(BuildStep bs, BuildListener listener) throws InterruptedException, IOException {
804 BuildStepMonitor mon;
805 try {
806 mon = bs.getRequiredMonitorService();
807 } catch (AbstractMethodError e) {
808 mon = BuildStepMonitor.BUILD;
809 }
810 Result oldResult = AbstractBuild.this.getResult();
811 for (BuildStepListener bsl : BuildStepListener.all()) {
812 bsl.started(AbstractBuild.this, bs, listener);
813 }
814 boolean canContinue = mon.perform(bs, AbstractBuild.this, launcher, listener);
815 for (BuildStepListener bsl : BuildStepListener.all()) {
816 bsl.finished(AbstractBuild.this, bs, listener, canContinue);
817 }
818 Result newResult = AbstractBuild.this.getResult();
819 if (newResult != oldResult) {
820 String buildStepName = getBuildStepName(bs);
821 listener.getLogger().format("Build step '%s' changed build result to %s%n", buildStepName, newResult);
822 }
823 if (!canContinue) {
824 String buildStepName = getBuildStepName(bs);
825 listener.getLogger().format("Build step '%s' marked build as failure%n", buildStepName);
826 }
827 return canContinue;
828 }
829
830 private String getBuildStepName(BuildStep bs) {
831 if (bs instanceof Describable<?>) {
832 return ((Describable<?>) bs).getDescriptor().getDisplayName();
833 } else {
834 return bs.getClass().getSimpleName();
835 }
836 }
837
838 protected final boolean preBuild(BuildListener listener,Map<?,? extends BuildStep> steps) {
839 return preBuild(listener,steps.values());
840 }
841
842 protected final boolean preBuild(BuildListener listener,Collection<? extends BuildStep> steps) {
843 return preBuild(listener,(Iterable<? extends BuildStep>)steps);
844 }
845
846 protected final boolean preBuild(BuildListener listener,Iterable<? extends BuildStep> steps) {
847 for (BuildStep bs : steps)
848 if (!bs.prebuild(AbstractBuild.this,listener)) {
849 LOGGER.fine(MessageFormat.format("{0} : {1} failed", AbstractBuild.this.toString(), bs));
850 return false;
851 }
852 return true;
853 }
854 }
855
856 /**
857 * get the fingerprints associated with this build
858 *
859 * @return never null
860 */
861 @Exported(name = "fingerprint", inline = true, visibility = -1)
862 public Collection<Fingerprint> getBuildFingerprints() {
863 FingerprintAction fingerprintAction = getAction(FingerprintAction.class);
864 if (fingerprintAction != null) {
865 return fingerprintAction.getFingerprints().values();
866 }
867 return Collections.<Fingerprint>emptyList();
868 }
869
870 /*
871 * No need to to lock the entire AbstractBuild on change set calculcation
872 */
873 private transient Object changeSetLock = new Object();
874
875 /**
876 * Gets the changes incorporated into this build.
877 *
878 * @return never null.
879 */
880 @Exported
881 public ChangeLogSet<? extends Entry> getChangeSet() {
882 synchronized (changeSetLock) {
883 if (scm==null) {
884 // for historical reason, null means CVS.
885 try {
886 Class<?> c = Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.scm.CVSChangeLogParser");
887 scm = (ChangeLogParser)c.newInstance();
888 } catch (ClassNotFoundException e) {
889 // if CVS isn't available, fall back to something non-null.
890 scm = NullChangeLogParser.INSTANCE;
891 } catch (InstantiationException e) {
892 scm = NullChangeLogParser.INSTANCE;
893 throw (Error)new InstantiationError().initCause(e);
894 } catch (IllegalAccessException e) {
895 scm = NullChangeLogParser.INSTANCE;
896 throw (Error)new IllegalAccessError().initCause(e);
897 }
898 }
899 }
900
901 ChangeLogSet<? extends Entry> cs = null;
902 if (changeSet!=null)
903 cs = changeSet.get();
904
905 if (cs==null)
906 cs = calcChangeSet();
907
908 // defensive check. if the calculation fails (such as through an exception),
909 // set a dummy value so that it'll work the next time. the exception will
910 // be still reported, giving the plugin developer an opportunity to fix it.
911 if (cs==null)
912 cs = ChangeLogSet.createEmpty(this);
913
914 changeSet = new WeakReference<ChangeLogSet<? extends Entry>>(cs);
915 return cs;
916 }
917
918 /**
919 * Returns true if the changelog is already computed.
920 */
921 public boolean hasChangeSetComputed() {
922 File changelogFile = new File(getRootDir(), "changelog.xml");
923 return changelogFile.exists();
924 }
925
926 private ChangeLogSet<? extends Entry> calcChangeSet() {
927 File changelogFile = new File(getRootDir(), "changelog.xml");
928 if (!changelogFile.exists())
929 return ChangeLogSet.createEmpty(this);
930
931 try {
932 return scm.parse(this,changelogFile);
933 } catch (IOException e) {
934 e.printStackTrace();
935 } catch (SAXException e) {
936 e.printStackTrace();
937 }
938 return ChangeLogSet.createEmpty(this);
939 }
940
941 @Override
942 public EnvVars getEnvironment(TaskListener log) throws IOException, InterruptedException {
943 EnvVars env = super.getEnvironment(log);
944 FilePath ws = getWorkspace();
945 if (ws!=null) // if this is done very early on in the build, workspace may not be decided yet. see HUDSON-3997
946 env.put("WORKSPACE", ws.getRemote());
947
948 project.getScm().buildEnvVars(this,env);
949
950 if (buildEnvironments!=null)
951 for (Environment e : buildEnvironments)
952 e.buildEnvVars(env);
953
954 for (EnvironmentContributingAction a : Util.filter(getActions(),EnvironmentContributingAction.class))
955 a.buildEnvVars(this,env);
956
957 EnvVars.resolve(env);
958
959 return env;
960 }
961
962 /**
963 * During the build, expose the environments contributed by {@link BuildWrapper}s and others.
964 *
965 * <p>
966 * Since 1.444, executor thread that's doing the build can access mutable underlying list,
967 * which allows the caller to add/remove environments. The recommended way of adding
968 * environment is through {@link BuildWrapper}, but this might be handy for build steps
969 * who wants to expose additional environment variables to the rest of the build.
970 *
971 * @return can be empty list, but never null. Immutable.
972 * @since 1.437
973 */
974 public EnvironmentList getEnvironments() {
975 Executor e = Executor.currentExecutor();
976 if (e!=null && e.getCurrentExecutable()==this) {
977 if (buildEnvironments==null) buildEnvironments = new ArrayList<Environment>();
978 return new EnvironmentList(buildEnvironments);
979 }
980
981 return new EnvironmentList(buildEnvironments==null ? Collections.<Environment>emptyList() : ImmutableList.copyOf(buildEnvironments));
982 }
983
984 public Calendar due() {
985 return getTimestamp();
986 }
987
988 public List<Action> getPersistentActions(){
989 return super.getActions();
990 }
991
992 /**
993 * Builds up a set of variable names that contain sensitive values that
994 * should not be exposed. The expectation is that this set is populated with
995 * keys returned by {@link #getBuildVariables()} that should have their
996 * values masked for display purposes.
997 *
998 * @since 1.378
999 */
1000 public Set<String> getSensitiveBuildVariables() {
1001 Set<String> s = new HashSet<String>();
1002
1003 ParametersAction parameters = getAction(ParametersAction.class);
1004 if (parameters != null) {
1005 for (ParameterValue p : parameters) {
1006 if (p.isSensitive()) {
1007 s.add(p.getName());
1008 }
1009 }
1010 }
1011
1012 // Allow BuildWrappers to determine if any of their data is sensitive
1013 if (project instanceof BuildableItemWithBuildWrappers) {
1014 for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList()) {
1015 bw.makeSensitiveBuildVariables(this, s);
1016 }
1017 }
1018
1019 return s;
1020 }
1021
1022 /**
1023 * Provides additional variables and their values to {@link Builder}s.
1024 *
1025 * <p>
1026 * This mechanism is used by {@link MatrixConfiguration} to pass
1027 * the configuration values to the current build. It is up to
1028 * {@link Builder}s to decide whether they want to recognize the values
1029 * or how to use them.
1030 *
1031 * <p>
1032 * This also includes build parameters if a build is parameterized.
1033 *
1034 * @return
1035 * The returned map is mutable so that subtypes can put more values.
1036 */
1037 public Map<String,String> getBuildVariables() {
1038 Map<String,String> r = new HashMap<String, String>();
1039
1040 ParametersAction parameters = getAction(ParametersAction.class);
1041 if (parameters!=null) {
1042 // this is a rather round about way of doing this...
1043 for (ParameterValue p : parameters) {
1044 String v = p.createVariableResolver(this).resolve(p.getName());
1045 if (v!=null) r.put(p.getName(),v);
1046 }
1047 }
1048
1049 // allow the BuildWrappers to contribute additional build variables
1050 if (project instanceof BuildableItemWithBuildWrappers) {
1051 for (BuildWrapper bw : ((BuildableItemWithBuildWrappers) project).getBuildWrappersList())
1052 bw.makeBuildVariables(this,r);
1053 }
1054
1055 for (BuildVariableContributor bvc : BuildVariableContributor.all())
1056 bvc.buildVariablesFor(this,r);
1057
1058 return r;
1059 }
1060
1061 /**
1062 * Creates {@link VariableResolver} backed by {@link #getBuildVariables()}.
1063 */
1064 public final VariableResolver<String> getBuildVariableResolver() {
1065 return new VariableResolver.ByMap<String>(getBuildVariables());
1066 }
1067
1068 /**
1069 * Gets {@link AbstractTestResultAction} associated with this build if any.
1070 */
1071 public AbstractTestResultAction getTestResultAction() {
1072 return getAction(AbstractTestResultAction.class);
1073 }
1074
1075 /**
1076 * Gets {@link AggregatedTestResultAction} associated with this build if any.
1077 */
1078 public AggregatedTestResultAction getAggregatedTestResultAction() {
1079 return getAction(AggregatedTestResultAction.class);
1080 }
1081
1082 /**
1083 * Invoked by {@link Executor} to performs a build.
1084 */
1085 public abstract void run();
1086
1087 //
1088 //
1089 // fingerprint related stuff
1090 //
1091 //
1092
1093 @Override
1094 public String getWhyKeepLog() {
1095 // if any of the downstream project is configured with 'keep dependency component',
1096 // we need to keep this log
1097 OUTER:
1098 for (AbstractProject<?,?> p : getParent().getDownstreamProjects()) {
1099 if (!p.isKeepDependencies()) continue;
1100
1101 AbstractBuild<?,?> fb = p.getFirstBuild();
1102 if (fb==null) continue; // no active record
1103
1104 // is there any active build that depends on us?
1105 for (int i : getDownstreamRelationship(p).listNumbersReverse()) {
1106 // TODO: this is essentially a "find intersection between two sparse sequences"
1107 // and we should be able to do much better.
1108
1109 if (i<fb.getNumber())
1110 continue OUTER; // all the other records are younger than the first record, so pointless to search.
1111
1112 AbstractBuild<?,?> b = p.getBuildByNumber(i);
1113 if (b!=null)
1114 return Messages.AbstractBuild_KeptBecause(b);
1115 }
1116 }
1117
1118 return super.getWhyKeepLog();
1119 }
1120
1121 /**
1122 * Gets the dependency relationship from this build (as the source)
1123 * and that project (as the sink.)
1124 *
1125 * @return
1126 * range of build numbers that represent which downstream builds are using this build.
1127 * 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.
1128 */
1129 public RangeSet getDownstreamRelationship(AbstractProject that) {
1130 RangeSet rs = new RangeSet();
1131
1132 FingerprintAction f = getAction(FingerprintAction.class);
1133 if (f==null) return rs;
1134
1135 // look for fingerprints that point to this build as the source, and merge them all
1136 for (Fingerprint e : f.getFingerprints().values()) {
1137
1138 if (upstreamCulprits) {
1139 // With upstreamCulprits, we allow downstream relationships
1140 // from intermediate jobs
1141 rs.add(e.getRangeSet(that));
1142 } else {
1143 BuildPtr o = e.getOriginal();
1144 if (o!=null && o.is(this))
1145 rs.add(e.getRangeSet(that));
1146 }
1147 }
1148
1149 return rs;
1150 }
1151
1152 /**
1153 * Works like {@link #getDownstreamRelationship(AbstractProject)} but returns
1154 * the actual build objects, in ascending order.
1155 * @since 1.150
1156 */
1157 public Iterable<AbstractBuild<?,?>> getDownstreamBuilds(final AbstractProject<?,?> that) {
1158 final Iterable<Integer> nums = getDownstreamRelationship(that).listNumbers();
1159
1160 return new Iterable<AbstractBuild<?, ?>>() {
1161 public Iterator<AbstractBuild<?, ?>> iterator() {
1162 return Iterators.removeNull(
1163 new AdaptedIterator<Integer,AbstractBuild<?,?>>(nums) {
1164 protected AbstractBuild<?, ?> adapt(Integer item) {
1165 return that.getBuildByNumber(item);
1166 }
1167 });
1168 }
1169 };
1170 }
1171
1172 /**
1173 * Gets the dependency relationship from this build (as the sink)
1174 * and that project (as the source.)
1175 *
1176 * @return
1177 * Build number of the upstream build that feed into this build,
1178 * or -1 if no record is available (for example if there is no {@link FingerprintAction}, even if there is an {@link Cause.UpstreamCause}).
1179 */
1180 public int getUpstreamRelationship(AbstractProject that) {
1181 FingerprintAction f = getAction(FingerprintAction.class);
1182 if (f==null) return -1;
1183
1184 int n = -1;
1185
1186 // look for fingerprints that point to the given project as the source, and merge them all
1187 for (Fingerprint e : f.getFingerprints().values()) {
1188 if (upstreamCulprits) {
1189 // With upstreamCulprits, we allow upstream relationships
1190 // from intermediate jobs
1191 Fingerprint.RangeSet rangeset = e.getRangeSet(that);
1192 if (!rangeset.isEmpty()) {
1193 n = Math.max(n, rangeset.listNumbersReverse().iterator().next());
1194 }
1195 } else {
1196 BuildPtr o = e.getOriginal();
1197 if (o!=null && o.belongsTo(that))
1198 n = Math.max(n,o.getNumber());
1199 }
1200 }
1201
1202 return n;
1203 }
1204
1205 /**
1206 * Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the
1207 * actual build object.
1208 *
1209 * @return
1210 * null if no such upstream build was found, or it was found but the
1211 * build record is already lost.
1212 */
1213 public AbstractBuild<?,?> getUpstreamRelationshipBuild(AbstractProject<?,?> that) {
1214 int n = getUpstreamRelationship(that);
1215 if (n==-1) return null;
1216 return that.getBuildByNumber(n);
1217 }
1218
1219 /**
1220 * Gets the downstream builds of this build, which are the builds of the
1221 * downstream projects that use artifacts of this build.
1222 *
1223 * @return
1224 * For each project with fingerprinting enabled, returns the range
1225 * of builds (which can be empty if no build uses the artifact from this build or downstream is not {@link AbstractProject#isFingerprintConfigured}.)
1226 */
1227 public Map<AbstractProject,RangeSet> getDownstreamBuilds() {
1228 Map<AbstractProject,RangeSet> r = new HashMap<AbstractProject,RangeSet>();
1229 for (AbstractProject p : getParent().getDownstreamProjects()) {
1230 if (p.isFingerprintConfigured())
1231 r.put(p,getDownstreamRelationship(p));
1232 }
1233 return r;
1234 }
1235
1236 /**
1237 * Gets the upstream builds of this build, which are the builds of the
1238 * upstream projects whose artifacts feed into this build.
1239 * @return empty if there is no {@link FingerprintAction} (even if there is an {@link Cause.UpstreamCause})
1240 * @see #getTransitiveUpstreamBuilds()
1241 */
1242 public Map<AbstractProject,Integer> getUpstreamBuilds() {
1243 return _getUpstreamBuilds(getParent().getUpstreamProjects());
1244 }
1245
1246 /**
1247 * Works like {@link #getUpstreamBuilds()} but also includes all the transitive
1248 * dependencies as well.
1249 */
1250 public Map<AbstractProject,Integer> getTransitiveUpstreamBuilds() {
1251 return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects());
1252 }
1253
1254 private Map<AbstractProject, Integer> _getUpstreamBuilds(Collection<AbstractProject> projects) {
1255 Map<AbstractProject,Integer> r = new HashMap<AbstractProject,Integer>();
1256 for (AbstractProject p : projects) {
1257 int n = getUpstreamRelationship(p);
1258 if (n>=0)
1259 r.put(p,n);
1260 }
1261 return r;
1262 }
1263
1264 /**
1265 * Gets the changes in the dependency between the given build and this build.
1266 * @return empty if there is no {@link FingerprintAction}
1267 */
1268 public Map<AbstractProject,DependencyChange> getDependencyChanges(AbstractBuild from) {
1269 if (from==null) return Collections.emptyMap(); // make it easy to call this from views
1270 FingerprintAction n = this.getAction(FingerprintAction.class);
1271 FingerprintAction o = from.getAction(FingerprintAction.class);
1272 if (n==null || o==null) return Collections.emptyMap();
1273
1274 Map<AbstractProject,Integer> ndep = n.getDependencies(true);
1275 Map<AbstractProject,Integer> odep = o.getDependencies(true);
1276
1277 Map<AbstractProject,DependencyChange> r = new HashMap<AbstractProject,DependencyChange>();
1278
1279 for (Map.Entry<AbstractProject,Integer> entry : odep.entrySet()) {
1280 AbstractProject p = entry.getKey();
1281 Integer oldNumber = entry.getValue();
1282 Integer newNumber = ndep.get(p);
1283 if (newNumber!=null && oldNumber.compareTo(newNumber)<0) {
1284 r.put(p,new DependencyChange(p,oldNumber,newNumber));
1285 }
1286 }
1287
1288 return r;
1289 }
1290
1291 /**
1292 * Represents a change in the dependency.
1293 */
1294 public static final class DependencyChange {
1295 /**
1296 * The dependency project.
1297 */
1298 public final AbstractProject project;
1299 /**
1300 * Version of the dependency project used in the previous build.
1301 */
1302 public final int fromId;
1303 /**
1304 * {@link Build} object for {@link #fromId}. Can be null if the log is gone.
1305 */
1306 public final AbstractBuild from;
1307 /**
1308 * Version of the dependency project used in this build.
1309 */
1310 public final int toId;
1311
1312 public final AbstractBuild to;
1313
1314 public DependencyChange(AbstractProject<?,?> project, int fromId, int toId) {
1315 this.project = project;
1316 this.fromId = fromId;
1317 this.toId = toId;
1318 this.from = project.getBuildByNumber(fromId);
1319 this.to = project.getBuildByNumber(toId);
1320 }
1321
1322 /**
1323 * Gets the {@link AbstractBuild} objects (fromId,toId].
1324 * <p>
1325 * This method returns all such available builds in the ascending order
1326 * of IDs, but due to log rotations, some builds may be already unavailable.
1327 */
1328 public List<AbstractBuild> getBuilds() {
1329 List<AbstractBuild> r = new ArrayList<AbstractBuild>();
1330
1331 AbstractBuild<?,?> b = (AbstractBuild)project.getNearestBuild(fromId);
1332 if (b!=null && b.getNumber()==fromId)
1333 b = b.getNextBuild(); // fromId exclusive
1334
1335 while (b!=null && b.getNumber()<=toId) {
1336 r.add(b);
1337 b = b.getNextBuild();
1338 }
1339
1340 return r;
1341 }
1342 }
1343
1344 //
1345 // web methods
1346 //
1347
1348 /**
1349 * @deprecated as of 1.489
1350 * Use {@link #doStop()}
1351 */
1352 public void doStop(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
1353 doStop().generateResponse(req,rsp,this);
1354 }
1355
1356 /**
1357 * Stops this build if it's still going.
1358 *
1359 * If we use this/executor/stop URL, it causes 404 if the build is already killed,
1360 * as {@link #getExecutor()} returns null.
1361 *
1362 * @since 1.489
1363 */
1364 @RequirePOST
1365 public synchronized HttpResponse doStop() throws IOException, ServletException {
1366 Executor e = getExecutor();
1367 if (e==null)
1368 e = getOneOffExecutor();
1369 if (e!=null)
1370 return e.doStop();
1371 else
1372 // nothing is building
1373 return HttpResponses.forwardToPreviousPage();
1374 }
1375
1376 private static final Logger LOGGER = Logger.getLogger(AbstractBuild.class.getName());
1377 }
1378
1379
1380