steps.scm 10 KB

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