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