search-paths: 'evaluate-search-paths' now returns spec/value pairs.
[jackhill/guix/guix.git] / guix / profiles.scm
CommitLineData
cc4ecc2d 1;;; GNU Guix --- Functional package management for GNU
e87f0591 2;;; Copyright © 2013, 2014, 2015 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>
cc4ecc2d
LC
6;;;
7;;; This file is part of GNU Guix.
8;;;
9;;; GNU Guix is free software; you can redistribute it and/or modify it
10;;; under the terms of the GNU General Public License as published by
11;;; the Free Software Foundation; either version 3 of the License, or (at
12;;; your option) any later version.
13;;;
14;;; GNU Guix is distributed in the hope that it will be useful, but
15;;; WITHOUT ANY WARRANTY; without even the implied warranty of
16;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;;; GNU General Public License for more details.
18;;;
19;;; You should have received a copy of the GNU General Public License
20;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
21
22(define-module (guix profiles)
23 #:use-module (guix utils)
24 #:use-module (guix records)
cc4ecc2d 25 #:use-module (guix packages)
e89431bf
LC
26 #:use-module (guix derivations)
27 #:use-module (guix search-paths)
a54c94a4 28 #:use-module (guix gexp)
79ee406d 29 #:use-module (guix monads)
e87f0591 30 #:use-module (guix store)
cc4ecc2d
LC
31 #:use-module (ice-9 match)
32 #:use-module (ice-9 regex)
33 #:use-module (ice-9 ftw)
343745c8 34 #:use-module (ice-9 format)
cc4ecc2d
LC
35 #:use-module (srfi srfi-1)
36 #:use-module (srfi srfi-9)
79601521 37 #:use-module (srfi srfi-11)
cc4ecc2d
LC
38 #:use-module (srfi srfi-19)
39 #:use-module (srfi srfi-26)
c0c018f1
AK
40 #:use-module (srfi srfi-34)
41 #:use-module (srfi srfi-35)
42 #:export (&profile-error
43 profile-error?
44 profile-error-profile
45 &profile-not-found-error
46 profile-not-found-error?
47 &missing-generation-error
48 missing-generation-error?
49 missing-generation-error-generation
50
51 manifest make-manifest
cc4ecc2d
LC
52 manifest?
53 manifest-entries
54
55 <manifest-entry> ; FIXME: eventually make it internal
56 manifest-entry
57 manifest-entry?
58 manifest-entry-name
59 manifest-entry-version
60 manifest-entry-output
a54c94a4 61 manifest-entry-item
cc4ecc2d 62 manifest-entry-dependencies
dedb17ad 63 manifest-entry-search-paths
cc4ecc2d 64
a2078770
LC
65 manifest-pattern
66 manifest-pattern?
67
cc4ecc2d 68 manifest-remove
f7554030 69 manifest-add
ef8993e2 70 manifest-lookup
cc4ecc2d 71 manifest-installed?
a2078770 72 manifest-matching-entries
cc4ecc2d 73
343745c8
AK
74 manifest-transaction
75 manifest-transaction?
76 manifest-transaction-install
77 manifest-transaction-remove
78 manifest-perform-transaction
79601521 79 manifest-transaction-effects
343745c8 80
cc4ecc2d 81 profile-manifest
462f5cca 82 package->manifest-entry
aa46a028 83 %default-profile-hooks
cc4ecc2d
LC
84 profile-derivation
85 generation-number
86 generation-numbers
f452e8ff 87 profile-generations
3ccde087 88 relative-generation
cc4ecc2d
LC
89 previous-generation-number
90 generation-time
91 generation-file-name))
92
93;;; Commentary:
94;;;
95;;; Tools to create and manipulate profiles---i.e., the representation of a
96;;; set of installed packages.
97;;;
98;;; Code:
99
100\f
c0c018f1
AK
101;;;
102;;; Condition types.
103;;;
104
105(define-condition-type &profile-error &error
106 profile-error?
107 (profile profile-error-profile))
108
109(define-condition-type &profile-not-found-error &profile-error
110 profile-not-found-error?)
111
112(define-condition-type &missing-generation-error &profile-error
113 missing-generation-error?
114 (generation missing-generation-error-generation))
115
116\f
cc4ecc2d
LC
117;;;
118;;; Manifests.
119;;;
120
121(define-record-type <manifest>
122 (manifest entries)
123 manifest?
124 (entries manifest-entries)) ; list of <manifest-entry>
125
126;; Convenient alias, to avoid name clashes.
127(define make-manifest manifest)
128
129(define-record-type* <manifest-entry> manifest-entry
130 make-manifest-entry
131 manifest-entry?
132 (name manifest-entry-name) ; string
133 (version manifest-entry-version) ; string
134 (output manifest-entry-output ; string
135 (default "out"))
a54c94a4 136 (item manifest-entry-item) ; package | store path
4ca0b410 137 (dependencies manifest-entry-dependencies ; (store path | package)*
dedb17ad
LC
138 (default '()))
139 (search-paths manifest-entry-search-paths ; search-path-specification*
4ca0b410 140 (default '())))
cc4ecc2d 141
a2078770
LC
142(define-record-type* <manifest-pattern> manifest-pattern
143 make-manifest-pattern
144 manifest-pattern?
145 (name manifest-pattern-name) ; string
146 (version manifest-pattern-version ; string | #f
147 (default #f))
148 (output manifest-pattern-output ; string | #f
149 (default "out")))
150
cc4ecc2d
LC
151(define (profile-manifest profile)
152 "Return the PROFILE's manifest."
153 (let ((file (string-append profile "/manifest")))
154 (if (file-exists? file)
155 (call-with-input-file file read-manifest)
156 (manifest '()))))
157
462f5cca
LC
158(define* (package->manifest-entry package #:optional output)
159 "Return a manifest entry for the OUTPUT of package PACKAGE. When OUTPUT is
160omitted or #f, use the first output of PACKAGE."
161 (let ((deps (map (match-lambda
162 ((label package)
b4a4bec0 163 (gexp-input package))
462f5cca 164 ((label package output)
b4a4bec0 165 (gexp-input package output)))
462f5cca
LC
166 (package-transitive-propagated-inputs package))))
167 (manifest-entry
168 (name (package-name package))
169 (version (package-version package))
170 (output (or output (car (package-outputs package))))
171 (item package)
dedb17ad
LC
172 (dependencies (delete-duplicates deps))
173 (search-paths (package-native-search-paths package)))))
462f5cca 174
a54c94a4
LC
175(define (manifest->gexp manifest)
176 "Return a representation of MANIFEST as a gexp."
177 (define (entry->gexp entry)
cc4ecc2d 178 (match entry
dedb17ad
LC
179 (($ <manifest-entry> name version output (? string? path)
180 (deps ...) (search-paths ...))
181 #~(#$name #$version #$output #$path
182 (propagated-inputs #$deps)
183 (search-paths #$(map search-path-specification->sexp
184 search-paths))))
185 (($ <manifest-entry> name version output (? package? package)
186 (deps ...) (search-paths ...))
a54c94a4 187 #~(#$name #$version #$output
dedb17ad
LC
188 (ungexp package (or output "out"))
189 (propagated-inputs #$deps)
190 (search-paths #$(map search-path-specification->sexp
191 search-paths))))))
cc4ecc2d
LC
192
193 (match manifest
194 (($ <manifest> (entries ...))
dedb17ad 195 #~(manifest (version 2)
a54c94a4 196 (packages #$(map entry->gexp entries))))))
cc4ecc2d 197
dedb17ad
LC
198(define (find-package name version)
199 "Return a package from the distro matching NAME and possibly VERSION. This
200procedure is here for backward-compatibility and will eventually vanish."
201 (define find-best-packages-by-name ;break abstractions
202 (module-ref (resolve-interface '(gnu packages))
203 'find-best-packages-by-name))
204
205 ;; Use 'find-best-packages-by-name' and not 'find-packages-by-name'; the
206 ;; former traverses the module tree only once and then allows for efficient
207 ;; access via a vhash.
208 (match (find-best-packages-by-name name version)
209 ((p _ ...) p)
210 (_
211 (match (find-best-packages-by-name name #f)
212 ((p _ ...) p)
213 (_ #f)))))
214
cc4ecc2d
LC
215(define (sexp->manifest sexp)
216 "Parse SEXP as a manifest."
dedb17ad
LC
217 (define (infer-search-paths name version)
218 ;; Infer the search path specifications for NAME-VERSION by looking up a
219 ;; same-named package in the distro. Useful for the old manifest formats
220 ;; that did not store search path info.
221 (let ((package (find-package name version)))
222 (if package
223 (package-native-search-paths package)
224 '())))
225
cc4ecc2d
LC
226 (match sexp
227 (('manifest ('version 0)
228 ('packages ((name version output path) ...)))
229 (manifest
230 (map (lambda (name version output path)
231 (manifest-entry
232 (name name)
233 (version version)
234 (output output)
dedb17ad
LC
235 (item path)
236 (search-paths (infer-search-paths name version))))
cc4ecc2d
LC
237 name version output path)))
238
239 ;; Version 1 adds a list of propagated inputs to the
240 ;; name/version/output/path tuples.
241 (('manifest ('version 1)
242 ('packages ((name version output path deps) ...)))
243 (manifest
244 (map (lambda (name version output path deps)
d34736c5
LC
245 ;; Up to Guix 0.7 included, dependencies were listed as ("gmp"
246 ;; "/gnu/store/...-gmp") for instance. Discard the 'label' in
247 ;; such lists.
248 (let ((deps (match deps
249 (((labels directories) ...)
250 directories)
251 ((directories ...)
252 directories))))
253 (manifest-entry
254 (name name)
255 (version version)
256 (output output)
257 (item path)
dedb17ad
LC
258 (dependencies deps)
259 (search-paths (infer-search-paths name version)))))
cc4ecc2d
LC
260 name version output path deps)))
261
dedb17ad
LC
262 ;; Version 2 adds search paths and is slightly more verbose.
263 (('manifest ('version 2 minor-version ...)
264 ('packages ((name version output path
265 ('propagated-inputs deps)
266 ('search-paths search-paths)
267 extra-stuff ...)
268 ...)))
269 (manifest
270 (map (lambda (name version output path deps search-paths)
271 (manifest-entry
272 (name name)
273 (version version)
274 (output output)
275 (item path)
276 (dependencies deps)
277 (search-paths (map sexp->search-path-specification
278 search-paths))))
279 name version output path deps search-paths)))
cc4ecc2d 280 (_
dedb17ad 281 (error "unsupported manifest format" sexp))))
cc4ecc2d
LC
282
283(define (read-manifest port)
284 "Return the packages listed in MANIFEST."
285 (sexp->manifest (read port)))
286
a2078770
LC
287(define (entry-predicate pattern)
288 "Return a procedure that returns #t when passed a manifest entry that
289matches NAME/OUTPUT/VERSION. OUTPUT and VERSION may be #f, in which case they
290are ignored."
291 (match pattern
292 (($ <manifest-pattern> name version output)
293 (match-lambda
294 (($ <manifest-entry> entry-name entry-version entry-output)
295 (and (string=? entry-name name)
296 (or (not entry-output) (not output)
297 (string=? entry-output output))
298 (or (not version)
299 (string=? entry-version version))))))))
300
301(define (manifest-remove manifest patterns)
302 "Remove entries for each of PATTERNS from MANIFEST. Each item in PATTERNS
303must be a manifest-pattern."
304 (define (remove-entry pattern lst)
305 (remove (entry-predicate pattern) lst))
306
307 (make-manifest (fold remove-entry
cc4ecc2d 308 (manifest-entries manifest)
a2078770 309 patterns)))
cc4ecc2d 310
f7554030
AK
311(define (manifest-add manifest entries)
312 "Add a list of manifest ENTRIES to MANIFEST and return new manifest.
313Remove MANIFEST entries that have the same name and output as ENTRIES."
314 (define (same-entry? entry name output)
315 (match entry
316 (($ <manifest-entry> entry-name _ entry-output _ ...)
317 (and (equal? name entry-name)
318 (equal? output entry-output)))))
319
320 (make-manifest
321 (append entries
322 (fold (lambda (entry result)
323 (match entry
324 (($ <manifest-entry> name _ out _ ...)
325 (filter (negate (cut same-entry? <> name out))
326 result))))
327 (manifest-entries manifest)
328 entries))))
329
ef8993e2
LC
330(define (manifest-lookup manifest pattern)
331 "Return the first item of MANIFEST that matches PATTERN, or #f if there is
332no match.."
333 (find (entry-predicate pattern)
334 (manifest-entries manifest)))
335
a2078770
LC
336(define (manifest-installed? manifest pattern)
337 "Return #t if MANIFEST has an entry matching PATTERN (a manifest-pattern),
338#f otherwise."
ef8993e2 339 (->bool (manifest-lookup manifest pattern)))
cc4ecc2d 340
a2078770
LC
341(define (manifest-matching-entries manifest patterns)
342 "Return all the entries of MANIFEST that match one of the PATTERNS."
343 (define predicates
344 (map entry-predicate patterns))
345
346 (define (matches? entry)
347 (any (lambda (pred)
348 (pred entry))
349 predicates))
350
351 (filter matches? (manifest-entries manifest)))
352
cc4ecc2d 353\f
343745c8
AK
354;;;
355;;; Manifest transactions.
356;;;
357
358(define-record-type* <manifest-transaction> manifest-transaction
359 make-manifest-transaction
360 manifest-transaction?
361 (install manifest-transaction-install ; list of <manifest-entry>
362 (default '()))
363 (remove manifest-transaction-remove ; list of <manifest-pattern>
364 (default '())))
365
79601521 366(define (manifest-transaction-effects manifest transaction)
46b23e1a
LC
367 "Compute the effect of applying TRANSACTION to MANIFEST. Return 4 values:
368the list of packages that would be removed, installed, upgraded, or downgraded
369when applying TRANSACTION to MANIFEST. Upgrades are represented as pairs
370where the head is the entry being upgraded and the tail is the entry that will
371replace it."
79601521
LC
372 (define (manifest-entry->pattern entry)
373 (manifest-pattern
374 (name (manifest-entry-name entry))
375 (output (manifest-entry-output entry))))
376
46b23e1a
LC
377 (let loop ((input (manifest-transaction-install transaction))
378 (install '())
379 (upgrade '())
380 (downgrade '()))
79601521
LC
381 (match input
382 (()
383 (let ((remove (manifest-transaction-remove transaction)))
384 (values (manifest-matching-entries manifest remove)
46b23e1a 385 (reverse install) (reverse upgrade) (reverse downgrade))))
79601521
LC
386 ((entry rest ...)
387 ;; Check whether installing ENTRY corresponds to the installation of a
388 ;; new package or to an upgrade.
389
390 ;; XXX: When the exact same output directory is installed, we're not
391 ;; really upgrading anything. Add a check for that case.
392 (let* ((pattern (manifest-entry->pattern entry))
46b23e1a
LC
393 (previous (manifest-lookup manifest pattern))
394 (newer? (and previous
3bea13bb
LC
395 (version>=? (manifest-entry-version entry)
396 (manifest-entry-version previous)))))
79601521 397 (loop rest
ef8993e2 398 (if previous install (cons entry install))
46b23e1a 399 (if (and previous newer?)
ef8993e2 400 (alist-cons previous entry upgrade)
46b23e1a
LC
401 upgrade)
402 (if (and previous (not newer?))
403 (alist-cons previous entry downgrade)
404 downgrade)))))))
79601521 405
343745c8
AK
406(define (manifest-perform-transaction manifest transaction)
407 "Perform TRANSACTION on MANIFEST and return new manifest."
408 (let ((install (manifest-transaction-install transaction))
409 (remove (manifest-transaction-remove transaction)))
410 (manifest-add (manifest-remove manifest remove)
411 install)))
412
343745c8 413\f
cc4ecc2d
LC
414;;;
415;;; Profiles.
416;;;
417
79ee406d 418(define (manifest-inputs manifest)
b4a4bec0 419 "Return a list of <gexp-input> objects for MANIFEST."
79ee406d 420 (append-map (match-lambda
b4a4bec0
LC
421 (($ <manifest-entry> name version output thing deps)
422 ;; THING may be a package or a file name. In the latter case,
423 ;; assume it's already valid. Ditto for DEPS.
424 (cons (gexp-input thing output) deps)))
79ee406d
LC
425 (manifest-entries manifest)))
426
427(define (info-dir-file manifest)
428 "Return a derivation that builds the 'dir' file for all the entries of
429MANIFEST."
2f0556ae
LC
430 (define texinfo ;lazy reference
431 (module-ref (resolve-interface '(gnu packages texinfo)) 'texinfo))
432 (define gzip ;lazy reference
433 (module-ref (resolve-interface '(gnu packages compression)) 'gzip))
434
79ee406d 435 (define build
a54c94a4 436 #~(begin
79ee406d
LC
437 (use-modules (guix build utils)
438 (srfi srfi-1) (srfi srfi-26)
439 (ice-9 ftw))
440
441 (define (info-file? file)
442 (or (string-suffix? ".info" file)
443 (string-suffix? ".info.gz" file)))
444
445 (define (info-files top)
446 (let ((infodir (string-append top "/share/info")))
447 (map (cut string-append infodir "/" <>)
c2815c0f 448 (or (scandir infodir info-file?) '()))))
79ee406d
LC
449
450 (define (install-info info)
2f0556ae 451 (setenv "PATH" (string-append #+gzip "/bin")) ;for info.gz files
79ee406d
LC
452 (zero?
453 (system* (string-append #+texinfo "/bin/install-info")
454 info (string-append #$output "/share/info/dir"))))
455
456 (mkdir-p (string-append #$output "/share/info"))
457 (every install-info
458 (append-map info-files
459 '#$(manifest-inputs manifest)))))
460
aa46a028
LC
461 (gexp->derivation "info-dir" build
462 #:modules '((guix build utils))))
79ee406d 463
042bc828
FB
464(define (ghc-package-cache-file manifest)
465 "Return a derivation that builds the GHC 'package.cache' file for all the
aa46a028 466entries of MANIFEST, or #f if MANIFEST does not have any GHC packages."
042bc828
FB
467 (define ghc ;lazy reference
468 (module-ref (resolve-interface '(gnu packages haskell)) 'ghc))
469
470 (define build
471 #~(begin
472 (use-modules (guix build utils)
473 (srfi srfi-1) (srfi srfi-26)
474 (ice-9 ftw))
475
476 (define ghc-name-version
477 (let* ((base (basename #+ghc)))
478 (string-drop base
479 (+ 1 (string-index base #\-)))))
480
481 (define db-subdir
482 (string-append "lib/" ghc-name-version "/package.conf.d"))
483
484 (define db-dir
485 (string-append #$output "/" db-subdir))
486
487 (define (conf-files top)
488 (find-files (string-append top "/" db-subdir) "\\.conf$"))
489
490 (define (copy-conf-file conf)
491 (let ((base (basename conf)))
492 (copy-file conf (string-append db-dir "/" base))))
493
494 (system* (string-append #+ghc "/bin/ghc-pkg") "init" db-dir)
495 (for-each copy-conf-file
496 (append-map conf-files
497 '#$(manifest-inputs manifest)))
498 (let ((success
499 (zero?
500 (system* (string-append #+ghc "/bin/ghc-pkg") "recache"
501 (string-append "--package-db=" db-dir)))))
502 (for-each delete-file (find-files db-dir "\\.conf$"))
503 success)))
504
505 ;; Don't depend on GHC when there's nothing to do.
aa46a028
LC
506 (and (any (cut string-prefix? "ghc" <>)
507 (map manifest-entry-name (manifest-entries manifest)))
508 (gexp->derivation "ghc-package-cache" build
509 #:modules '((guix build utils))
510 #:local-build? #t)))
042bc828 511
536c3ee4
MW
512(define (ca-certificate-bundle manifest)
513 "Return a derivation that builds a single-file bundle containing the CA
514certificates in the /etc/ssl/certs sub-directories of the packages in
515MANIFEST. Single-file bundles are required by programs such as Git and Lynx."
516 ;; See <http://lists.gnu.org/archive/html/guix-devel/2015-02/msg00429.html>
517 ;; for a discussion.
518
519 (define glibc-utf8-locales ;lazy reference
520 (module-ref (resolve-interface '(gnu packages base)) 'glibc-utf8-locales))
521
522 (define build
523 #~(begin
524 (use-modules (guix build utils)
525 (rnrs io ports)
526 (srfi srfi-1)
527 (srfi srfi-26)
c568191a
LC
528 (ice-9 ftw)
529 (ice-9 match))
536c3ee4
MW
530
531 (define (pem-file? file)
532 (string-suffix? ".pem" file))
533
534 (define (ca-files top)
535 (let ((cert-dir (string-append top "/etc/ssl/certs")))
536 (map (cut string-append cert-dir "/" <>)
537 (or (scandir cert-dir pem-file?) '()))))
538
539 (define (concatenate-files files result)
540 "Make RESULT the concatenation of all of FILES."
541 (define (dump file port)
542 (display (call-with-input-file file get-string-all)
543 port)
544 (newline port)) ;required, see <https://bugs.debian.org/635570>
545
546 (call-with-output-file result
547 (lambda (port)
548 (for-each (cut dump <> port) files))))
549
550 ;; Some file names in the NSS certificates are UTF-8 encoded so
551 ;; install a UTF-8 locale.
552 (setenv "LOCPATH" (string-append #+glibc-utf8-locales "/lib/locale"))
553 (setlocale LC_ALL "en_US.UTF-8")
554
c568191a
LC
555 (match (append-map ca-files '#$(manifest-inputs manifest))
556 (()
557 ;; Since there are no CA files, just create an empty directory. Do
558 ;; not create the etc/ssl/certs sub-directory, since that would
559 ;; wrongfully lead to a message about 'SSL_CERT_DIR' needing to be
560 ;; defined.
561 (mkdir #$output)
562 #t)
563 ((ca-files ...)
564 (let ((result (string-append #$output "/etc/ssl/certs")))
565 (mkdir-p result)
566 (concatenate-files ca-files
567 (string-append result
568 "/ca-certificates.crt"))
569 #t)))))
536c3ee4 570
aa46a028
LC
571 (gexp->derivation "ca-certificate-bundle" build
572 #:modules '((guix build utils))
573 #:local-build? #t))
574
575(define %default-profile-hooks
576 ;; This is the list of derivation-returning procedures that are called by
577 ;; default when making a non-empty profile.
578 (list info-dir-file
579 ghc-package-cache-file
580 ca-certificate-bundle))
536c3ee4
MW
581
582(define* (profile-derivation manifest
583 #:key
aa46a028 584 (hooks %default-profile-hooks))
79ee406d 585 "Return a derivation that builds a profile (aka. 'user environment') with
aa46a028
LC
586the given MANIFEST. The profile includes additional derivations returned by
587the monadic procedures listed in HOOKS--such as an Info 'dir' file, etc."
588 (mlet %store-monad ((extras (if (null? (manifest-entries manifest))
589 (return '())
590 (sequence %store-monad
591 (filter-map (lambda (hook)
592 (hook manifest))
593 hooks)))))
79ee406d 594 (define inputs
aa46a028 595 (append (map gexp-input extras)
536c3ee4 596 (manifest-inputs manifest)))
79ee406d
LC
597
598 (define builder
599 #~(begin
600 (use-modules (ice-9 pretty-print)
601 (guix build union))
602
603 (setvbuf (current-output-port) _IOLBF)
604 (setvbuf (current-error-port) _IOLBF)
605
606 (union-build #$output '#$inputs
607 #:log-port (%make-void-port "w"))
608 (call-with-output-file (string-append #$output "/manifest")
609 (lambda (p)
610 (pretty-print '#$(manifest->gexp manifest) p)))))
611
612 (gexp->derivation "profile" builder
613 #:modules '((guix build union))
614 #:local-build? #t)))
cc4ecc2d
LC
615
616(define (profile-regexp profile)
617 "Return a regular expression that matches PROFILE's name and number."
618 (make-regexp (string-append "^" (regexp-quote (basename profile))
619 "-([0-9]+)")))
620
621(define (generation-number profile)
622 "Return PROFILE's number or 0. An absolute file name must be used."
623 (or (and=> (false-if-exception (regexp-exec (profile-regexp profile)
624 (basename (readlink profile))))
625 (compose string->number (cut match:substring <> 1)))
626 0))
627
628(define (generation-numbers profile)
629 "Return the sorted list of generation numbers of PROFILE, or '(0) if no
630former profiles were found."
631 (define* (scandir name #:optional (select? (const #t))
632 (entry<? (@ (ice-9 i18n) string-locale<?)))
633 ;; XXX: Bug-fix version introduced in Guile v2.0.6-62-g139ce19.
634 (define (enter? dir stat result)
635 (and stat (string=? dir name)))
636
637 (define (visit basename result)
638 (if (select? basename)
639 (cons basename result)
640 result))
641
642 (define (leaf name stat result)
643 (and result
644 (visit (basename name) result)))
645
646 (define (down name stat result)
647 (visit "." '()))
648
649 (define (up name stat result)
650 (visit ".." result))
651
652 (define (skip name stat result)
653 ;; All the sub-directories are skipped.
654 (visit (basename name) result))
655
656 (define (error name* stat errno result)
657 (if (string=? name name*) ; top-level NAME is unreadable
658 result
659 (visit (basename name*) result)))
660
661 (and=> (file-system-fold enter? leaf down up skip error #f name lstat)
662 (lambda (files)
663 (sort files entry<?))))
664
665 (match (scandir (dirname profile)
666 (cute regexp-exec (profile-regexp profile) <>))
667 (#f ; no profile directory
668 '(0))
669 (() ; no profiles
670 '(0))
671 ((profiles ...) ; former profiles around
672 (sort (map (compose string->number
673 (cut match:substring <> 1)
674 (cute regexp-exec (profile-regexp profile) <>))
675 profiles)
676 <))))
677
f452e8ff
AK
678(define (profile-generations profile)
679 "Return a list of PROFILE's generations."
680 (let ((generations (generation-numbers profile)))
681 (if (equal? generations '(0))
682 '()
683 generations)))
684
3ccde087
AK
685(define* (relative-generation profile shift #:optional
686 (current (generation-number profile)))
687 "Return PROFILE's generation shifted from the CURRENT generation by SHIFT.
688SHIFT is a positive or negative number.
689Return #f if there is no such generation."
690 (let* ((abs-shift (abs shift))
691 (numbers (profile-generations profile))
692 (from-current (memq current
693 (if (negative? shift)
694 (reverse numbers)
695 numbers))))
696 (and from-current
697 (< abs-shift (length from-current))
698 (list-ref from-current abs-shift))))
699
700(define* (previous-generation-number profile #:optional
701 (number (generation-number profile)))
cc4ecc2d
LC
702 "Return the number of the generation before generation NUMBER of
703PROFILE, or 0 if none exists. It could be NUMBER - 1, but it's not the
704case when generations have been deleted (there are \"holes\")."
3ccde087
AK
705 (or (relative-generation profile -1 number)
706 0))
cc4ecc2d
LC
707
708(define (generation-file-name profile generation)
709 "Return the file name for PROFILE's GENERATION."
710 (format #f "~a-~a-link" profile generation))
711
712(define (generation-time profile number)
713 "Return the creation time of a generation in the UTC format."
714 (make-time time-utc 0
715 (stat:ctime (stat (generation-file-name profile number)))))
716
717;;; profiles.scm ends here