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