about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAntoine Eiche <lewo@abesis.fr>2018-09-29T07·42+0200
committerAntoine Eiche <lewo@abesis.fr>2018-10-20T07·48+0200
commit73c2ae43f08ca35cbb8f86ec7c2efc15ad8686b9 (patch)
treef9e9e24a187bced536401bcffed46d6791267147
parent9617a043541d77d79e4f20f9676aae63de72f45d (diff)
Add --graphml option to the nix-store --query command
This prints the references graph of the store paths in the graphML
format [1]. The graphML format is supported by several graph tools
such as the Python Networkx library or the Apache Thinkerpop project.

[1] http://graphml.graphdrawing.org
-rw-r--r--doc/manual/command-ref/nix-store.xml12
-rw-r--r--src/nix-store/graphml.cc90
-rw-r--r--src/nix-store/graphml.hh11
-rw-r--r--src/nix-store/nix-store.cc14
4 files changed, 126 insertions, 1 deletions
diff --git a/doc/manual/command-ref/nix-store.xml b/doc/manual/command-ref/nix-store.xml
index c827d85b3815..41a04f265d7c 100644
--- a/doc/manual/command-ref/nix-store.xml
+++ b/doc/manual/command-ref/nix-store.xml
@@ -679,6 +679,18 @@ query is applied to the target of the symlink.</para>
 
   </varlistentry>
 
+  <varlistentry><term><option>--graphml</option></term>
+
+    <listitem><para>Prints the references graph of the store paths
+    <replaceable>paths</replaceable> in the <link
+    xlink:href="http://graphml.graphdrawing.org/">GraphML</link> file format.
+    This can be used to visualise dependency graphs. To obtain a
+    build-time dependency graph, apply this to a store derivation. To
+    obtain a runtime dependency graph, apply it to an output
+    path.</para></listitem>
+
+  </varlistentry>
+
   <varlistentry><term><option>--binding</option> <replaceable>name</replaceable></term>
     <term><option>-b</option> <replaceable>name</replaceable></term>
 
diff --git a/src/nix-store/graphml.cc b/src/nix-store/graphml.cc
new file mode 100644
index 000000000000..670fbe227a4c
--- /dev/null
+++ b/src/nix-store/graphml.cc
@@ -0,0 +1,90 @@
+#include "graphml.hh"
+#include "util.hh"
+#include "store-api.hh"
+#include "derivations.hh"
+
+#include <iostream>
+
+
+using std::cout;
+
+namespace nix {
+
+
+static inline const string & xmlQuote(const string & s)
+{
+    // Luckily, store paths shouldn't contain any character that needs to be
+    // quoted.
+    return s;
+}
+
+
+static string symbolicName(const string & path)
+{
+    string p = baseNameOf(path);
+    return string(p, p.find('-') + 1);
+}
+
+
+static string makeEdge(const string & src, const string & dst)
+{
+    return fmt("  <edge source=\"%1%\" target=\"%2%\"/>\n",
+        xmlQuote(src), xmlQuote(dst));
+}
+
+
+static string makeNode(const ValidPathInfo & info)
+{
+    return fmt(
+        "  <node id=\"%1%\">\n"
+        "    <data key=\"narSize\">%2%</data>\n"
+        "    <data key=\"name\">%3%</data>\n"
+        "    <data key=\"type\">%4%</data>\n"
+        "  </node>\n",
+        info.path,
+        info.narSize,
+        symbolicName(info.path),
+        (isDerivation(info.path) ? "derivation" : "output-path"));
+}
+
+
+void printGraphML(ref<Store> store, const PathSet & roots)
+{
+    PathSet workList(roots);
+    PathSet doneSet;
+    std::pair<PathSet::iterator,bool> ret;
+
+    cout << "<?xml version='1.0' encoding='utf-8'?>\n"
+         << "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n"
+         << "    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
+         << "    xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n"
+         << "<key id='narSize' for='node' attr.name='narSize' attr.type='int'/>"
+         << "<key id='name' for='node' attr.name='name' attr.type='string'/>"
+         << "<key id='type' for='node' attr.name='type' attr.type='string'/>"
+         << "<graph id='G' edgedefault='directed'>\n";
+
+    while (!workList.empty()) {
+        Path path = *(workList.begin());
+        workList.erase(path);
+
+        ret = doneSet.insert(path);
+        if (ret.second == false) continue;
+
+        ValidPathInfo info = *(store->queryPathInfo(path));
+        cout << makeNode(info);
+
+        for (auto & p : store->queryPathInfo(path)->references) {
+            if (p != path) {
+                workList.insert(p);
+                cout << makeEdge(path, p);
+            }
+        }
+
+    }
+
+    cout << "</graph>\n";
+    cout << "</graphml>\n";
+}
+
+
+}
diff --git a/src/nix-store/graphml.hh b/src/nix-store/graphml.hh
new file mode 100644
index 000000000000..b78df1e49a67
--- /dev/null
+++ b/src/nix-store/graphml.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "types.hh"
+
+namespace nix {
+
+class Store;
+
+void printGraphML(ref<Store> store, const PathSet & roots);
+
+}
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index fe68f681ae28..4051fdbe166c 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -9,6 +9,7 @@
 #include "util.hh"
 #include "worker-protocol.hh"
 #include "xmlgraph.hh"
+#include "graphml.hh"
 
 #include <iostream>
 #include <algorithm>
@@ -273,7 +274,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
     enum QueryType
         { qDefault, qOutputs, qRequisites, qReferences, qReferrers
         , qReferrersClosure, qDeriver, qBinding, qHash, qSize
-        , qTree, qGraph, qXml, qResolve, qRoots };
+        , qTree, qGraph, qXml, qGraphML, qResolve, qRoots };
     QueryType query = qDefault;
     bool useOutput = false;
     bool includeOutputs = false;
@@ -300,6 +301,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
         else if (i == "--tree") query = qTree;
         else if (i == "--graph") query = qGraph;
         else if (i == "--xml") query = qXml;
+        else if (i == "--graphml") query = qGraphML;
         else if (i == "--resolve") query = qResolve;
         else if (i == "--roots") query = qRoots;
         else if (i == "--use-output" || i == "-u") useOutput = true;
@@ -413,6 +415,16 @@ static void opQuery(Strings opFlags, Strings opArgs)
             break;
         }
 
+        case qGraphML: {
+            PathSet roots;
+            for (auto & i : opArgs) {
+                PathSet paths = maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise);
+                roots.insert(paths.begin(), paths.end());
+            }
+            printGraphML(ref<Store>(store), roots);
+            break;
+        }
+
         case qResolve: {
             for (auto & i : opArgs)
                 cout << format("%1%\n") % store->followLinksToStorePath(i);