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