提交 7fe3a4a3 编写于 作者: A Andrew Bayer

Initial work on DependencyGraph

上级 8ff1dd63
......@@ -25,6 +25,7 @@
package hudson;
import hudson.model.AbstractProject;
import hudson.model.Job;
import jenkins.model.Jenkins;
import hudson.security.ACL;
......@@ -76,20 +77,23 @@ public class DependencyRunner implements Runnable {
}
}
private void populate(Collection<? extends AbstractProject> projectList) {
for (AbstractProject<?,?> p : projectList) {
if (polledProjects.contains(p)) {
// Project will be readded at the queue, so that we always use
// the longest path
LOGGER.fine("removing project " + p.getName() + " for re-add");
polledProjects.remove(p);
}
private void populate(Collection<? extends Job> projectList) {
for (Job<?,?> j : projectList) {
if (j instanceof AbstractProject) {
AbstractProject<?,?> p = (AbstractProject<?,?>)j;
if (polledProjects.contains(p)) {
// Project will be readded at the queue, so that we always use
// the longest path
LOGGER.fine("removing project " + p.getName() + " for re-add");
polledProjects.remove(p);
}
LOGGER.fine("adding project " + p.getName());
polledProjects.add(p);
LOGGER.fine("adding project " + p.getName());
polledProjects.add(p);
// Add all downstream dependencies
populate(p.getDownstreamProjects());
// Add all downstream dependencies
populate(p.getDownstreamProjects());
}
}
}
......
......@@ -102,11 +102,6 @@ import jenkins.model.lazy.LazyBuildMixIn;
*/
public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Run<P,R> implements Queue.Executable, LazyBuildMixIn.LazyLoadingRun<P,R>, RunWithSCMMixIn.RunWithSCM<P,R> {
/**
* Set if we want the blame information to flow from upstream to downstream build.
*/
private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits");
/**
* Name of the agent this project was built on.
* Null or "" if built by the master. (null happens when we read old record that didn't have this information.)
......@@ -249,24 +244,6 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
this.builtOn = builtOn;
}
/**
* Gets the nearest ancestor {@link AbstractBuild} that belongs to
* {@linkplain AbstractProject#getRootProject() the root project of getProject()} that
* dominates/governs/encompasses this build.
*
* <p>
* Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs,
* and still in some of them, builds of child projects are related/tied to that of the parent project.
* In such a case, this method returns the governing build.
*
* @return never null. In the worst case the build dominates itself.
* @since 1.421
* @see AbstractProject#getRootProject()
*/
public AbstractBuild<?,?> getRootBuild() {
return this;
}
/**
* Used to render the side panel "Back to project" link.
*
......@@ -1061,10 +1038,10 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
// if any of the downstream project is configured with 'keep dependency component',
// we need to keep this log
OUTER:
for (AbstractProject<?,?> p : getParent().getDownstreamProjects()) {
for (Job<?,?> p : getParent().getDownstreamProjects()) {
if (!p.isKeepDependencies()) continue;
AbstractBuild<?,?> fb = p.getFirstBuild();
Run<?,?> fb = p.getFirstBuild();
if (fb==null) continue; // no active record
// is there any active build that depends on us?
......@@ -1075,7 +1052,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
if (i<fb.getNumber())
continue OUTER; // all the other records are younger than the first record, so pointless to search.
AbstractBuild<?,?> b = p.getBuildByNumber(i);
Run<?,?> b = p.getBuildByNumber(i);
if (b!=null)
return Messages.AbstractBuild_KeptBecause(b);
}
......@@ -1085,225 +1062,11 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
}
/**
* Gets the dependency relationship from this build (as the source)
* and that project (as the sink.)
*
* @return
* range of build numbers that represent which downstream builds are using this build.
* The range will be empty if no build of that project matches this (or there is no {@link FingerprintAction}), but it'll never be null.
*/
public RangeSet getDownstreamRelationship(AbstractProject that) {
RangeSet rs = new RangeSet();
FingerprintAction f = getAction(FingerprintAction.class);
if (f==null) return rs;
// look for fingerprints that point to this build as the source, and merge them all
for (Fingerprint e : f.getFingerprints().values()) {
if (upstreamCulprits) {
// With upstreamCulprits, we allow downstream relationships
// from intermediate jobs
rs.add(e.getRangeSet(that));
} else {
BuildPtr o = e.getOriginal();
if (o!=null && o.is(this))
rs.add(e.getRangeSet(that));
}
}
return rs;
}
/**
* Works like {@link #getDownstreamRelationship(AbstractProject)} but returns
* the actual build objects, in ascending order.
* @since 1.150
*/
public Iterable<AbstractBuild<?,?>> getDownstreamBuilds(final AbstractProject<?,?> that) {
final Iterable<Integer> nums = getDownstreamRelationship(that).listNumbers();
return new Iterable<AbstractBuild<?, ?>>() {
public Iterator<AbstractBuild<?, ?>> iterator() {
return Iterators.removeNull(
new AdaptedIterator<Integer,AbstractBuild<?,?>>(nums) {
protected AbstractBuild<?, ?> adapt(Integer item) {
return that.getBuildByNumber(item);
}
});
}
};
}
/**
* Gets the dependency relationship from this build (as the sink)
* and that project (as the source.)
*
* @return
* Build number of the upstream build that feed into this build,
* or -1 if no record is available (for example if there is no {@link FingerprintAction}, even if there is an {@link Cause.UpstreamCause}).
*/
public int getUpstreamRelationship(AbstractProject that) {
FingerprintAction f = getAction(FingerprintAction.class);
if (f==null) return -1;
int n = -1;
// look for fingerprints that point to the given project as the source, and merge them all
for (Fingerprint e : f.getFingerprints().values()) {
if (upstreamCulprits) {
// With upstreamCulprits, we allow upstream relationships
// from intermediate jobs
Fingerprint.RangeSet rangeset = e.getRangeSet(that);
if (!rangeset.isEmpty()) {
n = Math.max(n, rangeset.listNumbersReverse().iterator().next());
}
} else {
BuildPtr o = e.getOriginal();
if (o!=null && o.belongsTo(that))
n = Math.max(n,o.getNumber());
}
}
return n;
}
/**
* Works like {@link #getUpstreamRelationship(AbstractProject)} but returns the
* actual build object.
*
* @return
* null if no such upstream build was found, or it was found but the
* build record is already lost.
*/
public AbstractBuild<?,?> getUpstreamRelationshipBuild(AbstractProject<?,?> that) {
int n = getUpstreamRelationship(that);
if (n==-1) return null;
return that.getBuildByNumber(n);
}
/**
* Gets the downstream builds of this build, which are the builds of the
* downstream projects that use artifacts of this build.
*
* @return
* For each project with fingerprinting enabled, returns the range
* of builds (which can be empty if no build uses the artifact from this build or downstream is not {@link AbstractProject#isFingerprintConfigured}.)
*/
public Map<AbstractProject,RangeSet> getDownstreamBuilds() {
Map<AbstractProject,RangeSet> r = new HashMap<AbstractProject,RangeSet>();
for (AbstractProject p : getParent().getDownstreamProjects()) {
if (p.isFingerprintConfigured())
r.put(p,getDownstreamRelationship(p));
}
return r;
}
/**
* Gets the upstream builds of this build, which are the builds of the
* upstream projects whose artifacts feed into this build.
* @return empty if there is no {@link FingerprintAction} (even if there is an {@link Cause.UpstreamCause})
* @see #getTransitiveUpstreamBuilds()
*/
public Map<AbstractProject,Integer> getUpstreamBuilds() {
return _getUpstreamBuilds(getParent().getUpstreamProjects());
}
/**
* Works like {@link #getUpstreamBuilds()} but also includes all the transitive
* dependencies as well.
* Retained for compatibility.
*/
public Map<AbstractProject,Integer> getTransitiveUpstreamBuilds() {
return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects());
}
private Map<AbstractProject, Integer> _getUpstreamBuilds(Collection<AbstractProject> projects) {
Map<AbstractProject,Integer> r = new HashMap<AbstractProject,Integer>();
for (AbstractProject p : projects) {
int n = getUpstreamRelationship(p);
if (n>=0)
r.put(p,n);
}
return r;
}
/**
* Gets the changes in the dependency between the given build and this build.
* @return empty if there is no {@link FingerprintAction}
*/
public Map<AbstractProject,DependencyChange> getDependencyChanges(AbstractBuild from) {
if (from==null) return Collections.emptyMap(); // make it easy to call this from views
FingerprintAction n = this.getAction(FingerprintAction.class);
FingerprintAction o = from.getAction(FingerprintAction.class);
if (n==null || o==null) return Collections.emptyMap();
Map<AbstractProject,Integer> ndep = n.getDependencies(true);
Map<AbstractProject,Integer> odep = o.getDependencies(true);
Map<AbstractProject,DependencyChange> r = new HashMap<AbstractProject,DependencyChange>();
for (Map.Entry<AbstractProject,Integer> entry : odep.entrySet()) {
AbstractProject p = entry.getKey();
Integer oldNumber = entry.getValue();
Integer newNumber = ndep.get(p);
if (newNumber!=null && oldNumber.compareTo(newNumber)<0) {
r.put(p,new DependencyChange(p,oldNumber,newNumber));
}
}
return r;
}
/**
* Represents a change in the dependency.
*/
public static final class DependencyChange {
/**
* The dependency project.
*/
public final AbstractProject project;
/**
* Version of the dependency project used in the previous build.
*/
public final int fromId;
/**
* {@link Build} object for {@link #fromId}. Can be null if the log is gone.
*/
public final AbstractBuild from;
/**
* Version of the dependency project used in this build.
*/
public final int toId;
public final AbstractBuild to;
public DependencyChange(AbstractProject<?,?> project, int fromId, int toId) {
this.project = project;
this.fromId = fromId;
this.toId = toId;
this.from = project.getBuildByNumber(fromId);
this.to = project.getBuildByNumber(toId);
}
/**
* Gets the {@link AbstractBuild} objects (fromId,toId].
* <p>
* This method returns all such available builds in the ascending order
* of IDs, but due to log rotations, some builds may be already unavailable.
*/
public List<AbstractBuild> getBuilds() {
List<AbstractBuild> r = new ArrayList<AbstractBuild>();
AbstractBuild<?,?> b = project.getNearestBuild(fromId);
if (b!=null && b.getNumber()==fromId)
b = b.getNextBuild(); // fromId exclusive
while (b!=null && b.getNumber()<=toId) {
r.add(b);
b = b.getNextBuild();
}
return r;
public static final class DependencyChange extends Run.DependencyChange {
public DependencyChange(Job<?, ?> project, int fromId, int toId) {
super(project, fromId, toId);
}
}
......
......@@ -471,28 +471,6 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return AlternativeUiTextProvider.get(BUILD_NOW_TEXT, this, getParameterizedJobMixIn().getBuildNowText());
}
/**
* Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}.
*
* <p>
* Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs
* that acts as a single unit. This method can be used to find the top most dominating job that
* covers such a tree.
*
* @return never null.
* @see AbstractBuild#getRootBuild()
*/
public AbstractProject<?,?> getRootProject() {
if (this instanceof TopLevelItem) {
return this;
} else {
ItemGroup p = this.getParent();
if (p instanceof AbstractProject)
return ((AbstractProject) p).getRootProject();
return this;
}
}
/**
* Gets the directory where the module is checked out.
*
......@@ -1111,9 +1089,9 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
* Because the downstream build is in progress, and we are configured to wait for that.
*/
public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage {
public final AbstractProject<?,?> up;
public final Job<?,?> up;
public BecauseOfDownstreamBuildInProgress(AbstractProject<?,?> up) {
public BecauseOfDownstreamBuildInProgress(Job<?,?> up) {
this.up = up;
}
......@@ -1127,9 +1105,9 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
* Because the upstream build is in progress, and we are configured to wait for that.
*/
public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage {
public final AbstractProject<?,?> up;
public final Job<?,?> up;
public BecauseOfUpstreamBuildInProgress(AbstractProject<?,?> up) {
public BecauseOfUpstreamBuildInProgress(Job<?,?> up) {
this.up = up;
}
......@@ -1154,52 +1132,18 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
}
if (blockBuildWhenDownstreamBuilding()) {
AbstractProject<?,?> bup = getBuildingDownstream();
Job<?,?> bup = getBuildingDownstream();
if (bup!=null)
return new BecauseOfDownstreamBuildInProgress(bup);
}
if (blockBuildWhenUpstreamBuilding()) {
AbstractProject<?,?> bup = getBuildingUpstream();
Job<?,?> bup = getBuildingUpstream();
if (bup!=null)
return new BecauseOfUpstreamBuildInProgress(bup);
}
return null;
}
/**
* Returns the project if any of the downstream project is either
* building, waiting, pending or buildable.
* <p>
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public AbstractProject getBuildingDownstream() {
Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
for (AbstractProject tup : getTransitiveDownstreamProjects()) {
if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
return tup;
}
return null;
}
/**
* Returns the project if any of the upstream project is either
* building or is in the queue.
* <p>
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public AbstractProject getBuildingUpstream() {
Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
for (AbstractProject tup : getTransitiveUpstreamProjects()) {
if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
return tup;
}
return null;
}
public List<SubTask> getSubTasks() {
List<SubTask> r = new ArrayList<SubTask>();
r.add(this);
......@@ -1635,97 +1579,31 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
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<AbstractProject> getDownstreamProjects() {
return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
}
@Exported
public final List<AbstractProject> getUpstreamProjects() {
return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
}
/**
* Returns only those upstream projects that defines {@link BuildTrigger} to this project.
* This is a subset of {@link #getUpstreamProjects()}
* This is a subset of {@link #getUpstreamProjects()}, only including {@link AbstractProject}s.
* <p>No longer used in the UI.
* @return A List of upstream projects that has a {@link BuildTrigger} to this project.
*/
public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
for (AbstractProject<?,?> ap : getUpstreamProjects()) {
BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
if (buildTrigger != null)
if (buildTrigger.getChildProjects(ap).contains(this))
result.add(ap);
for (Job<?,?> j : getUpstreamProjects()) {
if (j instanceof AbstractProject) {
AbstractProject<?,?> ap = (AbstractProject) j;
BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
if (buildTrigger != null)
if (buildTrigger.getChildProjects(ap).contains(this))
result.add(ap);
}
}
return result;
}
/**
* Gets all the upstream projects including transitive upstream projects.
*
* @since 1.138
*/
public final Set<AbstractProject> getTransitiveUpstreamProjects() {
return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
}
/**
* Gets all the downstream projects including transitive downstream projects.
*
* @since 1.138
*/
public final Set<AbstractProject> getTransitiveDownstreamProjects() {
return Jenkins.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<Integer, RangeSet> getRelationship(AbstractProject that) {
TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(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<Integer, RangeSet> r, Collection<R> 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.
* Since 1.558, not abstract and by default includes dependencies contributed by {@link #triggers()}.
*/
@Override
protected void buildDependencyGraph(DependencyGraph graph) {
triggers().buildDependencyGraph(this, graph);
}
......@@ -2160,12 +2038,6 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return Items.findNearest(AbstractProject.class, name, context);
}
private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
};
private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
/**
......
......@@ -68,17 +68,17 @@ import java.util.Stack;
* @see Jenkins#getDependencyGraph()
* @author Kohsuke Kawaguchi
*/
public class DependencyGraph implements Comparator<AbstractProject> {
public class DependencyGraph implements Comparator<Job> {
private Map<AbstractProject, List<DependencyGroup>> forward = new HashMap<AbstractProject, List<DependencyGroup>>();
private Map<AbstractProject, List<DependencyGroup>> backward = new HashMap<AbstractProject, List<DependencyGroup>>();
private Map<Job, List<DependencyGroup>> forward = new HashMap<Job, List<DependencyGroup>>();
private Map<Job, List<DependencyGroup>> backward = new HashMap<Job, List<DependencyGroup>>();
private transient Map<Class<?>, Object> computationalData;
private boolean built;
private Comparator<AbstractProject<?,?>> topologicalOrder;
private List<AbstractProject<?,?>> topologicallySorted;
private Comparator<Job<?,?>> topologicalOrder;
private List<Job<?,?>> topologicallySorted;
/**
* Builds the dependency graph.
......@@ -91,7 +91,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM);
try {
this.computationalData = new HashMap<Class<?>, Object>();
for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) )
for( Job p : Jenkins.getInstance().allItems(Job.class) )
p.buildDependencyGraph(this);
forward = finalize(forward);
......@@ -110,36 +110,36 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* See http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
*/
private void topologicalDagSort() {
DirectedGraph<AbstractProject> g = new DirectedGraph<AbstractProject>() {
DirectedGraph<Job> g = new DirectedGraph<Job>() {
@Override
protected Collection<AbstractProject> nodes() {
final Set<AbstractProject> nodes = new HashSet<AbstractProject>();
protected Collection<Job> nodes() {
final Set<Job> nodes = new HashSet<Job>();
nodes.addAll(forward.keySet());
nodes.addAll(backward.keySet());
return nodes;
}
@Override
protected Collection<AbstractProject> forward(AbstractProject node) {
protected Collection<Job> forward(Job node) {
return getDownstream(node);
}
};
List<SCC<AbstractProject>> sccs = g.getStronglyConnectedComponents();
List<SCC<Job>> sccs = g.getStronglyConnectedComponents();
final Map<AbstractProject,Integer> topoOrder = new HashMap<AbstractProject,Integer>();
topologicallySorted = new ArrayList<AbstractProject<?,?>>();
final Map<Job,Integer> topoOrder = new HashMap<Job,Integer>();
topologicallySorted = new ArrayList<Job<?,?>>();
int idx=0;
for (SCC<AbstractProject> scc : sccs) {
for (AbstractProject n : scc) {
for (SCC<Job> scc : sccs) {
for (Job n : scc) {
topoOrder.put(n,idx++);
topologicallySorted.add(n);
}
}
topologicalOrder = new Comparator<AbstractProject<?, ?>>() {
topologicalOrder = new Comparator<Job<?, ?>>() {
@Override
public int compare(AbstractProject<?,?> o1, AbstractProject<?,?> o2) {
public int compare(Job<?,?> o1, Job<?,?> o2) {
return topoOrder.get(o1)-topoOrder.get(o2);
}
};
......@@ -179,7 +179,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @return
* can be empty but never null.
*/
public List<AbstractProject> getDownstream(AbstractProject p) {
public List<Job> getDownstream(Job p) {
return get(forward,p,false);
}
......@@ -189,33 +189,34 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @return
* can be empty but never null.
*/
public List<AbstractProject> getUpstream(AbstractProject p) {
public List<Job> getUpstream(Job p) {
return get(backward,p,true);
}
private List<AbstractProject> get(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject src, boolean up) {
private List<Job> get(Map<Job, List<DependencyGroup>> map, Job src, boolean up) {
List<DependencyGroup> v = map.get(src);
if(v==null) return Collections.emptyList();
List<AbstractProject> result = new ArrayList<AbstractProject>(v.size());
List<Job> result = new ArrayList<Job>(v.size());
for (DependencyGroup d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject());
return result;
}
/**
* @since 1.341
*/
public List<Dependency> getDownstreamDependencies(AbstractProject p) {
public List<Dependency> getDownstreamDependencies(Job p) {
return get(forward,p);
}
/**
* @since 1.341
*/
public List<Dependency> getUpstreamDependencies(AbstractProject p) {
public List<Dependency> getUpstreamDependencies(Job p) {
return get(backward,p);
}
private List<Dependency> get(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject src) {
private List<Dependency> get(Map<Job, List<DependencyGroup>> map, Job src) {
List<DependencyGroup> v = map.get(src);
if(v==null) {
return ImmutableList.of();
......@@ -233,7 +234,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @deprecated since 1.341; use {@link #addDependency(Dependency)}
*/
@Deprecated
public void addDependency(AbstractProject upstream, AbstractProject downstream) {
public void addDependency(Job upstream, Job downstream) {
addDependency(new Dependency(upstream,downstream));
}
......@@ -251,8 +252,8 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @deprecated since 1.341
*/
@Deprecated
public void addDependency(AbstractProject upstream, Collection<? extends AbstractProject> downstream) {
for (AbstractProject p : downstream)
public void addDependency(Job upstream, Collection<? extends Job> downstream) {
for (Job p : downstream)
addDependency(upstream,p);
}
......@@ -260,15 +261,15 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @deprecated since 1.341
*/
@Deprecated
public void addDependency(Collection<? extends AbstractProject> upstream, AbstractProject downstream) {
for (AbstractProject p : upstream)
public void addDependency(Collection<? extends Job> upstream, Job downstream) {
for (Job p : upstream)
addDependency(p,downstream);
}
/**
* Lists up {@link DependencyDeclarer} from the collection and let them builds dependencies.
*/
public void addDependencyDeclarers(AbstractProject upstream, Collection<?> possibleDependecyDeclarers) {
public void addDependencyDeclarers(Job upstream, Collection<?> possibleDependecyDeclarers) {
for (Object o : possibleDependecyDeclarers) {
if (o instanceof DependencyDeclarer) {
DependencyDeclarer dd = (DependencyDeclarer) o;
......@@ -283,15 +284,15 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* A non-direct dependency is a path of dependency "edge"s from the source to the destination,
* where the length is greater than 1.
*/
public boolean hasIndirectDependencies(AbstractProject src, AbstractProject dst) {
Set<AbstractProject> visited = new HashSet<AbstractProject>();
Stack<AbstractProject> queue = new Stack<AbstractProject>();
public boolean hasIndirectDependencies(Job src, Job dst) {
Set<Job> visited = new HashSet<Job>();
Stack<Job> queue = new Stack<Job>();
queue.addAll(getDownstream(src));
queue.remove(dst);
while(!queue.isEmpty()) {
AbstractProject p = queue.pop();
Job p = queue.pop();
if(p==dst)
return true;
if(visited.add(p))
......@@ -304,27 +305,27 @@ public class DependencyGraph implements Comparator<AbstractProject> {
/**
* Gets all the direct and indirect upstream dependencies of the given project.
*/
public Set<AbstractProject> getTransitiveUpstream(AbstractProject src) {
public Set<Job> getTransitiveUpstream(Job src) {
return getTransitive(backward,src,true);
}
/**
* Gets all the direct and indirect downstream dependencies of the given project.
*/
public Set<AbstractProject> getTransitiveDownstream(AbstractProject src) {
public Set<Job> getTransitiveDownstream(Job src) {
return getTransitive(forward,src,false);
}
private Set<AbstractProject> getTransitive(Map<AbstractProject, List<DependencyGroup>> direction, AbstractProject src, boolean up) {
Set<AbstractProject> visited = new HashSet<AbstractProject>();
Stack<AbstractProject> queue = new Stack<AbstractProject>();
private Set<Job> getTransitive(Map<Job, List<DependencyGroup>> direction, Job src, boolean up) {
Set<Job> visited = new HashSet<Job>();
Stack<Job> queue = new Stack<Job>();
queue.add(src);
while(!queue.isEmpty()) {
AbstractProject p = queue.pop();
Job p = queue.pop();
for (AbstractProject child : get(direction,p,up)) {
for (Job child : get(direction,p,up)) {
if(visited.add(child))
queue.add(child);
}
......@@ -333,7 +334,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
return visited;
}
private void add(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject key, Dependency dep) {
private void add(Map<Job, List<DependencyGroup>> map, Job key, Dependency dep) {
List<DependencyGroup> set = map.get(key);
if(set==null) {
set = new ArrayList<DependencyGroup>();
......@@ -351,8 +352,8 @@ public class DependencyGraph implements Comparator<AbstractProject> {
set.add(new DependencyGroup(dep));
}
private Map<AbstractProject, List<DependencyGroup>> finalize(Map<AbstractProject, List<DependencyGroup>> m) {
for (Entry<AbstractProject, List<DependencyGroup>> e : m.entrySet()) {
private Map<Job, List<DependencyGroup>> finalize(Map<Job, List<DependencyGroup>> m) {
for (Entry<Job, List<DependencyGroup>> e : m.entrySet()) {
Collections.sort( e.getValue(), NAME_COMPARATOR );
e.setValue( Collections.unmodifiableList(e.getValue()) );
}
......@@ -371,7 +372,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
/**
* Compare two Projects based on the topological order defined by this Dependency Graph
*/
public int compare(AbstractProject o1, AbstractProject o2) {
public int compare(Job o1, Job o2) {
return topologicalOrder.compare(o1,o2);
}
......@@ -383,7 +384,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
*
* @since 1.521
*/
public List<AbstractProject<?,?>> getTopologicallySorted() {
public List<Job<?,?>> getTopologicallySorted() {
return topologicallySorted;
}
......@@ -392,18 +393,18 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @since 1.341
*/
public static class Dependency {
private AbstractProject upstream, downstream;
private Job upstream, downstream;
public Dependency(AbstractProject upstream, AbstractProject downstream) {
public Dependency(Job upstream, Job downstream) {
this.upstream = upstream;
this.downstream = downstream;
}
public AbstractProject getUpstreamProject() {
public Job getUpstreamProject() {
return upstream;
}
public AbstractProject getDownstreamProject() {
public Job getDownstreamProject() {
return downstream;
}
......@@ -420,7 +421,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
* @param actions Add Actions for the triggered build to this list; never null
* @return True to trigger a build of the downstream project
*/
public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener,
public boolean shouldTriggerBuild(Run build, TaskListener listener,
List<Action> actions) {
return true;
}
......@@ -462,7 +463,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
DependencyGroup(Dependency first) {
this.upstream = first.getUpstreamProject();
this.downstream= first.getDownstreamProject();
this.downstream = first.getDownstreamProject();
group.add(first);
}
......@@ -474,13 +475,13 @@ public class DependencyGraph implements Comparator<AbstractProject> {
return group;
}
private AbstractProject upstream, downstream;
private Job upstream, downstream;
public AbstractProject getUpstreamProject() {
public Job getUpstreamProject() {
return upstream;
}
public AbstractProject getDownstreamProject() {
public Job getDownstreamProject() {
return downstream;
}
}
......
......@@ -72,11 +72,14 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
......@@ -179,6 +182,13 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
// this should have been DescribableList but now it's too late
protected CopyOnWriteList<JobProperty<? super JobT>> properties = new CopyOnWriteList<JobProperty<? super JobT>>();
private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
};
@Restricted(NoExternalUse.class)
public transient RunIdMigrator runIdMigrator;
......@@ -332,6 +342,63 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
return keepDependencies;
}
/**
* Returns the project if any of the downstream project is either
* building, waiting, pending or buildable.
* <p>
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public Job getBuildingDownstream() {
Set<Queue.Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
for (Job tup : getTransitiveDownstreamProjects()) {
if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
return tup;
}
return null;
}
/**
* Returns the project if any of the upstream project is either
* building or is in the queue.
* <p>
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public Job getBuildingUpstream() {
Set<Queue.Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
for (Job tup : getTransitiveUpstreamProjects()) {
if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
return tup;
}
return null;
}
/**
* Gets the nearest ancestor {@link TopLevelItem} that's also an {@link Job}.
*
* <p>
* Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs
* that acts as a single unit. This method can be used to find the top most dominating job that
* covers such a tree.
*
* @return never null.
* @see Run#getRootBuild()
*/
public Job<?,?> getRootProject() {
if (this instanceof TopLevelItem) {
return this;
} else {
ItemGroup p = this.getParent();
if (p instanceof Job)
return ((Job) p).getRootProject();
return this;
}
}
/**
* Allocates a new buildCommand number.
*/
......@@ -499,9 +566,86 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
return Collections.<Job> singleton(this);
}
/**
* Builds the dependency graph. No-op by default.
* @param graph
*/
protected void buildDependencyGraph(DependencyGraph graph) {
}
/**
* Gets the other {@link Job}s that should be built
* when a build of this project is completed.
*/
@Exported
public final List<Job> getDownstreamProjects() {
return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
}
@Exported
public final List<Job> getUpstreamProjects() {
return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
}
/**
* Gets all the upstream projects including transitive upstream projects.
*
* @since 1.138
*/
public final Set<Job> getTransitiveUpstreamProjects() {
return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
}
/**
* Gets all the downstream projects including transitive downstream projects.
*
* @since 1.138
*/
public final Set<Job> getTransitiveDownstreamProjects() {
return Jenkins.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<Integer, RangeSet> getRelationship(AbstractProject that) {
TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(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<Integer, RangeSet> r, Collection<RunT> builds) {
for (RunT 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);
}
}
/**
* Adds {@link JobProperty}.
*
*
* @since 1.188
*/
public void addProperty(JobProperty<? super JobT> jobProp) throws IOException {
......
......@@ -44,6 +44,10 @@ import hudson.console.PlainTextConsoleOutputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.StandardOpenOption;
import hudson.tasks.Fingerprinter;
import hudson.util.AdaptedIterator;
import hudson.util.Iterators;
import jenkins.util.SystemProperties;
import hudson.Util;
import hudson.XmlFile;
......@@ -80,12 +84,14 @@ import java.text.SimpleDateFormat;
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.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
......@@ -143,6 +149,11 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,RunT>>
extends Actionable implements ExtensionPoint, Comparable<RunT>, AccessControlled, PersistenceRoot, DescriptorByNameOwner, OnMaster {
/**
* Set if we want the blame information to flow from upstream to downstream build.
*/
private static final boolean upstreamCulprits = SystemProperties.getBoolean("hudson.upstreamCulprits");
/**
* The original {@link Queue.Item#getId()} has not yet been mapped onto the {@link Run} instance.
* @since 1.601
......@@ -961,6 +972,246 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
return nextBuild;
}
/**
* Gets the dependency relationship from this build (as the source)
* and that project (as the sink.)
*
* @return
* range of build numbers that represent which downstream builds are using this build.
* The range will be empty if no build of that project matches this (or there is no {@link Fingerprinter.FingerprintAction}), but it'll never be null.
*/
public Fingerprint.RangeSet getDownstreamRelationship(Job that) {
Fingerprint.RangeSet rs = new Fingerprint.RangeSet();
Fingerprinter.FingerprintAction f = getAction(Fingerprinter.FingerprintAction.class);
if (f==null) return rs;
// look for fingerprints that point to this build as the source, and merge them all
for (Fingerprint e : f.getFingerprints().values()) {
if (upstreamCulprits) {
// With upstreamCulprits, we allow downstream relationships
// from intermediate jobs
rs.add(e.getRangeSet(that));
} else {
Fingerprint.BuildPtr o = e.getOriginal();
if (o!=null && o.is(this))
rs.add(e.getRangeSet(that));
}
}
return rs;
}
/**
* Works like {@link #getDownstreamRelationship(Job)} but returns
* the actual build objects, in ascending order.
* @since 1.150
*/
public Iterable<Run<?,?>> getDownstreamBuilds(final Job<?,?> that) {
final Iterable<Integer> nums = getDownstreamRelationship(that).listNumbers();
return new Iterable<Run<?, ?>>() {
public Iterator<Run<?, ?>> iterator() {
return Iterators.removeNull(
new AdaptedIterator<Integer,Run<?,?>>(nums) {
protected Run<?, ?> adapt(Integer item) {
return that.getBuildByNumber(item);
}
});
}
};
}
/**
* Gets the dependency relationship from this build (as the sink)
* and that project (as the source.)
*
* @return
* Build number of the upstream build that feed into this build,
* or -1 if no record is available (for example if there is no {@link Fingerprinter.FingerprintAction}, even if there is an {@link Cause.UpstreamCause}).
*/
public int getUpstreamRelationship(Job that) {
Fingerprinter.FingerprintAction f = getAction(Fingerprinter.FingerprintAction.class);
if (f==null) return -1;
int n = -1;
// look for fingerprints that point to the given project as the source, and merge them all
for (Fingerprint e : f.getFingerprints().values()) {
if (upstreamCulprits) {
// With upstreamCulprits, we allow upstream relationships
// from intermediate jobs
Fingerprint.RangeSet rangeset = e.getRangeSet(that);
if (!rangeset.isEmpty()) {
n = Math.max(n, rangeset.listNumbersReverse().iterator().next());
}
} else {
Fingerprint.BuildPtr o = e.getOriginal();
if (o!=null && o.belongsTo(that))
n = Math.max(n,o.getNumber());
}
}
return n;
}
/**
* Works like {@link #getUpstreamRelationship(Job)} but returns the
* actual build object.
*
* @return
* null if no such upstream build was found, or it was found but the
* build record is already lost.
*/
public Run<?,?> getUpstreamRelationshipBuild(Job<?,?> that) {
int n = getUpstreamRelationship(that);
if (n==-1) return null;
return that.getBuildByNumber(n);
}
/**
* Gets the downstream builds of this build, which are the builds of the
* downstream projects that use artifacts of this build.
*
* @return
* For each project with fingerprinting enabled, returns the range
* of builds (which can be empty if no build uses the artifact from this build.
*/
public Map<Job,Fingerprint.RangeSet> getDownstreamBuilds() {
Map<Job,Fingerprint.RangeSet> r = new HashMap<Job,Fingerprint.RangeSet>();
for (Job p : getParent().getDownstreamProjects()) {
r.put(p,getDownstreamRelationship(p));
}
return r;
}
/**
* Gets the upstream builds of this build, which are the builds of the
* upstream projects whose artifacts feed into this build.
* @return empty if there is no {@link Fingerprinter.FingerprintAction} (even if there is an {@link Cause.UpstreamCause})
* @see #getTransitiveUpstreamBuilds()
*/
public Map<Job,Integer> getUpstreamBuilds() {
return _getUpstreamBuilds(getParent().getUpstreamProjects());
}
/**
* Works like {@link #getUpstreamBuilds()} but also includes all the transitive
* dependencies as well.
*/
public Map<Job,Integer> getTransitiveUpstreamBuilds() {
return _getUpstreamBuilds(getParent().getTransitiveUpstreamProjects());
}
private Map<Job, Integer> _getUpstreamBuilds(Collection<Job> projects) {
Map<Job,Integer> r = new HashMap<Job,Integer>();
for (Job p : projects) {
int n = getUpstreamRelationship(p);
if (n>=0)
r.put(p,n);
}
return r;
}
/**
* Gets the changes in the dependency between the given build and this build.
* @return empty if there is no {@link Fingerprinter.FingerprintAction}
*/
public Map<Job,Run.DependencyChange> getDependencyChanges(Run from) {
if (from==null) return Collections.emptyMap(); // make it easy to call this from views
Fingerprinter.FingerprintAction n = this.getAction(Fingerprinter.FingerprintAction.class);
Fingerprinter.FingerprintAction o = from.getAction(Fingerprinter.FingerprintAction.class);
if (n==null || o==null) return Collections.emptyMap();
Map<Job,Integer> ndep = n.getDependencies(true);
Map<Job,Integer> odep = o.getDependencies(true);
Map<Job,Run.DependencyChange> r = new HashMap<Job,Run.DependencyChange>();
for (Map.Entry<Job,Integer> entry : odep.entrySet()) {
Job p = entry.getKey();
Integer oldNumber = entry.getValue();
Integer newNumber = ndep.get(p);
if (newNumber!=null && oldNumber.compareTo(newNumber)<0) {
r.put(p,new Run.DependencyChange(p,oldNumber,newNumber));
}
}
return r;
}
/**
* Gets the nearest ancestor {@link Run} that belongs to
* {@linkplain Job#getRootProject() the root project of getParent()} that
* dominates/governs/encompasses this build.
*
* <p>
* Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs,
* and still in some of them, builds of child projects are related/tied to that of the parent project.
* In such a case, this method returns the governing build.
*
* @return never null. In the worst case the build dominates itself.
* @since 1.421
* @see Job#getRootProject()
*/
public Run<?,?> getRootBuild() {
return this;
}
/**
* Represents a change in the dependency.
*/
public static class DependencyChange {
/**
* The dependency project.
*/
public final Job project;
/**
* Version of the dependency project used in the previous build.
*/
public final int fromId;
/**
* {@link Run} object for {@link #fromId}. Can be null if the log is gone.
*/
public final Run from;
/**
* Version of the dependency project used in this build.
*/
public final int toId;
public final Run to;
public DependencyChange(Job<?,?> project, int fromId, int toId) {
this.project = project;
this.fromId = fromId;
this.toId = toId;
this.from = project.getBuildByNumber(fromId);
this.to = project.getBuildByNumber(toId);
}
/**
* Gets the {@link Run} objects (fromId,toId].
* <p>
* This method returns all such available builds in the ascending order
* of IDs, but due to log rotations, some builds may be already unavailable.
*/
public List<Run> getBuilds() {
List<Run> r = new ArrayList<Run>();
Run<?,?> b = project.getNearestBuild(fromId);
if (b!=null && b.getNumber()==fromId)
b = b.getNextBuild(); // fromId exclusive
while (b!=null && b.getNumber()<=toId) {
r.add(b);
b = b.getNextBuild();
}
return r;
}
}
/**
* Returns the URL of this {@link Run}, relative to the context root of Hudson.
*
......
......@@ -245,21 +245,24 @@ public class BuildTrigger extends Recorder implements DependencyDeclarer {
SecurityContext orig = ACL.impersonate(auth);
try {
if (dep.shouldTriggerBuild(build, listener, buildActions)) {
AbstractProject p = dep.getDownstreamProject();
// Allow shouldTriggerBuild to return false first, in case it is skipping because of a lack of Item.READ/DISCOVER permission:
if (p.isDisabled()) {
logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p)));
continue;
}
boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run)build), buildActions.toArray(new Action[buildActions.size()]));
if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) {
String name = ModelHyperlinkNote.encodeTo(p);
if (scheduled) {
logger.println(Messages.BuildTrigger_Triggering(name));
} else {
logger.println(Messages.BuildTrigger_InQueue(name));
Job j = dep.getDownstreamProject();
if (j instanceof AbstractProject) {
AbstractProject p = (AbstractProject) j;
// Allow shouldTriggerBuild to return false first, in case it is skipping because of a lack of Item.READ/DISCOVER permission:
if (p.isDisabled()) {
logger.println(Messages.BuildTrigger_Disabled(ModelHyperlinkNote.encodeTo(p)));
continue;
}
} // otherwise upstream users should not know that it happened
boolean scheduled = p.scheduleBuild(p.getQuietPeriod(), new UpstreamCause((Run) build), buildActions.toArray(new Action[buildActions.size()]));
if (Jenkins.getInstance().getItemByFullName(p.getFullName()) == p) {
String name = ModelHyperlinkNote.encodeTo(p);
if (scheduled) {
logger.println(Messages.BuildTrigger_Triggering(name));
} else {
logger.println(Messages.BuildTrigger_InQueue(name));
}
} // otherwise upstream users should not know that it happened
}
}
} finally {
SecurityContextHolder.setContext(orig);
......@@ -269,24 +272,28 @@ public class BuildTrigger extends Recorder implements DependencyDeclarer {
return true;
}
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
for (AbstractProject p : getChildProjects(owner))
graph.addDependency(new Dependency(owner, p) {
@Override
public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener,
List<Action> actions) {
AbstractProject downstream = getDownstreamProject();
if (Jenkins.getInstance().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders
LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()});
return false; // do not even issue a warning to build log
}
if (!downstream.hasPermission(Item.BUILD)) {
listener.getLogger().println(Messages.BuildTrigger_you_have_no_permission_to_build_(ModelHyperlinkNote.encodeTo(downstream)));
return false;
public void buildDependencyGraph(Job j, DependencyGraph graph) {
if (j instanceof AbstractProject) {
AbstractProject owner = (AbstractProject) j;
for (AbstractProject p : getChildProjects(owner))
graph.addDependency(new Dependency(owner, p) {
@Override
public boolean shouldTriggerBuild(Run build, TaskListener listener,
List<Action> actions) {
Job downstream = getDownstreamProject();
if (Jenkins.getInstance().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders
LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[]{Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()});
return false; // do not even issue a warning to build log
}
if (!downstream.hasPermission(Item.BUILD)) {
listener.getLogger().println(Messages.BuildTrigger_you_have_no_permission_to_build_(ModelHyperlinkNote.encodeTo(downstream)));
return false;
}
return build.getResult().isBetterOrEqualTo(threshold);
}
return build.getResult().isBetterOrEqualTo(threshold);
}
});
});
}
}
@Override
......
......@@ -149,7 +149,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
return BuildStepMonitor.NONE;
}
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
public void buildDependencyGraph(Job owner, DependencyGraph graph) {
if (enableFingerprintsInDependencyGraph) {
RunList builds = owner.getBuilds();
Set<String> seenUpstreamProjects = new HashSet<String>();
......@@ -157,12 +157,12 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
for ( ListIterator iter = builds.listIterator(); iter.hasNext(); ) {
Run build = (Run) iter.next();
for (FingerprintAction action : build.getActions(FingerprintAction.class)) {
for (AbstractProject key : action.getDependencies().keySet()) {
for (Job key : action.getDependencies().keySet()) {
if (key == owner) {
continue; // Avoid self references
}
AbstractProject p = key;
Job p = key;
// TODO is this harmful to call unconditionally, so it would apply also to MavenModule for example?
if (key.getClass().getName().equals("hudson.matrix.MatrixConfiguration")) {
p = key.getRootProject();
......@@ -175,7 +175,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
seenUpstreamProjects.add(p.getName());
graph.addDependency(new Dependency(p, owner) {
@Override
public boolean shouldTriggerBuild(AbstractBuild build,
public boolean shouldTriggerBuild(Run build,
TaskListener listener,
List<Action> actions) {
// Fingerprints should not trigger builds.
......@@ -389,7 +389,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
/**
* Gets the dependency to other existing builds in a map.
*/
public Map<AbstractProject,Integer> getDependencies() {
public Map<Job,Integer> getDependencies() {
return getDependencies(false);
}
......@@ -400,8 +400,8 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
* the result, even if it doesn't exist
* @since 1.430
*/
public Map<AbstractProject,Integer> getDependencies(boolean includeMissing) {
Map<AbstractProject,Integer> r = new HashMap<AbstractProject,Integer>();
public Map<Job,Integer> getDependencies(boolean includeMissing) {
Map<Job,Integer> r = new HashMap<Job,Integer>();
for (Fingerprint fp : getFingerprints().values()) {
BuildPtr bp = fp.getOriginal();
......@@ -411,11 +411,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
try {
Job job = bp.getJob();
if (job==null) continue; // project no longer exists
if (!(job instanceof AbstractProject)) {
// Ignoring this for now. In the future we may want a dependency map function not limited to AbstractProject.
// (Could be used by getDependencyChanges if pulled up from AbstractBuild into Run, for example.)
continue;
}
if (job.getParent()==build.getParent())
continue; // we are the parent of the build owner, that is almost like we are the owner
if(!includeMissing && job.getBuildByNumber(bp.getNumber())==null)
......@@ -424,7 +419,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
Integer existing = r.get(job);
if(existing!=null && existing>bp.getNumber())
continue; // the record in the map is already up to date
r.put((AbstractProject) job, bp.getNumber());
r.put(job, bp.getNumber());
} catch (AccessDeniedException e) {
// Need to log in to access this job, so ignore
continue;
......
......@@ -25,6 +25,7 @@ package jenkins.model;
import hudson.model.AbstractProject;
import hudson.model.DependencyGraph;
import hudson.model.Job;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
......@@ -50,7 +51,7 @@ public interface DependencyDeclarer {
// so that this concept can be extended elsewhere, like maven projects and so on.
/**
* Invoked from {@link AbstractProject#buildDependencyGraph(DependencyGraph)}.
* Invoked from {@link Job#buildDependencyGraph(DependencyGraph)}.
*
* @param owner
* The project that owns the publishers, builders, etc.
......@@ -61,5 +62,5 @@ public interface DependencyDeclarer {
* @param graph
* The dependency graph being built. Never null.
*/
void buildDependencyGraph(AbstractProject owner, DependencyGraph graph);
void buildDependencyGraph(Job owner, DependencyGraph graph);
}
......@@ -95,12 +95,17 @@ public abstract class RunWithSCMMixIn<JobT extends Job<JobT, RunT> & Queue.Task,
if (p instanceof AbstractBuild && upstreamCulprits) {
// If we have dependencies since the last successful build, add their authors to our list
if (p.getPreviousNotFailedBuild() != null) {
Map<AbstractProject, AbstractBuild.DependencyChange> depmap =
((AbstractBuild<?,?>) p).getDependencyChanges((AbstractBuild<?,?>)p.getPreviousSuccessfulBuild());
for (AbstractBuild.DependencyChange dep : depmap.values()) {
for (AbstractBuild<?, ?> b : dep.getBuilds()) {
for (ChangeLogSet.Entry entry : b.getChangeSet()) {
r.add(entry.getAuthor());
Map<Job, Run.DependencyChange> depmap =
p.getDependencyChanges(p.getPreviousSuccessfulBuild());
for (Run.DependencyChange dep : depmap.values()) {
for (Run<?, ?> rawRun : dep.getBuilds()) {
if (rawRun instanceof RunWithSCM) {
RunWithSCM<?, ?> b = (RunWithSCM<?,?>) rawRun;
for (ChangeLogSet<? extends ChangeLogSet.Entry> c : b.getChangeSets()) {
for (ChangeLogSet.Entry e : c) {
r.add(e.getAuthor());
}
}
}
}
}
......
......@@ -139,10 +139,10 @@ public final class ReverseBuildTrigger extends Trigger<Job> implements Dependenc
return result != null && result.isBetterOrEqualTo(threshold);
}
@Override public void buildDependencyGraph(final AbstractProject downstream, DependencyGraph graph) {
for (AbstractProject upstream : Items.fromNameList(downstream.getParent(), upstreamProjects, AbstractProject.class)) {
@Override public void buildDependencyGraph(final Job downstream, DependencyGraph graph) {
for (Job upstream : Items.fromNameList(downstream.getParent(), upstreamProjects, Job.class)) {
graph.addDependency(new DependencyGraph.Dependency(upstream, downstream) {
@Override public boolean shouldTriggerBuild(AbstractBuild upstreamBuild, TaskListener listener, List<Action> actions) {
@Override public boolean shouldTriggerBuild(Run upstreamBuild, TaskListener listener, List<Action> actions) {
return shouldTrigger(upstreamBuild, listener);
}
});
......
......@@ -93,10 +93,10 @@ public class DependencyGraphTest extends HudsonTestCase {
super(buildResult);
this.down = down;
}
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
public void buildDependencyGraph(Job owner, DependencyGraph graph) {
graph.addDependency(new DependencyGraph.Dependency(owner, down) {
@Override
public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener,
public boolean shouldTriggerBuild(Run build, TaskListener listener,
List<Action> actions) {
// Trigger for ODD build number
if (build.getNumber() % 2 == 1) {
......@@ -122,7 +122,7 @@ public class DependencyGraphTest extends HudsonTestCase {
// @LocalData for this test has jobs w/o anonymous Item.READ
AbstractProject up = (AbstractProject) jenkins.getItem("hiddenUpstream");
assertNotNull("hiddenUpstream project not found", up);
List<AbstractProject> down = jenkins.getDependencyGraph().getDownstream(up);
List<Job> down = jenkins.getDependencyGraph().getDownstream(up);
assertEquals("Should have one downstream project", 1, down.size());
} finally {
SecurityContextHolder.clearContext();
......@@ -150,9 +150,9 @@ public class DependencyGraphTest extends HudsonTestCase {
jenkins.rebuildDependencyGraph();
DependencyGraph g = jenkins.getDependencyGraph();
List<AbstractProject<?, ?>> sorted = g.getTopologicallySorted();
List<Job<?, ?>> sorted = g.getTopologicallySorted();
StringBuilder buf = new StringBuilder();
for (AbstractProject<?, ?> p : sorted) {
for (Job<?, ?> p : sorted) {
buf.append(p.getName());
}
String r = buf.toString();
......
......@@ -38,6 +38,7 @@ import hudson.model.FreeStyleProject;
import hudson.model.DependencyGraph;
import hudson.model.DependencyGraph.Dependency;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
......@@ -367,7 +368,7 @@ public class BuildTriggerTest {
}
@Override
public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List<Action> actions) {
public boolean shouldTriggerBuild(Run build, TaskListener listener, List<Action> actions) {
if (block) {
try {
Thread.sleep(5000);
......@@ -386,9 +387,12 @@ public class BuildTriggerTest {
}
@Override @SuppressWarnings("rawtypes")
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
for (AbstractProject ch: getChildProjects(owner)) {
graph.addDependency(new Dep(owner, ch));
public void buildDependencyGraph(Job j, DependencyGraph graph) {
if (j instanceof AbstractProject) {
AbstractProject owner = (AbstractProject) j;
for (AbstractProject ch : getChildProjects(owner)) {
graph.addDependency(new Dep(owner, ch));
}
}
}
}
......
......@@ -102,8 +102,8 @@ public class FingerprinterTest {
j.jenkins.rebuildDependencyGraph();
List<AbstractProject> downstreamProjects = upstream.getDownstreamProjects();
List<AbstractProject> upstreamProjects = downstream.getUpstreamProjects();
List<Job> downstreamProjects = upstream.getDownstreamProjects();
List<Job> upstreamProjects = downstream.getUpstreamProjects();
assertEquals(1, downstreamProjects.size());
assertEquals(1, upstreamProjects.size());
......@@ -142,9 +142,9 @@ public class FingerprinterTest {
j.jenkins.rebuildDependencyGraph();
List<AbstractProject> downstreamProjects = upstream.getDownstreamProjects();
List<AbstractProject> downstreamProjects2 = upstream2.getDownstreamProjects();
List<AbstractProject> upstreamProjects = downstream.getUpstreamProjects();
List<Job> downstreamProjects = upstream.getDownstreamProjects();
List<Job> downstreamProjects2 = upstream2.getDownstreamProjects();
List<Job> upstreamProjects = downstream.getUpstreamProjects();
assertEquals(1, downstreamProjects.size());
assertEquals(1, downstreamProjects2.size());
......@@ -165,9 +165,9 @@ public class FingerprinterTest {
j.jenkins.rebuildDependencyGraph();
List<AbstractProject> downstreamProjects = upstream.getDownstreamProjects();
List<AbstractProject> upstreamProjects = downstream.getUpstreamProjects();
List<AbstractProject> upstreamProjects2 = downstream2.getUpstreamProjects();
List<Job> downstreamProjects = upstream.getDownstreamProjects();
List<Job> upstreamProjects = downstream.getUpstreamProjects();
List<Job> upstreamProjects2 = downstream2.getUpstreamProjects();
assertEquals(2, downstreamProjects.size());
assertEquals(1, upstreamProjects.size());
......@@ -189,8 +189,8 @@ public class FingerprinterTest {
Jenkins.getInstance().rebuildDependencyGraph();
List<AbstractProject> upstreamProjects = downstream.getUpstreamProjects();
List<AbstractProject> downstreamProjects = upstream.getDownstreamProjects();
List<Job> upstreamProjects = downstream.getUpstreamProjects();
List<Job> downstreamProjects = upstream.getDownstreamProjects();
assertEquals(0, upstreamProjects.size());
assertEquals(0, downstreamProjects.size());
......@@ -204,8 +204,8 @@ public class FingerprinterTest {
Jenkins.getInstance().rebuildDependencyGraph();
List<AbstractProject> upstreamProjects = p.getUpstreamProjects();
List<AbstractProject> downstreamProjects = p.getDownstreamProjects();
List<Job> upstreamProjects = p.getUpstreamProjects();
List<Job> downstreamProjects = p.getDownstreamProjects();
assertEquals(0, upstreamProjects.size());
assertEquals(0, downstreamProjects.size());
......@@ -229,9 +229,9 @@ public class FingerprinterTest {
assertEquals("There should only be one FreestyleBuild", 1, builds.size());
FreeStyleBuild build = builds.iterator().next();
assertEquals(Result.SUCCESS, build.getResult());
List<AbstractProject> downstream = j.jenkins.getDependencyGraph().getDownstream(matrixProject);
List<Job> downstream = j.jenkins.getDependencyGraph().getDownstream(matrixProject);
assertTrue(downstream.contains(freestyleProject));
List<AbstractProject> upstream = j.jenkins.getDependencyGraph().getUpstream(freestyleProject);
List<Job> upstream = j.jenkins.getDependencyGraph().getUpstream(freestyleProject);
assertTrue(upstream.contains(matrixProject));
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册