guix: ci: Fix evaluation complete? field.
[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>
a6b8794c 7;;; Copyright © 2016, 2018, 2019, 2021 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
68228d80
LP
1118(define (emacs-subdirs manifest)
1119 (define build
1120 (with-imported-modules (source-module-closure
1121 '((guix build profiles)
1122 (guix build utils)))
1123 #~(begin
1124 (use-modules (guix build utils)
1125 (guix build profiles)
1126 (ice-9 ftw) ; scandir
1127 (srfi srfi-1) ; append-map
1128 (srfi srfi-26))
1129
1130 (let ((destdir (string-append #$output "/share/emacs/site-lisp"))
1131 (subdirs
1132 (append-map
1133 (lambda (dir)
1134 (filter
1135 file-is-directory?
1136 (map (cute string-append dir "/" <>)
1137 (scandir dir (negate (cute member <> '("." "..")))))))
1138 (filter file-exists?
1139 (map (cute string-append <> "/share/emacs/site-lisp")
1140 '#$(manifest-inputs manifest))))))
1141 (mkdir-p destdir)
1142 (with-directory-excursion destdir
1143 (call-with-output-file "subdirs.el"
1144 (lambda (port)
1145 (write
1146 `(normal-top-level-add-to-load-path
1147 (list ,@subdirs))
1148 port)
1149 (newline port)
1150 #t)))))))
1151 (gexp->derivation "emacs-subdirs" build
1152 #:local-build? #t
1153 #:substitutable? #f
1154 #:properties
1155 `((type . profile-hook)
1156 (hook . emacs-subdirs))))
1157
de136f3e
DM
1158(define (glib-schemas manifest)
1159 "Return a derivation that unions all schemas from manifest entries and
1160creates the Glib 'gschemas.compiled' file."
1161 (define glib ; lazy reference
1162 (module-ref (resolve-interface '(gnu packages glib)) 'glib))
1163
1164 (mlet %store-monad ((%glib (manifest-lookup-package manifest "glib"))
1165 ;; XXX: Can't use glib-compile-schemas corresponding
1166 ;; to the glib referenced by 'manifest'. Because
1167 ;; '%glib' can be either a package or store path, and
1168 ;; there's no way to get the "bin" output for the later.
1169 (glib-compile-schemas
1170 -> #~(string-append #+glib:bin
1171 "/bin/glib-compile-schemas")))
1172
1173 (define build
1174 (with-imported-modules '((guix build utils)
1175 (guix build union)
1176 (guix build profiles)
1177 (guix search-paths)
1178 (guix records))
1179 #~(begin
1180 (use-modules (guix build utils)
1181 (guix build union)
1182 (guix build profiles)
1183 (srfi srfi-26))
1184
1185 (let* ((destdir (string-append #$output "/share/glib-2.0/schemas"))
1186 (schemadirs (filter file-exists?
1187 (map (cut string-append <> "/share/glib-2.0/schemas")
1188 '#$(manifest-inputs manifest)))))
1189
1190 ;; Union all the schemas.
1191 (mkdir-p (string-append #$output "/share/glib-2.0"))
1192 (union-build destdir schemadirs
1193 #:log-port (%make-void-port "w"))
1194
1195 (let ((dir destdir))
1196 (when (file-is-directory? dir)
1197 (ensure-writable-directory dir)
1198 (invoke #+glib-compile-schemas
1199 (string-append "--targetdir=" dir)
1200 dir)))))))
1201
1202 ;; Don't run the hook when there's nothing to do.
1203 (if %glib
1204 (gexp->derivation "glib-schemas" build
1205 #:local-build? #t
80eebee9
RW
1206 #:substitutable? #f
1207 #:properties
1208 `((type . profile-hook)
1209 (hook . glib-schemas)))
de136f3e
DM
1210 (return #f))))
1211
b04af0ec
SB
1212(define (gtk-icon-themes manifest)
1213 "Return a derivation that unions all icon themes from manifest entries and
1214creates the GTK+ 'icon-theme.cache' file for each theme."
d1fb4af6
SB
1215 (define gtk+ ; lazy reference
1216 (module-ref (resolve-interface '(gnu packages gtk)) 'gtk+))
1217
1218 (mlet %store-monad ((%gtk+ (manifest-lookup-package manifest "gtk+"))
1219 ;; XXX: Can't use gtk-update-icon-cache corresponding
1220 ;; to the gtk+ referenced by 'manifest'. Because
1221 ;; '%gtk+' can be either a package or store path, and
1222 ;; there's no way to get the "bin" output for the later.
1223 (gtk-update-icon-cache
1224 -> #~(string-append #+gtk+:bin
1225 "/bin/gtk-update-icon-cache")))
1226
b04af0ec 1227 (define build
99b231de
LC
1228 (with-imported-modules '((guix build utils)
1229 (guix build union)
1230 (guix build profiles)
1231 (guix search-paths)
1232 (guix records))
1233 #~(begin
1234 (use-modules (guix build utils)
1235 (guix build union)
1236 (guix build profiles)
1237 (srfi srfi-26)
1238 (ice-9 ftw))
1239
1240 (let* ((destdir (string-append #$output "/share/icons"))
1241 (icondirs (filter file-exists?
1242 (map (cut string-append <> "/share/icons")
d1fb4af6 1243 '#$(manifest-inputs manifest)))))
99b231de
LC
1244
1245 ;; Union all the icons.
1246 (mkdir-p (string-append #$output "/share"))
1247 (union-build destdir icondirs
1248 #:log-port (%make-void-port "w"))
1249
1250 ;; Update the 'icon-theme.cache' file for each icon theme.
1251 (for-each
1252 (lambda (theme)
1253 (let ((dir (string-append destdir "/" theme)))
1254 ;; Occasionally DESTDIR contains plain files, such as
1255 ;; "abiword_48.png". Ignore these.
1256 (when (file-is-directory? dir)
1257 (ensure-writable-directory dir)
d1fb4af6 1258 (system* #+gtk-update-icon-cache "-t" dir "--quiet"))))
99b231de 1259 (scandir destdir (negate (cut member <> '("." "..")))))))))
b04af0ec
SB
1260
1261 ;; Don't run the hook when there's nothing to do.
d1fb4af6 1262 (if %gtk+
b04af0ec 1263 (gexp->derivation "gtk-icon-themes" build
a7a4fd9a 1264 #:local-build? #t
80eebee9
RW
1265 #:substitutable? #f
1266 #:properties
1267 `((type . profile-hook)
1268 (hook . gtk-icon-themes)))
b04af0ec
SB
1269 (return #f))))
1270
7ddc1780
RW
1271(define (gtk-im-modules manifest)
1272 "Return a derivation that builds the cache files for input method modules
1273for both major versions of GTK+."
1274
1275 (mlet %store-monad ((gtk+ (manifest-lookup-package manifest "gtk+" "3"))
1276 (gtk+-2 (manifest-lookup-package manifest "gtk+" "2")))
1277
06d7d119 1278 (define (build gtk gtk-version query)
7ddc1780
RW
1279 (let ((major (string-take gtk-version 1)))
1280 (with-imported-modules '((guix build utils)
1281 (guix build union)
1282 (guix build profiles)
1283 (guix search-paths)
1284 (guix records))
1285 #~(begin
1286 (use-modules (guix build utils)
1287 (guix build union)
1288 (guix build profiles)
1289 (ice-9 popen)
1290 (srfi srfi-1)
1291 (srfi srfi-26))
1292
1293 (let* ((prefix (string-append "/lib/gtk-" #$major ".0/"
1294 #$gtk-version))
7ddc1780
RW
1295 (destdir (string-append #$output prefix))
1296 (moddirs (cons (string-append #$gtk prefix "/immodules")
1297 (filter file-exists?
1298 (map (cut string-append <> prefix "/immodules")
1299 '#$(manifest-inputs manifest)))))
1300 (modules (append-map (cut find-files <> "\\.so$")
1301 moddirs)))
1302
1303 ;; Generate a new immodules cache file.
1304 (mkdir-p (string-append #$output prefix))
06d7d119 1305 (let ((pipe (apply open-pipe* OPEN_READ #$query modules))
7ddc1780
RW
1306 (outfile (string-append #$output prefix
1307 "/immodules-gtk" #$major ".cache")))
1308 (dynamic-wind
1309 (const #t)
1310 (lambda ()
1311 (call-with-output-file outfile
1312 (lambda (out)
1313 (while (not (eof-object? (peek-char pipe)))
1314 (write-char (read-char pipe) out))))
1315 #t)
1316 (lambda ()
1317 (close-pipe pipe)))))))))
1318
1319 ;; Don't run the hook when there's nothing to do.
06d7d119
YH
1320 (let* ((pkg-gtk+ (module-ref ; lazy reference
1321 (resolve-interface '(gnu packages gtk)) 'gtk+))
e8cbebb4
JL
1322 (pkg-gtk+2 (module-ref ; lazy reference
1323 (resolve-interface '(gnu packages gtk)) 'gtk+-2))
06d7d119
YH
1324 (gexp #~(begin
1325 #$(if gtk+
1326 (build
1327 gtk+ "3.0.0"
1328 ;; Use 'gtk-query-immodules-3.0' from the 'bin'
1329 ;; output of latest gtk+ package.
1330 #~(string-append
1331 #$pkg-gtk+:bin "/bin/gtk-query-immodules-3.0"))
1332 #t)
1333 #$(if gtk+-2
1334 (build
1335 gtk+-2 "2.10.0"
1336 #~(string-append
28292d8c 1337 #$pkg-gtk+2:bin "/bin/gtk-query-immodules-2.0"))
06d7d119 1338 #t))))
7ddc1780
RW
1339 (if (or gtk+ gtk+-2)
1340 (gexp->derivation "gtk-im-modules" gexp
1341 #:local-build? #t
80eebee9
RW
1342 #:substitutable? #f
1343 #:properties
1344 `((type . profile-hook)
1345 (hook . gtk-im-modules)))
7ddc1780
RW
1346 (return #f)))))
1347
5c79f238
DM
1348(define (linux-module-database manifest)
1349 "Return a derivation that unites all the kernel modules of the manifest
1350and creates the dependency graph of all these kernel modules.
1351
1352This is meant to be used as a profile hook."
fea072d7 1353 (define kmod ; lazy reference
5c79f238 1354 (module-ref (resolve-interface '(gnu packages linux)) 'kmod))
755f365b
MO
1355
1356 (define guile-zlib
1357 (module-ref (resolve-interface '(gnu packages guile)) 'guile-zlib))
1358
5c79f238 1359 (define build
fea072d7
LC
1360 (with-imported-modules (source-module-closure
1361 '((guix build utils)
5c79f238 1362 (gnu build linux-modules)))
755f365b
MO
1363 (with-extensions (list guile-zlib)
1364 #~(begin
1365 (use-modules (ice-9 ftw)
1366 (ice-9 match)
1367 (srfi srfi-1) ; append-map
1368 (gnu build linux-modules))
1369
1370 (let* ((inputs '#$(manifest-inputs manifest))
1371 (module-directories
1372 (map (lambda (directory)
1373 (string-append directory "/lib/modules"))
1374 inputs))
1375 (directory-entries
1376 (lambda (directory)
1377 (or (scandir directory
1378 (lambda (basename)
1379 (not (string-prefix? "." basename))))
1380 '())))
1381 ;; Note: Should usually result in one entry.
1382 (versions (delete-duplicates
1383 (append-map directory-entries
1384 module-directories))))
1385 (match versions
1386 ((version)
1387 (let ((old-path (getenv "PATH")))
1388 (setenv "PATH" #+(file-append kmod "/bin"))
1389 (make-linux-module-directory inputs version #$output)
1390 (setenv "PATH" old-path)))
1391 (()
1392 ;; Nothing here, maybe because this is a kernel with
1393 ;; CONFIG_MODULES=n.
1394 (mkdir #$output))
1395 (_ (error "Specified Linux kernel and Linux kernel modules
1396are not all of the same version"))))))))
5c79f238
DM
1397 (gexp->derivation "linux-module-database" build
1398 #:local-build? #t
1399 #:substitutable? #f
1400 #:properties
1401 `((type . profile-hook)
1402 (hook . linux-module-database))))
1403
842cb820
SB
1404(define (xdg-desktop-database manifest)
1405 "Return a derivation that builds the @file{mimeinfo.cache} database from
1406desktop files. It's used to query what applications can handle a given
1407MIME type."
85cfbd46
SB
1408 (define desktop-file-utils ; lazy reference
1409 (module-ref (resolve-interface '(gnu packages freedesktop))
1410 'desktop-file-utils))
1411
1412 (mlet %store-monad ((glib
d72d7833 1413 (manifest-lookup-package
85cfbd46 1414 manifest "glib")))
d72d7833 1415 (define build
99b231de
LC
1416 (with-imported-modules '((guix build utils)
1417 (guix build union))
1418 #~(begin
1419 (use-modules (srfi srfi-26)
1420 (guix build utils)
1421 (guix build union))
1422 (let* ((destdir (string-append #$output "/share/applications"))
1423 (appdirs (filter file-exists?
1424 (map (cut string-append <>
1425 "/share/applications")
1426 '#$(manifest-inputs manifest))))
1427 (update-desktop-database (string-append
1428 #+desktop-file-utils
1429 "/bin/update-desktop-database")))
1430 (mkdir-p (string-append #$output "/share"))
1431 (union-build destdir appdirs
1432 #:log-port (%make-void-port "w"))
1433 (exit (zero? (system* update-desktop-database destdir)))))))
842cb820 1434
85cfbd46
SB
1435 ;; Don't run the hook when 'glib' is not referenced.
1436 (if glib
d72d7833 1437 (gexp->derivation "xdg-desktop-database" build
d72d7833 1438 #:local-build? #t
80eebee9
RW
1439 #:substitutable? #f
1440 #:properties
1441 `((type . profile-hook)
1442 (hook . xdg-desktop-database)))
d72d7833 1443 (return #f))))
842cb820 1444
6c06b1fd
SB
1445(define (xdg-mime-database manifest)
1446 "Return a derivation that builds the @file{mime.cache} database from manifest
1447entries. It's used to query the MIME type of a given file."
801d316b
SB
1448 (define shared-mime-info ; lazy reference
1449 (module-ref (resolve-interface '(gnu packages gnome)) 'shared-mime-info))
1450
76ea70bd 1451 (mlet %store-monad ((glib (manifest-lookup-package manifest "glib")))
d72d7833 1452 (define build
99b231de
LC
1453 (with-imported-modules '((guix build utils)
1454 (guix build union))
1455 #~(begin
76ea70bd
LC
1456 (use-modules (guix build utils)
1457 (guix build union)
1458 (srfi srfi-26)
1459 (ice-9 match))
1460
99b231de
LC
1461 (let* ((datadir (string-append #$output "/share"))
1462 (destdir (string-append datadir "/mime"))
1463 (pkgdirs (filter file-exists?
1464 (map (cut string-append <>
1465 "/share/mime/packages")
801d316b 1466 (cons #+shared-mime-info
76ea70bd
LC
1467 '#$(manifest-inputs manifest))))))
1468
1469 (match pkgdirs
1470 ((shared-mime-info)
1471 ;; PKGDIRS contains nothing but 'shared-mime-info', which
1472 ;; already contains its database, so nothing to do.
1473 (mkdir-p datadir)
1474 (symlink #$(file-append shared-mime-info "/share/mime")
1475 destdir))
1476 (_
1477 ;; PKGDIRS contains additional packages providing
1478 ;; 'share/mime/packages' (very few packages do so) so rebuild
1479 ;; the database. TODO: Find a way to avoid reprocessing
1480 ;; 'shared-mime-info', which is the most expensive one.
1481 (mkdir-p destdir)
1482 (union-build (string-append destdir "/packages") pkgdirs
1483 #:log-port (%make-void-port "w"))
1484 (setenv "XDG_DATA_HOME" datadir)
1485 (invoke #+(file-append shared-mime-info
1486 "/bin/update-mime-database")
1487 destdir)))))))
d72d7833 1488
801d316b
SB
1489 ;; Don't run the hook when there are no GLib based applications.
1490 (if glib
d72d7833 1491 (gexp->derivation "xdg-mime-database" build
d72d7833 1492 #:local-build? #t
80eebee9
RW
1493 #:substitutable? #f
1494 #:properties
1495 `((type . profile-hook)
1496 (hook . xdg-mime-database)))
d72d7833 1497 (return #f))))
6c06b1fd 1498
0a5ce0d1
HY
1499;; Several font packages may install font files into same directory, so
1500;; fonts.dir and fonts.scale file should be generated here, instead of in
1501;; packages.
9eb5a449
AK
1502(define (fonts-dir-file manifest)
1503 "Return a derivation that builds the @file{fonts.dir} and @file{fonts.scale}
0a5ce0d1 1504files for the fonts of the @var{manifest} entries."
9eb5a449
AK
1505 (define mkfontscale
1506 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontscale))
1507
1508 (define mkfontdir
1509 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontdir))
1510
1511 (define build
1512 #~(begin
1513 (use-modules (srfi srfi-26)
1514 (guix build utils)
1515 (guix build union))
0a5ce0d1
HY
1516 (let ((fonts-dirs (filter file-exists?
1517 (map (cut string-append <>
1518 "/share/fonts")
1519 '#$(manifest-inputs manifest)))))
9eb5a449 1520 (mkdir #$output)
0a5ce0d1 1521 (if (null? fonts-dirs)
9eb5a449 1522 (exit #t)
0a5ce0d1
HY
1523 (let* ((share-dir (string-append #$output "/share"))
1524 (fonts-dir (string-append share-dir "/fonts"))
9eb5a449
AK
1525 (mkfontscale (string-append #+mkfontscale
1526 "/bin/mkfontscale"))
1527 (mkfontdir (string-append #+mkfontdir
0a5ce0d1
HY
1528 "/bin/mkfontdir"))
1529 (empty-file? (lambda (filename)
1530 (call-with-ascii-input-file filename
1531 (lambda (p)
1532 (eqv? #\0 (read-char p))))))
1533 (fonts-dir-file "fonts.dir")
1534 (fonts-scale-file "fonts.scale"))
1535 (mkdir-p share-dir)
1536 ;; Create all sub-directories, because we may create fonts.dir
1537 ;; and fonts.scale files in the sub-directories.
1538 (union-build fonts-dir fonts-dirs
1539 #:log-port (%make-void-port "w")
1540 #:create-all-directories? #t)
1541 (let ((directories (find-files fonts-dir
1542 (lambda (file stat)
1543 (eq? 'directory (stat:type stat)))
1544 #:directories? #t)))
1545 (for-each (lambda (dir)
1546 (with-directory-excursion dir
1547 (when (file-exists? fonts-scale-file)
1548 (delete-file fonts-scale-file))
1549 (when (file-exists? fonts-dir-file)
1550 (delete-file fonts-dir-file))
1551 (unless (and (zero? (system* mkfontscale))
1552 (zero? (system* mkfontdir)))
1553 (exit #f))
32b7506c
RW
1554 (when (and (file-exists? fonts-scale-file)
1555 (empty-file? fonts-scale-file))
0a5ce0d1 1556 (delete-file fonts-scale-file))
32b7506c
RW
1557 (when (and (file-exists? fonts-dir-file)
1558 (empty-file? fonts-dir-file))
0a5ce0d1
HY
1559 (delete-file fonts-dir-file))))
1560 directories)))))))
9eb5a449
AK
1561
1562 (gexp->derivation "fonts-dir" build
1563 #:modules '((guix build utils)
0a5ce0d1
HY
1564 (guix build union)
1565 (srfi srfi-26))
9eb5a449 1566 #:local-build? #t
80eebee9
RW
1567 #:substitutable? #f
1568 #:properties
1569 `((type . profile-hook)
1570 (hook . fonts-dir))))
9eb5a449 1571
a0b87ef8
MC
1572(define (manual-database manifest)
1573 "Return a derivation that builds the manual page database (\"mandb\") for
1574the entries in MANIFEST."
b8396f96
LC
1575 (define gdbm-ffi
1576 (module-ref (resolve-interface '(gnu packages guile))
1577 'guile-gdbm-ffi))
1578
4c0c65ac
MO
1579 (define guile-zlib
1580 (module-ref (resolve-interface '(gnu packages guile)) 'guile-zlib))
b8396f96
LC
1581
1582 (define modules
4c0c65ac
MO
1583 (delete '(guix config)
1584 (source-module-closure `((guix build utils)
1585 (guix man-db)))))
a0b87ef8
MC
1586
1587 (define build
b8396f96 1588 (with-imported-modules modules
4c0c65ac
MO
1589 (with-extensions (list gdbm-ffi ;for (guix man-db)
1590 guile-zlib)
331ac4cc
LC
1591 #~(begin
1592 (use-modules (guix man-db)
1593 (guix build utils)
ef4b5f2f 1594 (ice-9 threads)
331ac4cc
LC
1595 (srfi srfi-1)
1596 (srfi srfi-19))
1597
ef4b5f2f
AB
1598 (define (print-string msg)
1599 (display msg)
1600 (force-output))
1601
1602 (define-syntax-rule (print fmt args ...)
1603 ;; Build up the string and display it at once.
1604 (print-string (format #f fmt args ...)))
1605
1606 (define (compute-entry directory count total)
1607 (print "\r[~3d/~3d] building list of man-db entries..."
1608 count total)
1609 (let ((man (string-append directory "/share/man")))
1610 (if (directory-exists? man)
1611 (mandb-entries man)
1612 '())))
1613
331ac4cc 1614 (define (compute-entries)
f6fe7da3
LC
1615 ;; This is the most expensive part (I/O and CPU, due to
1616 ;; decompression), so report progress as we traverse INPUTS.
ef4b5f2f
AB
1617 ;; Cap at 4 threads because we don't see any speedup beyond that
1618 ;; on an SSD laptop.
1619 (let* ((inputs '#$(manifest-inputs manifest))
1620 (total (length inputs))
1621 (threads (min (parallel-job-count) 4)))
1622 (concatenate
1623 (n-par-map threads compute-entry inputs
1624 (iota total 1)
1625 (make-list total total)))))
331ac4cc
LC
1626
1627 (define man-directory
1628 (string-append #$output "/share/man"))
1629
1630 (mkdir-p man-directory)
1631
1632 (format #t "Creating manual page database...~%")
1633 (force-output)
1634 (let* ((start (current-time))
1635 (entries (compute-entries))
1636 (_ (write-mandb-database (string-append man-directory
1637 "/index.db")
1638 entries))
1639 (duration (time-difference (current-time) start)))
f6fe7da3 1640 (newline)
331ac4cc
LC
1641 (format #t "~a entries processed in ~,1f s~%"
1642 (length entries)
1643 (+ (time-second duration)
1644 (* (time-nanosecond duration) (expt 10 -9))))
1645 (force-output))))))
a0b87ef8
MC
1646
1647 (gexp->derivation "manual-database" build
b8396f96
LC
1648
1649 ;; Work around GDBM 1.13 issue whereby uninitialized bytes
1650 ;; get written to disk:
1651 ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29654#23>.
1652 #:env-vars `(("MALLOC_PERTURB_" . "1"))
1653
69de9839 1654 #:substitutable? #f
80eebee9
RW
1655 #:local-build? #t
1656 #:properties
1657 `((type . profile-hook)
1658 (hook . manual-database))))
a0b87ef8 1659
743497b5
RW
1660(define (texlive-configuration manifest)
1661 "Return a derivation that builds a TeXlive configuration for the entries in
1662MANIFEST."
1663 (define entry->texlive-input
1664 (match-lambda
1665 (($ <manifest-entry> name version output thing deps)
1666 (if (string-prefix? "texlive-" name)
1667 (cons (gexp-input thing output)
1668 (append-map entry->texlive-input deps))
1669 '()))))
a6b8794c
RW
1670 (define texlive-bin
1671 (module-ref (resolve-interface '(gnu packages tex)) 'texlive-bin))
1672 (define coreutils
1673 (module-ref (resolve-interface '(gnu packages base)) 'coreutils))
1674 (define sed
1675 (module-ref (resolve-interface '(gnu packages base)) 'sed))
1676 (define updmap.cfg
1677 (module-ref (resolve-interface '(gnu packages tex))
1678 'texlive-default-updmap.cfg))
743497b5
RW
1679 (define build
1680 (with-imported-modules '((guix build utils)
1681 (guix build union))
1682 #~(begin
1683 (use-modules (guix build utils)
a6b8794c
RW
1684 (guix build union)
1685 (ice-9 popen))
743497b5
RW
1686
1687 ;; Build a modifiable union of all texlive inputs. We do this so
1688 ;; that TeX live can resolve the parent and grandparent directories
1689 ;; correctly. There might be a more elegant way to accomplish this.
1690 (union-build #$output
1691 '#$(append-map entry->texlive-input
1692 (manifest-entries manifest))
1693 #:create-all-directories? #t
1694 #:log-port (%make-void-port "w"))
cf22e99f
CB
1695 (let ((texmf.cnf (string-append
1696 #$output
1697 "/share/texmf-dist/web2c/texmf.cnf")))
1698 (when (file-exists? texmf.cnf)
1699 (substitute* texmf.cnf
1700 (("^TEXMFROOT = .*")
1701 (string-append "TEXMFROOT = " #$output "/share\n"))
1702 (("^TEXMF = .*")
a6b8794c
RW
1703 "TEXMF = $TEXMFROOT/share/texmf-dist\n"))
1704
1705 ;; XXX: This is annoying, but it's necessary because texlive-bin
1706 ;; does not provide wrapped executables.
1707 (setenv "PATH"
1708 (string-append #$(file-append coreutils "/bin")
1709 ":"
1710 #$(file-append sed "/bin")))
1711 (setenv "PERL5LIB" #$(file-append texlive-bin "/share/tlpkg"))
1712 (setenv "TEXMF" (string-append #$output "/share/texmf-dist"))
1713
1714 ;; Remove invalid maps from config file.
56e4d720
RW
1715 (let* ((web2c (string-append #$output "/share/texmf-config/web2c/"))
1716 (maproot (string-append #$output "/share/texmf-dist/fonts/map/"))
1717 (updmap.cfg (string-append web2c "updmap.cfg")))
a6b8794c 1718 (mkdir-p web2c)
56e4d720
RW
1719
1720 ;; Some profiles may already have this file, which prevents us
1721 ;; from copying it. Since we need to generate it from scratch
1722 ;; anyway, we delete it here.
1723 (when (file-exists? updmap.cfg)
1724 (delete-file updmap.cfg))
1725 (copy-file #$updmap.cfg updmap.cfg)
1726 (make-file-writable updmap.cfg)
a6b8794c
RW
1727 (let* ((port (open-pipe* OPEN_WRITE
1728 #$(file-append texlive-bin "/bin/updmap-sys")
1729 "--syncwithtrees"
1730 "--nohash"
1731 "--force"
1732 (string-append "--cnffile=" web2c "updmap.cfg"))))
1733 (display "Y\n" port)
1734 (when (not (zero? (status:exit-val (close-pipe port))))
1735 (error "failed to filter updmap.cfg")))
1736
1737 ;; Generate font maps.
1738 (invoke #$(file-append texlive-bin "/bin/updmap-sys")
1739 (string-append "--cnffile=" web2c "updmap.cfg")
1740 (string-append "--dvipdfmxoutputdir="
1741 maproot "updmap/dvipdfmx/")
1742 (string-append "--dvipsoutputdir="
1743 maproot "updmap/dvips/")
1744 (string-append "--pdftexoutputdir="
1745 maproot "updmap/pdftex/")))))
743497b5
RW
1746 #t)))
1747
bd8e7621
RW
1748 (mlet %store-monad ((texlive-base (manifest-lookup-package manifest "texlive-base")))
1749 (if texlive-base
1750 (gexp->derivation "texlive-configuration" build
1751 #:substitutable? #f
1752 #:local-build? #t
1753 #:properties
1754 `((type . profile-hook)
1755 (hook . texlive-configuration)))
1756 (return #f))))
743497b5 1757
aa46a028
LC
1758(define %default-profile-hooks
1759 ;; This is the list of derivation-returning procedures that are called by
1760 ;; default when making a non-empty profile.
1761 (list info-dir-file
a0b87ef8 1762 manual-database
9eb5a449 1763 fonts-dir-file
aa46a028 1764 ghc-package-cache-file
b04af0ec 1765 ca-certificate-bundle
68228d80 1766 emacs-subdirs
de136f3e 1767 glib-schemas
842cb820 1768 gtk-icon-themes
7ddc1780 1769 gtk-im-modules
743497b5 1770 texlive-configuration
6c06b1fd
SB
1771 xdg-desktop-database
1772 xdg-mime-database))
536c3ee4
MW
1773
1774(define* (profile-derivation manifest
1775 #:key
416f7f4f 1776 (name "profile")
e5f04c2d 1777 (hooks %default-profile-hooks)
a6562c7e 1778 (locales? #t)
afd06f60 1779 (allow-collisions? #f)
e00ade3f 1780 (relative-symlinks? #f)
176febe3 1781 system target)
79ee406d 1782 "Return a derivation that builds a profile (aka. 'user environment') with
aa46a028 1783the given MANIFEST. The profile includes additional derivations returned by
a6562c7e 1784the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc.
afd06f60
LC
1785Unless ALLOW-COLLISIONS? is true, a '&profile-collision-error' is raised if
1786entries in MANIFEST collide (for instance if there are two same-name packages
1787with a different version number.)
a6562c7e
LC
1788
1789When LOCALES? is true, the build is performed under a UTF-8 locale; this adds
176febe3
LC
1790a dependency on the 'glibc-utf8-locales' package.
1791
e00ade3f
LC
1792When RELATIVE-SYMLINKS? is true, use relative file names for symlink targets.
1793This is one of the things to do for the result to be relocatable.
1794
176febe3
LC
1795When TARGET is true, it must be a GNU triplet, and the packages in MANIFEST
1796are cross-built for TARGET."
a654dc4b
LC
1797 (mlet* %store-monad ((system (if system
1798 (return system)
1799 (current-system)))
91be09de
MO
1800 (target (if target
1801 (return target)
1802 (current-target-system)))
afd06f60
LC
1803 (ok? (if allow-collisions?
1804 (return #t)
1805 (check-for-collisions manifest system
1806 #:target target)))
a654dc4b
LC
1807 (extras (if (null? (manifest-entries manifest))
1808 (return '())
25af35fa
LC
1809 (mapm/accumulate-builds (lambda (hook)
1810 (hook manifest))
1811 hooks))))
79ee406d 1812 (define inputs
eeae0b3c
SB
1813 (append (filter-map (lambda (drv)
1814 (and (derivation? drv)
1815 (gexp-input drv)))
07eaecfa 1816 extras)
536c3ee4 1817 (manifest-inputs manifest)))
79ee406d 1818
1af0860e
LC
1819 (define glibc-utf8-locales ;lazy reference
1820 (module-ref (resolve-interface '(gnu packages base))
1821 'glibc-utf8-locales))
1822
a6562c7e
LC
1823 (define set-utf8-locale
1824 ;; Some file names (e.g., in 'nss-certs') are UTF-8 encoded so
1825 ;; install a UTF-8 locale.
1826 #~(begin
1827 (setenv "LOCPATH"
1828 #$(file-append glibc-utf8-locales "/lib/locale/"
c6bc8e22
MB
1829 (version-major+minor
1830 (package-version glibc-utf8-locales))))
a6562c7e
LC
1831 (setlocale LC_ALL "en_US.utf8")))
1832
79ee406d 1833 (define builder
99b231de
LC
1834 (with-imported-modules '((guix build profiles)
1835 (guix build union)
1836 (guix build utils)
1837 (guix search-paths)
1838 (guix records))
1839 #~(begin
1840 (use-modules (guix build profiles)
1841 (guix search-paths)
1842 (srfi srfi-1))
1843
39569dbb
LC
1844 (let ((line (cond-expand (guile-2.2 'line)
1845 (else _IOLBF)))) ;Guile 2.0
1846 (setvbuf (current-output-port) line)
1847 (setvbuf (current-error-port) line))
99b231de 1848
a6562c7e 1849 #+(if locales? set-utf8-locale #t)
1af0860e 1850
99b231de
LC
1851 (define search-paths
1852 ;; Search paths of MANIFEST's packages, converted back to their
1853 ;; record form.
1854 (map sexp->search-path-specification
1855 (delete-duplicates
1856 '#$(map search-path-specification->sexp
f03df3ee 1857 (manifest-search-paths manifest)))))
99b231de
LC
1858
1859 (build-profile #$output '#$inputs
e00ade3f
LC
1860 #:symlink #$(if relative-symlinks?
1861 #~symlink-relative
1862 #~symlink)
99b231de
LC
1863 #:manifest '#$(manifest->gexp manifest)
1864 #:search-paths search-paths))))
79ee406d 1865
416f7f4f 1866 (gexp->derivation name builder
40d71e44 1867 #:system system
176febe3 1868 #:target target
a7a4fd9a 1869
cbb76780
LC
1870 ;; Don't complain about _IO* on Guile 2.2.
1871 #:env-vars '(("GUILE_WARN_DEPRECATED" . "no"))
1872
a7a4fd9a
LC
1873 ;; Not worth offloading.
1874 #:local-build? #t
1875
1876 ;; Disable substitution because it would trigger a
1877 ;; connection to the substitute server, which is likely
1878 ;; to have no substitute to offer.
e7570ec2
LC
1879 #:substitutable? #f
1880
1881 #:properties `((type . profile)
1882 (profile
1883 (count
1884 . ,(length
1885 (manifest-entries manifest))))))))
cc4ecc2d 1886
ef674a24
LC
1887;; Declarative profile.
1888(define-record-type* <profile> profile make-profile
1889 profile?
1890 (name profile-name (default "profile")) ;string
1891 (content profile-content) ;<manifest>
1892 (hooks profile-hooks ;list of procedures
1893 (default %default-profile-hooks))
1894 (locales? profile-locales? ;Boolean
1895 (default #t))
1896 (allow-collisions? profile-allow-collisions? ;Boolean
1897 (default #f))
1898 (relative-symlinks? profile-relative-symlinks? ;Boolean
1899 (default #f)))
1900
1901(define-gexp-compiler (profile-compiler (profile <profile>) system target)
1902 "Compile PROFILE to a derivation."
1903 (match profile
1904 (($ <profile> name manifest hooks
1905 locales? allow-collisions? relative-symlinks?)
1906 (profile-derivation manifest
1907 #:name name
1908 #:hooks hooks
1909 #:locales? locales?
1910 #:allow-collisions? allow-collisions?
1911 #:relative-symlinks? relative-symlinks?
1912 #:system system #:target target))))
1913
78d55b70
LC
1914(define* (profile-search-paths profile
1915 #:optional (manifest (profile-manifest profile))
1916 #:key (getenv (const #f)))
1917 "Read the manifest of PROFILE and evaluate the values of search path
1918environment variables required by PROFILE; return a list of
1919specification/value pairs. If MANIFEST is not #f, it is assumed to be the
1920manifest of PROFILE, which avoids rereading it.
1921
1922Use GETENV to determine the current settings and report only settings not
1923already effective."
1924 (evaluate-search-paths (manifest-search-paths manifest)
1925 (list profile) getenv))
1926
cc4ecc2d
LC
1927(define (profile-regexp profile)
1928 "Return a regular expression that matches PROFILE's name and number."
1929 (make-regexp (string-append "^" (regexp-quote (basename profile))
1930 "-([0-9]+)")))
1931
1932(define (generation-number profile)
1933 "Return PROFILE's number or 0. An absolute file name must be used."
1934 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
1935 (basename (readlink profile))))
1936 (compose string->number (cut match:substring <> 1)))
1937 0))
1938
c872b952
LC
1939(define %profile-generation-rx
1940 ;; Regexp that matches profile generation.
1941 (make-regexp "(.*)-([0-9]+)-link$"))
1942
1943(define (generation-profile file)
1944 "If FILE is a profile generation GC root such as \"guix-profile-42-link\",
1945return its corresponding profile---e.g., \"guix-profile\". Otherwise return
1946#f."
1947 (match (regexp-exec %profile-generation-rx file)
1948 (#f #f)
1949 (m (let ((profile (match:substring m 1)))
1950 (and (file-exists? (string-append profile "/manifest"))
1951 profile)))))
1952
cc4ecc2d
LC
1953(define (generation-numbers profile)
1954 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
1955former profiles were found."
cc4ecc2d
LC
1956 (match (scandir (dirname profile)
1957 (cute regexp-exec (profile-regexp profile) <>))
1958 (#f ; no profile directory
1959 '(0))
1960 (() ; no profiles
1961 '(0))
1962 ((profiles ...) ; former profiles around
1963 (sort (map (compose string->number
1964 (cut match:substring <> 1)
1965 (cute regexp-exec (profile-regexp profile) <>))
1966 profiles)
1967 <))))
1968
f452e8ff
AK
1969(define (profile-generations profile)
1970 "Return a list of PROFILE's generations."
1971 (let ((generations (generation-numbers profile)))
1972 (if (equal? generations '(0))
1973 '()
1974 generations)))
1975
9008debc
CM
1976(define (relative-generation-spec->number profile spec)
1977 "Return PROFILE's generation specified by SPEC, which is a string. The SPEC
1978may be a N, -N, or +N, where N is a number. If the spec is N, then the number
1979returned is N. If it is -N, then the number returned is the profile's current
1980generation number minus N. If it is +N, then the number returned is the
1981profile's current generation number plus N. Return #f if there is no such
1982generation."
1983 (let ((number (string->number spec)))
1984 (and number
1985 (case (string-ref spec 0)
1986 ((#\+ #\-)
1987 (relative-generation profile number))
1988 (else (if (memv number (profile-generations profile))
1989 number
1990 #f))))))
1991
1992
3ccde087
AK
1993(define* (relative-generation profile shift #:optional
1994 (current (generation-number profile)))
1995 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
1996SHIFT is a positive or negative number.
1997Return #f if there is no such generation."
1998 (let* ((abs-shift (abs shift))
1999 (numbers (profile-generations profile))
2000 (from-current (memq current
2001 (if (negative? shift)
2002 (reverse numbers)
2003 numbers))))
2004 (and from-current
2005 (< abs-shift (length from-current))
2006 (list-ref from-current abs-shift))))
2007
2008(define* (previous-generation-number profile #:optional
2009 (number (generation-number profile)))
cc4ecc2d
LC
2010 "Return the number of the generation before generation NUMBER of
2011PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
2012case when generations have been deleted (there are \"holes\")."
3ccde087
AK
2013 (or (relative-generation profile -1 number)
2014 0))
cc4ecc2d
LC
2015
2016(define (generation-file-name profile generation)
2017 "Return the file name for PROFILE's GENERATION."
2018 (format #f "~a-~a-link" profile generation))
2019
2020(define (generation-time profile number)
2021 "Return the creation time of a generation in the UTC format."
2022 (make-time time-utc 0
2023 (stat:ctime (stat (generation-file-name profile number)))))
2024
06d45f45
LC
2025(define (link-to-empty-profile store generation)
2026 "Link GENERATION, a string, to the empty profile. An error is raised if
2027that fails."
2028 (let* ((drv (run-with-store store
a6562c7e
LC
2029 (profile-derivation (manifest '())
2030 #:locales? #f)))
06d45f45
LC
2031 (prof (derivation->output-path drv "out")))
2032 (build-derivations store (list drv))
2033 (switch-symlinks generation prof)))
2034
2035(define (switch-to-generation profile number)
2036 "Atomically switch PROFILE to the generation NUMBER. Return the number of
2037the generation that was current before switching."
2038 (let ((current (generation-number profile))
2039 (generation (generation-file-name profile number)))
2040 (cond ((not (file-exists? profile))
2041 (raise (condition (&profile-not-found-error
2042 (profile profile)))))
2043 ((not (file-exists? generation))
2044 (raise (condition (&missing-generation-error
2045 (profile profile)
2046 (generation number)))))
2047 (else
bc6e291e 2048 (switch-symlinks profile (basename generation))
06d45f45
LC
2049 current))))
2050
2051(define (switch-to-previous-generation profile)
2052 "Atomically switch PROFILE to the previous generation. Return the former
2053generation number and the current one."
2054 (let ((previous (previous-generation-number profile)))
2055 (values (switch-to-generation profile previous)
2056 previous)))
2057
2058(define (roll-back store profile)
2059 "Roll back to the previous generation of PROFILE. Return the number of the
2060generation that was current before switching and the new generation number."
2061 (let* ((number (generation-number profile))
2062 (previous-number (previous-generation-number profile number))
2063 (previous-generation (generation-file-name profile previous-number)))
2064 (cond ((not (file-exists? profile)) ;invalid profile
2065 (raise (condition (&profile-not-found-error
2066 (profile profile)))))
2067 ((zero? number) ;empty profile
2068 (values number number))
2069 ((or (zero? previous-number) ;going to emptiness
2070 (not (file-exists? previous-generation)))
2071 (link-to-empty-profile store previous-generation)
2072 (switch-to-previous-generation profile))
2073 (else ;anything else
2074 (switch-to-previous-generation profile)))))
2075
2076(define (delete-generation store profile number)
2077 "Delete generation with NUMBER from PROFILE. Return the file name of the
2078generation that has been deleted, or #f if nothing was done (for instance
2079because the NUMBER is zero.)"
2080 (define (delete-and-return)
2081 (let ((generation (generation-file-name profile number)))
2082 (delete-file generation)
2083 generation))
2084
2085 (let* ((current-number (generation-number profile))
2086 (previous-number (previous-generation-number profile number))
2087 (previous-generation (generation-file-name profile previous-number)))
2088 (cond ((zero? number) #f) ;do not delete generation 0
2089 ((and (= number current-number)
2090 (not (file-exists? previous-generation)))
2091 (link-to-empty-profile store previous-generation)
2092 (switch-to-previous-generation profile)
2093 (delete-and-return))
2094 ((= number current-number)
2095 (roll-back store profile)
2096 (delete-and-return))
2097 (else
2098 (delete-and-return)))))
2099
efcb4441
LC
2100(define %user-profile-directory
2101 (and=> (getenv "HOME")
2102 (cut string-append <> "/.guix-profile")))
2103
2104(define %profile-directory
2105 (string-append %state-directory "/profiles/"
2106 (or (and=> (or (getenv "USER")
c20ba183
LC
2107 (getenv "LOGNAME")
2108 (false-if-exception
2109 (passwd:name (getpwuid (getuid)))))
efcb4441
LC
2110 (cut string-append "per-user/" <>))
2111 "default")))
2112
2113(define %current-profile
2114 ;; Call it `guix-profile', not `profile', to allow Guix profiles to
2115 ;; coexist with Nix profiles.
2116 (string-append %profile-directory "/guix-profile"))
2117
77dcfb4c 2118(define (ensure-profile-directory)
81c580c8
LC
2119 "Attempt to create /…/profiles/per-user/$USER if needed. Nowadays this is
2120taken care of by the daemon."
77dcfb4c
LC
2121 (let ((s (stat %profile-directory #f)))
2122 (unless (and s (eq? 'directory (stat:type s)))
2123 (catch 'system-error
2124 (lambda ()
2125 (mkdir-p %profile-directory))
2126 (lambda args
2127 ;; Often, we cannot create %PROFILE-DIRECTORY because its
2128 ;; parent directory is root-owned and we're running
2129 ;; unprivileged.
2130 (raise (condition
2131 (&message
2132 (message
2133 (format #f
2134 (G_ "while creating directory `~a': ~a")
2135 %profile-directory
2136 (strerror (system-error-errno args)))))
2137 (&fix-hint
2138 (hint
2139 (format #f (G_ "Please create the @file{~a} directory, \
2140with you as the owner.")
2141 %profile-directory))))))))
2142
2143 ;; Bail out if it's not owned by the user.
2144 (unless (or (not s) (= (stat:uid s) (getuid)))
2145 (raise (condition
2146 (&message
2147 (message
2148 (format #f (G_ "directory `~a' is not owned by you")
2149 %profile-directory)))
2150 (&fix-hint
2151 (hint
2152 (format #f (G_ "Please change the owner of @file{~a} \
2153to user ~s.")
2154 %profile-directory (or (getenv "USER")
2155 (getenv "LOGNAME")
2156 (getuid))))))))))
2157
efcb4441 2158(define (canonicalize-profile profile)
50c72ecd
LC
2159 "If PROFILE points to a profile in %PROFILE-DIRECTORY, return that.
2160Otherwise return PROFILE unchanged. The goal is to treat '-p ~/.guix-profile'
2161as if '-p' was omitted." ; see <http://bugs.gnu.org/17939>
2162 ;; Trim trailing slashes so 'readlink' can do its job.
efcb4441 2163 (let ((profile (string-trim-right profile #\/)))
50c72ecd
LC
2164 (catch 'system-error
2165 (lambda ()
2166 (let ((target (readlink profile)))
2167 (if (string=? (dirname target) %profile-directory)
2168 target
2169 profile)))
2170 (const profile))))
efcb4441 2171
1c795c4f
LC
2172(define %known-shorthand-profiles
2173 ;; Known shorthand forms for profiles that the user manipulates.
2174 (list (string-append (config-directory #:ensure? #f) "/current")
2175 %user-profile-directory))
2176
efcb4441 2177(define (user-friendly-profile profile)
1c795c4f
LC
2178 "Return either ~/.guix-profile or ~/.config/guix/current if that's what
2179PROFILE refers to, directly or indirectly, or PROFILE."
2180 (or (find (lambda (shorthand)
2181 (and shorthand
2182 (let ((target (false-if-exception
2183 (readlink shorthand))))
2184 (and target (string=? target profile)))))
2185 %known-shorthand-profiles)
efcb4441
LC
2186 profile))
2187
cc4ecc2d 2188;;; profiles.scm ends here