8173374: Update GenGraphs tool to generate dot graph with requires transitive edges
Reviewed-by: dfuchs, redestad
--- a/jdk/make/CompileModuleTools.gmk Wed Feb 15 12:55:20 2017 -0800
+++ b/jdk/make/CompileModuleTools.gmk Wed Feb 15 16:18:22 2017 -0800
@@ -37,5 +37,7 @@
build/tools/jigsaw, \
BIN := $(TOOLS_CLASSES_DIR), \
ADD_JAVAC_FLAGS := \
+ --add-modules jdk.jdeps \
--add-exports java.base/jdk.internal.module=ALL-UNNAMED \
+ --add-exports jdk.jdeps/com.sun.tools.jdeps=ALL-UNNAMED \
))
--- a/jdk/make/GenerateModuleSummary.gmk Wed Feb 15 12:55:20 2017 -0800
+++ b/jdk/make/GenerateModuleSummary.gmk Wed Feb 15 16:18:22 2017 -0800
@@ -31,11 +31,16 @@
include ModuleTools.gmk
GENGRAPHS_DIR := $(IMAGES_OUTPUTDIR)/gengraphs
+SPEC_DOTFILES_DIR := $(IMAGES_OUTPUTDIR)/spec-dotfiles
TOOLS_MODULE_SRCDIR := $(JDK_TOPDIR)/make/src/classes/build/tools/jigsaw
$(GENGRAPHS_DIR)/jdk.dot: $(BUILD_JIGSAW_TOOLS)
$(MKDIR) -p $(@D)
- $(TOOL_GENGRAPHS) $(GENGRAPHS_DIR)
+ $(TOOL_GENGRAPHS) --output $(GENGRAPHS_DIR)
+
+$(SPEC_DOTFILES_DIR)/java.se.dot: $(BUILD_JIGSAW_TOOLS)
+ $(MKDIR) -p $(@D)
+ $(TOOL_GENGRAPHS) --spec --output $(SPEC_DOTFILES_DIR)
$(GENGRAPHS_DIR)/technology-summary.html: $(TOOLS_MODULE_SRCDIR)/technology-summary.html
$(install-file)
@@ -44,4 +49,4 @@
$(MKDIR) -p $(@D)
$(TOOL_MODULESUMMARY) -o $@ --module-path $(IMAGES_OUTPUTDIR)/jmods
-all: $(GENGRAPHS_DIR)/jdk.dot $(GENGRAPHS_DIR)/module-summary.html
+all: $(GENGRAPHS_DIR)/jdk.dot $(GENGRAPHS_DIR)/module-summary.html $(SPEC_DOTFILES_DIR)/java.se.dot
--- a/jdk/make/ModuleTools.gmk Wed Feb 15 12:55:20 2017 -0800
+++ b/jdk/make/ModuleTools.gmk Wed Feb 15 16:18:22 2017 -0800
@@ -36,6 +36,8 @@
BUILD_JIGSAW_TOOLS, $(TOOLS_CLASSES_DIR))
TOOL_GENGRAPHS := $(BUILD_JAVA) -esa -ea -cp $(TOOLS_CLASSES_DIR) \
+ --add-modules jdk.jdeps \
+ --add-exports jdk.jdeps/com.sun.tools.jdeps=ALL-UNNAMED \
build.tools.jigsaw.GenGraphs
TOOL_MODULESUMMARY := $(BUILD_JAVA) -esa -ea -cp $(TOOLS_CLASSES_DIR) \
--- a/jdk/make/src/classes/build/tools/jigsaw/GenGraphs.java Wed Feb 15 12:55:20 2017 -0800
+++ b/jdk/make/src/classes/build/tools/jigsaw/GenGraphs.java Wed Feb 15 16:18:22 2017 -0800
@@ -25,29 +25,21 @@
package build.tools.jigsaw;
+import com.sun.tools.jdeps.ModuleDotGraph;
+import com.sun.tools.jdeps.ModuleDotGraph.DotGraphBuilder;
+
import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
-import java.lang.module.ResolvedModule;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
-import java.util.function.Function;
-
-import static java.util.stream.Collectors.*;
-import static java.lang.module.ModuleDescriptor.Requires.Modifier.TRANSITIVE;
/**
* Generate the DOT file for a module graph for each module in the JDK
@@ -56,238 +48,100 @@
public class GenGraphs {
public static void main(String[] args) throws Exception {
+ Path dir = null;
+ boolean spec = false;
+ for (int i=0; i < args.length; i++) {
+ String arg = args[i];
+ if (arg.equals("--spec")) {
+ spec = true;
+ } else if (arg.equals("--output")) {
+ i++;
+ dir = i < args.length ? Paths.get(args[i]) : null;
+ } else if (arg.startsWith("-")) {
+ throw new IllegalArgumentException("Invalid option: " + arg);
+ }
+ }
- if (args.length != 1) {
- System.err.println("ERROR: specify the output directory");
+ if (dir == null) {
+ System.err.println("ERROR: must specify --output argument");
System.exit(1);
}
- Path dir = Paths.get(args[0]);
+
+ // setup and configure the dot graph attributes
+ initDotGraphAttributes();
Files.createDirectories(dir);
- ModuleFinder finder = ModuleFinder.ofSystem();
+ GenGraphs genGraphs = new GenGraphs(dir, spec);
- Set<ModuleDescriptor> javaSEModules
- = new TreeSet<>(finder.findAll().stream()
- .map(ModuleReference::descriptor)
- .filter(m -> (m.name().startsWith("java.") &&
- !m.name().equals("java.smartcardio")))
- .collect(toSet()));
- Set<ModuleDescriptor> jdkModules
- = new TreeSet<>(finder.findAll().stream()
- .map(ModuleReference::descriptor)
- .filter(m -> !javaSEModules.contains(m))
- .collect(toSet()));
-
- GenGraphs genGraphs = new GenGraphs(dir, javaSEModules, jdkModules);
- Set<String> mods = new HashSet<>();
- for (ModuleReference mref: finder.findAll()) {
- mods.add(mref.descriptor().name());
- genGraphs.genDotFile(mref);
+ // print dot file for each module
+ Map<String, Configuration> configurations = new HashMap<>();
+ Set<String> modules = new HashSet<>();
+ ModuleFinder finder = ModuleFinder.ofSystem();
+ for (ModuleReference mref : finder.findAll()) {
+ String name = (mref.descriptor().name());
+ modules.add(name);
+ if (genGraphs.accept(name, mref.descriptor())) {
+ configurations.put(name, Configuration.empty()
+ .resolve(finder,
+ ModuleFinder.of(),
+ Set.of(name)));
+ }
}
- // all modules
- genGraphs.genDotFile("jdk", mods);
+ if (genGraphs.accept("jdk", null)) {
+ // print a graph of all JDK modules
+ configurations.put("jdk", Configuration.empty()
+ .resolve(finder,
+ ModuleFinder.of(),
+ modules));
+ }
+ genGraphs.genDotFiles(configurations);
}
- private static final String ORANGE = "#e76f00";
- private static final String BLUE = "#437291";
- private static final String GRAY = "#dddddd";
-
- private static final String REEXPORTS = "";
- private static final String REQUIRES = "style=\"dashed\"";
- private static final String REQUIRES_BASE = "color=\"" + GRAY + "\"";
-
- private static final Map<String,Integer> weights = new HashMap<>();
- private static final List<Set<String>> ranks = new ArrayList<>();
-
- private static void weight(String s, String t, int w) {
- weights.put(s + ":" + t, w);
- }
+ static void initDotGraphAttributes() {
+ int h = 1000;
+ DotGraphBuilder.weight("java.se", "java.sql.rowset", h * 10);
+ DotGraphBuilder.weight("java.sql.rowset", "java.sql", h * 10);
+ DotGraphBuilder.weight("java.sql", "java.xml", h * 10);
+ DotGraphBuilder.weight("java.xml", "java.base", h * 10);
- private static int weightOf(String s, String t) {
- int w = weights.getOrDefault(s + ":" + t, 1);
- if (w != 1)
- return w;
- if (s.startsWith("java.") && t.startsWith("java."))
- return 10;
- return 1;
- }
-
- static {
- int h = 1000;
- weight("java.se", "java.sql.rowset", h * 10);
- weight("java.sql.rowset", "java.sql", h * 10);
- weight("java.sql", "java.xml", h * 10);
- weight("java.xml", "java.base", h * 10);
-
- ranks.add(Set.of("java.logging", "java.scripting", "java.xml"));
- ranks.add(Set.of("java.sql"));
- ranks.add(Set.of("java.compiler", "java.instrument"));
- ranks.add(Set.of("java.desktop", "java.management"));
- ranks.add(Set.of("java.corba", "java.xml.ws"));
- ranks.add(Set.of("java.xml.bind", "java.xml.ws.annotation"));
-
+ DotGraphBuilder.sameRankNodes(Set.of("java.logging", "java.scripting", "java.xml"));
+ DotGraphBuilder.sameRankNodes(Set.of("java.sql"));
+ DotGraphBuilder.sameRankNodes(Set.of("java.compiler", "java.instrument"));
+ DotGraphBuilder.sameRankNodes(Set.of("java.desktop", "java.management"));
+ DotGraphBuilder.sameRankNodes(Set.of("java.corba", "java.xml.ws"));
+ DotGraphBuilder.sameRankNodes(Set.of("java.xml.bind", "java.xml.ws.annotation"));
+ DotGraphBuilder.setRankSep(0.7);
+ DotGraphBuilder.setFontSize(12);
+ DotGraphBuilder.setArrowSize(1);
+ DotGraphBuilder.setArrowWidth(2);
}
private final Path dir;
- private final Set<ModuleDescriptor> javaGroup;
- private final Set<ModuleDescriptor> jdkGroup;
-
- GenGraphs(Path dir, Set<ModuleDescriptor> javaGroup, Set<ModuleDescriptor> jdkGroup) {
+ private final boolean spec;
+ GenGraphs(Path dir, boolean spec) {
this.dir = dir;
- this.javaGroup = Collections.unmodifiableSet(javaGroup);
- this.jdkGroup = Collections.unmodifiableSet(jdkGroup);
- }
-
- /**
- * Generates a dot file for the given module reference as the root.
- */
- void genDotFile(ModuleReference mref) throws IOException {
- String name = mref.descriptor().name();
- genDotFile(name, Set.of(name));
+ this.spec = spec;
}
- /**
- * Generates a dot file for the given set of root modules.
- */
- void genDotFile(String name, Set<String> roots) throws IOException {
- Configuration cf =
- Configuration.empty().resolve(ModuleFinder.ofSystem(),
- ModuleFinder.of(),
- roots);
-
- Set<ModuleDescriptor> mds = cf.modules().stream()
- .map(ResolvedModule::reference)
- .map(ModuleReference::descriptor)
- .collect(toSet());
-
- // generate a dot file for the resolved graph
- try (OutputStream os = Files.newOutputStream(dir.resolve(name + ".dot"));
- PrintStream out = new PrintStream(os)) {
- printGraph(out, name, gengraph(cf),
- mds.stream()
- .collect(toMap(ModuleDescriptor::name, Function.identity()))
- );
- }
-
- if (name.equals("java.se") || name.equals("java.se.ee")) {
- // generate a dot file for Java SE module graph
- try (OutputStream os = Files.newOutputStream(dir.resolve(name + "-spec.dot"));
- PrintStream out = new PrintStream(os)) {
- // transitive reduction on the graph of `requires transitive` edges
- // filter out jdk.* modules which are implementation dependences
- Graph<String> graph = requiresTransitiveGraph(cf, true);
- printGraph(out, name, graph,
- mds.stream()
- .filter(md -> !md.name().startsWith("jdk.") &&
- graph.nodes().contains(md.name()))
- .collect(toMap(ModuleDescriptor::name, Function.identity()))
- );
- }
- }
+ void genDotFiles(Map<String, Configuration> configurations) throws IOException {
+ ModuleDotGraph dotGraph = new ModuleDotGraph(configurations, spec);
+ dotGraph.genDotFiles(dir);
}
- private void printGraph(PrintStream out,
- String name,
- Graph<String> graph,
- Map<String, ModuleDescriptor> nameToModule)
- throws IOException
- {
- Set<ModuleDescriptor> descriptors = new TreeSet<>(nameToModule.values());
-
- out.format("digraph \"%s\" {%n", name);
- out.format("size=\"25,25\";");
- out.format("nodesep=.5;%n");
- out.format("ranksep=1.5;%n");
- out.format("pencolor=transparent;%n");
- out.format("node [shape=plaintext, fontname=\"DejaVuSans\", fontsize=36, margin=\".2,.2\"];%n");
- out.format("edge [penwidth=4, color=\"#999999\", arrowhead=open, arrowsize=2];%n");
-
- out.format("subgraph %sse {%n", name.equals("jdk") ? "cluster_" : "");
- descriptors.stream()
- .filter(javaGroup::contains)
- .map(ModuleDescriptor::name)
- .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n",
- mn, ORANGE, "java"));
- out.format("}%n");
+ boolean accept(String name, ModuleDescriptor descriptor) {
+ if (!spec) return true;
- // same ranks
- ranks.stream()
- .map(group -> descriptors.stream()
- .map(ModuleDescriptor::name)
- .filter(group::contains)
- .map(mn -> "\"" + mn + "\"")
- .collect(joining(",")))
- .filter(group -> group.length() > 0)
- .forEach(group -> out.format("{rank=same %s}%n", group));
-
- descriptors.stream()
- .filter(jdkGroup::contains)
- .map(ModuleDescriptor::name)
- .forEach(mn -> out.format(" \"%s\" [fontcolor=\"%s\", group=%s];%n",
- mn, BLUE, "jdk"));
-
- descriptors.stream()
- .forEach(md -> {
- String mn = md.name();
- Set<String> requiresTransitive = md.requires().stream()
- .filter(d -> d.modifiers().contains(TRANSITIVE))
- .map(d -> d.name())
- .collect(toSet());
+ if (name.equals("jdk"))
+ return false;
- graph.adjacentNodes(mn)
- .stream()
- .filter(nameToModule::containsKey)
- .forEach(dn -> {
- String attr = dn.equals("java.base") ? REQUIRES_BASE
- : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES);
- int w = weightOf(mn, dn);
- if (w > 1)
- attr += "weight=" + w;
- out.format(" \"%s\" -> \"%s\" [%s];%n", mn, dn, attr);
- });
- });
-
- out.println("}");
- }
+ if (name.equals("java.se") || name.equals("java.se.ee"))
+ return true;
- /**
- * Returns a Graph of the given Configuration after transitive reduction.
- *
- * Transitive reduction of requires transitive edge and requires edge have
- * to be applied separately to prevent the requires transitive edges
- * (e.g. U -> V) from being reduced by a path (U -> X -> Y -> V)
- * in which V would not be re-exported from U.
- */
- private Graph<String> gengraph(Configuration cf) {
- Graph.Builder<String> builder = new Graph.Builder<>();
- for (ResolvedModule resolvedModule : cf.modules()) {
- String mn = resolvedModule.reference().descriptor().name();
- builder.addNode(mn);
- resolvedModule.reads().stream()
- .map(ResolvedModule::name)
- .forEach(target -> builder.addEdge(mn, target));
- }
- Graph<String> rpg = requiresTransitiveGraph(cf, false);
- return builder.build().reduce(rpg);
+ // only the module that has exported API
+ return descriptor.exports().stream()
+ .filter(e -> !e.isQualified())
+ .findAny().isPresent();
}
-
- /**
- * Returns a Graph containing only requires transitive edges
- * with transitive reduction.
- */
- private Graph<String> requiresTransitiveGraph(Configuration cf, boolean includeBase) {
- Graph.Builder<String> builder = new Graph.Builder<>();
- for (ResolvedModule resolvedModule : cf.modules()) {
- ModuleDescriptor descriptor = resolvedModule.reference().descriptor();
- String mn = descriptor.name();
- descriptor.requires().stream()
- .filter(d -> d.modifiers().contains(TRANSITIVE)
- || (includeBase && d.name().equals("java.base")))
- .map(d -> d.name())
- .forEach(d -> builder.addEdge(mn, d));
- }
- return builder.build().reduce();
- }
-}
+}
\ No newline at end of file
--- a/jdk/make/src/classes/build/tools/jigsaw/Graph.java Wed Feb 15 12:55:20 2017 -0800
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package build.tools.jigsaw;
-
-import java.io.PrintStream;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-
-public class Graph<T> {
- private static boolean traceOn = Boolean.getBoolean("build.tools.module.trace");
- private final Set<T> nodes;
- private final Map<T, Set<T>> edges;
- private Graph(Set<T> nodes, Map<T, Set<T>> edges) {
- this.nodes = nodes;
- this.edges = edges;
- }
-
- public Set<T> nodes() {
- return nodes;
- }
-
- public Map<T, Set<T>> edges() {
- return edges;
- }
-
- public Set<T> adjacentNodes(T u) {
- return edges.get(u);
- }
-
- /**
- * Returns a new Graph after transitive reduction
- */
- public Graph<T> reduce() {
- Graph.Builder<T> builder = new Builder<>();
- nodes.stream()
- .forEach(u -> {
- builder.addNode(u);
- edges.get(u).stream()
- .filter(v -> !pathExists(u, v, false))
- .forEach(v -> builder.addEdge(u, v));
- });
- return builder.build();
- }
-
- /**
- * Returns a new Graph after transitive reduction. All edges in
- * the given g takes precedence over this graph.
- *
- * @throw IllegalArgumentException g must be a subgraph this graph
- */
- public Graph<T> reduce(Graph<T> g) {
- boolean subgraph = nodes.containsAll(g.nodes) && g.edges.keySet().stream()
- .allMatch(u -> adjacentNodes(u).containsAll(g.adjacentNodes(u)));
- if (!subgraph) {
- throw new IllegalArgumentException("the given argument is not a subgraph of this graph");
- }
-
- Graph.Builder<T> builder = new Builder<>();
- nodes.stream()
- .forEach(u -> {
- builder.addNode(u);
- // filter the edge if there exists a path from u to v in the given g
- // or there exists another path from u to v in this graph
- edges.get(u).stream()
- .filter(v -> !g.pathExists(u, v) && !pathExists(u, v, false))
- .forEach(v -> builder.addEdge(u, v));
- });
-
- // add the overlapped edges from this graph and the given g
- g.edges().keySet().stream()
- .forEach(u -> g.adjacentNodes(u).stream()
- .filter(v -> isAdjacent(u, v))
- .forEach(v -> builder.addEdge(u, v)));
- return builder.build();
- }
-
- private boolean isAdjacent(T u, T v) {
- return edges.containsKey(u) && edges.get(u).contains(v);
- }
-
- private boolean pathExists(T u, T v) {
- return pathExists(u, v, true);
- }
-
- /**
- * Returns true if there exists a path from u to v in this graph.
- * If includeAdjacent is false, it returns true if there exists
- * another path from u to v of distance > 1
- */
- private boolean pathExists(T u, T v, boolean includeAdjacent) {
- if (!nodes.contains(u) || !nodes.contains(v)) {
- return false;
- }
- if (includeAdjacent && isAdjacent(u, v)) {
- return true;
- }
- Deque<T> stack = new LinkedList<>();
- Set<T> visited = new HashSet<>();
- stack.push(u);
- while (!stack.isEmpty()) {
- T node = stack.pop();
- if (node.equals(v)) {
- if (traceOn) {
- System.out.format("Edge %s -> %s removed%n", u, v);
- }
- return true;
- }
- if (!visited.contains(node)) {
- visited.add(node);
- edges.get(node).stream()
- .filter(e -> includeAdjacent || !node.equals(u) || !e.equals(v))
- .forEach(e -> stack.push(e));
- }
- }
- assert !visited.contains(v);
- return false;
- }
-
- void printGraph(PrintStream out) {
- nodes.stream()
- .forEach(u -> adjacentNodes(u).stream()
- .forEach(v -> out.format("%s -> %s%n", u, v)));
- }
-
- public static class Builder<T> {
- final Set<T> nodes = new HashSet<>();
- final Map<T, Set<T>> edges = new HashMap<>();
- public void addNode(T node) {
- if (nodes.contains(node)) {
- return;
- }
- nodes.add(node);
- edges.computeIfAbsent(node, _e -> new HashSet<>());
- }
- public void addEdge(T u, T v) {
- addNode(u);
- addNode(v);
- edges.get(u).add(v);
- }
- public Graph<T> build() {
- return new Graph<>(nodes, edges);
- }
- }
-}