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