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