doc: Mention xdot.
[jackhill/guix/guix.git] / guix / scripts / graph.scm
CommitLineData
88856916 1;;; GNU Guix --- Functional package management for GNU
09238d61 2;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
ee9a735b 3;;; Copyright © 2019 Simon Tournier <zimon.toutoune@gmail.com>
88856916
LC
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
20(define-module (guix scripts graph)
21 #:use-module (guix ui)
8fb58371 22 #:use-module (guix graph)
c22a1324 23 #:use-module (guix grafts)
88981dd3 24 #:use-module (guix scripts)
88856916
LC
25 #:use-module (guix packages)
26 #:use-module (guix monads)
27 #:use-module (guix store)
28 #:use-module (guix gexp)
29 #:use-module (guix derivations)
f9704f17 30 #:use-module (guix memoization)
b06a70e0 31 #:use-module (guix modules)
88856916
LC
32 #:use-module ((guix build-system gnu) #:select (standard-packages))
33 #:use-module (gnu packages)
34 #:use-module (guix sets)
b06a70e0 35 #:use-module ((guix utils) #:select (location-file))
3e962e59
LC
36 #:use-module ((guix scripts build)
37 #:select (show-transformation-options-help
38 options->transformation
ee9a735b 39 %standard-build-options
3e962e59 40 %transformation-options))
88856916 41 #:use-module (srfi srfi-1)
38b92daa 42 #:use-module (srfi srfi-26)
88856916 43 #:use-module (srfi srfi-34)
a773c314 44 #:use-module (srfi srfi-35)
88856916
LC
45 #:use-module (srfi srfi-37)
46 #:use-module (ice-9 match)
88856916 47 #:export (%package-node-type
b96a0640 48 %reverse-package-node-type
88856916 49 %bag-node-type
38b92daa 50 %bag-with-origins-node-type
88856916 51 %bag-emerged-node-type
2b81eac0 52 %reverse-bag-node-type
88856916
LC
53 %derivation-node-type
54 %reference-node-type
7f8fec0f 55 %referrer-node-type
b06a70e0 56 %module-node-type
c1a8c5ab
AK
57 %node-types
58
88856916
LC
59 guix-graph))
60
61\f
88856916
LC
62;;;
63;;; Package DAG.
64;;;
65
88856916
LC
66(define (node-full-name thing)
67 "Return a human-readable name to denote THING, a package, origin, or file
68name."
69 (cond ((package? thing)
70 (package-full-name thing))
71 ((origin? thing)
3b4d0103 72 (origin-actual-file-name thing))
88856916
LC
73 ((string? thing) ;file name
74 (or (basename thing)
75 (error "basename" thing)))
76 (else
77 (number->string (object-address thing) 16))))
78
79(define (package-node-edges package)
80 "Return the list of dependencies of PACKAGE."
81 (match (package-direct-inputs package)
82 (((labels packages . outputs) ...)
83 ;; Filter out origins and other non-package dependencies.
84 (filter package? packages))))
85
a773c314
LC
86(define assert-package
87 (match-lambda
88 ((? package? package)
89 package)
90 (x
91 (raise
92 (condition
93 (&message
69daee23 94 (message (format #f (G_ "~a: invalid argument (package name expected)")
a773c314
LC
95 x))))))))
96
97(define nodes-from-package
98 ;; The default conversion method.
99 (lift1 (compose list assert-package) %store-monad))
100
88856916
LC
101(define %package-node-type
102 ;; Type for the traversal of package nodes.
103 (node-type
104 (name "package")
105 (description "the DAG of packages, excluding implicit inputs")
a773c314 106 (convert nodes-from-package)
88856916
LC
107
108 ;; We use package addresses as unique identifiers. This generally works
109 ;; well, but for generated package objects, we could end up with two
110 ;; packages that are not 'eq?', yet map to the same derivation (XXX).
111 (identifier (lift1 object-address %store-monad))
112 (label node-full-name)
113 (edges (lift1 package-node-edges %store-monad))))
114
115\f
b96a0640
LC
116;;;
117;;; Reverse package DAG.
118;;;
119
a53ecfc8
LC
120(define (all-packages) ;XXX: duplicated from (guix scripts refresh)
121 "Return the list of all the distro's packages."
122 (fold-packages (lambda (package result)
123 ;; Ignore deprecated packages.
124 (if (package-superseded package)
125 result
126 (cons package result)))
127 '()
128 #:select? (const #t))) ;include hidden packages
129
b96a0640
LC
130(define %reverse-package-node-type
131 ;; For this node type we first need to compute the list of packages and the
132 ;; list of back-edges. Since we want to do it only once, we use the
133 ;; promises below.
a53ecfc8 134 (let* ((packages (delay (all-packages)))
b96a0640
LC
135 (back-edges (delay (run-with-store #f ;store not actually needed
136 (node-back-edges %package-node-type
137 (force packages))))))
138 (node-type
139 (inherit %package-node-type)
140 (name "reverse-package")
141 (description "the reverse DAG of packages")
142 (edges (lift1 (force back-edges) %store-monad)))))
143
144\f
88856916
LC
145;;;
146;;; Package DAG using bags.
147;;;
148
149(define (bag-node-identifier thing)
150 "Return a unique identifier for THING, which may be a package, origin, or a
151file name."
152 ;; If THING is a file name (a string), we just return it; if it's a package
153 ;; or origin, we return its address. That gives us the object graph, but
154 ;; that may differ from the derivation graph (for instance,
155 ;; 'package-with-bootstrap-guile' generates fresh package objects, and
156 ;; several packages that are not 'eq?' may actually map to the same
157 ;; derivation.) Thus, we lower THING and use its derivation file name as a
158 ;; unique identifier.
159 (with-monad %store-monad
160 (if (string? thing)
161 (return thing)
162 (mlet %store-monad ((low (lower-object thing)))
163 (return (if (derivation? low)
164 (derivation-file-name low)
165 low))))))
166
167(define (bag-node-edges thing)
38b92daa
LC
168 "Return the list of dependencies of THING, a package or origin.
169Dependencies may include packages, origin, and file names."
170 (cond ((package? thing)
171 (match (bag-direct-inputs (package->bag thing))
172 (((labels things . outputs) ...)
173 things)))
174 ((origin? thing)
51385362 175 (cons (or (origin-patch-guile thing) (default-guile))
38b92daa
LC
176 (if (or (pair? (origin-patches thing))
177 (origin-snippet thing))
178 (match (origin-patch-inputs thing)
179 (#f '())
180 (((labels dependencies _ ...) ...)
181 (delete-duplicates dependencies eq?)))
182 '())))
183 (else
184 '())))
88856916
LC
185
186(define %bag-node-type
187 ;; Type for the traversal of package nodes via the "bag" representation,
188 ;; which includes implicit inputs.
189 (node-type
190 (name "bag")
191 (description "the DAG of packages, including implicit inputs")
a773c314 192 (convert nodes-from-package)
88856916
LC
193 (identifier bag-node-identifier)
194 (label node-full-name)
38b92daa
LC
195 (edges (lift1 (compose (cut filter package? <>) bag-node-edges)
196 %store-monad))))
197
198(define %bag-with-origins-node-type
199 (node-type
200 (name "bag-with-origins")
201 (description "the DAG of packages and origins, including implicit inputs")
a773c314 202 (convert nodes-from-package)
38b92daa
LC
203 (identifier bag-node-identifier)
204 (label node-full-name)
205 (edges (lift1 (lambda (thing)
206 (filter (match-lambda
207 ((? package?) #t)
208 ((? origin?) #t)
209 (_ #f))
210 (bag-node-edges thing)))
211 %store-monad))))
88856916
LC
212
213(define standard-package-set
55b2d921
LC
214 (mlambda ()
215 "Return the set of standard packages provided by GNU-BUILD-SYSTEM."
216 (match (standard-packages)
217 (((labels packages . output) ...)
218 (list->setq packages)))))
88856916
LC
219
220(define (bag-node-edges-sans-bootstrap thing)
221 "Like 'bag-node-edges', but pretend that the standard packages of
222GNU-BUILD-SYSTEM have zero dependencies."
223 (if (set-contains? (standard-package-set) thing)
224 '()
225 (bag-node-edges thing)))
226
227(define %bag-emerged-node-type
228 ;; Like %BAG-NODE-TYPE, but without the bootstrap subset of the DAG.
229 (node-type
230 (name "bag-emerged")
231 (description "same as 'bag', but without the bootstrap nodes")
a773c314 232 (convert nodes-from-package)
88856916
LC
233 (identifier bag-node-identifier)
234 (label node-full-name)
f88282af
LC
235 (edges (lift1 (compose (cut filter package? <>)
236 bag-node-edges-sans-bootstrap)
237 %store-monad))))
88856916 238
2b81eac0
LC
239(define %reverse-bag-node-type
240 ;; Type for the reverse traversal of package nodes via the "bag"
241 ;; representation, which includes implicit inputs.
a53ecfc8 242 (let* ((packages (delay (package-closure (all-packages))))
2b81eac0
LC
243 (back-edges (delay (run-with-store #f ;store not actually needed
244 (node-back-edges %bag-node-type
245 (force packages))))))
246 (node-type
247 (name "reverse-bag")
248 (description "the reverse DAG of packages, including implicit inputs")
249 (convert nodes-from-package)
250 (identifier bag-node-identifier)
251 (label node-full-name)
252 (edges (lift1 (force back-edges) %store-monad)))))
253
88856916
LC
254\f
255;;;
256;;; Derivation DAG.
257;;;
258
88856916
LC
259(define (derivation-dependencies obj)
260 "Return the <derivation> objects and store items corresponding to the
261dependencies of OBJ, a <derivation> or store item."
262 (if (derivation? obj)
a2500619 263 (append (map derivation-input-derivation (derivation-inputs obj))
88856916
LC
264 (derivation-sources obj))
265 '()))
266
267(define (derivation-node-identifier node)
268 "Return a unique identifier for NODE, which may be either a <derivation> or
269a plain store file."
270 (if (derivation? node)
271 (derivation-file-name node)
272 node))
273
274(define (derivation-node-label node)
275 "Return a label for NODE, a <derivation> object or plain store item."
276 (store-path-package-name (match node
277 ((? derivation? drv)
278 (derivation-file-name drv))
279 ((? string? file)
280 file))))
281
282(define %derivation-node-type
283 ;; DAG of derivations. Very accurate, very detailed, but usually too much
284 ;; detailed.
285 (node-type
286 (name "derivation")
287 (description "the DAG of derivations")
a773c314
LC
288 (convert (match-lambda
289 ((? package? package)
290 (with-monad %store-monad
291 (>>= (package->derivation package)
292 (lift1 list %store-monad))))
293 ((? derivation-path? item)
294 (mbegin %store-monad
295 ((store-lift add-temp-root) item)
015f17e8 296 (return (list (read-derivation-from-file item)))))
a773c314
LC
297 (x
298 (raise
299 (condition (&message (message "unsupported argument for \
300derivation graph")))))))
88856916
LC
301 (identifier (lift1 derivation-node-identifier %store-monad))
302 (label derivation-node-label)
303 (edges (lift1 derivation-dependencies %store-monad))))
304
305\f
306;;;
307;;; DAG of residual references (aka. run-time dependencies).
308;;;
309
7f8fec0f
LC
310(define ensure-store-items
311 ;; Return a list of store items as a monadic value based on the given
312 ;; argument, which may be a store item or a package.
313 (match-lambda
314 ((? package? package)
315 ;; Return the output file names of PACKAGE.
316 (mlet %store-monad ((drv (package->derivation package)))
317 (return (match (derivation->output-paths drv)
318 (((_ . file-names) ...)
319 file-names)))))
320 ((? store-path? item)
321 (with-monad %store-monad
322 (return (list item))))
323 (x
324 (raise
325 (condition (&message (message "unsupported argument for \
326this type of graph")))))))
327
88856916
LC
328(define (references* item)
329 "Return as a monadic value the references of ITEM, based either on the
330information available in the local store or using information about
331substitutes."
332 (lambda (store)
f9e8a123 333 (guard (c ((store-protocol-error? c)
88856916
LC
334 (match (substitutable-path-info store (list item))
335 ((info)
336 (values (substitutable-references info) store))
337 (()
69daee23 338 (leave (G_ "references for '~a' are not known~%")
88856916
LC
339 item)))))
340 (values (references store item) store))))
341
342(define %reference-node-type
343 (node-type
344 (name "references")
345 (description "the DAG of run-time dependencies (store references)")
7f8fec0f 346 (convert ensure-store-items)
88856916
LC
347 (identifier (lift1 identity %store-monad))
348 (label store-path-package-name)
349 (edges references*)))
350
7f8fec0f
LC
351(define non-derivation-referrers
352 (let ((referrers (store-lift referrers)))
353 (lambda (item)
354 "Return the referrers of ITEM, except '.drv' files."
355 (mlet %store-monad ((items (referrers item)))
356 (return (remove derivation-path? items))))))
357
358(define %referrer-node-type
359 (node-type
360 (name "referrers")
361 (description "the DAG of referrers in the store")
362 (convert ensure-store-items)
363 (identifier (lift1 identity %store-monad))
364 (label store-path-package-name)
365 (edges non-derivation-referrers)))
366
88856916 367\f
b06a70e0
LC
368;;;
369;;; Scheme modules.
370;;;
371
372(define (module-from-package package)
373 (file-name->module-name (location-file (package-location package))))
374
375(define (source-module-dependencies* module)
376 "Like 'source-module-dependencies' but filter out modules that are not
377package modules, while attempting to retain user package modules."
378 (remove (match-lambda
379 (('guix _ ...) #t)
380 (('system _ ...) #t)
381 (('language _ ...) #t)
382 (('ice-9 _ ...) #t)
383 (('srfi _ ...) #t)
384 (_ #f))
385 (source-module-dependencies module)))
386
387(define %module-node-type
388 ;; Show the graph of package modules.
389 (node-type
390 (name "module")
391 (description "the graph of package modules")
392 (convert (lift1 (compose list module-from-package) %store-monad))
393 (identifier (lift1 identity %store-monad))
394 (label object->string)
395 (edges (lift1 source-module-dependencies* %store-monad))))
396
397\f
88856916
LC
398;;;
399;;; List of node types.
400;;;
401
402(define %node-types
403 ;; List of all the node types.
404 (list %package-node-type
b96a0640 405 %reverse-package-node-type
88856916 406 %bag-node-type
38b92daa 407 %bag-with-origins-node-type
88856916 408 %bag-emerged-node-type
2b81eac0 409 %reverse-bag-node-type
88856916 410 %derivation-node-type
7f8fec0f 411 %reference-node-type
b06a70e0
LC
412 %referrer-node-type
413 %module-node-type))
88856916
LC
414
415(define (lookup-node-type name)
416 "Return the node type called NAME. Raise an error if it is not found."
417 (or (find (lambda (type)
418 (string=? (node-type-name type) name))
419 %node-types)
69daee23 420 (leave (G_ "~a: unknown node type~%") name)))
88856916 421
642339dc
RW
422(define (lookup-backend name)
423 "Return the graph backend called NAME. Raise an error if it is not found."
424 (or (find (lambda (backend)
425 (string=? (graph-backend-name backend) name))
426 %graph-backends)
69daee23 427 (leave (G_ "~a: unknown backend~%") name)))
642339dc 428
88856916
LC
429(define (list-node-types)
430 "Print the available node types along with their synopsis."
69daee23 431 (display (G_ "The available node types are:\n"))
88856916
LC
432 (newline)
433 (for-each (lambda (type)
434 (format #t " - ~a: ~a~%"
435 (node-type-name type)
436 (node-type-description type)))
437 %node-types))
438
642339dc
RW
439(define (list-backends)
440 "Print the available backends along with their synopsis."
69daee23 441 (display (G_ "The available backend types are:\n"))
642339dc
RW
442 (newline)
443 (for-each (lambda (backend)
444 (format #t " - ~a: ~a~%"
445 (graph-backend-name backend)
446 (graph-backend-description backend)))
447 %graph-backends))
448
88856916 449\f
88856916
LC
450;;;
451;;; Command-line options.
452;;;
453
454(define %options
3e962e59
LC
455 (cons* (option '(#\t "type") #t #f
456 (lambda (opt name arg result)
457 (alist-cons 'node-type (lookup-node-type arg)
458 result)))
459 (option '("list-types") #f #f
460 (lambda (opt name arg result)
461 (list-node-types)
462 (exit 0)))
463 (option '(#\b "backend") #t #f
464 (lambda (opt name arg result)
465 (alist-cons 'backend (lookup-backend arg)
466 result)))
467 (option '("list-backends") #f #f
468 (lambda (opt name arg result)
469 (list-backends)
470 (exit 0)))
471 (option '(#\e "expression") #t #f
472 (lambda (opt name arg result)
473 (alist-cons 'expression arg result)))
474 (option '(#\s "system") #t #f
475 (lambda (opt name arg result)
476 (alist-cons 'system arg
477 (alist-delete 'system result eq?))))
ee9a735b
PN
478 (find (lambda (option)
479 (member "load-path" (option-names option)))
480 %standard-build-options)
3e962e59
LC
481 (option '(#\h "help") #f #f
482 (lambda args
483 (show-help)
484 (exit 0)))
485 (option '(#\V "version") #f #f
486 (lambda args
487 (show-version-and-exit "guix graph")))
488
489 %transformation-options))
88856916
LC
490
491(define (show-help)
492 ;; TRANSLATORS: Here 'dot' is the name of a program; it must not be
493 ;; translated.
69daee23 494 (display (G_ "Usage: guix graph PACKAGE...
34b1f339 495Emit a representation of the dependency graph of PACKAGE...\n"))
69daee23 496 (display (G_ "
642339dc 497 -b, --backend=TYPE produce a graph with the given backend TYPE"))
69daee23 498 (display (G_ "
642339dc 499 --list-backends list the available graph backends"))
69daee23 500 (display (G_ "
88856916 501 -t, --type=TYPE represent nodes of the given TYPE"))
69daee23 502 (display (G_ "
88856916 503 --list-types list the available graph types"))
69daee23 504 (display (G_ "
4c8f997a 505 -e, --expression=EXPR consider the package EXPR evaluates to"))
ebbfc59c
LC
506 (display (G_ "
507 -s, --system=SYSTEM consider the graph for SYSTEM--e.g., \"i686-linux\""))
88856916 508 (newline)
ee9a735b
PN
509 (display (G_ "
510 -L, --load-path=DIR prepend DIR to the package module search path"))
511 (newline)
3e962e59
LC
512 (show-transformation-options-help)
513 (newline)
69daee23 514 (display (G_ "
88856916 515 -h, --help display this help and exit"))
69daee23 516 (display (G_ "
88856916
LC
517 -V, --version display version information and exit"))
518 (newline)
519 (show-bug-report-information))
520
521(define %default-options
642339dc 522 `((node-type . ,%package-node-type)
ebbfc59c
LC
523 (backend . ,%graphviz-backend)
524 (system . ,(%current-system))))
88856916
LC
525
526\f
527;;;
528;;; Entry point.
529;;;
530
531(define (guix-graph . args)
532 (with-error-handling
3e962e59
LC
533 (define opts
534 (parse-command-line args %options
535 (list %default-options)
536 #:build-options? #f))
537 (define backend
538 (assoc-ref opts 'backend))
539 (define type
540 (assoc-ref opts 'node-type))
541
542 (with-store store
543 (let* ((transform (options->transformation opts))
544 (items (filter-map (match-lambda
545 (('argument . (? store-path? item))
546 item)
547 (('argument . spec)
548 (transform store
549 (specification->package spec)))
550 (('expression . exp)
551 (transform store
552 (read/eval-package-expression exp)))
553 (_ #f))
554 opts)))
09238d61
LC
555 (run-with-store store
556 ;; XXX: Since grafting can trigger unsolicited builds, disable it.
557 (mlet %store-monad ((_ (set-grafting #f))
558 (nodes (mapm %store-monad
559 (node-type-convert type)
560 items)))
561 (export-graph (concatenate nodes)
562 (current-output-port)
563 #:node-type type
564 #:backend backend))
565 #:system (assq-ref opts 'system)))))
88856916
LC
566 #t)
567
568;;; graph.scm ends here