gnu: jsoncpp: Update to 1.9.1.
[guix.git] / gnu / installer / steps.scm
blob4e90f32f953bd69eb94e442d29c4ee9f8f1617aa
1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2018, 2019 Mathieu Othacehe <m.othacehe@gmail.com>
3 ;;;
4 ;;; This file is part of GNU Guix.
5 ;;;
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
10 ;;;
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ;;; GNU General Public License for more details.
15 ;;;
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
19 (define-module (gnu installer steps)
20   #:use-module (guix records)
21   #:use-module (guix build utils)
22   #:use-module (ice-9 match)
23   #:use-module (ice-9 pretty-print)
24   #:use-module (srfi srfi-1)
25   #:use-module (srfi srfi-34)
26   #:use-module (srfi srfi-35)
27   #:use-module (rnrs io ports)
28   #:export (&installer-step-abort
29             installer-step-abort?
31             &installer-step-break
32             installer-step-break?
34             <installer-step>
35             installer-step
36             make-installer-step
37             installer-step?
38             installer-step-id
39             installer-step-description
40             installer-step-compute
41             installer-step-configuration-formatter
43             run-installer-steps
44             find-step-by-id
45             result->step-ids
46             result-step
47             result-step-done?
49             %installer-configuration-file
50             %installer-target-dir
51             %configuration-file-width
52             format-configuration
53             configuration->file))
55 ;; This condition may be raised to abort the current step.
56 (define-condition-type &installer-step-abort &condition
57   installer-step-abort?)
59 ;; This condition may be raised to break out from the steps execution.
60 (define-condition-type &installer-step-break &condition
61   installer-step-break?)
63 ;; An installer-step record is basically an id associated to a compute
64 ;; procedure. The COMPUTE procedure takes exactly one argument, an association
65 ;; list containing the results of previously executed installer-steps (see
66 ;; RUN-INSTALLER-STEPS description). The value returned by the COMPUTE
67 ;; procedure will be stored in the results list passed to the next
68 ;; installer-step and so on.
69 (define-record-type* <installer-step>
70   installer-step make-installer-step
71   installer-step?
72   (id                         installer-step-id) ;symbol
73   (description                installer-step-description ;string
74                               (default #f)
76                               ;; Make it thunked so that 'G_' is called at the
77                               ;; right time, as opposed to being called once
78                               ;; when the installer starts.
79                               (thunked))
80   (compute                    installer-step-compute) ;procedure
81   (configuration-formatter    installer-step-configuration-formatter ;procedure
82                               (default #f)))
84 (define* (run-installer-steps #:key
85                               steps
86                               (rewind-strategy 'previous)
87                               (menu-proc (const #f)))
88   "Run the COMPUTE procedure of all <installer-step> records in STEPS
89 sequencially. If the &installer-step-abort condition is raised, fallback to a
90 previous install-step, accordingly to the specified REWIND-STRATEGY.
92 REWIND-STRATEGY possible values are 'previous, 'menu and 'start.  If 'previous
93 is selected, the execution will resume at the previous installer-step. If
94 'menu is selected, the MENU-PROC procedure will be called. Its return value
95 has to be an installer-step ID to jump to. The ID has to be the one of a
96 previously executed step. It is impossible to jump forward. Finally if 'start
97 is selected, the execution will resume at the first installer-step.
99 The result of every COMPUTE procedures is stored in an association list, under
100 the form:
102                 '((STEP-ID . COMPUTE-RESULT) ...)
104 where STEP-ID is the ID field of the installer-step and COMPUTE-RESULT the
105 result of the associated COMPUTE procedure. This result association list is
106 passed as argument of every COMPUTE procedure. It is finally returned when the
107 computation is over.
109 If the &installer-step-break condition is raised, stop the computation and
110 return the accumalated result so far."
111   (define (pop-result list)
112     (cdr list))
114   (define (first-step? steps step)
115     (match steps
116       ((first-step . rest-steps)
117        (equal? first-step step))))
119   (define* (skip-to-step step result
120                          #:key todo-steps done-steps)
121     (match todo-steps
122       ((todo . rest-todo)
123        (let ((found? (eq? (installer-step-id todo)
124                           (installer-step-id step))))
125          (cond
126           (found?
127            (run result
128                 #:todo-steps todo-steps
129                 #:done-steps done-steps))
130           ((and (not found?)
131                 (null? done-steps))
132            (error (format #f "Step ~a not found" (installer-step-id step))))
133           (else
134            (match done-steps
135              ((prev-done ... last-done)
136               (skip-to-step step (pop-result result)
137                             #:todo-steps (cons last-done todo-steps)
138                             #:done-steps prev-done)))))))))
140   (define* (run result #:key todo-steps done-steps)
141     (match todo-steps
142       (() (reverse result))
143       ((step . rest-steps)
144        (guard (c ((installer-step-abort? c)
145                   (case rewind-strategy
146                     ((previous)
147                      (match done-steps
148                        (()
149                         ;; We cannot go previous the first step. So re-raise
150                         ;; the exception. It might be useful in the case of
151                         ;; nested run-installer-steps. Abort to 'raise-above
152                         ;; prompt to prevent the condition from being catched
153                         ;; by one of the previously installed guard.
154                         (abort-to-prompt 'raise-above c))
155                        ((prev-done ... last-done)
156                         (run (pop-result result)
157                              #:todo-steps (cons last-done todo-steps)
158                              #:done-steps prev-done))))
159                     ((menu)
160                      (let ((goto-step (menu-proc
161                                        (append done-steps (list step)))))
162                        (if (eq? goto-step step)
163                            (run result
164                                 #:todo-steps todo-steps
165                                 #:done-steps done-steps)
166                            (skip-to-step goto-step result
167                                          #:todo-steps todo-steps
168                                          #:done-steps done-steps))))
169                     ((start)
170                      (if (null? done-steps)
171                          ;; Same as above, it makes no sense to jump to start
172                          ;; when we are at the first installer-step. Abort to
173                          ;; 'raise-above prompt to re-raise the condition.
174                          (abort-to-prompt 'raise-above c)
175                          (run '()
176                               #:todo-steps steps
177                               #:done-steps '())))))
178                  ((installer-step-break? c)
179                   (reverse result)))
180          (let* ((id (installer-step-id step))
181                 (compute (installer-step-compute step))
182                 (res (compute result done-steps)))
183            (run (alist-cons id res result)
184                 #:todo-steps rest-steps
185                 #:done-steps (append done-steps (list step))))))))
187   (call-with-prompt 'raise-above
188     (lambda ()
189       (run '()
190            #:todo-steps steps
191            #:done-steps '()))
192     (lambda (k condition)
193       (raise condition))))
195 (define (find-step-by-id steps id)
196   "Find and return the step in STEPS whose id is equal to ID."
197   (find (lambda (step)
198           (eq? (installer-step-id step) id))
199         steps))
201 (define (result-step results step-id)
202   "Return the result of the installer-step specified by STEP-ID in
203 RESULTS."
204   (assoc-ref results step-id))
206 (define (result-step-done? results step-id)
207   "Return #t if the installer-step specified by STEP-ID has a COMPUTE value
208 stored in RESULTS. Return #f otherwise."
209   (and (assoc step-id results) #t))
211 (define %installer-configuration-file (make-parameter "/mnt/etc/config.scm"))
212 (define %installer-target-dir (make-parameter "/mnt"))
213 (define %configuration-file-width (make-parameter 79))
215 (define (format-configuration steps results)
216   "Return the list resulting from the application of the procedure defined in
217 CONFIGURATION-FORMATTER field of <installer-step> on the associated result
218 found in RESULTS."
219   (let ((configuration
220          (append-map
221           (lambda (step)
222             (let* ((step-id (installer-step-id step))
223                    (conf-formatter
224                     (installer-step-configuration-formatter step))
225                    (result-step (result-step results step-id)))
226               (if (and result-step conf-formatter)
227                   (conf-formatter result-step)
228                   '())))
229           steps))
230         (modules '((use-modules (gnu))
231                    (use-service-modules desktop networking ssh xorg))))
232     `(,@modules
233       ()
234       (operating-system ,@configuration))))
236 (define* (configuration->file configuration
237                               #:key (filename (%installer-configuration-file)))
238   "Write the given CONFIGURATION to FILENAME."
239   (mkdir-p (dirname filename))
240   (call-with-output-file filename
241     (lambda (port)
242       (format port ";; This is an operating system configuration generated~%")
243       (format port ";; by the graphical installer.~%")
244       (newline port)
245       (for-each (lambda (part)
246                   (if (null? part)
247                       (newline port)
248                       (pretty-print part port)))
249                 configuration)
250       (flush-output-port port))))