Commit | Line | Data |
---|---|---|
0afdc485 LC |
1 | #!/bin/sh |
2 | # aside from this initial boilerplate, this is actually -*- scheme -*- code | |
3 | ||
4 | prefix="@prefix@" | |
5 | datarootdir="@datarootdir@" | |
6 | ||
7 | GUILE_LOAD_COMPILED_PATH="@guilemoduledir@:$GUILE_LOAD_COMPILED_PATH" | |
8 | export GUILE_LOAD_COMPILED_PATH | |
9 | ||
10 | main='(module-ref (resolve-interface '\''(guix-package)) '\'guix-package')' | |
11 | exec ${GUILE-@GUILE@} -L "@guilemoduledir@" -l "$0" \ | |
12 | -c "(apply $main (cdr (command-line)))" "$@" | |
13 | !# | |
233e7676 LC |
14 | ;;; GNU Guix --- Functional package management for GNU |
15 | ;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org> | |
0afdc485 | 16 | ;;; |
233e7676 | 17 | ;;; This file is part of GNU Guix. |
0afdc485 | 18 | ;;; |
233e7676 | 19 | ;;; GNU Guix is free software; you can redistribute it and/or modify it |
0afdc485 LC |
20 | ;;; under the terms of the GNU General Public License as published by |
21 | ;;; the Free Software Foundation; either version 3 of the License, or (at | |
22 | ;;; your option) any later version. | |
23 | ;;; | |
233e7676 | 24 | ;;; GNU Guix is distributed in the hope that it will be useful, but |
0afdc485 LC |
25 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of |
26 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
27 | ;;; GNU General Public License for more details. | |
28 | ;;; | |
29 | ;;; You should have received a copy of the GNU General Public License | |
233e7676 | 30 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. |
0afdc485 LC |
31 | |
32 | (define-module (guix-package) | |
cdd5d6f9 | 33 | #:use-module (guix ui) |
0afdc485 LC |
34 | #:use-module (guix store) |
35 | #:use-module (guix derivations) | |
36 | #:use-module (guix packages) | |
37 | #:use-module (guix utils) | |
a020d2a9 | 38 | #:use-module (guix config) |
0afdc485 LC |
39 | #:use-module (ice-9 ftw) |
40 | #:use-module (ice-9 format) | |
41 | #:use-module (ice-9 match) | |
42 | #:use-module (ice-9 regex) | |
43 | #:use-module (srfi srfi-1) | |
44 | #:use-module (srfi srfi-11) | |
45 | #:use-module (srfi srfi-26) | |
46 | #:use-module (srfi srfi-34) | |
47 | #:use-module (srfi srfi-37) | |
64fc89b6 | 48 | #:use-module (distro) |
1227fabb | 49 | #:use-module (distro packages guile) |
0afdc485 LC |
50 | #:export (guix-package)) |
51 | ||
0afdc485 LC |
52 | (define %store |
53 | (open-connection)) | |
54 | ||
55 | \f | |
56 | ;;; | |
57 | ;;; User environment. | |
58 | ;;; | |
59 | ||
60 | (define %user-environment-directory | |
61 | (and=> (getenv "HOME") | |
62 | (cut string-append <> "/.guix-profile"))) | |
63 | ||
64 | (define %profile-directory | |
a020d2a9 | 65 | (string-append %state-directory "/profiles/" |
0afdc485 LC |
66 | (or (and=> (getenv "USER") |
67 | (cut string-append "per-user/" <>)) | |
68 | "default"))) | |
69 | ||
70 | (define %current-profile | |
4aa52039 LC |
71 | ;; Call it `guix-profile', not `profile', to allow Guix profiles to |
72 | ;; coexist with Nix profiles. | |
73 | (string-append %profile-directory "/guix-profile")) | |
0afdc485 LC |
74 | |
75 | (define (profile-manifest profile) | |
76 | "Return the PROFILE's manifest." | |
77 | (let ((manifest (string-append profile "/manifest"))) | |
78 | (if (file-exists? manifest) | |
79 | (call-with-input-file manifest read) | |
80 | '(manifest (version 0) (packages ()))))) | |
81 | ||
82 | (define (manifest-packages manifest) | |
83 | "Return the packages listed in MANIFEST." | |
84 | (match manifest | |
85 | (('manifest ('version 0) ('packages packages)) | |
86 | packages) | |
87 | (_ | |
88 | (error "unsupported manifest format" manifest)))) | |
89 | ||
90 | (define (latest-profile-number profile) | |
91 | "Return the identifying number of the latest generation of PROFILE. | |
92 | PROFILE is the name of the symlink to the current generation." | |
93 | (define %profile-rx | |
94 | (make-regexp (string-append "^" (regexp-quote (basename profile)) | |
95 | "-([0-9]+)"))) | |
96 | ||
97 | (define* (scandir name #:optional (select? (const #t)) | |
98 | (entry<? (@ (ice-9 i18n) string-locale<?))) | |
99 | ;; XXX: Bug-fix version introduced in Guile v2.0.6-62-g139ce19. | |
100 | (define (enter? dir stat result) | |
101 | (and stat (string=? dir name))) | |
102 | ||
103 | (define (visit basename result) | |
104 | (if (select? basename) | |
105 | (cons basename result) | |
106 | result)) | |
107 | ||
108 | (define (leaf name stat result) | |
109 | (and result | |
110 | (visit (basename name) result))) | |
111 | ||
112 | (define (down name stat result) | |
113 | (visit "." '())) | |
114 | ||
115 | (define (up name stat result) | |
116 | (visit ".." result)) | |
117 | ||
118 | (define (skip name stat result) | |
119 | ;; All the sub-directories are skipped. | |
120 | (visit (basename name) result)) | |
121 | ||
122 | (define (error name* stat errno result) | |
123 | (if (string=? name name*) ; top-level NAME is unreadable | |
124 | result | |
125 | (visit (basename name*) result))) | |
126 | ||
127 | (and=> (file-system-fold enter? leaf down up skip error #f name lstat) | |
128 | (lambda (files) | |
129 | (sort files entry<?)))) | |
130 | ||
131 | (match (scandir (dirname profile) | |
132 | (cut regexp-exec %profile-rx <>)) | |
133 | (#f ; no profile directory | |
134 | 0) | |
135 | (() ; no profiles | |
136 | 0) | |
137 | ((profiles ...) ; former profiles around | |
138 | (let ((numbers (map (compose string->number | |
139 | (cut match:substring <> 1) | |
140 | (cut regexp-exec %profile-rx <>)) | |
141 | profiles))) | |
142 | (fold (lambda (number highest) | |
143 | (if (> number highest) | |
144 | number | |
145 | highest)) | |
146 | 0 | |
147 | numbers))))) | |
148 | ||
149 | (define (profile-derivation store packages) | |
150 | "Return a derivation that builds a profile (a user environment) with | |
151 | all of PACKAGES, a list of name/version/output/path tuples." | |
152 | (define builder | |
153 | `(begin | |
154 | (use-modules (ice-9 pretty-print) | |
155 | (guix build union)) | |
156 | ||
157 | (setvbuf (current-output-port) _IOLBF) | |
158 | (setvbuf (current-error-port) _IOLBF) | |
159 | ||
160 | (let ((output (assoc-ref %outputs "out")) | |
161 | (inputs (map cdr %build-inputs))) | |
162 | (format #t "building user environment `~a' with ~a packages...~%" | |
163 | output (length inputs)) | |
164 | (union-build output inputs) | |
165 | (call-with-output-file (string-append output "/manifest") | |
166 | (lambda (p) | |
167 | (pretty-print '(manifest (version 0) | |
168 | (packages ,packages)) | |
169 | p)))))) | |
170 | ||
171 | (build-expression->derivation store "user-environment" | |
172 | (%current-system) | |
173 | builder | |
174 | (map (match-lambda | |
175 | ((name version output path) | |
176 | `(,name ,path))) | |
177 | packages) | |
178 | #:modules '((guix build union)))) | |
179 | ||
180 | \f | |
181 | ;;; | |
182 | ;;; Command-line options. | |
183 | ;;; | |
184 | ||
185 | (define %default-options | |
186 | ;; Alist of default option values. | |
187 | `((profile . ,%current-profile))) | |
188 | ||
0afdc485 LC |
189 | (define (show-help) |
190 | (display (_ "Usage: guix-package [OPTION]... PACKAGES... | |
191 | Install, remove, or upgrade PACKAGES in a single transaction.\n")) | |
192 | (display (_ " | |
193 | -i, --install=PACKAGE install PACKAGE")) | |
194 | (display (_ " | |
195 | -r, --remove=PACKAGE remove PACKAGE")) | |
196 | (display (_ " | |
197 | -u, --upgrade=REGEXP upgrade all the installed packages matching REGEXP")) | |
198 | (newline) | |
199 | (display (_ " | |
200 | -p, --profile=PROFILE use PROFILE instead of the user's default profile")) | |
201 | (display (_ " | |
202 | -n, --dry-run show what would be done without actually doing it")) | |
203 | (display (_ " | |
204 | -b, --bootstrap use the bootstrap Guile to build the profile")) | |
70915c1a LC |
205 | (display (_ " |
206 | --verbose produce verbose output")) | |
0afdc485 LC |
207 | (newline) |
208 | (display (_ " | |
733b4130 LC |
209 | -I, --list-installed[=REGEXP] |
210 | list installed packages matching REGEXP")) | |
64fc89b6 LC |
211 | (display (_ " |
212 | -A, --list-available[=REGEXP] | |
213 | list available packages matching REGEXP")) | |
733b4130 LC |
214 | (newline) |
215 | (display (_ " | |
0afdc485 LC |
216 | -h, --help display this help and exit")) |
217 | (display (_ " | |
218 | -V, --version display version information and exit")) | |
219 | (newline) | |
3441e164 | 220 | (show-bug-report-information)) |
0afdc485 LC |
221 | |
222 | (define %options | |
223 | ;; Specification of the command-line options. | |
224 | (list (option '(#\h "help") #f #f | |
225 | (lambda args | |
226 | (show-help) | |
227 | (exit 0))) | |
228 | (option '(#\V "version") #f #f | |
229 | (lambda args | |
cdd5d6f9 | 230 | (show-version-and-exit "guix-package"))) |
0afdc485 LC |
231 | |
232 | (option '(#\i "install") #t #f | |
233 | (lambda (opt name arg result) | |
234 | (alist-cons 'install arg result))) | |
235 | (option '(#\r "remove") #t #f | |
236 | (lambda (opt name arg result) | |
237 | (alist-cons 'remove arg result))) | |
238 | (option '(#\p "profile") #t #f | |
239 | (lambda (opt name arg result) | |
240 | (alist-cons 'profile arg | |
241 | (alist-delete 'profile result)))) | |
242 | (option '(#\n "dry-run") #f #f | |
243 | (lambda (opt name arg result) | |
244 | (alist-cons 'dry-run? #t result))) | |
245 | (option '(#\b "bootstrap") #f #f | |
246 | (lambda (opt name arg result) | |
733b4130 | 247 | (alist-cons 'bootstrap? #t result))) |
70915c1a LC |
248 | (option '("verbose") #f #f |
249 | (lambda (opt name arg result) | |
250 | (alist-cons 'verbose? #t result))) | |
733b4130 LC |
251 | (option '(#\I "list-installed") #f #t |
252 | (lambda (opt name arg result) | |
253 | (cons `(query list-installed ,(or arg "")) | |
64fc89b6 LC |
254 | result))) |
255 | (option '(#\A "list-available") #f #t | |
256 | (lambda (opt name arg result) | |
257 | (cons `(query list-available ,(or arg "")) | |
733b4130 | 258 | result))))) |
0afdc485 LC |
259 | |
260 | \f | |
261 | ;;; | |
262 | ;;; Entry point. | |
263 | ;;; | |
264 | ||
265 | (define (guix-package . args) | |
266 | (define (parse-options) | |
267 | ;; Return the alist of option values. | |
268 | (args-fold args %options | |
269 | (lambda (opt name arg result) | |
270 | (leave (_ "~A: unrecognized option~%") name)) | |
271 | (lambda (arg result) | |
272 | (alist-cons 'argument arg result)) | |
273 | %default-options)) | |
274 | ||
275 | (define (show-what-to-build drv dry-run?) | |
276 | ;; Show what will/would be built in realizing the derivations listed | |
277 | ;; in DRV. | |
278 | (let* ((req (append-map (lambda (drv-path) | |
279 | (let ((d (call-with-input-file drv-path | |
280 | read-derivation))) | |
281 | (derivation-prerequisites-to-build %store d))) | |
282 | drv)) | |
283 | (req* (delete-duplicates | |
284 | (append (remove (compose (cut valid-path? %store <>) | |
285 | derivation-path->output-path) | |
286 | drv) | |
287 | (map derivation-input-path req))))) | |
288 | (if dry-run? | |
289 | (format (current-error-port) | |
290 | (N_ "~:[the following derivation would be built:~%~{ ~a~%~}~;~]" | |
291 | "~:[the following derivations would be built:~%~{ ~a~%~}~;~]" | |
292 | (length req*)) | |
293 | (null? req*) req*) | |
294 | (format (current-error-port) | |
295 | (N_ "~:[the following derivation will be built:~%~{ ~a~%~}~;~]" | |
296 | "~:[the following derivations will be built:~%~{ ~a~%~}~;~]" | |
297 | (length req*)) | |
298 | (null? req*) req*)))) | |
299 | ||
300 | (define (find-package name) | |
301 | ;; Find the package NAME; NAME may contain a version number and a | |
302 | ;; sub-derivation name. | |
303 | (define request name) | |
0afdc485 LC |
304 | |
305 | (let*-values (((name sub-drv) | |
306 | (match (string-rindex name #\:) | |
307 | (#f (values name "out")) | |
9518856b LC |
308 | (colon (values (substring name 0 colon) |
309 | (substring name (+ 1 colon)))))) | |
0afdc485 | 310 | ((name version) |
9b48fb88 | 311 | (package-name->name+version name))) |
0afdc485 LC |
312 | (match (find-packages-by-name name version) |
313 | ((p) | |
d9d05363 | 314 | (list name (package-version p) sub-drv p)) |
c6f09dfa | 315 | ((p p* ...) |
0afdc485 LC |
316 | (format (current-error-port) |
317 | (_ "warning: ambiguous package specification `~a'~%") | |
318 | request) | |
319 | (format (current-error-port) | |
d9d05363 LC |
320 | (_ "warning: choosing ~a from ~a~%") |
321 | (package-full-name p) | |
322 | (location->string (package-location p))) | |
323 | (list name (package-version p) sub-drv p)) | |
0afdc485 LC |
324 | (() |
325 | (leave (_ "~a: package not found~%") request))))) | |
326 | ||
733b4130 LC |
327 | (define (process-actions opts) |
328 | ;; Process any install/remove/upgrade action from OPTS. | |
329 | (let* ((dry-run? (assoc-ref opts 'dry-run?)) | |
70915c1a | 330 | (verbose? (assoc-ref opts 'verbose?)) |
733b4130 LC |
331 | (profile (assoc-ref opts 'profile)) |
332 | (install (filter-map (match-lambda | |
333 | (('install . (? store-path?)) | |
334 | #f) | |
335 | (('install . package) | |
336 | (find-package package)) | |
337 | (_ #f)) | |
338 | opts)) | |
339 | (drv (filter-map (match-lambda | |
340 | ((name version sub-drv | |
341 | (? package? package)) | |
342 | (package-derivation %store package)) | |
343 | (_ #f)) | |
344 | install)) | |
345 | (install* (append | |
346 | (filter-map (match-lambda | |
347 | (('install . (? store-path? path)) | |
5075e283 LC |
348 | (let-values (((name version) |
349 | (package-name->name+version | |
350 | (store-path-package-name | |
351 | path)))) | |
352 | `(,name ,version #f ,path))) | |
733b4130 LC |
353 | (_ #f)) |
354 | opts) | |
355 | (map (lambda (tuple drv) | |
356 | (match tuple | |
357 | ((name version sub-drv _) | |
358 | (let ((output-path | |
359 | (derivation-path->output-path | |
360 | drv sub-drv))) | |
361 | `(,name ,version ,sub-drv ,output-path))))) | |
362 | install drv))) | |
363 | (remove (filter-map (match-lambda | |
364 | (('remove . package) | |
365 | package) | |
366 | (_ #f)) | |
367 | opts)) | |
368 | (packages (append install* | |
1c67d639 LC |
369 | (fold (lambda (package result) |
370 | (match package | |
371 | ((name _ ...) | |
372 | (alist-delete name result)))) | |
373 | (fold alist-delete | |
374 | (manifest-packages | |
375 | (profile-manifest profile)) | |
376 | remove) | |
377 | install*)))) | |
733b4130 LC |
378 | |
379 | (show-what-to-build drv dry-run?) | |
380 | ||
381 | (or dry-run? | |
382 | (and (build-derivations %store drv) | |
383 | (let* ((prof-drv (profile-derivation %store packages)) | |
384 | (prof (derivation-path->output-path prof-drv)) | |
1c67d639 LC |
385 | (old-drv (profile-derivation |
386 | %store (manifest-packages | |
387 | (profile-manifest profile)))) | |
388 | (old-prof (derivation-path->output-path old-drv)) | |
733b4130 LC |
389 | (number (latest-profile-number profile)) |
390 | (name (format #f "~a/~a-~a-link" | |
391 | (dirname profile) | |
392 | (basename profile) (+ 1 number)))) | |
1c67d639 LC |
393 | (if (string=? old-prof prof) |
394 | (format (current-error-port) (_ "nothing to be done~%")) | |
70915c1a LC |
395 | (and (parameterize ((current-build-output-port |
396 | (if verbose? | |
397 | (current-error-port) | |
398 | (%make-void-port "w")))) | |
399 | (build-derivations %store (list prof-drv))) | |
1c67d639 LC |
400 | (begin |
401 | (symlink prof name) | |
402 | (when (file-exists? profile) | |
403 | (delete-file profile)) | |
404 | (symlink name profile))))))))) | |
733b4130 LC |
405 | |
406 | (define (process-query opts) | |
407 | ;; Process any query specified by OPTS. Return #t when a query was | |
408 | ;; actually processed, #f otherwise. | |
409 | (let ((profile (assoc-ref opts 'profile))) | |
410 | (match (assoc-ref opts 'query) | |
411 | (('list-installed regexp) | |
412 | (let* ((regexp (and regexp (make-regexp regexp))) | |
413 | (manifest (profile-manifest profile)) | |
414 | (installed (manifest-packages manifest))) | |
415 | (for-each (match-lambda | |
416 | ((name version output path) | |
417 | (when (or (not regexp) | |
418 | (regexp-exec regexp name)) | |
419 | (format #t "~a\t~a\t~a\t~a~%" | |
420 | name (or version "?") output path)))) | |
64fc89b6 LC |
421 | installed) |
422 | #t)) | |
423 | (('list-available regexp) | |
424 | (let* ((regexp (and regexp (make-regexp regexp))) | |
425 | (available (fold-packages | |
426 | (lambda (p r) | |
427 | (let ((n (package-name p))) | |
428 | (if regexp | |
429 | (if (regexp-exec regexp n) | |
430 | (cons p r) | |
431 | r) | |
432 | (cons p r)))) | |
433 | '()))) | |
434 | (for-each (lambda (p) | |
435 | (format #t "~a\t~a\t~a~%" | |
436 | (package-name p) | |
437 | (package-version p) | |
438 | (location->string (package-location p)))) | |
439 | (sort available | |
440 | (lambda (p1 p2) | |
441 | (string<? (package-name p1) | |
442 | (package-name p2))))) | |
443 | #t)) | |
733b4130 LC |
444 | (_ #f)))) |
445 | ||
0afdc485 LC |
446 | (setlocale LC_ALL "") |
447 | (textdomain "guix") | |
448 | (setvbuf (current-output-port) _IOLBF) | |
449 | (setvbuf (current-error-port) _IOLBF) | |
450 | ||
451 | (let ((opts (parse-options))) | |
1275baeb | 452 | (with-error-handling |
733b4130 LC |
453 | (or (process-query opts) |
454 | (parameterize ((%guile-for-build | |
455 | (package-derivation %store | |
456 | (if (assoc-ref opts 'bootstrap?) | |
457 | (@@ (distro packages base) | |
458 | %bootstrap-guile) | |
459 | guile-2.0)))) | |
460 | (process-actions opts)))))) | |
0afdc485 LC |
461 | |
462 | ;; Local Variables: | |
463 | ;; eval: (put 'guard 'scheme-indent-function 1) | |
464 | ;; End: |