Merge branch 'master' into core-updates
[jackhill/guix/guix.git] / guix / profiles.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
3 ;;; Copyright © 2013 Nikita Karetnikov <nikita@karetnikov.org>
4 ;;; Copyright © 2014, 2016 Alex Kost <alezost@gmail.com>
5 ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
6 ;;; Copyright © 2015 Sou Bunnbu <iyzsong@gmail.com>
7 ;;; Copyright © 2016 Ricardo Wurmus <rekado@elephly.net>
8 ;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
9 ;;; Copyright © 2017 Huang Ying <huang.ying.caritas@gmail.com>
10 ;;; Copyright © 2017 Maxim Cournoyer <maxim.cournoyer@gmail.com>
11 ;;;
12 ;;; This file is part of GNU Guix.
13 ;;;
14 ;;; GNU Guix is free software; you can redistribute it and/or modify it
15 ;;; under the terms of the GNU General Public License as published by
16 ;;; the Free Software Foundation; either version 3 of the License, or (at
17 ;;; your option) any later version.
18 ;;;
19 ;;; GNU Guix is distributed in the hope that it will be useful, but
20 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;;; GNU General Public License for more details.
23 ;;;
24 ;;; You should have received a copy of the GNU General Public License
25 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
26
27 (define-module (guix profiles)
28 #:use-module ((guix utils) #:hide (package-name->name+version))
29 #:use-module ((guix build utils)
30 #:select (package-name->name+version))
31 #:use-module (guix records)
32 #:use-module (guix packages)
33 #:use-module (guix derivations)
34 #:use-module (guix search-paths)
35 #:use-module (guix gexp)
36 #:use-module (guix modules)
37 #:use-module (guix monads)
38 #:use-module (guix store)
39 #:use-module (guix sets)
40 #:use-module (ice-9 vlist)
41 #:use-module (ice-9 match)
42 #:use-module (ice-9 regex)
43 #:use-module (ice-9 ftw)
44 #:use-module (ice-9 format)
45 #:use-module (srfi srfi-1)
46 #:use-module (srfi srfi-9)
47 #:use-module (srfi srfi-11)
48 #:use-module (srfi srfi-19)
49 #:use-module (srfi srfi-26)
50 #:use-module (srfi srfi-34)
51 #:use-module (srfi srfi-35)
52 #:export (&profile-error
53 profile-error?
54 profile-error-profile
55 &profile-not-found-error
56 profile-not-found-error?
57 &profile-collistion-error
58 profile-collision-error?
59 profile-collision-error-entry
60 profile-collision-error-conflict
61 &missing-generation-error
62 missing-generation-error?
63 missing-generation-error-generation
64
65 manifest make-manifest
66 manifest?
67 manifest-entries
68 manifest-transitive-entries
69
70 <manifest-entry> ; FIXME: eventually make it internal
71 manifest-entry
72 manifest-entry?
73 manifest-entry-name
74 manifest-entry-version
75 manifest-entry-output
76 manifest-entry-item
77 manifest-entry-dependencies
78 manifest-entry-search-paths
79 manifest-entry-parent
80
81 manifest-pattern
82 manifest-pattern?
83 manifest-pattern-name
84 manifest-pattern-version
85 manifest-pattern-output
86
87 manifest-remove
88 manifest-add
89 manifest-lookup
90 manifest-installed?
91 manifest-matching-entries
92
93 manifest-transaction
94 manifest-transaction?
95 manifest-transaction-install
96 manifest-transaction-remove
97 manifest-transaction-install-entry
98 manifest-transaction-remove-pattern
99 manifest-transaction-null?
100 manifest-transaction-removal-candidate?
101 manifest-perform-transaction
102 manifest-transaction-effects
103
104 profile-manifest
105 package->manifest-entry
106 packages->manifest
107 ca-certificate-bundle
108 %default-profile-hooks
109 profile-derivation
110
111 generation-number
112 generation-numbers
113 profile-generations
114 relative-generation-spec->number
115 relative-generation
116 previous-generation-number
117 generation-time
118 generation-file-name
119 switch-to-generation
120 roll-back
121 delete-generation))
122
123 ;;; Commentary:
124 ;;;
125 ;;; Tools to create and manipulate profiles---i.e., the representation of a
126 ;;; set of installed packages.
127 ;;;
128 ;;; Code:
129
130 \f
131 ;;;
132 ;;; Condition types.
133 ;;;
134
135 (define-condition-type &profile-error &error
136 profile-error?
137 (profile profile-error-profile))
138
139 (define-condition-type &profile-not-found-error &profile-error
140 profile-not-found-error?)
141
142 (define-condition-type &profile-collision-error &error
143 profile-collision-error?
144 (entry profile-collision-error-entry) ;<manifest-entry>
145 (conflict profile-collision-error-conflict)) ;<manifest-entry>
146
147 (define-condition-type &missing-generation-error &profile-error
148 missing-generation-error?
149 (generation missing-generation-error-generation))
150
151 \f
152 ;;;
153 ;;; Manifests.
154 ;;;
155
156 (define-record-type <manifest>
157 (manifest entries)
158 manifest?
159 (entries manifest-entries)) ; list of <manifest-entry>
160
161 ;; Convenient alias, to avoid name clashes.
162 (define make-manifest manifest)
163
164 (define-record-type* <manifest-entry> manifest-entry
165 make-manifest-entry
166 manifest-entry?
167 (name manifest-entry-name) ; string
168 (version manifest-entry-version) ; string
169 (output manifest-entry-output ; string
170 (default "out"))
171 (item manifest-entry-item) ; package | store path
172 (dependencies manifest-entry-dependencies ; <manifest-entry>*
173 (default '()))
174 (search-paths manifest-entry-search-paths ; search-path-specification*
175 (default '()))
176 (parent manifest-entry-parent ; promise (#f | <manifest-entry>)
177 (default (delay #f))))
178
179 (define-record-type* <manifest-pattern> manifest-pattern
180 make-manifest-pattern
181 manifest-pattern?
182 (name manifest-pattern-name) ; string
183 (version manifest-pattern-version ; string | #f
184 (default #f))
185 (output manifest-pattern-output ; string | #f
186 (default "out")))
187
188 (define (manifest-transitive-entries manifest)
189 "Return the entries of MANIFEST along with their propagated inputs,
190 recursively."
191 (let loop ((entries (manifest-entries manifest))
192 (result '())
193 (visited (set))) ;compare with 'equal?'
194 (match entries
195 (()
196 (reverse result))
197 ((head . tail)
198 (if (set-contains? visited head)
199 (loop tail result visited)
200 (loop (append (manifest-entry-dependencies head)
201 tail)
202 (cons head result)
203 (set-insert head visited)))))))
204
205 (define (profile-manifest profile)
206 "Return the PROFILE's manifest."
207 (let ((file (string-append profile "/manifest")))
208 (if (file-exists? file)
209 (call-with-input-file file read-manifest)
210 (manifest '()))))
211
212 (define (manifest-entry-lookup manifest)
213 "Return a lookup procedure for the entries of MANIFEST. The lookup
214 procedure takes two arguments: the entry name and output."
215 (define mapping
216 (let loop ((entries (manifest-entries manifest))
217 (mapping vlist-null))
218 (fold (lambda (entry result)
219 (vhash-cons (cons (manifest-entry-name entry)
220 (manifest-entry-output entry))
221 entry
222 (loop (manifest-entry-dependencies entry)
223 result)))
224 mapping
225 entries)))
226
227 (lambda (name output)
228 (match (vhash-assoc (cons name output) mapping)
229 ((_ . entry) entry)
230 (#f #f))))
231
232 (define* (lower-manifest-entry entry system #:key target)
233 "Lower ENTRY for SYSTEM and TARGET such that its 'item' field is a store
234 file name."
235 (let ((item (manifest-entry-item entry)))
236 (if (string? item)
237 (with-monad %store-monad
238 (return entry))
239 (mlet %store-monad ((drv (lower-object item system
240 #:target target))
241 (output -> (manifest-entry-output entry)))
242 (return (manifest-entry
243 (inherit entry)
244 (item (derivation->output-path drv output))))))))
245
246 (define* (check-for-collisions manifest system #:key target)
247 "Check whether the entries of MANIFEST conflict with one another; raise a
248 '&profile-collision-error' when a conflict is encountered."
249 (define lookup
250 (manifest-entry-lookup manifest))
251
252 (with-monad %store-monad
253 (foldm %store-monad
254 (lambda (entry result)
255 (match (lookup (manifest-entry-name entry)
256 (manifest-entry-output entry))
257 ((? manifest-entry? second) ;potential conflict
258 (mlet %store-monad ((first (lower-manifest-entry entry system
259 #:target
260 target))
261 (second (lower-manifest-entry second system
262 #:target
263 target)))
264 (if (string=? (manifest-entry-item first)
265 (manifest-entry-item second))
266 (return result)
267 (raise (condition
268 (&profile-collision-error
269 (entry first)
270 (conflict second)))))))
271 (#f ;no conflict
272 (return result))))
273 #t
274 (manifest-transitive-entries manifest))))
275
276 (define* (package->manifest-entry package #:optional (output "out")
277 #:key (parent (delay #f)))
278 "Return a manifest entry for the OUTPUT of package PACKAGE."
279 ;; For each dependency, keep a promise pointing to its "parent" entry.
280 (letrec* ((deps (map (match-lambda
281 ((label package)
282 (package->manifest-entry package
283 #:parent (delay entry)))
284 ((label package output)
285 (package->manifest-entry package output
286 #:parent (delay entry))))
287 (package-propagated-inputs package)))
288 (entry (manifest-entry
289 (name (package-name package))
290 (version (package-version package))
291 (output output)
292 (item package)
293 (dependencies (delete-duplicates deps))
294 (search-paths
295 (package-transitive-native-search-paths package))
296 (parent parent))))
297 entry))
298
299 (define (packages->manifest packages)
300 "Return a list of manifest entries, one for each item listed in PACKAGES.
301 Elements of PACKAGES can be either package objects or package/string tuples
302 denoting a specific output of a package."
303 (manifest
304 (map (match-lambda
305 ((package output)
306 (package->manifest-entry package output))
307 ((? package? package)
308 (package->manifest-entry package)))
309 packages)))
310
311 (define (manifest->gexp manifest)
312 "Return a representation of MANIFEST as a gexp."
313 (define (entry->gexp entry)
314 (match entry
315 (($ <manifest-entry> name version output (? string? path)
316 (deps ...) (search-paths ...))
317 #~(#$name #$version #$output #$path
318 (propagated-inputs #$(map entry->gexp deps))
319 (search-paths #$(map search-path-specification->sexp
320 search-paths))))
321 (($ <manifest-entry> name version output (? package? package)
322 (deps ...) (search-paths ...))
323 #~(#$name #$version #$output
324 (ungexp package (or output "out"))
325 (propagated-inputs #$(map entry->gexp deps))
326 (search-paths #$(map search-path-specification->sexp
327 search-paths))))))
328
329 (match manifest
330 (($ <manifest> (entries ...))
331 #~(manifest (version 3)
332 (packages #$(map entry->gexp entries))))))
333
334 (define (find-package name version)
335 "Return a package from the distro matching NAME and possibly VERSION. This
336 procedure is here for backward-compatibility and will eventually vanish."
337 (define find-best-packages-by-name ;break abstractions
338 (module-ref (resolve-interface '(gnu packages))
339 'find-best-packages-by-name))
340
341 ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; the
342 ;; former traverses the module tree only once and then allows for efficient
343 ;; access via a vhash.
344 (match (find-best-packages-by-name name version)
345 ((p _ ...) p)
346 (_
347 (match (find-best-packages-by-name name #f)
348 ((p _ ...) p)
349 (_ #f)))))
350
351 (define (sexp->manifest sexp)
352 "Parse SEXP as a manifest."
353 (define (infer-search-paths name version)
354 ;; Infer the search path specifications for NAME-VERSION by looking up a
355 ;; same-named package in the distro. Useful for the old manifest formats
356 ;; that did not store search path info.
357 (let ((package (find-package name version)))
358 (if package
359 (package-native-search-paths package)
360 '())))
361
362 (define (infer-dependency item parent)
363 ;; Return a <manifest-entry> for ITEM.
364 (let-values (((name version)
365 (package-name->name+version
366 (store-path-package-name item))))
367 (manifest-entry
368 (name name)
369 (version version)
370 (item item)
371 (parent parent))))
372
373 (define* (sexp->manifest-entry sexp #:optional (parent (delay #f)))
374 (match sexp
375 ((name version output path
376 ('propagated-inputs deps)
377 ('search-paths search-paths)
378 extra-stuff ...)
379 ;; For each of DEPS, keep a promise pointing to ENTRY.
380 (letrec* ((deps* (map (cut sexp->manifest-entry <> (delay entry))
381 deps))
382 (entry (manifest-entry
383 (name name)
384 (version version)
385 (output output)
386 (item path)
387 (dependencies deps*)
388 (search-paths (map sexp->search-path-specification
389 search-paths))
390 (parent parent))))
391 entry))))
392
393 (match sexp
394 (('manifest ('version 0)
395 ('packages ((name version output path) ...)))
396 (manifest
397 (map (lambda (name version output path)
398 (manifest-entry
399 (name name)
400 (version version)
401 (output output)
402 (item path)
403 (search-paths (infer-search-paths name version))))
404 name version output path)))
405
406 ;; Version 1 adds a list of propagated inputs to the
407 ;; name/version/output/path tuples.
408 (('manifest ('version 1)
409 ('packages ((name version output path deps) ...)))
410 (manifest
411 (map (lambda (name version output path deps)
412 ;; Up to Guix 0.7 included, dependencies were listed as ("gmp"
413 ;; "/gnu/store/...-gmp") for instance. Discard the 'label' in
414 ;; such lists.
415 (let ((deps (match deps
416 (((labels directories) ...)
417 directories)
418 ((directories ...)
419 directories))))
420 (letrec* ((deps* (map (cute infer-dependency <> (delay entry))
421 deps))
422 (entry (manifest-entry
423 (name name)
424 (version version)
425 (output output)
426 (item path)
427 (dependencies deps*)
428 (search-paths
429 (infer-search-paths name version)))))
430 entry)))
431 name version output path deps)))
432
433 ;; Version 2 adds search paths and is slightly more verbose.
434 (('manifest ('version 2 minor-version ...)
435 ('packages ((name version output path
436 ('propagated-inputs deps)
437 ('search-paths search-paths)
438 extra-stuff ...)
439 ...)))
440 (manifest
441 (map (lambda (name version output path deps search-paths)
442 (letrec* ((deps* (map (cute infer-dependency <> (delay entry))
443 deps))
444 (entry (manifest-entry
445 (name name)
446 (version version)
447 (output output)
448 (item path)
449 (dependencies deps*)
450 (search-paths
451 (map sexp->search-path-specification
452 search-paths)))))
453 entry))
454 name version output path deps search-paths)))
455
456 ;; Version 3 represents DEPS as full-blown manifest entries.
457 (('manifest ('version 3 minor-version ...)
458 ('packages (entries ...)))
459 (manifest (map sexp->manifest-entry entries)))
460 (_
461 (raise (condition
462 (&message (message "unsupported manifest format")))))))
463
464 (define (read-manifest port)
465 "Return the packages listed in MANIFEST."
466 (sexp->manifest (read port)))
467
468 (define (entry-predicate pattern)
469 "Return a procedure that returns #t when passed a manifest entry that
470 matches NAME/OUTPUT/VERSION. OUTPUT and VERSION may be #f, in which case they
471 are ignored."
472 (match pattern
473 (($ <manifest-pattern> name version output)
474 (match-lambda
475 (($ <manifest-entry> entry-name entry-version entry-output)
476 (and (string=? entry-name name)
477 (or (not entry-output) (not output)
478 (string=? entry-output output))
479 (or (not version)
480 (string=? entry-version version))))))))
481
482 (define (manifest-remove manifest patterns)
483 "Remove entries for each of PATTERNS from MANIFEST. Each item in PATTERNS
484 must be a manifest-pattern."
485 (define (remove-entry pattern lst)
486 (remove (entry-predicate pattern) lst))
487
488 (make-manifest (fold remove-entry
489 (manifest-entries manifest)
490 patterns)))
491
492 (define (manifest-add manifest entries)
493 "Add a list of manifest ENTRIES to MANIFEST and return new manifest.
494 Remove MANIFEST entries that have the same name and output as ENTRIES."
495 (define (same-entry? entry name output)
496 (match entry
497 (($ <manifest-entry> entry-name _ entry-output _ ...)
498 (and (equal? name entry-name)
499 (equal? output entry-output)))))
500
501 (make-manifest
502 (append entries
503 (fold (lambda (entry result)
504 (match entry
505 (($ <manifest-entry> name _ out _ ...)
506 (filter (negate (cut same-entry? <> name out))
507 result))))
508 (manifest-entries manifest)
509 entries))))
510
511 (define (manifest-lookup manifest pattern)
512 "Return the first item of MANIFEST that matches PATTERN, or #f if there is
513 no match.."
514 (find (entry-predicate pattern)
515 (manifest-entries manifest)))
516
517 (define (manifest-installed? manifest pattern)
518 "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern),
519 #f otherwise."
520 (->bool (manifest-lookup manifest pattern)))
521
522 (define (manifest-matching-entries manifest patterns)
523 "Return all the entries of MANIFEST that match one of the PATTERNS."
524 (define predicates
525 (map entry-predicate patterns))
526
527 (define (matches? entry)
528 (any (lambda (pred)
529 (pred entry))
530 predicates))
531
532 (filter matches? (manifest-entries manifest)))
533
534 \f
535 ;;;
536 ;;; Manifest transactions.
537 ;;;
538
539 (define-record-type* <manifest-transaction> manifest-transaction
540 make-manifest-transaction
541 manifest-transaction?
542 (install manifest-transaction-install ; list of <manifest-entry>
543 (default '()))
544 (remove manifest-transaction-remove ; list of <manifest-pattern>
545 (default '())))
546
547 (define (manifest-transaction-install-entry entry transaction)
548 "Augment TRANSACTION's set of installed packages with ENTRY, a
549 <manifest-entry>."
550 (manifest-transaction
551 (inherit transaction)
552 (install
553 (cons entry (manifest-transaction-install transaction)))))
554
555 (define (manifest-transaction-remove-pattern pattern transaction)
556 "Add PATTERN to TRANSACTION's list of packages to remove."
557 (manifest-transaction
558 (inherit transaction)
559 (remove
560 (cons pattern (manifest-transaction-remove transaction)))))
561
562 (define (manifest-transaction-null? transaction)
563 "Return true if TRANSACTION has no effect---i.e., it neither installs nor
564 remove software."
565 (match transaction
566 (($ <manifest-transaction> () ()) #t)
567 (($ <manifest-transaction> _ _) #f)))
568
569 (define (manifest-transaction-removal-candidate? entry transaction)
570 "Return true if ENTRY is a candidate for removal in TRANSACTION."
571 (any (lambda (pattern)
572 ((entry-predicate pattern) entry))
573 (manifest-transaction-remove transaction)))
574
575 (define (manifest-transaction-effects manifest transaction)
576 "Compute the effect of applying TRANSACTION to MANIFEST. Return 4 values:
577 the list of packages that would be removed, installed, upgraded, or downgraded
578 when applying TRANSACTION to MANIFEST. Upgrades are represented as pairs
579 where the head is the entry being upgraded and the tail is the entry that will
580 replace it."
581 (define (manifest-entry->pattern entry)
582 (manifest-pattern
583 (name (manifest-entry-name entry))
584 (output (manifest-entry-output entry))))
585
586 (let loop ((input (manifest-transaction-install transaction))
587 (install '())
588 (upgrade '())
589 (downgrade '()))
590 (match input
591 (()
592 (let ((remove (manifest-transaction-remove transaction)))
593 (values (manifest-matching-entries manifest remove)
594 (reverse install) (reverse upgrade) (reverse downgrade))))
595 ((entry rest ...)
596 ;; Check whether installing ENTRY corresponds to the installation of a
597 ;; new package or to an upgrade.
598
599 ;; XXX: When the exact same output directory is installed, we're not
600 ;; really upgrading anything. Add a check for that case.
601 (let* ((pattern (manifest-entry->pattern entry))
602 (previous (manifest-lookup manifest pattern))
603 (newer? (and previous
604 (version>=? (manifest-entry-version entry)
605 (manifest-entry-version previous)))))
606 (loop rest
607 (if previous install (cons entry install))
608 (if (and previous newer?)
609 (alist-cons previous entry upgrade)
610 upgrade)
611 (if (and previous (not newer?))
612 (alist-cons previous entry downgrade)
613 downgrade)))))))
614
615 (define (manifest-perform-transaction manifest transaction)
616 "Perform TRANSACTION on MANIFEST and return the new manifest."
617 (let ((install (manifest-transaction-install transaction))
618 (remove (manifest-transaction-remove transaction)))
619 (manifest-add (manifest-remove manifest remove)
620 install)))
621
622 \f
623 ;;;
624 ;;; Profiles.
625 ;;;
626
627 (define (manifest-inputs manifest)
628 "Return a list of <gexp-input> objects for MANIFEST."
629 (define entry->input
630 (match-lambda
631 (($ <manifest-entry> name version output thing deps)
632 ;; THING may be a package or a file name. In the latter case, assume
633 ;; it's already valid.
634 (cons (gexp-input thing output)
635 (append-map entry->input deps)))))
636
637 (append-map entry->input (manifest-entries manifest)))
638
639 (define* (manifest-lookup-package manifest name #:optional version)
640 "Return as a monadic value the first package or store path referenced by
641 MANIFEST that is named NAME and optionally has the given VERSION prefix, or #f
642 if not found."
643 ;; Return as a monadic value the package or store path referenced by the
644 ;; manifest ENTRY, or #f if not referenced.
645 (define (entry-lookup-package entry)
646 (define (find-among-inputs inputs)
647 (find (lambda (input)
648 (and (package? input)
649 (equal? name (package-name input))
650 (if version
651 (string-prefix? version (package-version input))
652 #t)))
653 inputs))
654 (define (find-among-store-items items)
655 (find (lambda (item)
656 (let-values (((name* version*)
657 (package-name->name+version
658 (store-path-package-name item))))
659 (and (string=? name name*)
660 (if version
661 (string-prefix? version version*)
662 #t))))
663 items))
664
665 (with-monad %store-monad
666 (match (manifest-entry-item entry)
667 ((? package? package)
668 (match (cons (list (package-name package) package)
669 (package-transitive-inputs package))
670 (((labels inputs . _) ...)
671 (return (find-among-inputs inputs)))))
672 ((? string? item)
673 (mlet %store-monad ((refs (references* item)))
674 (return (find-among-store-items refs)))))))
675
676 (anym %store-monad
677 entry-lookup-package (manifest-entries manifest)))
678
679 (define (info-dir-file manifest)
680 "Return a derivation that builds the 'dir' file for all the entries of
681 MANIFEST."
682 (define texinfo ;lazy reference
683 (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
684 (define gzip ;lazy reference
685 (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
686
687 (define build
688 (with-imported-modules '((guix build utils))
689 #~(begin
690 (use-modules (guix build utils)
691 (srfi srfi-1) (srfi srfi-26)
692 (ice-9 ftw))
693
694 (define (info-file? file)
695 (or (string-suffix? ".info" file)
696 (string-suffix? ".info.gz" file)))
697
698 (define (info-files top)
699 (let ((infodir (string-append top "/share/info")))
700 (map (cut string-append infodir "/" <>)
701 (or (scandir infodir info-file?) '()))))
702
703 (define (install-info info)
704 (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
705 (zero?
706 (system* (string-append #+texinfo "/bin/install-info") "--silent"
707 info (string-append #$output "/share/info/dir"))))
708
709 (mkdir-p (string-append #$output "/share/info"))
710 (exit (every install-info
711 (append-map info-files
712 '#$(manifest-inputs manifest)))))))
713
714 (gexp->derivation "info-dir" build
715 #:local-build? #t
716 #:substitutable? #f))
717
718 (define (ghc-package-cache-file manifest)
719 "Return a derivation that builds the GHC 'package.cache' file for all the
720 entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
721 (define ghc ;lazy reference
722 (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
723
724 (define build
725 (with-imported-modules '((guix build utils))
726 #~(begin
727 (use-modules (guix build utils)
728 (srfi srfi-1) (srfi srfi-26)
729 (ice-9 ftw))
730
731 (define ghc-name-version
732 (let* ((base (basename #+ghc)))
733 (string-drop base
734 (+ 1 (string-index base #\-)))))
735
736 (define db-subdir
737 (string-append "lib/" ghc-name-version "/package.conf.d"))
738
739 (define db-dir
740 (string-append #$output "/" db-subdir))
741
742 (define (conf-files top)
743 (let ((db (string-append top "/" db-subdir)))
744 (if (file-exists? db)
745 (find-files db "\\.conf$")
746 '())))
747
748 (define (copy-conf-file conf)
749 (let ((base (basename conf)))
750 (copy-file conf (string-append db-dir "/" base))))
751
752 (system* (string-append #+ghc "/bin/ghc-pkg") "init" db-dir)
753 (for-each copy-conf-file
754 (append-map conf-files
755 (delete-duplicates
756 '#$(manifest-inputs manifest))))
757 (let ((success
758 (zero?
759 (system* (string-append #+ghc "/bin/ghc-pkg") "recache"
760 (string-append "--package-db=" db-dir)))))
761 (for-each delete-file (find-files db-dir "\\.conf$"))
762 (exit success)))))
763
764 (with-monad %store-monad
765 ;; Don't depend on GHC when there's nothing to do.
766 (if (any (cut string-prefix? "ghc" <>)
767 (map manifest-entry-name (manifest-entries manifest)))
768 (gexp->derivation "ghc-package-cache" build
769 #:local-build? #t
770 #:substitutable? #f)
771 (return #f))))
772
773 (define (ca-certificate-bundle manifest)
774 "Return a derivation that builds a single-file bundle containing the CA
775 certificates in the /etc/ssl/certs sub-directories of the packages in
776 MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
777 ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
778 ;; for a discussion.
779
780 (define glibc-utf8-locales ;lazy reference
781 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
782
783 (define build
784 (with-imported-modules '((guix build utils))
785 #~(begin
786 (use-modules (guix build utils)
787 (rnrs io ports)
788 (srfi srfi-1)
789 (srfi srfi-26)
790 (ice-9 ftw)
791 (ice-9 match))
792
793 (define (pem-file? file)
794 (string-suffix? ".pem" file))
795
796 (define (ca-files top)
797 (let ((cert-dir (string-append top "/etc/ssl/certs")))
798 (map (cut string-append cert-dir "/" <>)
799 (or (scandir cert-dir pem-file?) '()))))
800
801 (define (concatenate-files files result)
802 "Make RESULT the concatenation of all of FILES."
803 (define (dump file port)
804 (display (call-with-input-file file get-string-all)
805 port)
806 (newline port)) ;required, see <https://bugs.debian.org/635570>
807
808 (call-with-output-file result
809 (lambda (port)
810 (for-each (cut dump <> port) files))))
811
812 ;; Some file names in the NSS certificates are UTF-8 encoded so
813 ;; install a UTF-8 locale.
814 (setenv "LOCPATH"
815 (string-append #+glibc-utf8-locales "/lib/locale/"
816 #+(version-major+minor
817 (package-version glibc-utf8-locales))))
818 (setlocale LC_ALL "en_US.utf8")
819
820 (match (append-map ca-files '#$(manifest-inputs manifest))
821 (()
822 ;; Since there are no CA files, just create an empty directory. Do
823 ;; not create the etc/ssl/certs sub-directory, since that would
824 ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
825 ;; defined.
826 (mkdir #$output)
827 #t)
828 ((ca-files ...)
829 (let ((result (string-append #$output "/etc/ssl/certs")))
830 (mkdir-p result)
831 (concatenate-files ca-files
832 (string-append result
833 "/ca-certificates.crt"))
834 #t))))))
835
836 (gexp->derivation "ca-certificate-bundle" build
837 #:local-build? #t
838 #:substitutable? #f))
839
840 (define (gtk-icon-themes manifest)
841 "Return a derivation that unions all icon themes from manifest entries and
842 creates the GTK+ 'icon-theme.cache' file for each theme."
843 (define gtk+ ; lazy reference
844 (module-ref (resolve-interface '(gnu packages gtk)) 'gtk+))
845
846 (mlet %store-monad ((%gtk+ (manifest-lookup-package manifest "gtk+"))
847 ;; XXX: Can't use gtk-update-icon-cache corresponding
848 ;; to the gtk+ referenced by 'manifest'. Because
849 ;; '%gtk+' can be either a package or store path, and
850 ;; there's no way to get the "bin" output for the later.
851 (gtk-update-icon-cache
852 -> #~(string-append #+gtk+:bin
853 "/bin/gtk-update-icon-cache")))
854
855 (define build
856 (with-imported-modules '((guix build utils)
857 (guix build union)
858 (guix build profiles)
859 (guix search-paths)
860 (guix records))
861 #~(begin
862 (use-modules (guix build utils)
863 (guix build union)
864 (guix build profiles)
865 (srfi srfi-26)
866 (ice-9 ftw))
867
868 (let* ((destdir (string-append #$output "/share/icons"))
869 (icondirs (filter file-exists?
870 (map (cut string-append <> "/share/icons")
871 '#$(manifest-inputs manifest)))))
872
873 ;; Union all the icons.
874 (mkdir-p (string-append #$output "/share"))
875 (union-build destdir icondirs
876 #:log-port (%make-void-port "w"))
877
878 ;; Update the 'icon-theme.cache' file for each icon theme.
879 (for-each
880 (lambda (theme)
881 (let ((dir (string-append destdir "/" theme)))
882 ;; Occasionally DESTDIR contains plain files, such as
883 ;; "abiword_48.png". Ignore these.
884 (when (file-is-directory? dir)
885 (ensure-writable-directory dir)
886 (system* #+gtk-update-icon-cache "-t" dir "--quiet"))))
887 (scandir destdir (negate (cut member <> '("." "..")))))))))
888
889 ;; Don't run the hook when there's nothing to do.
890 (if %gtk+
891 (gexp->derivation "gtk-icon-themes" build
892 #:local-build? #t
893 #:substitutable? #f)
894 (return #f))))
895
896 (define (gtk-im-modules manifest)
897 "Return a derivation that builds the cache files for input method modules
898 for both major versions of GTK+."
899
900 (mlet %store-monad ((gtk+ (manifest-lookup-package manifest "gtk+" "3"))
901 (gtk+-2 (manifest-lookup-package manifest "gtk+" "2")))
902
903 (define (build gtk gtk-version query)
904 (let ((major (string-take gtk-version 1)))
905 (with-imported-modules '((guix build utils)
906 (guix build union)
907 (guix build profiles)
908 (guix search-paths)
909 (guix records))
910 #~(begin
911 (use-modules (guix build utils)
912 (guix build union)
913 (guix build profiles)
914 (ice-9 popen)
915 (srfi srfi-1)
916 (srfi srfi-26))
917
918 (let* ((prefix (string-append "/lib/gtk-" #$major ".0/"
919 #$gtk-version))
920 (destdir (string-append #$output prefix))
921 (moddirs (cons (string-append #$gtk prefix "/immodules")
922 (filter file-exists?
923 (map (cut string-append <> prefix "/immodules")
924 '#$(manifest-inputs manifest)))))
925 (modules (append-map (cut find-files <> "\\.so$")
926 moddirs)))
927
928 ;; Generate a new immodules cache file.
929 (mkdir-p (string-append #$output prefix))
930 (let ((pipe (apply open-pipe* OPEN_READ #$query modules))
931 (outfile (string-append #$output prefix
932 "/immodules-gtk" #$major ".cache")))
933 (dynamic-wind
934 (const #t)
935 (lambda ()
936 (call-with-output-file outfile
937 (lambda (out)
938 (while (not (eof-object? (peek-char pipe)))
939 (write-char (read-char pipe) out))))
940 #t)
941 (lambda ()
942 (close-pipe pipe)))))))))
943
944 ;; Don't run the hook when there's nothing to do.
945 (let* ((pkg-gtk+ (module-ref ; lazy reference
946 (resolve-interface '(gnu packages gtk)) 'gtk+))
947 (gexp #~(begin
948 #$(if gtk+
949 (build
950 gtk+ "3.0.0"
951 ;; Use 'gtk-query-immodules-3.0' from the 'bin'
952 ;; output of latest gtk+ package.
953 #~(string-append
954 #$pkg-gtk+:bin "/bin/gtk-query-immodules-3.0"))
955 #t)
956 #$(if gtk+-2
957 (build
958 gtk+-2 "2.10.0"
959 #~(string-append
960 #$gtk+-2 "/bin/gtk-query-immodules-2.0"))
961 #t))))
962 (if (or gtk+ gtk+-2)
963 (gexp->derivation "gtk-im-modules" gexp
964 #:local-build? #t
965 #:substitutable? #f)
966 (return #f)))))
967
968 (define (xdg-desktop-database manifest)
969 "Return a derivation that builds the @file{mimeinfo.cache} database from
970 desktop files. It's used to query what applications can handle a given
971 MIME type."
972 (define desktop-file-utils ; lazy reference
973 (module-ref (resolve-interface '(gnu packages freedesktop))
974 'desktop-file-utils))
975
976 (mlet %store-monad ((glib
977 (manifest-lookup-package
978 manifest "glib")))
979 (define build
980 (with-imported-modules '((guix build utils)
981 (guix build union))
982 #~(begin
983 (use-modules (srfi srfi-26)
984 (guix build utils)
985 (guix build union))
986 (let* ((destdir (string-append #$output "/share/applications"))
987 (appdirs (filter file-exists?
988 (map (cut string-append <>
989 "/share/applications")
990 '#$(manifest-inputs manifest))))
991 (update-desktop-database (string-append
992 #+desktop-file-utils
993 "/bin/update-desktop-database")))
994 (mkdir-p (string-append #$output "/share"))
995 (union-build destdir appdirs
996 #:log-port (%make-void-port "w"))
997 (exit (zero? (system* update-desktop-database destdir)))))))
998
999 ;; Don't run the hook when 'glib' is not referenced.
1000 (if glib
1001 (gexp->derivation "xdg-desktop-database" build
1002 #:local-build? #t
1003 #:substitutable? #f)
1004 (return #f))))
1005
1006 (define (xdg-mime-database manifest)
1007 "Return a derivation that builds the @file{mime.cache} database from manifest
1008 entries. It's used to query the MIME type of a given file."
1009 (define shared-mime-info ; lazy reference
1010 (module-ref (resolve-interface '(gnu packages gnome)) 'shared-mime-info))
1011
1012 (mlet %store-monad ((glib
1013 (manifest-lookup-package
1014 manifest "glib")))
1015 (define build
1016 (with-imported-modules '((guix build utils)
1017 (guix build union))
1018 #~(begin
1019 (use-modules (srfi srfi-26)
1020 (guix build utils)
1021 (guix build union))
1022 (let* ((datadir (string-append #$output "/share"))
1023 (destdir (string-append datadir "/mime"))
1024 (pkgdirs (filter file-exists?
1025 (map (cut string-append <>
1026 "/share/mime/packages")
1027 (cons #+shared-mime-info
1028 '#$(manifest-inputs manifest)))))
1029 (update-mime-database (string-append
1030 #+shared-mime-info
1031 "/bin/update-mime-database")))
1032 (mkdir-p destdir)
1033 (union-build (string-append destdir "/packages") pkgdirs
1034 #:log-port (%make-void-port "w"))
1035 (setenv "XDG_DATA_HOME" datadir)
1036 (exit (zero? (system* update-mime-database destdir)))))))
1037
1038 ;; Don't run the hook when there are no GLib based applications.
1039 (if glib
1040 (gexp->derivation "xdg-mime-database" build
1041 #:local-build? #t
1042 #:substitutable? #f)
1043 (return #f))))
1044
1045 ;; Several font packages may install font files into same directory, so
1046 ;; fonts.dir and fonts.scale file should be generated here, instead of in
1047 ;; packages.
1048 (define (fonts-dir-file manifest)
1049 "Return a derivation that builds the @file{fonts.dir} and @file{fonts.scale}
1050 files for the fonts of the @var{manifest} entries."
1051 (define mkfontscale
1052 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontscale))
1053
1054 (define mkfontdir
1055 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontdir))
1056
1057 (define build
1058 #~(begin
1059 (use-modules (srfi srfi-26)
1060 (guix build utils)
1061 (guix build union))
1062 (let ((fonts-dirs (filter file-exists?
1063 (map (cut string-append <>
1064 "/share/fonts")
1065 '#$(manifest-inputs manifest)))))
1066 (mkdir #$output)
1067 (if (null? fonts-dirs)
1068 (exit #t)
1069 (let* ((share-dir (string-append #$output "/share"))
1070 (fonts-dir (string-append share-dir "/fonts"))
1071 (mkfontscale (string-append #+mkfontscale
1072 "/bin/mkfontscale"))
1073 (mkfontdir (string-append #+mkfontdir
1074 "/bin/mkfontdir"))
1075 (empty-file? (lambda (filename)
1076 (call-with-ascii-input-file filename
1077 (lambda (p)
1078 (eqv? #\0 (read-char p))))))
1079 (fonts-dir-file "fonts.dir")
1080 (fonts-scale-file "fonts.scale"))
1081 (mkdir-p share-dir)
1082 ;; Create all sub-directories, because we may create fonts.dir
1083 ;; and fonts.scale files in the sub-directories.
1084 (union-build fonts-dir fonts-dirs
1085 #:log-port (%make-void-port "w")
1086 #:create-all-directories? #t)
1087 (let ((directories (find-files fonts-dir
1088 (lambda (file stat)
1089 (eq? 'directory (stat:type stat)))
1090 #:directories? #t)))
1091 (for-each (lambda (dir)
1092 (with-directory-excursion dir
1093 (when (file-exists? fonts-scale-file)
1094 (delete-file fonts-scale-file))
1095 (when (file-exists? fonts-dir-file)
1096 (delete-file fonts-dir-file))
1097 (unless (and (zero? (system* mkfontscale))
1098 (zero? (system* mkfontdir)))
1099 (exit #f))
1100 (when (and (file-exists? fonts-scale-file)
1101 (empty-file? fonts-scale-file))
1102 (delete-file fonts-scale-file))
1103 (when (and (file-exists? fonts-dir-file)
1104 (empty-file? fonts-dir-file))
1105 (delete-file fonts-dir-file))))
1106 directories)))))))
1107
1108 (gexp->derivation "fonts-dir" build
1109 #:modules '((guix build utils)
1110 (guix build union)
1111 (srfi srfi-26))
1112 #:local-build? #t
1113 #:substitutable? #f))
1114
1115 (define (manual-database manifest)
1116 "Return a derivation that builds the manual page database (\"mandb\") for
1117 the entries in MANIFEST."
1118 (define gdbm-ffi
1119 (module-ref (resolve-interface '(gnu packages guile))
1120 'guile-gdbm-ffi))
1121
1122 (define zlib
1123 (module-ref (resolve-interface '(gnu packages compression)) 'zlib))
1124
1125 (define config.scm
1126 (scheme-file "config.scm"
1127 #~(begin
1128 (define-module (guix config)
1129 #:export (%libz))
1130
1131 (define %libz
1132 #+(file-append zlib "/lib/libz")))))
1133
1134 (define modules
1135 (cons `((guix config) => ,config.scm)
1136 (delete '(guix config)
1137 (source-module-closure `((guix build utils)
1138 (guix man-db))))))
1139
1140 (define build
1141 (with-imported-modules modules
1142 #~(begin
1143 (add-to-load-path (string-append #$gdbm-ffi "/share/guile/site/"
1144 (effective-version)))
1145
1146 (use-modules (guix man-db)
1147 (guix build utils)
1148 (srfi srfi-1)
1149 (srfi srfi-19))
1150
1151 (define (compute-entries)
1152 (append-map (lambda (directory)
1153 (let ((man (string-append directory "/share/man")))
1154 (if (directory-exists? man)
1155 (mandb-entries man)
1156 '())))
1157 '#$(manifest-inputs manifest)))
1158
1159 (define man-directory
1160 (string-append #$output "/share/man"))
1161
1162 (mkdir-p man-directory)
1163
1164 (format #t "Creating manual page database...~%")
1165 (force-output)
1166 (let* ((start (current-time))
1167 (entries (compute-entries))
1168 (_ (write-mandb-database (string-append man-directory
1169 "/index.db")
1170 entries))
1171 (duration (time-difference (current-time) start)))
1172 (format #t "~a entries processed in ~,1f s~%"
1173 (length entries)
1174 (+ (time-second duration)
1175 (* (time-nanosecond duration) (expt 10 -9))))
1176 (force-output)))))
1177
1178 (gexp->derivation "manual-database" build
1179
1180 ;; Work around GDBM 1.13 issue whereby uninitialized bytes
1181 ;; get written to disk:
1182 ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29654#23>.
1183 #:env-vars `(("MALLOC_PERTURB_" . "1"))
1184
1185 #:local-build? #t))
1186
1187 (define %default-profile-hooks
1188 ;; This is the list of derivation-returning procedures that are called by
1189 ;; default when making a non-empty profile.
1190 (list info-dir-file
1191 manual-database
1192 fonts-dir-file
1193 ghc-package-cache-file
1194 ca-certificate-bundle
1195 gtk-icon-themes
1196 gtk-im-modules
1197 xdg-desktop-database
1198 xdg-mime-database))
1199
1200 (define* (profile-derivation manifest
1201 #:key
1202 (hooks %default-profile-hooks)
1203 (locales? #t)
1204 (allow-collisions? #f)
1205 system target)
1206 "Return a derivation that builds a profile (aka. 'user environment') with
1207 the given MANIFEST. The profile includes additional derivations returned by
1208 the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc.
1209 Unless ALLOW-COLLISIONS? is true, a '&profile-collision-error' is raised if
1210 entries in MANIFEST collide (for instance if there are two same-name packages
1211 with a different version number.)
1212
1213 When LOCALES? is true, the build is performed under a UTF-8 locale; this adds
1214 a dependency on the 'glibc-utf8-locales' package.
1215
1216 When TARGET is true, it must be a GNU triplet, and the packages in MANIFEST
1217 are cross-built for TARGET."
1218 (mlet* %store-monad ((system (if system
1219 (return system)
1220 (current-system)))
1221 (ok? (if allow-collisions?
1222 (return #t)
1223 (check-for-collisions manifest system
1224 #:target target)))
1225 (extras (if (null? (manifest-entries manifest))
1226 (return '())
1227 (sequence %store-monad
1228 (map (lambda (hook)
1229 (hook manifest))
1230 hooks)))))
1231 (define inputs
1232 (append (filter-map (lambda (drv)
1233 (and (derivation? drv)
1234 (gexp-input drv)))
1235 extras)
1236 (manifest-inputs manifest)))
1237
1238 (define glibc-utf8-locales ;lazy reference
1239 (module-ref (resolve-interface '(gnu packages base))
1240 'glibc-utf8-locales))
1241
1242 (define set-utf8-locale
1243 ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so
1244 ;; install a UTF-8 locale.
1245 #~(begin
1246 (setenv "LOCPATH"
1247 #$(file-append glibc-utf8-locales "/lib/locale/"
1248 (version-major+minor
1249 (package-version glibc-utf8-locales))))
1250 (setlocale LC_ALL "en_US.utf8")))
1251
1252 (define builder
1253 (with-imported-modules '((guix build profiles)
1254 (guix build union)
1255 (guix build utils)
1256 (guix search-paths)
1257 (guix records))
1258 #~(begin
1259 (use-modules (guix build profiles)
1260 (guix search-paths)
1261 (srfi srfi-1))
1262
1263 (setvbuf (current-output-port) _IOLBF)
1264 (setvbuf (current-error-port) _IOLBF)
1265
1266 #+(if locales? set-utf8-locale #t)
1267
1268 (define search-paths
1269 ;; Search paths of MANIFEST's packages, converted back to their
1270 ;; record form.
1271 (map sexp->search-path-specification
1272 (delete-duplicates
1273 '#$(map search-path-specification->sexp
1274 (append-map manifest-entry-search-paths
1275 (manifest-entries manifest))))))
1276
1277 (build-profile #$output '#$inputs
1278 #:manifest '#$(manifest->gexp manifest)
1279 #:search-paths search-paths))))
1280
1281 (gexp->derivation "profile" builder
1282 #:system system
1283 #:target target
1284
1285 ;; Don't complain about _IO* on Guile 2.2.
1286 #:env-vars '(("GUILE_WARN_DEPRECATED" . "no"))
1287
1288 ;; Not worth offloading.
1289 #:local-build? #t
1290
1291 ;; Disable substitution because it would trigger a
1292 ;; connection to the substitute server, which is likely
1293 ;; to have no substitute to offer.
1294 #:substitutable? #f)))
1295
1296 (define (profile-regexp profile)
1297 "Return a regular expression that matches PROFILE's name and number."
1298 (make-regexp (string-append "^" (regexp-quote (basename profile))
1299 "-([0-9]+)")))
1300
1301 (define (generation-number profile)
1302 "Return PROFILE's number or 0. An absolute file name must be used."
1303 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
1304 (basename (readlink profile))))
1305 (compose string->number (cut match:substring <> 1)))
1306 0))
1307
1308 (define (generation-numbers profile)
1309 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
1310 former profiles were found."
1311 (match (scandir (dirname profile)
1312 (cute regexp-exec (profile-regexp profile) <>))
1313 (#f ; no profile directory
1314 '(0))
1315 (() ; no profiles
1316 '(0))
1317 ((profiles ...) ; former profiles around
1318 (sort (map (compose string->number
1319 (cut match:substring <> 1)
1320 (cute regexp-exec (profile-regexp profile) <>))
1321 profiles)
1322 <))))
1323
1324 (define (profile-generations profile)
1325 "Return a list of PROFILE's generations."
1326 (let ((generations (generation-numbers profile)))
1327 (if (equal? generations '(0))
1328 '()
1329 generations)))
1330
1331 (define (relative-generation-spec->number profile spec)
1332 "Return PROFILE's generation specified by SPEC, which is a string. The SPEC
1333 may be a N, -N, or +N, where N is a number. If the spec is N, then the number
1334 returned is N. If it is -N, then the number returned is the profile's current
1335 generation number minus N. If it is +N, then the number returned is the
1336 profile's current generation number plus N. Return #f if there is no such
1337 generation."
1338 (let ((number (string->number spec)))
1339 (and number
1340 (case (string-ref spec 0)
1341 ((#\+ #\-)
1342 (relative-generation profile number))
1343 (else (if (memv number (profile-generations profile))
1344 number
1345 #f))))))
1346
1347
1348 (define* (relative-generation profile shift #:optional
1349 (current (generation-number profile)))
1350 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
1351 SHIFT is a positive or negative number.
1352 Return #f if there is no such generation."
1353 (let* ((abs-shift (abs shift))
1354 (numbers (profile-generations profile))
1355 (from-current (memq current
1356 (if (negative? shift)
1357 (reverse numbers)
1358 numbers))))
1359 (and from-current
1360 (< abs-shift (length from-current))
1361 (list-ref from-current abs-shift))))
1362
1363 (define* (previous-generation-number profile #:optional
1364 (number (generation-number profile)))
1365 "Return the number of the generation before generation NUMBER of
1366 PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
1367 case when generations have been deleted (there are \"holes\")."
1368 (or (relative-generation profile -1 number)
1369 0))
1370
1371 (define (generation-file-name profile generation)
1372 "Return the file name for PROFILE's GENERATION."
1373 (format #f "~a-~a-link" profile generation))
1374
1375 (define (generation-time profile number)
1376 "Return the creation time of a generation in the UTC format."
1377 (make-time time-utc 0
1378 (stat:ctime (stat (generation-file-name profile number)))))
1379
1380 (define (link-to-empty-profile store generation)
1381 "Link GENERATION, a string, to the empty profile. An error is raised if
1382 that fails."
1383 (let* ((drv (run-with-store store
1384 (profile-derivation (manifest '())
1385 #:locales? #f)))
1386 (prof (derivation->output-path drv "out")))
1387 (build-derivations store (list drv))
1388 (switch-symlinks generation prof)))
1389
1390 (define (switch-to-generation profile number)
1391 "Atomically switch PROFILE to the generation NUMBER. Return the number of
1392 the generation that was current before switching."
1393 (let ((current (generation-number profile))
1394 (generation (generation-file-name profile number)))
1395 (cond ((not (file-exists? profile))
1396 (raise (condition (&profile-not-found-error
1397 (profile profile)))))
1398 ((not (file-exists? generation))
1399 (raise (condition (&missing-generation-error
1400 (profile profile)
1401 (generation number)))))
1402 (else
1403 (switch-symlinks profile generation)
1404 current))))
1405
1406 (define (switch-to-previous-generation profile)
1407 "Atomically switch PROFILE to the previous generation. Return the former
1408 generation number and the current one."
1409 (let ((previous (previous-generation-number profile)))
1410 (values (switch-to-generation profile previous)
1411 previous)))
1412
1413 (define (roll-back store profile)
1414 "Roll back to the previous generation of PROFILE. Return the number of the
1415 generation that was current before switching and the new generation number."
1416 (let* ((number (generation-number profile))
1417 (previous-number (previous-generation-number profile number))
1418 (previous-generation (generation-file-name profile previous-number)))
1419 (cond ((not (file-exists? profile)) ;invalid profile
1420 (raise (condition (&profile-not-found-error
1421 (profile profile)))))
1422 ((zero? number) ;empty profile
1423 (values number number))
1424 ((or (zero? previous-number) ;going to emptiness
1425 (not (file-exists? previous-generation)))
1426 (link-to-empty-profile store previous-generation)
1427 (switch-to-previous-generation profile))
1428 (else ;anything else
1429 (switch-to-previous-generation profile)))))
1430
1431 (define (delete-generation store profile number)
1432 "Delete generation with NUMBER from PROFILE. Return the file name of the
1433 generation that has been deleted, or #f if nothing was done (for instance
1434 because the NUMBER is zero.)"
1435 (define (delete-and-return)
1436 (let ((generation (generation-file-name profile number)))
1437 (delete-file generation)
1438 generation))
1439
1440 (let* ((current-number (generation-number profile))
1441 (previous-number (previous-generation-number profile number))
1442 (previous-generation (generation-file-name profile previous-number)))
1443 (cond ((zero? number) #f) ;do not delete generation 0
1444 ((and (= number current-number)
1445 (not (file-exists? previous-generation)))
1446 (link-to-empty-profile store previous-generation)
1447 (switch-to-previous-generation profile)
1448 (delete-and-return))
1449 ((= number current-number)
1450 (roll-back store profile)
1451 (delete-and-return))
1452 (else
1453 (delete-and-return)))))
1454
1455 ;;; profiles.scm ends here