describe.scm 12 KB

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