From 06c31af142bb882ecde09aaab9c70c3a63949b2d Mon Sep 17 00:00:00 2001 From: mindless Date: Wed, 13 Jan 2010 07:13:35 +0000 Subject: [PATCH] [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 --- core/src/main/java/hudson/model/Build.java | 2 +- .../java/hudson/model/DependencyGraph.java | 140 ++++++++++++++---- .../main/java/hudson/tasks/BuildTrigger.java | 69 +++++---- 3 files changed, 157 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/hudson/model/Build.java b/core/src/main/java/hudson/model/Build.java index 83a5aa544f..62139b5a13 100644 --- a/core/src/main/java/hudson/model/Build.java +++ b/core/src/main/java/hudson/model/Build.java @@ -157,7 +157,7 @@ public abstract class Build

,B extends Build> 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 steps) throws IOException, InterruptedException { diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java index fa9cd6dfc1..ef7e4e8954 100644 --- a/core/src/main/java/hudson/model/DependencyGraph.java +++ b/core/src/main/java/hudson/model/DependencyGraph.java @@ -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 { +public final class DependencyGraph implements Comparator { - private Map> forward = new HashMap>(); - private Map> backward = new HashMap>(); + private Map> forward = new HashMap>(); + private Map> backward = new HashMap>(); private boolean built; @@ -110,7 +111,7 @@ public final class DependencyGraph implements Comparator { * can be empty but never null. */ public List getDownstream(AbstractProject p) { - return get(forward,p); + return get(forward,p,false); } /** @@ -120,32 +121,70 @@ public final class DependencyGraph implements Comparator { * can be empty but never null. */ public List getUpstream(AbstractProject p) { + return get(backward,p,true); + } + + private List get(Map> map, AbstractProject src, boolean up) { + List v = map.get(src); + if(v==null) return Collections.emptyList(); + List result = new ArrayList(v.size()); + for (Dependency d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject()); + return result; + } + + /** + * @since 1.341 + */ + public List getDownstreamDependencies(AbstractProject p) { + return get(forward,p); + } + + /** + * @since 1.341 + */ + public List getUpstreamDependencies(AbstractProject p) { return get(backward,p); } - private List get(Map> map, AbstractProject src) { - List v = map.get(src); + private List get(Map> map, AbstractProject src) { + List 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 downstream) { for (AbstractProject p : downstream) addDependency(upstream,p); } + /** + * @deprecated since 1.341 + */ + @Deprecated public void addDependency(Collection upstream, AbstractProject downstream) { for (AbstractProject p : upstream) addDependency(p,downstream); @@ -191,17 +230,17 @@ public final class DependencyGraph implements Comparator { * Gets all the direct and indirect upstream dependencies of the given project. */ public Set 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 getTransitiveDownstream(AbstractProject src) { - return getTransitive(forward,src); + return getTransitive(forward,src,false); } - private Set getTransitive(Map> direction, AbstractProject src) { + private Set getTransitive(Map> direction, AbstractProject src, boolean up) { Set visited = new HashSet(); Stack queue = new Stack(); @@ -210,7 +249,7 @@ public final class DependencyGraph implements Comparator { 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 { return visited; } - private void add(Map> map, AbstractProject src, AbstractProject dst) { - List set = map.get(src); + private void add(Map> map, AbstractProject key, Dependency dep) { + List set = map.get(key); if(set==null) { - set = new ArrayList(); - map.put(src,set); + set = new ArrayList(); + map.put(key,set); } - if(!set.contains(dst)) - set.add(dst); + if(!set.contains(dep)) + set.add(dep); } - private Map> finalize(Map> m) { - for (Entry> e : m.entrySet()) { + private Map> finalize(Map> m) { + for (Entry> 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 { * 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 { private static final int MARGIN = 4; - private static final Comparator NAME_COMPARATOR = new Comparator() { - public int compare(AbstractProject lhs, AbstractProject rhs) { - return lhs.getName().compareTo(rhs.getName()); + private static final Comparator NAME_COMPARATOR = new Comparator() { + 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 { 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 getBuildActions(AbstractBuild build, TaskListener listener) { + return Collections.emptyList(); + } + } } diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java index a36f13b2bf..078ddbe018 100644 --- a/core/src/main/java/hudson/tasks/BuildTrigger.java +++ b/core/src/main/java/hudson/tasks/BuildTrigger.java @@ -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 downstreamProjects = - new ArrayList (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 downstreamProjects = graph.getDownstreamDependencies(build.getProject()); + // Sort topologically + TreeMap downstreamMap = + new TreeMap(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 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); } }; } -- GitLab