store: 'open-connection' never returns #f.
[jackhill/guix/guix.git] / guix / profiles.scm
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 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, 2018, 2019, 2021 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 ;;; Copyright © 2019 Kyle Meyer <kyle@kyleam.com>
12 ;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com>
13 ;;; Copyright © 2020 Danny Milosavljevic <dannym@scratchpost.org>
14 ;;;
15 ;;; This file is part of GNU Guix.
16 ;;;
17 ;;; GNU Guix is free software; you can redistribute it and/or modify it
18 ;;; under the terms of the GNU General Public License as published by
19 ;;; the Free Software Foundation; either version 3 of the License, or (at
20 ;;; your option) any later version.
21 ;;;
22 ;;; GNU Guix is distributed in the hope that it will be useful, but
23 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
24 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 ;;; GNU General Public License for more details.
26 ;;;
27 ;;; You should have received a copy of the GNU General Public License
28 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
29
30 (define-module (guix profiles)
31 #:use-module ((guix config) #:select (%state-directory))
32 #:use-module ((guix utils) #:hide (package-name->name+version))
33 #:use-module ((guix build utils)
34 #:select (package-name->name+version mkdir-p))
35 #:use-module ((guix diagnostics) #:select (&fix-hint))
36 #:use-module (guix i18n)
37 #:use-module (guix records)
38 #:use-module (guix packages)
39 #:use-module (guix derivations)
40 #:use-module (guix search-paths)
41 #:use-module (guix gexp)
42 #:use-module (guix modules)
43 #:use-module (guix monads)
44 #:use-module (guix store)
45 #:use-module (ice-9 vlist)
46 #:use-module (ice-9 match)
47 #:use-module (ice-9 regex)
48 #:use-module (ice-9 ftw)
49 #:use-module (ice-9 format)
50 #:use-module (srfi srfi-1)
51 #:use-module (srfi srfi-9)
52 #:use-module (srfi srfi-11)
53 #:use-module (srfi srfi-19)
54 #:use-module (srfi srfi-26)
55 #:use-module (srfi srfi-34)
56 #:use-module (srfi srfi-35)
57 #:export (&profile-error
58 profile-error?
59 profile-error-profile
60 &profile-not-found-error
61 profile-not-found-error?
62 &profile-collision-error
63 profile-collision-error?
64 profile-collision-error-entry
65 profile-collision-error-conflict
66 &missing-generation-error
67 missing-generation-error?
68 missing-generation-error-generation
69 &unmatched-pattern-error
70 unmatched-pattern-error?
71 unmatched-pattern-error-pattern
72 unmatched-pattern-error-manifest
73
74 manifest make-manifest
75 manifest?
76 manifest-entries
77 manifest-transitive-entries
78
79 <manifest-entry> ; FIXME: eventually make it internal
80 manifest-entry
81 manifest-entry?
82 manifest-entry-name
83 manifest-entry-version
84 manifest-entry-output
85 manifest-entry-item
86 manifest-entry-dependencies
87 manifest-entry-search-paths
88 manifest-entry-parent
89 manifest-entry-properties
90 lower-manifest-entry
91
92 manifest-entry=?
93
94 manifest-pattern
95 manifest-pattern?
96 manifest-pattern-name
97 manifest-pattern-version
98 manifest-pattern-output
99
100 concatenate-manifests
101 map-manifest-entries
102 manifest-remove
103 manifest-add
104 manifest-lookup
105 manifest-installed?
106 manifest-matching-entries
107 manifest-search-paths
108 check-for-collisions
109
110 manifest->code
111
112 manifest-transaction
113 manifest-transaction?
114 manifest-transaction-install
115 manifest-transaction-remove
116 manifest-transaction-install-entry
117 manifest-transaction-remove-pattern
118 manifest-transaction-null?
119 manifest-transaction-removal-candidate?
120 manifest-perform-transaction
121 manifest-transaction-effects
122
123 profile-manifest
124 package->manifest-entry
125 packages->manifest
126 ca-certificate-bundle
127 %default-profile-hooks
128 profile-derivation
129 profile-search-paths
130
131 profile
132 profile?
133 profile-name
134 profile-content
135 profile-hooks
136 profile-locales?
137 profile-allow-collisions?
138 profile-relative-symlinks?
139
140 generation-number
141 generation-profile
142 generation-numbers
143 profile-generations
144 relative-generation-spec->number
145 relative-generation
146 previous-generation-number
147 generation-time
148 generation-file-name
149 switch-to-generation
150 roll-back
151 delete-generation
152
153 %user-profile-directory
154 %profile-directory
155 %current-profile
156 ensure-profile-directory
157 canonicalize-profile
158 user-friendly-profile
159
160 linux-module-database))
161
162 ;;; Commentary:
163 ;;;
164 ;;; Tools to create and manipulate profiles---i.e., the representation of a
165 ;;; set of installed packages.
166 ;;;
167 ;;; Code:
168
169 \f
170 ;;;
171 ;;; Condition types.
172 ;;;
173
174 (define-condition-type &profile-error &error
175 profile-error?
176 (profile profile-error-profile))
177
178 (define-condition-type &profile-not-found-error &profile-error
179 profile-not-found-error?)
180
181 (define-condition-type &profile-collision-error &error
182 profile-collision-error?
183 (entry profile-collision-error-entry) ;<manifest-entry>
184 (conflict profile-collision-error-conflict)) ;<manifest-entry>
185
186 (define-condition-type &unmatched-pattern-error &error
187 unmatched-pattern-error?
188 (pattern unmatched-pattern-error-pattern) ;<manifest-pattern>
189 (manifest unmatched-pattern-error-manifest)) ;<manifest>
190
191 (define-condition-type &missing-generation-error &profile-error
192 missing-generation-error?
193 (generation missing-generation-error-generation))
194
195 \f
196 ;;;
197 ;;; Manifests.
198 ;;;
199
200 (define-record-type <manifest>
201 (manifest entries)
202 manifest?
203 (entries manifest-entries)) ; list of <manifest-entry>
204
205 ;; Convenient alias, to avoid name clashes.
206 (define make-manifest manifest)
207
208 (define-record-type* <manifest-entry> manifest-entry
209 make-manifest-entry
210 manifest-entry?
211 (name manifest-entry-name) ; string
212 (version manifest-entry-version) ; string
213 (output manifest-entry-output ; string
214 (default "out"))
215 (item manifest-entry-item) ; package | file-like | store path
216 (dependencies manifest-entry-dependencies ; <manifest-entry>*
217 (default '()))
218 (search-paths manifest-entry-search-paths ; search-path-specification*
219 (default '()))
220 (parent manifest-entry-parent ; promise (#f | <manifest-entry>)
221 (default (delay #f)))
222 (properties manifest-entry-properties ; list of symbol/value pairs
223 (default '())))
224
225 (define-record-type* <manifest-pattern> manifest-pattern
226 make-manifest-pattern
227 manifest-pattern?
228 (name manifest-pattern-name) ; string
229 (version manifest-pattern-version ; string | #f
230 (default #f))
231 (output manifest-pattern-output ; string | #f
232 (default "out")))
233
234 (define (list=? = lst1 lst2)
235 "Return true if LST1 and LST2 have the same length and their elements are
236 pairwise equal per =."
237 (match lst1
238 (()
239 (null? lst2))
240 ((head1 . tail1)
241 (match lst2
242 ((head2 . tail2)
243 (and (= head1 head2) (list=? = tail1 tail2)))
244 (()
245 #f)))))
246
247 (define (manifest-entry=? entry1 entry2)
248 "Return true if ENTRY1 is equivalent to ENTRY2, ignoring their 'properties'
249 field."
250 (match entry1
251 (($ <manifest-entry> name1 version1 output1 item1 dependencies1 paths1)
252 (match entry2
253 (($ <manifest-entry> name2 version2 output2 item2 dependencies2 paths2)
254 (and (string=? name1 name2)
255 (string=? version1 version2)
256 (string=? output1 output2)
257 (equal? item1 item2) ;XXX: could be <package> vs. store item
258 (equal? paths1 paths2)
259 (list=? manifest-entry=? dependencies1 dependencies2)))))))
260
261 (define (manifest-transitive-entries manifest)
262 "Return the entries of MANIFEST along with their propagated inputs,
263 recursively."
264 (let loop ((entries (manifest-entries manifest))
265 (result '())
266 (visited vlist-null)) ;compare with 'manifest-entry=?'
267 (match entries
268 (()
269 (reverse result))
270 ((head . tail)
271 (if (vhash-assoc head visited manifest-entry=?)
272 (loop tail result visited)
273 (loop (append (manifest-entry-dependencies head)
274 tail)
275 (cons head result)
276 (vhash-cons head #t visited)))))))
277
278 (define (profile-manifest profile)
279 "Return the PROFILE's manifest."
280 (let ((file (string-append profile "/manifest")))
281 (if (file-exists? file)
282 (call-with-input-file file read-manifest)
283 (manifest '()))))
284
285 (define (manifest-entry-lookup manifest)
286 "Return a lookup procedure for the entries of MANIFEST. The lookup
287 procedure takes two arguments: the entry name and output."
288 (define mapping
289 (let loop ((entries (manifest-entries manifest))
290 (mapping vlist-null))
291 (fold (lambda (entry result)
292 (vhash-cons (cons (manifest-entry-name entry)
293 (manifest-entry-output entry))
294 entry
295 (loop (manifest-entry-dependencies entry)
296 result)))
297 mapping
298 entries)))
299
300 (lambda (name output)
301 (match (vhash-assoc (cons name output) mapping)
302 ((_ . entry) entry)
303 (#f #f))))
304
305 (define* (lower-manifest-entry entry system #:key target)
306 "Lower ENTRY for SYSTEM and TARGET such that its 'item' field is a store
307 file name."
308 (define (recurse entry)
309 (mapm/accumulate-builds (lambda (entry)
310 (lower-manifest-entry entry system
311 #:target target))
312 (manifest-entry-dependencies entry)))
313
314 (let ((item (manifest-entry-item entry)))
315 (if (string? item)
316 (with-monad %store-monad
317 (return entry))
318 (mlet %store-monad ((drv (lower-object item system
319 #:target target))
320 (dependencies (recurse entry))
321 (output -> (manifest-entry-output entry)))
322 (return (manifest-entry
323 (inherit entry)
324 (item (derivation->output-path drv output))
325 (dependencies dependencies)))))))
326
327 (define* (check-for-collisions manifest system #:key target)
328 "Check whether the entries of MANIFEST conflict with one another; raise a
329 '&profile-collision-error' when a conflict is encountered."
330 (define lookup
331 (manifest-entry-lookup manifest))
332
333 (define candidates
334 (filter-map (lambda (entry)
335 (let ((other (lookup (manifest-entry-name entry)
336 (manifest-entry-output entry))))
337 (and other (list entry other))))
338 (manifest-transitive-entries manifest)))
339
340 (define lower-pair
341 (match-lambda
342 ((first second)
343 (mlet %store-monad ((first (lower-manifest-entry first system
344 #:target target))
345 (second (lower-manifest-entry second system
346 #:target target)))
347 (return (list first second))))))
348
349 ;; Start by lowering CANDIDATES "in parallel".
350 (mlet %store-monad ((lst (mapm/accumulate-builds lower-pair candidates)))
351 (foldm %store-monad
352 (lambda (entries result)
353 (match entries
354 ((first second)
355 (if (string=? (manifest-entry-item first)
356 (manifest-entry-item second))
357 (return result)
358 (raise (condition
359 (&profile-collision-error
360 (entry first)
361 (conflict second))))))))
362 #t
363 lst)))
364
365 (define (default-properties package)
366 "Return the default properties of a manifest entry for PACKAGE."
367 ;; Preserve transformation options by default.
368 (match (assq-ref (package-properties package) 'transformations)
369 (#f '())
370 (transformations `((transformations . ,transformations)))))
371
372 (define* (package->manifest-entry package #:optional (output "out")
373 #:key (parent (delay #f))
374 (properties (default-properties package)))
375 "Return a manifest entry for the OUTPUT of package PACKAGE."
376 ;; For each dependency, keep a promise pointing to its "parent" entry.
377 (letrec* ((deps (map (match-lambda
378 ((label package)
379 (package->manifest-entry package
380 #:parent (delay entry)))
381 ((label package output)
382 (package->manifest-entry package output
383 #:parent (delay entry))))
384 (package-propagated-inputs package)))
385 (entry (manifest-entry
386 (name (package-name package))
387 (version (package-version package))
388 (output output)
389 (item package)
390 (dependencies (delete-duplicates deps))
391 (search-paths
392 (package-transitive-native-search-paths package))
393 (parent parent)
394 (properties properties))))
395 entry))
396
397 (define (packages->manifest packages)
398 "Return a list of manifest entries, one for each item listed in PACKAGES.
399 Elements of PACKAGES can be either package objects or package/string tuples
400 denoting a specific output of a package."
401 (define inferiors-loaded?
402 ;; This hack allows us to provide seamless integration for inferior
403 ;; packages while not having a hard dependency on (guix inferior).
404 (resolve-module '(guix inferior) #f #f #:ensure #f))
405
406 (define (inferior->entry)
407 (module-ref (resolve-interface '(guix inferior))
408 'inferior-package->manifest-entry))
409
410 (manifest
411 (delete-duplicates
412 (map (match-lambda
413 (((? package? package) output)
414 (package->manifest-entry package output))
415 ((? package? package)
416 (package->manifest-entry package))
417 ((thing output)
418 (if inferiors-loaded?
419 ((inferior->entry) thing output)
420 (throw 'wrong-type-arg 'packages->manifest
421 "Wrong package object: ~S" (list thing) (list thing))))
422 (thing
423 (if inferiors-loaded?
424 ((inferior->entry) thing)
425 (throw 'wrong-type-arg 'packages->manifest
426 "Wrong package object: ~S" (list thing) (list thing)))))
427 packages)
428 manifest-entry=?)))
429
430 (define (manifest->gexp manifest)
431 "Return a representation of MANIFEST as a gexp."
432 (define (entry->gexp entry)
433 (match entry
434 (($ <manifest-entry> name version output (? string? path)
435 (deps ...) (search-paths ...) _ (properties ...))
436 #~(#$name #$version #$output #$path
437 (propagated-inputs #$(map entry->gexp deps))
438 (search-paths #$(map search-path-specification->sexp
439 search-paths))
440 (properties . #$properties)))
441 (($ <manifest-entry> name version output package
442 (deps ...) (search-paths ...) _ (properties ...))
443 #~(#$name #$version #$output
444 (ungexp package (or output "out"))
445 (propagated-inputs #$(map entry->gexp deps))
446 (search-paths #$(map search-path-specification->sexp
447 search-paths))
448 (properties . #$properties)))))
449
450 (match manifest
451 (($ <manifest> (entries ...))
452 #~(manifest (version 3)
453 (packages #$(map entry->gexp entries))))))
454
455 (define (find-package name version)
456 "Return a package from the distro matching NAME and possibly VERSION. This
457 procedure is here for backward-compatibility and will eventually vanish."
458 (define find-best-packages-by-name ;break abstractions
459 (module-ref (resolve-interface '(gnu packages))
460 'find-best-packages-by-name))
461
462 ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; the
463 ;; former traverses the module tree only once and then allows for efficient
464 ;; access via a vhash.
465 (match (find-best-packages-by-name name version)
466 ((p _ ...) p)
467 (_
468 (match (find-best-packages-by-name name #f)
469 ((p _ ...) p)
470 (_ #f)))))
471
472 (define (sexp->manifest sexp)
473 "Parse SEXP as a manifest."
474 (define (infer-search-paths name version)
475 ;; Infer the search path specifications for NAME-VERSION by looking up a
476 ;; same-named package in the distro. Useful for the old manifest formats
477 ;; that did not store search path info.
478 (let ((package (find-package name version)))
479 (if package
480 (package-native-search-paths package)
481 '())))
482
483 (define (infer-dependency item parent)
484 ;; Return a <manifest-entry> for ITEM.
485 (let-values (((name version)
486 (package-name->name+version
487 (store-path-package-name item))))
488 (manifest-entry
489 (name name)
490 (version version)
491 (item item)
492 (parent parent))))
493
494 (define* (sexp->manifest-entry sexp #:optional (parent (delay #f)))
495 (match sexp
496 ((name version output path
497 ('propagated-inputs deps)
498 ('search-paths search-paths)
499 extra-stuff ...)
500 ;; For each of DEPS, keep a promise pointing to ENTRY.
501 (letrec* ((deps* (map (cut sexp->manifest-entry <> (delay entry))
502 deps))
503 (entry (manifest-entry
504 (name name)
505 (version version)
506 (output output)
507 (item path)
508 (dependencies deps*)
509 (search-paths (map sexp->search-path-specification
510 search-paths))
511 (parent parent)
512 (properties (or (assoc-ref extra-stuff 'properties)
513 '())))))
514 entry))))
515
516 (match sexp
517 (('manifest ('version 0)
518 ('packages ((name version output path) ...)))
519 (manifest
520 (map (lambda (name version output path)
521 (manifest-entry
522 (name name)
523 (version version)
524 (output output)
525 (item path)
526 (search-paths (infer-search-paths name version))))
527 name version output path)))
528
529 ;; Version 1 adds a list of propagated inputs to the
530 ;; name/version/output/path tuples.
531 (('manifest ('version 1)
532 ('packages ((name version output path deps) ...)))
533 (manifest
534 (map (lambda (name version output path deps)
535 ;; Up to Guix 0.7 included, dependencies were listed as ("gmp"
536 ;; "/gnu/store/...-gmp") for instance. Discard the 'label' in
537 ;; such lists.
538 (let ((deps (match deps
539 (((labels directories) ...)
540 directories)
541 ((directories ...)
542 directories))))
543 (letrec* ((deps* (map (cute infer-dependency <> (delay entry))
544 deps))
545 (entry (manifest-entry
546 (name name)
547 (version version)
548 (output output)
549 (item path)
550 (dependencies deps*)
551 (search-paths
552 (infer-search-paths name version)))))
553 entry)))
554 name version output path deps)))
555
556 ;; Version 2 adds search paths and is slightly more verbose.
557 (('manifest ('version 2 minor-version ...)
558 ('packages ((name version output path
559 ('propagated-inputs deps)
560 ('search-paths search-paths)
561 extra-stuff ...)
562 ...)))
563 (manifest
564 (map (lambda (name version output path deps search-paths)
565 (letrec* ((deps* (map (cute infer-dependency <> (delay entry))
566 deps))
567 (entry (manifest-entry
568 (name name)
569 (version version)
570 (output output)
571 (item path)
572 (dependencies deps*)
573 (search-paths
574 (map sexp->search-path-specification
575 search-paths)))))
576 entry))
577 name version output path deps search-paths)))
578
579 ;; Version 3 represents DEPS as full-blown manifest entries.
580 (('manifest ('version 3 minor-version ...)
581 ('packages (entries ...)))
582 (manifest (map sexp->manifest-entry entries)))
583 (_
584 (raise (condition
585 (&message (message "unsupported manifest format")))))))
586
587 (define (read-manifest port)
588 "Return the packages listed in MANIFEST."
589 (sexp->manifest (read port)))
590
591 (define (concatenate-manifests lst)
592 "Concatenate the manifests listed in LST and return the resulting manifest."
593 (manifest (append-map manifest-entries lst)))
594
595 (define (map-manifest-entries proc manifest)
596 "Apply PROC to all the entries of MANIFEST and return a new manifest."
597 (make-manifest
598 (map proc (manifest-entries manifest))))
599
600 (define (entry-predicate pattern)
601 "Return a procedure that returns #t when passed a manifest entry that
602 matches NAME/OUTPUT/VERSION. OUTPUT and VERSION may be #f, in which case they
603 are ignored."
604 (match pattern
605 (($ <manifest-pattern> name version output)
606 (match-lambda
607 (($ <manifest-entry> entry-name entry-version entry-output)
608 (and (string=? entry-name name)
609 (or (not entry-output) (not output)
610 (string=? entry-output output))
611 (or (not version)
612 (string=? entry-version version))))))))
613
614 (define (manifest-remove manifest patterns)
615 "Remove entries for each of PATTERNS from MANIFEST. Each item in PATTERNS
616 must be a manifest-pattern."
617 (define (remove-entry pattern lst)
618 (remove (entry-predicate pattern) lst))
619
620 (make-manifest (fold remove-entry
621 (manifest-entries manifest)
622 patterns)))
623
624 (define (manifest-add manifest entries)
625 "Add a list of manifest ENTRIES to MANIFEST and return new manifest.
626 Remove MANIFEST entries that have the same name and output as ENTRIES."
627 (define (same-entry? entry name output)
628 (match entry
629 (($ <manifest-entry> entry-name _ entry-output _)
630 (and (equal? name entry-name)
631 (equal? output entry-output)))))
632
633 (make-manifest
634 (fold (lambda (entry result) ;XXX: quadratic
635 (match entry
636 (($ <manifest-entry> name _ out _)
637 (cons entry
638 (remove (cut same-entry? <> name out)
639 result)))))
640 (manifest-entries manifest)
641 entries)))
642
643 (define (manifest-lookup manifest pattern)
644 "Return the first item of MANIFEST that matches PATTERN, or #f if there is
645 no match.."
646 (find (entry-predicate pattern)
647 (manifest-entries manifest)))
648
649 (define (manifest-installed? manifest pattern)
650 "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern),
651 #f otherwise."
652 (->bool (manifest-lookup manifest pattern)))
653
654 (define (manifest-matching-entries manifest patterns)
655 "Return all the entries of MANIFEST that match one of the PATTERNS. Raise
656 an '&unmatched-pattern-error' if none of the entries of MANIFEST matches one
657 of PATTERNS."
658 (fold-right (lambda (pattern matches)
659 (match (filter (entry-predicate pattern)
660 (manifest-entries manifest))
661 (()
662 (raise (condition
663 (&unmatched-pattern-error
664 (pattern pattern)
665 (manifest manifest)))))
666 (lst
667 (append lst matches))))
668 '()
669 patterns))
670
671 (define (manifest-search-paths manifest)
672 "Return the list of search path specifications that apply to MANIFEST,
673 including the search path specification for $PATH."
674 (delete-duplicates
675 (cons $PATH
676 (append-map manifest-entry-search-paths
677 (manifest-entries manifest)))))
678
679 (define* (manifest->code manifest
680 #:key (entry-package-version (const "")))
681 "Return an sexp representing code to build an approximate version of
682 MANIFEST; the code is wrapped in a top-level 'begin' form. Call
683 ENTRY-PACKAGE-VERSION to determine the version number to use in the spec for a
684 given entry; it can be set to 'manifest-entry-version' for fully-specified
685 version numbers, or to some other procedure to disambiguate versions for
686 packages for which several versions are available."
687 (define (entry-transformations entry)
688 ;; Return the transformations that apply to ENTRY.
689 (assoc-ref (manifest-entry-properties entry) 'transformations))
690
691 (define transformation-procedures
692 ;; List of transformation options/procedure name pairs.
693 (let loop ((entries (manifest-entries manifest))
694 (counter 1)
695 (result '()))
696 (match entries
697 (() result)
698 ((entry . tail)
699 (match (entry-transformations entry)
700 (#f
701 (loop tail counter result))
702 (options
703 (if (assoc-ref result options)
704 (loop tail counter result)
705 (loop tail (+ 1 counter)
706 (alist-cons options
707 (string->symbol
708 (format #f "transform~a" counter))
709 result)))))))))
710
711 (define (qualified-name entry)
712 ;; Return the name of ENTRY possibly with "@" followed by a version.
713 (match (entry-package-version entry)
714 ("" (manifest-entry-name entry))
715 (version (string-append (manifest-entry-name entry)
716 "@" version))))
717
718 (if (null? transformation-procedures)
719 `(begin ;simplest case
720 (specifications->manifest
721 (list ,@(map (lambda (entry)
722 (match (manifest-entry-output entry)
723 ("out" (qualified-name entry))
724 (output (string-append (qualified-name entry)
725 ":" output))))
726 (manifest-entries manifest)))))
727 (let* ((transform (lambda (options exp)
728 (if (not options)
729 exp
730 (let ((proc (assoc-ref transformation-procedures
731 options)))
732 `(,proc ,exp))))))
733 `(begin ;transformations apply
734 (use-modules (guix transformations))
735
736 ,@(map (match-lambda
737 ((options . name)
738 `(define ,name
739 (options->transformation ',options))))
740 transformation-procedures)
741
742 (packages->manifest
743 (list ,@(map (lambda (entry)
744 (define options
745 (entry-transformations entry))
746
747 (define name
748 (qualified-name entry))
749
750 (match (manifest-entry-output entry)
751 ("out"
752 (transform options
753 `(specification->package ,name)))
754 (output
755 `(list ,(transform
756 options
757 `(specification->package ,name))
758 ,output))))
759 (manifest-entries manifest))))))))
760
761 \f
762 ;;;
763 ;;; Manifest transactions.
764 ;;;
765
766 (define-record-type* <manifest-transaction> manifest-transaction
767 make-manifest-transaction
768 manifest-transaction?
769 (install manifest-transaction-install ; list of <manifest-entry>
770 (default '()))
771 (remove manifest-transaction-remove ; list of <manifest-pattern>
772 (default '())))
773
774 (define (manifest-transaction-install-entry entry transaction)
775 "Augment TRANSACTION's set of installed packages with ENTRY, a
776 <manifest-entry>."
777 (manifest-transaction
778 (inherit transaction)
779 (install
780 (cons entry (manifest-transaction-install transaction)))))
781
782 (define (manifest-transaction-remove-pattern pattern transaction)
783 "Add PATTERN to TRANSACTION's list of packages to remove."
784 (manifest-transaction
785 (inherit transaction)
786 (remove
787 (cons pattern (manifest-transaction-remove transaction)))))
788
789 (define (manifest-transaction-null? transaction)
790 "Return true if TRANSACTION has no effect---i.e., it neither installs nor
791 remove software."
792 (match transaction
793 (($ <manifest-transaction> () ()) #t)
794 (($ <manifest-transaction> _ _) #f)))
795
796 (define (manifest-transaction-removal-candidate? entry transaction)
797 "Return true if ENTRY is a candidate for removal in TRANSACTION."
798 (any (lambda (pattern)
799 ((entry-predicate pattern) entry))
800 (manifest-transaction-remove transaction)))
801
802 (define (manifest-transaction-effects manifest transaction)
803 "Compute the effect of applying TRANSACTION to MANIFEST. Return 4 values:
804 the list of packages that would be removed, installed, upgraded, or downgraded
805 when applying TRANSACTION to MANIFEST. Upgrades are represented as pairs
806 where the head is the entry being upgraded and the tail is the entry that will
807 replace it."
808 (define (manifest-entry->pattern entry)
809 (manifest-pattern
810 (name (manifest-entry-name entry))
811 (output (manifest-entry-output entry))))
812 (define manifest-entry-pair=?
813 (match-lambda*
814 (((m1a . m2a) (m1b . m2b))
815 (and (manifest-entry=? m1a m1b)
816 (manifest-entry=? m2a m2b)))
817 (_ #f)))
818
819 (let loop ((input (manifest-transaction-install transaction))
820 (install '())
821 (upgrade '())
822 (downgrade '()))
823 (match input
824 (()
825 (let ((remove (manifest-transaction-remove transaction)))
826 (values (delete-duplicates
827 (manifest-matching-entries manifest remove)
828 manifest-entry=?)
829 (delete-duplicates (reverse install) manifest-entry=?)
830 (delete-duplicates
831 (reverse upgrade)
832 manifest-entry-pair=?)
833 (delete-duplicates
834 (reverse downgrade)
835 manifest-entry-pair=?))))
836 ((entry rest ...)
837 ;; Check whether installing ENTRY corresponds to the installation of a
838 ;; new package or to an upgrade.
839
840 ;; XXX: When the exact same output directory is installed, we're not
841 ;; really upgrading anything. Add a check for that case.
842 (let* ((pattern (manifest-entry->pattern entry))
843 (previous (manifest-lookup manifest pattern))
844 (newer? (and previous
845 (version>=? (manifest-entry-version entry)
846 (manifest-entry-version previous)))))
847 (loop rest
848 (if previous install (cons entry install))
849 (if (and previous newer?)
850 (alist-cons previous entry upgrade)
851 upgrade)
852 (if (and previous (not newer?))
853 (alist-cons previous entry downgrade)
854 downgrade)))))))
855
856 (define (manifest-perform-transaction manifest transaction)
857 "Perform TRANSACTION on MANIFEST and return the new manifest."
858 (let ((install (manifest-transaction-install transaction))
859 (remove (manifest-transaction-remove transaction)))
860 (manifest-add (manifest-remove manifest remove)
861 install)))
862
863 \f
864 ;;;
865 ;;; Profiles.
866 ;;;
867
868 (define (manifest-inputs manifest)
869 "Return a list of <gexp-input> objects for MANIFEST."
870 (define entry->input
871 (match-lambda
872 (($ <manifest-entry> name version output thing deps)
873 ;; THING may be a package or a file name. In the latter case, assume
874 ;; it's already valid.
875 (cons (gexp-input thing output)
876 (append-map entry->input deps)))))
877
878 (append-map entry->input (manifest-entries manifest)))
879
880 (define* (manifest-lookup-package manifest name #:optional version)
881 "Return as a monadic value the first package or store path referenced by
882 MANIFEST that is named NAME and optionally has the given VERSION prefix, or #f
883 if not found."
884 ;; Return as a monadic value the package or store path referenced by the
885 ;; manifest ENTRY, or #f if not referenced.
886 (define (entry-lookup-package entry)
887 (define (find-among-inputs inputs)
888 (find (lambda (input)
889 (and (package? input)
890 (equal? name (package-name input))
891 (if version
892 (string-prefix? version (package-version input))
893 #t)))
894 inputs))
895 (define (find-among-store-items items)
896 (find (lambda (item)
897 (let-values (((name* version*)
898 (package-name->name+version
899 (store-path-package-name item))))
900 (and (string=? name name*)
901 (if version
902 (string-prefix? version version*)
903 #t))))
904 items))
905
906 (with-monad %store-monad
907 (match (manifest-entry-item entry)
908 ((? package? package)
909 (match (cons (list (package-name package) package)
910 (package-transitive-inputs package))
911 (((labels inputs . _) ...)
912 (return (find-among-inputs inputs)))))
913 ((? string? item)
914 (mlet %store-monad ((refs (references* item)))
915 (return (find-among-store-items refs))))
916 (item
917 ;; XXX: ITEM might be a 'computed-file' or anything like that, in
918 ;; which case we don't know what to do. The fix may be to check
919 ;; references once ITEM is compiled, as proposed at
920 ;; <https://bugs.gnu.org/29927>.
921 (return #f)))))
922
923 (anym %store-monad
924 entry-lookup-package (manifest-entries manifest)))
925
926 (define (info-dir-file manifest)
927 "Return a derivation that builds the 'dir' file for all the entries of
928 MANIFEST."
929 (define texinfo ;lazy reference
930 (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
931 (define gzip ;lazy reference
932 (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
933 (define glibc-utf8-locales ;lazy reference
934 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
935
936 (define build
937 (with-imported-modules '((guix build utils))
938 #~(begin
939 (use-modules (guix build utils)
940 (srfi srfi-1) (srfi srfi-26)
941 (ice-9 ftw))
942
943 (define (info-file? file)
944 (or (string-suffix? ".info" file)
945 (string-suffix? ".info.gz" file)))
946
947 (define (info-files top)
948 (let ((infodir (string-append top "/share/info")))
949 (map (cut string-append infodir "/" <>)
950 (or (scandir infodir info-file?) '()))))
951
952 (define (info-file-language file)
953 (let* ((base (if (string-suffix? ".gz" file)
954 (basename file ".info.gz")
955 (basename file ".info")))
956 (dot (string-rindex base #\.)))
957 (if dot
958 (string-drop base (+ 1 dot))
959 "en")))
960
961 (define (install-info info)
962 (let ((language (info-file-language info)))
963 ;; We need to choose a valid locale for $LANGUAGE to be honored.
964 (setenv "LC_ALL" "en_US.utf8")
965 (setenv "LANGUAGE" language)
966 (zero?
967 (system* #+(file-append texinfo "/bin/install-info")
968 "--silent" info
969 (apply string-append #$output "/share/info/dir"
970 (if (string=? "en" language)
971 '("")
972 `("." ,language)))))))
973
974 (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
975 (setenv "GUIX_LOCPATH"
976 #+(file-append glibc-utf8-locales "/lib/locale"))
977
978 (mkdir-p (string-append #$output "/share/info"))
979 (exit (every install-info
980 (append-map info-files
981 '#$(manifest-inputs manifest)))))))
982
983 (gexp->derivation "info-dir" build
984 #:local-build? #t
985 #:substitutable? #f
986 #:properties
987 `((type . profile-hook)
988 (hook . info-dir))))
989
990 (define (ghc-package-cache-file manifest)
991 "Return a derivation that builds the GHC 'package.cache' file for all the
992 entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
993 (define ghc ;lazy reference
994 (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
995
996 (define build
997 (with-imported-modules '((guix build utils))
998 #~(begin
999 (use-modules (guix build utils)
1000 (srfi srfi-1) (srfi srfi-26)
1001 (ice-9 ftw))
1002
1003 (define ghc-name-version
1004 (let* ((base (basename #+ghc)))
1005 (string-drop base
1006 (+ 1 (string-index base #\-)))))
1007
1008 (define db-subdir
1009 (string-append "lib/" ghc-name-version "/package.conf.d"))
1010
1011 (define db-dir
1012 (string-append #$output "/" db-subdir))
1013
1014 (define (conf-files top)
1015 (let ((db (string-append top "/" db-subdir)))
1016 (if (file-exists? db)
1017 (find-files db "\\.conf$")
1018 '())))
1019
1020 (define (copy-conf-file conf)
1021 (let ((base (basename conf)))
1022 (copy-file conf (string-append db-dir "/" base))))
1023
1024 (system* (string-append #+ghc "/bin/ghc-pkg") "init" db-dir)
1025 (for-each copy-conf-file
1026 (append-map conf-files
1027 (delete-duplicates
1028 '#$(manifest-inputs manifest))))
1029 (let ((success
1030 (zero?
1031 (system* (string-append #+ghc "/bin/ghc-pkg") "recache"
1032 (string-append "--package-db=" db-dir)))))
1033 (for-each delete-file (find-files db-dir "\\.conf$"))
1034 (exit success)))))
1035
1036 (with-monad %store-monad
1037 ;; Don't depend on GHC when there's nothing to do.
1038 (if (any (cut string-prefix? "ghc" <>)
1039 (map manifest-entry-name (manifest-entries manifest)))
1040 (gexp->derivation "ghc-package-cache" build
1041 #:local-build? #t
1042 #:substitutable? #f
1043 #:properties
1044 `((type . profile-hook)
1045 (hook . ghc-package-cache)))
1046 (return #f))))
1047
1048 (define (ca-certificate-bundle manifest)
1049 "Return a derivation that builds a single-file bundle containing the CA
1050 certificates in the /etc/ssl/certs sub-directories of the packages in
1051 MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
1052 ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
1053 ;; for a discussion.
1054
1055 (define glibc-utf8-locales ;lazy reference
1056 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
1057
1058 (define build
1059 (with-imported-modules '((guix build utils))
1060 #~(begin
1061 (use-modules (guix build utils)
1062 (rnrs io ports)
1063 (srfi srfi-1)
1064 (srfi srfi-26)
1065 (ice-9 ftw)
1066 (ice-9 match))
1067
1068 (define (pem-file? file)
1069 (string-suffix? ".pem" file))
1070
1071 (define (ca-files top)
1072 (let ((cert-dir (string-append top "/etc/ssl/certs")))
1073 (map (cut string-append cert-dir "/" <>)
1074 (or (scandir cert-dir pem-file?) '()))))
1075
1076 (define (concatenate-files files result)
1077 "Make RESULT the concatenation of all of FILES."
1078 (define (dump file port)
1079 (display (call-with-input-file file get-string-all)
1080 port)
1081 (newline port)) ;required, see <https://bugs.debian.org/635570>
1082
1083 (call-with-output-file result
1084 (lambda (port)
1085 (for-each (cut dump <> port) files))))
1086
1087 ;; Some file names in the NSS certificates are UTF-8 encoded so
1088 ;; install a UTF-8 locale.
1089 (setenv "LOCPATH"
1090 (string-append #+glibc-utf8-locales "/lib/locale/"
1091 #+(version-major+minor
1092 (package-version glibc-utf8-locales))))
1093 (setlocale LC_ALL "en_US.utf8")
1094
1095 (match (append-map ca-files '#$(manifest-inputs manifest))
1096 (()
1097 ;; Since there are no CA files, just create an empty directory. Do
1098 ;; not create the etc/ssl/certs sub-directory, since that would
1099 ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
1100 ;; defined.
1101 (mkdir #$output)
1102 #t)
1103 ((ca-files ...)
1104 (let ((result (string-append #$output "/etc/ssl/certs")))
1105 (mkdir-p result)
1106 (concatenate-files ca-files
1107 (string-append result
1108 "/ca-certificates.crt"))
1109 #t))))))
1110
1111 (gexp->derivation "ca-certificate-bundle" build
1112 #:local-build? #t
1113 #:substitutable? #f
1114 #:properties
1115 `((type . profile-hook)
1116 (hook . ca-certificate-bundle))))
1117
1118 (define (emacs-subdirs manifest)
1119 (define build
1120 (with-imported-modules (source-module-closure
1121 '((guix build profiles)
1122 (guix build utils)))
1123 #~(begin
1124 (use-modules (guix build utils)
1125 (guix build profiles)
1126 (ice-9 ftw) ; scandir
1127 (srfi srfi-1) ; append-map
1128 (srfi srfi-26))
1129
1130 (let ((destdir (string-append #$output "/share/emacs/site-lisp"))
1131 (subdirs
1132 (append-map
1133 (lambda (dir)
1134 (filter
1135 file-is-directory?
1136 (map (cute string-append dir "/" <>)
1137 (scandir dir (negate (cute member <> '("." "..")))))))
1138 (filter file-exists?
1139 (map (cute string-append <> "/share/emacs/site-lisp")
1140 '#$(manifest-inputs manifest))))))
1141 (mkdir-p destdir)
1142 (with-directory-excursion destdir
1143 (call-with-output-file "subdirs.el"
1144 (lambda (port)
1145 (write
1146 `(normal-top-level-add-to-load-path
1147 (list ,@subdirs))
1148 port)
1149 (newline port)
1150 #t)))))))
1151 (gexp->derivation "emacs-subdirs" build
1152 #:local-build? #t
1153 #:substitutable? #f
1154 #:properties
1155 `((type . profile-hook)
1156 (hook . emacs-subdirs))))
1157
1158 (define (glib-schemas manifest)
1159 "Return a derivation that unions all schemas from manifest entries and
1160 creates the Glib 'gschemas.compiled' file."
1161 (define glib ; lazy reference
1162 (module-ref (resolve-interface '(gnu packages glib)) 'glib))
1163
1164 (mlet %store-monad ((%glib (manifest-lookup-package manifest "glib"))
1165 ;; XXX: Can't use glib-compile-schemas corresponding
1166 ;; to the glib referenced by 'manifest'. Because
1167 ;; '%glib' can be either a package or store path, and
1168 ;; there's no way to get the "bin" output for the later.
1169 (glib-compile-schemas
1170 -> #~(string-append #+glib:bin
1171 "/bin/glib-compile-schemas")))
1172
1173 (define build
1174 (with-imported-modules '((guix build utils)
1175 (guix build union)
1176 (guix build profiles)
1177 (guix search-paths)
1178 (guix records))
1179 #~(begin
1180 (use-modules (guix build utils)
1181 (guix build union)
1182 (guix build profiles)
1183 (srfi srfi-26))
1184
1185 (let* ((destdir (string-append #$output "/share/glib-2.0/schemas"))
1186 (schemadirs (filter file-exists?
1187 (map (cut string-append <> "/share/glib-2.0/schemas")
1188 '#$(manifest-inputs manifest)))))
1189
1190 ;; Union all the schemas.
1191 (mkdir-p (string-append #$output "/share/glib-2.0"))
1192 (union-build destdir schemadirs
1193 #:log-port (%make-void-port "w"))
1194
1195 (let ((dir destdir))
1196 (when (file-is-directory? dir)
1197 (ensure-writable-directory dir)
1198 (invoke #+glib-compile-schemas
1199 (string-append "--targetdir=" dir)
1200 dir)))))))
1201
1202 ;; Don't run the hook when there's nothing to do.
1203 (if %glib
1204 (gexp->derivation "glib-schemas" build
1205 #:local-build? #t
1206 #:substitutable? #f
1207 #:properties
1208 `((type . profile-hook)
1209 (hook . glib-schemas)))
1210 (return #f))))
1211
1212 (define (gtk-icon-themes manifest)
1213 "Return a derivation that unions all icon themes from manifest entries and
1214 creates the GTK+ 'icon-theme.cache' file for each theme."
1215 (define gtk+ ; lazy reference
1216 (module-ref (resolve-interface '(gnu packages gtk)) 'gtk+))
1217
1218 (mlet %store-monad ((%gtk+ (manifest-lookup-package manifest "gtk+"))
1219 ;; XXX: Can't use gtk-update-icon-cache corresponding
1220 ;; to the gtk+ referenced by 'manifest'. Because
1221 ;; '%gtk+' can be either a package or store path, and
1222 ;; there's no way to get the "bin" output for the later.
1223 (gtk-update-icon-cache
1224 -> #~(string-append #+gtk+:bin
1225 "/bin/gtk-update-icon-cache")))
1226
1227 (define build
1228 (with-imported-modules '((guix build utils)
1229 (guix build union)
1230 (guix build profiles)
1231 (guix search-paths)
1232 (guix records))
1233 #~(begin
1234 (use-modules (guix build utils)
1235 (guix build union)
1236 (guix build profiles)
1237 (srfi srfi-26)
1238 (ice-9 ftw))
1239
1240 (let* ((destdir (string-append #$output "/share/icons"))
1241 (icondirs (filter file-exists?
1242 (map (cut string-append <> "/share/icons")
1243 '#$(manifest-inputs manifest)))))
1244
1245 ;; Union all the icons.
1246 (mkdir-p (string-append #$output "/share"))
1247 (union-build destdir icondirs
1248 #:log-port (%make-void-port "w"))
1249
1250 ;; Update the 'icon-theme.cache' file for each icon theme.
1251 (for-each
1252 (lambda (theme)
1253 (let ((dir (string-append destdir "/" theme)))
1254 ;; Occasionally DESTDIR contains plain files, such as
1255 ;; "abiword_48.png". Ignore these.
1256 (when (file-is-directory? dir)
1257 (ensure-writable-directory dir)
1258 (system* #+gtk-update-icon-cache "-t" dir "--quiet"))))
1259 (scandir destdir (negate (cut member <> '("." "..")))))))))
1260
1261 ;; Don't run the hook when there's nothing to do.
1262 (if %gtk+
1263 (gexp->derivation "gtk-icon-themes" build
1264 #:local-build? #t
1265 #:substitutable? #f
1266 #:properties
1267 `((type . profile-hook)
1268 (hook . gtk-icon-themes)))
1269 (return #f))))
1270
1271 (define (gtk-im-modules manifest)
1272 "Return a derivation that builds the cache files for input method modules
1273 for both major versions of GTK+."
1274
1275 (mlet %store-monad ((gtk+ (manifest-lookup-package manifest "gtk+" "3"))
1276 (gtk+-2 (manifest-lookup-package manifest "gtk+" "2")))
1277
1278 (define (build gtk gtk-version query)
1279 (let ((major (string-take gtk-version 1)))
1280 (with-imported-modules '((guix build utils)
1281 (guix build union)
1282 (guix build profiles)
1283 (guix search-paths)
1284 (guix records))
1285 #~(begin
1286 (use-modules (guix build utils)
1287 (guix build union)
1288 (guix build profiles)
1289 (ice-9 popen)
1290 (srfi srfi-1)
1291 (srfi srfi-26))
1292
1293 (let* ((prefix (string-append "/lib/gtk-" #$major ".0/"
1294 #$gtk-version))
1295 (destdir (string-append #$output prefix))
1296 (moddirs (cons (string-append #$gtk prefix "/immodules")
1297 (filter file-exists?
1298 (map (cut string-append <> prefix "/immodules")
1299 '#$(manifest-inputs manifest)))))
1300 (modules (append-map (cut find-files <> "\\.so$")
1301 moddirs)))
1302
1303 ;; Generate a new immodules cache file.
1304 (mkdir-p (string-append #$output prefix))
1305 (let ((pipe (apply open-pipe* OPEN_READ #$query modules))
1306 (outfile (string-append #$output prefix
1307 "/immodules-gtk" #$major ".cache")))
1308 (dynamic-wind
1309 (const #t)
1310 (lambda ()
1311 (call-with-output-file outfile
1312 (lambda (out)
1313 (while (not (eof-object? (peek-char pipe)))
1314 (write-char (read-char pipe) out))))
1315 #t)
1316 (lambda ()
1317 (close-pipe pipe)))))))))
1318
1319 ;; Don't run the hook when there's nothing to do.
1320 (let* ((pkg-gtk+ (module-ref ; lazy reference
1321 (resolve-interface '(gnu packages gtk)) 'gtk+))
1322 (pkg-gtk+2 (module-ref ; lazy reference
1323 (resolve-interface '(gnu packages gtk)) 'gtk+-2))
1324 (gexp #~(begin
1325 #$(if gtk+
1326 (build
1327 gtk+ "3.0.0"
1328 ;; Use 'gtk-query-immodules-3.0' from the 'bin'
1329 ;; output of latest gtk+ package.
1330 #~(string-append
1331 #$pkg-gtk+:bin "/bin/gtk-query-immodules-3.0"))
1332 #t)
1333 #$(if gtk+-2
1334 (build
1335 gtk+-2 "2.10.0"
1336 #~(string-append
1337 #$pkg-gtk+2:bin "/bin/gtk-query-immodules-2.0"))
1338 #t))))
1339 (if (or gtk+ gtk+-2)
1340 (gexp->derivation "gtk-im-modules" gexp
1341 #:local-build? #t
1342 #:substitutable? #f
1343 #:properties
1344 `((type . profile-hook)
1345 (hook . gtk-im-modules)))
1346 (return #f)))))
1347
1348 (define (linux-module-database manifest)
1349 "Return a derivation that unites all the kernel modules of the manifest
1350 and creates the dependency graph of all these kernel modules.
1351
1352 This is meant to be used as a profile hook."
1353 (define kmod ; lazy reference
1354 (module-ref (resolve-interface '(gnu packages linux)) 'kmod))
1355
1356 (define guile-zlib
1357 (module-ref (resolve-interface '(gnu packages guile)) 'guile-zlib))
1358
1359 (define build
1360 (with-imported-modules (source-module-closure
1361 '((guix build utils)
1362 (gnu build linux-modules)))
1363 (with-extensions (list guile-zlib)
1364 #~(begin
1365 (use-modules (ice-9 ftw)
1366 (ice-9 match)
1367 (srfi srfi-1) ; append-map
1368 (gnu build linux-modules))
1369
1370 (let* ((inputs '#$(manifest-inputs manifest))
1371 (module-directories
1372 (map (lambda (directory)
1373 (string-append directory "/lib/modules"))
1374 inputs))
1375 (directory-entries
1376 (lambda (directory)
1377 (or (scandir directory
1378 (lambda (basename)
1379 (not (string-prefix? "." basename))))
1380 '())))
1381 ;; Note: Should usually result in one entry.
1382 (versions (delete-duplicates
1383 (append-map directory-entries
1384 module-directories))))
1385 (match versions
1386 ((version)
1387 (let ((old-path (getenv "PATH")))
1388 (setenv "PATH" #+(file-append kmod "/bin"))
1389 (make-linux-module-directory inputs version #$output)
1390 (setenv "PATH" old-path)))
1391 (()
1392 ;; Nothing here, maybe because this is a kernel with
1393 ;; CONFIG_MODULES=n.
1394 (mkdir #$output))
1395 (_ (error "Specified Linux kernel and Linux kernel modules
1396 are not all of the same version"))))))))
1397 (gexp->derivation "linux-module-database" build
1398 #:local-build? #t
1399 #:substitutable? #f
1400 #:properties
1401 `((type . profile-hook)
1402 (hook . linux-module-database))))
1403
1404 (define (xdg-desktop-database manifest)
1405 "Return a derivation that builds the @file{mimeinfo.cache} database from
1406 desktop files. It's used to query what applications can handle a given
1407 MIME type."
1408 (define desktop-file-utils ; lazy reference
1409 (module-ref (resolve-interface '(gnu packages freedesktop))
1410 'desktop-file-utils))
1411
1412 (mlet %store-monad ((glib
1413 (manifest-lookup-package
1414 manifest "glib")))
1415 (define build
1416 (with-imported-modules '((guix build utils)
1417 (guix build union))
1418 #~(begin
1419 (use-modules (srfi srfi-26)
1420 (guix build utils)
1421 (guix build union))
1422 (let* ((destdir (string-append #$output "/share/applications"))
1423 (appdirs (filter file-exists?
1424 (map (cut string-append <>
1425 "/share/applications")
1426 '#$(manifest-inputs manifest))))
1427 (update-desktop-database (string-append
1428 #+desktop-file-utils
1429 "/bin/update-desktop-database")))
1430 (mkdir-p (string-append #$output "/share"))
1431 (union-build destdir appdirs
1432 #:log-port (%make-void-port "w"))
1433 (exit (zero? (system* update-desktop-database destdir)))))))
1434
1435 ;; Don't run the hook when 'glib' is not referenced.
1436 (if glib
1437 (gexp->derivation "xdg-desktop-database" build
1438 #:local-build? #t
1439 #:substitutable? #f
1440 #:properties
1441 `((type . profile-hook)
1442 (hook . xdg-desktop-database)))
1443 (return #f))))
1444
1445 (define (xdg-mime-database manifest)
1446 "Return a derivation that builds the @file{mime.cache} database from manifest
1447 entries. It's used to query the MIME type of a given file."
1448 (define shared-mime-info ; lazy reference
1449 (module-ref (resolve-interface '(gnu packages gnome)) 'shared-mime-info))
1450
1451 (mlet %store-monad ((glib (manifest-lookup-package manifest "glib")))
1452 (define build
1453 (with-imported-modules '((guix build utils)
1454 (guix build union))
1455 #~(begin
1456 (use-modules (guix build utils)
1457 (guix build union)
1458 (srfi srfi-26)
1459 (ice-9 match))
1460
1461 (let* ((datadir (string-append #$output "/share"))
1462 (destdir (string-append datadir "/mime"))
1463 (pkgdirs (filter file-exists?
1464 (map (cut string-append <>
1465 "/share/mime/packages")
1466 (cons #+shared-mime-info
1467 '#$(manifest-inputs manifest))))))
1468
1469 (match pkgdirs
1470 ((shared-mime-info)
1471 ;; PKGDIRS contains nothing but 'shared-mime-info', which
1472 ;; already contains its database, so nothing to do.
1473 (mkdir-p datadir)
1474 (symlink #$(file-append shared-mime-info "/share/mime")
1475 destdir))
1476 (_
1477 ;; PKGDIRS contains additional packages providing
1478 ;; 'share/mime/packages' (very few packages do so) so rebuild
1479 ;; the database. TODO: Find a way to avoid reprocessing
1480 ;; 'shared-mime-info', which is the most expensive one.
1481 (mkdir-p destdir)
1482 (union-build (string-append destdir "/packages") pkgdirs
1483 #:log-port (%make-void-port "w"))
1484 (setenv "XDG_DATA_HOME" datadir)
1485 (invoke #+(file-append shared-mime-info
1486 "/bin/update-mime-database")
1487 destdir)))))))
1488
1489 ;; Don't run the hook when there are no GLib based applications.
1490 (if glib
1491 (gexp->derivation "xdg-mime-database" build
1492 #:local-build? #t
1493 #:substitutable? #f
1494 #:properties
1495 `((type . profile-hook)
1496 (hook . xdg-mime-database)))
1497 (return #f))))
1498
1499 ;; Several font packages may install font files into same directory, so
1500 ;; fonts.dir and fonts.scale file should be generated here, instead of in
1501 ;; packages.
1502 (define (fonts-dir-file manifest)
1503 "Return a derivation that builds the @file{fonts.dir} and @file{fonts.scale}
1504 files for the fonts of the @var{manifest} entries."
1505 (define mkfontscale
1506 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontscale))
1507
1508 (define mkfontdir
1509 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontdir))
1510
1511 (define build
1512 #~(begin
1513 (use-modules (srfi srfi-26)
1514 (guix build utils)
1515 (guix build union))
1516 (let ((fonts-dirs (filter file-exists?
1517 (map (cut string-append <>
1518 "/share/fonts")
1519 '#$(manifest-inputs manifest)))))
1520 (mkdir #$output)
1521 (if (null? fonts-dirs)
1522 (exit #t)
1523 (let* ((share-dir (string-append #$output "/share"))
1524 (fonts-dir (string-append share-dir "/fonts"))
1525 (mkfontscale (string-append #+mkfontscale
1526 "/bin/mkfontscale"))
1527 (mkfontdir (string-append #+mkfontdir
1528 "/bin/mkfontdir"))
1529 (empty-file? (lambda (filename)
1530 (call-with-ascii-input-file filename
1531 (lambda (p)
1532 (eqv? #\0 (read-char p))))))
1533 (fonts-dir-file "fonts.dir")
1534 (fonts-scale-file "fonts.scale"))
1535 (mkdir-p share-dir)
1536 ;; Create all sub-directories, because we may create fonts.dir
1537 ;; and fonts.scale files in the sub-directories.
1538 (union-build fonts-dir fonts-dirs
1539 #:log-port (%make-void-port "w")
1540 #:create-all-directories? #t)
1541 (let ((directories (find-files fonts-dir
1542 (lambda (file stat)
1543 (eq? 'directory (stat:type stat)))
1544 #:directories? #t)))
1545 (for-each (lambda (dir)
1546 (with-directory-excursion dir
1547 (when (file-exists? fonts-scale-file)
1548 (delete-file fonts-scale-file))
1549 (when (file-exists? fonts-dir-file)
1550 (delete-file fonts-dir-file))
1551 (unless (and (zero? (system* mkfontscale))
1552 (zero? (system* mkfontdir)))
1553 (exit #f))
1554 (when (and (file-exists? fonts-scale-file)
1555 (empty-file? fonts-scale-file))
1556 (delete-file fonts-scale-file))
1557 (when (and (file-exists? fonts-dir-file)
1558 (empty-file? fonts-dir-file))
1559 (delete-file fonts-dir-file))))
1560 directories)))))))
1561
1562 (gexp->derivation "fonts-dir" build
1563 #:modules '((guix build utils)
1564 (guix build union)
1565 (srfi srfi-26))
1566 #:local-build? #t
1567 #:substitutable? #f
1568 #:properties
1569 `((type . profile-hook)
1570 (hook . fonts-dir))))
1571
1572 (define (manual-database manifest)
1573 "Return a derivation that builds the manual page database (\"mandb\") for
1574 the entries in MANIFEST."
1575 (define gdbm-ffi
1576 (module-ref (resolve-interface '(gnu packages guile))
1577 'guile-gdbm-ffi))
1578
1579 (define guile-zlib
1580 (module-ref (resolve-interface '(gnu packages guile)) 'guile-zlib))
1581
1582 (define modules
1583 (delete '(guix config)
1584 (source-module-closure `((guix build utils)
1585 (guix man-db)))))
1586
1587 (define build
1588 (with-imported-modules modules
1589 (with-extensions (list gdbm-ffi ;for (guix man-db)
1590 guile-zlib)
1591 #~(begin
1592 (use-modules (guix man-db)
1593 (guix build utils)
1594 (ice-9 threads)
1595 (srfi srfi-1)
1596 (srfi srfi-19))
1597
1598 (define (print-string msg)
1599 (display msg)
1600 (force-output))
1601
1602 (define-syntax-rule (print fmt args ...)
1603 ;; Build up the string and display it at once.
1604 (print-string (format #f fmt args ...)))
1605
1606 (define (compute-entry directory count total)
1607 (print "\r[~3d/~3d] building list of man-db entries..."
1608 count total)
1609 (let ((man (string-append directory "/share/man")))
1610 (if (directory-exists? man)
1611 (mandb-entries man)
1612 '())))
1613
1614 (define (compute-entries)
1615 ;; This is the most expensive part (I/O and CPU, due to
1616 ;; decompression), so report progress as we traverse INPUTS.
1617 ;; Cap at 4 threads because we don't see any speedup beyond that
1618 ;; on an SSD laptop.
1619 (let* ((inputs '#$(manifest-inputs manifest))
1620 (total (length inputs))
1621 (threads (min (parallel-job-count) 4)))
1622 (concatenate
1623 (n-par-map threads compute-entry inputs
1624 (iota total 1)
1625 (make-list total total)))))
1626
1627 (define man-directory
1628 (string-append #$output "/share/man"))
1629
1630 (mkdir-p man-directory)
1631
1632 (format #t "Creating manual page database...~%")
1633 (force-output)
1634 (let* ((start (current-time))
1635 (entries (compute-entries))
1636 (_ (write-mandb-database (string-append man-directory
1637 "/index.db")
1638 entries))
1639 (duration (time-difference (current-time) start)))
1640 (newline)
1641 (format #t "~a entries processed in ~,1f s~%"
1642 (length entries)
1643 (+ (time-second duration)
1644 (* (time-nanosecond duration) (expt 10 -9))))
1645 (force-output))))))
1646
1647 (gexp->derivation "manual-database" build
1648
1649 ;; Work around GDBM 1.13 issue whereby uninitialized bytes
1650 ;; get written to disk:
1651 ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29654#23>.
1652 #:env-vars `(("MALLOC_PERTURB_" . "1"))
1653
1654 #:substitutable? #f
1655 #:local-build? #t
1656 #:properties
1657 `((type . profile-hook)
1658 (hook . manual-database))))
1659
1660 (define (texlive-configuration manifest)
1661 "Return a derivation that builds a TeXlive configuration for the entries in
1662 MANIFEST."
1663 (define entry->texlive-input
1664 (match-lambda
1665 (($ <manifest-entry> name version output thing deps)
1666 (if (string-prefix? "texlive-" name)
1667 (cons (gexp-input thing output)
1668 (append-map entry->texlive-input deps))
1669 '()))))
1670 (define texlive-bin
1671 (module-ref (resolve-interface '(gnu packages tex)) 'texlive-bin))
1672 (define coreutils
1673 (module-ref (resolve-interface '(gnu packages base)) 'coreutils))
1674 (define sed
1675 (module-ref (resolve-interface '(gnu packages base)) 'sed))
1676 (define updmap.cfg
1677 (module-ref (resolve-interface '(gnu packages tex))
1678 'texlive-default-updmap.cfg))
1679 (define build
1680 (with-imported-modules '((guix build utils)
1681 (guix build union))
1682 #~(begin
1683 (use-modules (guix build utils)
1684 (guix build union)
1685 (ice-9 popen))
1686
1687 ;; Build a modifiable union of all texlive inputs. We do this so
1688 ;; that TeX live can resolve the parent and grandparent directories
1689 ;; correctly. There might be a more elegant way to accomplish this.
1690 (union-build #$output
1691 '#$(append-map entry->texlive-input
1692 (manifest-entries manifest))
1693 #:create-all-directories? #t
1694 #:log-port (%make-void-port "w"))
1695 (let ((texmf.cnf (string-append
1696 #$output
1697 "/share/texmf-dist/web2c/texmf.cnf")))
1698 (when (file-exists? texmf.cnf)
1699 (substitute* texmf.cnf
1700 (("^TEXMFROOT = .*")
1701 (string-append "TEXMFROOT = " #$output "/share\n"))
1702 (("^TEXMF = .*")
1703 "TEXMF = $TEXMFROOT/share/texmf-dist\n"))
1704
1705 ;; XXX: This is annoying, but it's necessary because texlive-bin
1706 ;; does not provide wrapped executables.
1707 (setenv "PATH"
1708 (string-append #$(file-append coreutils "/bin")
1709 ":"
1710 #$(file-append sed "/bin")))
1711 (setenv "PERL5LIB" #$(file-append texlive-bin "/share/tlpkg"))
1712 (setenv "TEXMF" (string-append #$output "/share/texmf-dist"))
1713
1714 ;; Remove invalid maps from config file.
1715 (let* ((web2c (string-append #$output "/share/texmf-config/web2c/"))
1716 (maproot (string-append #$output "/share/texmf-dist/fonts/map/"))
1717 (updmap.cfg (string-append web2c "updmap.cfg")))
1718 (mkdir-p web2c)
1719
1720 ;; Some profiles may already have this file, which prevents us
1721 ;; from copying it. Since we need to generate it from scratch
1722 ;; anyway, we delete it here.
1723 (when (file-exists? updmap.cfg)
1724 (delete-file updmap.cfg))
1725 (copy-file #$updmap.cfg updmap.cfg)
1726 (make-file-writable updmap.cfg)
1727 (let* ((port (open-pipe* OPEN_WRITE
1728 #$(file-append texlive-bin "/bin/updmap-sys")
1729 "--syncwithtrees"
1730 "--nohash"
1731 "--force"
1732 (string-append "--cnffile=" web2c "updmap.cfg"))))
1733 (display "Y\n" port)
1734 (when (not (zero? (status:exit-val (close-pipe port))))
1735 (error "failed to filter updmap.cfg")))
1736
1737 ;; Generate font maps.
1738 (invoke #$(file-append texlive-bin "/bin/updmap-sys")
1739 (string-append "--cnffile=" web2c "updmap.cfg")
1740 (string-append "--dvipdfmxoutputdir="
1741 maproot "updmap/dvipdfmx/")
1742 (string-append "--dvipsoutputdir="
1743 maproot "updmap/dvips/")
1744 (string-append "--pdftexoutputdir="
1745 maproot "updmap/pdftex/")))))
1746 #t)))
1747
1748 (with-monad %store-monad
1749 (if (any (cut string-prefix? "texlive-" <>)
1750 (map manifest-entry-name (manifest-entries manifest)))
1751 (gexp->derivation "texlive-configuration" build
1752 #:substitutable? #f
1753 #:local-build? #t
1754 #:properties
1755 `((type . profile-hook)
1756 (hook . texlive-configuration)))
1757 (return #f))))
1758
1759 (define %default-profile-hooks
1760 ;; This is the list of derivation-returning procedures that are called by
1761 ;; default when making a non-empty profile.
1762 (list info-dir-file
1763 manual-database
1764 fonts-dir-file
1765 ghc-package-cache-file
1766 ca-certificate-bundle
1767 emacs-subdirs
1768 glib-schemas
1769 gtk-icon-themes
1770 gtk-im-modules
1771 texlive-configuration
1772 xdg-desktop-database
1773 xdg-mime-database))
1774
1775 (define* (profile-derivation manifest
1776 #:key
1777 (name "profile")
1778 (hooks %default-profile-hooks)
1779 (locales? #t)
1780 (allow-collisions? #f)
1781 (relative-symlinks? #f)
1782 system target)
1783 "Return a derivation that builds a profile (aka. 'user environment') with
1784 the given MANIFEST. The profile includes additional derivations returned by
1785 the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc.
1786 Unless ALLOW-COLLISIONS? is true, a '&profile-collision-error' is raised if
1787 entries in MANIFEST collide (for instance if there are two same-name packages
1788 with a different version number.)
1789
1790 When LOCALES? is true, the build is performed under a UTF-8 locale; this adds
1791 a dependency on the 'glibc-utf8-locales' package.
1792
1793 When RELATIVE-SYMLINKS? is true, use relative file names for symlink targets.
1794 This is one of the things to do for the result to be relocatable.
1795
1796 When TARGET is true, it must be a GNU triplet, and the packages in MANIFEST
1797 are cross-built for TARGET."
1798 (mlet* %store-monad ((system (if system
1799 (return system)
1800 (current-system)))
1801 (target (if target
1802 (return target)
1803 (current-target-system)))
1804 (ok? (if allow-collisions?
1805 (return #t)
1806 (check-for-collisions manifest system
1807 #:target target)))
1808 (extras (if (null? (manifest-entries manifest))
1809 (return '())
1810 (mapm/accumulate-builds (lambda (hook)
1811 (hook manifest))
1812 hooks))))
1813 (define inputs
1814 (append (filter-map (lambda (drv)
1815 (and (derivation? drv)
1816 (gexp-input drv)))
1817 extras)
1818 (manifest-inputs manifest)))
1819
1820 (define glibc-utf8-locales ;lazy reference
1821 (module-ref (resolve-interface '(gnu packages base))
1822 'glibc-utf8-locales))
1823
1824 (define set-utf8-locale
1825 ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so
1826 ;; install a UTF-8 locale.
1827 #~(begin
1828 (setenv "LOCPATH"
1829 #$(file-append glibc-utf8-locales "/lib/locale/"
1830 (version-major+minor
1831 (package-version glibc-utf8-locales))))
1832 (setlocale LC_ALL "en_US.utf8")))
1833
1834 (define builder
1835 (with-imported-modules '((guix build profiles)
1836 (guix build union)
1837 (guix build utils)
1838 (guix search-paths)
1839 (guix records))
1840 #~(begin
1841 (use-modules (guix build profiles)
1842 (guix search-paths)
1843 (srfi srfi-1))
1844
1845 (let ((line (cond-expand (guile-2.2 'line)
1846 (else _IOLBF)))) ;Guile 2.0
1847 (setvbuf (current-output-port) line)
1848 (setvbuf (current-error-port) line))
1849
1850 #+(if locales? set-utf8-locale #t)
1851
1852 (define search-paths
1853 ;; Search paths of MANIFEST's packages, converted back to their
1854 ;; record form.
1855 (map sexp->search-path-specification
1856 (delete-duplicates
1857 '#$(map search-path-specification->sexp
1858 (manifest-search-paths manifest)))))
1859
1860 (build-profile #$output '#$inputs
1861 #:symlink #$(if relative-symlinks?
1862 #~symlink-relative
1863 #~symlink)
1864 #:manifest '#$(manifest->gexp manifest)
1865 #:search-paths search-paths))))
1866
1867 (gexp->derivation name builder
1868 #:system system
1869 #:target target
1870
1871 ;; Don't complain about _IO* on Guile 2.2.
1872 #:env-vars '(("GUILE_WARN_DEPRECATED" . "no"))
1873
1874 ;; Not worth offloading.
1875 #:local-build? #t
1876
1877 ;; Disable substitution because it would trigger a
1878 ;; connection to the substitute server, which is likely
1879 ;; to have no substitute to offer.
1880 #:substitutable? #f
1881
1882 #:properties `((type . profile)
1883 (profile
1884 (count
1885 . ,(length
1886 (manifest-entries manifest))))))))
1887
1888 ;; Declarative profile.
1889 (define-record-type* <profile> profile make-profile
1890 profile?
1891 (name profile-name (default "profile")) ;string
1892 (content profile-content) ;<manifest>
1893 (hooks profile-hooks ;list of procedures
1894 (default %default-profile-hooks))
1895 (locales? profile-locales? ;Boolean
1896 (default #t))
1897 (allow-collisions? profile-allow-collisions? ;Boolean
1898 (default #f))
1899 (relative-symlinks? profile-relative-symlinks? ;Boolean
1900 (default #f)))
1901
1902 (define-gexp-compiler (profile-compiler (profile <profile>) system target)
1903 "Compile PROFILE to a derivation."
1904 (match profile
1905 (($ <profile> name manifest hooks
1906 locales? allow-collisions? relative-symlinks?)
1907 (profile-derivation manifest
1908 #:name name
1909 #:hooks hooks
1910 #:locales? locales?
1911 #:allow-collisions? allow-collisions?
1912 #:relative-symlinks? relative-symlinks?
1913 #:system system #:target target))))
1914
1915 (define* (profile-search-paths profile
1916 #:optional (manifest (profile-manifest profile))
1917 #:key (getenv (const #f)))
1918 "Read the manifest of PROFILE and evaluate the values of search path
1919 environment variables required by PROFILE; return a list of
1920 specification/value pairs. If MANIFEST is not #f, it is assumed to be the
1921 manifest of PROFILE, which avoids rereading it.
1922
1923 Use GETENV to determine the current settings and report only settings not
1924 already effective."
1925 (evaluate-search-paths (manifest-search-paths manifest)
1926 (list profile) getenv))
1927
1928 (define (profile-regexp profile)
1929 "Return a regular expression that matches PROFILE's name and number."
1930 (make-regexp (string-append "^" (regexp-quote (basename profile))
1931 "-([0-9]+)")))
1932
1933 (define (generation-number profile)
1934 "Return PROFILE's number or 0. An absolute file name must be used."
1935 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
1936 (basename (readlink profile))))
1937 (compose string->number (cut match:substring <> 1)))
1938 0))
1939
1940 (define %profile-generation-rx
1941 ;; Regexp that matches profile generation.
1942 (make-regexp "(.*)-([0-9]+)-link$"))
1943
1944 (define (generation-profile file)
1945 "If FILE is a profile generation GC root such as \"guix-profile-42-link\",
1946 return its corresponding profile---e.g., \"guix-profile\". Otherwise return
1947 #f."
1948 (match (regexp-exec %profile-generation-rx file)
1949 (#f #f)
1950 (m (let ((profile (match:substring m 1)))
1951 (and (file-exists? (string-append profile "/manifest"))
1952 profile)))))
1953
1954 (define (generation-numbers profile)
1955 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
1956 former profiles were found."
1957 (match (scandir (dirname profile)
1958 (cute regexp-exec (profile-regexp profile) <>))
1959 (#f ; no profile directory
1960 '(0))
1961 (() ; no profiles
1962 '(0))
1963 ((profiles ...) ; former profiles around
1964 (sort (map (compose string->number
1965 (cut match:substring <> 1)
1966 (cute regexp-exec (profile-regexp profile) <>))
1967 profiles)
1968 <))))
1969
1970 (define (profile-generations profile)
1971 "Return a list of PROFILE's generations."
1972 (let ((generations (generation-numbers profile)))
1973 (if (equal? generations '(0))
1974 '()
1975 generations)))
1976
1977 (define (relative-generation-spec->number profile spec)
1978 "Return PROFILE's generation specified by SPEC, which is a string. The SPEC
1979 may be a N, -N, or +N, where N is a number. If the spec is N, then the number
1980 returned is N. If it is -N, then the number returned is the profile's current
1981 generation number minus N. If it is +N, then the number returned is the
1982 profile's current generation number plus N. Return #f if there is no such
1983 generation."
1984 (let ((number (string->number spec)))
1985 (and number
1986 (case (string-ref spec 0)
1987 ((#\+ #\-)
1988 (relative-generation profile number))
1989 (else (if (memv number (profile-generations profile))
1990 number
1991 #f))))))
1992
1993
1994 (define* (relative-generation profile shift #:optional
1995 (current (generation-number profile)))
1996 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
1997 SHIFT is a positive or negative number.
1998 Return #f if there is no such generation."
1999 (let* ((abs-shift (abs shift))
2000 (numbers (profile-generations profile))
2001 (from-current (memq current
2002 (if (negative? shift)
2003 (reverse numbers)
2004 numbers))))
2005 (and from-current
2006 (< abs-shift (length from-current))
2007 (list-ref from-current abs-shift))))
2008
2009 (define* (previous-generation-number profile #:optional
2010 (number (generation-number profile)))
2011 "Return the number of the generation before generation NUMBER of
2012 PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
2013 case when generations have been deleted (there are \"holes\")."
2014 (or (relative-generation profile -1 number)
2015 0))
2016
2017 (define (generation-file-name profile generation)
2018 "Return the file name for PROFILE's GENERATION."
2019 (format #f "~a-~a-link" profile generation))
2020
2021 (define (generation-time profile number)
2022 "Return the creation time of a generation in the UTC format."
2023 (make-time time-utc 0
2024 (stat:ctime (stat (generation-file-name profile number)))))
2025
2026 (define (link-to-empty-profile store generation)
2027 "Link GENERATION, a string, to the empty profile. An error is raised if
2028 that fails."
2029 (let* ((drv (run-with-store store
2030 (profile-derivation (manifest '())
2031 #:locales? #f)))
2032 (prof (derivation->output-path drv "out")))
2033 (build-derivations store (list drv))
2034 (switch-symlinks generation prof)))
2035
2036 (define (switch-to-generation profile number)
2037 "Atomically switch PROFILE to the generation NUMBER. Return the number of
2038 the generation that was current before switching."
2039 (let ((current (generation-number profile))
2040 (generation (generation-file-name profile number)))
2041 (cond ((not (file-exists? profile))
2042 (raise (condition (&profile-not-found-error
2043 (profile profile)))))
2044 ((not (file-exists? generation))
2045 (raise (condition (&missing-generation-error
2046 (profile profile)
2047 (generation number)))))
2048 (else
2049 (switch-symlinks profile (basename generation))
2050 current))))
2051
2052 (define (switch-to-previous-generation profile)
2053 "Atomically switch PROFILE to the previous generation. Return the former
2054 generation number and the current one."
2055 (let ((previous (previous-generation-number profile)))
2056 (values (switch-to-generation profile previous)
2057 previous)))
2058
2059 (define (roll-back store profile)
2060 "Roll back to the previous generation of PROFILE. Return the number of the
2061 generation that was current before switching and the new generation number."
2062 (let* ((number (generation-number profile))
2063 (previous-number (previous-generation-number profile number))
2064 (previous-generation (generation-file-name profile previous-number)))
2065 (cond ((not (file-exists? profile)) ;invalid profile
2066 (raise (condition (&profile-not-found-error
2067 (profile profile)))))
2068 ((zero? number) ;empty profile
2069 (values number number))
2070 ((or (zero? previous-number) ;going to emptiness
2071 (not (file-exists? previous-generation)))
2072 (link-to-empty-profile store previous-generation)
2073 (switch-to-previous-generation profile))
2074 (else ;anything else
2075 (switch-to-previous-generation profile)))))
2076
2077 (define (delete-generation store profile number)
2078 "Delete generation with NUMBER from PROFILE. Return the file name of the
2079 generation that has been deleted, or #f if nothing was done (for instance
2080 because the NUMBER is zero.)"
2081 (define (delete-and-return)
2082 (let ((generation (generation-file-name profile number)))
2083 (delete-file generation)
2084 generation))
2085
2086 (let* ((current-number (generation-number profile))
2087 (previous-number (previous-generation-number profile number))
2088 (previous-generation (generation-file-name profile previous-number)))
2089 (cond ((zero? number) #f) ;do not delete generation 0
2090 ((and (= number current-number)
2091 (not (file-exists? previous-generation)))
2092 (link-to-empty-profile store previous-generation)
2093 (switch-to-previous-generation profile)
2094 (delete-and-return))
2095 ((= number current-number)
2096 (roll-back store profile)
2097 (delete-and-return))
2098 (else
2099 (delete-and-return)))))
2100
2101 (define %user-profile-directory
2102 (and=> (getenv "HOME")
2103 (cut string-append <> "/.guix-profile")))
2104
2105 (define %profile-directory
2106 (string-append %state-directory "/profiles/"
2107 (or (and=> (or (getenv "USER")
2108 (getenv "LOGNAME")
2109 (false-if-exception
2110 (passwd:name (getpwuid (getuid)))))
2111 (cut string-append "per-user/" <>))
2112 "default")))
2113
2114 (define %current-profile
2115 ;; Call it `guix-profile', not `profile', to allow Guix profiles to
2116 ;; coexist with Nix profiles.
2117 (string-append %profile-directory "/guix-profile"))
2118
2119 (define (ensure-profile-directory)
2120 "Attempt to create /…/profiles/per-user/$USER if needed. Nowadays this is
2121 taken care of by the daemon."
2122 (let ((s (stat %profile-directory #f)))
2123 (unless (and s (eq? 'directory (stat:type s)))
2124 (catch 'system-error
2125 (lambda ()
2126 (mkdir-p %profile-directory))
2127 (lambda args
2128 ;; Often, we cannot create %PROFILE-DIRECTORY because its
2129 ;; parent directory is root-owned and we're running
2130 ;; unprivileged.
2131 (raise (condition
2132 (&message
2133 (message
2134 (format #f
2135 (G_ "while creating directory `~a': ~a")
2136 %profile-directory
2137 (strerror (system-error-errno args)))))
2138 (&fix-hint
2139 (hint
2140 (format #f (G_ "Please create the @file{~a} directory, \
2141 with you as the owner.")
2142 %profile-directory))))))))
2143
2144 ;; Bail out if it's not owned by the user.
2145 (unless (or (not s) (= (stat:uid s) (getuid)))
2146 (raise (condition
2147 (&message
2148 (message
2149 (format #f (G_ "directory `~a' is not owned by you")
2150 %profile-directory)))
2151 (&fix-hint
2152 (hint
2153 (format #f (G_ "Please change the owner of @file{~a} \
2154 to user ~s.")
2155 %profile-directory (or (getenv "USER")
2156 (getenv "LOGNAME")
2157 (getuid))))))))))
2158
2159 (define (canonicalize-profile profile)
2160 "If PROFILE points to a profile in %PROFILE-DIRECTORY, return that.
2161 Otherwise return PROFILE unchanged. The goal is to treat '-p ~/.guix-profile'
2162 as if '-p' was omitted." ; see <http://bugs.gnu.org/17939>
2163 ;; Trim trailing slashes so 'readlink' can do its job.
2164 (let ((profile (string-trim-right profile #\/)))
2165 (catch 'system-error
2166 (lambda ()
2167 (let ((target (readlink profile)))
2168 (if (string=? (dirname target) %profile-directory)
2169 target
2170 profile)))
2171 (const profile))))
2172
2173 (define %known-shorthand-profiles
2174 ;; Known shorthand forms for profiles that the user manipulates.
2175 (list (string-append (config-directory #:ensure? #f) "/current")
2176 %user-profile-directory))
2177
2178 (define (user-friendly-profile profile)
2179 "Return either ~/.guix-profile or ~/.config/guix/current if that's what
2180 PROFILE refers to, directly or indirectly, or PROFILE."
2181 (or (find (lambda (shorthand)
2182 (and shorthand
2183 (let ((target (false-if-exception
2184 (readlink shorthand))))
2185 (and target (string=? target profile)))))
2186 %known-shorthand-profiles)
2187 profile))
2188
2189 ;;; profiles.scm ends here