提交 06c31af1 编写于 作者: M mindless

[HUDSON-5236] Introduce DependencyGraph.Dependency so DependecyDeclarers can control

whether builds are triggered and provide Actions for the triggered build.
(previously only tasks.BuildTrigger had hardcoded support for logic on whether or
 not to trigger.. now it uses this mechanism)


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@25751 71c3de6d-444a-0410-be80-ed276b4c234a
上级 f8e67c48
......@@ -157,7 +157,7 @@ public abstract class Build <P extends Project<P,B>,B extends Build<P,B>>
public void cleanUp(BuildListener listener) throws Exception {
performAllBuildStep(listener, project.getPublishers(),false);
performAllBuildStep(listener, project.getProperties(),false);
BuildTrigger.execute(Build.this,listener, project.getPublishersList().get(BuildTrigger.class));
BuildTrigger.execute(Build.this, listener);
}
private boolean build(BuildListener listener, Collection<Builder> steps) throws IOException, InterruptedException {
......
......@@ -23,6 +23,7 @@
*/
package hudson.model;
import hudson.security.Permission;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.graph_layouter.Layout;
......@@ -36,12 +37,12 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.HashSet;
import java.util.Stack;
import java.util.Map.Entry;
import java.io.IOException;
import java.awt.Dimension;
import java.awt.Font;
......@@ -75,10 +76,10 @@ import java.awt.image.BufferedImage;
* @see Hudson#getDependencyGraph()
* @author Kohsuke Kawaguchi
*/
public final class DependencyGraph implements Comparator <AbstractProject> {
public final class DependencyGraph implements Comparator<AbstractProject> {
private Map<AbstractProject, List<AbstractProject>> forward = new HashMap<AbstractProject, List<AbstractProject>>();
private Map<AbstractProject, List<AbstractProject>> backward = new HashMap<AbstractProject, List<AbstractProject>>();
private Map<AbstractProject, List<Dependency>> forward = new HashMap<AbstractProject, List<Dependency>>();
private Map<AbstractProject, List<Dependency>> backward = new HashMap<AbstractProject, List<Dependency>>();
private boolean built;
......@@ -110,7 +111,7 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
* can be empty but never null.
*/
public List<AbstractProject> getDownstream(AbstractProject p) {
return get(forward,p);
return get(forward,p,false);
}
/**
......@@ -120,32 +121,70 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
* can be empty but never null.
*/
public List<AbstractProject> getUpstream(AbstractProject p) {
return get(backward,p,true);
}
private List<AbstractProject> get(Map<AbstractProject, List<Dependency>> map, AbstractProject src, boolean up) {
List<Dependency> v = map.get(src);
if(v==null) return Collections.emptyList();
List<AbstractProject> result = new ArrayList<AbstractProject>(v.size());
for (Dependency d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject());
return result;
}
/**
* @since 1.341
*/
public List<Dependency> getDownstreamDependencies(AbstractProject p) {
return get(forward,p);
}
/**
* @since 1.341
*/
public List<Dependency> getUpstreamDependencies(AbstractProject p) {
return get(backward,p);
}
private List<AbstractProject> get(Map<AbstractProject, List<AbstractProject>> map, AbstractProject src) {
List<AbstractProject> v = map.get(src);
private List<Dependency> get(Map<AbstractProject, List<Dependency>> map, AbstractProject src) {
List<Dependency> v = map.get(src);
if(v!=null) return v;
else return Collections.emptyList();
}
/**
* Called during the dependency graph build phase to add a dependency edge.
* @deprecated since 1.341; use {@link #addDependency(Dependency)}
*/
@Deprecated
public void addDependency(AbstractProject upstream, AbstractProject downstream) {
addDependency(new Dependency(upstream,downstream));
}
/**
* Called during the dependency graph build phase to add a dependency edge.
*/
public void addDependency(Dependency dep) {
if(built)
throw new IllegalStateException();
if(upstream==downstream)
if(dep.getUpstreamProject()==dep.getDownstreamProject())
return;
add(forward,upstream,downstream);
add(backward,downstream,upstream);
add(forward,dep.getUpstreamProject(),dep);
add(backward,dep.getDownstreamProject(),dep);
}
/**
* @deprecated since 1.341
*/
@Deprecated
public void addDependency(AbstractProject upstream, Collection<? extends AbstractProject> downstream) {
for (AbstractProject p : downstream)
addDependency(upstream,p);
}
/**
* @deprecated since 1.341
*/
@Deprecated
public void addDependency(Collection<? extends AbstractProject> upstream, AbstractProject downstream) {
for (AbstractProject p : upstream)
addDependency(p,downstream);
......@@ -191,17 +230,17 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
* Gets all the direct and indirect upstream dependencies of the given project.
*/
public Set<AbstractProject> getTransitiveUpstream(AbstractProject src) {
return getTransitive(backward,src);
return getTransitive(backward,src,true);
}
/**
* Gets all the direct and indirect downstream dependencies of the given project.
*/
public Set<AbstractProject> getTransitiveDownstream(AbstractProject src) {
return getTransitive(forward,src);
return getTransitive(forward,src,false);
}
private Set<AbstractProject> getTransitive(Map<AbstractProject, List<AbstractProject>> direction, AbstractProject src) {
private Set<AbstractProject> getTransitive(Map<AbstractProject, List<Dependency>> direction, AbstractProject src, boolean up) {
Set<AbstractProject> visited = new HashSet<AbstractProject>();
Stack<AbstractProject> queue = new Stack<AbstractProject>();
......@@ -210,7 +249,7 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
while(!queue.isEmpty()) {
AbstractProject p = queue.pop();
for (AbstractProject child : get(direction,p)) {
for (AbstractProject child : get(direction,p,up)) {
if(visited.add(child))
queue.add(child);
}
......@@ -219,18 +258,18 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
return visited;
}
private void add(Map<AbstractProject, List<AbstractProject>> map, AbstractProject src, AbstractProject dst) {
List<AbstractProject> set = map.get(src);
private void add(Map<AbstractProject, List<Dependency>> map, AbstractProject key, Dependency dep) {
List<Dependency> set = map.get(key);
if(set==null) {
set = new ArrayList<AbstractProject>();
map.put(src,set);
set = new ArrayList<Dependency>();
map.put(key,set);
}
if(!set.contains(dst))
set.add(dst);
if(!set.contains(dep))
set.add(dep);
}
private Map<AbstractProject, List<AbstractProject>> finalize(Map<AbstractProject, List<AbstractProject>> m) {
for (Entry<AbstractProject, List<AbstractProject>> e : m.entrySet()) {
private Map<AbstractProject, List<Dependency>> finalize(Map<AbstractProject, List<Dependency>> m) {
for (Entry<AbstractProject, List<Dependency>> e : m.entrySet()) {
Collections.sort( e.getValue(), NAME_COMPARATOR );
e.setValue( Collections.unmodifiableList(e.getValue()) );
}
......@@ -241,6 +280,8 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
* Experimental visualization of project dependencies.
*/
public void doGraph( StaplerRequest req, StaplerResponse rsp ) throws IOException {
// Require admin permission for now (avoid exposing project names with restricted permissions)
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
try {
// creates a dummy graphics just so that we can measure font metrics
......@@ -328,9 +369,10 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
private static final int MARGIN = 4;
private static final Comparator<AbstractProject> NAME_COMPARATOR = new Comparator<AbstractProject>() {
public int compare(AbstractProject lhs, AbstractProject rhs) {
return lhs.getName().compareTo(rhs.getName());
private static final Comparator<Dependency> NAME_COMPARATOR = new Comparator<Dependency>() {
public int compare(Dependency lhs, Dependency rhs) {
int cmp = lhs.getUpstreamProject().getName().compareTo(rhs.getUpstreamProject().getName());
return cmp != 0 ? cmp : lhs.getDownstreamProject().getName().compareTo(rhs.getDownstreamProject().getName());
}
};
......@@ -348,4 +390,48 @@ public final class DependencyGraph implements Comparator <AbstractProject> {
if (o2sdownstreams.contains(o1)) return -1; else return 0;
}
}
/**
* Represents an edge in the dependency graph.
* @since 1.341
*/
public static class Dependency {
private AbstractProject upstream, downstream;
public Dependency(AbstractProject upstream, AbstractProject downstream) {
this.upstream = upstream;
this.downstream = downstream;
}
public AbstractProject getUpstreamProject() {
return upstream;
}
public AbstractProject getDownstreamProject() {
return downstream;
}
/**
* Should the downstream job be triggered?
* Default implementation always returns true (for backward compatibility).
* Subclasses may override to check build result, etc.
* @param build Build of upstream project that just completed
* @param listener For any error/log output
* @return True to trigger a build of the downstream project
*/
public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener) {
return true;
}
/**
* Actions to include in triggered build. Default implementation returns empty list,
* but subclasses may override to add Actions.
* @param build Build of upstream project that just completed
* @param listener For ny error/log output
* @return List of Actions to include in triggered build; may return null or empty list.
*/
public List<Action> getBuildActions(AbstractBuild build, TaskListener listener) {
return Collections.emptyList();
}
}
}
......@@ -32,9 +32,11 @@ import hudson.matrix.MatrixAggregator;
import hudson.matrix.MatrixBuild;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.DependecyDeclarer;
import hudson.model.DependencyGraph;
import hudson.model.DependencyGraph.Dependency;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.Items;
......@@ -43,6 +45,7 @@ import hudson.model.Project;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.Cause.UpstreamCause;
import hudson.model.TaskListener;
import hudson.model.listeners.ItemListener;
import hudson.util.FormValidation;
import net.sf.json.JSONObject;
......@@ -53,11 +56,11 @@ import org.kohsuke.stapler.QueryParameter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -138,10 +141,19 @@ public class BuildTrigger extends Recorder implements DependecyDeclarer, MatrixA
return children.size()==projects.size() && children.containsAll(projects);
}
@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
return true;
}
/**
* @deprecated since 1.341; use {@link #execute(AbstractBuild,BuildListener)}
*/
@Deprecated
public static boolean execute(AbstractBuild build, BuildListener listener, BuildTrigger trigger) {
return execute(build, listener);
}
/**
* Convenience method to trigger downstream builds.
*
......@@ -149,33 +161,32 @@ public class BuildTrigger extends Recorder implements DependecyDeclarer, MatrixA
* The current build. Its downstreams will be triggered.
* @param listener
* Receives the progress report.
* @param trigger
* Optional {@link BuildTrigger} configured for the current build.
* If it is non-null, its configuration value will affect the triggering behavior.
* But even when this is null (meaning no user-defined downstream project is set up),
* there might be other dependencies defined by somebody else, so build would
* still have to call this method.
*/
public static boolean execute(AbstractBuild build, BuildListener listener, BuildTrigger trigger) {
if(trigger==null || !build.getResult().isWorseThan(trigger.getThreshold())) {
PrintStream logger = listener.getLogger();
//Trigger all downstream Project of the project, not just those defined by this buildtrigger
List <AbstractProject> downstreamProjects =
new ArrayList<AbstractProject> (build.getProject().getDownstreamProjects());
// Sort topologically
Collections.sort(downstreamProjects,
Collections.reverseOrder (Hudson.getInstance().getDependencyGraph()));
for (AbstractProject p : downstreamProjects) {
if(p.isDisabled()) {
logger.println(Messages.BuildTrigger_Disabled(p.getName()));
continue;
}
public static boolean execute(AbstractBuild build, BuildListener listener) {
PrintStream logger = listener.getLogger();
// Check all downstream Project of the project, not just those defined by BuildTrigger
DependencyGraph graph = Hudson.getInstance().getDependencyGraph();
List<Dependency> downstreamProjects = graph.getDownstreamDependencies(build.getProject());
// Sort topologically
TreeMap<AbstractProject,Dependency> downstreamMap =
new TreeMap<AbstractProject,Dependency>(Collections.reverseOrder(graph));
for (Dependency dep : downstreamProjects)
downstreamMap.put(dep.getDownstreamProject(), dep);
for (Dependency dep : downstreamMap.values()) {
AbstractProject p = dep.getDownstreamProject();
if (p.isDisabled()) {
logger.println(Messages.BuildTrigger_Disabled(p.getName()));
continue;
}
if (dep.shouldTriggerBuild(build, listener)) {
List<Action> buildActions = dep.getBuildActions(build, listener);
if (buildActions == null) buildActions = Collections.emptyList();
// this is not completely accurate, as a new build might be triggered
// between these calls
String name = p.getName()+" #"+p.getNextBuildNumber();
if(p.scheduleBuild(new UpstreamCause((Run)build))) {
if(p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run)build),
buildActions.toArray(new Action[buildActions.size()]))) {
logger.println(Messages.BuildTrigger_Triggering(name));
} else {
logger.println(Messages.BuildTrigger_InQueue(name));
......@@ -187,7 +198,13 @@ public class BuildTrigger extends Recorder implements DependecyDeclarer, MatrixA
}
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
graph.addDependency(owner,getChildProjects());
for (AbstractProject p : getChildProjects())
graph.addDependency(new Dependency(owner, p) {
@Override
public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener) {
return build.getResult().isBetterOrEqualTo(threshold);
}
});
}
@Override
......@@ -199,7 +216,7 @@ public class BuildTrigger extends Recorder implements DependecyDeclarer, MatrixA
return new MatrixAggregator(build, launcher, listener) {
@Override
public boolean endBuild() throws InterruptedException, IOException {
return execute(build,listener,BuildTrigger.this);
return execute(build,listener);
}
};
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册