steps.scm 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018 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/>.
  18. (define-module (gnu installer steps)
  19. #:use-module (guix records)
  20. #:use-module (guix build utils)
  21. #:use-module (ice-9 match)
  22. #:use-module (ice-9 pretty-print)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-34)
  25. #:use-module (srfi srfi-35)
  26. #:use-module (rnrs io ports)
  27. #:export (&installer-step-abort
  28. installer-step-abort?
  29. &installer-step-break
  30. installer-step-break?
  31. <installer-step>
  32. installer-step
  33. make-installer-step
  34. installer-step?
  35. installer-step-id
  36. installer-step-description
  37. installer-step-compute
  38. installer-step-configuration-formatter
  39. run-installer-steps
  40. find-step-by-id
  41. result->step-ids
  42. result-step
  43. result-step-done?
  44. %installer-configuration-file
  45. %installer-target-dir
  46. %configuration-file-width
  47. format-configuration
  48. configuration->file))
  49. ;; This condition may be raised to abort the current step.
  50. (define-condition-type &installer-step-abort &condition
  51. installer-step-abort?)
  52. ;; This condition may be raised to break out from the steps execution.
  53. (define-condition-type &installer-step-break &condition
  54. installer-step-break?)
  55. ;; An installer-step record is basically an id associated to a compute
  56. ;; procedure. The COMPUTE procedure takes exactly one argument, an association
  57. ;; list containing the results of previously executed installer-steps (see
  58. ;; RUN-INSTALLER-STEPS description). The value returned by the COMPUTE
  59. ;; procedure will be stored in the results list passed to the next
  60. ;; installer-step and so on.
  61. (define-record-type* <installer-step>
  62. installer-step make-installer-step
  63. installer-step?
  64. (id installer-step-id) ;symbol
  65. (description installer-step-description ;string
  66. (default #f))
  67. (compute installer-step-compute) ;procedure
  68. (configuration-formatter installer-step-configuration-formatter ;procedure
  69. (default #f)))
  70. (define* (run-installer-steps #:key
  71. steps
  72. (rewind-strategy 'previous)
  73. (menu-proc (const #f)))
  74. "Run the COMPUTE procedure of all <installer-step> records in STEPS
  75. sequencially. If the &installer-step-abort condition is raised, fallback to a
  76. previous install-step, accordingly to the specified REWIND-STRATEGY.
  77. REWIND-STRATEGY possible values are 'previous, 'menu and 'start. If 'previous
  78. is selected, the execution will resume at the previous installer-step. If
  79. 'menu is selected, the MENU-PROC procedure will be called. Its return value
  80. has to be an installer-step ID to jump to. The ID has to be the one of a
  81. previously executed step. It is impossible to jump forward. Finally if 'start
  82. is selected, the execution will resume at the first installer-step.
  83. The result of every COMPUTE procedures is stored in an association list, under
  84. the form:
  85. '((STEP-ID . COMPUTE-RESULT) ...)
  86. where STEP-ID is the ID field of the installer-step and COMPUTE-RESULT the
  87. result of the associated COMPUTE procedure. This result association list is
  88. passed as argument of every COMPUTE procedure. It is finally returned when the
  89. computation is over.
  90. If the &installer-step-break condition is raised, stop the computation and
  91. return the accumalated result so far."
  92. (define (pop-result list)
  93. (cdr list))
  94. (define (first-step? steps step)
  95. (match steps
  96. ((first-step . rest-steps)
  97. (equal? first-step step))))
  98. (define* (skip-to-step step result
  99. #:key todo-steps done-steps)
  100. (match (list todo-steps done-steps)
  101. (((todo . rest-todo) (prev-done ... last-done))
  102. (if (eq? (installer-step-id todo)
  103. (installer-step-id step))
  104. (run result
  105. #:todo-steps todo-steps
  106. #:done-steps done-steps)
  107. (skip-to-step step (pop-result result)
  108. #:todo-steps (cons last-done todo-steps)
  109. #:done-steps prev-done)))))
  110. (define* (run result #:key todo-steps done-steps)
  111. (match todo-steps
  112. (() (reverse result))
  113. ((step . rest-steps)
  114. (guard (c ((installer-step-abort? c)
  115. (case rewind-strategy
  116. ((previous)
  117. (match done-steps
  118. (()
  119. ;; We cannot go previous the first step. So re-raise
  120. ;; the exception. It might be useful in the case of
  121. ;; nested run-installer-steps. Abort to 'raise-above
  122. ;; prompt to prevent the condition from being catched
  123. ;; by one of the previously installed guard.
  124. (abort-to-prompt 'raise-above c))
  125. ((prev-done ... last-done)
  126. (run (pop-result result)
  127. #:todo-steps (cons last-done todo-steps)
  128. #:done-steps prev-done))))
  129. ((menu)
  130. (let ((goto-step (menu-proc
  131. (append done-steps (list step)))))
  132. (if (eq? goto-step step)
  133. (run result
  134. #:todo-steps todo-steps
  135. #:done-steps done-steps)
  136. (skip-to-step goto-step result
  137. #:todo-steps todo-steps
  138. #:done-steps done-steps))))
  139. ((start)
  140. (if (null? done-steps)
  141. ;; Same as above, it makes no sense to jump to start
  142. ;; when we are at the first installer-step. Abort to
  143. ;; 'raise-above prompt to re-raise the condition.
  144. (abort-to-prompt 'raise-above c)
  145. (run '()
  146. #:todo-steps steps
  147. #:done-steps '())))))
  148. ((installer-step-break? c)
  149. (reverse result)))
  150. (let* ((id (installer-step-id step))
  151. (compute (installer-step-compute step))
  152. (res (compute result done-steps)))
  153. (run (alist-cons id res result)
  154. #:todo-steps rest-steps
  155. #:done-steps (append done-steps (list step))))))))
  156. (call-with-prompt 'raise-above
  157. (lambda ()
  158. (run '()
  159. #:todo-steps steps
  160. #:done-steps '()))
  161. (lambda (k condition)
  162. (raise condition))))
  163. (define (find-step-by-id steps id)
  164. "Find and return the step in STEPS whose id is equal to ID."
  165. (find (lambda (step)
  166. (eq? (installer-step-id step) id))
  167. steps))
  168. (define (result-step results step-id)
  169. "Return the result of the installer-step specified by STEP-ID in
  170. RESULTS."
  171. (assoc-ref results step-id))
  172. (define (result-step-done? results step-id)
  173. "Return #t if the installer-step specified by STEP-ID has a COMPUTE value
  174. stored in RESULTS. Return #f otherwise."
  175. (and (assoc step-id results) #t))
  176. (define %installer-configuration-file (make-parameter "/mnt/etc/config.scm"))
  177. (define %installer-target-dir (make-parameter "/mnt"))
  178. (define %configuration-file-width (make-parameter 79))
  179. (define (format-configuration steps results)
  180. "Return the list resulting from the application of the procedure defined in
  181. CONFIGURATION-FORMATTER field of <installer-step> on the associated result
  182. found in RESULTS."
  183. (let ((configuration
  184. (append-map
  185. (lambda (step)
  186. (let* ((step-id (installer-step-id step))
  187. (conf-formatter
  188. (installer-step-configuration-formatter step))
  189. (result-step (result-step results step-id)))
  190. (if (and result-step conf-formatter)
  191. (conf-formatter result-step)
  192. '())))
  193. steps))
  194. (modules '((use-modules (gnu))
  195. (use-service-modules desktop))))
  196. `(,@modules
  197. ()
  198. (operating-system ,@configuration))))
  199. (define* (configuration->file configuration
  200. #:key (filename (%installer-configuration-file)))
  201. "Write the given CONFIGURATION to FILENAME."
  202. (mkdir-p (dirname filename))
  203. (call-with-output-file filename
  204. (lambda (port)
  205. (format port ";; This is an operating system configuration generated~%")
  206. (format port ";; by the graphical installer.~%")
  207. (newline port)
  208. (for-each (lambda (part)
  209. (if (null? part)
  210. (newline port)
  211. (pretty-print part port)))
  212. configuration)
  213. (flush-output-port port))))