aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018, 2019 Mathieu Othacehe <m.othacehe@gmail.com>
;;; Copyright © 2020-2022 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2024 Janneke Nieuwenhuizen <janneke@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu installer steps)
  #:use-module (guix records)
  #:use-module (guix build utils)
  #:use-module (guix i18n)
  #:use-module (guix read-print)
  #:use-module (guix utils)
  #:use-module (gnu installer utils)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-34)
  #:use-module (srfi srfi-35)
  #:use-module (rnrs io ports)
  #:export (&user-abort-error
            user-abort-error?

            <installer-step>
            installer-step
            make-installer-step
            installer-step?
            installer-step-id
            installer-step-description
            installer-step-compute
            installer-step-configuration-formatter

            run-installer-steps
            find-step-by-id
            result->step-ids
            result-step
            result-step-done?

            %installer-configuration-file
            %installer-target-dir
            format-configuration
            configuration->file

            %current-result))

(define-condition-type &user-abort-error &error
  user-abort-error?)

;; Hash table storing the step results. Use it only for logging and debug
;; purposes.
(define %current-result (make-hash-table))

;; An installer-step record is basically an id associated to a compute
;; procedure. The COMPUTE procedure takes exactly one argument, an association
;; list containing the results of previously executed installer-steps (see
;; RUN-INSTALLER-STEPS description). The value returned by the COMPUTE
;; procedure will be stored in the results list passed to the next
;; installer-step and so on.
(define-record-type* <installer-step>
  installer-step make-installer-step
  installer-step?
  (id                         installer-step-id) ;symbol
  (description                installer-step-description ;string
                              (default #f)

                              ;; Make it thunked so that 'G_' is called at the
                              ;; right time, as opposed to being called once
                              ;; when the installer starts.
                              (thunked))
  (compute                    installer-step-compute) ;procedure
  (configuration-formatter    installer-step-configuration-formatter ;procedure
                              (default #f)))

(define* (run-installer-steps #:key
                              steps
                              (rewind-strategy 'previous)
                              (menu-proc (const #f))
                              dry-run?)
  "Run the COMPUTE procedure of all <installer-step> records in STEPS
sequentially, inside a the 'installer-step prompt.  When aborted to with a
parameter of 'abort, fallback to a previous install-step, accordingly to the
specified REWIND-STRATEGY.  When aborted to with a parameter of 'break, stop
the computation and return the accumalated result so far.

REWIND-STRATEGY possible values are 'previous, 'menu and 'start.  If 'previous
is selected, the execution will resume at the previous installer-step. If
'menu is selected, the MENU-PROC procedure will be called. Its return value
has to be an installer-step ID to jump to. The ID has to be the one of a
previously executed step. It is impossible to jump forward. Finally if 'start
is selected, the execution will resume at the first installer-step.

The result of every COMPUTE procedures is stored in an association list, under
the form:

		'((STEP-ID . COMPUTE-RESULT) ...)

where STEP-ID is the ID field of the installer-step and COMPUTE-RESULT the
result of the associated COMPUTE procedure. This result association list is
passed as argument of every COMPUTE procedure. It is finally returned when the
computation is over."
  (define (pop-result list)
    (cdr list))

  (define (first-step? steps step)
    (match steps
      ((first-step . rest-steps)
       (equal? first-step step))))

  (define* (skip-to-step step result
                         #:key todo-steps done-steps)
    (match todo-steps
      ((todo . rest-todo)
       (let ((found? (eq? (installer-step-id todo)
                          (installer-step-id step))))
         (cond
          (found?
           (run result
                #:todo-steps todo-steps
                #:done-steps done-steps))
          ((and (not found?)
                (null? done-steps))
           (error (format #f "Step ~a not found" (installer-step-id step))))
          (else
           (match done-steps
             ((prev-done ... last-done)
              (skip-to-step step (pop-result result)
                            #:todo-steps (cons last-done todo-steps)
                            #:done-steps prev-done)))))))))

  (define* (run result #:key todo-steps done-steps)
    (match todo-steps
      (() (reverse result))
      ((step . rest-steps)
       (call-with-prompt 'installer-step
         (lambda ()
           (installer-log-line "running step '~a'" (installer-step-id step))
           (let* ((id (installer-step-id step))
                  (compute (installer-step-compute step))
                  (res (compute result done-steps)))
             (hash-set! %current-result id res)
             (run (alist-cons id res result)
                  #:todo-steps rest-steps
                  #:done-steps (append done-steps (list step)))))
         (lambda (k action)
           (match action
             ('abort
              (case rewind-strategy
                ((previous)
                 (match done-steps
                   (()
                    ;; We cannot go previous the first step. Abort again to
                    ;; 'installer-step prompt. It might be useful in the case
                    ;; of nested run-installer-steps.
                    (abort-to-prompt 'installer-step action))
                   ((prev-done ... last-done)
                    (run (pop-result result)
                         #:todo-steps (cons last-done todo-steps)
                         #:done-steps prev-done))))
                ((menu)
                 (let ((goto-step (menu-proc
                                   (append done-steps (list step)))))
                   (if (eq? goto-step step)
                       (run result
                            #:todo-steps todo-steps
                            #:done-steps done-steps)
                       (skip-to-step goto-step result
                                     #:todo-steps todo-steps
                                     #:done-steps done-steps))))
                ((start)
                 (if (null? done-steps)
                     ;; Same as above, it makes no sense to jump to start
                     ;; when we are at the first installer-step. Abort to
                     ;; 'installer-step prompt again.
                     (abort-to-prompt 'installer-step action)
                     (run '()
                          #:todo-steps steps
                          #:done-steps '())))))
             ('break
              (reverse result))))))))

  ;; Ignore SIGPIPE so that we don't die if a client closes the connection
  ;; prematurely.
  (sigaction SIGPIPE SIG_IGN)

  (if dry-run?
      (run '()
           #:todo-steps steps
           #:done-steps '())
      (with-server-socket
        (run '()
             #:todo-steps steps
             #:done-steps '()))))

(define (find-step-by-id steps id)
  "Find and return the step in STEPS whose id is equal to ID."
  (find (lambda (step)
          (eq? (installer-step-id step) id))
        steps))

(define (result-step results step-id)
  "Return the result of the installer-step specified by STEP-ID in
RESULTS."
  (assoc-ref results step-id))

(define (result-step-done? results step-id)
  "Return #t if the installer-step specified by STEP-ID has a COMPUTE value
stored in RESULTS. Return #f otherwise."
  (and (assoc step-id results) #t))

(define %installer-configuration-file (make-parameter "/mnt/etc/config.scm"))
(define %installer-target-dir (make-parameter "/mnt"))

(define (format-configuration steps results)
  "Return the list resulting from the application of the procedure defined in
CONFIGURATION-FORMATTER field of <installer-step> on the associated result
found in RESULTS."
  (let ((configuration
         (append-map
          (lambda (step)
            (let* ((step-id (installer-step-id step))
                   (conf-formatter
                    (installer-step-configuration-formatter step))
                   (result-step (result-step results step-id)))
              (if (and result-step conf-formatter)
                  (conf-formatter result-step)
                  '())))
          steps))
        (modules `(,(vertical-space 1)
                   ,(comment (G_ "\
;; Indicate which modules to import to access the variables
;; used in this configuration.\n"))
                   ,@(if (target-hurd?)
                         '((use-modules (gnu) (gnu system hurd))
                           (use-package-modules hurd ssh))
                         '((use-modules (gnu))))
                   (use-service-modules cups desktop networking ssh xorg))))
    `(,@modules
      ,(vertical-space 1)
      (operating-system ,@configuration))))

(define* (configuration->file configuration
                              #:key (file-name (%installer-configuration-file)))
  "Write the given CONFIGURATION to FILE-NAME."
  (mkdir-p (dirname file-name))
  (call-with-output-file file-name
    (lambda (port)
      ;; TRANSLATORS: This is a comment within a Scheme file.  Each line must
      ;; start with ";; " (two semicolons and a space).  Please keep line
      ;; length below 60 characters.
      (display (G_ "\
;; This is an operating system configuration generated
;; by the graphical installer.
;;
;; Once installation is complete, you can learn and modify
;; this file to tweak the system configuration, and pass it
;; to the 'guix system reconfigure' command to effect your
;; changes.\n")
               port)
      (newline port)
      (pretty-print-with-comments/splice port configuration
                                         #:max-width 75
                                         #:format-comment
                                         (lambda (c indent)
                                           ;; Localize C.
                                           (comment (G_ (comment->string c))
                                                    (comment-margin? c))))

      (flush-output-port port))))

;;; Local Variables:
;;; eval: (put 'with-server-socket 'scheme-indent-function 0)
;;; End:
: Likewise. * gnu/tests.scm: Likewise. * guix/inferior.scm: Likewise. * tests/channels.scm: Likewise. * tests/packages.scm: Likewise. Ludovic Courtès 2020-07-13packages: Ensure bags are insensitive to '%current-system'....Fixes <https://bugs.gnu.org/42327>. Reported by Jan Nieuwenhuizen <janneke@gnu.org>. This is a followup to f52fbf7094c9c346d38ad469cc8d92d18387786e. * guix/packages.scm (bag-transitive-inputs, bag-transitive-build-inputs) (bag-transitive-host-inputs, bag-transitive-target-inputs): Parameterize %CURRENT-SYSTEM in addition to %CURRENT-TARGET-SYSTEM. * tests/packages.scm ("package->bag, sensitivity to %current-system"): New test. Ludovic Courtès 2020-06-27packages: Recognize SHA3 and BLAKE2s for 'content-hash'....* guix/packages.scm (build-content-hash): Add 'sha3-256', 'sha3-512', and 'blake2s-256'. * tests/packages.scm ("package-source-derivation, origin, sha3-512"): New test. Ludovic Courtès 2020-06-11packages: 'package-grafts' returns grafts for all the relevant outputs....Fixes <https://bugs.gnu.org/41796>. Reported by Jakub Kądziołka <kuba@kadziolka.net>. * guix/packages.scm (input-graft): Add 'output' parameter and honor it. Add OUTPUT to the cache key. (input-cross-graft): Likewise. (fold-bag-dependencies): Operate on inputs instead of nodes. Turn VISITED into a vhash instead of a set. Pass PROC HEAD and OUTPUT instead of just HEAD. (bag-grafts): Adjust accordingly. * tests/packages.scm ("package-grafts, dependency on several outputs"): New test. Ludovic Courtès 2020-06-06packages: Make 'bag-grafts' insensitive to '%current-target-system'....Fixes <https://bugs.gnu.org/41713>. Reported by Mathieu Othacehe. * guix/packages.scm (bag-grafts): Wrap 'fold-bag-dependencies' calls in 'parameterize'. * tests/packages.scm ("package->bag, sensitivity to %current-target-system"): New test. Ludovic Courtès 2020-05-23tests: Use a #:prefix for (gcrypt hash)....* tests/packages.scm: Use #:prefix instead of #:hide for (gcrypt hash). This accomodates for 'sha512' syntax literal matches with Guile-Gcrypt 0.3.0, which exports 'sha512' in addition to 'sha256'. Ludovic Courtès 2020-05-22packages: Introduce <content-hash> and use it in <origin>....* guix/packages.scm (<content-hash>): New record type. (define-content-hash-constructor, build-content-hash) (content-hash): New macros. (print-content-hash): New procedure. (<origin>): Rename constructor to '%origin'. [sha256]: Remove field. [hash]: New field. Adjust users. (origin-compatibility-helper, origin): New macros. (origin-sha256): New deprecated procedure. (origin->derivation): Adjust accordingly. * tests/packages.scm ("package-source-derivation, origin, sha512"): New test. * guix/tests.scm: Hide (gcrypt hash) 'sha256' for proper syntax matching. * tests/challenge.scm: Add #:prefix for (gcrypt hash) and adjust users. * tests/derivations.scm: Likewise. * tests/store.scm: Likewise. * tests/graph.scm ("bag DAG, including origins"): Provide 'sha256' field with the right length. * gnu/packages/aspell.scm (aspell-dictionary) (aspell-dict-ca, aspell-dict-it): Use 'hash' and 'content-hash' for proper syntax matching. * gnu/packages/bash.scm (bash-patch): Rename 'sha256' to 'sha256-bv'. * gnu/packages/bootstrap.scm (bootstrap-executable): Rename 'sha256' to 'bv'. * gnu/packages/readline.scm (readline-patch): Likewise. * gnu/packages/virtualization.scm (qemu-patch): Rename 'sha256' to 'sha256-bv'. * guix/import/utils.scm: Hide (gcrypt hash) 'sha256'. Ludovic Courtès 2020-05-14packages: Ensure bags are insensitive to '%current-target-system'....Fixes a bug whereby a bag's transitive dependencies would depend on the global '%current-target-system' value. Partly fixes <https://issues.guix.gnu.org/41182>. * guix/packages.scm (bag-transitive-inputs) (bag-transitive-build-inputs, bag-transitive-target-inputs): Parameterize '%current-target-system'. * tests/packages.scm ("package->bag, sensitivity to %current-target-system"): New test. Ludovic Courtès 2020-03-31guix package: Do not misdiagnose upgrades when there are propagated inputs....Fixes <https://bugs.gnu.org/35872>. Reported by Andy Tai <atai@atai.org>. * guix/profiles.scm (list=?, manifest-entry=?): New procedures. * guix/scripts/package.scm (transaction-upgrade-entry): In the '=' case, use 'manifest-entry=?' to determine whether it's an upgrade. * tests/packages.scm ("transaction-upgrade-entry, zero upgrades, propagated inputs"): New test. Ludovic Courtès 2020-03-31guix package: Add 'transaction-upgrade-entry' test....* tests/packages.scm ("transaction-upgrade-entry, zero upgrades, equivalent package"): New test. Ludovic Courtès 2020-03-31guix package: 'transaction-upgrade-entry' swallows build requests....Fixes a regression introduced in 131f50cdc9dbb7183023f4dae759876a9e700bef whereby the install/upgrade message would not be displayed: $ guix upgrade -n 2.1 MB would be downloaded: /gnu/store/…-something-1.2 /gnu/store/…-its-dependency-2.3 This is because we'd directly abort from 'transaction-upgrade-entry' to the build handler of 'build-notifier'. * guix/scripts/package.scm (transaction-upgrade-entry): Call 'string=?' expression in 'with-build-handler'. * tests/packages.scm ("transaction-upgrade-entry, grafts"): New test. Ludovic Courtès 2020-01-16guix package: Export 'transaction-upgrade-entry'....* guix/scripts/package.scm (transaction-upgrade-entry): Add 'store' parameter and use it instead of (%store). Export. * tests/packages.scm ("transaction-upgrade-entry, zero upgrades") ("transaction-upgrade-entry, one upgrade") ("transaction-upgrade-entry, superseded package"): Adjust accordingly. Ludovic Courtès