All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.model.Project Maven / Gradle / Ivy

package hudson.model;

import hudson.FilePath;
import hudson.Launcher;
import hudson.util.EditDistance;
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.tasks.BuildStep;
import hudson.tasks.BuildTrigger;
import hudson.tasks.Builder;
import hudson.tasks.Fingerprinter;
import hudson.tasks.Publisher;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrappers;
import hudson.tasks.test.AbstractTestResultAction;
import hudson.triggers.Trigger;
import hudson.triggers.Triggers;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Vector;

/**
 * Buildable software project.
 *
 * @author Kohsuke Kawaguchi
 */
public class Project extends Job {

    /**
     * All the builds keyed by their build number.
     */
    private transient /*almost final*/ RunMap builds = new RunMap();

    private SCM scm = new NullSCM();

    private boolean enableRemoteTrigger = false;

    private String authToken = null;

    /**
     * List of all {@link Trigger}s for this project.
     */
    private List triggers = new Vector();

    /**
     * List of active {@link Builder}s configured for this project.
     */
    private List builders = new Vector();

    /**
     * List of active {@link Publisher}s configured for this project.
     */
    private List publishers = new Vector();

    /**
     * List of active {@link BuildWrapper}s configured for this project.
     */
    private List buildWrappers = new Vector();

    /**
     * {@link Action}s contributed from {@link #triggers}, {@link #builders},
     * and {@link #publishers}.
     *
     * We don't want to persist them separately, and these actions
     * come and go as configuration change, so it's kept separate.
     */
    private transient /*final*/ List transientActions = new Vector();

    /**
     * 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; /** * The quiet period. Null to delegate to the system default. */ private Integer quietPeriod = null; /** * If this project is configured to be only built on a certain node, * this value will be set to that node. Null to indicate the affinity * with the master node. * * see #canRoam */ private String assignedNode; /** * 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. */ private boolean canRoam; /** * True to suspend new builds. */ private boolean disabled; /** * Creates a new project. */ public Project(Hudson parent,String name) { super(parent,name); if(!parent.getSlaves().isEmpty()) { // if a new job is configured with Hudson that already has slave nodes // make it roamable by default canRoam = true; } } /** * If this project is configured to be always built on this node, * return that {@link Node}. Otherwise null. */ public Node getAssignedNode() { if(canRoam) return null; if(assignedNode ==null) return Hudson.getInstance(); return getParent().getSlave(assignedNode); } public JDK getJDK() { return getParent().getJDK(jdk); } public int getQuietPeriod() { return quietPeriod!=null ? quietPeriod : getParent().getQuietPeriod(); } // ugly name because of EL public boolean getHasCustomQuietPeriod() { return quietPeriod!=null; } protected void onLoad(Hudson root, String name) throws IOException { super.onLoad(root, name); if(triggers==null) // it didn't exist in < 1.28 triggers = new Vector(); if(buildWrappers==null) // it didn't exist in < 1.64 buildWrappers = new Vector(); this.builds = new RunMap(); this.builds.load(this,new Constructor() { public Build create(File dir) throws IOException { return new Build(Project.this,dir); } }); for (Trigger t : triggers) t.start(this,false); updateTransientActions(); } public boolean isBuildable() { return !isDisabled(); } public boolean isDisabled() { return disabled; } public SCM getScm() { return scm; } public void setScm(SCM scm) { this.scm = scm; } public boolean isEnableRemoteTrigger() { // no need to enable this option if security disabled return (Hudson.getInstance().isUseSecurity()) && enableRemoteTrigger; } public String getAuthToken() { return authToken; } @Override public BallColor getIconColor() { if(isDisabled()) // use grey to indicate that the build is disabled return BallColor.GREY; else return super.getIconColor(); } public synchronized Map,Trigger> getTriggers() { return Descriptor.toMap(triggers); } public synchronized Map,Builder> getBuilders() { return Descriptor.toMap(builders); } public synchronized Map,Publisher> getPublishers() { return Descriptor.toMap(publishers); } public synchronized Map,BuildWrapper> getBuildWrappers() { return Descriptor.toMap(buildWrappers); } private synchronized > void addToList( T item, List collection ) throws IOException { for( int i=0; i> 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(); return; } } } /** * 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(Descriptor trigger) throws IOException { removeFromList(trigger,triggers); } /** * Adds a new {@link BuildStep} to this {@link Project} and saves the configuration. */ private void addPublisher(Publisher buildStep) throws IOException { addToList(buildStep,publishers); } /** * Removes a publisher from this project, if it's active. */ private void removePublisher(Descriptor descriptor) throws IOException { removeFromList(descriptor, publishers); } public SortedMap _getRuns() { return builds.getView(); } public void removeRun(Build run) { this.builds.remove(run); } /** * Creates a new build of this project for immediate execution. */ public Build newBuild() throws IOException { Build lastBuild = new Build(this); builds.put(lastBuild); return lastBuild; } public boolean checkout(Build build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException { if(scm==null) return true; // no SCM FilePath workspace = getWorkspace(); workspace.mkdirs(); return scm.checkout(build, launcher, workspace, listener, changelogFile); } /** * 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; // no SCM } 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; } try { // TODO: do this by using the right slave return scm.pollChanges(this, new Launcher(listener), workspace, listener ); } catch (IOException e) { e.printStackTrace(listener.fatalError(e.getMessage())); return false; } } /** * 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? Build b = getLastBuild(); if(b==null) return null; else return b.getBuiltOn(); } /** * Gets the directory where the module is checked out. */ public FilePath getWorkspace() { Node node = getLastBuiltOn(); if(node==null) node = getParent(); if(node instanceof Slave) return ((Slave)node).getWorkspaceRoot().child(getName()); else return new FilePath(new File(getRootDir(),"workspace")); } /** * Returns the root directory of the checked-out module. * * @return * When running remotely, this returns a remote fs directory. */ public FilePath getModuleRoot() { return getScm().getModuleRoot(getWorkspace()); } /** * 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(Project that) { TreeMap r = new TreeMap(REVERSE_INTEGER_COMPARATOR); checkAndRecord(that, r, this.getBuilds()); // checkAndRecord(that, r, that.getBuilds()); return r; } public List getDownstreamProjects() { BuildTrigger buildTrigger = (BuildTrigger) getPublishers().get(BuildTrigger.DESCRIPTOR); if(buildTrigger==null) return new ArrayList(); else return buildTrigger.getChildProjects(); } public List getUpstreamProjects() { List r = new ArrayList(); for( Project p : Hudson.getInstance().getProjects() ) { synchronized(p) { for (BuildStep step : p.publishers) { if (step instanceof BuildTrigger) { BuildTrigger trigger = (BuildTrigger) step; if(trigger.getChildProjects().contains(this)) r.add(p); } } } } 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(Project that, TreeMap r, Collection builds) { for (Build 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); } } /** * Schedules a build of this project. */ public void scheduleBuild() { if(!disabled) getParent().getQueue().add(this); } /** * Returns true if the build is in the queue. */ @Override public boolean isInQueue() { return getParent().getQueue().contains(this); } /** * Schedules the SCM polling. If a polling is already in progress * or a build is in progress, polling will take place after that. * Otherwise the polling will be started immediately on a separate thread. * *

* In any case this method returns immediately. */ public void scheduleSCMPolling() { // TODO } /** * Returns true if the fingerprint record is configured in this project. */ public boolean isFingerprintConfigured() { synchronized(publishers) { for (Publisher p : publishers) { if(p instanceof Fingerprinter) return true; } } return false; } // // // actions // // /** * Schedules a new build command. */ public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { if (authorizedToStartBuild(req, rsp)) { scheduleBuild(); rsp.forwardToPreviousPage(req); } } private boolean authorizedToStartBuild(StaplerRequest req, StaplerResponse rsp) throws IOException { if (isEnableRemoteTrigger()) { String providedToken = req.getParameter("token"); if (providedToken != null && providedToken.equals(getAuthToken())) { return true; } } return Hudson.adminCheck(req, rsp); } /** * Cancels a scheduled build. */ public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { if(!Hudson.adminCheck(req,rsp)) return; getParent().getQueue().cancel(this); rsp.forwardToPreviousPage(req); } /** * Accepts submission from the configuration page. */ public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { Set upstream = Collections.emptySet(); synchronized(this) { try { if(!Hudson.adminCheck(req,rsp)) return; req.setCharacterEncoding("UTF-8"); int scmidx = Integer.parseInt(req.getParameter("scm")); scm = SCMS.SCMS.get(scmidx).newInstance(req); disabled = req.getParameter("disable")!=null; jdk = req.getParameter("jdk"); if(req.getParameter("hasCustomQuietPeriod")!=null) { quietPeriod = Integer.parseInt(req.getParameter("quiet_period")); } else { quietPeriod = null; } if(req.getParameter("hasSlaveAffinity")!=null) { canRoam = false; assignedNode = req.getParameter("slave"); if(assignedNode !=null) { if(Hudson.getInstance().getSlave(assignedNode)==null) { assignedNode = null; // no such slave } } } else { canRoam = true; assignedNode = null; } if (req.getParameter("pseudoRemoteTrigger") != null) { authToken = req.getParameter("authToken"); enableRemoteTrigger = true; } else { enableRemoteTrigger = false; } buildDescribable(req, BuildWrappers.WRAPPERS, buildWrappers, "wrapper"); buildDescribable(req, BuildStep.BUILDERS, builders, "builder"); buildDescribable(req, BuildStep.PUBLISHERS, publishers, "publisher"); for (Trigger t : triggers) t.stop(); buildDescribable(req, Triggers.TRIGGERS, triggers, "trigger"); for (Trigger t : triggers) t.start(this,true); updateTransientActions(); super.doConfigSubmit(req,rsp); } catch (FormException e) { sendError(e,req,rsp); } } if(req.getParameter("pseudoUpstreamTrigger")!=null) { upstream = new HashSet(Project.fromNameList(req.getParameter("upstreamProjects"))); } // this needs to be done after we release the lock on this, // or otherwise we could dead-lock for (Project p : Hudson.getInstance().getProjects()) { boolean isUpstream = upstream.contains(p); synchronized(p) { List newChildProjects = p.getDownstreamProjects(); if(isUpstream) { if(!newChildProjects.contains(this)) newChildProjects.add(this); } else { newChildProjects.remove(this); } if(newChildProjects.isEmpty()) { p.removePublisher(BuildTrigger.DESCRIPTOR); } else { p.addPublisher(new BuildTrigger(newChildProjects)); } } } } private void updateTransientActions() { if(transientActions==null) transientActions = new Vector(); // happens when loaded from disk synchronized(transientActions) { transientActions.clear(); for (BuildStep step : builders) { Action a = step.getProjectAction(this); if(a!=null) transientActions.add(a); } for (BuildStep step : publishers) { Action a = step.getProjectAction(this); if(a!=null) transientActions.add(a); } for (Trigger trigger : triggers) { Action a = trigger.getProjectAction(); if(a!=null) transientActions.add(a); } } } public synchronized List getActions() { // add all the transient actions, too List actions = new Vector(super.getActions()); actions.addAll(transientActions); return actions; } public List getProminentActions() { List a = getActions(); List pa = new Vector(); for (Action action : a) { if(action instanceof ProminentProjectAction) pa.add((ProminentProjectAction) action); } return pa; } private > void buildDescribable(StaplerRequest req, List> descriptors, List result, String prefix) throws FormException { result.clear(); for( int i=0; i< descriptors.size(); i++ ) { if(req.getParameter(prefix +i)!=null) { T instance = descriptors.get(i).newInstance(req); result.add(instance); } } } /** * Serves the workspace files. */ public void doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { File dir = getWorkspace().getLocal(); if(!dir.exists()) { // if there's no workspace, report a nice error message rsp.forward(this,"noWorkspace",req); } else { serveFile(req, rsp, dir, "folder.gif", true); } } /** * Display the test result trend. */ public void doTestResultTrend( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { Build b = getLastSuccessfulBuild(); if(b!=null) { AbstractTestResultAction a = b.getTestResultAction(); if(a!=null) { a.doGraph(req,rsp); return; } } // error rsp.setStatus(HttpServletResponse.SC_NOT_FOUND); } /** * Changes the test result report display mode. */ public void doFlipTestResultTrend( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { boolean failureOnly = false; // check the current preference value Cookie[] cookies = req.getCookies(); if(cookies!=null) { for (Cookie cookie : cookies) { if(cookie.getName().equals(FAILURE_ONLY_COOKIE)) failureOnly = Boolean.parseBoolean(cookie.getValue()); } } // flip! failureOnly = !failureOnly; // set the updated value Cookie cookie = new Cookie(FAILURE_ONLY_COOKIE,String.valueOf(failureOnly)); List anc = req.getAncestors(); Ancestor a = (Ancestor) anc.get(anc.size()-1); // last cookie.setPath(a.getUrl()); // just for this chart cookie.setMaxAge(Integer.MAX_VALUE); rsp.addCookie(cookie); // back to the project page rsp.sendRedirect("."); } /** * @deprecated * left for legacy config file compatibility */ @Deprecated private transient String slave; private static final String FAILURE_ONLY_COOKIE = "TestResultAction_failureOnly"; /** * Converts a list of projects into a camma-separated names. */ public static String toNameList(Collection projects) { StringBuilder buf = new StringBuilder(); for (Project project : projects) { if(buf.length()>0) buf.append(", "); buf.append(project.getName()); } return buf.toString(); } /** * Does the opposite of {@link #toNameList(Collection)}. */ public static List fromNameList(String list) { Hudson hudson = Hudson.getInstance(); List r = new ArrayList(); StringTokenizer tokens = new StringTokenizer(list,","); while(tokens.hasMoreTokens()) { String projectName = tokens.nextToken().trim(); Job job = hudson.getJob(projectName); if(!(job instanceof Project)) { continue; // ignore this token } r.add((Project) job); } return r; } /** * Finds a {@link Project} that has the name closest to the given name. */ public static Project findNearest(String name) { List projects = Hudson.getInstance().getProjects(); String[] names = new String[projects.size()]; for( int i=0; i REVERSE_INTEGER_COMPARATOR = new Comparator() { public int compare(Integer o1, Integer o2) { return o2-o1; } }; public JobDescriptor getDescriptor() { return DESCRIPTOR; } public static final JobDescriptor DESCRIPTOR = new JobDescriptor(Project.class) { public String getDisplayName() { return "Building a software project"; } public Project newInstance(String name) { return new Project(Hudson.getInstance(),name); } }; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy