Commit | Line | Data |
---|---|---|
bd747018 | 1 | ;;; GNU Guix --- Functional package management for GNU |
1d88470e | 2 | ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org> |
8548f995 | 3 | ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com> |
9a27d84b | 4 | ;;; Copyright © 2020 Ekaitz Zarraga <ekaitz@elenq.tech> |
bd747018 LC |
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 | ||
21 | (define-module (guix scripts describe) | |
06633026 | 22 | #:use-module ((guix config) #:select (%guix-version)) |
bd747018 | 23 | #:use-module ((guix ui) #:hide (display-profile-content)) |
1d88470e | 24 | #:use-module ((guix utils) #:select (string-replace-substring)) |
8548f995 | 25 | #:use-module (guix channels) |
bd747018 LC |
26 | #:use-module (guix scripts) |
27 | #:use-module (guix describe) | |
28 | #:use-module (guix profiles) | |
6d39f0cb | 29 | #:autoload (guix openpgp) (openpgp-format-fingerprint) |
bd747018 | 30 | #:use-module (git) |
81a40ee0 | 31 | #:use-module (json) |
bd747018 | 32 | #:use-module (srfi srfi-1) |
6d39f0cb | 33 | #:use-module (srfi srfi-26) |
bd747018 LC |
34 | #:use-module (srfi srfi-37) |
35 | #:use-module (ice-9 match) | |
25b267af | 36 | #:use-module (ice-9 format) |
bd747018 | 37 | #:autoload (ice-9 pretty-print) (pretty-print) |
1d88470e LC |
38 | #:use-module (web uri) |
39 | #:export (display-profile-content | |
40 | channel-commit-hyperlink | |
41 | ||
42 | guix-describe)) | |
bd747018 LC |
43 | |
44 | \f | |
45 | ;;; | |
46 | ;;; Command-line options. | |
47 | ;;; | |
6d39f0cb LC |
48 | (define %available-formats |
49 | '("human" "channels" "channels-sans-intro" "json" "recutils")) | |
9a27d84b EZ |
50 | |
51 | (define (list-formats) | |
52 | (display (G_ "The available formats are:\n")) | |
53 | (newline) | |
54 | (for-each (lambda (f) | |
55 | (format #t " - ~a~%" f)) | |
56 | %available-formats)) | |
bd747018 LC |
57 | |
58 | (define %options | |
59 | ;; Specifications of the command-line options. | |
60 | (list (option '(#\f "format") #t #f | |
61 | (lambda (opt name arg result) | |
9a27d84b | 62 | (unless (member arg %available-formats) |
bd747018 | 63 | (leave (G_ "~a: unsupported output format~%") arg)) |
3dd28aa3 | 64 | (alist-cons 'format (string->symbol arg) result))) |
9a27d84b EZ |
65 | (option '("list-formats") #f #f |
66 | (lambda (opt name arg result) | |
67 | (list-formats) | |
68 | (exit 0))) | |
1255400f OP |
69 | (option '(#\p "profile") #t #f |
70 | (lambda (opt name arg result) | |
71 | (alist-cons 'profile (canonicalize-profile arg) | |
72 | result))) | |
bd747018 LC |
73 | (option '(#\h "help") #f #f |
74 | (lambda args | |
75 | (show-help) | |
76 | (exit 0))) | |
77 | (option '(#\V "version") #f #f | |
78 | (lambda args | |
79 | (show-version-and-exit "guix describe"))))) | |
80 | ||
81 | (define %default-options | |
82 | ;; Alist of default option values. | |
83 | '((format . human))) | |
84 | ||
85 | (define (show-help) | |
86 | (display (G_ "Usage: guix describe [OPTION]... | |
87 | Display information about the channels currently in use.\n")) | |
88 | (display (G_ " | |
89 | -f, --format=FORMAT display information in the given FORMAT")) | |
9a27d84b EZ |
90 | (display (G_ " |
91 | --list-formats display available formats")) | |
1255400f OP |
92 | (display (G_ " |
93 | -p, --profile=PROFILE display information about PROFILE")) | |
bd747018 LC |
94 | (newline) |
95 | (display (G_ " | |
96 | -h, --help display this help and exit")) | |
97 | (display (G_ " | |
98 | -V, --version display version information and exit")) | |
99 | (newline) | |
100 | (show-bug-report-information)) | |
101 | ||
102 | (define (display-package-search-path fmt) | |
103 | "Display GUIX_PACKAGE_PATH, if it is set, according to FMT." | |
104 | (match (getenv "GUIX_PACKAGE_PATH") | |
105 | (#f #t) | |
106 | (string | |
107 | (match fmt | |
108 | ('human | |
109 | (format #t "~%GUIX_PACKAGE_PATH=\"~a\"~%" string)) | |
110 | ('channels | |
111 | (format #t (G_ "~%;; warning: GUIX_PACKAGE_PATH=\"~a\"~%") | |
b6fd086a LC |
112 | string)) |
113 | (_ | |
114 | (warning (G_ "'GUIX_PACKAGE_PATH' is set but it is not captured~%"))))))) | |
bd747018 | 115 | |
6d39f0cb LC |
116 | (define* (channel->sexp channel #:key (include-introduction? #t)) |
117 | (let ((intro (and include-introduction? | |
118 | (channel-introduction channel)))) | |
119 | `(channel | |
120 | (name ',(channel-name channel)) | |
121 | (url ,(channel-url channel)) | |
122 | (commit ,(channel-commit channel)) | |
123 | ,@(if intro | |
124 | `((introduction (make-channel-introduction | |
125 | ,(channel-introduction-first-signed-commit intro) | |
126 | (openpgp-fingerprint | |
127 | ,(openpgp-format-fingerprint | |
128 | (channel-introduction-first-commit-signer | |
129 | intro)))))) | |
130 | '())))) | |
8548f995 | 131 | |
81a40ee0 | 132 | (define (channel->json channel) |
6d39f0cb LC |
133 | (scm->json-string |
134 | (let ((intro (channel-introduction channel))) | |
135 | `((name . ,(channel-name channel)) | |
136 | (url . ,(channel-url channel)) | |
137 | (commit . ,(channel-commit channel)) | |
138 | ,@(if intro | |
139 | `((introduction | |
140 | . ((commit . ,(channel-introduction-first-signed-commit | |
141 | intro)) | |
142 | (signer . ,(openpgp-format-fingerprint | |
143 | (channel-introduction-first-commit-signer | |
144 | intro)))))) | |
145 | '()))))) | |
81a40ee0 | 146 | |
85e9c4b9 | 147 | (define (channel->recutils channel port) |
6d39f0cb LC |
148 | (define intro |
149 | (channel-introduction channel)) | |
150 | ||
85e9c4b9 OP |
151 | (format port "name: ~a~%" (channel-name channel)) |
152 | (format port "url: ~a~%" (channel-url channel)) | |
6d39f0cb LC |
153 | (format port "commit: ~a~%" (channel-commit channel)) |
154 | (when intro | |
155 | (format port "introductioncommit: ~a~%" | |
156 | (channel-introduction-first-signed-commit intro)) | |
157 | (format port "introductionsigner: ~a~%" | |
158 | (openpgp-format-fingerprint | |
159 | (channel-introduction-first-commit-signer intro))))) | |
85e9c4b9 | 160 | |
01262f1e | 161 | (define (display-checkout-info fmt) |
bd747018 LC |
162 | "Display information about the current checkout according to FMT, a symbol |
163 | denoting the requested format. Exit if the current directory does not lie | |
164 | within a Git checkout." | |
01262f1e | 165 | (let* ((program (car (command-line))) |
bd747018 LC |
166 | (directory (catch 'git-error |
167 | (lambda () | |
168 | (repository-discover (dirname program))) | |
169 | (lambda (key err) | |
06633026 LC |
170 | (report-error (G_ "failed to determine origin~%")) |
171 | (display-hint (format #f (G_ "Perhaps this | |
172 | @command{guix} command was not obtained with @command{guix pull}? Its version | |
173 | string is ~a.~%") | |
174 | %guix-version)) | |
175 | (exit 1)))) | |
bd747018 LC |
176 | (repository (repository-open directory)) |
177 | (head (repository-head repository)) | |
178 | (commit (oid->string (reference-target head)))) | |
179 | (match fmt | |
180 | ('human | |
181 | (format #t (G_ "Git checkout:~%")) | |
182 | (format #t (G_ " repository: ~a~%") (dirname directory)) | |
183 | (format #t (G_ " branch: ~a~%") (reference-shorthand head)) | |
184 | (format #t (G_ " commit: ~a~%") commit)) | |
185 | ('channels | |
8548f995 OP |
186 | (pretty-print `(list ,(channel->sexp (channel (name 'guix) |
187 | (url (dirname directory)) | |
81a40ee0 OP |
188 | (commit commit)))))) |
189 | ('json | |
190 | (display (channel->json (channel (name 'guix) | |
191 | (url (dirname directory)) | |
192 | (commit commit)))) | |
85e9c4b9 OP |
193 | (newline)) |
194 | ('recutils | |
195 | (channel->recutils (channel (name 'guix) | |
196 | (url (dirname directory)) | |
197 | (commit commit)) | |
198 | (current-output-port)))) | |
bd747018 LC |
199 | (display-package-search-path fmt))) |
200 | ||
201 | (define (display-profile-info profile fmt) | |
202 | "Display information about PROFILE, a profile as created by (guix channels), | |
203 | in the format specified by FMT." | |
204 | (define number | |
da34a19f | 205 | (generation-number profile)) |
bd747018 | 206 | |
8548f995 | 207 | (define channels |
a7c714d3 LC |
208 | (profile-channels (if (zero? number) |
209 | profile | |
210 | (generation-file-name profile number)))) | |
8548f995 | 211 | |
bd747018 LC |
212 | (match fmt |
213 | ('human | |
214 | (display-profile-content profile number)) | |
215 | ('channels | |
81a40ee0 | 216 | (pretty-print `(list ,@(map channel->sexp channels)))) |
6d39f0cb LC |
217 | ('channels-sans-intro |
218 | (pretty-print `(list ,@(map (cut channel->sexp <> | |
219 | #:include-introduction? #f) | |
220 | channels)))) | |
81a40ee0 | 221 | ('json |
85e9c4b9 OP |
222 | (format #t "[~a]~%" (string-join (map channel->json channels) ","))) |
223 | ('recutils | |
224 | (format #t "~{~a~%~}" | |
225 | (map (lambda (channel) | |
226 | (with-output-to-string | |
227 | (lambda () | |
228 | (channel->recutils channel (current-output-port))))) | |
229 | channels)))) | |
bd747018 LC |
230 | (display-package-search-path fmt)) |
231 | ||
1d88470e LC |
232 | (define (display-profile-content profile number) |
233 | "Display the packages in PROFILE, generation NUMBER, in a human-readable | |
234 | way and displaying details about the channel's source code." | |
235 | (display-generation profile number) | |
236 | (for-each (lambda (entry) | |
237 | (format #t " ~a ~a~%" | |
238 | (manifest-entry-name entry) | |
239 | (manifest-entry-version entry)) | |
240 | (match (assq 'source (manifest-entry-properties entry)) | |
241 | (('source ('repository ('version 0) | |
242 | ('url url) | |
243 | ('branch branch) | |
244 | ('commit commit) | |
245 | _ ...)) | |
246 | (let ((channel (channel (name 'nameless) | |
247 | (url url) | |
248 | (branch branch) | |
249 | (commit commit)))) | |
250 | (format #t (G_ " repository URL: ~a~%") url) | |
251 | (when branch | |
252 | (format #t (G_ " branch: ~a~%") branch)) | |
253 | (format #t (G_ " commit: ~a~%") | |
254 | (if (supports-hyperlinks?) | |
255 | (channel-commit-hyperlink channel commit) | |
c2f9ea2b | 256 | commit)))) |
1d88470e LC |
257 | (_ #f))) |
258 | ||
259 | ;; Show most recently installed packages last. | |
260 | (reverse | |
261 | (manifest-entries | |
262 | (profile-manifest (if (zero? number) | |
263 | profile | |
264 | (generation-file-name profile number))))))) | |
265 | ||
266 | (define %vcs-web-views | |
267 | ;; Hard-coded list of host names and corresponding web view URL templates. | |
268 | ;; TODO: Allow '.guix-channel' files to specify a URL template. | |
269 | (let ((labhub-url (lambda (repository-url commit) | |
270 | (string-append | |
271 | (if (string-suffix? ".git" repository-url) | |
272 | (string-drop-right repository-url 4) | |
273 | repository-url) | |
274 | "/commit/" commit)))) | |
275 | `(("git.savannah.gnu.org" | |
276 | ,(lambda (repository-url commit) | |
277 | (string-append (string-replace-substring repository-url | |
278 | "/git/" "/cgit/") | |
279 | "/commit/?id=" commit))) | |
280 | ("notabug.org" ,labhub-url) | |
281 | ("framagit.org" ,labhub-url) | |
282 | ("gitlab.com" ,labhub-url) | |
283 | ("gitlab.inria.fr" ,labhub-url) | |
284 | ("github.com" ,labhub-url)))) | |
285 | ||
286 | (define* (channel-commit-hyperlink channel | |
287 | #:optional | |
c2f9ea2b | 288 | (commit (channel-commit channel))) |
1d88470e | 289 | "Return a hyperlink for COMMIT in CHANNEL, using COMMIT as the hyperlink's |
c2f9ea2b | 290 | text. The hyperlink links to a web view of COMMIT, when available." |
1d88470e LC |
291 | (let* ((url (channel-url channel)) |
292 | (uri (string->uri url)) | |
293 | (host (and uri (uri-host uri)))) | |
294 | (if host | |
295 | (match (assoc host %vcs-web-views) | |
296 | (#f | |
297 | commit) | |
298 | ((_ template) | |
c2f9ea2b | 299 | (hyperlink (template url commit) commit))) |
1d88470e LC |
300 | commit))) |
301 | ||
bd747018 LC |
302 | \f |
303 | ;;; | |
304 | ;;; Entry point. | |
305 | ;;; | |
306 | ||
3794ce93 LC |
307 | (define-command (guix-describe . args) |
308 | (synopsis "describe the channel revisions currently used") | |
1255400f OP |
309 | (let* ((opts (args-fold* args %options |
310 | (lambda (opt name arg result) | |
311 | (leave (G_ "~A: unrecognized option~%") | |
312 | name)) | |
313 | cons | |
314 | %default-options)) | |
315 | (format (assq-ref opts 'format)) | |
316 | (profile (or (assq-ref opts 'profile) (current-profile)))) | |
bd747018 | 317 | (with-error-handling |
1255400f | 318 | (match profile |
bd747018 LC |
319 | (#f |
320 | (display-checkout-info format)) | |
321 | (profile | |
e0caff9e | 322 | (display-profile-info (canonicalize-profile profile) format)))))) |