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