Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial import

  • Loading branch information...
commit 1c255d9e15502db4ad0cdd430d00cfe7047348f5 0 parents
@wolfs authored
0  README
No changes.
57 pom.xml
@@ -0,0 +1,57 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.jvnet.hudson.plugins</groupId>
+ <artifactId>plugin</artifactId>
+ <version>1.386</version><!-- which version of Hudson is this plugin built against? -->
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+<!-- <groupId>hudson.plugin</groupId>-->
+ <artifactId>depgraph-view</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>hpi</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.0</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-provider-gitexe</artifactId>
+ <version>1.3</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ <extensions>
+ <extension>
+ <groupId>org.jvnet.wagon-svn</groupId>
+ <artifactId>wagon-svn</artifactId>
+ <version>1.9</version>
+ </extension>
+ </extensions>
+ </build>
+ <scm>
+ <connection>scm:git:git://github.com/wolfs/depgraph-view.git</connection>
+ <developerConnection>scm:git:git@github.com:wolfs/depgraph-view.git</developerConnection>
+ <url>http://github.com/wolfs/depgraph-view</url>
+ </scm>
+ <!-- get every artifact through maven.glassfish.org, which proxies all the artifacts that we need -->
+ <repositories>
+ <repository>
+ <id>m.g.o-public</id>
+ <url>http://maven.glassfish.org/content/groups/public/</url>
+ </repository>
+ </repositories>
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>m.g.o-public</id>
+ <url>http://maven.glassfish.org/content/groups/public/</url>
+ </pluginRepository>
+ </pluginRepositories>
+</project>
174 src/main/java/hudson/plugins/depgraph_view/AbstractDependencyGraphAction.java
@@ -0,0 +1,174 @@
+package hudson.plugins.depgraph_view;
+
+import hudson.Launcher;
+import hudson.model.Action;
+import hudson.model.DependencyGraph.Dependency;
+import hudson.model.AbstractProject;
+import hudson.model.Hudson;
+import hudson.plugins.depgraph_view.DependencyGraphProperty.DescriptorImpl;
+import hudson.util.LogTaskListener;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+
+import com.google.common.collect.ImmutableMap;
+
+public abstract class AbstractDependencyGraphAction implements Action {
+ private final Logger LOGGER = Logger.getLogger(Logger.class.getName());
+
+ protected static final ImmutableMap<String, SupportedImageType> extension2Type =
+ ImmutableMap.of(
+ "png",SupportedImageType.of("image/png", "png"),
+ "svg",SupportedImageType.of("image/svg", "svg"),
+ "map",SupportedImageType.of("image/cmapx", "cmapx"),
+ "dot",SupportedImageType.of("text/plain", "dot")
+ );
+
+ private static final Comparator<Dependency> DEP_COMPARATOR = new Comparator<Dependency>() {
+ public int compare(Dependency o1, Dependency o2) {
+ int down = (PROJECT_COMPARATOR.compare(o1.getDownstreamProject(),o2.getDownstreamProject()));
+ return down != 0 ? down : PROJECT_COMPARATOR
+ .compare(o1.getUpstreamProject(),o2.getUpstreamProject());
+ }
+ };
+ private static final Comparator<AbstractProject<?,?>> PROJECT_COMPARATOR = new Comparator<AbstractProject<?,?>>() {
+ public int compare(AbstractProject<?,?> o1, AbstractProject<?,?> o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ };
+
+ protected static class SupportedImageType {
+ final String contentType;
+ final String dotType;
+
+ private SupportedImageType(String contentType,
+ String dotType) {
+ this.contentType = contentType;
+ this.dotType = dotType;
+ }
+
+ public static SupportedImageType of(String contentType, String dotType) {
+ return new SupportedImageType(contentType, dotType);
+ }
+
+ }
+
+ public String getIconFileName() {
+ return "graph.gif";
+ }
+
+ public String getDisplayName() {
+ return "Dependency Graph";
+ }
+
+ public String getUrlName() {
+ return "depgraph-view";
+ }
+
+ protected void runDot(OutputStream output, InputStream input, String type)
+ throws IOException {
+ DescriptorImpl descriptor = Hudson.getInstance().getDescriptorByType(DependencyGraphProperty.DescriptorImpl.class);
+ String dotPath = descriptor.getDotExeOrDefault();
+ Launcher launcher = Hudson.getInstance().createLauncher(new LogTaskListener(LOGGER, Level.CONFIG));
+ try {
+ launcher.launch()
+ .cmds(dotPath,"-T" + type)
+ .stdin(input)
+ .stdout(output).start().join();
+ } catch (InterruptedException e) {
+ LOGGER.severe("Interrupted while waiting for dot-file to be created:" + e);
+ e.printStackTrace();
+ }
+ }
+
+ public String generateDotText(Set<AbstractProject<?,?>> projects, Set<Dependency> deps) {
+ List<Dependency> sortedDeps = new ArrayList<Dependency>(deps);
+ Collections.sort(sortedDeps, DEP_COMPARATOR);
+
+ List<AbstractProject<?, ?>> depProjects = new ArrayList<AbstractProject<?, ?>>();
+ for (Dependency dependency : sortedDeps) {
+ depProjects.add(dependency.getUpstreamProject());
+ depProjects.add(dependency.getDownstreamProject());
+ }
+
+ List<AbstractProject<?, ?>> sortedProjects = new ArrayList<AbstractProject<?, ?>>(projects);
+ sortedProjects.removeAll(depProjects);
+ Collections.sort(sortedProjects, PROJECT_COMPARATOR);
+ Collections.sort(depProjects, PROJECT_COMPARATOR);
+ sortedProjects.addAll(depProjects);
+
+ StringBuilder builder = new StringBuilder("digraph {\n");
+ builder.append("node [shape=box, style=rounded];\n");
+ builder.append("subgraph clusterdepgraph {\n");
+ for (AbstractProject<?, ?> proj:sortedProjects) {
+ builder.append(projectToNodeString(proj)).append(";\n");
+ }
+
+ Map<Class<?>, String> depClass2Color = new HashMap<Class<?>, String>();
+ for (Dependency dep : sortedDeps) {
+ builder.append(dependencyToEdgeString(dep));
+ builder.append(";\n");
+
+
+ }
+ builder.append("color=white;\n}\n");
+ return builder.append("}").toString();
+ }
+
+ private String projectToNodeString(AbstractProject<?, ?> proj) {
+ return escapeString(proj.getName()) +
+ " [href=" +
+ escapeString(Hudson.getInstance().getRootUrlFromRequest() + proj.getUrl()) + "]";
+ }
+
+ private String dependencyToEdgeString(Dependency dep) {
+ return escapeString(dep.getUpstreamProject().getName()) + " -> " +
+ escapeString(dep.getDownstreamProject().getName());
+ }
+
+ private String escapeString(String toEscape) {
+ return "\"" + toEscape + "\"";
+ }
+
+ protected abstract Collection<? extends AbstractProject<?, ?>> getProjectsForDepgraph();
+
+ public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException {
+ String path = req.getRestOfPath();
+ if (path.startsWith("/graph.")) {
+ String extension = path.substring("/graph.".length());
+ if (extension2Type.containsKey(extension.toLowerCase())) {
+ SupportedImageType imageType = extension2Type.get(extension.toLowerCase());
+ CalculateDeps calculateDeps = new CalculateDeps(getProjectsForDepgraph());
+ String graphDot = generateDotText(calculateDeps.getProjects(), calculateDeps.getDependencies());
+ rsp.setContentType(imageType.contentType);
+ if ("dot".equalsIgnoreCase(extension)) {
+ rsp.getWriter().append(graphDot).close();
+ } else {
+ runDot(rsp.getOutputStream(), new ByteArrayInputStream(graphDot.getBytes()), imageType.dotType);
+ }
+ rsp.getOutputStream().close();
+ }
+ } else {
+ rsp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
+ return;
+ }
+ }
+
+}
114 src/main/java/hudson/plugins/depgraph_view/CalculateDeps.java
@@ -0,0 +1,114 @@
+package hudson.plugins.depgraph_view;
+
+import hudson.model.DependencyGraph;
+import hudson.model.DependencyGraph.Dependency;
+import hudson.model.Item;
+import hudson.model.AbstractProject;
+import hudson.model.Hudson;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class CalculateDeps {
+ private static final Logger LOGGER = Logger.getLogger(CalculateDeps.class.getName());
+ private final Set<Dependency> visitedDeps = new HashSet<Dependency>();
+ private final Set<AbstractProject<?,?>> visitedProj = new HashSet<AbstractProject<?,?>>();
+ private boolean calculated = false;
+
+ public CalculateDeps(AbstractProject<?, ?> proj) {
+ visitedProj.add(proj);
+ }
+
+ public CalculateDeps(Collection<? extends AbstractProject<?, ?>> projects) {
+ visitedProj.addAll(projects);
+ }
+
+ public void calculateNodesAndDependencies() {
+ if (!calculated) {
+ calculateNodesAndDependencies(visitedProj);
+ calculated = true;
+ }
+ }
+
+ private void calculateNodesAndDependencies(Set<AbstractProject<?, ?>> fromProjects) {
+ Set<AbstractProject<?,?>> newProj = new HashSet<AbstractProject<?, ?>>();
+ DependencyGraph depGraph = Hudson.getInstance().getDependencyGraph();
+ for (AbstractProject<?,?> project : fromProjects) {
+ newProj.addAll(
+ addNewDependencies(getRealDependencies(depGraph.getUpstreamDependencies(project)),true));
+ newProj.addAll(
+ addNewDependencies(getRealDependencies(depGraph.getDownstreamDependencies(project)),false));
+ }
+ visitedProj.addAll(newProj);
+ if (!newProj.isEmpty()) {
+ calculateNodesAndDependencies(newProj);
+ }
+ }
+
+ private Set<Dependency> getRealDependencies(Collection<Dependency> depGroups) {
+ Set<Dependency> deps = new HashSet<Dependency>();
+ for (Dependency dependency : depGroups) {
+ deps.addAll(getDependenciesFromDepGroup(dependency));
+ }
+ return deps;
+ }
+
+ private Set<AbstractProject<?, ?>> addNewDependencies(Set<Dependency> dependencies, boolean isUpstream) {
+ Set<AbstractProject<?,?>> newProj = new HashSet<AbstractProject<?, ?>>();
+ for (Dependency dep : dependencies) {
+ AbstractProject<?,?> projectToAdd = isUpstream ? dep.getUpstreamProject() : dep.getDownstreamProject();
+ if (projectToAdd.hasPermission(Item.READ) && !visitedDeps.contains(dep)) {
+ visitedDeps.add(dep);
+ if (!visitedProj.contains(projectToAdd)) {
+ newProj.add(projectToAdd);
+ }
+ }
+ }
+ return newProj;
+ }
+
+ private Set<Dependency> getDependenciesFromDepGroup(Dependency dep) {
+ Class<?>[] declaredClasses = DependencyGraph.class.getDeclaredClasses();
+ Class<?> depGroup = null;
+ for (Class<?> clazz : declaredClasses) {
+ if (clazz.getName().endsWith("DependencyGroup")) {
+ depGroup = clazz;
+ break;
+ }
+ }
+ if (depGroup == null) {
+ LOGGER.log(Level.SEVERE,"Error extracting dependencies vom DependencyGroup");
+ return Collections.singleton(dep);
+ }
+ try {
+ Field declaredField = depGroup.getDeclaredField("group");
+ declaredField.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ Set<Dependency> subDeps = (Set<Dependency>) declaredField.get(dep);
+ return subDeps;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE,"Error extracting dependencies vom DependencyGroup",e);
+ return Collections.singleton(dep);
+ }
+ }
+
+
+ public Set<AbstractProject<?, ?>> getProjects() {
+ if (!calculated) {
+ calculateNodesAndDependencies();
+ }
+ return visitedProj;
+ }
+ public Set<Dependency> getDependencies() {
+ if (!calculated) {
+ calculateNodesAndDependencies();
+ }
+ return visitedDeps;
+ }
+
+}
28 src/main/java/hudson/plugins/depgraph_view/DependencyGraphProjectAction.java
@@ -0,0 +1,28 @@
+package hudson.plugins.depgraph_view;
+
+import hudson.model.AbstractProject;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.logging.Logger;
+
+
+
+public final class DependencyGraphProjectAction extends AbstractDependencyGraphAction {
+ Logger LOGGER = Logger.getLogger(DependencyGraphProperty.class.getName());
+
+ final private AbstractProject<?, ?> project;
+
+ public DependencyGraphProjectAction(AbstractProject<?, ?> project) {
+ this.project = project;
+ }
+
+ public AbstractProject<?, ?> getProject() {
+ return project;
+ }
+
+ @Override
+ protected Collection<AbstractProject<?, ?>> getProjectsForDepgraph() {
+ return Collections.<AbstractProject<?, ?>>singleton(project);
+ }
+}
90 src/main/java/hudson/plugins/depgraph_view/DependencyGraphProperty.java
@@ -0,0 +1,90 @@
+package hudson.plugins.depgraph_view;
+
+import hudson.Extension;
+import hudson.Functions;
+import hudson.Launcher;
+import hudson.Util;
+import hudson.model.Action;
+import hudson.model.BuildListener;
+import hudson.model.JobProperty;
+import hudson.model.JobPropertyDescriptor;
+import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
+import hudson.util.FormValidation;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+
+import net.sf.json.JSONObject;
+
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.StaplerRequest;
+
+
+public class DependencyGraphProperty extends JobProperty<AbstractProject<?,?>> {
+
+ @DataBoundConstructor
+ public DependencyGraphProperty() {
+ }
+
+ @Override
+ public Collection<? extends Action> getJobActions(AbstractProject<?, ?> job) {
+ return Collections.singleton(new DependencyGraphProjectAction(job));
+ }
+
+ @Override
+ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
+ BuildListener listener) throws InterruptedException, IOException {
+ return true;
+ }
+
+
+
+ @Extension
+ public static class DescriptorImpl extends JobPropertyDescriptor {
+
+ private String dotExe;
+
+ public DescriptorImpl() {
+ load();
+ }
+
+ @Override
+ public boolean configure( StaplerRequest req, JSONObject o ) {
+ dotExe = Util.fixEmptyAndTrim(o.getString("dotExe"));
+ save();
+
+ return true;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Dependency Graph Viewer";
+ }
+
+ public String getDotExe() {
+ return dotExe;
+ }
+
+ public String getDotExeOrDefault() {
+ if (Util.fixEmptyAndTrim(dotExe) == null) {
+ return Functions.isWindows() ? "dot.ext" : "dot";
+ } else {
+ return dotExe;
+ }
+ }
+
+ public synchronized void setDotExe(String dotPath) {
+ this.dotExe = dotPath;
+ save();
+ }
+
+ public FormValidation doCheckDotExe(@QueryParameter final String value) {
+ return FormValidation.validateExecutable(value);
+ }
+
+ }
+
+}
26 src/main/java/hudson/plugins/depgraph_view/DependencyGraphRootAction.java
@@ -0,0 +1,26 @@
+package hudson.plugins.depgraph_view;
+
+import hudson.Extension;
+import hudson.model.RootAction;
+import hudson.model.AbstractProject;
+import hudson.model.Hudson;
+import hudson.model.View;
+
+import java.util.Collection;
+import java.util.List;
+
+@Extension
+public class DependencyGraphRootAction extends AbstractDependencyGraphAction
+ implements RootAction {
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ protected Collection<? extends AbstractProject<?, ?>> getProjectsForDepgraph() {
+ return (List) Hudson.getInstance().getAllItems(AbstractProject.class);
+ }
+
+ public View getMainView() {
+ return Hudson.getInstance().getPrimaryView();
+ }
+
+}
31 src/main/java/hudson/plugins/depgraph_view/ItemListenerImpl.java
@@ -0,0 +1,31 @@
+package hudson.plugins.depgraph_view;
+
+import hudson.Extension;
+import hudson.model.AbstractProject;
+import hudson.model.Hudson;
+import hudson.model.listeners.ItemListener;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@Extension
+public class ItemListenerImpl extends ItemListener {
+
+ private static final Logger LOGGER = Logger.getLogger(ItemListener.class.getName());
+
+ @Override
+ public void onLoaded() {
+ for (AbstractProject<?,?> project : Hudson.getInstance().getAllItems(AbstractProject.class)) {
+ DependencyGraphProperty property = project.getProperty(DependencyGraphProperty.class);
+ if (property == null) {
+ try {
+ project.addProperty(new DependencyGraphProperty());
+ } catch (IOException e) {
+ LOGGER.log(Level.SEVERE, "Failed to persist " + project, e);
+ }
+ }
+ }
+ }
+
+}
9 src/main/resources/hudson/plugins/depgraph_view/DependencyGraphProjectAction/index.jelly
@@ -0,0 +1,9 @@
+<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:sv="/lib/sectioned_view" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
+<l:layout title="${it.project.displayName} Dependency Graph">
+<st:include it="${it.project}" page="sidepanel.jelly" />
+<l:main-panel>
+<h2>Dependency Graph</h2>
+<img src="graph.png" lazymap="graph.map"/>
+</l:main-panel>
+</l:layout>
+</j:jelly>
9 src/main/resources/hudson/plugins/depgraph_view/DependencyGraphProperty/global.jelly
@@ -0,0 +1,9 @@
+<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
+ <f:section title="${%Dependency Graph Viewer Configuration}">
+ <f:entry title="${%Dot Executable Path}" field="dotExe">
+<!-- help="/plugin/dependency-graph/help-globalConfig.html"-->
+<!-- <f:textbox checkUrl="'${rootURL}/jobProperty/DependencyGraphProperty/checkDotPath?value='+escape(this.value)"/>-->
+<f:textbox/>
+ </f:entry>
+ </f:section>
+</j:jelly>
3  src/main/resources/hudson/plugins/depgraph_view/DependencyGraphProperty/help-dotExe.html
@@ -0,0 +1,3 @@
+<div>
+ Configure the location of the <tt>dot</tt> binary. If not set, defaults to <tt>dot</tt> (or <tt>dot.exe</tt> on Windows).
+</div>
9 src/main/resources/hudson/plugins/depgraph_view/DependencyGraphRootAction/index.jelly
@@ -0,0 +1,9 @@
+<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
+<l:layout title="Dependency Graph">
+<st:include it="${it.mainView}" page="sidepanel.jelly" />
+<l:main-panel>
+<h2>Dependency Graph</h2>
+<img src="graph.png" lazymap="graph.map"/>
+</l:main-panel>
+</l:layout>
+</j:jelly>
8 src/main/resources/index.jelly
@@ -0,0 +1,8 @@
+<!--
+ This view is used to render the plugin list page.
+
+ Since we don't really have anything dynamic here, let's just use static HTML.
+-->
+<div>
+ This plugin shows the dependency graph of each project.
+</div>
5 src/main/webapp/help-globalConfig.html
@@ -0,0 +1,5 @@
+<div>
+ <p>
+ Put the name of the dot executable from the graphviz project here.
+ </p>
+</div>
Please sign in to comment.
Something went wrong with that request. Please try again.