AbstractProject.java 82.4 KB
Newer Older
K
kohsuke 已提交
1 2
/*
 * The MIT License
3
 *
4
 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5
 * Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot,
6
 * Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts,
7 8
 * id:cactusman, Yahoo! Inc., Andrew Bayer, Manufacture Francaise des Pneumatiques
 * Michelin, Romain Seguy
9
 *
K
kohsuke 已提交
10 11 12 13 14 15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
16
 *
K
kohsuke 已提交
17 18
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
19
 *
K
kohsuke 已提交
20 21 22 23 24 25 26 27
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
28 29
package hudson.model;

30
import antlr.ANTLRException;
31
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
K
kohsuke 已提交
32
import hudson.AbortException;
33
import hudson.CopyOnWrite;
34
import hudson.EnvVars;
35
import hudson.ExtensionList;
36
import hudson.ExtensionPoint;
37
import hudson.FeedAdapter;
38
import hudson.FilePath;
39
import hudson.Functions;
40
import hudson.Launcher;
41
import hudson.Util;
42
import hudson.cli.declarative.CLIMethod;
43
import hudson.cli.declarative.CLIResolver;
M
mdonohue 已提交
44
import hudson.model.Cause.LegacyCodeCause;
45 46
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
M
Mads Nielsen 已提交
47
import hudson.model.Node.Mode;
K
kohsuke 已提交
48
import hudson.model.Queue.Executable;
49
import hudson.model.Queue.Task;
50 51
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
52
import hudson.model.listeners.ItemListener;
53
import hudson.model.listeners.SCMPollListener;
54
import hudson.model.queue.CauseOfBlockage;
55 56
import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.SubTask;
57
import hudson.model.queue.SubTaskContributor;
K
kohsuke 已提交
58
import hudson.scm.ChangeLogSet;
K
kohsuke 已提交
59
import hudson.scm.ChangeLogSet.Entry;
60
import hudson.scm.NullSCM;
61
import hudson.scm.PollingResult;
62 63

import static hudson.scm.PollingResult.*;
64
import hudson.scm.SCM;
65
import hudson.scm.SCMRevisionState;
66
import hudson.scm.SCMS;
J
jbq 已提交
67
import hudson.search.SearchIndexBuilder;
68
import hudson.security.ACL;
K
kohsuke 已提交
69
import hudson.security.Permission;
70
import hudson.slaves.Cloud;
71
import hudson.slaves.WorkspaceList;
72
import hudson.tasks.BuildStep;
73
import hudson.tasks.BuildStepDescriptor;
J
jbq 已提交
74
import hudson.tasks.BuildTrigger;
75
import hudson.tasks.BuildWrapperDescriptor;
76
import hudson.tasks.Publisher;
K
kohsuke 已提交
77
import hudson.triggers.SCMTrigger;
78
import hudson.triggers.Trigger;
79
import hudson.triggers.TriggerDescriptor;
80 81
import hudson.util.AlternativeUiTextProvider;
import hudson.util.AlternativeUiTextProvider.Message;
82
import hudson.util.DescribableList;
S
 
shinodkm 已提交
83
import hudson.util.FormValidation;
K
Kohsuke Kawaguchi 已提交
84
import hudson.util.TimeUnit2;
K
kohsuke 已提交
85
import hudson.widgets.HistoryWidget;
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
108
import jenkins.model.BlockedBecauseOfBuildInProgress;
109
import jenkins.model.Jenkins;
110
import jenkins.model.JenkinsLocationConfiguration;
111
import jenkins.model.ParameterizedJobMixIn;
K
Kohsuke Kawaguchi 已提交
112
import jenkins.model.Uptime;
J
Jesse Glick 已提交
113
import jenkins.model.lazy.LazyBuildMixIn;
114 115 116
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
117
import jenkins.scm.SCMDecisionHandler;
118
import jenkins.util.TimeDuration;
K
kohsuke 已提交
119
import net.sf.json.JSONObject;
120
import org.acegisecurity.Authentication;
K
Kohsuke Kawaguchi 已提交
121
import org.jenkinsci.bytecode.AdaptField;
122
import org.kohsuke.accmod.Restricted;
123
import org.kohsuke.accmod.restrictions.DoNotUse;
124
import org.kohsuke.accmod.restrictions.NoExternalUse;
125 126
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
127
import org.kohsuke.stapler.AncestorInPath;
128 129 130 131
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
K
kohsuke 已提交
132 133 134
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
135
import org.kohsuke.stapler.interceptor.RequirePOST;
136 137 138

/**
 * Base implementation of {@link Job}s that build software.
139
 *
140
 * For now this is primarily the common part of {@link Project} and MavenModule.
141
 *
142 143 144
 * @author Kohsuke Kawaguchi
 * @see AbstractBuild
 */
C
Christoph Kutzinski 已提交
145
@SuppressWarnings("rawtypes")
146
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem, LazyBuildMixIn.LazyLoadingJob<P,R>, ParameterizedJobMixIn.ParameterizedJob {
147

148
    /**
149 150 151
     * {@link SCM} associated with the project.
     * To allow derived classes to link {@link SCM} config to elsewhere,
     * access to this variable should always go through {@link #getScm()}.
152
     */
K
kohsuke 已提交
153
    private volatile SCM scm = new NullSCM();
154

155 156 157 158 159
    /**
     * Controls how the checkout is done.
     */
    private volatile SCMCheckoutStrategy scmCheckoutStrategy;

160 161 162
    /**
     * State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
     */
163
    private volatile transient SCMRevisionState pollingBaseline = null;
164

J
Jesse Glick 已提交
165 166
    private transient LazyBuildMixIn<P,R> buildMixIn;

167 168
    /**
     * All the builds keyed by their build number.
J
Jesse Glick 已提交
169
     * Kept here for binary compatibility only; otherwise use {@link #buildMixIn}.
170 171
     * External code should use {@link #getBuildByNumber(int)} or {@link #getLastBuild()} and traverse via
     * {@link Run#getPreviousBuild()}
172
     */
173
    @Restricted(NoExternalUse.class)
J
Jesse Glick 已提交
174
    protected transient RunMap<R> builds;
175 176 177 178

    /**
     * The quiet period. Null to delegate to the system default.
     */
K
kohsuke 已提交
179
    private volatile Integer quietPeriod = null;
180

S
 
shinodkm 已提交
181
    /**
182
     * The retry count. Null to delegate to the system default.
S
 
shinodkm 已提交
183
     */
184
    private volatile Integer scmCheckoutRetryCount = null;
185 186

    /**
187 188 189 190 191 192 193
     * If this project is configured to be only built on a certain label,
     * this value will be set to that label.
     *
     * For historical reasons, this is called 'assignedNode'. Also for
     * a historical reason, null to indicate the affinity
     * with the master node.
     *
194
     * @see #canRoam
195 196 197 198 199
     */
    private String assignedNode;

    /**
     * True if this project can be built on any node.
200
     *
201
     * <p>
202 203
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
204
     */
K
kohsuke 已提交
205
    private volatile boolean canRoam;
206 207 208 209

    /**
     * True to suspend new builds.
     */
K
kohsuke 已提交
210
    protected volatile boolean disabled;
211

212 213 214 215 216 217
    /**
     * True to keep builds of this project in queue when downstream projects are
     * building. False by default to keep from breaking existing behavior.
     */
    protected volatile boolean blockBuildWhenDownstreamBuilding = false;

218 219 220 221 222 223
    /**
     * True to keep builds of this project in queue when upstream projects are
     * building. False by default to keep from breaking existing behavior.
     */
    protected volatile boolean blockBuildWhenUpstreamBuilding = false;

224
    /**
225 226 227
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     *
228
     * <p>
229
     * Can't store {@link JDK} directly because {@link Jenkins} and {@link Project}
230 231
     * are saved independently.
     *
232
     * @see Jenkins#getJDK(String)
233
     */
K
kohsuke 已提交
234
    private volatile String jdk;
235

K
kohsuke 已提交
236
    private volatile BuildAuthorizationToken authToken = null;
237

238 239 240
    /**
     * List of all {@link Trigger}s for this project.
     */
K
Kohsuke Kawaguchi 已提交
241
    @AdaptField(was=List.class)
K
Kohsuke Kawaguchi 已提交
242
    protected volatile DescribableList<Trigger<?>,TriggerDescriptor> triggers = new DescribableList<Trigger<?>,TriggerDescriptor>(this);
K
Kohsuke Kawaguchi 已提交
243
    private static final AtomicReferenceFieldUpdater<AbstractProject,DescribableList> triggersUpdater
K
bug fix  
Kohsuke Kawaguchi 已提交
244
            = AtomicReferenceFieldUpdater.newUpdater(AbstractProject.class,DescribableList.class,"triggers");
245

246 247
    /**
     * {@link Action}s contributed from subsidiary objects associated with
248 249 250 251
     * {@link AbstractProject}, such as from triggers, builders, publishers, etc.
     *
     * We don't want to persist them separately, and these actions
     * come and go as configuration change, so it's kept separate.
252
     */
253 254
    @CopyOnWrite
    protected transient volatile List<Action> transientActions = new Vector<Action>();
255

K
kohsuke 已提交
256 257
    private boolean concurrentBuild;

258 259 260
    /**
     * See {@link #setCustomWorkspace(String)}.
     *
261
     * @since 1.410
262 263
     */
    private String customWorkspace;
264

265
    protected AbstractProject(ItemGroup parent, String name) {
266
        super(parent,name);
267 268
        buildMixIn = createBuildMixIn();
        builds = buildMixIn.getRunMap();
269

270 271 272
        final Jenkins j = Jenkins.getInstance();
        final List<Node> nodes = j != null ? j.getNodes() : null;
        if(nodes!=null && !nodes.isEmpty()) {
273
            // if a new job is configured with Hudson that already has agent nodes
274 275 276
            // make it roamable by default
            canRoam = true;
        }
277 278
    }

279
    private LazyBuildMixIn<P,R> createBuildMixIn() {
J
Jesse Glick 已提交
280 281 282 283 284
        return new LazyBuildMixIn<P,R>() {
            @SuppressWarnings("unchecked") // untypable
            @Override protected P asJob() {
                return (P) AbstractProject.this;
            }
J
Jesse Glick 已提交
285 286 287 288 289 290
            @Override protected Class<R> getBuildClass() {
                return AbstractProject.this.getBuildClass();
            }
        };
    }

291 292 293 294
    @Override public LazyBuildMixIn<P,R> getLazyBuildMixIn() {
        return buildMixIn;
    }

295 296 297 298 299 300 301
    private ParameterizedJobMixIn<P,R> getParameterizedJobMixIn() {
        return new ParameterizedJobMixIn<P,R>() {
            @SuppressWarnings("unchecked") // untypable
            @Override protected P asJob() {
                return (P) AbstractProject.this;
            }
        };
302 303
    }

304 305 306 307 308 309
    @Override
    public synchronized void save() throws IOException {
        super.save();
        updateTransientActions();
    }

310 311 312
    @Override
    public void onCreatedFromScratch() {
        super.onCreatedFromScratch();
J
Jesse Glick 已提交
313 314
        buildMixIn.onCreatedFromScratch();
        builds = buildMixIn.getRunMap();
315 316
        // solicit initial contributions, especially from TransientProjectActionFactory
        updateTransientActions();
317 318
    }

319
    @Override
320
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
321
        super.onLoad(parent, name);
322 323 324
        if (buildMixIn == null) {
            buildMixIn = createBuildMixIn();
        }
325 326
        buildMixIn.onLoad(parent, name);
        builds = buildMixIn.getRunMap();
K
Kohsuke Kawaguchi 已提交
327
        triggers().setOwner(this);
328 329 330 331 332
        for (Trigger t : triggers()) {
            try {
                t.start(this, Items.currentlyUpdatingByXml());
            } catch (Throwable e) {
                LOGGER.log(Level.WARNING, "could not start trigger while loading project '" + getFullName() + "'", e);
333
            }
334
        }
335 336
        if(scm==null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
337

338 339
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
340
        updateTransientActions();
341 342
    }

K
Kohsuke Kawaguchi 已提交
343 344
    @WithBridgeMethods(List.class)
    protected DescribableList<Trigger<?>,TriggerDescriptor> triggers() {
345
        if (triggers == null) {
K
Kohsuke Kawaguchi 已提交
346
            triggersUpdater.compareAndSet(this,null,new DescribableList<Trigger<?>,TriggerDescriptor>(this));
347 348 349 350
        }
        return triggers;
    }

351 352 353 354
    @Override
    public EnvVars getEnvironment(Node node, TaskListener listener) throws IOException, InterruptedException {
        EnvVars env =  super.getEnvironment(node, listener);

355 356
        JDK jdkTool = getJDK();
        if (jdkTool != null) {
357
            if (node != null) { // just in case were not in a build
358
                jdkTool = jdkTool.forNode(node, listener);
359
            }
360
            jdkTool.buildEnvVars(env);
361
        } else if (!JDK.isDefaultName(jdk)) {
362
            listener.getLogger().println("No JDK named ‘" + jdk + "’ found");
363 364 365 366 367
        }

        return env;
    }

368
    @Override
369
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
370 371
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
372
        FilePath ws = getWorkspace();
373
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
374 375 376 377
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
378
        }
K
kohsuke 已提交
379 380 381
        super.performDelete();
    }

K
kohsuke 已提交
382 383
    /**
     * Does this project perform concurrent builds?
K
kohsuke 已提交
384
     * @since 1.319
K
kohsuke 已提交
385
     */
K
kohsuke 已提交
386
    @Exported
K
kohsuke 已提交
387
    public boolean isConcurrentBuild() {
388
        return concurrentBuild;
K
kohsuke 已提交
389 390 391 392 393 394 395
    }

    public void setConcurrentBuild(boolean b) throws IOException {
        concurrentBuild = b;
        save();
    }

396
    /**
397 398
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
399
     */
400
    public @CheckForNull Label getAssignedLabel() {
401
        if(canRoam)
402 403
            return null;

404
        if(assignedNode==null)
405 406
            return Jenkins.getInstance().getSelfLabel();
        return Jenkins.getInstance().getLabel(assignedNode);
407 408
    }

409 410 411
    /**
     * Set of labels relevant to this job.
     *
412
     * This method is used to determine what agents are relevant to jobs, for example by {@link View}s.
413 414 415 416 417 418 419 420 421 422 423
     * It does not affect the scheduling. This information is informational and the best-effort basis.
     *
     * @since 1.456
     * @return
     *      Minimally it should contain {@link #getAssignedLabel()}. The set can contain null element
     *      to correspond to the null return value from {@link #getAssignedLabel()}.
     */
    public Set<Label> getRelevantLabels() {
        return Collections.singleton(getAssignedLabel());
    }

424 425 426 427
    /**
     * Gets the textual representation of the assigned label as it was entered by the user.
     */
    public String getAssignedLabelString() {
428 429 430 431 432 433 434 435
        if (canRoam || assignedNode==null)    return null;
        try {
            LabelExpression.parseExpression(assignedNode);
            return assignedNode;
        } catch (ANTLRException e) {
            // must be old label or host name that includes whitespace or other unsafe chars
            return LabelAtom.escape(assignedNode);
        }
436 437
    }

K
kohsuke 已提交
438 439 440 441 442 443 444 445 446
    /**
     * Sets the assigned label.
     */
    public void setAssignedLabel(Label l) throws IOException {
        if(l==null) {
            canRoam = true;
            assignedNode = null;
        } else {
            canRoam = false;
447
            if(l== Jenkins.getInstance().getSelfLabel())  assignedNode = null;
448
            else                                        assignedNode = l.getExpression();
K
kohsuke 已提交
449 450 451 452
        }
        save();
    }

453 454 455 456 457 458 459
    /**
     * Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
     */
    public void setAssignedNode(Node l) throws IOException {
        setAssignedLabel(l.getSelfLabel());
    }

460
    /**
461 462
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
463 464 465
     */
    @Override
    public String getPronoun() {
K
Kohsuke Kawaguchi 已提交
466
        return AlternativeUiTextProvider.get(PRONOUN, this,Messages.AbstractProject_Pronoun());
467 468
    }

469 470 471 472 473 474
    /**
     * Gets the human readable display name to be rendered in the "Build Now" link.
     *
     * @since 1.401
     */
    public String getBuildNowText() {
475
        // For compatibility, still use the deprecated replacer if specified.
476
        return AlternativeUiTextProvider.get(BUILD_NOW_TEXT, this, getParameterizedJobMixIn().getBuildNowText());
477 478
    }

479
    /**
480
     * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}.
481
     *
482 483 484 485 486 487 488
     * <p>
     * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs
     * that acts as a single unit. This method can be used to find the top most dominating job that
     * covers such a tree.
     *
     * @return never null.
     * @see AbstractBuild#getRootBuild()
489
     */
490 491
    public AbstractProject<?,?> getRootProject() {
        if (this instanceof TopLevelItem) {
492 493
            return this;
        } else {
494 495 496 497
            ItemGroup p = this.getParent();
            if (p instanceof AbstractProject)
                return ((AbstractProject) p).getRootProject();
            return this;
498 499 500
        }
    }

501 502
    /**
     * Gets the directory where the module is checked out.
503 504
     *
     * @return
505
     *      null if the workspace is on an agent that's not connected.
K
kohsuke 已提交
506
     * @deprecated as of 1.319
K
kohsuke 已提交
507 508 509 510 511
     *      To support concurrent builds of the same project, this method is moved to {@link AbstractBuild}.
     *      For backward compatibility, this method returns the right {@link AbstractBuild#getWorkspace()} if called
     *      from {@link Executor}, and otherwise the workspace of the last build.
     *
     *      <p>
512
     *      If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
K
kohsuke 已提交
513 514 515
     *      If you are calling this method to serve a file from the workspace, doing a form validation, etc., then
     *      use {@link #getSomeWorkspace()}
     */
516
    @Deprecated
K
kohsuke 已提交
517
    public final FilePath getWorkspace() {
518 519 520 521
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getWorkspace() : null;

    }
522

523 524 525
    /**
     * Various deprecated methods in this class all need the 'current' build.  This method returns
     * the build suitable for that purpose.
526
     *
527 528 529
     * @return An AbstractBuild for deprecated methods to use.
     */
    private AbstractBuild getBuildForDeprecatedMethods() {
K
kohsuke 已提交
530 531 532 533 534 535
        Executor e = Executor.currentExecutor();
        if(e!=null) {
            Executable exe = e.getCurrentExecutable();
            if (exe instanceof AbstractBuild) {
                AbstractBuild b = (AbstractBuild) exe;
                if(b.getProject()==this)
536
                    return b;
K
kohsuke 已提交
537 538 539
            }
        }
        R lb = getLastBuild();
540
        if(lb!=null)    return lb;
K
kohsuke 已提交
541 542 543 544 545 546 547 548 549 550 551 552 553
        return null;
    }

    /**
     * Gets a workspace for some build of this project.
     *
     * <p>
     * This is useful for obtaining a workspace for the purpose of form field validation, where exactly
     * which build the workspace belonged is less important. The implementation makes a cursory effort
     * to find some workspace.
     *
     * @return
     *      null if there's no available workspace.
K
kohsuke 已提交
554
     * @since 1.319
555
     */
J
Jesse Glick 已提交
556
    public final @CheckForNull FilePath getSomeWorkspace() {
557
        R b = getSomeBuildWithWorkspace();
558
        if (b!=null) return b.getWorkspace();
559
        for (WorkspaceBrowser browser : ExtensionList.lookup(WorkspaceBrowser.class)) {
560 561 562 563
            FilePath f = browser.getWorkspace(this);
            if (f != null) return f;
        }
        return null;
564 565 566 567 568 569 570 571
    }

    /**
     * Gets some build that has a live workspace.
     *
     * @return null if no such build exists.
     */
    public final R getSomeBuildWithWorkspace() {
K
kohsuke 已提交
572 573 574
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
575
            if (ws!=null)   return b;
K
kohsuke 已提交
576 577 578
        }
        return null;
    }
579

580 581 582 583 584 585 586 587
    private R getSomeBuildWithExistingWorkspace() throws IOException, InterruptedException {
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
            if (ws!=null && ws.exists())   return b;
        }
        return null;
    }
588

589 590 591
    /**
     * Returns the root directory of the checked-out module.
     * <p>
592 593
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
K
kohsuke 已提交
594
     *
K
kohsuke 已提交
595
     * @deprecated as of 1.319
K
kohsuke 已提交
596
     *      See {@link #getWorkspace()} for a migration strategy.
597
     */
598
    @Deprecated
599
    public FilePath getModuleRoot() {
600 601
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoot() : null;
602 603
    }

S
stephenconnolly 已提交
604 605 606 607 608 609
    /**
     * Returns the root directories of all checked-out modules.
     * <p>
     * Some SCMs support checking out multiple modules into the same workspace.
     * In these cases, the returned array will have a length greater than one.
     * @return The roots of all modules checked out from the SCM.
K
kohsuke 已提交
610
     *
K
kohsuke 已提交
611
     * @deprecated as of 1.319
K
kohsuke 已提交
612
     *      See {@link #getWorkspace()} for a migration strategy.
S
stephenconnolly 已提交
613
     */
614
    @Deprecated
S
stephenconnolly 已提交
615
    public FilePath[] getModuleRoots() {
616 617
        AbstractBuild b = getBuildForDeprecatedMethods();
        return b != null ? b.getModuleRoots() : null;
S
stephenconnolly 已提交
618 619
    }

620
    public int getQuietPeriod() {
621
        return quietPeriod!=null ? quietPeriod : Jenkins.getInstance().getQuietPeriod();
622
    }
623

624
    public SCMCheckoutStrategy getScmCheckoutStrategy() {
625 626 627
        return scmCheckoutStrategy == null ? new DefaultSCMCheckoutStrategyImpl() : scmCheckoutStrategy;
    }

628
    public void setScmCheckoutStrategy(SCMCheckoutStrategy scmCheckoutStrategy) throws IOException {
629 630 631 632 633
        this.scmCheckoutStrategy = scmCheckoutStrategy;
        save();
    }


634
    public int getScmCheckoutRetryCount() {
635
        return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.getInstance().getScmCheckoutRetryCount();
S
 
shinodkm 已提交
636
    }
637 638 639

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
640
        return quietPeriod!=null;
641
    }
K
kohsuke 已提交
642 643

    /**
644
     * Sets the custom quiet period of this project, or revert to the global default if null is given.
K
kohsuke 已提交
645
     */
646
    public void setQuietPeriod(Integer seconds) throws IOException {
K
kohsuke 已提交
647 648 649
        this.quietPeriod = seconds;
        save();
    }
650

651
    public boolean hasCustomScmCheckoutRetryCount(){
652
        return scmCheckoutRetryCount != null;
S
 
shinodkm 已提交
653
    }
654

655
    @Override
656
    public boolean isBuildable() {
J
jpederzolli 已提交
657
        return !isDisabled() && !isHoldOffBuildUntilSave();
658 659
    }

660
    /**
661 662
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
663 664 665 666 667
     */
    public boolean isConfigurable() {
        return true;
    }

668 669 670 671 672 673 674 675 676
    public boolean blockBuildWhenDownstreamBuilding() {
        return blockBuildWhenDownstreamBuilding;
    }

    public void setBlockBuildWhenDownstreamBuilding(boolean b) throws IOException {
        blockBuildWhenDownstreamBuilding = b;
        save();
    }

677
    public boolean blockBuildWhenUpstreamBuilding() {
678
        return blockBuildWhenUpstreamBuilding;
679 680
    }

681
    public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
682 683
        blockBuildWhenUpstreamBuilding = b;
        save();
684 685
    }

686 687 688
    public boolean isDisabled() {
        return disabled;
    }
689

S
 
shinodkm 已提交
690 691 692 693
    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
694 695 696 697 698
        // retry count is optional so this is ok
        if(value == null || value.trim().equals(""))
            return FormValidation.ok();
        if (!value.matches("[0-9]*")) {
            return FormValidation.error("Invalid retry count");
699
        }
700
        return FormValidation.ok();
S
 
shinodkm 已提交
701
    }
702

703 704
    /**
     * Marks the build as disabled.
705 706
     * The method will ignore the disable command if {@link #supportsMakeDisabled()}
     * returns false. The enable command will be executed in any case.
707
     * @param b true - disable, false - enable
O
Oleg Nenashev 已提交
708
     * @since 1.585 Do not disable projects if {@link #supportsMakeDisabled()} returns false
709 710
     */
    public void makeDisabled(boolean b) throws IOException {
711
        if(disabled==b)     return; // noop
712
        if (b && !supportsMakeDisabled()) return; // do nothing if the disabling is unsupported
713
        this.disabled = b;
K
bug fix  
kohsuke 已提交
714
        if(b)
715
            Jenkins.getInstance().getQueue().cancel(this);
716

717
        save();
R
rednuht 已提交
718
        ItemListener.fireOnUpdated(this);
719 720
    }

721 722
    /**
     * Specifies whether this project may be disabled by the user.
723 724
     * By default, it can be only if this is a {@link TopLevelItem};
     * would be false for matrix configurations, etc.
725 726 727 728
     * @return true if the GUI should allow {@link #doDisable} and the like
     * @since 1.475
     */
    public boolean supportsMakeDisabled() {
729
        return this instanceof TopLevelItem;
730 731
    }

732 733 734 735 736 737 738 739
    public void disable() throws IOException {
        makeDisabled(true);
    }

    public void enable() throws IOException {
        makeDisabled(false);
    }

K
kohsuke 已提交
740 741
    @Override
    public BallColor getIconColor() {
742
        if(isDisabled())
743
            return isBuilding() ? BallColor.DISABLED_ANIME : BallColor.DISABLED;
K
kohsuke 已提交
744 745 746
        else
            return super.getIconColor();
    }
747

748 749 750 751 752 753 754
    /**
     * effectively deprecated. Since using updateTransientActions correctly
     * under concurrent environment requires a lock that can too easily cause deadlocks.
     *
     * <p>
     * Override {@link #createTransientActions()} instead.
     */
755
    protected void updateTransientActions() {
756 757 758 759
        transientActions = createTransientActions();
    }

    protected List<Action> createTransientActions() {
760
        Vector<Action> ta = new Vector<Action>();
761

K
Kohsuke Kawaguchi 已提交
762
        for (JobProperty<? super P> p : Util.fixNull(properties))
763
            ta.addAll(p.getJobActions((P)this));
764

765 766 767 768
        for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all()) {
            try {
                ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
            } catch (Exception e) {
769
                LOGGER.log(Level.SEVERE, "Could not load actions from " + tpaf + " for " + this, e);
770 771
            }
        }
772
        return ta;
773 774
    }

775
    /**
776 777
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
778
     * <p>
779 780
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
781
     */
782
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
783

K
kohsuke 已提交
784 785 786 787 788 789
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

790
    public List<ProminentProjectAction> getProminentActions() {
791
        return getActions(ProminentProjectAction.class);
792 793
    }

794
    @Override
795
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
796
        super.doConfigSubmit(req,rsp);
797

798 799
        updateTransientActions();

800
        // notify the queue as the project might be now tied to different node
801
        Jenkins.getInstance().getQueue().scheduleMaintenance();
802 803

        // this is to reflect the upstream build adjustments done above
804
        Jenkins.getInstance().rebuildDependencyGraphAsync();
805 806
    }

807
    /**
M
mdonohue 已提交
808 809 810
	 * @deprecated
	 *    Use {@link #scheduleBuild(Cause)}.  Since 1.283
	 */
811
    @Deprecated
M
mdonohue 已提交
812
    public boolean scheduleBuild() {
813
    	return getParameterizedJobMixIn().scheduleBuild();
M
mdonohue 已提交
814
    }
815

M
mdonohue 已提交
816 817 818 819
	/**
	 * @deprecated
	 *    Use {@link #scheduleBuild(int, Cause)}.  Since 1.283
	 */
820
    @Deprecated
M
mdonohue 已提交
821
    public boolean scheduleBuild(int quietPeriod) {
822
    	return getParameterizedJobMixIn().scheduleBuild(quietPeriod);
M
mdonohue 已提交
823
    }
824

825 826
    /**
     * Schedules a build of this project.
827 828
     *
     * @return
K
Kohsuke Kawaguchi 已提交
829 830
     *      true if the project is added to the queue.
     *      false if the task was rejected from the queue (such as when the system is being shut down.)
831
     */
M
mdonohue 已提交
832
    public boolean scheduleBuild(Cause c) {
833
        return getParameterizedJobMixIn().scheduleBuild(c);
K
kohsuke 已提交
834 835
    }

M
mdonohue 已提交
836
    public boolean scheduleBuild(int quietPeriod, Cause c) {
837
        return getParameterizedJobMixIn().scheduleBuild(quietPeriod, c);
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
    }

    /**
     * Schedules a build.
     *
     * Important: the actions should be persistable without outside references (e.g. don't store
     * references to this project). To provide parameters for a parameterized project, add a ParametersAction. If
     * no ParametersAction is provided for such a project, one will be created with the default parameter values.
     *
     * @param quietPeriod the quiet period to observer
     * @param c the cause for this build which should be recorded
     * @param actions a list of Actions that will be added to the build
     * @return whether the build was actually scheduled
     */
    public boolean scheduleBuild(int quietPeriod, Cause c, Action... actions) {
853 854 855 856 857 858
        return scheduleBuild2(quietPeriod,c,actions)!=null;
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
859 860 861
     *
     * @param actions
     *      For the convenience of the caller, this array can contain null, and those will be silently ignored.
862
     */
863 864
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
K
kohsuke 已提交
865 866 867 868 869 870 871 872 873 874 875
        return scheduleBuild2(quietPeriod,c,Arrays.asList(actions));
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * @param actions
     *      For the convenience of the caller, this collection can contain null, and those will be silently ignored.
     * @since 1.383
     */
C
Christoph Kutzinski 已提交
876
    @SuppressWarnings("unchecked")
877 878
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c, Collection<? extends Action> actions) {
K
kohsuke 已提交
879
        List<Action> queueActions = new ArrayList<Action>(actions);
S
sogabe 已提交
880 881 882
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }
883
        return getParameterizedJobMixIn().scheduleBuild2(quietPeriod, queueActions.toArray(new Action[queueActions.size()]));
K
kohsuke 已提交
884 885
    }

886
    /**
887 888 889 890
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
891
     * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
892
     * as deprecated.
893
     */
C
Christoph Kutzinski 已提交
894
    @SuppressWarnings("deprecation")
895 896
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod) {
897
        return scheduleBuild2(quietPeriod, new LegacyCodeCause());
M
mdonohue 已提交
898
    }
899

K
kohsuke 已提交
900
    /**
901 902
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
903
     */
904 905
    @WithBridgeMethods(Future.class)
    public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c) {
906 907 908
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

909 910 911 912
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
913
        if(isDisabled())    return false;
914
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
915
        if(scmt==null)      return false;
916 917 918 919
        scmt.run();
        return true;
    }

920 921 922 923 924
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
925
        return Jenkins.getInstance().getQueue().contains(this);
926 927
    }

K
kohsuke 已提交
928 929
    @Override
    public Queue.Item getQueueItem() {
930
        return Jenkins.getInstance().getQueue().getItem(this);
K
kohsuke 已提交
931 932
    }

K
kohsuke 已提交
933 934 935
    /**
     * Gets the JDK that this project is configured with, or null.
     */
936
    public JDK getJDK() {
937
        return Jenkins.getInstance().getJDK(jdk);
938 939 940 941 942
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
943
    public void setJDK(JDK jdk) throws IOException {
944 945 946 947
        this.jdk = jdk.getName();
        save();
    }

948 949
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
950 951
    }

952
    @Override
953
    public RunMap<R> _getRuns() {
J
Jesse Glick 已提交
954
        return buildMixIn._getRuns();
955 956
    }

957
    @Override
958
    public void removeRun(R run) {
J
Jesse Glick 已提交
959
        buildMixIn.removeRun(run);
960 961
    }

962 963 964 965 966 967 968
    /**
     * {@inheritDoc}
     *
     * More efficient implementation.
     */
    @Override
    public R getBuild(String id) {
J
Jesse Glick 已提交
969
        return buildMixIn.getBuild(id);
970 971 972 973 974 975 976 977 978
    }

    /**
     * {@inheritDoc}
     *
     * More efficient implementation.
     */
    @Override
    public R getBuildByNumber(int n) {
J
Jesse Glick 已提交
979
        return buildMixIn.getBuildByNumber(n);
980 981 982 983 984 985 986 987 988
    }

    /**
     * {@inheritDoc}
     *
     * More efficient implementation.
     */
    @Override
    public R getFirstBuild() {
J
Jesse Glick 已提交
989
        return buildMixIn.getFirstBuild();
990 991 992
    }

    @Override
993
    public @CheckForNull R getLastBuild() {
J
Jesse Glick 已提交
994
        return buildMixIn.getLastBuild();
995 996 997 998
    }

    @Override
    public R getNearestBuild(int n) {
J
Jesse Glick 已提交
999
        return buildMixIn.getNearestBuild(n);
1000 1001 1002 1003
    }

    @Override
    public R getNearestOldBuild(int n) {
J
Jesse Glick 已提交
1004
        return buildMixIn.getNearestOldBuild(n);
1005 1006
    }

1007
    /**
J
Jesse Glick 已提交
1008 1009 1010 1011
     * Type token for the corresponding build type.
     * The build class must have two constructors:
     * one taking this project type;
     * and one taking this project type, then {@link File}.
1012 1013 1014
     */
    protected abstract Class<R> getBuildClass();

1015 1016 1017
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
1018
    protected synchronized R newBuild() throws IOException {
J
Jesse Glick 已提交
1019
        return buildMixIn.newBuild();
1020 1021
    }

1022 1023 1024
    /**
     * Loads an existing build record from disk.
     */
1025
    protected R loadBuild(File dir) throws IOException {
J
Jesse Glick 已提交
1026
        return buildMixIn.loadBuild(dir);
1027
    }
1028

K
kohsuke 已提交
1029 1030
    /**
     * {@inheritDoc}
1031
     *
K
kohsuke 已提交
1032 1033
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
1034
     * {@link BuildStep}s and others who want to add a project action
1035
     * should do so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
1036 1037
     *
     * @see TransientProjectActionFactory
K
kohsuke 已提交
1038
     */
1039
    @SuppressWarnings("deprecation")
1040
    @Override
1041
    public List<Action> getActions() {
1042 1043 1044
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
1045
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
1046
        return Collections.unmodifiableList(actions);
1047 1048
    }

1049 1050
    // TODO implement addAction, addOrReplaceAction, removeAction, removeActions, replaceActions

1051 1052
    /**
     * Gets the {@link Node} where this project was last built on.
1053 1054 1055 1056
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
1057 1058 1059 1060
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
1061
        if(b==null)
1062 1063 1064 1065 1066
            return null;
        else
            return b.getBuiltOn();
    }

1067
    public Object getSameNodeConstraint() {
1068
        return this; // in this way, any member that wants to run with the main guy can nominate the project itself
1069 1070
    }

1071 1072 1073 1074
    public final Task getOwnerTask() {
        return this;
    }

1075 1076 1077 1078
    @Nonnull
    public Authentication getDefaultAuthentication() {
        // backward compatible behaviour.
        return ACL.SYSTEM;
1079 1080
    }

1081 1082 1083 1084 1085 1086
    @Nonnull
    @Override
    public Authentication getDefaultAuthentication(Queue.Item item) {
        return getDefaultAuthentication();
    }

1087
    /**
1088
     * {@inheritDoc}
1089
     *
1090
     * <p>
1091
     * A project must be blocked if its own previous build is in progress,
1092 1093
     * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
     * project is building, but derived classes can also check other conditions.
1094
     */
1095
    @Override
1096
    public boolean isBuildBlocked() {
1097 1098 1099 1100 1101 1102 1103 1104 1105
        return getCauseOfBlockage()!=null;
    }

    public String getWhyBlocked() {
        CauseOfBlockage cb = getCauseOfBlockage();
        return cb!=null ? cb.getShortDescription() : null;
    }

    /**
1106
     * @deprecated use {@link BlockedBecauseOfBuildInProgress} instead.
1107
     */
1108
    @Deprecated
1109
    public static class BecauseOfBuildInProgress extends BlockedBecauseOfBuildInProgress {
1110
        public BecauseOfBuildInProgress(@Nonnull AbstractBuild<?, ?> build) {
1111
            super(build);
1112
        }
1113
    }
1114

1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
    /**
     * Because the downstream build is in progress, and we are configured to wait for that.
     */
    public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage {
        public final AbstractProject<?,?> up;

        public BecauseOfDownstreamBuildInProgress(AbstractProject<?,?> up) {
            this.up = up;
        }

        @Override
        public String getShortDescription() {
            return Messages.AbstractProject_DownstreamBuildInProgress(up.getName());
        }
    }
1130

1131 1132 1133 1134 1135 1136 1137 1138 1139 1140
    /**
     * Because the upstream build is in progress, and we are configured to wait for that.
     */
    public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage {
        public final AbstractProject<?,?> up;

        public BecauseOfUpstreamBuildInProgress(AbstractProject<?,?> up) {
            this.up = up;
        }

1141
        @Override
1142 1143 1144
        public String getShortDescription() {
            return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
        }
1145 1146
    }

J
Jesse Glick 已提交
1147 1148
    @Override
    public CauseOfBlockage getCauseOfBlockage() {
1149
        // Block builds until they are done with post-production
1150
        if (isLogUpdated() && !isConcurrentBuild()) {
1151 1152 1153 1154 1155
            final R lastBuild = getLastBuild();
            if (lastBuild != null) {
                return new BlockedBecauseOfBuildInProgress(lastBuild);
            } else {
                // The build has been likely deleted after the isLogUpdated() call.
1156
                // Another cause may be an API implementation glitсh in the implementation for AbstractProject. 
1157 1158 1159
                // Anyway, we should let the code go then.
                LOGGER.log(Level.FINE, "The last build has been deleted during the non-concurrent cause creation. The build is not blocked anymore");
            }
1160
        }
1161 1162 1163 1164
        if (blockBuildWhenDownstreamBuilding()) {
            AbstractProject<?,?> bup = getBuildingDownstream();
            if (bup!=null)
                return new BecauseOfDownstreamBuildInProgress(bup);
1165 1166
        }
        if (blockBuildWhenUpstreamBuilding()) {
1167 1168 1169 1170 1171 1172
            AbstractProject<?,?> bup = getBuildingUpstream();
            if (bup!=null)
                return new BecauseOfUpstreamBuildInProgress(bup);
        }
        return null;
    }
1173

1174
    /**
1175 1176
     * Returns the project if any of the downstream project is either
     * building, waiting, pending or buildable.
1177 1178 1179 1180
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
1181
    public AbstractProject getBuildingDownstream() {
1182
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1183

1184
        for (AbstractProject tup : getTransitiveDownstreamProjects()) {
1185
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1186 1187 1188 1189 1190
                return tup;
        }
        return null;
    }

1191
    /**
1192
     * Returns the project if any of the upstream project is either
1193 1194 1195 1196 1197
     * building or is in the queue.
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
1198
    public AbstractProject getBuildingUpstream() {
1199
        Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1200

1201
        for (AbstractProject tup : getTransitiveUpstreamProjects()) {
1202
			if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1203 1204 1205
                return tup;
        }
        return null;
1206 1207
    }

1208 1209
    public List<SubTask> getSubTasks() {
        List<SubTask> r = new ArrayList<SubTask>();
1210
        r.add(this);
1211 1212 1213
        for (SubTaskContributor euc : SubTaskContributor.all())
            r.addAll(euc.forProject(this));
        for (JobProperty<? super P> p : properties)
1214
            r.addAll(p.getSubTasks());
1215
        return r;
1216 1217
    }

1218
    public @CheckForNull R createExecutable() throws IOException {
1219
        if(isDisabled())    return null;
1220
        return newBuild();
1221 1222
    }

1223
    public void checkAbortPermission() {
1224
        checkPermission(CANCEL);
1225 1226
    }

K
kohsuke 已提交
1227
    public boolean hasAbortPermission() {
1228
        return hasPermission(CANCEL);
K
kohsuke 已提交
1229 1230
    }

1231 1232
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
1233
     * Useful for locking and mutual exclusion control.
K
kohsuke 已提交
1234
     *
K
kohsuke 已提交
1235
     * @deprecated as of 1.319
K
kohsuke 已提交
1236 1237 1238 1239 1240 1241 1242
     *      Projects no longer have a fixed workspace, ands builds will find an available workspace via
     *      {@link WorkspaceList} for each build (furthermore, that happens after a build is started.)
     *      So a {@link Resource} representation for a workspace at the project level no longer makes sense.
     *
     *      <p>
     *      If you need to lock a workspace while you do some computation, see the source code of
     *      {@link #pollSCMChanges(TaskListener)} for how to obtain a lock of a workspace through {@link WorkspaceList}.
1243
     */
1244
    @Deprecated
1245
    public Resource getWorkspaceResource() {
1246
        return new Resource(getFullDisplayName()+" workspace");
1247 1248 1249 1250 1251 1252
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
1253
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
1254
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
1255 1256 1257 1258 1259 1260 1261 1262 1263 1264
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
1265 1266
     * Set of child resource activities of the build of this project (override in child projects).
     * @return The set of child resource activities of the build of this project.
1267 1268
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
1269
        return Collections.emptySet();
1270 1271
    }

1272
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
1273
        SCM scm = getScm();
1274 1275
        if(scm==null)
            return true;    // no SCM
1276

1277
        FilePath workspace = build.getWorkspace();
1278
        workspace.mkdirs();
1279

1280
        boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
C
Christoph Kutzinski 已提交
1281 1282 1283
        if (r) {
            // Only calcRevisionsFromBuild if checkout was successful. Note that modern SCM implementations
            // won't reach this line anyway, as they throw AbortExceptions on checkout failure.
1284 1285
            calcPollingBaseline(build, launcher, listener);
        }
1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
        return r;
    }

    /**
     * Pushes the baseline up to the newly checked out revision.
     */
    private void calcPollingBaseline(AbstractBuild build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
        SCMRevisionState baseline = build.getAction(SCMRevisionState.class);
        if (baseline==null) {
            try {
1296
                baseline = getScm().calcRevisionsFromBuild(build, launcher, listener);
1297 1298 1299 1300 1301 1302 1303 1304 1305
            } catch (AbstractMethodError e) {
                baseline = SCMRevisionState.NONE; // pre-1.345 SCM implementations, which doesn't use the baseline in polling
            }
            if (baseline!=null)
                build.addAction(baseline);
        }
        pollingBaseline = baseline;
    }

1306 1307
    /**
     * Checks if there's any update in SCM, and returns true if any is found.
1308
     *
1309 1310
     * @deprecated as of 1.346
     *      Use {@link #poll(TaskListener)} instead.
1311
     */
1312
    @Deprecated
1313
    public boolean pollSCMChanges( TaskListener listener ) {
1314 1315 1316 1317 1318 1319 1320
        return poll(listener).hasChanges();
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
     *
     * <p>
1321 1322
     * The implementation is responsible for ensuring mutual exclusion between polling and builds
     * if necessary.
1323 1324 1325 1326
     *
     * @since 1.345
     */
    public PollingResult poll( TaskListener listener ) {
1327
        SCM scm = getScm();
1328
        if (scm==null) {
K
i18n  
kohsuke 已提交
1329
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
1330
            return NO_CHANGES;
1331
        }
1332
        if (!isBuildable()) {
K
i18n  
kohsuke 已提交
1333
            listener.getLogger().println(Messages.AbstractProject_Disabled());
1334 1335
            return NO_CHANGES;
        }
1336
        SCMDecisionHandler veto = SCMDecisionHandler.firstShouldPollVeto(this);
1337 1338 1339
        if (veto != null) {
            listener.getLogger().println(Messages.AbstractProject_PollingVetoed(veto));
            return NO_CHANGES;
1340
        }
1341 1342 1343

        R lb = getLastBuild();
        if (lb==null) {
1344
            listener.getLogger().println(Messages.AbstractProject_NoBuilds());
1345
            return isInQueue() ? NO_CHANGES : BUILD_NOW;
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
        }

        if (pollingBaseline==null) {
            R success = getLastSuccessfulBuild(); // if we have a persisted baseline, we'll find it by this
            for (R r=lb; r!=null; r=r.getPreviousBuild()) {
                SCMRevisionState s = r.getAction(SCMRevisionState.class);
                if (s!=null) {
                    pollingBaseline = s;
                    break;
                }
                if (r==success) break;  // searched far enough
            }
            // NOTE-NO-BASELINE:
            // if we don't have baseline yet, it means the data is built by old Hudson that doesn't set the baseline
            // as action, so we need to compute it. This happens later.
1361 1362 1363
        }

        try {
K
Kohsuke Kawaguchi 已提交
1364
            SCMPollListener.fireBeforePolling(this, listener);
1365
            PollingResult r = _poll(listener, scm);
K
Kohsuke Kawaguchi 已提交
1366 1367
            SCMPollListener.firePollingSuccess(this,listener, r);
            return r;
1368
        } catch (AbortException e) {
1369
            listener.getLogger().println(e.getMessage());
K
i18n  
kohsuke 已提交
1370
            listener.fatalError(Messages.AbstractProject_Aborted());
1371
            LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
K
Kohsuke Kawaguchi 已提交
1372
            SCMPollListener.firePollingFailed(this, listener,e);
1373
            return NO_CHANGES;
1374
        } catch (IOException e) {
1375
            Functions.printStackTrace(e, listener.fatalError(e.getMessage()));
K
Kohsuke Kawaguchi 已提交
1376
            SCMPollListener.firePollingFailed(this, listener,e);
1377
            return NO_CHANGES;
1378
        } catch (InterruptedException e) {
1379
            Functions.printStackTrace(e, listener.fatalError(Messages.AbstractProject_PollingABorted()));
K
Kohsuke Kawaguchi 已提交
1380
            SCMPollListener.firePollingFailed(this, listener,e);
1381
            return NO_CHANGES;
K
Kohsuke Kawaguchi 已提交
1382 1383 1384 1385 1386 1387
        } catch (RuntimeException e) {
            SCMPollListener.firePollingFailed(this, listener,e);
            throw e;
        } catch (Error e) {
            SCMPollListener.firePollingFailed(this, listener,e);
            throw e;
1388 1389
        }
    }
K
Kohsuke Kawaguchi 已提交
1390 1391 1392 1393

    /**
     * {@link #poll(TaskListener)} method without the try/catch block that does listener notification and .
     */
1394
    private PollingResult _poll(TaskListener listener, SCM scm) throws IOException, InterruptedException {
K
Kohsuke Kawaguchi 已提交
1395
        if (scm.requiresWorkspaceForPolling()) {
1396 1397 1398 1399
            R b = getSomeBuildWithExistingWorkspace();
            if (b == null) b = getLastBuild();
            // lock the workspace for the given build
            FilePath ws=b.getWorkspace();
K
Kohsuke Kawaguchi 已提交
1400

1401
            WorkspaceOfflineReason workspaceOfflineReason = workspaceOffline( b );
1402
            if ( workspaceOfflineReason != null ) {
1403
                // workspace offline
1404
                for (WorkspaceBrowser browser : ExtensionList.lookup(WorkspaceBrowser.class)) {
1405 1406
                    ws = browser.getWorkspace(this);
                    if (ws != null) {
1407
                        return pollWithWorkspace(listener, scm, b, ws, browser.getWorkspaceList());
1408 1409 1410
                    }
                }

K
Kohsuke Kawaguchi 已提交
1411 1412 1413
                // At this point we start thinking about triggering a build just to get a workspace,
                // because otherwise there's no way we can detect changes.
                // However, first there are some conditions in which we do not want to do so.
1414
                // give time for agents to come online if we are right after reconnection (JENKINS-8408)
K
Kohsuke Kawaguchi 已提交
1415 1416
                long running = Jenkins.getInstance().getInjector().getInstance(Uptime.class).getUptime();
                long remaining = TimeUnit2.MINUTES.toMillis(10)-running;
1417
                if (remaining>0 && /* this logic breaks tests of polling */!Functions.getIsUnitTest()) {
K
Kohsuke Kawaguchi 已提交
1418 1419 1420 1421 1422
                    listener.getLogger().print(Messages.AbstractProject_AwaitingWorkspaceToComeOnline(remaining/1000));
                    listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
                    return NO_CHANGES;
                }

1423
                // Do not trigger build, if no suitable agent is online
1424 1425 1426 1427 1428 1429 1430
                if (workspaceOfflineReason.equals(WorkspaceOfflineReason.all_suitable_nodes_are_offline)) {
                    // No suitable executor is online
                    listener.getLogger().print(Messages.AbstractProject_AwaitingWorkspaceToComeOnline(running/1000));
                    listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
                    return NO_CHANGES;
                }

K
Kohsuke Kawaguchi 已提交
1431 1432 1433
                Label label = getAssignedLabel();
                if (label != null && label.isSelfLabel()) {
                    // if the build is fixed on a node, then attempting a build will do us
1434
                    // no good. We should just wait for the agent to come back.
1435 1436
                    listener.getLogger().print(Messages.AbstractProject_NoWorkspace());
                    listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
K
Kohsuke Kawaguchi 已提交
1437 1438
                    return NO_CHANGES;
                }
K
Kohsuke Kawaguchi 已提交
1439

K
Kohsuke Kawaguchi 已提交
1440 1441 1442 1443 1444 1445 1446
                listener.getLogger().println( ws==null
                    ? Messages.AbstractProject_WorkspaceOffline()
                    : Messages.AbstractProject_NoWorkspace());
                if (isInQueue()) {
                    listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
                    return NO_CHANGES;
                }
K
Kohsuke Kawaguchi 已提交
1447 1448 1449 1450 1451

                // build now, or nothing will ever be built
                listener.getLogger().print(Messages.AbstractProject_NewBuildForWorkspace());
                listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
                return BUILD_NOW;
K
Kohsuke Kawaguchi 已提交
1452
            } else {
1453 1454
                WorkspaceList l = b.getBuiltOn().toComputer().getWorkspaceList();
                return pollWithWorkspace(listener, scm, b, ws, l);
K
Kohsuke Kawaguchi 已提交
1455
            }
1456

K
Kohsuke Kawaguchi 已提交
1457 1458 1459 1460
        } else {
            // polling without workspace
            LOGGER.fine("Polling SCM changes of " + getName());
            if (pollingBaseline==null) // see NOTE-NO-BASELINE above
1461
                calcPollingBaseline(getLastBuild(),null,listener);
K
Kohsuke Kawaguchi 已提交
1462 1463 1464 1465 1466 1467
            PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
            pollingBaseline = r.remote;
            return r;
        }
    }

1468
    private PollingResult pollWithWorkspace(TaskListener listener, SCM scm, R lb, @Nonnull FilePath ws, WorkspaceList l) throws InterruptedException, IOException {
1469 1470 1471 1472 1473 1474
        // if doing non-concurrent build, acquire a workspace in a way that causes builds to block for this workspace.
        // this prevents multiple workspaces of the same job --- the behavior of Hudson < 1.319.
        //
        // OTOH, if a concurrent build is chosen, the user is willing to create a multiple workspace,
        // so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
        // by having multiple workspaces
1475 1476
        Node node = lb.getBuiltOn();
        Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(node,listener));
1477
        WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
1478
        try {
1479 1480
            String nodeName = node != null ? node.getSelfLabel().getName() : "[node_unavailable]";
            listener.getLogger().println("Polling SCM changes on " + nodeName);
1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491
            LOGGER.fine("Polling SCM changes of " + getName());
            if (pollingBaseline==null) // see NOTE-NO-BASELINE above
                calcPollingBaseline(lb,launcher,listener);
            PollingResult r = scm.poll(this, launcher, ws, listener, pollingBaseline);
            pollingBaseline = r.remote;
            return r;
        } finally {
            lease.release();
        }
    }

1492 1493 1494
    enum WorkspaceOfflineReason {
        nonexisting_workspace,
        builton_node_gone,
1495
        builton_node_no_executors,
1496 1497
        all_suitable_nodes_are_offline,
        use_ondemand_slave
1498 1499 1500 1501 1502 1503 1504
    }

    /**
     * Returns true if all suitable nodes for the job are offline.
     *
     */
    private boolean isAllSuitableNodesOffline(R build) {
1505
        Label label = getAssignedLabel();
1506 1507 1508
        List<Node> allNodes = Jenkins.getInstance().getNodes();

        if (label != null) {
1509 1510 1511 1512 1513
            //Invalid label. Put in queue to make administrator fix
            if(label.getNodes().isEmpty()) {
                return false;
            }
            //Returns true, if all suitable nodes are offline
1514
            return label.isOffline();
1515
        } else {
1516 1517
            if(canRoam) {
                for (Node n : Jenkins.getInstance().getNodes()) {
M
Mads Nielsen 已提交
1518 1519 1520 1521 1522
                    Computer c = n.toComputer();
                    if (c != null && c.isOnline() && c.isAcceptingTasks() && n.getMode() == Mode.NORMAL) {
                        // Some executor is online that  is ready and this job can run anywhere
                        return false;
                    }
1523
                }
M
Mads Nielsen 已提交
1524 1525 1526 1527
                //We can roam, check that the master is set to be used as much as possible, and not tied jobs only.
                if(Jenkins.getInstance().getMode() == Mode.EXCLUSIVE) {
                    return true;
                } else {
1528
                    return false;
1529 1530 1531 1532
                }
            }
        }
        return true;
1533 1534 1535
    }

    private WorkspaceOfflineReason workspaceOffline(R build) throws IOException, InterruptedException {
1536
        FilePath ws = build.getWorkspace();
1537 1538
        Label label = getAssignedLabel();

1539
        if (isAllSuitableNodesOffline(build)) {
1540
            Collection<Cloud> applicableClouds = label == null ? Jenkins.getInstance().clouds : label.getClouds();
1541
            return applicableClouds.isEmpty() ? WorkspaceOfflineReason.all_suitable_nodes_are_offline : WorkspaceOfflineReason.use_ondemand_slave;
1542 1543
        }

1544
        if (ws==null || !ws.exists()) {
1545
            return WorkspaceOfflineReason.nonexisting_workspace;
1546
        }
1547

1548 1549
        Node builtOn = build.getBuiltOn();
        if (builtOn == null) { // node built-on doesn't exist anymore
1550
            return WorkspaceOfflineReason.builton_node_gone;
1551
        }
1552

1553
        if (builtOn.toComputer() == null) { // node still exists, but has 0 executors - o.s.l.t.
1554
            return WorkspaceOfflineReason.builton_node_no_executors;
1555
        }
1556 1557

        return null;
1558
    }
1559

1560 1561
    /**
     * Returns true if this user has made a commit to this project.
1562
     *
1563 1564 1565
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
1566 1567
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
1568 1569 1570 1571
                return true;
        return false;
    }

1572
    @Exported
1573 1574 1575 1576
    public SCM getScm() {
        return scm;
    }

1577
    public void setScm(SCM scm) throws IOException {
1578
        this.scm = scm;
1579
        save();
1580 1581
    }

1582 1583 1584
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
1585
    public void addTrigger(Trigger<?> trigger) throws IOException {
1586
        addToList(trigger,triggers());
1587 1588
    }

1589
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1590
        removeFromList(trigger,triggers());
1591 1592
    }

1593 1594
    protected final synchronized <T extends Describable<T>>
    void addToList( T item, List<T> collection ) throws IOException {
1595 1596
        //No support to replace item in position, remove then add
        removeFromList(item.getDescriptor(), collection);
1597 1598
        collection.add(item);
        save();
1599
        updateTransientActions();
1600 1601
    }

1602 1603
    protected final synchronized <T extends Describable<T>>
    void removeFromList(Descriptor<T> item, List<T> collection) throws IOException {
1604 1605 1606 1607
        final Iterator<T> iCollection = collection.iterator();
        while(iCollection.hasNext()) {
            final T next = iCollection.next();
            if(next.getDescriptor()==item) {
1608
                // found it
1609
                iCollection.remove();
1610
                save();
1611
                updateTransientActions();
1612 1613 1614 1615 1616
                return;
            }
        }
    }

C
Christoph Kutzinski 已提交
1617
    @SuppressWarnings("unchecked")
1618
    @Override public Map<TriggerDescriptor,Trigger<?>> getTriggers() {
K
Kohsuke Kawaguchi 已提交
1619
        return triggers().toMap();
1620 1621
    }

1622
    /**
1623
     * Gets the specific trigger, or null if the property is not configured for this job.
1624 1625
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
1626
        for (Trigger p : triggers()) {
1627
            if(clazz.isInstance(p))
1628 1629 1630 1631 1632
                return clazz.cast(p);
        }
        return null;
    }

1633 1634 1635 1636 1637
//
//
// fingerprint related
//
//
1638 1639 1640 1641 1642 1643
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
1644 1645
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
1646
     */
K
kohsuke 已提交
1647
    @Exported
1648
    public final List<AbstractProject> getDownstreamProjects() {
1649
        return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
1650
    }
1651

K
kohsuke 已提交
1652
    @Exported
1653
    public final List<AbstractProject> getUpstreamProjects() {
1654
        return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
1655 1656
    }

K
kohsuke 已提交
1657
    /**
1658 1659
     * Returns only those upstream projects that defines {@link BuildTrigger} to this project.
     * This is a subset of {@link #getUpstreamProjects()}
1660
     * <p>No longer used in the UI.
1661
     * @return A List of upstream projects that has a {@link BuildTrigger} to this project.
K
kohsuke 已提交
1662 1663 1664
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1665 1666
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1667
            if (buildTrigger != null)
1668
                if (buildTrigger.getChildProjects(ap).contains(this))
1669
                    result.add(ap);
1670
        }
K
kohsuke 已提交
1671
        return result;
1672 1673
    }

K
kohsuke 已提交
1674 1675
    /**
     * Gets all the upstream projects including transitive upstream projects.
1676
     *
K
kohsuke 已提交
1677 1678 1679
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1680
        return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
1681 1682 1683
    }

    /**
1684 1685
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
1686 1687 1688
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1689
        return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1690 1691 1692 1693 1694
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1695 1696 1697 1698
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1699 1700
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1701
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1702 1703 1704 1705 1706 1707 1708 1709 1710

        checkAndRecord(that, r, this.getBuilds());
        // checkAndRecord(that, r, that.getBuilds());

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1711 1712
     *
     * For each given build, find the build number range of the given project and put that into the map.
1713
     */
1714
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1715 1716
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1717
            if(rs==null || rs.isEmpty())
1718 1719 1720 1721 1722
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1723 1724
            if(value==null)
                r.put(n,rs);
1725 1726 1727 1728 1729
            else
                value.add(rs);
        }
    }

1730 1731
    /**
     * Builds the dependency graph.
1732
     * Since 1.558, not abstract and by default includes dependencies contributed by {@link #triggers()}.
1733
     */
1734 1735 1736
    protected void buildDependencyGraph(DependencyGraph graph) {
        triggers().buildDependencyGraph(this, graph);
    }
1737

1738
    @Override
K
kohsuke 已提交
1739
    protected SearchIndexBuilder makeSearchIndex() {
1740
        return getParameterizedJobMixIn().extendSearchIndex(super.makeSearchIndex());
K
kohsuke 已提交
1741 1742
    }

1743 1744
    @Override
    protected HistoryWidget createHistoryWidget() {
J
Jesse Glick 已提交
1745
        return buildMixIn.createHistoryWidget();
1746
    }
1747

K
kohsuke 已提交
1748
    public boolean isParameterized() {
1749
        return getParameterizedJobMixIn().isParameterized();
K
kohsuke 已提交
1750
    }
1751

1752 1753 1754 1755 1756
//
//
// actions
//
//
1757 1758 1759
    /**
     * Schedules a new build command.
     */
1760
    public void doBuild( StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay ) throws IOException, ServletException {
1761
        getParameterizedJobMixIn().doBuild(req, rsp, delay);
1762 1763
    }

1764 1765 1766 1767 1768 1769
    /** @deprecated use {@link #doBuild(StaplerRequest, StaplerResponse, TimeDuration)} */
    @Deprecated
    public void doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        doBuild(req, rsp, TimeDuration.fromString(req.getParameter("delay")));
    }

1770 1771
    /**
     * Computes the delay by taking the default value and the override in the request parameter into the account.
1772
     *
1773
     * @deprecated as of 1.489
1774
     *      Inject {@link TimeDuration}.
1775
     */
1776
    @Deprecated
1777
    public int getDelay(StaplerRequest req) throws ServletException {
1778
        String delay = req.getParameter("delay");
1779 1780 1781 1782 1783 1784 1785 1786 1787
        if (delay==null)    return getQuietPeriod();

        try {
            // TODO: more unit handling
            if(delay.endsWith("sec"))   delay=delay.substring(0,delay.length()-3);
            if(delay.endsWith("secs"))  delay=delay.substring(0,delay.length()-4);
            return Integer.parseInt(delay);
        } catch (NumberFormatException e) {
            throw new ServletException("Invalid delay parameter value: "+delay);
1788
        }
1789
    }
1790

1791 1792 1793 1794
    /**
     * Supports build trigger with parameters via an HTTP GET or POST.
     * Currently only String parameters are supported.
     */
1795
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
1796
        getParameterizedJobMixIn().doBuildWithParameters(req, rsp, delay);
1797
    }
1798

1799 1800 1801 1802 1803 1804
    /** @deprecated use {@link #doBuildWithParameters(StaplerRequest, StaplerResponse, TimeDuration)} */
    @Deprecated
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        doBuildWithParameters(req, rsp, TimeDuration.fromString(req.getParameter("delay")));
    }

1805 1806 1807
    /**
     * Schedules a new SCM polling command.
     */
1808
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1809
        BuildAuthorizationToken.checkPermission((Job) this, authToken, req, rsp);
1810
        schedulePolling();
J
Jesse Glick 已提交
1811
        rsp.sendRedirect(".");
1812 1813 1814 1815 1816
    }

    /**
     * Cancels a scheduled build.
     */
J
Jesse Glick 已提交
1817
    @RequirePOST
1818
    public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1819
        getParameterizedJobMixIn().doCancelQueue(req, rsp);
1820 1821
    }

1822
    @Override
1823 1824
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1825
        JSONObject json = req.getSubmittedForm();
1826

1827
        makeDisabled(json.optBoolean("disable"));
1828

1829 1830
        jdk = json.optString("jdk", null);

1831 1832
        if(json.optBoolean("hasCustomQuietPeriod", json.has("quiet_period"))) {
            quietPeriod = json.optInt("quiet_period");
1833 1834 1835
        } else {
            quietPeriod = null;
        }
1836

1837 1838
        if(json.optBoolean("hasCustomScmCheckoutRetryCount", json.has("scmCheckoutRetryCount"))) {
            scmCheckoutRetryCount = json.optInt("scmCheckoutRetryCount");
S
 
shinodkm 已提交
1839
        } else {
1840
            scmCheckoutRetryCount = null;
S
 
shinodkm 已提交
1841
        }
1842

1843 1844
        blockBuildWhenDownstreamBuilding = json.optBoolean("blockBuildWhenDownstreamBuilding");
        blockBuildWhenUpstreamBuilding = json.optBoolean("blockBuildWhenUpstreamBuilding");
1845

1846 1847
        if(req.hasParameter("customWorkspace.directory")) {
            // Workaround for JENKINS-25221 while plugins are being updated.
1848
            LOGGER.log(Level.WARNING, "label assignment is using legacy 'customWorkspace.directory'");
1849 1850
            customWorkspace = Util.fixEmptyAndTrim(req.getParameter("customWorkspace.directory"));
        } else if(json.optBoolean("hasCustomWorkspace", json.has("customWorkspace"))) {
1851
            customWorkspace = Util.fixEmptyAndTrim(json.optString("customWorkspace"));
1852 1853 1854
        } else {
            customWorkspace = null;
        }
1855 1856 1857 1858 1859 1860 1861

        if (json.has("scmCheckoutStrategy"))
            scmCheckoutStrategy = req.bindJSON(SCMCheckoutStrategy.class,
                json.getJSONObject("scmCheckoutStrategy"));
        else
            scmCheckoutStrategy = null;

1862 1863 1864
        if(json.optBoolean("hasSlaveAffinity", json.has("label"))) {
            assignedNode = Util.fixEmptyAndTrim(json.optString("label"));
        } else if(req.hasParameter("_.assignedLabelString")) {
1865
            // Workaround for JENKINS-25372 while plugin is being updated.
1866
            // Keep this condition second for JENKINS-25533
1867 1868
            LOGGER.log(Level.WARNING, "label assignment is using legacy '_.assignedLabelString'");
            assignedNode = Util.fixEmptyAndTrim(req.getParameter("_.assignedLabelString"));
1869
        } else  {
1870 1871
            assignedNode = null;
        }
1872
        canRoam = assignedNode==null;
1873

1874
        keepDependencies = json.has("keepDependencies");
1875

1876
        concurrentBuild = json.optBoolean("concurrentBuild");
K
kohsuke 已提交
1877

1878
        authToken = BuildAuthorizationToken.create(req);
1879

K
kohsuke 已提交
1880
        setScm(SCMS.parseSCM(req,this));
1881

1882
        for (Trigger t : triggers())
1883
            t.stop();
K
Kohsuke Kawaguchi 已提交
1884
        triggers.replaceBy(buildDescribable(req, Trigger.for_(this)));
1885 1886
        for (Trigger t : triggers())
            t.start(this,true);
1887 1888
    }

K
kohsuke 已提交
1889 1890 1891 1892
    /**
     * @deprecated
     *      As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
     */
1893
    @Deprecated
K
kohsuke 已提交
1894 1895 1896 1897 1898
    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors, String prefix) throws FormException, ServletException {
        return buildDescribable(req,descriptors);
    }

    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors)
1899
        throws FormException, ServletException {
1900

1901
        JSONObject data = req.getSubmittedForm();
1902
        List<T> r = new Vector<T>();
1903
        for (Descriptor<T> d : descriptors) {
1904 1905 1906
            String safeName = d.getJsonSafeClassName();
            if (req.getParameter(safeName) != null) {
                T instance = d.newInstance(req, data.getJSONObject(safeName));
1907
                r.add(instance);
1908 1909
            }
        }
1910
        return r;
1911 1912 1913 1914 1915
    }

    /**
     * Serves the workspace files.
     */
1916
    public DirectoryBrowserSupport doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1917
        checkPermission(Item.WORKSPACE);
K
kohsuke 已提交
1918
        FilePath ws = getSomeWorkspace();
1919
        if ((ws == null) || (!ws.exists())) {
1920
            // if there's no workspace, report a nice error message
1921 1922 1923 1924
            // Would be good if when asked for *plain*, do something else!
            // (E.g. return 404, or send empty doc.)
            // Not critical; client can just check if content type is not text/plain,
            // which also serves to detect old versions of Hudson.
1925
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1926
            return null;
1927
        } else {
1928 1929 1930 1931 1932 1933 1934 1935
            Computer c = ws.toComputer();
            String title;
            if (c == null) {
                title = Messages.AbstractProject_WorkspaceTitle(getDisplayName());
            } else {
                title = Messages.AbstractProject_WorkspaceTitleOnComputer(getDisplayName(), c.getDisplayName());
            }
            return new DirectoryBrowserSupport(this, ws, title, "folder.png", true);
1936 1937
        }
    }
1938

1939 1940 1941
    /**
     * Wipes out the workspace.
     */
1942
    @RequirePOST
1943
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
1944
        checkPermission(Functions.isWipeOutPermissionEnabled() ? WIPEOUT : BUILD);
1945 1946 1947 1948
        R b = getSomeBuildWithWorkspace();
        FilePath ws = b!=null ? b.getWorkspace() : null;
        if (ws!=null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
            ws.deleteRecursive();
1949 1950 1951
            for (WorkspaceListener wl : WorkspaceListener.all()) {
                wl.afterDelete(this);
            }
1952 1953 1954 1955
            return new HttpRedirect(".");
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
1956
        }
1957 1958
    }

1959
    @CLIMethod(name="disable-job")
1960
    @RequirePOST
1961
    public HttpResponse doDisable() throws IOException, ServletException {
1962 1963
        checkPermission(CONFIGURE);
        makeDisabled(true);
1964
        return new HttpRedirect(".");
1965 1966
    }

1967
    @CLIMethod(name="enable-job")
1968
    @RequirePOST
1969
    public HttpResponse doEnable() throws IOException, ServletException {
1970 1971
        checkPermission(CONFIGURE);
        makeDisabled(false);
1972
        return new HttpRedirect(".");
1973
    }
1974

1975

K
kohsuke 已提交
1976 1977 1978
    /**
     * RSS feed for changes in this project.
     */
1979
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
1980 1981 1982 1983 1984 1985 1986 1987 1988
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

            public FeedItem(Entry e, int idx) {
                this.e = e;
                this.idx = idx;
            }

1989
            AbstractBuild<?,?> getBuild() {
1990
                return e.getParent().build;
K
kohsuke 已提交
1991 1992 1993 1994 1995
            }
        }

        List<FeedItem> entries = new ArrayList<FeedItem>();

1996 1997 1998 1999
        for(R r=getLastBuild(); r!=null; r=r.getPreviousBuild()) {
            int idx=0;
            for( ChangeLogSet.Entry e : r.getChangeSet())
                entries.add(new FeedItem(e,idx++));
K
kohsuke 已提交
2000 2001
        }

2002 2003 2004 2005 2006 2007 2008
        RSS.forwardToRss(
            getDisplayName()+' '+getScm().getDescriptor().getDisplayName()+" changes",
            getUrl()+"changes",
            entries, new FeedAdapter<FeedItem>() {
                public String getEntryTitle(FeedItem item) {
                    return "#"+item.getBuild().number+' '+item.e.getMsg()+" ("+item.e.getAuthor()+")";
                }
K
kohsuke 已提交
2009

2010 2011 2012
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
2013

2014 2015 2016
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
2017

2018 2019 2020 2021 2022 2023
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
2024

2025 2026 2027
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
2028

2029
                public String getEntryAuthor(FeedItem entry) {
2030
                    return JenkinsLocationConfiguration.get().getAdminAddress();
2031 2032 2033
                }
            },
            req, rsp );
K
kohsuke 已提交
2034 2035
    }

2036 2037 2038 2039 2040 2041 2042
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
2043
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
2044
         * from showing up on their configuration screen. This is often useful when you are building
2045 2046
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
2047 2048
         *
         * <p>
2049 2050 2051 2052
         * Some {@link Descriptor}s define their own schemes for controlling applicability
         * (such as {@link BuildStepDescriptor#isApplicable(Class)}),
         * This method works like AND in conjunction with them;
         * Both this method and that method need to return true in order for a given {@link Descriptor}
2053 2054 2055 2056
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
2057
         *
2058 2059
         * @see BuildStepDescriptor#isApplicable(Class)
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject)
K
kohsuke 已提交
2060
         * @see TriggerDescriptor#isApplicable(Item)
2061
         */
K
kohsuke 已提交
2062
        @Override
2063
        public boolean isApplicable(Descriptor descriptor) {
2064 2065
            return true;
        }
2066

2067
        @Restricted(DoNotUse.class)
2068 2069 2070 2071 2072 2073 2074 2075
        public FormValidation doCheckAssignedLabelString(@AncestorInPath AbstractProject<?,?> project,
                                                         @QueryParameter String value) {
          // Provide a legacy interface in case plugins are not going through p:config-assignedLabel
          // see: JENKINS-25372
          LOGGER.log(Level.WARNING, "checking label via legacy '_.assignedLabelString'");
          return doCheckLabel(project, value);
        }

2076 2077
        public FormValidation doCheckLabel(@AncestorInPath AbstractProject<?,?> project,
                                           @QueryParameter String value) {
2078 2079 2080 2081 2082 2083 2084
            return validateLabelExpression(value, project);
        }

        /**
         * Validate label expression string.
         *
         * @param project May be specified to perform project specific validation.
2085
         * @since 1.590
2086 2087
         */
        public static @Nonnull FormValidation validateLabelExpression(String value, @CheckForNull AbstractProject<?, ?> project) {
2088 2089
            if (Util.fixEmpty(value)==null)
                return FormValidation.ok(); // nothing typed yet
2090 2091 2092
            try {
                Label.parseExpression(value);
            } catch (ANTLRException e) {
S
Seiji Sogabe 已提交
2093 2094
                return FormValidation.error(e,
                        Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
2095
            }
2096 2097
            Jenkins j = Jenkins.getInstance();
            Label l = j.getLabel(value);
2098 2099 2100 2101 2102 2103 2104
            if (l.isEmpty()) {
                for (LabelAtom a : l.listAtoms()) {
                    if (a.isEmpty()) {
                        LabelAtom nearest = LabelAtom.findNearest(a.getName());
                        return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch_DidYouMean(a.getName(),nearest.getDisplayName()));
                    }
                }
S
Seiji Sogabe 已提交
2105
                return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
2106
            }
S
Javadoc  
Stephen Connolly 已提交
2107
            if (project != null) {
2108
                for (AbstractProject.LabelValidator v : j
S
Javadoc  
Stephen Connolly 已提交
2109 2110 2111 2112 2113
                        .getExtensionList(AbstractProject.LabelValidator.class)) {
                    FormValidation result = v.check(project, l);
                    if (!FormValidation.Kind.OK.equals(result.kind)) {
                        return result;
                    }
2114 2115
                }
            }
2116
            return FormValidation.okWithMarkup(Messages.AbstractProject_LabelLink(
2117
                    j.getRootUrl(), Util.escape(l.getName()), l.getUrl(), l.getNodes().size(), l.getClouds().size())
2118
            );
2119
        }
2120

2121
        public FormValidation doCheckCustomWorkspace(@QueryParameter String customWorkspace){
2122
        	if(Util.fixEmptyAndTrim(customWorkspace)==null)
S
Seiji Sogabe 已提交
2123
        		return FormValidation.error(Messages.AbstractProject_CustomWorkspaceEmpty());
2124 2125 2126
        	else
        		return FormValidation.ok();
        }
2127

2128 2129
        public AutoCompletionCandidates doAutoCompleteUpstreamProjects(@QueryParameter String value) {
            AutoCompletionCandidates candidates = new AutoCompletionCandidates();
2130
            List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
2131 2132 2133 2134 2135 2136 2137 2138 2139 2140
            for (Job job: jobs) {
                if (job.getFullName().startsWith(value)) {
                    if (job.hasPermission(Item.READ)) {
                        candidates.add(job.getFullName());
                    }
                }
            }
            return candidates;
        }

2141
        @Restricted(DoNotUse.class)
2142 2143 2144 2145 2146 2147 2148
        public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParameter String value) {
          // Provide a legacy interface in case plugins are not going through p:config-assignedLabel
          // see: JENKINS-25372
          LOGGER.log(Level.WARNING, "autocompleting label via legacy '_.assignedLabelString'");
          return doAutoCompleteLabel(value);
        }

2149
        public AutoCompletionCandidates doAutoCompleteLabel(@QueryParameter String value) {
2150
            AutoCompletionCandidates c = new AutoCompletionCandidates();
2151
            Set<Label> labels = Jenkins.getInstance().getLabels();
2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163
            List<String> queries = new AutoCompleteSeeder(value).getSeeds();

            for (String term : queries) {
                for (Label l : labels) {
                    if (l.getName().startsWith(term)) {
                        c.add(l.getName());
                    }
                }
            }
            return c;
        }

2164
        public List<SCMCheckoutStrategyDescriptor> getApplicableSCMCheckoutStrategyDescriptors(AbstractProject p) {
2165 2166 2167
            return SCMCheckoutStrategyDescriptor._for(p);
        }

2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179
        /**
        * Utility class for taking the current input value and computing a list
        * of potential terms to match against the list of defined labels.
         */
        static class AutoCompleteSeeder {
            private String source;

            AutoCompleteSeeder(String source) {
                this.source = source;
            }

            List<String> getSeeds() {
C
Christoph Kutzinski 已提交
2180
                ArrayList<String> terms = new ArrayList<String>();
2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207
                boolean trailingQuote = source.endsWith("\"");
                boolean leadingQuote = source.startsWith("\"");
                boolean trailingSpace = source.endsWith(" ");

                if (trailingQuote || (trailingSpace && !leadingQuote)) {
                    terms.add("");
                } else {
                    if (leadingQuote) {
                        int quote = source.lastIndexOf('"');
                        if (quote == 0) {
                            terms.add(source.substring(1));
                        } else {
                            terms.add("");
                        }
                    } else {
                        int space = source.lastIndexOf(' ');
                        if (space > -1) {
                            terms.add(source.substring(space+1));
                        } else {
                            terms.add(source);
                        }
                    }
                }

                return terms;
            }
        }
2208 2209
    }

2210
    /**
2211
     * Finds a {@link AbstractProject} that has the name closest to the given name.
2212
     * @see Items#findNearest
2213
     */
2214
    public static @CheckForNull AbstractProject findNearest(String name) {
J
Jesse Glick 已提交
2215
        return findNearest(name,Jenkins.getInstance());
2216 2217 2218 2219 2220 2221
    }

    /**
     * Finds a {@link AbstractProject} whose name (when referenced from the specified context) is closest to the given name.
     *
     * @since 1.419
2222
     * @see Items#findNearest
2223
     */
2224
    public static @CheckForNull AbstractProject findNearest(String name, ItemGroup context) {
2225
        return Items.findNearest(AbstractProject.class, name, context);
2226
    }
2227 2228 2229

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
2230
            return o2-o1;
2231 2232
        }
    };
2233

2234
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
2235

2236
    /**
2237
     * @deprecated Just use {@link #CANCEL}.
2238
     */
2239
    @Deprecated
2240
    public static final Permission ABORT = CANCEL;
2241

K
Kohsuke Kawaguchi 已提交
2242
    /**
2243
     * @deprecated Use {@link ParameterizedJobMixIn#BUILD_NOW_TEXT}.
K
Kohsuke Kawaguchi 已提交
2244
     */
2245
    @Deprecated
2246 2247
    public static final Message<AbstractProject> BUILD_NOW_TEXT = new Message<AbstractProject>();

2248 2249 2250 2251 2252 2253
    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static AbstractProject resolveForCLI(
            @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
2254
        AbstractProject item = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class);
2255 2256 2257 2258 2259
        if (item==null) {
            AbstractProject project = AbstractProject.findNearest(name);
            throw new CmdLineException(null, project == null ? Messages.AbstractItem_NoSuchJobExistsWithoutSuggestion(name)
                    : Messages.AbstractItem_NoSuchJobExists(name, project.getFullName()));
        }
2260 2261
        return item;
    }
2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278

    public String getCustomWorkspace() {
        return customWorkspace;
    }

    /**
     * User-specified workspace directory, or null if it's up to Jenkins.
     *
     * <p>
     * Normally a project uses the workspace location assigned by its parent container,
     * but sometimes people have builds that have hard-coded paths.
     *
     * <p>
     * This is not {@link File} because it may have to hold a path representation on another OS.
     *
     * <p>
     * If this path is relative, it's resolved against {@link Node#getRootPath()} on the node where this workspace
2279
     * is prepared.
2280
     *
2281
     * @since 1.410
2282 2283
     */
    public void setCustomWorkspace(String customWorkspace) throws IOException {
2284
        this.customWorkspace= Util.fixEmptyAndTrim(customWorkspace);
2285 2286
        save();
    }
2287 2288 2289

    /**
     * Plugins may want to contribute additional restrictions on the use of specific labels for specific projects.
S
Javadoc  
Stephen Connolly 已提交
2290 2291 2292
     * This extension point allows such restrictions.
     *
     * @since 1.540
2293 2294
     */
    public static abstract class LabelValidator implements ExtensionPoint {
S
Javadoc  
Stephen Connolly 已提交
2295 2296 2297 2298 2299 2300 2301 2302 2303
        /**
         * Check the use of the label within the specified context.
         *
         * @param project the project that wants to restrict itself to the specified label.
         * @param label   the label that the project wants to restrict itself to.
         * @return the {@link FormValidation} result.
         */
        @Nonnull
        public abstract FormValidation check(@Nonnull AbstractProject<?, ?> project, @Nonnull Label label);
2304
    }
S
Javadoc  
Stephen Connolly 已提交
2305

2306
}