graph: Add '--max-depth'.

* guix/graph.scm (export-graph): Add #:max-depth and honor it, adding
'depths' argument to 'loop'.
* guix/scripts/graph.scm (%options, show-help): Add '--max-depth'.
(%default-options): Add 'max-depth'.
(guix-graph): Pass #:max-depth to 'export-graph'.
* tests/graph.scm ("package DAG, limited depth"): New test.
* doc/guix.texi (Invoking guix graph): Document it.
This commit is contained in:
Ludovic Courtès 2021-09-17 10:13:15 +02:00 committed by Ludovic Courtès
parent be32889902
commit 5b32ad4f6f
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
4 changed files with 72 additions and 19 deletions

View file

@ -12644,6 +12644,20 @@ $ guix graph --path -t references emacs libunistring
/gnu/store/@dots{}-libunistring-0.9.10
@end example
Sometimes you still want to visualize the graph but would like to trim
it so it can actually be displayed. One way to do it is via the
@option{--max-depth} (or @option{-M}) option, which lets you specify the
maximum depth of the graph. In the example below, we visualize only
@code{libreoffice} and the nodes whose distance to @code{libreoffice} is
at most 2:
@example
guix graph -M 2 libreoffice | xdot -f fdp -
@end example
Mind you, that's still a big ball of spaghetti, but at least
@command{dot} can render it quickly and it can be browsed somewhat.
The available options are the following:
@table @option

View file

@ -337,11 +337,12 @@ (define %graph-backends
(define* (export-graph sinks port
#:key
reverse-edges? node-type
reverse-edges? node-type (max-depth +inf.0)
(backend %graphviz-backend))
"Write to PORT the representation of the DAG with the given SINKS, using the
given BACKEND. Use NODE-TYPE to traverse the DAG. When REVERSE-EDGES? is
true, draw reverse arrows."
true, draw reverse arrows. Do not represent nodes whose distance to one of
the SINKS is greater than MAX-DEPTH."
(match backend
(($ <graph-backend> _ _ emit-prologue emit-epilogue emit-node emit-edge)
(emit-prologue (node-type-name node-type) port)
@ -349,6 +350,7 @@ (define* (export-graph sinks port
(match node-type
(($ <node-type> node-identifier node-label node-edges)
(let loop ((nodes sinks)
(depths (make-list (length sinks) 0))
(visited (set)))
(match nodes
(()
@ -356,20 +358,29 @@ (define* (export-graph sinks port
(emit-epilogue port)
(store-return #t)))
((head . tail)
(mlet %store-monad ((id (node-identifier head)))
(if (set-contains? visited id)
(loop tail visited)
(mlet* %store-monad ((dependencies (node-edges head))
(ids (mapm %store-monad
node-identifier
dependencies)))
(emit-node id (node-label head) port)
(for-each (lambda (dependency dependency-id)
(if reverse-edges?
(emit-edge dependency-id id port)
(emit-edge id dependency-id port)))
dependencies ids)
(loop (append dependencies tail)
(set-insert id visited)))))))))))))
(match depths
((depth . depths)
(mlet %store-monad ((id (node-identifier head)))
(if (set-contains? visited id)
(loop tail depths visited)
(mlet* %store-monad ((dependencies
(if (= depth max-depth)
(return '())
(node-edges head)))
(ids
(mapm %store-monad
node-identifier
dependencies)))
(emit-node id (node-label head) port)
(for-each (lambda (dependency dependency-id)
(if reverse-edges?
(emit-edge dependency-id id port)
(emit-edge id dependency-id port)))
dependencies ids)
(loop (append dependencies tail)
(append (make-list (length dependencies)
(+ 1 depth))
depths)
(set-insert id visited)))))))))))))))
;;; graph.scm ends here

View file

@ -500,6 +500,10 @@ (define %options
(lambda (opt name arg result)
(alist-cons 'backend (lookup-backend arg)
result)))
(option '(#\M "max-depth") #t #f
(lambda (opt name arg result)
(alist-cons 'max-depth (string->number* arg)
result)))
(option '("list-backends") #f #f
(lambda (opt name arg result)
(list-backends)
@ -537,6 +541,8 @@ (define (show-help)
-t, --type=TYPE represent nodes of the given TYPE"))
(display (G_ "
--list-types list the available graph types"))
(display (G_ "
--max-depth=DEPTH limit to nodes within distance DEPTH"))
(display (G_ "
--path display the shortest path between the given nodes"))
(display (G_ "
@ -559,6 +565,7 @@ (define (show-help)
(define %default-options
`((node-type . ,%package-node-type)
(backend . ,%graphviz-backend)
(max-depth . +inf.0)
(system . ,(%current-system))))
@ -582,6 +589,7 @@ (define type
(with-store store
(let* ((transform (options->transformation opts))
(max-depth (assoc-ref opts 'max-depth))
(items (filter-map (match-lambda
(('argument . (? store-path? item))
item)
@ -613,7 +621,8 @@ (define type
(export-graph (concatenate nodes)
(current-output-port)
#:node-type type
#:backend backend)))
#:backend backend
#:max-depth max-depth)))
#:system (assq-ref opts 'system)))))
#t)

View file

@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@ -94,6 +94,25 @@ (define (edge->tuple source target)
(list p3 p3 p2)
(list p2 p1 p1))))))))
(test-assert "package DAG, limited depth"
(let-values (((backend nodes+edges) (make-recording-backend)))
(let* ((p1 (dummy-package "p1"))
(p2 (dummy-package "p2" (inputs `(("p1" ,p1)))))
(p3 (dummy-package "p3" (inputs `(("p1" ,p1)))))
(p4 (dummy-package "p4" (inputs `(("p2" ,p2) ("p3" ,p3))))))
(run-with-store %store
(export-graph (list p4) 'port
#:max-depth 1
#:node-type %package-node-type
#:backend backend))
;; We should see nothing more than these 3 packages.
(let-values (((nodes edges) (nodes+edges)))
(and (equal? nodes (map package->tuple (list p4 p2 p3)))
(equal? edges
(map edge->tuple
(list p4 p4)
(list p2 p3))))))))
(test-assert "reverse package DAG"
(let-values (((backend nodes+edges) (make-recording-backend)))
(run-with-store %store