graph.scm 22 KB

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