gexp: Export 'gexp-input' constructor.
[jackhill/guix/guix.git] / guix / profiles.scm
CommitLineData
cc4ecc2d 1;;; GNU Guix --- Functional package management for GNU
e87f0591 2;;; Copyright © 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
cc4ecc2d 3;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
343745c8 4;;; Copyright © 2014 Alex Kost <alezost@gmail.com>
536c3ee4 5;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
cc4ecc2d
LC
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
22(define-module (guix profiles)
23 #:use-module (guix utils)
24 #:use-module (guix records)
25 #:use-module (guix derivations)
26 #:use-module (guix packages)
a54c94a4 27 #:use-module (guix gexp)
79ee406d 28 #:use-module (guix monads)
e87f0591 29 #:use-module (guix store)
cc4ecc2d
LC
30 #:use-module (ice-9 match)
31 #:use-module (ice-9 regex)
32 #:use-module (ice-9 ftw)
343745c8 33 #:use-module (ice-9 format)
cc4ecc2d
LC
34 #:use-module (srfi srfi-1)
35 #:use-module (srfi srfi-9)
79601521 36 #:use-module (srfi srfi-11)
cc4ecc2d
LC
37 #:use-module (srfi srfi-19)
38 #:use-module (srfi srfi-26)
c0c018f1
AK
39 #:use-module (srfi srfi-34)
40 #:use-module (srfi srfi-35)
41 #:export (&profile-error
42 profile-error?
43 profile-error-profile
44 &profile-not-found-error
45 profile-not-found-error?
46 &missing-generation-error
47 missing-generation-error?
48 missing-generation-error-generation
49
50 manifest make-manifest
cc4ecc2d
LC
51 manifest?
52 manifest-entries
53
54 <manifest-entry> ; FIXME: eventually make it internal
55 manifest-entry
56 manifest-entry?
57 manifest-entry-name
58 manifest-entry-version
59 manifest-entry-output
a54c94a4 60 manifest-entry-item
cc4ecc2d
LC
61 manifest-entry-dependencies
62
a2078770
LC
63 manifest-pattern
64 manifest-pattern?
65
cc4ecc2d 66 manifest-remove
f7554030 67 manifest-add
ef8993e2 68 manifest-lookup
cc4ecc2d 69 manifest-installed?
a2078770 70 manifest-matching-entries
cc4ecc2d 71
343745c8
AK
72 manifest-transaction
73 manifest-transaction?
74 manifest-transaction-install
75 manifest-transaction-remove
76 manifest-perform-transaction
79601521 77 manifest-transaction-effects
343745c8 78
cc4ecc2d 79 profile-manifest
462f5cca 80 package->manifest-entry
cc4ecc2d
LC
81 profile-derivation
82 generation-number
83 generation-numbers
f452e8ff 84 profile-generations
3ccde087 85 relative-generation
cc4ecc2d
LC
86 previous-generation-number
87 generation-time
88 generation-file-name))
89
90;;; Commentary:
91;;;
92;;; Tools to create and manipulate profiles---i.e., the representation of a
93;;; set of installed packages.
94;;;
95;;; Code:
96
97\f
c0c018f1
AK
98;;;
99;;; Condition types.
100;;;
101
102(define-condition-type &profile-error &error
103 profile-error?
104 (profile profile-error-profile))
105
106(define-condition-type &profile-not-found-error &profile-error
107 profile-not-found-error?)
108
109(define-condition-type &missing-generation-error &profile-error
110 missing-generation-error?
111 (generation missing-generation-error-generation))
112
113\f
cc4ecc2d
LC
114;;;
115;;; Manifests.
116;;;
117
118(define-record-type <manifest>
119 (manifest entries)
120 manifest?
121 (entries manifest-entries)) ; list of <manifest-entry>
122
123;; Convenient alias, to avoid name clashes.
124(define make-manifest manifest)
125
126(define-record-type* <manifest-entry> manifest-entry
127 make-manifest-entry
128 manifest-entry?
129 (name manifest-entry-name) ; string
130 (version manifest-entry-version) ; string
131 (output manifest-entry-output ; string
132 (default "out"))
a54c94a4 133 (item manifest-entry-item) ; package | store path
4ca0b410
LC
134 (dependencies manifest-entry-dependencies ; (store path | package)*
135 (default '())))
cc4ecc2d 136
a2078770
LC
137(define-record-type* <manifest-pattern> manifest-pattern
138 make-manifest-pattern
139 manifest-pattern?
140 (name manifest-pattern-name) ; string
141 (version manifest-pattern-version ; string | #f
142 (default #f))
143 (output manifest-pattern-output ; string | #f
144 (default "out")))
145
cc4ecc2d
LC
146(define (profile-manifest profile)
147 "Return the PROFILE's manifest."
148 (let ((file (string-append profile "/manifest")))
149 (if (file-exists? file)
150 (call-with-input-file file read-manifest)
151 (manifest '()))))
152
462f5cca
LC
153(define* (package->manifest-entry package #:optional output)
154 "Return a manifest entry for the OUTPUT of package PACKAGE. When OUTPUT is
155omitted or #f, use the first output of PACKAGE."
156 (let ((deps (map (match-lambda
157 ((label package)
158 `(,package "out"))
159 ((label package output)
160 `(,package ,output)))
161 (package-transitive-propagated-inputs package))))
162 (manifest-entry
163 (name (package-name package))
164 (version (package-version package))
165 (output (or output (car (package-outputs package))))
166 (item package)
167 (dependencies (delete-duplicates deps)))))
168
a54c94a4
LC
169(define (manifest->gexp manifest)
170 "Return a representation of MANIFEST as a gexp."
171 (define (entry->gexp entry)
cc4ecc2d 172 (match entry
a54c94a4
LC
173 (($ <manifest-entry> name version output (? string? path) (deps ...))
174 #~(#$name #$version #$output #$path #$deps))
175 (($ <manifest-entry> name version output (? package? package) (deps ...))
176 #~(#$name #$version #$output
177 (ungexp package (or output "out")) #$deps))))
cc4ecc2d
LC
178
179 (match manifest
180 (($ <manifest> (entries ...))
a54c94a4
LC
181 #~(manifest (version 1)
182 (packages #$(map entry->gexp entries))))))
cc4ecc2d
LC
183
184(define (sexp->manifest sexp)
185 "Parse SEXP as a manifest."
186 (match sexp
187 (('manifest ('version 0)
188 ('packages ((name version output path) ...)))
189 (manifest
190 (map (lambda (name version output path)
191 (manifest-entry
192 (name name)
193 (version version)
194 (output output)
a54c94a4 195 (item path)))
cc4ecc2d
LC
196 name version output path)))
197
198 ;; Version 1 adds a list of propagated inputs to the
199 ;; name/version/output/path tuples.
200 (('manifest ('version 1)
201 ('packages ((name version output path deps) ...)))
202 (manifest
203 (map (lambda (name version output path deps)
d34736c5
LC
204 ;; Up to Guix 0.7 included, dependencies were listed as ("gmp"
205 ;; "/gnu/store/...-gmp") for instance. Discard the 'label' in
206 ;; such lists.
207 (let ((deps (match deps
208 (((labels directories) ...)
209 directories)
210 ((directories ...)
211 directories))))
212 (manifest-entry
213 (name name)
214 (version version)
215 (output output)
216 (item path)
217 (dependencies deps))))
cc4ecc2d
LC
218 name version output path deps)))
219
220 (_
221 (error "unsupported manifest format" manifest))))
222
223(define (read-manifest port)
224 "Return the packages listed in MANIFEST."
225 (sexp->manifest (read port)))
226
a2078770
LC
227(define (entry-predicate pattern)
228 "Return a procedure that returns #t when passed a manifest entry that
229matches NAME/OUTPUT/VERSION. OUTPUT and VERSION may be #f, in which case they
230are ignored."
231 (match pattern
232 (($ <manifest-pattern> name version output)
233 (match-lambda
234 (($ <manifest-entry> entry-name entry-version entry-output)
235 (and (string=? entry-name name)
236 (or (not entry-output) (not output)
237 (string=? entry-output output))
238 (or (not version)
239 (string=? entry-version version))))))))
240
241(define (manifest-remove manifest patterns)
242 "Remove entries for each of PATTERNS from MANIFEST. Each item in PATTERNS
243must be a manifest-pattern."
244 (define (remove-entry pattern lst)
245 (remove (entry-predicate pattern) lst))
246
247 (make-manifest (fold remove-entry
cc4ecc2d 248 (manifest-entries manifest)
a2078770 249 patterns)))
cc4ecc2d 250
f7554030
AK
251(define (manifest-add manifest entries)
252 "Add a list of manifest ENTRIES to MANIFEST and return new manifest.
253Remove MANIFEST entries that have the same name and output as ENTRIES."
254 (define (same-entry? entry name output)
255 (match entry
256 (($ <manifest-entry> entry-name _ entry-output _ ...)
257 (and (equal? name entry-name)
258 (equal? output entry-output)))))
259
260 (make-manifest
261 (append entries
262 (fold (lambda (entry result)
263 (match entry
264 (($ <manifest-entry> name _ out _ ...)
265 (filter (negate (cut same-entry? <> name out))
266 result))))
267 (manifest-entries manifest)
268 entries))))
269
ef8993e2
LC
270(define (manifest-lookup manifest pattern)
271 "Return the first item of MANIFEST that matches PATTERN, or #f if there is
272no match.."
273 (find (entry-predicate pattern)
274 (manifest-entries manifest)))
275
a2078770
LC
276(define (manifest-installed? manifest pattern)
277 "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern),
278#f otherwise."
ef8993e2 279 (->bool (manifest-lookup manifest pattern)))
cc4ecc2d 280
a2078770
LC
281(define (manifest-matching-entries manifest patterns)
282 "Return all the entries of MANIFEST that match one of the PATTERNS."
283 (define predicates
284 (map entry-predicate patterns))
285
286 (define (matches? entry)
287 (any (lambda (pred)
288 (pred entry))
289 predicates))
290
291 (filter matches? (manifest-entries manifest)))
292
cc4ecc2d 293\f
343745c8
AK
294;;;
295;;; Manifest transactions.
296;;;
297
298(define-record-type* <manifest-transaction> manifest-transaction
299 make-manifest-transaction
300 manifest-transaction?
301 (install manifest-transaction-install ; list of <manifest-entry>
302 (default '()))
303 (remove manifest-transaction-remove ; list of <manifest-pattern>
304 (default '())))
305
79601521 306(define (manifest-transaction-effects manifest transaction)
46b23e1a
LC
307 "Compute the effect of applying TRANSACTION to MANIFEST. Return 4 values:
308the list of packages that would be removed, installed, upgraded, or downgraded
309when applying TRANSACTION to MANIFEST. Upgrades are represented as pairs
310where the head is the entry being upgraded and the tail is the entry that will
311replace it."
79601521
LC
312 (define (manifest-entry->pattern entry)
313 (manifest-pattern
314 (name (manifest-entry-name entry))
315 (output (manifest-entry-output entry))))
316
46b23e1a
LC
317 (let loop ((input (manifest-transaction-install transaction))
318 (install '())
319 (upgrade '())
320 (downgrade '()))
79601521
LC
321 (match input
322 (()
323 (let ((remove (manifest-transaction-remove transaction)))
324 (values (manifest-matching-entries manifest remove)
46b23e1a 325 (reverse install) (reverse upgrade) (reverse downgrade))))
79601521
LC
326 ((entry rest ...)
327 ;; Check whether installing ENTRY corresponds to the installation of a
328 ;; new package or to an upgrade.
329
330 ;; XXX: When the exact same output directory is installed, we're not
331 ;; really upgrading anything. Add a check for that case.
332 (let* ((pattern (manifest-entry->pattern entry))
46b23e1a
LC
333 (previous (manifest-lookup manifest pattern))
334 (newer? (and previous
3bea13bb
LC
335 (version>=? (manifest-entry-version entry)
336 (manifest-entry-version previous)))))
79601521 337 (loop rest
ef8993e2 338 (if previous install (cons entry install))
46b23e1a 339 (if (and previous newer?)
ef8993e2 340 (alist-cons previous entry upgrade)
46b23e1a
LC
341 upgrade)
342 (if (and previous (not newer?))
343 (alist-cons previous entry downgrade)
344 downgrade)))))))
79601521 345
343745c8
AK
346(define (manifest-perform-transaction manifest transaction)
347 "Perform TRANSACTION on MANIFEST and return new manifest."
348 (let ((install (manifest-transaction-install transaction))
349 (remove (manifest-transaction-remove transaction)))
350 (manifest-add (manifest-remove manifest remove)
351 install)))
352
343745c8 353\f
cc4ecc2d
LC
354;;;
355;;; Profiles.
356;;;
357
79ee406d
LC
358(define (manifest-inputs manifest)
359 "Return the list of inputs for MANIFEST. Each input has one of the
360following forms:
361
362 (PACKAGE OUTPUT-NAME)
363
364or
365
366 STORE-PATH
367"
368 (append-map (match-lambda
369 (($ <manifest-entry> name version
370 output (? package? package) deps)
371 `((,package ,output) ,@deps))
372 (($ <manifest-entry> name version output path deps)
373 ;; Assume PATH and DEPS are already valid.
374 `(,path ,@deps)))
375 (manifest-entries manifest)))
376
377(define (info-dir-file manifest)
378 "Return a derivation that builds the 'dir' file for all the entries of
379MANIFEST."
2f0556ae
LC
380 (define texinfo ;lazy reference
381 (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
382 (define gzip ;lazy reference
383 (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
384
79ee406d 385 (define build
a54c94a4 386 #~(begin
79ee406d
LC
387 (use-modules (guix build utils)
388 (srfi srfi-1) (srfi srfi-26)
389 (ice-9 ftw))
390
391 (define (info-file? file)
392 (or (string-suffix? ".info" file)
393 (string-suffix? ".info.gz" file)))
394
395 (define (info-files top)
396 (let ((infodir (string-append top "/share/info")))
397 (map (cut string-append infodir "/" <>)
c2815c0f 398 (or (scandir infodir info-file?) '()))))
79ee406d
LC
399
400 (define (install-info info)
2f0556ae 401 (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
79ee406d
LC
402 (zero?
403 (system* (string-append #+texinfo "/bin/install-info")
404 info (string-append #$output "/share/info/dir"))))
405
406 (mkdir-p (string-append #$output "/share/info"))
407 (every install-info
408 (append-map info-files
409 '#$(manifest-inputs manifest)))))
410
411 ;; Don't depend on Texinfo when there's nothing to do.
412 (if (null? (manifest-entries manifest))
413 (gexp->derivation "info-dir" #~(mkdir #$output))
414 (gexp->derivation "info-dir" build
415 #:modules '((guix build utils)))))
416
536c3ee4
MW
417(define (ca-certificate-bundle manifest)
418 "Return a derivation that builds a single-file bundle containing the CA
419certificates in the /etc/ssl/certs sub-directories of the packages in
420MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
421 ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
422 ;; for a discussion.
423
424 (define glibc-utf8-locales ;lazy reference
425 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
426
427 (define build
428 #~(begin
429 (use-modules (guix build utils)
430 (rnrs io ports)
431 (srfi srfi-1)
432 (srfi srfi-26)
433 (ice-9 ftw))
434
435 (define (pem-file? file)
436 (string-suffix? ".pem" file))
437
438 (define (ca-files top)
439 (let ((cert-dir (string-append top "/etc/ssl/certs")))
440 (map (cut string-append cert-dir "/" <>)
441 (or (scandir cert-dir pem-file?) '()))))
442
443 (define (concatenate-files files result)
444 "Make RESULT the concatenation of all of FILES."
445 (define (dump file port)
446 (display (call-with-input-file file get-string-all)
447 port)
448 (newline port)) ;required, see <https://bugs.debian.org/635570>
449
450 (call-with-output-file result
451 (lambda (port)
452 (for-each (cut dump <> port) files))))
453
454 ;; Some file names in the NSS certificates are UTF-8 encoded so
455 ;; install a UTF-8 locale.
456 (setenv "LOCPATH" (string-append #+glibc-utf8-locales "/lib/locale"))
457 (setlocale LC_ALL "en_US.UTF-8")
458
459 (let ((ca-files (append-map ca-files
460 '#$(manifest-inputs manifest)))
461 (result (string-append #$output "/etc/ssl/certs")))
462 (mkdir-p result)
463 (concatenate-files ca-files
464 (string-append result
465 "/ca-certificates.crt")))))
466
6d0b9d03
LC
467 ;; Don't depend on 'glibc-utf8-locales' and its dependencies when there's
468 ;; nothing to do.
469 (if (null? (manifest-entries manifest))
470 (gexp->derivation "ca-certificate-bundle" #~(mkdir #$output))
471 (gexp->derivation "ca-certificate-bundle" build
472 #:modules '((guix build utils))
473 #:local-build? #t)))
536c3ee4
MW
474
475(define* (profile-derivation manifest
476 #:key
477 (info-dir? #t)
478 (ca-certificate-bundle? #t))
79ee406d 479 "Return a derivation that builds a profile (aka. 'user environment') with
536c3ee4
MW
480the given MANIFEST. The profile includes a top-level Info 'dir' file unless
481INFO-DIR? is #f, and a single-file CA certificate bundle unless
482CA-CERTIFICATE-BUNDLE? is #f."
79ee406d
LC
483 (mlet %store-monad ((info-dir (if info-dir?
484 (info-dir-file manifest)
536c3ee4
MW
485 (return #f)))
486 (ca-cert-bundle (if ca-certificate-bundle?
487 (ca-certificate-bundle manifest)
488 (return #f))))
79ee406d 489 (define inputs
536c3ee4
MW
490 ;; XXX: Here we use tuples of the form (DIR "out") just so that the list
491 ;; is unambiguous for the gexp code when MANIFEST has a single input
492 ;; denoted as a string (the pattern (DRV STRING) is normally
493 ;; interpreted in a gexp as "the STRING output of DRV".). See
494 ;; <http://lists.gnu.org/archive/html/guix-devel/2014-12/msg00292.html>.
495 (append (if info-dir
496 `((,info-dir "out"))
497 '())
498 (if ca-cert-bundle
499 `((,ca-cert-bundle "out"))
500 '())
501 (manifest-inputs manifest)))
79ee406d
LC
502
503 (define builder
504 #~(begin
505 (use-modules (ice-9 pretty-print)
506 (guix build union))
507
508 (setvbuf (current-output-port) _IOLBF)
509 (setvbuf (current-error-port) _IOLBF)
510
511 (union-build #$output '#$inputs
512 #:log-port (%make-void-port "w"))
513 (call-with-output-file (string-append #$output "/manifest")
514 (lambda (p)
515 (pretty-print '#$(manifest->gexp manifest) p)))))
516
517 (gexp->derivation "profile" builder
518 #:modules '((guix build union))
519 #:local-build? #t)))
cc4ecc2d
LC
520
521(define (profile-regexp profile)
522 "Return a regular expression that matches PROFILE's name and number."
523 (make-regexp (string-append "^" (regexp-quote (basename profile))
524 "-([0-9]+)")))
525
526(define (generation-number profile)
527 "Return PROFILE's number or 0. An absolute file name must be used."
528 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
529 (basename (readlink profile))))
530 (compose string->number (cut match:substring <> 1)))
531 0))
532
533(define (generation-numbers profile)
534 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
535former profiles were found."
536 (define* (scandir name #:optional (select? (const #t))
537 (entry<? (@ (ice-9 i18n) string-locale<?)))
538 ;; XXX: Bug-fix version introduced in Guile v2.0.6-62-g139ce19.
539 (define (enter? dir stat result)
540 (and stat (string=? dir name)))
541
542 (define (visit basename result)
543 (if (select? basename)
544 (cons basename result)
545 result))
546
547 (define (leaf name stat result)
548 (and result
549 (visit (basename name) result)))
550
551 (define (down name stat result)
552 (visit "." '()))
553
554 (define (up name stat result)
555 (visit ".." result))
556
557 (define (skip name stat result)
558 ;; All the sub-directories are skipped.
559 (visit (basename name) result))
560
561 (define (error name* stat errno result)
562 (if (string=? name name*) ; top-level NAME is unreadable
563 result
564 (visit (basename name*) result)))
565
566 (and=> (file-system-fold enter? leaf down up skip error #f name lstat)
567 (lambda (files)
568 (sort files entry<?))))
569
570 (match (scandir (dirname profile)
571 (cute regexp-exec (profile-regexp profile) <>))
572 (#f ; no profile directory
573 '(0))
574 (() ; no profiles
575 '(0))
576 ((profiles ...) ; former profiles around
577 (sort (map (compose string->number
578 (cut match:substring <> 1)
579 (cute regexp-exec (profile-regexp profile) <>))
580 profiles)
581 <))))
582
f452e8ff
AK
583(define (profile-generations profile)
584 "Return a list of PROFILE's generations."
585 (let ((generations (generation-numbers profile)))
586 (if (equal? generations '(0))
587 '()
588 generations)))
589
3ccde087
AK
590(define* (relative-generation profile shift #:optional
591 (current (generation-number profile)))
592 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
593SHIFT is a positive or negative number.
594Return #f if there is no such generation."
595 (let* ((abs-shift (abs shift))
596 (numbers (profile-generations profile))
597 (from-current (memq current
598 (if (negative? shift)
599 (reverse numbers)
600 numbers))))
601 (and from-current
602 (< abs-shift (length from-current))
603 (list-ref from-current abs-shift))))
604
605(define* (previous-generation-number profile #:optional
606 (number (generation-number profile)))
cc4ecc2d
LC
607 "Return the number of the generation before generation NUMBER of
608PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
609case when generations have been deleted (there are \"holes\")."
3ccde087
AK
610 (or (relative-generation profile -1 number)
611 0))
cc4ecc2d
LC
612
613(define (generation-file-name profile generation)
614 "Return the file name for PROFILE's GENERATION."
615 (format #f "~a-~a-link" profile generation))
616
617(define (generation-time profile number)
618 "Return the creation time of a generation in the UTC format."
619 (make-time time-utc 0
620 (stat:ctime (stat (generation-file-name profile number)))))
621
622;;; profiles.scm ends here