
hudson.model.AbstractProject Maven / Gradle / Ivy
Show all versions of hudson-core Show documentation
/**
* *****************************************************************************
*
* Copyright (c) 2004-2011 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste
* Quenot, Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom
* Huybrechts, id:cactusman, Yahoo! Inc., Anton Kozak, Nikita Levyankov
*
*
******************************************************************************
*/
package hudson.model;
import antlr.ANTLRException;
import hudson.AbortException;
import hudson.CopyOnWrite;
import hudson.FeedAdapter;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Cause.LegacyCodeCause;
import hudson.model.Cause.RemoteCause;
import hudson.model.Cause.UserCause;
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
import hudson.util.CascadingUtil;
import hudson.util.DescribableListUtil;
import org.hudsonci.model.project.property.IntegerProjectProperty;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.SubTask;
import hudson.model.queue.SubTaskContributor;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.NullSCM;
import hudson.scm.PollingResult;
import hudson.scm.SCM;
import hudson.scm.SCMRevisionState;
import hudson.scm.SCMS;
import hudson.search.SearchIndexBuilder;
import hudson.security.Permission;
import hudson.slaves.WorkspaceList;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildTrigger;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.tasks.Mailer;
import hudson.tasks.Publisher;
import hudson.triggers.SCMTrigger;
import hudson.triggers.Trigger;
import hudson.triggers.TriggerDescriptor;
import hudson.util.AutoCompleteSeeder;
import hudson.util.DescribableList;
import hudson.util.EditDistance;
import hudson.util.FormValidation;
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
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.GregorianCalendar;
import java.util.HashSet;
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.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.math.NumberUtils;
import org.hudsonci.api.model.IAbstractProject;
import org.hudsonci.model.project.property.TriggerProjectProperty;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import static hudson.scm.PollingResult.BUILD_NOW;
import static hudson.scm.PollingResult.NO_CHANGES;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
/**
* Base implementation of {@link Job}s that build software.
*
* For now this is primarily the common part of {@link Project} and MavenModule.
*
* @author Kohsuke Kawaguchi
* @see AbstractBuild
*/
public abstract class AbstractProject, R extends AbstractBuild
> extends Job
implements BuildableItem,
IAbstractProject {
public static final String CONCURRENT_BUILD_PROPERTY_NAME = "concurrentBuild";
public static final String CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME = "cleanWorkspaceRequired";
public static final String BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME = "blockBuildWhenDownstreamBuilding";
public static final String BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME = "blockBuildWhenUpstreamBuilding";
public static final String QUIET_PERIOD_PROPERTY_NAME = "quietPeriod";
public static final String SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME = "scmCheckoutRetryCount";
public static final String CUSTOM_WORKSPACE_PROPERTY_NAME = "customWorkspace";
public static final String JDK_PROPERTY_NAME = "jdk";
public static final String SCM_PROPERTY_NAME = "scm";
public static final String HAS_QUIET_PERIOD_PROPERTY_NAME = "hasQuietPeriod";
public static final String HAS_SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME = "hasScmCheckoutRetryCount";
public static final String BUILD_TRIGGER_PROPERTY_NAME = "hudson-tasks-BuildTrigger";
public static final String APPOINTED_NODE_PROPERTY_NAME = "appointedNode";
public static final String BASIC_KEY = "basic";
public static final String AFFINITY_CHO0SER_KEY = "affinityChooser";
public static final String SLAVE_KEY = "slave";
public static final String ASSIGNED_LABEL_KEY = "_.assignedLabelString";
/**
* {@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()}.
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*/
@Deprecated
private volatile SCM scm = new NullSCM();
/**
* State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
*/
private volatile transient SCMRevisionState pollingBaseline = null;
/**
* All the builds keyed by their build number.
*/
protected transient /*
* almost final
*/ RunMap builds = new RunMap();
/**
* The quiet period. Null to delegate to the system default.
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*
*/
@Deprecated
private volatile Integer quietPeriod = null;
/**
* The retry count. Null to delegate to the system default.
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*/
@Deprecated
private volatile Integer scmCheckoutRetryCount = null;
/**
* 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.
*
* @see #canRoam
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to
* {@link hudson.model.AbstractProject#getAppointedNode()#getName()}.
*/
@Deprecated
private String assignedNode;
/**
* Node list is dropdown or textfield
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to
* {@link hudson.model.AbstractProject#getAppointedNode()#isAdvancedAffinityChooser()}.
*/
@Deprecated
private Boolean advancedAffinityChooser;
/**
* True if this project can be built on any node.
*
* This somewhat ugly flag combination is so that we can migrate
* existing Hudson installations nicely.
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to
* {@link hudson.model.AbstractProject#getAppointedNode()#getCanRoam}.
*/
@Deprecated
private volatile boolean canRoam;
/**
* True to suspend new builds.
*/
protected volatile boolean disabled;
/**
* True to keep builds of this project in queue when downstream projects are
* building.
*
* @deprecated as of 2.2.0. Don't use this field directly, logic was moved
* to {@link org.hudsonci.api.model.IProjectProperty}. Use
* getter/setter for accessing to this field.
*/
@Deprecated
protected volatile boolean blockBuildWhenDownstreamBuilding;
/**
* True to keep builds of this project in queue when upstream projects are
* building.
*
* @deprecated as of 2.2.0. Don't use this field directly, logic was moved
* to {@link org.hudsonci.api.model.IProjectProperty}. Use
* getter/setter for accessing to this field.
*/
@Deprecated
protected volatile boolean blockBuildWhenUpstreamBuilding;
/**
* Identifies {@link JDK} to be used. Null if no explicit configuration is
* required.
*
*
Can't store {@link JDK} directly because {@link Hudson} and {@link Project}
* are saved independently.
*
* @see Hudson#getJDK(String)
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*/
@Deprecated
private volatile String jdk;
/**
* @deprecated since 2007-01-29.
*/
@Deprecated
private transient boolean enableRemoteTrigger;
private volatile BuildAuthorizationToken authToken = null;
/**
* List of all {@link Trigger}s for this project.
*
* @deprecated as of 2.2.0
*
* don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*/
protected List> triggers = new Vector>();
/**
* {@link Action}s contributed from subsidiary objects associated with
* {@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.
*/
@CopyOnWrite
protected transient volatile List transientActions = new Vector();
/**
* @deprecated as of 2.2.0 Don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*/
@Deprecated
private boolean concurrentBuild;
/**
* True to clean the workspace prior to each build.
*
* @deprecated as of 2.2.0 don't use this field directly, logic was moved to {@link org.hudsonci.api.model.IProjectProperty}.
* Use getter/setter for accessing to this field.
*/
@Deprecated
private volatile boolean cleanWorkspaceRequired;
protected AbstractProject(ItemGroup parent, String name) {
super(parent, name);
//TODO: Investigate when this case happens.
//if (Hudson.getInstance() != null && !Hudson.getInstance().getNodes().isEmpty()) {
// if a new job is configured with Hudson that already has slave nodes
// make it roamable by default
// canRoam = true;
//}
}
@Override
public void onCreatedFromScratch() {
super.onCreatedFromScratch();
// solicit initial contributions, especially from TransientProjectActionFactory
updateTransientActions();
setCreationTime(new GregorianCalendar().getTimeInMillis());
User user = User.current();
if (user != null) {
setCreatedBy(user.getId());
grantProjectMatrixPermissions(user);
}
}
@Override
public void onLoad(ItemGroup extends Item> parent, String name) throws IOException {
super.onLoad(parent, name);
this.builds = new RunMap();
this.builds.load(this, new Constructor() {
public R create(File dir) throws IOException {
return loadBuild(dir);
}
});
// boolean! Can't tell if xml file contained false..
if (enableRemoteTrigger) {
OldDataMonitor.report(this, "1.77");
}
for (Trigger t : getTriggerDescribableList()) {
t.start(this, false);
}
if (scm == null) {
scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
}
if (transientActions == null) {
transientActions = new Vector(); // happens when loaded from disk
}
updateTransientActions();
getTriggerDescribableList().setOwner(this);
}
@Override
protected void buildProjectProperties() throws IOException {
super.buildProjectProperties();
convertBlockBuildWhenUpstreamBuildingProperty();
convertBlockBuildWhenDownstreamBuildingProperty();
convertConcurrentBuildProperty();
convertCleanWorkspaceRequiredProperty();
convertQuietPeriodProperty();
convertScmCheckoutRetryCountProperty();
convertJDKProperty();
convertScmProperty();
convertTriggerProperties();
convertAppointedNode();
}
void convertBlockBuildWhenUpstreamBuildingProperty() throws IOException {
if (null == getProperty(BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME)) {
setBlockBuildWhenUpstreamBuilding(blockBuildWhenUpstreamBuilding);
blockBuildWhenUpstreamBuilding = false;
}
}
void convertBlockBuildWhenDownstreamBuildingProperty() throws IOException {
if (null == getProperty(BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME)) {
setBlockBuildWhenDownstreamBuilding(blockBuildWhenDownstreamBuilding);
blockBuildWhenDownstreamBuilding = false;
}
}
void convertConcurrentBuildProperty() throws IOException {
if (null == getProperty(CONCURRENT_BUILD_PROPERTY_NAME)) {
setConcurrentBuild(concurrentBuild);
concurrentBuild = false;
}
}
void convertCleanWorkspaceRequiredProperty() throws IOException {
if (null == getProperty(CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME)) {
setCleanWorkspaceRequired(cleanWorkspaceRequired);
cleanWorkspaceRequired = false;
}
}
void convertQuietPeriodProperty() throws IOException {
if (null != quietPeriod && null == getProperty(QUIET_PERIOD_PROPERTY_NAME)) {
setQuietPeriod(quietPeriod);
quietPeriod = null;
}
}
void convertScmCheckoutRetryCountProperty() throws IOException {
if (null != scmCheckoutRetryCount && null == getProperty(SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME)) {
setScmCheckoutRetryCount(scmCheckoutRetryCount);
scmCheckoutRetryCount = null;
}
}
void convertJDKProperty() throws IOException {
if (null != jdk && null == getProperty(JDK_PROPERTY_NAME)) {
setJDK(jdk);
jdk = null;
}
}
void convertScmProperty() throws IOException {
if (null != scm && null == getProperty(SCM_PROPERTY_NAME)) {
setScm(scm);
scm = null;
}
}
void convertTriggerProperties() {
if (triggers != null) {
setTriggers(triggers);
triggers = null;
}
}
void convertAppointedNode() {
if (assignedNode != null && getProperty(APPOINTED_NODE_PROPERTY_NAME) == null) {
setAppointedNode(new AppointedNode(assignedNode, advancedAffinityChooser));
assignedNode = null;
advancedAffinityChooser = null;
}
}
@Override
protected void performDelete() throws IOException, InterruptedException {
// prevent a new build while a delete operation is in progress
makeDisabled(true);
FilePath ws = getWorkspace();
if (ws != null) {
Node on = getLastBuiltOn();
getScm().processWorkspaceBeforeDeletion(this, ws, on);
if (on != null) {
on.getFileSystemProvisioner().discardWorkspace(this, ws);
}
}
super.performDelete();
}
/**
* Does this project perform concurrent builds?
*
* @since 1.319
*/
@Exported
public boolean isConcurrentBuild() {
return Hudson.CONCURRENT_BUILD
&& CascadingUtil.getBooleanProjectProperty(this, CONCURRENT_BUILD_PROPERTY_NAME).getValue();
}
public void setConcurrentBuild(boolean b) throws IOException {
CascadingUtil.getBooleanProjectProperty(this, CONCURRENT_BUILD_PROPERTY_NAME).setValue(b);
save();
}
public boolean isCleanWorkspaceRequired() {
return CascadingUtil.getBooleanProjectProperty(this, CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME).getValue();
}
public void setCleanWorkspaceRequired(boolean cleanWorkspaceRequired) {
CascadingUtil.getBooleanProjectProperty(this,
CLEAN_WORKSPACE_REQUIRED_PROPERTY_NAME).setValue(cleanWorkspaceRequired);
}
/**
* If this project is configured to be always built on this node, return
* that {@link Node}. Otherwise null.
*/
public Label getAssignedLabel() {
return getAppointedNode() == null ? null : getAppointedNode().getAssignedLabel();
}
/**
* Gets the textual representation of the assigned label as it was entered
* by the user.
*/
public String getAssignedLabelString() {
return getAppointedNode() == null ? null : getAppointedNode().getAssignedLabelString();
}
/**
* Sets the assigned label.
*
* @param label node label.
*
* @throws java.io.IOException exception.
*/
public void setAssignedLabel(Label label) throws IOException {
AppointedNode node = getAppointedNode();
if (node == null) {
node = new AppointedNode();
setAppointedNode(node);
}
node.setAssignedLabel(label);
save();
}
/**
* Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
*
* @param node node.
* @throws java.io.IOException exception
*/
public void setAssignedNode(Node node) throws IOException {
setAssignedLabel(node.getSelfLabel());
}
/**
* Gets whether this project is using the advanced affinity chooser UI.
*
* @return true - advanced chooser, false - simple textfield.
*/
public boolean isAdvancedAffinityChooser() {
//For newly created project advanced chooser is not used.
//Set value to false in order to avoid NullPointerException
return getAppointedNode() != null && getAppointedNode().getAdvancedAffinityChooser();
}
/**
* Sets whether this project is using the advanced affinity chooser UI.
*
* @param b true - advanced chooser, false - otherwise
* @throws java.io.IOException exception.
*/
public void setAdvancedAffinityChooser(boolean b) throws IOException {
AppointedNode node = getAppointedNode();
if (node == null) {
node = new AppointedNode();
setAppointedNode(node);
}
node.setAdvancedAffinityChooser(b);
save();
}
/**
* Sets {@link AppointedNode}.
*
* @param appointedNode {@link AppointedNode}.
*/
@SuppressWarnings("unchecked")
public void setAppointedNode(AppointedNode appointedNode) {
CascadingUtil.getBaseProjectProperty(this, APPOINTED_NODE_PROPERTY_NAME).setValue(appointedNode);
}
/**
* Returns {@link AppointedNode}. Returned value is not null.
*
* @return appointedNode {@link AppointedNode}.
*/
public AppointedNode getAppointedNode() {
return (AppointedNode) CascadingUtil.getBaseProjectProperty(this, APPOINTED_NODE_PROPERTY_NAME).getValue();
}
/**
* Get the term used in the UI to represent this kind of {@link AbstractProject}.
* Must start with a capital letter.
*/
@Override
public String getPronoun() {
return Messages.AbstractProject_Pronoun();
}
/**
* Returns the root project value.
*
* @return the root project value.
*/
public AbstractProject getRootProject() {
if (this.getParent() instanceof Hudson) {
return this;
} else {
return ((AbstractProject) this.getParent()).getRootProject();
}
}
/**
* Gets the directory where the module is checked out.
*
* @return null if the workspace is on a slave that's not connected.
* @deprecated as of 1.319 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.
*
* If you are calling this method during a build from an executor,
* switch it to {@link AbstractBuild#getWorkspace()}. If you are calling
* this method to serve a file from the workspace, doing a form validation,
* etc., then use {@link #getSomeWorkspace()}
*/
public final FilePath getWorkspace() {
AbstractBuild b = getBuildForDeprecatedMethods();
return b != null ? b.getWorkspace() : null;
}
/**
* Various deprecated methods in this class all need the 'current' build.
* This method returns the build suitable for that purpose.
*
* @return An AbstractBuild for deprecated methods to use.
*/
private AbstractBuild getBuildForDeprecatedMethods() {
Executor e = Executor.currentExecutor();
if (e != null) {
Executable exe = e.getCurrentExecutable();
if (exe instanceof AbstractBuild) {
AbstractBuild b = (AbstractBuild) exe;
if (b.getProject() == this) {
return b;
}
}
}
R lb = getLastBuild();
if (lb != null) {
return lb;
}
return null;
}
/**
* Gets a workspace for some build of this project.
*
*
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.
* @since 1.319
*/
public final FilePath getSomeWorkspace() {
R b = getSomeBuildWithWorkspace();
return b != null ? b.getWorkspace() : null;
}
/**
* Gets some build that has a live workspace.
*
* @return null if no such build exists.
*/
public final R getSomeBuildWithWorkspace() {
int cnt = 0;
for (R b = getLastBuild(); cnt < 5 && b != null; b = b.getPreviousBuild()) {
FilePath ws = b.getWorkspace();
if (ws != null) {
return b;
}
}
return null;
}
/**
* Returns the root directory of the checked-out module.
This is usually
* where pom.xml, build.xml and so on exists.
*
* @deprecated as of 1.319 See {@link #getWorkspace()} for a migration
* strategy.
*/
public FilePath getModuleRoot() {
AbstractBuild b = getBuildForDeprecatedMethods();
return b != null ? b.getModuleRoot() : null;
}
/**
* Returns the root directories of all checked-out modules.
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.
*
* @deprecated as of 1.319 See {@link #getWorkspace()} for a migration
* strategy.
*/
public FilePath[] getModuleRoots() {
AbstractBuild b = getBuildForDeprecatedMethods();
return b != null ? b.getModuleRoots() : null;
}
public int getQuietPeriod() {
IntegerProjectProperty property = CascadingUtil.getIntegerProjectProperty(this, QUIET_PERIOD_PROPERTY_NAME);
Integer value = property.getValue();
return property.getDefaultValue().equals(value) ? Hudson.getInstance().getQuietPeriod() : value;
}
/**
* Sets the custom quiet period of this project, or revert to the global
* default if null is given.
*
* @param seconds quiet period
* @throws IOException if any.
*/
public void setQuietPeriod(Integer seconds) throws IOException {
CascadingUtil.getIntegerProjectProperty(this, QUIET_PERIOD_PROPERTY_NAME).setValue(seconds);
save();
}
public int getScmCheckoutRetryCount() {
IntegerProjectProperty property = CascadingUtil.getIntegerProjectProperty(this,
SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME);
Integer value = property.getValue();
return property.getDefaultValue().equals(value) ? Hudson.getInstance().getScmCheckoutRetryCount() : value;
}
public void setScmCheckoutRetryCount(Integer retryCount) {
CascadingUtil.getIntegerProjectProperty(this, SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME).setValue(retryCount);
}
/**
* Sets scmCheckoutRetryCount, Uses {@link NumberUtils#isNumber(String)} for
* checking retryCount param. If it is not valid number, null will be set.
*
* @param scmCheckoutRetryCount retry count.
* @throws IOException if any.
*/
protected void setScmCheckoutRetryCount(String scmCheckoutRetryCount) throws IOException {
Integer retryCount = null;
if (NumberUtils.isNumber(scmCheckoutRetryCount)) {
retryCount = NumberUtils.createInteger(scmCheckoutRetryCount);
}
setScmCheckoutRetryCount(retryCount);
}
/**
* @return true if quiet period was configured.
* @deprecated as of 2.1.2 This method was used only on UI side. No longer
* required.
*/
// ugly name because of EL
public boolean getHasCustomQuietPeriod() {
return null != CascadingUtil.getIntegerProjectProperty(this, QUIET_PERIOD_PROPERTY_NAME).getValue();
}
/**
* Sets quietPeriod, Uses {@link NumberUtils#isNumber(String)} for checking
* seconds param. If seconds is not valid number, null will be set.
*
* @param seconds quiet period.
* @throws IOException if any.
*/
protected void setQuietPeriod(String seconds) throws IOException {
Integer period = null;
if (NumberUtils.isNumber(seconds)) {
period = NumberUtils.createInteger(seconds);
}
setQuietPeriod(period);
}
/**
* Checks whether scmRetryCount is configured
*
* @return true if yes, false - otherwise.
* @deprecated as of 2.1.2
*/
public boolean hasCustomScmCheckoutRetryCount() {
return null != CascadingUtil.getIntegerProjectProperty(this, SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME).getValue();
}
@Override
public boolean isBuildable() {
return !isDisabled() && !isHoldOffBuildUntilSave();
}
/**
* Used in sidepanel.jelly to decide whether to display the
* config/delete/build links.
*/
public boolean isConfigurable() {
return true;
}
public boolean blockBuildWhenDownstreamBuilding() {
return CascadingUtil.getBooleanProjectProperty(this,
BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME).getValue();
}
public void setBlockBuildWhenDownstreamBuilding(boolean b) throws IOException {
CascadingUtil.getBooleanProjectProperty(this, BLOCK_BUILD_WHEN_DOWNSTREAM_BUILDING_PROPERTY_NAME).setValue(b);
save();
}
public boolean blockBuildWhenUpstreamBuilding() {
return CascadingUtil.getBooleanProjectProperty(this,
BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME).getValue();
}
public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
CascadingUtil.getBooleanProjectProperty(this, BLOCK_BUILD_WHEN_UPSTREAM_BUILDING_PROPERTY_NAME).setValue(b);
save();
}
public boolean isDisabled() {
return disabled;
}
/**
* Validates the retry count Regex
*/
public FormValidation doCheckRetryCount(@QueryParameter String value) throws IOException, ServletException {
// 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");
}
return FormValidation.ok();
}
/**
* Marks the build as disabled.
*/
public void makeDisabled(boolean b) throws IOException {
if (disabled == b) {
return; // noop
}
this.disabled = b;
if (b) {
Hudson.getInstance().getQueue().cancel(this);
}
save();
}
public void disable() throws IOException {
makeDisabled(true);
}
public void enable() throws IOException {
makeDisabled(false);
}
@Override
public BallColor getIconColor() {
if (isDisabled()) {
return BallColor.DISABLED;
} else {
return super.getIconColor();
}
}
/**
* effectively deprecated. Since using updateTransientActions correctly
* under concurrent environment requires a lock that can too easily cause
* deadlocks.
*
*
Override {@link #createTransientActions()} instead.
*/
protected void updateTransientActions() {
transientActions = createTransientActions();
}
protected List createTransientActions() {
Vector ta = new Vector();
for (JobProperty super P> p : getAllProperties()) {
ta.addAll(p.getJobActions((P) this));
}
for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all()) {
ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
}
return ta;
}
/**
* Returns the live list of all {@link Publisher}s configured for this
* project.
*
* This method couldn't be called getPublishers() because
* existing methods in sub-classes return different inconsistent types.
*/
public abstract DescribableList> getPublishersList();
@Override
public void addProperty(JobProperty super P> jobProp) throws IOException {
super.addProperty(jobProp);
updateTransientActions();
}
public List getProminentActions() {
List a = getActions();
List pa = new Vector();
for (Action action : a) {
if (action instanceof ProminentProjectAction) {
pa.add((ProminentProjectAction) action);
}
}
return pa;
}
@Override
public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
super.doConfigSubmit(req, rsp);
updateTransientActions();
Set upstream = Collections.emptySet();
if (req.getParameter("pseudoUpstreamTrigger") != null) {
upstream = new HashSet(Items.fromNameList(req.getParameter("upstreamProjects"), AbstractProject.class));
}
// dependency setting might have been changed by the user, so rebuild.
Hudson.getInstance().rebuildDependencyGraph();
// reflect the submission of the pseudo 'upstream build trriger'.
// this needs to be done after we release the lock on 'this',
// or otherwise we could dead-lock
for (AbstractProject, ?> p : Hudson.getInstance().getAllItems(AbstractProject.class)) {
// Don't consider child projects such as MatrixConfiguration:
if (!p.isConfigurable()) {
continue;
}
boolean isUpstream = upstream.contains(p);
synchronized (p) {
// does 'p' include us in its BuildTrigger?
DescribableList> pl = p.getPublishersList();
BuildTrigger trigger = pl.get(BuildTrigger.class);
List newChildProjects = trigger == null ? new ArrayList() : trigger.getChildProjects();
if (isUpstream) {
if (!newChildProjects.contains(this)) {
newChildProjects.add(this);
}
} else {
newChildProjects.remove(this);
}
if (newChildProjects.isEmpty()) {
pl.remove(BuildTrigger.class);
} else {
// here, we just need to replace the old one with the new one,
// but there was a regression (we don't know when it started) that put multiple BuildTriggers
// into the list.
// for us not to lose the data, we need to merge them all.
List existingList = pl.getAll(BuildTrigger.class);
BuildTrigger existing;
switch (existingList.size()) {
case 0:
existing = null;
break;
case 1:
existing = existingList.get(0);
break;
default:
pl.removeAll(BuildTrigger.class);
Set combinedChildren = new HashSet();
for (BuildTrigger bt : existingList) {
combinedChildren.addAll(bt.getChildProjects());
}
existing = new BuildTrigger(new ArrayList(combinedChildren), existingList.get(0).getThreshold());
pl.add(existing);
break;
}
if (existing != null && existing.hasSame(newChildProjects)) {
continue; // no need to touch
}
pl.replace(new BuildTrigger(newChildProjects,
existing == null ? Result.SUCCESS : existing.getThreshold()));
}
BuildTrigger buildTrigger = pl.get(BuildTrigger.class);
CascadingUtil.getExternalProjectProperty(p, BUILD_TRIGGER_PROPERTY_NAME).setValue(buildTrigger);
}
}
// notify the queue as the project might be now tied to different node
Hudson.getInstance().getQueue().scheduleMaintenance();
// this is to reflect the upstream build adjustments done above
Hudson.getInstance().rebuildDependencyGraph();
}
/**
* @deprecated Use {@link #scheduleBuild(Cause)}. Since 1.283
*/
public boolean scheduleBuild() {
return scheduleBuild(new LegacyCodeCause());
}
/**
* @deprecated Use {@link #scheduleBuild(int, Cause)}. Since 1.283
*/
public boolean scheduleBuild(int quietPeriod) {
return scheduleBuild(quietPeriod, new LegacyCodeCause());
}
/**
* Schedules a build of this project.
*
* @return true if the project is actually added to the queue. false if the
* queue contained it and therefore the add() was noop
*/
public boolean scheduleBuild(Cause c) {
return scheduleBuild(getQuietPeriod(), c);
}
public boolean scheduleBuild(int quietPeriod, Cause c) {
return scheduleBuild(quietPeriod, c, new Action[0]);
}
/**
* 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) {
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.
*
* @param actions For the convenience of the caller, this array can contain
* null, and those will be silently ignored.
*/
public Future scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
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
*/
public Future scheduleBuild2(int quietPeriod, Cause c, Collection extends Action> actions) {
if (!isBuildable()) {
return null;
}
List queueActions = new ArrayList(actions);
if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
queueActions.add(new ParametersAction(getDefaultParametersValues()));
}
if (c != null) {
queueActions.add(new CauseAction(c));
}
WaitingItem i = Hudson.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
if (i != null) {
return (Future) i.getFuture();
}
return null;
}
private List getDefaultParametersValues() {
ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
ArrayList defValues = new ArrayList();
/*
* This check is made ONLY if someone will call this method even if
* isParametrized() is false.
*/
if (paramDefProp == null) {
return defValues;
}
/*
* Scan for all parameter with an associated default values
*/
for (ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions()) {
ParameterValue defaultValue = paramDefinition.getDefaultParameterValue();
if (defaultValue != null) {
defValues.add(defaultValue);
}
}
return defValues;
}
/**
* Schedules a build, and returns a {@link Future} object to wait for the
* completion of the build.
*
* Production code shouldn't be using this, but for tests this is very
* convenient, so this isn't marked as deprecated.
*/
public Future scheduleBuild2(int quietPeriod) {
return scheduleBuild2(quietPeriod, new LegacyCodeCause());
}
/**
* Schedules a build of this project, and returns a {@link Future} object to
* wait for the completion of the build.
*/
public Future scheduleBuild2(int quietPeriod, Cause c) {
return scheduleBuild2(quietPeriod, c, new Action[0]);
}
/**
* Schedules a polling of this project.
*/
public boolean schedulePolling() {
if (isDisabled()) {
return false;
}
SCMTrigger scmt = getTrigger(SCMTrigger.class);
if (scmt == null) {
return false;
}
scmt.run();
return true;
}
/**
* Returns true if the build is in the queue.
*/
@Override
public boolean isInQueue() {
return Hudson.getInstance().getQueue().contains(this);
}
@Override
public Queue.Item getQueueItem() {
return Hudson.getInstance().getQueue().getItem(this);
}
/**
* @return name of jdk chosen for current project. Could taken from parent
*/
public String getJDKName() {
return CascadingUtil.getStringProjectProperty(this, JDK_PROPERTY_NAME).getValue();
}
/**
* @return JDK that this project is configured with, or null.
*/
public JDK getJDK() {
return Hudson.getInstance().getJDK(getJDKName());
}
/**
* Overwrites the JDK setting.
*/
public void setJDK(JDK jdk) throws IOException {
setJDK(jdk.getName());
save();
}
public void setJDK(String jdk) {
CascadingUtil.getStringProjectProperty(this, JDK_PROPERTY_NAME).setValue(jdk);
}
public BuildAuthorizationToken getAuthToken() {
return authToken;
}
@Override
public SortedMap _getRuns() {
return builds.getView();
}
@Override
public void removeRun(R run) {
this.builds.remove(run);
}
/**
* Determines Class<R>.
*/
protected abstract Class getBuildClass();
// keep track of the previous time we started a build
private transient long lastBuildStartTime;
/**
* Creates a new build of this project for immediate execution.
*/
protected synchronized R newBuild() throws IOException {
// make sure we don't start two builds in the same second
// so the build directories will be different too
long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
if (timeSinceLast < 1000) {
try {
Thread.sleep(1000 - timeSinceLast);
} catch (InterruptedException e) {
}
}
lastBuildStartTime = System.currentTimeMillis();
try {
R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
builds.put(lastBuild);
return lastBuild;
} catch (InstantiationException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw handleInvocationTargetException(e);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
private IOException handleInvocationTargetException(InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Error) {
throw (Error) t;
}
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof IOException) {
return (IOException) t;
}
throw new Error(t);
}
/**
* Loads an existing build record from disk.
*/
protected R loadBuild(File dir) throws IOException {
try {
return getBuildClass().getConstructor(getClass(), File.class).newInstance(this, dir);
} catch (InstantiationException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw handleInvocationTargetException(e);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
/**
* {@inheritDoc}
*
* Note that this method returns a read-only view of {@link Action}s.
* {@link BuildStep}s and others who want to add a project action should do
* so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
*
* @see TransientProjectActionFactory
*/
@Override
public synchronized List getActions() {
// add all the transient actions, too
List actions = new Vector(super.getActions());
actions.addAll(transientActions);
// return the read only list to cause a failure on plugins who try to add an action here
return Collections.unmodifiableList(actions);
}
/**
* Gets the {@link Node} where this project was last built on.
*
* @return null if no information is available (for example, if no build was
* done yet.)
*/
public Node getLastBuiltOn() {
// where was it built on?
AbstractBuild b = getLastBuild();
if (b == null) {
return null;
} else {
return b.getBuiltOn();
}
}
public Object getSameNodeConstraint() {
return this; // in this way, any member that wants to run with the main guy can nominate the project itself
}
public final Task getOwnerTask() {
return this;
}
/**
* {@inheritDoc}
*
* A project must be blocked if its own previous build is in progress,
* or if the blockBuildWhenUpstreamBuilding option is true and an upstream
* project is building, but derived classes can also check other conditions.
*/
public boolean isBuildBlocked() {
return getCauseOfBlockage() != null;
}
public String getWhyBlocked() {
CauseOfBlockage cb = getCauseOfBlockage();
return cb != null ? cb.getShortDescription() : null;
}
/**
* Blocked because the previous build is already in progress.
*/
public static class BecauseOfBuildInProgress extends CauseOfBlockage {
private final AbstractBuild, ?> build;
public BecauseOfBuildInProgress(AbstractBuild, ?> build) {
this.build = build;
}
@Override
public String getShortDescription() {
Executor e = build.getExecutor();
String eta = "";
if (e != null) {
eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
}
int lbn = build.getNumber();
return Messages.AbstractProject_BuildInProgress(lbn, eta);
}
}
/**
* Because the downstream build is in progress, and we are configured to
* wait for that.
*/
public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage {
//TODO: review and check whether we can do it private
public final AbstractProject, ?> up;
public AbstractProject getUp() {
return up;
}
public BecauseOfDownstreamBuildInProgress(AbstractProject, ?> up) {
this.up = up;
}
@Override
public String getShortDescription() {
return Messages.AbstractProject_DownstreamBuildInProgress(up.getName());
}
}
/**
* Because the upstream build is in progress, and we are configured to wait
* for that.
*/
public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage {
//TODO: review and check whether we can do it private
public final AbstractProject, ?> up;
public BecauseOfUpstreamBuildInProgress(AbstractProject, ?> up) {
this.up = up;
}
public AbstractProject getUp() {
return up;
}
@Override
public String getShortDescription() {
return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
}
}
public CauseOfBlockage getCauseOfBlockage() {
if (isBuilding() && !isConcurrentBuild()) {
return new BecauseOfBuildInProgress(getLastBuild());
}
if (blockBuildWhenDownstreamBuilding()) {
AbstractProject, ?> bup = getBuildingDownstream();
if (bup != null) {
return new BecauseOfDownstreamBuildInProgress(bup);
}
}
if (blockBuildWhenUpstreamBuilding()) {
AbstractProject, ?> bup = getBuildingUpstream();
if (bup != null) {
return new BecauseOfUpstreamBuildInProgress(bup);
}
}
return null;
}
/**
* Returns the project if any of the downstream project (or itself) is
* either building or is in the unblocked queue.
This means eventually
* there will be an automatic triggering of the given project (provided that
* all builds went smoothly.)
*/
protected AbstractProject getBuildingDownstream() {
DependencyGraph graph = Hudson.getInstance().getDependencyGraph();
Set tups = graph.getTransitiveDownstream(this);
tups.add(this);
for (AbstractProject tup : tups) {
if (tup != this && (tup.isBuilding() || tup.isInUnblockedQueue())) {
return tup;
}
}
return null;
}
/**
* Returns the project if any of the upstream project (or itself) is either
* building or is in the unblocked queue. This means eventually there
* will be an automatic triggering of the given project (provided that all
* builds went smoothly.)
*/
protected AbstractProject getBuildingUpstream() {
DependencyGraph graph = Hudson.getInstance().getDependencyGraph();
Set tups = graph.getTransitiveUpstream(this);
tups.add(this);
for (AbstractProject tup : tups) {
if (tup != this && (tup.isBuilding() || tup.isInUnblockedQueue())) {
return tup;
}
}
return null;
}
private boolean isInUnblockedQueue() {
return Hudson.getInstance().getQueue().getUnblockedQueuedTasks().contains(this);
}
public List getSubTasks() {
List r = new ArrayList();
r.add(this);
for (SubTaskContributor euc : SubTaskContributor.all()) {
r.addAll(euc.forProject(this));
}
for (JobProperty super P> p : getAllProperties()) {
r.addAll(p.getSubTasks());
}
return r;
}
public R createExecutable() throws IOException {
if (isDisabled()) {
return null;
}
return newBuild();
}
public void checkAbortPermission() {
checkPermission(AbstractProject.ABORT);
}
public boolean hasAbortPermission() {
return hasPermission(AbstractProject.ABORT);
}
/**
* Gets the {@link Resource} that represents the workspace of this project.
* Useful for locking and mutual exclusion control.
*
* @deprecated as of 1.319 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.
*
* 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}.
*/
public Resource getWorkspaceResource() {
return new Resource(getFullDisplayName() + " workspace");
}
/**
* List of necessary resources to perform the build of this project.
*/
public ResourceList getResourceList() {
final Set resourceActivities = getResourceActivities();
final List resourceLists = new ArrayList(1 + resourceActivities.size());
for (ResourceActivity activity : resourceActivities) {
if (activity != this && activity != null) {
// defensive infinite recursion and null check
resourceLists.add(activity.getResourceList());
}
}
return ResourceList.union(resourceLists);
}
/**
* 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.
*/
protected Set getResourceActivities() {
return Collections.emptySet();
}
public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
SCM scm = getScm();
if (scm == null) {
return true; // no SCM
}
FilePath workspace = build.getWorkspace();
workspace.mkdirs();
boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
calcPollingBaseline(build, launcher, listener);
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 {
baseline = getScm()._calcRevisionsFromBuild(build, launcher, listener);
} 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;
}
/**
* For reasons I don't understand, if I inline this method,
* AbstractMethodError escapes try/catch block.
*/
private SCMRevisionState safeCalcRevisionsFromBuild(AbstractBuild build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
return getScm()._calcRevisionsFromBuild(build, launcher, listener);
}
/**
* Checks if there's any update in SCM, and returns true if any is found.
*
* @deprecated as of 1.346 Use {@link #poll(TaskListener)} instead.
*/
public boolean pollSCMChanges(TaskListener listener) {
return poll(listener).hasChanges();
}
/**
* Checks if there's any update in SCM, and returns true if any is found.
*
* The implementation is responsible for ensuring mutual exclusion
* between polling and builds if necessary.
*
* @since 1.345
*/
public PollingResult poll(TaskListener listener) {
SCM scm = getScm();
if (scm == null) {
listener.getLogger().println(Messages.AbstractProject_NoSCM());
return NO_CHANGES;
}
if (isDisabled()) {
listener.getLogger().println(Messages.AbstractProject_Disabled());
return NO_CHANGES;
}
R lb = getLastBuild();
if (lb == null) {
listener.getLogger().println(Messages.AbstractProject_NoBuilds());
return isInQueue() ? NO_CHANGES : BUILD_NOW;
}
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.
}
try {
if (scm.requiresWorkspaceForPolling()) {
// lock the workspace of the last build
FilePath ws = lb.getWorkspace();
if (ws == null || !ws.exists()) {
// workspace offline. build now, or nothing will ever be built
Label label = getAssignedLabel();
if (label != null && label.isSelfLabel()) {
// if the build is fixed on a node, then attempting a build will do us
// no good. We should just wait for the slave to come back.
listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
return NO_CHANGES;
}
listener.getLogger().println(ws == null
? Messages.AbstractProject_WorkspaceOffline()
: Messages.AbstractProject_NoWorkspace());
if (isInQueue()) {
listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
return NO_CHANGES;
} else {
listener.getLogger().println(Messages.AbstractProject_NewBuildForWorkspace());
return BUILD_NOW;
}
} else {
Node node = lb.getBuiltOn();
if (node == null || node.toComputer() == null) {
LOGGER.log(Level.FINE, "Node on which this job previously was built is not available now, build is started on an available node");
return isInQueue() ? NO_CHANGES : BUILD_NOW;
}
WorkspaceList l = node.toComputer().getWorkspaceList();
// 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
WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
Launcher launcher = ws.createLauncher(listener);
try {
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();
}
}
} else {
// polling without workspace
LOGGER.fine("Polling SCM changes of " + getName());
if (pollingBaseline == null) // see NOTE-NO-BASELINE above
{
calcPollingBaseline(lb, null, listener);
}
PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
pollingBaseline = r.remote;
return r;
}
} catch (AbortException e) {
listener.getLogger().println(e.getMessage());
listener.fatalError(Messages.AbstractProject_Aborted());
LOGGER.log(Level.FINE, "Polling " + this + " aborted", e);
return NO_CHANGES;
} catch (IOException e) {
e.printStackTrace(listener.fatalError(e.getMessage()));
return NO_CHANGES;
} catch (InterruptedException e) {
e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
return NO_CHANGES;
}
}
/**
* Returns true if this user has made a commit to this project.
*
* @since 1.191
*/
public boolean hasParticipant(User user) {
for (R build = getLastBuild(); build != null; build = build.getPreviousBuild()) {
if (build.hasParticipant(user)) {
return true;
}
}
return false;
}
@Exported
public SCM getScm() {
return CascadingUtil.getScmProjectProperty(this, SCM_PROPERTY_NAME).getValue();
}
@SuppressWarnings("unchecked")
public void setScm(SCM scm) throws IOException {
CascadingUtil.getScmProjectProperty(this, SCM_PROPERTY_NAME).setValue(scm);
save();
}
/**
* Adds a new {@link Trigger} to this {@link Project} if not active yet.
*/
@SuppressWarnings("unchecked")
public void addTrigger(Trigger> trigger) throws IOException {
CascadingUtil.getTriggerProjectProperty(this, trigger.getDescriptor().getJsonSafeClassName()).setValue(trigger);
save();
updateTransientActions();
}
@SuppressWarnings("unchecked")
public void removeTrigger(TriggerDescriptor trigger) throws IOException {
CascadingUtil.getTriggerProjectProperty(this, trigger.getJsonSafeClassName()).setValue(null);
save();
updateTransientActions();
}
protected final synchronized > void addToList(T item, List collection) throws IOException {
for (int i = 0; i < collection.size(); i++) {
if (collection.get(i).getDescriptor() == item.getDescriptor()) {
// replace
collection.set(i, item);
save();
return;
}
}
// add
collection.add(item);
save();
updateTransientActions();
}
protected final synchronized > void removeFromList(Descriptor item, List collection) throws IOException {
for (int i = 0; i < collection.size(); i++) {
if (collection.get(i).getDescriptor() == item) {
// found it
collection.remove(i);
save();
updateTransientActions();
return;
}
}
}
public synchronized Map getTriggers() {
return (Map) Descriptor.toMap(getTriggerDescribableList());
}
/**
* @return list of {@link Trigger} elements.
*/
public List> getTriggersList() {
return getTriggerDescribableList().toList();
}
/**
* @return describable list of trigger elements.
*/
public DescribableList, TriggerDescriptor> getTriggerDescribableList() {
return DescribableListUtil.convertToDescribableList(Trigger.for_(this), this, TriggerProjectProperty.class);
}
/**
* Gets the specific trigger, or null if the propert is not configured for
* this job.
*/
public T getTrigger(Class clazz) {
for (Trigger p : getTriggersList()) {
if (clazz.isInstance(p)) {
return clazz.cast(p);
}
}
return null;
}
public void setTriggers(List> triggerList) {
for (Trigger trigger : triggerList) {
CascadingUtil.getTriggerProjectProperty(this, trigger.getDescriptor().getJsonSafeClassName()).setValue(trigger);
}
}
//
//
// fingerprint related
//
//
/**
* True if the builds of this project produces {@link Fingerprint} records.
*/
public abstract boolean isFingerprintConfigured();
/**
* Gets the other {@link AbstractProject}s that should be built when a build
* of this project is completed.
*/
@Exported
public final List getDownstreamProjects() {
return Hudson.getInstance().getDependencyGraph().getDownstream(this);
}
@Exported
public final List getUpstreamProjects() {
return Hudson.getInstance().getDependencyGraph().getUpstream(this);
}
/**
* Returns only those upstream projects that defines {@link BuildTrigger} to
* this project. This is a subset of {@link #getUpstreamProjects()}
*
* @return A List of upstream projects that has a {@link BuildTrigger} to
* this project.
*/
public final List getBuildTriggerUpstreamProjects() {
ArrayList result = new ArrayList();
for (AbstractProject, ?> ap : getUpstreamProjects()) {
BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
if (buildTrigger != null) {
if (buildTrigger.getChildProjects().contains(this)) {
result.add(ap);
}
}
}
return result;
}
/**
* Gets all the upstream projects including transitive upstream projects.
*
* @since 1.138
*/
public final Set getTransitiveUpstreamProjects() {
return Hudson.getInstance().getDependencyGraph().getTransitiveUpstream(this);
}
/**
* Gets all the downstream projects including transitive downstream
* projects.
*
* @since 1.138
*/
public final Set getTransitiveDownstreamProjects() {
return Hudson.getInstance().getDependencyGraph().getTransitiveDownstream(this);
}
/**
* Gets the dependency relationship map between this project (as the source)
* and that project (as the sink.)
*
* @return can be empty but not null. build number of this project to the
* build numbers of that project.
*/
public SortedMap getRelationship(AbstractProject that) {
TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR);
checkAndRecord(that, r, this.getBuilds());
// checkAndRecord(that, r, that.getBuilds());
return r;
}
/**
* Helper method for getDownstreamRelationship.
*
* For each given build, find the build number range of the given project
* and put that into the map.
*/
private void checkAndRecord(AbstractProject that, TreeMap r, Collection builds) {
for (R build : builds) {
RangeSet rs = build.getDownstreamRelationship(that);
if (rs == null || rs.isEmpty()) {
continue;
}
int n = build.getNumber();
RangeSet value = r.get(n);
if (value == null) {
r.put(n, rs);
} else {
value.add(rs);
}
}
}
/**
* Builds the dependency graph.
*
* @see DependencyGraph
*/
protected abstract void buildDependencyGraph(DependencyGraph graph);
@Override
protected SearchIndexBuilder makeSearchIndex() {
SearchIndexBuilder sib = super.makeSearchIndex();
if (isBuildable() && hasPermission(Hudson.ADMINISTER)) {
sib.add("build", "build");
}
return sib;
}
@Override
protected HistoryWidget createHistoryWidget() {
return new BuildHistoryWidget(this, getBuilds(), HISTORY_ADAPTER);
}
public boolean isParameterized() {
return getProperty(ParametersDefinitionProperty.class) != null;
}
//
//
// actions
//
//
/**
* Schedules a new build command.
*/
public void doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
// if a build is parameterized, let that take over
ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
if (pp != null) {
pp._doBuild(req, rsp);
return;
}
if (!isBuildable()) {
throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR, new IOException(getFullName() + " is not buildable"));
}
Hudson.getInstance().getQueue().schedule(this, getDelay(req), getBuildCause(req));
rsp.forwardToPreviousPage(req);
}
/**
* Computes the build cause, using RemoteCause or UserCause as appropriate.
*/
/*
* package
*/ CauseAction getBuildCause(StaplerRequest req) {
Cause cause;
if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
// Optional additional cause text when starting via token
String causeText = req.getParameter("cause");
cause = new RemoteCause(req.getRemoteAddr(), causeText);
} else {
cause = new UserCause();
}
return new CauseAction(cause);
}
/**
* Computes the delay by taking the default value and the override in the
* request parameter into the account.
*/
public int getDelay(StaplerRequest req) throws ServletException {
String delay = req.getParameter("delay");
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);
}
}
/**
* Supports build trigger with parameters via an HTTP GET or POST. Currently
* only String parameters are supported.
*/
public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
if (pp != null) {
pp.buildWithParameters(req, rsp);
} else {
throw new IllegalStateException("This build is not parameterized!");
}
}
/**
* Schedules a new SCM polling command.
*/
public void doPolling(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
schedulePolling();
rsp.forwardToPreviousPage(req);
}
/**
* Cancels a scheduled build.
*/
public void doCancelQueue(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
checkPermission(BUILD);
Hudson.getInstance().getQueue().cancel(this);
rsp.forwardToPreviousPage(req);
}
@Override
protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
super.submit(req, rsp);
makeDisabled(null != req.getParameter("disable"));
setJDK(req.getParameter("jdk"));
setQuietPeriod(null != req.getParameter(HAS_QUIET_PERIOD_PROPERTY_NAME)
? req.getParameter("quiet_period") : null);
setScmCheckoutRetryCount(null != req.getParameter(HAS_SCM_CHECKOUT_RETRY_COUNT_PROPERTY_NAME)
? req.getParameter("scmCheckoutRetryCount") : null);
setBlockBuildWhenDownstreamBuilding(null != req.getParameter("blockBuildWhenDownstreamBuilding"));
setBlockBuildWhenUpstreamBuilding(null != req.getParameter("blockBuildWhenUpstreamBuilding"));
if (req.getParameter(APPOINTED_NODE_PROPERTY_NAME) != null) {
// New logic for handling whether this choice came from the dropdown or textfield.
if (BASIC_KEY.equals(req.getParameter(AFFINITY_CHO0SER_KEY))) {
setAppointedNode(
new AppointedNode(Util.fixEmptyAndTrim(req.getParameter(SLAVE_KEY)), false));
} else {
setAppointedNode(
new AppointedNode(Util.fixEmptyAndTrim(req.getParameter(ASSIGNED_LABEL_KEY)), true));
}
} else {
setAppointedNode(null);
}
setCleanWorkspaceRequired(null != req.getParameter("cleanWorkspaceRequired"));
setConcurrentBuild(req.getSubmittedForm().has("concurrentBuild"));
authToken = BuildAuthorizationToken.create(req);
setScm(SCMS.parseSCM(req, this));
buildTriggers(req, req.getSubmittedForm(), Trigger.for_(this));
}
/**
* @deprecated As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)}
* instead.
*/
protected final > List buildDescribable(StaplerRequest req, List extends Descriptor> descriptors, String prefix) throws FormException, ServletException {
return buildDescribable(req, descriptors);
}
protected final > List buildDescribable(StaplerRequest req, List extends Descriptor> descriptors)
throws FormException, ServletException {
JSONObject data = req.getSubmittedForm();
List r = new Vector();
for (Descriptor d : descriptors) {
String safeName = d.getJsonSafeClassName();
if (req.getParameter(safeName) != null) {
T instance = d.newInstance(req, data.getJSONObject(safeName));
r.add(instance);
}
}
return r;
}
protected void buildTriggers(StaplerRequest req, JSONObject json, List descriptors)
throws FormException {
for (TriggerDescriptor d : descriptors) {
String propertyName = d.getJsonSafeClassName();
CascadingUtil.setChildrenTrigger(this, d, propertyName, req, json);
}
}
/**
* Serves the workspace files.
*/
public DirectoryBrowserSupport doWs(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
checkPermission(AbstractProject.WORKSPACE);
FilePath ws = getSomeWorkspace();
if ((ws == null) || (!ws.exists())) {
// if there's no workspace, report a nice error message
rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
req.getView(this, "noWorkspace.jelly").forward(req, rsp);
return null;
} else {
return new DirectoryBrowserSupport(this, ws, getDisplayName() + " workspace", "folder.png", true);
}
}
/**
* Wipes out the workspace.
*/
public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
checkPermission(Functions.isWipeOutPermissionEnabled() ? WIPEOUT : BUILD);
try {
if (cleanWorkspace()) {
return new HttpRedirect(".");
} else {
return new ForwardToView(this, "wipeOutWorkspaceBlocked.jelly");
}
} catch (final IOException e) {
ForwardToView resp = new ForwardToView(this, "error.jelly") {
@Override
public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
req.setAttribute("message", Messages._AbstractProject_UnableWipeOut());
req.setAttribute("pre", false);
req.setAttribute("exception", e);
super.generateResponse(req, rsp, node);
}
};
return resp;
}
}
public boolean cleanWorkspace() throws IOException, InterruptedException {
checkPermission(BUILD);
R b = getSomeBuildWithWorkspace();
FilePath ws = b != null ? b.getWorkspace() : null;
if (ws != null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
ws.deleteRecursive();
return true;
} else {
// If we get here, that means the SCM blocked the workspace deletion.
return false;
}
}
@CLIMethod(name = "disable-job")
public HttpResponse doDisable() throws IOException, ServletException {
requirePOST();
checkPermission(CONFIGURE);
makeDisabled(true);
return new HttpRedirect(".");
}
@CLIMethod(name = "enable-job")
public HttpResponse doEnable() throws IOException, ServletException {
requirePOST();
checkPermission(CONFIGURE);
makeDisabled(false);
return new HttpRedirect(".");
}
/**
* RSS feed for changes in this project.
*/
public void doRssChangelog(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
class FeedItem {
ChangeLogSet.Entry e;
int idx;
public FeedItem(Entry e, int idx) {
this.e = e;
this.idx = idx;
}
AbstractBuild, ?> getBuild() {
return e.getParent().build;
}
}
List entries = new ArrayList();
for (R r = getLastBuild(); r != null; r = r.getPreviousBuild()) {
int idx = 0;
for (ChangeLogSet.Entry e : r.getChangeSet()) {
entries.add(new FeedItem(e, idx++));
}
}
RSS.forwardToRss(
getDisplayName() + ' ' + getScm().getDescriptor().getDisplayName() + " changes",
getUrl() + "changes",
entries, new FeedAdapter() {
public String getEntryTitle(FeedItem item) {
return "#" + item.getBuild().number + ' ' + item.e.getMsg() + " (" + item.e.getAuthor() + ")";
}
public String getEntryUrl(FeedItem item) {
return item.getBuild().getUrl() + "changes#detail" + item.idx;
}
public String getEntryID(FeedItem item) {
return getEntryUrl(item);
}
public String getEntryDescription(FeedItem item) {
StringBuilder buf = new StringBuilder();
for (String path : item.e.getAffectedPaths()) {
buf.append(path).append('\n');
}
return buf.toString();
}
public Calendar getEntryTimestamp(FeedItem item) {
return item.getBuild().getTimestamp();
}
public String getEntryAuthor(FeedItem entry) {
return Mailer.descriptor().getAdminAddress();
}
},
req, rsp);
}
/**
* {@link AbstractProject} subtypes should implement this base class as a
* descriptor.
*
* @since 1.294
*/
public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
/**
* {@link AbstractProject} subtypes can override this method to veto
* some {@link Descriptor}s from showing up on their configuration
* screen. This is often useful when you are building a workflow/company
* specific project type, where you want to limit the number of choices
* given to the users.
*
* 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}
* to show up for the given {@link Project}.
*
*
The default implementation returns true for everything.
*
* @see BuildStepDescriptor#isApplicable(Class)
* @see BuildWrapperDescriptor#isApplicable(AbstractProject)
* @see TriggerDescriptor#isApplicable(Item)
*/
@Override
public boolean isApplicable(Descriptor descriptor) {
return true;
}
public FormValidation doCheckAssignedLabelString(@QueryParameter String value) {
if (Util.fixEmpty(value) == null) {
return FormValidation.ok(); // nothing typed yet
}
try {
Label.parseExpression(value);
} catch (ANTLRException e) {
return FormValidation.error(e,
Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
}
// TODO: if there's an atom in the expression that is empty, report it
if (Hudson.getInstance().getLabel(value).isEmpty()) {
return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
}
return FormValidation.ok();
}
public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParameter String value) {
AutoCompletionCandidates c = new AutoCompletionCandidates();
Set