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