Dot is not producing deterministic output for SVG files
When using dot to produce SVG files, it turns out that the output is not deterministic. Running dot multiple time with the same input produces different svg output files. The difference is of no importance to how the SVG file is rendered, but it does thwart our efforts to produce reproducible builds in our setup (building OpenJDK, where SVG files generated by dot is part of the Javadoc API documentation). For now we can add an exception for SVG files to be reproducible, but there seems to be no good reason for the output to be randomly changed, so I believe a fix in GraphViz should be in place.
This is an example snippet of a file that has changed:
File 1:
<!-- java.sql->java.logging -->
<g id="edge29" class="edge"><title>java.sql->java.logging</title>
<path fill="none" stroke="#999999" stroke-width="2" d="M586.396,-176.441C542.895,-162.929 468.682,-139.879 416.691,-123.73"/>
<polygon fill="#999999" stroke="#999999" stroke-width="2" points="406.929,-120.698 417.814,-119.367 411.852,-121.703 416.627,-123.187 416.479,-123.664 416.331,-124.142 411.556,-122.658 415.144,-127.962 406.929,-120.698 406.929,-120.698"/>
</g>
<!-- java.sql->java.xml -->
<g id="edge30" class="edge"><title>java.sql->java.xml</title>
<path fill="none" stroke="#999999" stroke-width="2" d="M622,-167.65C622,-157.981 622,-146.15 622,-135.416"/>
<polygon fill="#999999" stroke="#999999" stroke-width="2" points="622,-125.167 626.5,-135.167 622.5,-130.167 622.5,-135.167 622,-135.167 621.5,-135.167 621.5,-130.167 617.5,-135.167 622,-125.167 622,-125.167"/>
</g>
<!-- java.transaction.xa -->
<g id="node5" class="node"><title>java.transaction.xa</title>
<text text-anchor="middle" x="204" y="-101.4" font-family="DejaVuSans" font-size="12.00" fill="#e76f00">java.transaction.xa</text>
</g>
<!-- java.sql->java.transaction.xa -->
<g id="edge28" class="edge"><title>java.sql->java.transaction.xa</title>
<path fill="none" stroke="#999999" stroke-width="2" d="M586.166,-181.144C525.351,-170.266 398.101,-147.108 291,-125 288.546,-124.493 286.055,-123.973 283.54,-123.442"/>
<polygon fill="#999999" stroke="#999999" stroke-width="2" points="273.759,-121.354 284.478,-119.041 278.753,-121.909 283.643,-122.953 283.538,-123.442 283.434,-123.931 278.544,-122.887 282.599,-127.843 273.759,-121.354 273.759,-121.354"/>
</g>
File 2:
<!-- java.sql->java.logging -->
<g id="edge28" class="edge"><title>java.sql->java.logging</title>
<path fill="none" stroke="#999999" stroke-width="2" d="M586.396,-176.441C542.895,-162.929 468.682,-139.879 416.691,-123.73"/>
<polygon fill="#999999" stroke="#999999" stroke-width="2" points="406.929,-120.698 417.814,-119.367 411.852,-121.703 416.627,-123.187 416.479,-123.664 416.331,-124.142 411.556,-122.658 415.144,-127.962 406.929,-120.698 406.929,-120.698"/>
</g>
<!-- java.sql->java.xml -->
<g id="edge30" class="edge"><title>java.sql->java.xml</title>
<path fill="none" stroke="#999999" stroke-width="2" d="M622,-167.65C622,-157.981 622,-146.15 622,-135.416"/>
<polygon fill="#999999" stroke="#999999" stroke-width="2" points="622,-125.167 626.5,-135.167 622.5,-130.167 622.5,-135.167 622,-135.167 621.5,-135.167 621.5,-130.167 617.5,-135.167 622,-125.167 622,-125.167"/>
</g>
<!-- java.transaction.xa -->
<g id="node5" class="node"><title>java.transaction.xa</title>
<text text-anchor="middle" x="204" y="-101.4" font-family="DejaVuSans" font-size="12.00" fill="#e76f00">java.transaction.xa</text>
</g>
<!-- java.sql->java.transaction.xa -->
<g id="edge29" class="edge"><title>java.sql->java.transaction.xa</title>
<path fill="none" stroke="#999999" stroke-width="2" d="M586.166,-181.144C525.351,-170.266 398.101,-147.108 291,-125 288.546,-124.493 286.055,-123.973 283.54,-123.442"/>
<polygon fill="#999999" stroke="#999999" stroke-width="2" points="273.759,-121.354 284.478,-119.041 278.753,-121.909 283.643,-122.953 283.538,-123.442 283.434,-123.931 278.544,-122.887 282.599,-127.843 273.759,-121.354 273.759,-121.354"/>
</g>
Note how the id "edge28" and "edge29" has swapped place.
We've been chasing non-reproducibility in OpenJDK for a while, so I recognize this kind of non-deterministic behavior. This kind of effects typically comes from using values from a hash map, where the internal order of the hash map is used directly in the output. A common solution is to normalize or sort the values before using them.
For reference: the above snippets were generated from this dot file, using this command line: dot -Tsvg -o module-graph.svg java.se.dot
:
digraph "java.se" {
nodesep=.5;
ranksep=0.600000;
pencolor=transparent;
node [shape=plaintext, fontcolor="#000000", fontname="DejaVuSans", fontsize=12, margin=".2,.2"];
edge [penwidth=2, color="#999999", arrowhead=open, arrowsize=1];
{rank=same "java.logging","java.scripting","java.xml"}
{rank=same "java.sql"}
{rank=same "java.transaction.xa"}
{rank=same "java.compiler","java.instrument"}
{rank=same "java.desktop","java.management"}
subgraph se {
"java.base" [fontcolor="#e76f00", group=java];
"java.compiler" [fontcolor="#e76f00", group=java];
"java.datatransfer" [fontcolor="#e76f00", group=java];
"java.desktop" [fontcolor="#e76f00", group=java];
"java.instrument" [fontcolor="#e76f00", group=java];
"java.logging" [fontcolor="#e76f00", group=java];
"java.management" [fontcolor="#e76f00", group=java];
"java.management.rmi" [fontcolor="#e76f00", group=java];
"java.naming" [fontcolor="#e76f00", group=java];
"java.net.http" [fontcolor="#e76f00", group=java];
"java.prefs" [fontcolor="#e76f00", group=java];
"java.rmi" [fontcolor="#e76f00", group=java];
"java.scripting" [fontcolor="#e76f00", group=java];
"java.se" [fontcolor="#e76f00", group=java];
"java.security.jgss" [fontcolor="#e76f00", group=java];
"java.security.sasl" [fontcolor="#e76f00", group=java];
"java.sql" [fontcolor="#e76f00", group=java];
"java.sql.rowset" [fontcolor="#e76f00", group=java];
"java.transaction.xa" [fontcolor="#e76f00", group=java];
"java.xml" [fontcolor="#e76f00", group=java];
"java.xml.crypto" [fontcolor="#e76f00", group=java];
}
subgraph jdk {
}
"java.compiler" -> "java.base" [color="#999999", weight=10];
"java.datatransfer" -> "java.base" [color="#999999", weight=10];
"java.desktop" -> "java.xml" [weight=10];
"java.desktop" -> "java.datatransfer" [weight=10];
"java.instrument" -> "java.base" [color="#999999", weight=10];
"java.logging" -> "java.base" [color="#999999", weight=10];
"java.management" -> "java.base" [color="#999999", weight=10];
"java.management.rmi" -> "java.rmi" [weight=10];
"java.management.rmi" -> "java.management" [weight=10];
"java.naming" -> "java.base" [color="#999999", weight=10];
"java.net.http" -> "java.base" [color="#999999", weight=10];
"java.prefs" -> "java.base" [color="#999999", weight=10];
"java.rmi" -> "java.base" [color="#999999", weight=10];
"java.scripting" -> "java.base" [color="#999999", weight=10];
"java.se" -> "java.security.jgss" [weight=10];
"java.se" -> "java.desktop" [weight=10];
"java.se" -> "java.compiler" [weight=10];
"java.se" -> "java.scripting" [weight=10];
"java.se" -> "java.xml.crypto" [weight=10];
"java.se" -> "java.prefs" [weight=10];
"java.se" -> "java.sql.rowset" [weight=10000];
"java.se" -> "java.net.http" [weight=10];
"java.se" -> "java.security.sasl" [weight=10];
"java.se" -> "java.management.rmi" [weight=10];
"java.se" -> "java.instrument" [weight=10];
"java.security.jgss" -> "java.base" [color="#999999", weight=10];
"java.security.sasl" -> "java.base" [color="#999999", weight=10];
"java.sql" -> "java.logging" [weight=10];
"java.sql" -> "java.transaction.xa" [weight=10];
"java.sql" -> "java.xml" [weight=10000];
"java.sql.rowset" -> "java.sql" [weight=10000];
"java.sql.rowset" -> "java.naming" [weight=10];
"java.transaction.xa" -> "java.base" [color="#999999", weight=10];
"java.xml" -> "java.base" [color="#999999", weight=10000];
"java.xml.crypto" -> "java.xml" [weight=10];
}