graph.scm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2015, 2016 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2016 Ricardo Wurmus <rekado@elephly.net>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix graph)
  20. #:use-module (guix store)
  21. #:use-module (guix monads)
  22. #:use-module (guix records)
  23. #:use-module (guix sets)
  24. #:use-module (rnrs io ports)
  25. #:use-module (srfi srfi-1)
  26. #:use-module (srfi srfi-9)
  27. #:use-module (srfi srfi-26)
  28. #:use-module (ice-9 match)
  29. #:use-module (ice-9 vlist)
  30. #:export (node-type
  31. node-type?
  32. node-type-identifier
  33. node-type-label
  34. node-type-edges
  35. node-type-convert
  36. node-type-name
  37. node-type-description
  38. node-edges
  39. node-back-edges
  40. traverse/depth-first
  41. node-transitive-edges
  42. node-reachable-count
  43. %graph-backends
  44. %d3js-backend
  45. %graphviz-backend
  46. graph-backend?
  47. graph-backend
  48. graph-backend-name
  49. graph-backend-description
  50. export-graph))
  51. ;;; Commentary:
  52. ;;;
  53. ;;; This module provides an abstract way to represent graphs and to manipulate
  54. ;;; them. It comes with several such representations for packages,
  55. ;;; derivations, and store items. It also provides a generic interface for
  56. ;;; exporting graphs in an external format, including a Graphviz
  57. ;;; implementation thereof.
  58. ;;;
  59. ;;; Code:
  60. ;;;
  61. ;;; Node types.
  62. ;;;
  63. (define-record-type* <node-type> node-type make-node-type
  64. node-type?
  65. (identifier node-type-identifier) ;node -> M identifier
  66. (label node-type-label) ;node -> string
  67. (edges node-type-edges) ;node -> M list of nodes
  68. (convert node-type-convert ;any -> M list of nodes
  69. (default (lift1 list %store-monad)))
  70. (name node-type-name) ;string
  71. (description node-type-description)) ;string
  72. (define (%node-edges type nodes cons-edge)
  73. (with-monad %store-monad
  74. (match type
  75. (($ <node-type> identifier label node-edges)
  76. (define (add-edge node edges)
  77. (>>= (node-edges node)
  78. (lambda (nodes)
  79. (return (fold (cut cons-edge node <> <>)
  80. edges nodes)))))
  81. (mlet %store-monad ((edges (foldm %store-monad
  82. add-edge vlist-null nodes)))
  83. (return (lambda (node)
  84. (reverse (vhash-foldq* cons '() node edges)))))))))
  85. (define (node-edges type nodes)
  86. "Return, as a monadic value, a one-argument procedure that, given a node of TYPE,
  87. returns its edges. NODES is taken to be the sinks of the global graph."
  88. (%node-edges type nodes
  89. (lambda (source target edges)
  90. (vhash-consq source target edges))))
  91. (define (node-back-edges type nodes)
  92. "Return, as a monadic value, a one-argument procedure that, given a node of TYPE,
  93. returns its back edges. NODES is taken to be the sinks of the global graph."
  94. (%node-edges type nodes
  95. (lambda (source target edges)
  96. (vhash-consq target source edges))))
  97. (define (traverse/depth-first proc seed nodes node-edges)
  98. "Do a depth-first traversal of NODES along NODE-EDGES, calling PROC with
  99. each node and the current result, and visiting each reachable node exactly
  100. once. NODES must be a list of nodes, and NODE-EDGES must be a one-argument
  101. procedure as returned by 'node-edges' or 'node-back-edges'."
  102. (let loop ((nodes (append-map node-edges nodes))
  103. (result seed)
  104. (visited (setq)))
  105. (match nodes
  106. (()
  107. result)
  108. ((head . tail)
  109. (if (set-contains? visited head)
  110. (loop tail result visited)
  111. (let ((edges (node-edges head)))
  112. (loop (append edges tail)
  113. (proc head result)
  114. (set-insert head visited))))))))
  115. (define (node-transitive-edges nodes node-edges)
  116. "Return the list of nodes directly or indirectly connected to NODES
  117. according to the NODE-EDGES procedure. NODE-EDGES must be a one-argument
  118. procedure that, given a node, returns its list of direct dependents; it is
  119. typically returned by 'node-edges' or 'node-back-edges'."
  120. (traverse/depth-first cons '() nodes node-edges))
  121. (define (node-reachable-count nodes node-edges)
  122. "Return the number of nodes reachable from NODES along NODE-EDGES."
  123. (traverse/depth-first (lambda (_ count)
  124. (+ 1 count))
  125. 0
  126. nodes node-edges))
  127. ;;;
  128. ;;; Graphviz export.
  129. ;;;
  130. (define-record-type <graph-backend>
  131. (graph-backend name description prologue epilogue node edge)
  132. graph-backend?
  133. (name graph-backend-name)
  134. (description graph-backend-description)
  135. (prologue graph-backend-prologue)
  136. (epilogue graph-backend-epilogue)
  137. (node graph-backend-node)
  138. (edge graph-backend-edge))
  139. (define %colors
  140. ;; See colortbl.h in Graphviz.
  141. #("red" "magenta" "blue" "cyan3" "darkseagreen"
  142. "peachpuff4" "darkviolet" "dimgrey" "darkgoldenrod"))
  143. (define (pop-color hint)
  144. "Return a Graphviz color based on HINT, an arbitrary object."
  145. (let ((index (hash hint (vector-length %colors))))
  146. (vector-ref %colors index)))
  147. (define (emit-prologue name port)
  148. (format port "digraph \"Guix ~a\" {\n"
  149. name))
  150. (define (emit-epilogue port)
  151. (display "\n}\n" port))
  152. (define (emit-node id label port)
  153. (format port " \"~a\" [label = \"~a\", shape = box, fontname = Helvetica];~%"
  154. id label))
  155. (define (emit-edge id1 id2 port)
  156. (format port " \"~a\" -> \"~a\" [color = ~a];~%"
  157. id1 id2 (pop-color id1)))
  158. (define %graphviz-backend
  159. (graph-backend "graphviz"
  160. "Generate graph in DOT format for use with Graphviz."
  161. emit-prologue emit-epilogue
  162. emit-node emit-edge))
  163. ;;;
  164. ;;; d3js export.
  165. ;;;
  166. (define (emit-d3js-prologue name port)
  167. (format port "\
  168. <!DOCTYPE html>
  169. <html>
  170. <head>
  171. <meta charset=\"utf-8\">
  172. <style>
  173. text {
  174. font: 10px sans-serif;
  175. pointer-events: none;
  176. }
  177. </style>
  178. <script type=\"text/javascript\" src=\"~a\"></script>
  179. </head>
  180. <body>
  181. <script type=\"text/javascript\">
  182. var nodes = {},
  183. nodeArray = [],
  184. links = [];
  185. " (search-path %load-path "d3.v3.js")))
  186. (define (emit-d3js-epilogue port)
  187. (format port "</script><script type=\"text/javascript\" src=\"~a\"></script></body></html>"
  188. (search-path %load-path "graph.js")))
  189. (define (emit-d3js-node id label port)
  190. (format port "\
  191. nodes[\"~a\"] = {\"id\": \"~a\", \"label\": \"~a\", \"index\": nodeArray.length};
  192. nodeArray.push(nodes[\"~a\"]);~%"
  193. id id label id))
  194. (define (emit-d3js-edge id1 id2 port)
  195. (format port "links.push({\"source\": \"~a\", \"target\": \"~a\"});~%"
  196. id1 id2))
  197. (define %d3js-backend
  198. (graph-backend "d3js"
  199. "Generate chord diagrams with d3js."
  200. emit-d3js-prologue emit-d3js-epilogue
  201. emit-d3js-node emit-d3js-edge))
  202. ;;;
  203. ;;; Cypher export.
  204. ;;;
  205. (define (emit-cypher-prologue name port)
  206. (format port ""))
  207. (define (emit-cypher-epilogue port)
  208. (format port ""))
  209. (define (emit-cypher-node id label port)
  210. (format port "MERGE (p:Package { id: ~s }) SET p.name = ~s;~%"
  211. id label ))
  212. (define (emit-cypher-edge id1 id2 port)
  213. (format port "MERGE (a:Package { id: ~s });~%" id1)
  214. (format port "MERGE (b:Package { id: ~s });~%" id2)
  215. (format port "MATCH (a:Package { id: ~s }), (b:Package { id: ~s }) CREATE UNIQUE (a)-[:NEEDS]->(b);~%"
  216. id1 id2))
  217. (define %cypher-backend
  218. (graph-backend "cypher"
  219. "Generate Cypher queries."
  220. emit-cypher-prologue emit-cypher-epilogue
  221. emit-cypher-node emit-cypher-edge))
  222. ;;;
  223. ;;; Shared.
  224. ;;;
  225. (define %graph-backends
  226. (list %graphviz-backend
  227. %d3js-backend
  228. %cypher-backend))
  229. (define* (export-graph sinks port
  230. #:key
  231. reverse-edges? node-type
  232. (backend %graphviz-backend))
  233. "Write to PORT the representation of the DAG with the given SINKS, using the
  234. given BACKEND. Use NODE-TYPE to traverse the DAG. When REVERSE-EDGES? is
  235. true, draw reverse arrows."
  236. (match backend
  237. (($ <graph-backend> _ _ emit-prologue emit-epilogue emit-node emit-edge)
  238. (emit-prologue (node-type-name node-type) port)
  239. (match node-type
  240. (($ <node-type> node-identifier node-label node-edges)
  241. (let loop ((nodes sinks)
  242. (visited (set)))
  243. (match nodes
  244. (()
  245. (with-monad %store-monad
  246. (emit-epilogue port)
  247. (store-return #t)))
  248. ((head . tail)
  249. (mlet %store-monad ((id (node-identifier head)))
  250. (if (set-contains? visited id)
  251. (loop tail visited)
  252. (mlet* %store-monad ((dependencies (node-edges head))
  253. (ids (mapm %store-monad
  254. node-identifier
  255. dependencies)))
  256. (emit-node id (node-label head) port)
  257. (for-each (lambda (dependency dependency-id)
  258. (if reverse-edges?
  259. (emit-edge dependency-id id port)
  260. (emit-edge id dependency-id port)))
  261. dependencies ids)
  262. (loop (append dependencies tail)
  263. (set-insert id visited)))))))))))))
  264. ;;; graph.scm ends here