describe.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2019, 2020 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. #:use-module (json)
  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->sexp channel #:key (include-introduction? #t))
  107. (let ((intro (and include-introduction?
  108. (channel-introduction channel))))
  109. `(channel
  110. (name ',(channel-name channel))
  111. (url ,(channel-url channel))
  112. (commit ,(channel-commit channel))
  113. ,@(if intro
  114. `((introduction (make-channel-introduction
  115. ,(channel-introduction-first-signed-commit intro)
  116. (openpgp-fingerprint
  117. ,(openpgp-format-fingerprint
  118. (channel-introduction-first-commit-signer
  119. intro))))))
  120. '()))))
  121. (define (channel->json channel)
  122. (scm->json-string
  123. (let ((intro (channel-introduction channel)))
  124. `((name . ,(channel-name channel))
  125. (url . ,(channel-url channel))
  126. (commit . ,(channel-commit channel))
  127. ,@(if intro
  128. `((introduction
  129. . ((commit . ,(channel-introduction-first-signed-commit
  130. intro))
  131. (signer . ,(openpgp-format-fingerprint
  132. (channel-introduction-first-commit-signer
  133. intro))))))
  134. '())))))
  135. (define (channel->recutils channel port)
  136. (define intro
  137. (channel-introduction channel))
  138. (format port "name: ~a~%" (channel-name channel))
  139. (format port "url: ~a~%" (channel-url channel))
  140. (format port "commit: ~a~%" (channel-commit channel))
  141. (when intro
  142. (format port "introductioncommit: ~a~%"
  143. (channel-introduction-first-signed-commit intro))
  144. (format port "introductionsigner: ~a~%"
  145. (openpgp-format-fingerprint
  146. (channel-introduction-first-commit-signer intro)))))
  147. (define (display-checkout-info fmt)
  148. "Display information about the current checkout according to FMT, a symbol
  149. denoting the requested format. Exit if the current directory does not lie
  150. within a Git checkout."
  151. (let* ((program (car (command-line)))
  152. (directory (catch 'git-error
  153. (lambda ()
  154. (repository-discover (dirname program)))
  155. (lambda (key err)
  156. (report-error (G_ "failed to determine origin~%"))
  157. (display-hint (format #f (G_ "Perhaps this
  158. @command{guix} command was not obtained with @command{guix pull}? Its version
  159. string is ~a.~%")
  160. %guix-version))
  161. (exit 1))))
  162. (repository (repository-open directory))
  163. (head (repository-head repository))
  164. (commit (oid->string (reference-target head))))
  165. (match fmt
  166. ('human
  167. (format #t (G_ "Git checkout:~%"))
  168. (format #t (G_ " repository: ~a~%") (dirname directory))
  169. (format #t (G_ " branch: ~a~%") (reference-shorthand head))
  170. (format #t (G_ " commit: ~a~%") commit))
  171. ('channels
  172. (pretty-print `(list ,(channel->sexp (channel (name 'guix)
  173. (url (dirname directory))
  174. (commit commit))))))
  175. ('json
  176. (display (channel->json (channel (name 'guix)
  177. (url (dirname directory))
  178. (commit commit))))
  179. (newline))
  180. ('recutils
  181. (channel->recutils (channel (name 'guix)
  182. (url (dirname directory))
  183. (commit commit))
  184. (current-output-port))))
  185. (display-package-search-path fmt)))
  186. (define (display-profile-info profile fmt)
  187. "Display information about PROFILE, a profile as created by (guix channels),
  188. in the format specified by FMT."
  189. (define number
  190. (generation-number profile))
  191. (define channels
  192. (profile-channels (if (zero? number)
  193. profile
  194. (generation-file-name profile number))))
  195. (match fmt
  196. ('human
  197. (display-profile-content profile number))
  198. ('channels
  199. (pretty-print `(list ,@(map channel->sexp channels))))
  200. ('channels-sans-intro
  201. (pretty-print `(list ,@(map (cut channel->sexp <>
  202. #:include-introduction? #f)
  203. channels))))
  204. ('json
  205. (format #t "[~a]~%" (string-join (map channel->json channels) ",")))
  206. ('recutils
  207. (format #t "~{~a~%~}"
  208. (map (lambda (channel)
  209. (with-output-to-string
  210. (lambda ()
  211. (channel->recutils channel (current-output-port)))))
  212. channels))))
  213. (display-package-search-path fmt))
  214. (define (display-profile-content profile number)
  215. "Display the packages in PROFILE, generation NUMBER, in a human-readable
  216. way and displaying details about the channel's source code."
  217. (display-generation profile number)
  218. (for-each (lambda (entry)
  219. (format #t " ~a ~a~%"
  220. (manifest-entry-name entry)
  221. (manifest-entry-version entry))
  222. (match (assq 'source (manifest-entry-properties entry))
  223. (('source ('repository ('version 0)
  224. ('url url)
  225. ('branch branch)
  226. ('commit commit)
  227. _ ...))
  228. (let ((channel (channel (name 'nameless)
  229. (url url)
  230. (branch branch)
  231. (commit commit))))
  232. (format #t (G_ " repository URL: ~a~%") url)
  233. (when branch
  234. (format #t (G_ " branch: ~a~%") branch))
  235. (format #t (G_ " commit: ~a~%")
  236. (if (supports-hyperlinks?)
  237. (channel-commit-hyperlink channel commit)
  238. commit))))
  239. (_ #f)))
  240. ;; Show most recently installed packages last.
  241. (reverse
  242. (manifest-entries
  243. (profile-manifest (if (zero? number)
  244. profile
  245. (generation-file-name profile number)))))))
  246. (define %vcs-web-views
  247. ;; Hard-coded list of host names and corresponding web view URL templates.
  248. ;; TODO: Allow '.guix-channel' files to specify a URL template.
  249. (let ((labhub-url (lambda (repository-url commit)
  250. (string-append
  251. (if (string-suffix? ".git" repository-url)
  252. (string-drop-right repository-url 4)
  253. repository-url)
  254. "/commit/" commit))))
  255. `(("git.savannah.gnu.org"
  256. ,(lambda (repository-url commit)
  257. (string-append (string-replace-substring repository-url
  258. "/git/" "/cgit/")
  259. "/commit/?id=" commit)))
  260. ("notabug.org" ,labhub-url)
  261. ("framagit.org" ,labhub-url)
  262. ("gitlab.com" ,labhub-url)
  263. ("gitlab.inria.fr" ,labhub-url)
  264. ("github.com" ,labhub-url))))
  265. (define* (channel-commit-hyperlink channel
  266. #:optional
  267. (commit (channel-commit channel)))
  268. "Return a hyperlink for COMMIT in CHANNEL, using COMMIT as the hyperlink's
  269. text. The hyperlink links to a web view of COMMIT, when available."
  270. (let* ((url (channel-url channel))
  271. (uri (string->uri url))
  272. (host (and uri (uri-host uri))))
  273. (if host
  274. (match (assoc host %vcs-web-views)
  275. (#f
  276. commit)
  277. ((_ template)
  278. (hyperlink (template url commit) commit)))
  279. commit)))
  280. ;;;
  281. ;;; Entry point.
  282. ;;;
  283. (define-command (guix-describe . args)
  284. (synopsis "describe the channel revisions currently used")
  285. (let* ((opts (args-fold* args %options
  286. (lambda (opt name arg result)
  287. (leave (G_ "~A: unrecognized option~%")
  288. name))
  289. cons
  290. %default-options))
  291. (format (assq-ref opts 'format))
  292. (profile (or (assq-ref opts 'profile) (current-profile))))
  293. (with-error-handling
  294. (match profile
  295. (#f
  296. (display-checkout-info format))
  297. (profile
  298. (display-profile-info (canonicalize-profile profile) format))))))