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, 2018 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 | file-like | 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
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 (fold (lambda (entry result) ;XXX: quadratic
503 (match entry
504 (($ <manifest-entry> name _ out _)
505 (cons entry
506 (remove (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 (item
676 ;; XXX: ITEM might be a 'computed-file' or anything like that, in
677 ;; which case we don't know what to do. The fix may be to check
678 ;; references once ITEM is compiled, as proposed at
679 ;; <https://bugs.gnu.org/29927>.
680 (return #f)))))
681
682 (anym %store-monad
683 entry-lookup-package (manifest-entries manifest)))
684
685 (define (info-dir-file manifest)
686 "Return a derivation that builds the 'dir' file for all the entries of
687 MANIFEST."
688 (define texinfo ;lazy reference
689 (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
690 (define gzip ;lazy reference
691 (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
692
693 (define build
694 (with-imported-modules '((guix build utils))
695 #~(begin
696 (use-modules (guix build utils)
697 (srfi srfi-1) (srfi srfi-26)
698 (ice-9 ftw))
699
700 (define (info-file? file)
701 (or (string-suffix? ".info" file)
702 (string-suffix? ".info.gz" file)))
703
704 (define (info-files top)
705 (let ((infodir (string-append top "/share/info")))
706 (map (cut string-append infodir "/" <>)
707 (or (scandir infodir info-file?) '()))))
708
709 (define (install-info info)
710 (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
711 (zero?
712 (system* (string-append #+texinfo "/bin/install-info") "--silent"
713 info (string-append #$output "/share/info/dir"))))
714
715 (mkdir-p (string-append #$output "/share/info"))
716 (exit (every install-info
717 (append-map info-files
718 '#$(manifest-inputs manifest)))))))
719
720 (gexp->derivation "info-dir" build
721 #:local-build? #t
722 #:substitutable? #f))
723
724 (define (ghc-package-cache-file manifest)
725 "Return a derivation that builds the GHC 'package.cache' file for all the
726 entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
727 (define ghc ;lazy reference
728 (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
729
730 (define build
731 (with-imported-modules '((guix build utils))
732 #~(begin
733 (use-modules (guix build utils)
734 (srfi srfi-1) (srfi srfi-26)
735 (ice-9 ftw))
736
737 (define ghc-name-version
738 (let* ((base (basename #+ghc)))
739 (string-drop base
740 (+ 1 (string-index base #\-)))))
741
742 (define db-subdir
743 (string-append "lib/" ghc-name-version "/package.conf.d"))
744
745 (define db-dir
746 (string-append #$output "/" db-subdir))
747
748 (define (conf-files top)
749 (let ((db (string-append top "/" db-subdir)))
750 (if (file-exists? db)
751 (find-files db "\\.conf$")
752 '())))
753
754 (define (copy-conf-file conf)
755 (let ((base (basename conf)))
756 (copy-file conf (string-append db-dir "/" base))))
757
758 (system* (string-append #+ghc "/bin/ghc-pkg") "init" db-dir)
759 (for-each copy-conf-file
760 (append-map conf-files
761 (delete-duplicates
762 '#$(manifest-inputs manifest))))
763 (let ((success
764 (zero?
765 (system* (string-append #+ghc "/bin/ghc-pkg") "recache"
766 (string-append "--package-db=" db-dir)))))
767 (for-each delete-file (find-files db-dir "\\.conf$"))
768 (exit success)))))
769
770 (with-monad %store-monad
771 ;; Don't depend on GHC when there's nothing to do.
772 (if (any (cut string-prefix? "ghc" <>)
773 (map manifest-entry-name (manifest-entries manifest)))
774 (gexp->derivation "ghc-package-cache" build
775 #:local-build? #t
776 #:substitutable? #f)
777 (return #f))))
778
779 (define (ca-certificate-bundle manifest)
780 "Return a derivation that builds a single-file bundle containing the CA
781 certificates in the /etc/ssl/certs sub-directories of the packages in
782 MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
783 ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
784 ;; for a discussion.
785
786 (define glibc-utf8-locales ;lazy reference
787 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
788
789 (define build
790 (with-imported-modules '((guix build utils))
791 #~(begin
792 (use-modules (guix build utils)
793 (rnrs io ports)
794 (srfi srfi-1)
795 (srfi srfi-26)
796 (ice-9 ftw)
797 (ice-9 match))
798
799 (define (pem-file? file)
800 (string-suffix? ".pem" file))
801
802 (define (ca-files top)
803 (let ((cert-dir (string-append top "/etc/ssl/certs")))
804 (map (cut string-append cert-dir "/" <>)
805 (or (scandir cert-dir pem-file?) '()))))
806
807 (define (concatenate-files files result)
808 "Make RESULT the concatenation of all of FILES."
809 (define (dump file port)
810 (display (call-with-input-file file get-string-all)
811 port)
812 (newline port)) ;required, see <https://bugs.debian.org/635570>
813
814 (call-with-output-file result
815 (lambda (port)
816 (for-each (cut dump <> port) files))))
817
818 ;; Some file names in the NSS certificates are UTF-8 encoded so
819 ;; install a UTF-8 locale.
820 (setenv "LOCPATH"
821 (string-append #+glibc-utf8-locales "/lib/locale/"
822 #+(version-major+minor
823 (package-version glibc-utf8-locales))))
824 (setlocale LC_ALL "en_US.utf8")
825
826 (match (append-map ca-files '#$(manifest-inputs manifest))
827 (()
828 ;; Since there are no CA files, just create an empty directory. Do
829 ;; not create the etc/ssl/certs sub-directory, since that would
830 ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
831 ;; defined.
832 (mkdir #$output)
833 #t)
834 ((ca-files ...)
835 (let ((result (string-append #$output "/etc/ssl/certs")))
836 (mkdir-p result)
837 (concatenate-files ca-files
838 (string-append result
839 "/ca-certificates.crt"))
840 #t))))))
841
842 (gexp->derivation "ca-certificate-bundle" build
843 #:local-build? #t
844 #:substitutable? #f))
845
846 (define (glib-schemas manifest)
847 "Return a derivation that unions all schemas from manifest entries and
848 creates the Glib 'gschemas.compiled' file."
849 (define glib ; lazy reference
850 (module-ref (resolve-interface '(gnu packages glib)) 'glib))
851
852 (mlet %store-monad ((%glib (manifest-lookup-package manifest "glib"))
853 ;; XXX: Can't use glib-compile-schemas corresponding
854 ;; to the glib referenced by 'manifest'. Because
855 ;; '%glib' can be either a package or store path, and
856 ;; there's no way to get the "bin" output for the later.
857 (glib-compile-schemas
858 -> #~(string-append #+glib:bin
859 "/bin/glib-compile-schemas")))
860
861 (define build
862 (with-imported-modules '((guix build utils)
863 (guix build union)
864 (guix build profiles)
865 (guix search-paths)
866 (guix records))
867 #~(begin
868 (use-modules (guix build utils)
869 (guix build union)
870 (guix build profiles)
871 (srfi srfi-26))
872
873 (let* ((destdir (string-append #$output "/share/glib-2.0/schemas"))
874 (schemadirs (filter file-exists?
875 (map (cut string-append <> "/share/glib-2.0/schemas")
876 '#$(manifest-inputs manifest)))))
877
878 ;; Union all the schemas.
879 (mkdir-p (string-append #$output "/share/glib-2.0"))
880 (union-build destdir schemadirs
881 #:log-port (%make-void-port "w"))
882
883 (let ((dir destdir))
884 (when (file-is-directory? dir)
885 (ensure-writable-directory dir)
886 (invoke #+glib-compile-schemas
887 (string-append "--targetdir=" dir)
888 dir)))))))
889
890 ;; Don't run the hook when there's nothing to do.
891 (if %glib
892 (gexp->derivation "glib-schemas" build
893 #:local-build? #t
894 #:substitutable? #f)
895 (return #f))))
896
897 (define (gtk-icon-themes manifest)
898 "Return a derivation that unions all icon themes from manifest entries and
899 creates the GTK+ 'icon-theme.cache' file for each theme."
900 (define gtk+ ; lazy reference
901 (module-ref (resolve-interface '(gnu packages gtk)) 'gtk+))
902
903 (mlet %store-monad ((%gtk+ (manifest-lookup-package manifest "gtk+"))
904 ;; XXX: Can't use gtk-update-icon-cache corresponding
905 ;; to the gtk+ referenced by 'manifest'. Because
906 ;; '%gtk+' can be either a package or store path, and
907 ;; there's no way to get the "bin" output for the later.
908 (gtk-update-icon-cache
909 -> #~(string-append #+gtk+:bin
910 "/bin/gtk-update-icon-cache")))
911
912 (define build
913 (with-imported-modules '((guix build utils)
914 (guix build union)
915 (guix build profiles)
916 (guix search-paths)
917 (guix records))
918 #~(begin
919 (use-modules (guix build utils)
920 (guix build union)
921 (guix build profiles)
922 (srfi srfi-26)
923 (ice-9 ftw))
924
925 (let* ((destdir (string-append #$output "/share/icons"))
926 (icondirs (filter file-exists?
927 (map (cut string-append <> "/share/icons")
928 '#$(manifest-inputs manifest)))))
929
930 ;; Union all the icons.
931 (mkdir-p (string-append #$output "/share"))
932 (union-build destdir icondirs
933 #:log-port (%make-void-port "w"))
934
935 ;; Update the 'icon-theme.cache' file for each icon theme.
936 (for-each
937 (lambda (theme)
938 (let ((dir (string-append destdir "/" theme)))
939 ;; Occasionally DESTDIR contains plain files, such as
940 ;; "abiword_48.png". Ignore these.
941 (when (file-is-directory? dir)
942 (ensure-writable-directory dir)
943 (system* #+gtk-update-icon-cache "-t" dir "--quiet"))))
944 (scandir destdir (negate (cut member <> '("." "..")))))))))
945
946 ;; Don't run the hook when there's nothing to do.
947 (if %gtk+
948 (gexp->derivation "gtk-icon-themes" build
949 #:local-build? #t
950 #:substitutable? #f)
951 (return #f))))
952
953 (define (gtk-im-modules manifest)
954 "Return a derivation that builds the cache files for input method modules
955 for both major versions of GTK+."
956
957 (mlet %store-monad ((gtk+ (manifest-lookup-package manifest "gtk+" "3"))
958 (gtk+-2 (manifest-lookup-package manifest "gtk+" "2")))
959
960 (define (build gtk gtk-version query)
961 (let ((major (string-take gtk-version 1)))
962 (with-imported-modules '((guix build utils)
963 (guix build union)
964 (guix build profiles)
965 (guix search-paths)
966 (guix records))
967 #~(begin
968 (use-modules (guix build utils)
969 (guix build union)
970 (guix build profiles)
971 (ice-9 popen)
972 (srfi srfi-1)
973 (srfi srfi-26))
974
975 (let* ((prefix (string-append "/lib/gtk-" #$major ".0/"
976 #$gtk-version))
977 (destdir (string-append #$output prefix))
978 (moddirs (cons (string-append #$gtk prefix "/immodules")
979 (filter file-exists?
980 (map (cut string-append <> prefix "/immodules")
981 '#$(manifest-inputs manifest)))))
982 (modules (append-map (cut find-files <> "\\.so$")
983 moddirs)))
984
985 ;; Generate a new immodules cache file.
986 (mkdir-p (string-append #$output prefix))
987 (let ((pipe (apply open-pipe* OPEN_READ #$query modules))
988 (outfile (string-append #$output prefix
989 "/immodules-gtk" #$major ".cache")))
990 (dynamic-wind
991 (const #t)
992 (lambda ()
993 (call-with-output-file outfile
994 (lambda (out)
995 (while (not (eof-object? (peek-char pipe)))
996 (write-char (read-char pipe) out))))
997 #t)
998 (lambda ()
999 (close-pipe pipe)))))))))
1000
1001 ;; Don't run the hook when there's nothing to do.
1002 (let* ((pkg-gtk+ (module-ref ; lazy reference
1003 (resolve-interface '(gnu packages gtk)) 'gtk+))
1004 (gexp #~(begin
1005 #$(if gtk+
1006 (build
1007 gtk+ "3.0.0"
1008 ;; Use 'gtk-query-immodules-3.0' from the 'bin'
1009 ;; output of latest gtk+ package.
1010 #~(string-append
1011 #$pkg-gtk+:bin "/bin/gtk-query-immodules-3.0"))
1012 #t)
1013 #$(if gtk+-2
1014 (build
1015 gtk+-2 "2.10.0"
1016 #~(string-append
1017 #$gtk+-2 "/bin/gtk-query-immodules-2.0"))
1018 #t))))
1019 (if (or gtk+ gtk+-2)
1020 (gexp->derivation "gtk-im-modules" gexp
1021 #:local-build? #t
1022 #:substitutable? #f)
1023 (return #f)))))
1024
1025 (define (xdg-desktop-database manifest)
1026 "Return a derivation that builds the @file{mimeinfo.cache} database from
1027 desktop files. It's used to query what applications can handle a given
1028 MIME type."
1029 (define desktop-file-utils ; lazy reference
1030 (module-ref (resolve-interface '(gnu packages freedesktop))
1031 'desktop-file-utils))
1032
1033 (mlet %store-monad ((glib
1034 (manifest-lookup-package
1035 manifest "glib")))
1036 (define build
1037 (with-imported-modules '((guix build utils)
1038 (guix build union))
1039 #~(begin
1040 (use-modules (srfi srfi-26)
1041 (guix build utils)
1042 (guix build union))
1043 (let* ((destdir (string-append #$output "/share/applications"))
1044 (appdirs (filter file-exists?
1045 (map (cut string-append <>
1046 "/share/applications")
1047 '#$(manifest-inputs manifest))))
1048 (update-desktop-database (string-append
1049 #+desktop-file-utils
1050 "/bin/update-desktop-database")))
1051 (mkdir-p (string-append #$output "/share"))
1052 (union-build destdir appdirs
1053 #:log-port (%make-void-port "w"))
1054 (exit (zero? (system* update-desktop-database destdir)))))))
1055
1056 ;; Don't run the hook when 'glib' is not referenced.
1057 (if glib
1058 (gexp->derivation "xdg-desktop-database" build
1059 #:local-build? #t
1060 #:substitutable? #f)
1061 (return #f))))
1062
1063 (define (xdg-mime-database manifest)
1064 "Return a derivation that builds the @file{mime.cache} database from manifest
1065 entries. It's used to query the MIME type of a given file."
1066 (define shared-mime-info ; lazy reference
1067 (module-ref (resolve-interface '(gnu packages gnome)) 'shared-mime-info))
1068
1069 (mlet %store-monad ((glib
1070 (manifest-lookup-package
1071 manifest "glib")))
1072 (define build
1073 (with-imported-modules '((guix build utils)
1074 (guix build union))
1075 #~(begin
1076 (use-modules (srfi srfi-26)
1077 (guix build utils)
1078 (guix build union))
1079 (let* ((datadir (string-append #$output "/share"))
1080 (destdir (string-append datadir "/mime"))
1081 (pkgdirs (filter file-exists?
1082 (map (cut string-append <>
1083 "/share/mime/packages")
1084 (cons #+shared-mime-info
1085 '#$(manifest-inputs manifest)))))
1086 (update-mime-database (string-append
1087 #+shared-mime-info
1088 "/bin/update-mime-database")))
1089 (mkdir-p destdir)
1090 (union-build (string-append destdir "/packages") pkgdirs
1091 #:log-port (%make-void-port "w"))
1092 (setenv "XDG_DATA_HOME" datadir)
1093 (exit (zero? (system* update-mime-database destdir)))))))
1094
1095 ;; Don't run the hook when there are no GLib based applications.
1096 (if glib
1097 (gexp->derivation "xdg-mime-database" build
1098 #:local-build? #t
1099 #:substitutable? #f)
1100 (return #f))))
1101
1102 ;; Several font packages may install font files into same directory, so
1103 ;; fonts.dir and fonts.scale file should be generated here, instead of in
1104 ;; packages.
1105 (define (fonts-dir-file manifest)
1106 "Return a derivation that builds the @file{fonts.dir} and @file{fonts.scale}
1107 files for the fonts of the @var{manifest} entries."
1108 (define mkfontscale
1109 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontscale))
1110
1111 (define mkfontdir
1112 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontdir))
1113
1114 (define build
1115 #~(begin
1116 (use-modules (srfi srfi-26)
1117 (guix build utils)
1118 (guix build union))
1119 (let ((fonts-dirs (filter file-exists?
1120 (map (cut string-append <>
1121 "/share/fonts")
1122 '#$(manifest-inputs manifest)))))
1123 (mkdir #$output)
1124 (if (null? fonts-dirs)
1125 (exit #t)
1126 (let* ((share-dir (string-append #$output "/share"))
1127 (fonts-dir (string-append share-dir "/fonts"))
1128 (mkfontscale (string-append #+mkfontscale
1129 "/bin/mkfontscale"))
1130 (mkfontdir (string-append #+mkfontdir
1131 "/bin/mkfontdir"))
1132 (empty-file? (lambda (filename)
1133 (call-with-ascii-input-file filename
1134 (lambda (p)
1135 (eqv? #\0 (read-char p))))))
1136 (fonts-dir-file "fonts.dir")
1137 (fonts-scale-file "fonts.scale"))
1138 (mkdir-p share-dir)
1139 ;; Create all sub-directories, because we may create fonts.dir
1140 ;; and fonts.scale files in the sub-directories.
1141 (union-build fonts-dir fonts-dirs
1142 #:log-port (%make-void-port "w")
1143 #:create-all-directories? #t)
1144 (let ((directories (find-files fonts-dir
1145 (lambda (file stat)
1146 (eq? 'directory (stat:type stat)))
1147 #:directories? #t)))
1148 (for-each (lambda (dir)
1149 (with-directory-excursion dir
1150 (when (file-exists? fonts-scale-file)
1151 (delete-file fonts-scale-file))
1152 (when (file-exists? fonts-dir-file)
1153 (delete-file fonts-dir-file))
1154 (unless (and (zero? (system* mkfontscale))
1155 (zero? (system* mkfontdir)))
1156 (exit #f))
1157 (when (and (file-exists? fonts-scale-file)
1158 (empty-file? fonts-scale-file))
1159 (delete-file fonts-scale-file))
1160 (when (and (file-exists? fonts-dir-file)
1161 (empty-file? fonts-dir-file))
1162 (delete-file fonts-dir-file))))
1163 directories)))))))
1164
1165 (gexp->derivation "fonts-dir" build
1166 #:modules '((guix build utils)
1167 (guix build union)
1168 (srfi srfi-26))
1169 #:local-build? #t
1170 #:substitutable? #f))
1171
1172 (define (manual-database manifest)
1173 "Return a derivation that builds the manual page database (\"mandb\") for
1174 the entries in MANIFEST."
1175 (define gdbm-ffi
1176 (module-ref (resolve-interface '(gnu packages guile))
1177 'guile-gdbm-ffi))
1178
1179 (define zlib
1180 (module-ref (resolve-interface '(gnu packages compression)) 'zlib))
1181
1182 (define config.scm
1183 (scheme-file "config.scm"
1184 #~(begin
1185 (define-module (guix config)
1186 #:export (%libz))
1187
1188 (define %libz
1189 #+(file-append zlib "/lib/libz")))))
1190
1191 (define modules
1192 (cons `((guix config) => ,config.scm)
1193 (delete '(guix config)
1194 (source-module-closure `((guix build utils)
1195 (guix man-db))))))
1196
1197 (define build
1198 (with-imported-modules modules
1199 (with-extensions (list gdbm-ffi) ;for (guix man-db)
1200 #~(begin
1201 (use-modules (guix man-db)
1202 (guix build utils)
1203 (srfi srfi-1)
1204 (srfi srfi-19))
1205
1206 (define (compute-entries)
1207 (append-map (lambda (directory)
1208 (let ((man (string-append directory "/share/man")))
1209 (if (directory-exists? man)
1210 (mandb-entries man)
1211 '())))
1212 '#$(manifest-inputs manifest)))
1213
1214 (define man-directory
1215 (string-append #$output "/share/man"))
1216
1217 (mkdir-p man-directory)
1218
1219 (format #t "Creating manual page database...~%")
1220 (force-output)
1221 (let* ((start (current-time))
1222 (entries (compute-entries))
1223 (_ (write-mandb-database (string-append man-directory
1224 "/index.db")
1225 entries))
1226 (duration (time-difference (current-time) start)))
1227 (format #t "~a entries processed in ~,1f s~%"
1228 (length entries)
1229 (+ (time-second duration)
1230 (* (time-nanosecond duration) (expt 10 -9))))
1231 (force-output))))))
1232
1233 (gexp->derivation "manual-database" build
1234
1235 ;; Work around GDBM 1.13 issue whereby uninitialized bytes
1236 ;; get written to disk:
1237 ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29654#23>.
1238 #:env-vars `(("MALLOC_PERTURB_" . "1"))
1239
1240 #:local-build? #t))
1241
1242 (define %default-profile-hooks
1243 ;; This is the list of derivation-returning procedures that are called by
1244 ;; default when making a non-empty profile.
1245 (list info-dir-file
1246 manual-database
1247 fonts-dir-file
1248 ghc-package-cache-file
1249 ca-certificate-bundle
1250 glib-schemas
1251 gtk-icon-themes
1252 gtk-im-modules
1253 xdg-desktop-database
1254 xdg-mime-database))
1255
1256 (define* (profile-derivation manifest
1257 #:key
1258 (hooks %default-profile-hooks)
1259 (locales? #t)
1260 (allow-collisions? #f)
1261 (relative-symlinks? #f)
1262 system target)
1263 "Return a derivation that builds a profile (aka. 'user environment') with
1264 the given MANIFEST. The profile includes additional derivations returned by
1265 the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc.
1266 Unless ALLOW-COLLISIONS? is true, a '&profile-collision-error' is raised if
1267 entries in MANIFEST collide (for instance if there are two same-name packages
1268 with a different version number.)
1269
1270 When LOCALES? is true, the build is performed under a UTF-8 locale; this adds
1271 a dependency on the 'glibc-utf8-locales' package.
1272
1273 When RELATIVE-SYMLINKS? is true, use relative file names for symlink targets.
1274 This is one of the things to do for the result to be relocatable.
1275
1276 When TARGET is true, it must be a GNU triplet, and the packages in MANIFEST
1277 are cross-built for TARGET."
1278 (mlet* %store-monad ((system (if system
1279 (return system)
1280 (current-system)))
1281 (ok? (if allow-collisions?
1282 (return #t)
1283 (check-for-collisions manifest system
1284 #:target target)))
1285 (extras (if (null? (manifest-entries manifest))
1286 (return '())
1287 (sequence %store-monad
1288 (map (lambda (hook)
1289 (hook manifest))
1290 hooks)))))
1291 (define inputs
1292 (append (filter-map (lambda (drv)
1293 (and (derivation? drv)
1294 (gexp-input drv)))
1295 extras)
1296 (manifest-inputs manifest)))
1297
1298 (define glibc-utf8-locales ;lazy reference
1299 (module-ref (resolve-interface '(gnu packages base))
1300 'glibc-utf8-locales))
1301
1302 (define set-utf8-locale
1303 ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so
1304 ;; install a UTF-8 locale.
1305 #~(begin
1306 (setenv "LOCPATH"
1307 #$(file-append glibc-utf8-locales "/lib/locale/"
1308 (version-major+minor
1309 (package-version glibc-utf8-locales))))
1310 (setlocale LC_ALL "en_US.utf8")))
1311
1312 (define builder
1313 (with-imported-modules '((guix build profiles)
1314 (guix build union)
1315 (guix build utils)
1316 (guix search-paths)
1317 (guix records))
1318 #~(begin
1319 (use-modules (guix build profiles)
1320 (guix search-paths)
1321 (srfi srfi-1))
1322
1323 (setvbuf (current-output-port) _IOLBF)
1324 (setvbuf (current-error-port) _IOLBF)
1325
1326 #+(if locales? set-utf8-locale #t)
1327
1328 (define search-paths
1329 ;; Search paths of MANIFEST's packages, converted back to their
1330 ;; record form.
1331 (map sexp->search-path-specification
1332 (delete-duplicates
1333 '#$(map search-path-specification->sexp
1334 (append-map manifest-entry-search-paths
1335 (manifest-entries manifest))))))
1336
1337 (build-profile #$output '#$inputs
1338 #:symlink #$(if relative-symlinks?
1339 #~symlink-relative
1340 #~symlink)
1341 #:manifest '#$(manifest->gexp manifest)
1342 #:search-paths search-paths))))
1343
1344 (gexp->derivation "profile" builder
1345 #:system system
1346 #:target target
1347
1348 ;; Don't complain about _IO* on Guile 2.2.
1349 #:env-vars '(("GUILE_WARN_DEPRECATED" . "no"))
1350
1351 ;; Not worth offloading.
1352 #:local-build? #t
1353
1354 ;; Disable substitution because it would trigger a
1355 ;; connection to the substitute server, which is likely
1356 ;; to have no substitute to offer.
1357 #:substitutable? #f)))
1358
1359 (define (profile-regexp profile)
1360 "Return a regular expression that matches PROFILE's name and number."
1361 (make-regexp (string-append "^" (regexp-quote (basename profile))
1362 "-([0-9]+)")))
1363
1364 (define (generation-number profile)
1365 "Return PROFILE's number or 0. An absolute file name must be used."
1366 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
1367 (basename (readlink profile))))
1368 (compose string->number (cut match:substring <> 1)))
1369 0))
1370
1371 (define (generation-numbers profile)
1372 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
1373 former profiles were found."
1374 (match (scandir (dirname profile)
1375 (cute regexp-exec (profile-regexp profile) <>))
1376 (#f ; no profile directory
1377 '(0))
1378 (() ; no profiles
1379 '(0))
1380 ((profiles ...) ; former profiles around
1381 (sort (map (compose string->number
1382 (cut match:substring <> 1)
1383 (cute regexp-exec (profile-regexp profile) <>))
1384 profiles)
1385 <))))
1386
1387 (define (profile-generations profile)
1388 "Return a list of PROFILE's generations."
1389 (let ((generations (generation-numbers profile)))
1390 (if (equal? generations '(0))
1391 '()
1392 generations)))
1393
1394 (define (relative-generation-spec->number profile spec)
1395 "Return PROFILE's generation specified by SPEC, which is a string. The SPEC
1396 may be a N, -N, or +N, where N is a number. If the spec is N, then the number
1397 returned is N. If it is -N, then the number returned is the profile's current
1398 generation number minus N. If it is +N, then the number returned is the
1399 profile's current generation number plus N. Return #f if there is no such
1400 generation."
1401 (let ((number (string->number spec)))
1402 (and number
1403 (case (string-ref spec 0)
1404 ((#\+ #\-)
1405 (relative-generation profile number))
1406 (else (if (memv number (profile-generations profile))
1407 number
1408 #f))))))
1409
1410
1411 (define* (relative-generation profile shift #:optional
1412 (current (generation-number profile)))
1413 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
1414 SHIFT is a positive or negative number.
1415 Return #f if there is no such generation."
1416 (let* ((abs-shift (abs shift))
1417 (numbers (profile-generations profile))
1418 (from-current (memq current
1419 (if (negative? shift)
1420 (reverse numbers)
1421 numbers))))
1422 (and from-current
1423 (< abs-shift (length from-current))
1424 (list-ref from-current abs-shift))))
1425
1426 (define* (previous-generation-number profile #:optional
1427 (number (generation-number profile)))
1428 "Return the number of the generation before generation NUMBER of
1429 PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
1430 case when generations have been deleted (there are \"holes\")."
1431 (or (relative-generation profile -1 number)
1432 0))
1433
1434 (define (generation-file-name profile generation)
1435 "Return the file name for PROFILE's GENERATION."
1436 (format #f "~a-~a-link" profile generation))
1437
1438 (define (generation-time profile number)
1439 "Return the creation time of a generation in the UTC format."
1440 (make-time time-utc 0
1441 (stat:ctime (stat (generation-file-name profile number)))))
1442
1443 (define (link-to-empty-profile store generation)
1444 "Link GENERATION, a string, to the empty profile. An error is raised if
1445 that fails."
1446 (let* ((drv (run-with-store store
1447 (profile-derivation (manifest '())
1448 #:locales? #f)))
1449 (prof (derivation->output-path drv "out")))
1450 (build-derivations store (list drv))
1451 (switch-symlinks generation prof)))
1452
1453 (define (switch-to-generation profile number)
1454 "Atomically switch PROFILE to the generation NUMBER. Return the number of
1455 the generation that was current before switching."
1456 (let ((current (generation-number profile))
1457 (generation (generation-file-name profile number)))
1458 (cond ((not (file-exists? profile))
1459 (raise (condition (&profile-not-found-error
1460 (profile profile)))))
1461 ((not (file-exists? generation))
1462 (raise (condition (&missing-generation-error
1463 (profile profile)
1464 (generation number)))))
1465 (else
1466 (switch-symlinks profile generation)
1467 current))))
1468
1469 (define (switch-to-previous-generation profile)
1470 "Atomically switch PROFILE to the previous generation. Return the former
1471 generation number and the current one."
1472 (let ((previous (previous-generation-number profile)))
1473 (values (switch-to-generation profile previous)
1474 previous)))
1475
1476 (define (roll-back store profile)
1477 "Roll back to the previous generation of PROFILE. Return the number of the
1478 generation that was current before switching and the new generation number."
1479 (let* ((number (generation-number profile))
1480 (previous-number (previous-generation-number profile number))
1481 (previous-generation (generation-file-name profile previous-number)))
1482 (cond ((not (file-exists? profile)) ;invalid profile
1483 (raise (condition (&profile-not-found-error
1484 (profile profile)))))
1485 ((zero? number) ;empty profile
1486 (values number number))
1487 ((or (zero? previous-number) ;going to emptiness
1488 (not (file-exists? previous-generation)))
1489 (link-to-empty-profile store previous-generation)
1490 (switch-to-previous-generation profile))
1491 (else ;anything else
1492 (switch-to-previous-generation profile)))))
1493
1494 (define (delete-generation store profile number)
1495 "Delete generation with NUMBER from PROFILE. Return the file name of the
1496 generation that has been deleted, or #f if nothing was done (for instance
1497 because the NUMBER is zero.)"
1498 (define (delete-and-return)
1499 (let ((generation (generation-file-name profile number)))
1500 (delete-file generation)
1501 generation))
1502
1503 (let* ((current-number (generation-number profile))
1504 (previous-number (previous-generation-number profile number))
1505 (previous-generation (generation-file-name profile previous-number)))
1506 (cond ((zero? number) #f) ;do not delete generation 0
1507 ((and (= number current-number)
1508 (not (file-exists? previous-generation)))
1509 (link-to-empty-profile store previous-generation)
1510 (switch-to-previous-generation profile)
1511 (delete-and-return))
1512 ((= number current-number)
1513 (roll-back store profile)
1514 (delete-and-return))
1515 (else
1516 (delete-and-return)))))
1517
1518 ;;; profiles.scm ends here