describe.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com>
  4. ;;; Copyright © 2020 Ekaitz Zarraga <ekaitz@elenq.tech>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix scripts describe)
  21. #:use-module ((guix config) #:select (%guix-version))
  22. #:use-module ((guix ui) #:hide (display-profile-content))
  23. #:use-module ((guix utils) #:select (string-replace-substring))
  24. #:use-module (guix channels)
  25. #:use-module (guix scripts)
  26. #:use-module (guix describe)
  27. #:use-module (guix profiles)
  28. #:autoload (guix openpgp) (openpgp-format-fingerprint)
  29. #:use-module (git)
  30. #:autoload (json builder) (scm->json-string)
  31. #:use-module (srfi srfi-1)
  32. #:use-module (srfi srfi-26)
  33. #:use-module (srfi srfi-37)
  34. #:use-module (ice-9 match)
  35. #:use-module (ice-9 format)
  36. #:autoload (ice-9 pretty-print) (pretty-print)
  37. #:use-module (web uri)
  38. #:export (display-profile-content
  39. channel-commit-hyperlink
  40. guix-describe))
  41. ;;;
  42. ;;; Command-line options.
  43. ;;;
  44. (define %available-formats
  45. '("human" "channels" "channels-sans-intro" "json" "recutils"))
  46. (define (list-formats)
  47. (display (G_ "The available formats are:\n"))
  48. (newline)
  49. (for-each (lambda (f)
  50. (format #t " - ~a~%" f))
  51. %available-formats))
  52. (define %options
  53. ;; Specifications of the command-line options.
  54. (list (option '(#\f "format") #t #f
  55. (lambda (opt name arg result)
  56. (unless (member arg %available-formats)
  57. (leave (G_ "~a: unsupported output format~%") arg))
  58. (alist-cons 'format (string->symbol arg) result)))
  59. (option '("list-formats") #f #f
  60. (lambda (opt name arg result)
  61. (list-formats)
  62. (exit 0)))
  63. (option '(#\p "profile") #t #f
  64. (lambda (opt name arg result)
  65. (alist-cons 'profile (canonicalize-profile arg)
  66. result)))
  67. (option '(#\h "help") #f #f
  68. (lambda args
  69. (show-help)
  70. (exit 0)))
  71. (option '(#\V "version") #f #f
  72. (lambda args
  73. (show-version-and-exit "guix describe")))))
  74. (define %default-options
  75. ;; Alist of default option values.
  76. '((format . human)))
  77. (define (show-help)
  78. (display (G_ "Usage: guix describe [OPTION]...
  79. Display information about the channels currently in use.\n"))
  80. (display (G_ "
  81. -f, --format=FORMAT display information in the given FORMAT"))
  82. (display (G_ "
  83. --list-formats display available formats"))
  84. (display (G_ "
  85. -p, --profile=PROFILE display information about PROFILE"))
  86. (newline)
  87. (display (G_ "
  88. -h, --help display this help and exit"))
  89. (display (G_ "
  90. -V, --version display version information and exit"))
  91. (newline)
  92. (show-bug-report-information))
  93. (define (display-package-search-path fmt)
  94. "Display GUIX_PACKAGE_PATH, if it is set, according to FMT."
  95. (match (getenv "GUIX_PACKAGE_PATH")
  96. (#f #t)
  97. (string
  98. (match fmt
  99. ('human
  100. (format #t "~%GUIX_PACKAGE_PATH=\"~a\"~%" string))
  101. ('channels
  102. (format #t (G_ "~%;; warning: GUIX_PACKAGE_PATH=\"~a\"~%")
  103. string))
  104. (_
  105. (warning (G_ "'GUIX_PACKAGE_PATH' is set but it is not captured~%")))))))
  106. (define (channel->json channel)
  107. (scm->json-string
  108. (let ((intro (channel-introduction channel)))
  109. `((name . ,(channel-name channel))
  110. (url . ,(channel-url channel))
  111. (commit . ,(channel-commit channel))
  112. ,@(if intro
  113. `((introduction
  114. . ((commit . ,(channel-introduction-first-signed-commit
  115. intro))
  116. (signer . ,(openpgp-format-fingerprint
  117. (channel-introduction-first-commit-signer
  118. intro))))))
  119. '())))))
  120. (define (channel->recutils channel port)
  121. (define intro
  122. (channel-introduction channel))
  123. (format port "name: ~a~%" (channel-name channel))
  124. (format port "url: ~a~%" (channel-url channel))
  125. (format port "commit: ~a~%" (channel-commit channel))
  126. (when intro
  127. (format port "introductioncommit: ~a~%"
  128. (channel-introduction-first-signed-commit intro))
  129. (format port "introductionsigner: ~a~%"
  130. (openpgp-format-fingerprint
  131. (channel-introduction-first-commit-signer intro)))))
  132. (define (display-checkout-info fmt)
  133. "Display information about the current checkout according to FMT, a symbol
  134. denoting the requested format. Exit if the current directory does not lie
  135. within a Git checkout."
  136. (let* ((program (car (command-line)))
  137. (directory (catch 'git-error
  138. (lambda ()
  139. (repository-discover (dirname program)))
  140. (lambda (key err)
  141. (report-error (G_ "failed to determine origin~%"))
  142. (display-hint (format #f (G_ "Perhaps this
  143. @command{guix} command was not obtained with @command{guix pull}? Its version
  144. string is ~a.~%")
  145. %guix-version))
  146. (exit 1))))
  147. (repository (repository-open directory))
  148. (head (repository-head repository))
  149. (commit (oid->string (reference-target head))))
  150. (match fmt
  151. ('human
  152. (format #t (G_ "Git checkout:~%"))
  153. (format #t (G_ " repository: ~a~%") (dirname directory))
  154. (format #t (G_ " branch: ~a~%") (reference-shorthand head))
  155. (format #t (G_ " commit: ~a~%") commit))
  156. ('channels
  157. (pretty-print `(list ,(channel->code (channel (name 'guix)
  158. (url (dirname directory))
  159. (commit commit))))))
  160. ('json
  161. (display (channel->json (channel (name 'guix)
  162. (url (dirname directory))
  163. (commit commit))))
  164. (newline))
  165. ('recutils
  166. (channel->recutils (channel (name 'guix)
  167. (url (dirname directory))
  168. (commit commit))
  169. (current-output-port))))
  170. (display-package-search-path fmt)))
  171. (define* (display-profile-info profile fmt
  172. #:optional
  173. (channels (profile-channels profile)))
  174. "Display information about PROFILE, a profile as created by (guix channels),
  175. in the format specified by FMT. PROFILE can be #f, in which case CHANNELS is
  176. what matters."
  177. (define number
  178. (and profile (generation-number profile)))
  179. (match fmt
  180. ('human
  181. (display-profile-content profile number channels))
  182. ('channels
  183. (pretty-print `(list ,@(map channel->code channels))))
  184. ('channels-sans-intro
  185. (pretty-print `(list ,@(map (cut channel->code <>
  186. #:include-introduction? #f)
  187. channels))))
  188. ('json
  189. (format #t "[~a]~%" (string-join (map channel->json channels) ",")))
  190. ('recutils
  191. (format #t "~{~a~%~}"
  192. (map (lambda (channel)
  193. (with-output-to-string
  194. (lambda ()
  195. (channel->recutils channel (current-output-port)))))
  196. channels))))
  197. (display-package-search-path fmt))
  198. (define (profile-generation-channels profile number)
  199. "Return the list of channels for generation NUMBER of PROFILE."
  200. (profile-channels (if (zero? number)
  201. profile
  202. (generation-file-name profile number))))
  203. (define* (display-profile-content profile number
  204. #:optional
  205. (channels
  206. (profile-generation-channels profile
  207. number)))
  208. "Display CHANNELS along with PROFILE info, generation NUMBER, in a
  209. human-readable way and displaying details about the channel's source code.
  210. PROFILE and NUMBER "
  211. (when (and number profile)
  212. (display-generation profile number))
  213. (for-each (lambda (channel)
  214. (format #t " ~a ~a~%"
  215. (channel-name channel)
  216. (string-take (channel-commit channel) 7))
  217. (format #t (G_ " repository URL: ~a~%")
  218. (channel-url channel))
  219. (when (channel-branch channel)
  220. (format #t (G_ " branch: ~a~%")
  221. (channel-branch channel)))
  222. (format #t (G_ " commit: ~a~%")
  223. (if (supports-hyperlinks?)
  224. (channel-commit-hyperlink channel)
  225. (channel-commit channel))))
  226. channels))
  227. (define %vcs-web-views
  228. ;; Hard-coded list of host names and corresponding web view URL templates.
  229. ;; TODO: Allow '.guix-channel' files to specify a URL template.
  230. (let ((labhub-url (lambda (repository-url commit)
  231. (string-append
  232. (if (string-suffix? ".git" repository-url)
  233. (string-drop-right repository-url 4)
  234. repository-url)
  235. "/commit/" commit))))
  236. `(("git.savannah.gnu.org"
  237. ,(lambda (repository-url commit)
  238. (string-append (string-replace-substring repository-url
  239. "/git/" "/cgit/")
  240. "/commit/?id=" commit)))
  241. ("notabug.org" ,labhub-url)
  242. ("framagit.org" ,labhub-url)
  243. ("gitlab.com" ,labhub-url)
  244. ("gitlab.inria.fr" ,labhub-url)
  245. ("github.com" ,labhub-url))))
  246. (define* (channel-commit-hyperlink channel
  247. #:optional
  248. (commit (channel-commit channel)))
  249. "Return a hyperlink for COMMIT in CHANNEL, using COMMIT as the hyperlink's
  250. text. The hyperlink links to a web view of COMMIT, when available."
  251. (let* ((url (channel-url channel))
  252. (uri (string->uri url))
  253. (host (and uri (uri-host uri))))
  254. (if host
  255. (match (assoc host %vcs-web-views)
  256. (#f
  257. commit)
  258. ((_ template)
  259. (hyperlink (template url commit) commit)))
  260. commit)))
  261. ;;;
  262. ;;; Entry point.
  263. ;;;
  264. (define-command (guix-describe . args)
  265. (synopsis "describe the channel revisions currently used")
  266. (let* ((opts (args-fold* args %options
  267. (lambda (opt name arg result)
  268. (leave (G_ "~A: unrecognized option~%")
  269. name))
  270. cons
  271. %default-options))
  272. (format (assq-ref opts 'format))
  273. (profile (or (assq-ref opts 'profile) (current-profile))))
  274. (with-error-handling
  275. (match profile
  276. (#f
  277. (match (current-channels)
  278. (()
  279. (display-checkout-info format))
  280. (channels
  281. (display-profile-info #f format channels))))
  282. (profile
  283. (display-profile-info (canonicalize-profile profile) format))))))