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