download: Protect against dangling symlinks in $SSL_CERT_DIR.
[jackhill/guix/guix.git] / guix / profiles.scm
CommitLineData
cc4ecc2d 1;;; GNU Guix --- Functional package management for GNU
e5f04c2d 2;;; Copyright © 2013, 2014, 2015, 2016 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>
7ddc1780 7;;; Copyright © 2016 Ricardo Wurmus <rekado@elephly.net>
9008debc 8;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
cc4ecc2d
LC
9;;;
10;;; This file is part of GNU Guix.
11;;;
12;;; GNU Guix is free software; you can redistribute it and/or modify it
13;;; under the terms of the GNU General Public License as published by
14;;; the Free Software Foundation; either version 3 of the License, or (at
15;;; your option) any later version.
16;;;
17;;; GNU Guix is distributed in the hope that it will be useful, but
18;;; WITHOUT ANY WARRANTY; without even the implied warranty of
19;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;;; GNU General Public License for more details.
21;;;
22;;; You should have received a copy of the GNU General Public License
23;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
24
25(define-module (guix profiles)
97425486
LC
26 #:use-module ((guix utils) #:hide (package-name->name+version))
27 #:use-module ((guix build utils)
28 #:select (package-name->name+version))
cc4ecc2d 29 #:use-module (guix records)
cc4ecc2d 30 #:use-module (guix packages)
e89431bf
LC
31 #:use-module (guix derivations)
32 #:use-module (guix search-paths)
a54c94a4 33 #:use-module (guix gexp)
79ee406d 34 #:use-module (guix monads)
e87f0591 35 #:use-module (guix store)
cc4ecc2d
LC
36 #:use-module (ice-9 match)
37 #:use-module (ice-9 regex)
38 #:use-module (ice-9 ftw)
343745c8 39 #:use-module (ice-9 format)
cc4ecc2d
LC
40 #:use-module (srfi srfi-1)
41 #:use-module (srfi srfi-9)
79601521 42 #:use-module (srfi srfi-11)
cc4ecc2d
LC
43 #:use-module (srfi srfi-19)
44 #:use-module (srfi srfi-26)
c0c018f1
AK
45 #:use-module (srfi srfi-34)
46 #:use-module (srfi srfi-35)
47 #:export (&profile-error
48 profile-error?
49 profile-error-profile
50 &profile-not-found-error
51 profile-not-found-error?
52 &missing-generation-error
53 missing-generation-error?
54 missing-generation-error-generation
55
56 manifest make-manifest
cc4ecc2d
LC
57 manifest?
58 manifest-entries
59
60 <manifest-entry> ; FIXME: eventually make it internal
61 manifest-entry
62 manifest-entry?
63 manifest-entry-name
64 manifest-entry-version
65 manifest-entry-output
a54c94a4 66 manifest-entry-item
cc4ecc2d 67 manifest-entry-dependencies
dedb17ad 68 manifest-entry-search-paths
cc4ecc2d 69
a2078770
LC
70 manifest-pattern
71 manifest-pattern?
03763d64
LC
72 manifest-pattern-name
73 manifest-pattern-version
74 manifest-pattern-output
a2078770 75
cc4ecc2d 76 manifest-remove
f7554030 77 manifest-add
ef8993e2 78 manifest-lookup
cc4ecc2d 79 manifest-installed?
a2078770 80 manifest-matching-entries
cc4ecc2d 81
343745c8
AK
82 manifest-transaction
83 manifest-transaction?
84 manifest-transaction-install
85 manifest-transaction-remove
c8c25704
LC
86 manifest-transaction-install-entry
87 manifest-transaction-remove-pattern
88 manifest-transaction-null?
343745c8 89 manifest-perform-transaction
79601521 90 manifest-transaction-effects
343745c8 91
cc4ecc2d 92 profile-manifest
462f5cca 93 package->manifest-entry
8404ed5c 94 packages->manifest
aa46a028 95 %default-profile-hooks
cc4ecc2d 96 profile-derivation
06d45f45 97
cc4ecc2d
LC
98 generation-number
99 generation-numbers
f452e8ff 100 profile-generations
9008debc 101 relative-generation-spec->number
3ccde087 102 relative-generation
cc4ecc2d
LC
103 previous-generation-number
104 generation-time
06d45f45
LC
105 generation-file-name
106 switch-to-generation
107 roll-back
108 delete-generation))
cc4ecc2d
LC
109
110;;; Commentary:
111;;;
112;;; Tools to create and manipulate profiles---i.e., the representation of a
113;;; set of installed packages.
114;;;
115;;; Code:
116
117\f
c0c018f1
AK
118;;;
119;;; Condition types.
120;;;
121
122(define-condition-type &profile-error &error
123 profile-error?
124 (profile profile-error-profile))
125
126(define-condition-type &profile-not-found-error &profile-error
127 profile-not-found-error?)
128
129(define-condition-type &missing-generation-error &profile-error
130 missing-generation-error?
131 (generation missing-generation-error-generation))
132
133\f
cc4ecc2d
LC
134;;;
135;;; Manifests.
136;;;
137
138(define-record-type <manifest>
139 (manifest entries)
140 manifest?
141 (entries manifest-entries)) ; list of <manifest-entry>
142
143;; Convenient alias, to avoid name clashes.
144(define make-manifest manifest)
145
146(define-record-type* <manifest-entry> manifest-entry
147 make-manifest-entry
148 manifest-entry?
149 (name manifest-entry-name) ; string
150 (version manifest-entry-version) ; string
151 (output manifest-entry-output ; string
152 (default "out"))
a54c94a4 153 (item manifest-entry-item) ; package | store path
4ca0b410 154 (dependencies manifest-entry-dependencies ; (store path | package)*
dedb17ad
LC
155 (default '()))
156 (search-paths manifest-entry-search-paths ; search-path-specification*
4ca0b410 157 (default '())))
cc4ecc2d 158
a2078770
LC
159(define-record-type* <manifest-pattern> manifest-pattern
160 make-manifest-pattern
161 manifest-pattern?
162 (name manifest-pattern-name) ; string
163 (version manifest-pattern-version ; string | #f
164 (default #f))
165 (output manifest-pattern-output ; string | #f
166 (default "out")))
167
cc4ecc2d
LC
168(define (profile-manifest profile)
169 "Return the PROFILE's manifest."
170 (let ((file (string-append profile "/manifest")))
171 (if (file-exists? file)
172 (call-with-input-file file read-manifest)
173 (manifest '()))))
174
9e90fc77
LC
175(define* (package->manifest-entry package #:optional (output "out"))
176 "Return a manifest entry for the OUTPUT of package PACKAGE."
462f5cca
LC
177 (let ((deps (map (match-lambda
178 ((label package)
b4a4bec0 179 (gexp-input package))
462f5cca 180 ((label package output)
b4a4bec0 181 (gexp-input package output)))
462f5cca
LC
182 (package-transitive-propagated-inputs package))))
183 (manifest-entry
184 (name (package-name package))
185 (version (package-version package))
9e90fc77 186 (output output)
462f5cca 187 (item package)
dedb17ad 188 (dependencies (delete-duplicates deps))
ccda8f7d 189 (search-paths (package-transitive-native-search-paths package)))))
462f5cca 190
8404ed5c
DT
191(define (packages->manifest packages)
192 "Return a list of manifest entries, one for each item listed in PACKAGES.
193Elements of PACKAGES can be either package objects or package/string tuples
194denoting a specific output of a package."
195 (manifest
196 (map (match-lambda
197 ((package output)
198 (package->manifest-entry package output))
9e90fc77
LC
199 ((? package? package)
200 (package->manifest-entry package)))
8404ed5c
DT
201 packages)))
202
a54c94a4
LC
203(define (manifest->gexp manifest)
204 "Return a representation of MANIFEST as a gexp."
205 (define (entry->gexp entry)
cc4ecc2d 206 (match entry
dedb17ad
LC
207 (($ <manifest-entry> name version output (? string? path)
208 (deps ...) (search-paths ...))
209 #~(#$name #$version #$output #$path
210 (propagated-inputs #$deps)
211 (search-paths #$(map search-path-specification->sexp
212 search-paths))))
213 (($ <manifest-entry> name version output (? package? package)
214 (deps ...) (search-paths ...))
a54c94a4 215 #~(#$name #$version #$output
dedb17ad
LC
216 (ungexp package (or output "out"))
217 (propagated-inputs #$deps)
218 (search-paths #$(map search-path-specification->sexp
219 search-paths))))))
cc4ecc2d
LC
220
221 (match manifest
222 (($ <manifest> (entries ...))
dedb17ad 223 #~(manifest (version 2)
a54c94a4 224 (packages #$(map entry->gexp entries))))))
cc4ecc2d 225
dedb17ad
LC
226(define (find-package name version)
227 "Return a package from the distro matching NAME and possibly VERSION. This
228procedure is here for backward-compatibility and will eventually vanish."
229 (define find-best-packages-by-name ;break abstractions
230 (module-ref (resolve-interface '(gnu packages))
231 'find-best-packages-by-name))
232
233 ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; the
234 ;; former traverses the module tree only once and then allows for efficient
235 ;; access via a vhash.
236 (match (find-best-packages-by-name name version)
237 ((p _ ...) p)
238 (_
239 (match (find-best-packages-by-name name #f)
240 ((p _ ...) p)
241 (_ #f)))))
242
cc4ecc2d
LC
243(define (sexp->manifest sexp)
244 "Parse SEXP as a manifest."
dedb17ad
LC
245 (define (infer-search-paths name version)
246 ;; Infer the search path specifications for NAME-VERSION by looking up a
247 ;; same-named package in the distro. Useful for the old manifest formats
248 ;; that did not store search path info.
249 (let ((package (find-package name version)))
250 (if package
251 (package-native-search-paths package)
252 '())))
253
cc4ecc2d
LC
254 (match sexp
255 (('manifest ('version 0)
256 ('packages ((name version output path) ...)))
257 (manifest
258 (map (lambda (name version output path)
259 (manifest-entry
260 (name name)
261 (version version)
262 (output output)
dedb17ad
LC
263 (item path)
264 (search-paths (infer-search-paths name version))))
cc4ecc2d
LC
265 name version output path)))
266
267 ;; Version 1 adds a list of propagated inputs to the
268 ;; name/version/output/path tuples.
269 (('manifest ('version 1)
270 ('packages ((name version output path deps) ...)))
271 (manifest
272 (map (lambda (name version output path deps)
d34736c5
LC
273 ;; Up to Guix 0.7 included, dependencies were listed as ("gmp"
274 ;; "/gnu/store/...-gmp") for instance. Discard the 'label' in
275 ;; such lists.
276 (let ((deps (match deps
277 (((labels directories) ...)
278 directories)
279 ((directories ...)
280 directories))))
281 (manifest-entry
282 (name name)
283 (version version)
284 (output output)
285 (item path)
dedb17ad
LC
286 (dependencies deps)
287 (search-paths (infer-search-paths name version)))))
cc4ecc2d
LC
288 name version output path deps)))
289
dedb17ad
LC
290 ;; Version 2 adds search paths and is slightly more verbose.
291 (('manifest ('version 2 minor-version ...)
292 ('packages ((name version output path
293 ('propagated-inputs deps)
294 ('search-paths search-paths)
295 extra-stuff ...)
296 ...)))
297 (manifest
298 (map (lambda (name version output path deps search-paths)
299 (manifest-entry
300 (name name)
301 (version version)
302 (output output)
303 (item path)
304 (dependencies deps)
305 (search-paths (map sexp->search-path-specification
306 search-paths))))
307 name version output path deps search-paths)))
cc4ecc2d 308 (_
88aab8e3
LC
309 (raise (condition
310 (&message (message "unsupported manifest format")))))))
cc4ecc2d
LC
311
312(define (read-manifest port)
313 "Return the packages listed in MANIFEST."
314 (sexp->manifest (read port)))
315
a2078770
LC
316(define (entry-predicate pattern)
317 "Return a procedure that returns #t when passed a manifest entry that
318matches NAME/OUTPUT/VERSION. OUTPUT and VERSION may be #f, in which case they
319are ignored."
320 (match pattern
321 (($ <manifest-pattern> name version output)
322 (match-lambda
323 (($ <manifest-entry> entry-name entry-version entry-output)
324 (and (string=? entry-name name)
325 (or (not entry-output) (not output)
326 (string=? entry-output output))
327 (or (not version)
328 (string=? entry-version version))))))))
329
330(define (manifest-remove manifest patterns)
331 "Remove entries for each of PATTERNS from MANIFEST. Each item in PATTERNS
332must be a manifest-pattern."
333 (define (remove-entry pattern lst)
334 (remove (entry-predicate pattern) lst))
335
336 (make-manifest (fold remove-entry
cc4ecc2d 337 (manifest-entries manifest)
a2078770 338 patterns)))
cc4ecc2d 339
f7554030
AK
340(define (manifest-add manifest entries)
341 "Add a list of manifest ENTRIES to MANIFEST and return new manifest.
342Remove MANIFEST entries that have the same name and output as ENTRIES."
343 (define (same-entry? entry name output)
344 (match entry
345 (($ <manifest-entry> entry-name _ entry-output _ ...)
346 (and (equal? name entry-name)
347 (equal? output entry-output)))))
348
349 (make-manifest
350 (append entries
351 (fold (lambda (entry result)
352 (match entry
353 (($ <manifest-entry> name _ out _ ...)
354 (filter (negate (cut same-entry? <> name out))
355 result))))
356 (manifest-entries manifest)
357 entries))))
358
ef8993e2
LC
359(define (manifest-lookup manifest pattern)
360 "Return the first item of MANIFEST that matches PATTERN, or #f if there is
361no match.."
362 (find (entry-predicate pattern)
363 (manifest-entries manifest)))
364
a2078770
LC
365(define (manifest-installed? manifest pattern)
366 "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern),
367#f otherwise."
ef8993e2 368 (->bool (manifest-lookup manifest pattern)))
cc4ecc2d 369
a2078770
LC
370(define (manifest-matching-entries manifest patterns)
371 "Return all the entries of MANIFEST that match one of the PATTERNS."
372 (define predicates
373 (map entry-predicate patterns))
374
375 (define (matches? entry)
376 (any (lambda (pred)
377 (pred entry))
378 predicates))
379
380 (filter matches? (manifest-entries manifest)))
381
cc4ecc2d 382\f
343745c8
AK
383;;;
384;;; Manifest transactions.
385;;;
386
387(define-record-type* <manifest-transaction> manifest-transaction
388 make-manifest-transaction
389 manifest-transaction?
390 (install manifest-transaction-install ; list of <manifest-entry>
391 (default '()))
392 (remove manifest-transaction-remove ; list of <manifest-pattern>
393 (default '())))
394
c8c25704
LC
395(define (manifest-transaction-install-entry entry transaction)
396 "Augment TRANSACTION's set of installed packages with ENTRY, a
397<manifest-entry>."
398 (manifest-transaction
399 (inherit transaction)
400 (install
401 (cons entry (manifest-transaction-install transaction)))))
402
403(define (manifest-transaction-remove-pattern pattern transaction)
404 "Add PATTERN to TRANSACTION's list of packages to remove."
405 (manifest-transaction
406 (inherit transaction)
407 (remove
408 (cons pattern (manifest-transaction-remove transaction)))))
409
410(define (manifest-transaction-null? transaction)
411 "Return true if TRANSACTION has no effect---i.e., it neither installs nor
412remove software."
413 (match transaction
414 (($ <manifest-transaction> () ()) #t)
415 (($ <manifest-transaction> _ _) #f)))
416
79601521 417(define (manifest-transaction-effects manifest transaction)
46b23e1a
LC
418 "Compute the effect of applying TRANSACTION to MANIFEST. Return 4 values:
419the list of packages that would be removed, installed, upgraded, or downgraded
420when applying TRANSACTION to MANIFEST. Upgrades are represented as pairs
421where the head is the entry being upgraded and the tail is the entry that will
422replace it."
79601521
LC
423 (define (manifest-entry->pattern entry)
424 (manifest-pattern
425 (name (manifest-entry-name entry))
426 (output (manifest-entry-output entry))))
427
46b23e1a
LC
428 (let loop ((input (manifest-transaction-install transaction))
429 (install '())
430 (upgrade '())
431 (downgrade '()))
79601521
LC
432 (match input
433 (()
434 (let ((remove (manifest-transaction-remove transaction)))
435 (values (manifest-matching-entries manifest remove)
46b23e1a 436 (reverse install) (reverse upgrade) (reverse downgrade))))
79601521
LC
437 ((entry rest ...)
438 ;; Check whether installing ENTRY corresponds to the installation of a
439 ;; new package or to an upgrade.
440
441 ;; XXX: When the exact same output directory is installed, we're not
442 ;; really upgrading anything. Add a check for that case.
443 (let* ((pattern (manifest-entry->pattern entry))
46b23e1a
LC
444 (previous (manifest-lookup manifest pattern))
445 (newer? (and previous
3bea13bb
LC
446 (version>=? (manifest-entry-version entry)
447 (manifest-entry-version previous)))))
79601521 448 (loop rest
ef8993e2 449 (if previous install (cons entry install))
46b23e1a 450 (if (and previous newer?)
ef8993e2 451 (alist-cons previous entry upgrade)
46b23e1a
LC
452 upgrade)
453 (if (and previous (not newer?))
454 (alist-cons previous entry downgrade)
455 downgrade)))))))
79601521 456
343745c8 457(define (manifest-perform-transaction manifest transaction)
c8c25704 458 "Perform TRANSACTION on MANIFEST and return the new manifest."
343745c8
AK
459 (let ((install (manifest-transaction-install transaction))
460 (remove (manifest-transaction-remove transaction)))
461 (manifest-add (manifest-remove manifest remove)
462 install)))
463
343745c8 464\f
cc4ecc2d
LC
465;;;
466;;; Profiles.
467;;;
468
79ee406d 469(define (manifest-inputs manifest)
b4a4bec0 470 "Return a list of <gexp-input> objects for MANIFEST."
79ee406d 471 (append-map (match-lambda
b4a4bec0
LC
472 (($ <manifest-entry> name version output thing deps)
473 ;; THING may be a package or a file name. In the latter case,
474 ;; assume it's already valid. Ditto for DEPS.
475 (cons (gexp-input thing output) deps)))
79ee406d
LC
476 (manifest-entries manifest)))
477
2c9f4786 478(define* (manifest-lookup-package manifest name #:optional version)
d72d7833 479 "Return as a monadic value the first package or store path referenced by
2c9f4786
RW
480MANIFEST that is named NAME and optionally has the given VERSION prefix, or #f
481if not found."
d72d7833
SB
482 ;; Return as a monadic value the package or store path referenced by the
483 ;; manifest ENTRY, or #f if not referenced.
484 (define (entry-lookup-package entry)
485 (define (find-among-inputs inputs)
486 (find (lambda (input)
487 (and (package? input)
2c9f4786
RW
488 (equal? name (package-name input))
489 (if version
490 (string-prefix? version (package-version input))
491 #t)))
d72d7833
SB
492 inputs))
493 (define (find-among-store-items items)
494 (find (lambda (item)
054f60cd 495 (let-values (((name* version*)
2c9f4786
RW
496 (package-name->name+version
497 (store-path-package-name item))))
054f60cd 498 (and (string=? name name*)
2c9f4786 499 (if version
054f60cd 500 (string-prefix? version version*)
2c9f4786 501 #t))))
d72d7833
SB
502 items))
503
d72d7833
SB
504 (with-monad %store-monad
505 (match (manifest-entry-item entry)
506 ((? package? package)
963521a3
SB
507 (match (cons (list (package-name package) package)
508 (package-transitive-inputs package))
d72d7833
SB
509 (((labels inputs . _) ...)
510 (return (find-among-inputs inputs)))))
511 ((? string? item)
512 (mlet %store-monad ((refs (references* item)))
513 (return (find-among-store-items refs)))))))
514
515 (anym %store-monad
516 entry-lookup-package (manifest-entries manifest)))
517
79ee406d
LC
518(define (info-dir-file manifest)
519 "Return a derivation that builds the 'dir' file for all the entries of
520MANIFEST."
2f0556ae
LC
521 (define texinfo ;lazy reference
522 (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
523 (define gzip ;lazy reference
524 (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
525
79ee406d 526 (define build
99b231de
LC
527 (with-imported-modules '((guix build utils))
528 #~(begin
529 (use-modules (guix build utils)
530 (srfi srfi-1) (srfi srfi-26)
531 (ice-9 ftw))
532
533 (define (info-file? file)
534 (or (string-suffix? ".info" file)
535 (string-suffix? ".info.gz" file)))
536
537 (define (info-files top)
538 (let ((infodir (string-append top "/share/info")))
539 (map (cut string-append infodir "/" <>)
540 (or (scandir infodir info-file?) '()))))
541
542 (define (install-info info)
543 (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
544 (zero?
545 (system* (string-append #+texinfo "/bin/install-info") "--silent"
546 info (string-append #$output "/share/info/dir"))))
547
548 (mkdir-p (string-append #$output "/share/info"))
549 (exit (every install-info
550 (append-map info-files
551 '#$(manifest-inputs manifest)))))))
79ee406d 552
aa46a028 553 (gexp->derivation "info-dir" build
a7a4fd9a
LC
554 #:local-build? #t
555 #:substitutable? #f))
79ee406d 556
042bc828
FB
557(define (ghc-package-cache-file manifest)
558 "Return a derivation that builds the GHC 'package.cache' file for all the
aa46a028 559entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
99b231de 560 (define ghc ;lazy reference
042bc828
FB
561 (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
562
563 (define build
99b231de
LC
564 (with-imported-modules '((guix build utils))
565 #~(begin
566 (use-modules (guix build utils)
567 (srfi srfi-1) (srfi srfi-26)
568 (ice-9 ftw))
569
570 (define ghc-name-version
571 (let* ((base (basename #+ghc)))
572 (string-drop base
573 (+ 1 (string-index base #\-)))))
574
575 (define db-subdir
576 (string-append "lib/" ghc-name-version "/package.conf.d"))
577
578 (define db-dir
579 (string-append #$output "/" db-subdir))
580
581 (define (conf-files top)
582 (let ((db (string-append top "/" db-subdir)))
583 (if (file-exists? db)
584 (find-files db "\\.conf$")
585 '())))
586
587 (define (copy-conf-file conf)
588 (let ((base (basename conf)))
589 (copy-file conf (string-append db-dir "/" base))))
590
591 (system* (string-append #+ghc "/bin/ghc-pkg") "init" db-dir)
592 (for-each copy-conf-file
593 (append-map conf-files
594 (delete-duplicates
595 '#$(manifest-inputs manifest))))
596 (let ((success
597 (zero?
598 (system* (string-append #+ghc "/bin/ghc-pkg") "recache"
599 (string-append "--package-db=" db-dir)))))
600 (for-each delete-file (find-files db-dir "\\.conf$"))
601 (exit success)))))
042bc828 602
07eaecfa
LC
603 (with-monad %store-monad
604 ;; Don't depend on GHC when there's nothing to do.
605 (if (any (cut string-prefix? "ghc" <>)
606 (map manifest-entry-name (manifest-entries manifest)))
607 (gexp->derivation "ghc-package-cache" build
a7a4fd9a
LC
608 #:local-build? #t
609 #:substitutable? #f)
07eaecfa 610 (return #f))))
042bc828 611
536c3ee4
MW
612(define (ca-certificate-bundle manifest)
613 "Return a derivation that builds a single-file bundle containing the CA
614certificates in the /etc/ssl/certs sub-directories of the packages in
615MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
616 ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
617 ;; for a discussion.
618
619 (define glibc-utf8-locales ;lazy reference
620 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
621
622 (define build
99b231de
LC
623 (with-imported-modules '((guix build utils))
624 #~(begin
625 (use-modules (guix build utils)
626 (rnrs io ports)
627 (srfi srfi-1)
628 (srfi srfi-26)
629 (ice-9 ftw)
630 (ice-9 match))
631
632 (define (pem-file? file)
633 (string-suffix? ".pem" file))
634
635 (define (ca-files top)
636 (let ((cert-dir (string-append top "/etc/ssl/certs")))
637 (map (cut string-append cert-dir "/" <>)
638 (or (scandir cert-dir pem-file?) '()))))
639
640 (define (concatenate-files files result)
641 "Make RESULT the concatenation of all of FILES."
642 (define (dump file port)
643 (display (call-with-input-file file get-string-all)
644 port)
645 (newline port)) ;required, see <https://bugs.debian.org/635570>
646
647 (call-with-output-file result
648 (lambda (port)
649 (for-each (cut dump <> port) files))))
650
651 ;; Some file names in the NSS certificates are UTF-8 encoded so
652 ;; install a UTF-8 locale.
653 (setenv "LOCPATH"
654 (string-append #+glibc-utf8-locales "/lib/locale/"
655 #+(package-version glibc-utf8-locales)))
656 (setlocale LC_ALL "en_US.utf8")
657
658 (match (append-map ca-files '#$(manifest-inputs manifest))
659 (()
660 ;; Since there are no CA files, just create an empty directory. Do
661 ;; not create the etc/ssl/certs sub-directory, since that would
662 ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
663 ;; defined.
664 (mkdir #$output)
665 #t)
666 ((ca-files ...)
667 (let ((result (string-append #$output "/etc/ssl/certs")))
668 (mkdir-p result)
669 (concatenate-files ca-files
670 (string-append result
671 "/ca-certificates.crt"))
672 #t))))))
536c3ee4 673
aa46a028 674 (gexp->derivation "ca-certificate-bundle" build
a7a4fd9a
LC
675 #:local-build? #t
676 #:substitutable? #f))
aa46a028 677
b04af0ec
SB
678(define (gtk-icon-themes manifest)
679 "Return a derivation that unions all icon themes from manifest entries and
680creates the GTK+ 'icon-theme.cache' file for each theme."
d1fb4af6
SB
681 (define gtk+ ; lazy reference
682 (module-ref (resolve-interface '(gnu packages gtk)) 'gtk+))
683
684 (mlet %store-monad ((%gtk+ (manifest-lookup-package manifest "gtk+"))
685 ;; XXX: Can't use gtk-update-icon-cache corresponding
686 ;; to the gtk+ referenced by 'manifest'. Because
687 ;; '%gtk+' can be either a package or store path, and
688 ;; there's no way to get the "bin" output for the later.
689 (gtk-update-icon-cache
690 -> #~(string-append #+gtk+:bin
691 "/bin/gtk-update-icon-cache")))
692
b04af0ec 693 (define build
99b231de
LC
694 (with-imported-modules '((guix build utils)
695 (guix build union)
696 (guix build profiles)
697 (guix search-paths)
698 (guix records))
699 #~(begin
700 (use-modules (guix build utils)
701 (guix build union)
702 (guix build profiles)
703 (srfi srfi-26)
704 (ice-9 ftw))
705
706 (let* ((destdir (string-append #$output "/share/icons"))
707 (icondirs (filter file-exists?
708 (map (cut string-append <> "/share/icons")
d1fb4af6 709 '#$(manifest-inputs manifest)))))
99b231de
LC
710
711 ;; Union all the icons.
712 (mkdir-p (string-append #$output "/share"))
713 (union-build destdir icondirs
714 #:log-port (%make-void-port "w"))
715
716 ;; Update the 'icon-theme.cache' file for each icon theme.
717 (for-each
718 (lambda (theme)
719 (let ((dir (string-append destdir "/" theme)))
720 ;; Occasionally DESTDIR contains plain files, such as
721 ;; "abiword_48.png". Ignore these.
722 (when (file-is-directory? dir)
723 (ensure-writable-directory dir)
d1fb4af6 724 (system* #+gtk-update-icon-cache "-t" dir "--quiet"))))
99b231de 725 (scandir destdir (negate (cut member <> '("." "..")))))))))
b04af0ec
SB
726
727 ;; Don't run the hook when there's nothing to do.
d1fb4af6 728 (if %gtk+
b04af0ec 729 (gexp->derivation "gtk-icon-themes" build
a7a4fd9a
LC
730 #:local-build? #t
731 #:substitutable? #f)
b04af0ec
SB
732 (return #f))))
733
7ddc1780
RW
734(define (gtk-im-modules manifest)
735 "Return a derivation that builds the cache files for input method modules
736for both major versions of GTK+."
737
738 (mlet %store-monad ((gtk+ (manifest-lookup-package manifest "gtk+" "3"))
739 (gtk+-2 (manifest-lookup-package manifest "gtk+" "2")))
740
741 (define (build gtk gtk-version)
742 (let ((major (string-take gtk-version 1)))
743 (with-imported-modules '((guix build utils)
744 (guix build union)
745 (guix build profiles)
746 (guix search-paths)
747 (guix records))
748 #~(begin
749 (use-modules (guix build utils)
750 (guix build union)
751 (guix build profiles)
752 (ice-9 popen)
753 (srfi srfi-1)
754 (srfi srfi-26))
755
756 (let* ((prefix (string-append "/lib/gtk-" #$major ".0/"
757 #$gtk-version))
758 (query (string-append #$gtk "/bin/gtk-query-immodules-"
759 #$major ".0"))
760 (destdir (string-append #$output prefix))
761 (moddirs (cons (string-append #$gtk prefix "/immodules")
762 (filter file-exists?
763 (map (cut string-append <> prefix "/immodules")
764 '#$(manifest-inputs manifest)))))
765 (modules (append-map (cut find-files <> "\\.so$")
766 moddirs)))
767
768 ;; Generate a new immodules cache file.
769 (mkdir-p (string-append #$output prefix))
770 (let ((pipe (apply open-pipe* OPEN_READ query modules))
771 (outfile (string-append #$output prefix
772 "/immodules-gtk" #$major ".cache")))
773 (dynamic-wind
774 (const #t)
775 (lambda ()
776 (call-with-output-file outfile
777 (lambda (out)
778 (while (not (eof-object? (peek-char pipe)))
779 (write-char (read-char pipe) out))))
780 #t)
781 (lambda ()
782 (close-pipe pipe)))))))))
783
784 ;; Don't run the hook when there's nothing to do.
785 (let ((gexp #~(begin
786 #$(if gtk+ (build gtk+ "3.0.0") #t)
787 #$(if gtk+-2 (build gtk+-2 "2.10.0") #t))))
788 (if (or gtk+ gtk+-2)
789 (gexp->derivation "gtk-im-modules" gexp
790 #:local-build? #t
791 #:substitutable? #f)
792 (return #f)))))
793
842cb820
SB
794(define (xdg-desktop-database manifest)
795 "Return a derivation that builds the @file{mimeinfo.cache} database from
796desktop files. It's used to query what applications can handle a given
797MIME type."
d72d7833
SB
798 (mlet %store-monad ((desktop-file-utils
799 (manifest-lookup-package
800 manifest "desktop-file-utils")))
801 (define build
99b231de
LC
802 (with-imported-modules '((guix build utils)
803 (guix build union))
804 #~(begin
805 (use-modules (srfi srfi-26)
806 (guix build utils)
807 (guix build union))
808 (let* ((destdir (string-append #$output "/share/applications"))
809 (appdirs (filter file-exists?
810 (map (cut string-append <>
811 "/share/applications")
812 '#$(manifest-inputs manifest))))
813 (update-desktop-database (string-append
814 #+desktop-file-utils
815 "/bin/update-desktop-database")))
816 (mkdir-p (string-append #$output "/share"))
817 (union-build destdir appdirs
818 #:log-port (%make-void-port "w"))
819 (exit (zero? (system* update-desktop-database destdir)))))))
842cb820 820
d72d7833
SB
821 ;; Don't run the hook when 'desktop-file-utils' is not referenced.
822 (if desktop-file-utils
823 (gexp->derivation "xdg-desktop-database" build
d72d7833
SB
824 #:local-build? #t
825 #:substitutable? #f)
826 (return #f))))
842cb820 827
6c06b1fd
SB
828(define (xdg-mime-database manifest)
829 "Return a derivation that builds the @file{mime.cache} database from manifest
830entries. It's used to query the MIME type of a given file."
801d316b
SB
831 (define shared-mime-info ; lazy reference
832 (module-ref (resolve-interface '(gnu packages gnome)) 'shared-mime-info))
833
834 (mlet %store-monad ((glib
d72d7833 835 (manifest-lookup-package
801d316b 836 manifest "glib")))
d72d7833 837 (define build
99b231de
LC
838 (with-imported-modules '((guix build utils)
839 (guix build union))
840 #~(begin
841 (use-modules (srfi srfi-26)
842 (guix build utils)
843 (guix build union))
844 (let* ((datadir (string-append #$output "/share"))
845 (destdir (string-append datadir "/mime"))
846 (pkgdirs (filter file-exists?
847 (map (cut string-append <>
848 "/share/mime/packages")
801d316b
SB
849 (cons #+shared-mime-info
850 '#$(manifest-inputs manifest)))))
99b231de
LC
851 (update-mime-database (string-append
852 #+shared-mime-info
853 "/bin/update-mime-database")))
854 (mkdir-p destdir)
855 (union-build (string-append destdir "/packages") pkgdirs
856 #:log-port (%make-void-port "w"))
857 (setenv "XDG_DATA_HOME" datadir)
858 (exit (zero? (system* update-mime-database destdir)))))))
d72d7833 859
801d316b
SB
860 ;; Don't run the hook when there are no GLib based applications.
861 (if glib
d72d7833 862 (gexp->derivation "xdg-mime-database" build
d72d7833
SB
863 #:local-build? #t
864 #:substitutable? #f)
865 (return #f))))
6c06b1fd 866
9eb5a449
AK
867(define (fonts-dir-file manifest)
868 "Return a derivation that builds the @file{fonts.dir} and @file{fonts.scale}
869files for the truetype fonts of the @var{manifest} entries."
870 (define mkfontscale
871 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontscale))
872
873 (define mkfontdir
874 (module-ref (resolve-interface '(gnu packages xorg)) 'mkfontdir))
875
876 (define build
877 #~(begin
878 (use-modules (srfi srfi-26)
879 (guix build utils)
880 (guix build union))
881 (let ((ttf-dirs (filter file-exists?
882 (map (cut string-append <>
883 "/share/fonts/truetype")
884 '#$(manifest-inputs manifest)))))
885 (mkdir #$output)
886 (if (null? ttf-dirs)
887 (exit #t)
888 (let* ((fonts-dir (string-append #$output "/share/fonts"))
889 (ttf-dir (string-append fonts-dir "/truetype"))
890 (mkfontscale (string-append #+mkfontscale
891 "/bin/mkfontscale"))
892 (mkfontdir (string-append #+mkfontdir
893 "/bin/mkfontdir")))
894 (mkdir-p fonts-dir)
895 (union-build ttf-dir ttf-dirs
896 #:log-port (%make-void-port "w"))
897 (with-directory-excursion ttf-dir
898 (exit (and (zero? (system* mkfontscale))
899 (zero? (system* mkfontdir))))))))))
900
901 (gexp->derivation "fonts-dir" build
902 #:modules '((guix build utils)
903 (guix build union))
904 #:local-build? #t
905 #:substitutable? #f))
906
aa46a028
LC
907(define %default-profile-hooks
908 ;; This is the list of derivation-returning procedures that are called by
909 ;; default when making a non-empty profile.
910 (list info-dir-file
9eb5a449 911 fonts-dir-file
aa46a028 912 ghc-package-cache-file
b04af0ec 913 ca-certificate-bundle
842cb820 914 gtk-icon-themes
7ddc1780 915 gtk-im-modules
6c06b1fd
SB
916 xdg-desktop-database
917 xdg-mime-database))
536c3ee4
MW
918
919(define* (profile-derivation manifest
920 #:key
e5f04c2d
LC
921 (hooks %default-profile-hooks)
922 system)
79ee406d 923 "Return a derivation that builds a profile (aka. 'user environment') with
aa46a028
LC
924the given MANIFEST. The profile includes additional derivations returned by
925the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc."
e5f04c2d
LC
926 (mlet %store-monad ((system (if system
927 (return system)
928 (current-system)))
929 (extras (if (null? (manifest-entries manifest))
aa46a028
LC
930 (return '())
931 (sequence %store-monad
07eaecfa
LC
932 (map (lambda (hook)
933 (hook manifest))
934 hooks)))))
79ee406d 935 (define inputs
eeae0b3c
SB
936 (append (filter-map (lambda (drv)
937 (and (derivation? drv)
938 (gexp-input drv)))
07eaecfa 939 extras)
536c3ee4 940 (manifest-inputs manifest)))
79ee406d
LC
941
942 (define builder
99b231de
LC
943 (with-imported-modules '((guix build profiles)
944 (guix build union)
945 (guix build utils)
946 (guix search-paths)
947 (guix records))
948 #~(begin
949 (use-modules (guix build profiles)
950 (guix search-paths)
951 (srfi srfi-1))
952
953 (setvbuf (current-output-port) _IOLBF)
954 (setvbuf (current-error-port) _IOLBF)
955
956 (define search-paths
957 ;; Search paths of MANIFEST's packages, converted back to their
958 ;; record form.
959 (map sexp->search-path-specification
960 (delete-duplicates
961 '#$(map search-path-specification->sexp
962 (append-map manifest-entry-search-paths
963 (manifest-entries manifest))))))
964
965 (build-profile #$output '#$inputs
966 #:manifest '#$(manifest->gexp manifest)
967 #:search-paths search-paths))))
79ee406d
LC
968
969 (gexp->derivation "profile" builder
40d71e44 970 #:system system
a7a4fd9a
LC
971
972 ;; Not worth offloading.
973 #:local-build? #t
974
975 ;; Disable substitution because it would trigger a
976 ;; connection to the substitute server, which is likely
977 ;; to have no substitute to offer.
978 #:substitutable? #f)))
cc4ecc2d
LC
979
980(define (profile-regexp profile)
981 "Return a regular expression that matches PROFILE's name and number."
982 (make-regexp (string-append "^" (regexp-quote (basename profile))
983 "-([0-9]+)")))
984
985(define (generation-number profile)
986 "Return PROFILE's number or 0. An absolute file name must be used."
987 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
988 (basename (readlink profile))))
989 (compose string->number (cut match:substring <> 1)))
990 0))
991
992(define (generation-numbers profile)
993 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
994former profiles were found."
995 (define* (scandir name #:optional (select? (const #t))
996 (entry<? (@ (ice-9 i18n) string-locale<?)))
997 ;; XXX: Bug-fix version introduced in Guile v2.0.6-62-g139ce19.
998 (define (enter? dir stat result)
999 (and stat (string=? dir name)))
1000
1001 (define (visit basename result)
1002 (if (select? basename)
1003 (cons basename result)
1004 result))
1005
1006 (define (leaf name stat result)
1007 (and result
1008 (visit (basename name) result)))
1009
1010 (define (down name stat result)
1011 (visit "." '()))
1012
1013 (define (up name stat result)
1014 (visit ".." result))
1015
1016 (define (skip name stat result)
1017 ;; All the sub-directories are skipped.
1018 (visit (basename name) result))
1019
1020 (define (error name* stat errno result)
1021 (if (string=? name name*) ; top-level NAME is unreadable
1022 result
1023 (visit (basename name*) result)))
1024
1025 (and=> (file-system-fold enter? leaf down up skip error #f name lstat)
1026 (lambda (files)
1027 (sort files entry<?))))
1028
1029 (match (scandir (dirname profile)
1030 (cute regexp-exec (profile-regexp profile) <>))
1031 (#f ; no profile directory
1032 '(0))
1033 (() ; no profiles
1034 '(0))
1035 ((profiles ...) ; former profiles around
1036 (sort (map (compose string->number
1037 (cut match:substring <> 1)
1038 (cute regexp-exec (profile-regexp profile) <>))
1039 profiles)
1040 <))))
1041
f452e8ff
AK
1042(define (profile-generations profile)
1043 "Return a list of PROFILE's generations."
1044 (let ((generations (generation-numbers profile)))
1045 (if (equal? generations '(0))
1046 '()
1047 generations)))
1048
9008debc
CM
1049(define (relative-generation-spec->number profile spec)
1050 "Return PROFILE's generation specified by SPEC, which is a string. The SPEC
1051may be a N, -N, or +N, where N is a number. If the spec is N, then the number
1052returned is N. If it is -N, then the number returned is the profile's current
1053generation number minus N. If it is +N, then the number returned is the
1054profile's current generation number plus N. Return #f if there is no such
1055generation."
1056 (let ((number (string->number spec)))
1057 (and number
1058 (case (string-ref spec 0)
1059 ((#\+ #\-)
1060 (relative-generation profile number))
1061 (else (if (memv number (profile-generations profile))
1062 number
1063 #f))))))
1064
1065
3ccde087
AK
1066(define* (relative-generation profile shift #:optional
1067 (current (generation-number profile)))
1068 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
1069SHIFT is a positive or negative number.
1070Return #f if there is no such generation."
1071 (let* ((abs-shift (abs shift))
1072 (numbers (profile-generations profile))
1073 (from-current (memq current
1074 (if (negative? shift)
1075 (reverse numbers)
1076 numbers))))
1077 (and from-current
1078 (< abs-shift (length from-current))
1079 (list-ref from-current abs-shift))))
1080
1081(define* (previous-generation-number profile #:optional
1082 (number (generation-number profile)))
cc4ecc2d
LC
1083 "Return the number of the generation before generation NUMBER of
1084PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
1085case when generations have been deleted (there are \"holes\")."
3ccde087
AK
1086 (or (relative-generation profile -1 number)
1087 0))
cc4ecc2d
LC
1088
1089(define (generation-file-name profile generation)
1090 "Return the file name for PROFILE's GENERATION."
1091 (format #f "~a-~a-link" profile generation))
1092
1093(define (generation-time profile number)
1094 "Return the creation time of a generation in the UTC format."
1095 (make-time time-utc 0
1096 (stat:ctime (stat (generation-file-name profile number)))))
1097
06d45f45
LC
1098(define (link-to-empty-profile store generation)
1099 "Link GENERATION, a string, to the empty profile. An error is raised if
1100that fails."
1101 (let* ((drv (run-with-store store
1102 (profile-derivation (manifest '()))))
1103 (prof (derivation->output-path drv "out")))
1104 (build-derivations store (list drv))
1105 (switch-symlinks generation prof)))
1106
1107(define (switch-to-generation profile number)
1108 "Atomically switch PROFILE to the generation NUMBER. Return the number of
1109the generation that was current before switching."
1110 (let ((current (generation-number profile))
1111 (generation (generation-file-name profile number)))
1112 (cond ((not (file-exists? profile))
1113 (raise (condition (&profile-not-found-error
1114 (profile profile)))))
1115 ((not (file-exists? generation))
1116 (raise (condition (&missing-generation-error
1117 (profile profile)
1118 (generation number)))))
1119 (else
1120 (switch-symlinks profile generation)
1121 current))))
1122
1123(define (switch-to-previous-generation profile)
1124 "Atomically switch PROFILE to the previous generation. Return the former
1125generation number and the current one."
1126 (let ((previous (previous-generation-number profile)))
1127 (values (switch-to-generation profile previous)
1128 previous)))
1129
1130(define (roll-back store profile)
1131 "Roll back to the previous generation of PROFILE. Return the number of the
1132generation that was current before switching and the new generation number."
1133 (let* ((number (generation-number profile))
1134 (previous-number (previous-generation-number profile number))
1135 (previous-generation (generation-file-name profile previous-number)))
1136 (cond ((not (file-exists? profile)) ;invalid profile
1137 (raise (condition (&profile-not-found-error
1138 (profile profile)))))
1139 ((zero? number) ;empty profile
1140 (values number number))
1141 ((or (zero? previous-number) ;going to emptiness
1142 (not (file-exists? previous-generation)))
1143 (link-to-empty-profile store previous-generation)
1144 (switch-to-previous-generation profile))
1145 (else ;anything else
1146 (switch-to-previous-generation profile)))))
1147
1148(define (delete-generation store profile number)
1149 "Delete generation with NUMBER from PROFILE. Return the file name of the
1150generation that has been deleted, or #f if nothing was done (for instance
1151because the NUMBER is zero.)"
1152 (define (delete-and-return)
1153 (let ((generation (generation-file-name profile number)))
1154 (delete-file generation)
1155 generation))
1156
1157 (let* ((current-number (generation-number profile))
1158 (previous-number (previous-generation-number profile number))
1159 (previous-generation (generation-file-name profile previous-number)))
1160 (cond ((zero? number) #f) ;do not delete generation 0
1161 ((and (= number current-number)
1162 (not (file-exists? previous-generation)))
1163 (link-to-empty-profile store previous-generation)
1164 (switch-to-previous-generation profile)
1165 (delete-and-return))
1166 ((= number current-number)
1167 (roll-back store profile)
1168 (delete-and-return))
1169 (else
1170 (delete-and-return)))))
1171
cc4ecc2d 1172;;; profiles.scm ends here