package hudson.model; import hudson.FilePath; import hudson.Launcher; import hudson.Launcher.LocalLauncher; import hudson.maven.MavenModule; import hudson.model.Descriptor.FormException; import hudson.model.Fingerprint.RangeSet; import hudson.model.RunMap.Constructor; import hudson.scm.NullSCM; import hudson.scm.SCM; import hudson.scm.SCMS; import hudson.triggers.Trigger; import hudson.triggers.Triggers; import hudson.triggers.TriggerDescriptor; import hudson.util.EditDistance; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import javax.servlet.ServletException; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.Vector; /** * Base implementation of {@link Job}s that build software. * * For now this is primarily the common part of {@link Project} and {@link MavenModule}. * * @author Kohsuke Kawaguchi * @see AbstractBuild */ public abstract class AbstractProject
,R extends AbstractBuild
> extends Job
implements BuildableItem {
private SCM scm = new NullSCM();
/**
* All the builds keyed by their build number.
*/
protected transient /*almost final*/ RunMap
* This somewhat ugly flag combination is so that we can migrate
* existing Hudson installations nicely.
*/
private boolean canRoam;
/**
* True to suspend new builds.
*/
protected boolean disabled;
/**
* 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)
*/
private String jdk;
/**
* @deprecated
*/
private transient boolean enableRemoteTrigger;
private BuildAuthorizationToken authToken = null;
/**
* List of all {@link Trigger}s for this project.
*/
protected List
* This is usually where pom.xml, build.xml
* and so on exists.
*/
public FilePath getModuleRoot() {
return getScm().getModuleRoot(getWorkspace());
}
public int getQuietPeriod() {
return quietPeriod!=null ? quietPeriod : Hudson.getInstance().getQuietPeriod();
}
// ugly name because of EL
public boolean getHasCustomQuietPeriod() {
return quietPeriod!=null;
}
public final boolean isBuildable() {
return !isDisabled();
}
public boolean isDisabled() {
return disabled;
}
@Override
public BallColor getIconColor() {
if(isDisabled())
// use grey to indicate that the build is disabled
return BallColor.GREY;
else
return super.getIconColor();
}
/**
* 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() {
if(isDisabled()) return false;
return Hudson.getInstance().getQueue().add(this);
}
/**
* Returns true if the build is in the queue.
*/
@Override
public boolean isInQueue() {
return Hudson.getInstance().getQueue().contains(this);
}
/**
* Returns true if a build of this project is in progress.
*/
public boolean isBuilding() {
R b = getLastBuild();
return b!=null && b.isBuilding();
}
public JDK getJDK() {
return Hudson.getInstance().getJDK(jdk);
}
/**
* Overwrites the JDK setting.
*/
public synchronized void setJDK(JDK jdk) throws IOException {
this.jdk = jdk.getName();
save();
}
public BuildAuthorizationToken getAuthToken() {
return authToken;
}
public SortedMap
* A project must be blocked if its own previous build is in progress,
* but derived classes can also check other conditions.
*/
protected boolean isBuildBlocked() {
return isBuilding();
}
public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException {
if(scm==null)
return true; // no SCM
try {
FilePath workspace = getWorkspace();
workspace.mkdirs();
return scm.checkout(build, launcher, workspace, listener, changelogFile);
} catch (InterruptedException e) {
e.printStackTrace(listener.fatalError("SCM check out aborted"));
return false;
}
}
/**
* Checks if there's any update in SCM, and returns true if any is found.
*
*
* The caller is responsible for coordinating the mutual exclusion between
* a build and polling, as both touches the workspace.
*/
public boolean pollSCMChanges( TaskListener listener ) {
if(scm==null) {
listener.getLogger().println("No SCM");
return false;
}
if(isDisabled()) {
listener.getLogger().println("Build disabled");
return false;
}
try {
FilePath workspace = getWorkspace();
if(!workspace.exists()) {
// no workspace. build now, or nothing will ever be built
listener.getLogger().println("No workspace is available, so can't check for updates.");
listener.getLogger().println("Scheduling a new build to get a workspace.");
return true;
}
// TODO: do this by using the right slave
return scm.pollChanges(this, new LocalLauncher(listener), workspace, listener );
} catch (IOException e) {
e.printStackTrace(listener.fatalError(e.getMessage()));
return false;
} catch (InterruptedException e) {
e.printStackTrace(listener.fatalError("SCM polling aborted"));
return false;
}
}
public SCM getScm() {
return scm;
}
public void setScm(SCM scm) {
this.scm = scm;
}
/**
* Adds a new {@link Trigger} to this {@link Project} if not active yet.
*/
public void addTrigger(Trigger> trigger) throws IOException {
addToList(trigger,triggers);
}
public void removeTrigger(TriggerDescriptor trigger) throws IOException {
removeFromList(trigger,triggers);
}
protected final synchronized