Subject: [bug#40373] [PATCH] guix: new command "guix run" generalizes "guix repl"

From: Konrad Hinsen <[email-masked]>
Date: Fri, 03 Apr 2020 11:19:43 +0200

* guix/scripts/run.scm: New file.
* Makefile.am: (MODULES): Add it.
* guix/scripts/repl.scm: Make "guix repl" an alias for "guix run".
* doc/guix.texi: Document "guix run"
---
 Makefile.am           |   1 +
 doc/guix.texi         |  51 ++++++++----
 guix/scripts/repl.scm | 157 +----------------------------------
 guix/scripts/run.scm  | 189 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 229 insertions(+), 169 deletions(-)
 create mode 100644 guix/scripts/run.scm

diff --git a/Makefile.am b/Makefile.am
index 344ecdbc42..db91c4f5da 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -255,6 +255,7 @@ MODULES =					\
   guix/scripts/authenticate.scm			\
   guix/scripts/refresh.scm			\
   guix/scripts/repl.scm				\
+  guix/scripts/run.scm				\
   guix/scripts/describe.scm			\
   guix/scripts/system.scm			\
   guix/scripts/system/search.scm		\
diff --git a/doc/guix.texi b/doc/guix.texi
index 8cb85fe62c..4e4c12866a 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -234,7 +234,7 @@ Programming Interface
 * Derivations::                 Low-level interface to package derivations.
 * The Store Monad::             Purely functional interface to the store.
 * G-Expressions::               Manipulating build expressions.
-* Invoking guix repl::          Fiddling with Guix interactively.
+* Invoking guix run::           Programming Guix in Guile
 
 Defining Packages
 
@@ -1154,7 +1154,7 @@ The @command{guix} command must be in the search path on the build
 machines.  You can check whether this is the case by running:
 
 @example
-ssh build-machine guix repl --version
+ssh build-machine guix run --version
 @end example
 
 There is one last thing to do once @file{machines.scm} is in place.  As
@@ -4259,7 +4259,7 @@ revisions in arbitrary ways.
 
 @cindex inferior packages
 Technically, an ``inferior'' is essentially a separate Guix process connected
-to your main Guix process through a REPL (@pxref{Invoking guix repl}).  The
+to your main Guix process through a REPL (@pxref{Invoking guix run}).  The
 @code{(guix inferior)} module allows you to create inferiors and to
 communicate with them.  It also provides a high-level interface to browse and
 manipulate the packages that an inferior provides---@dfn{inferior packages}.
@@ -4318,7 +4318,7 @@ As a side effect, this procedure may build or substitute binaries for
 @deffn {Scheme Procedure} open-inferior @var{directory} @
   [#:command "bin/guix"]
 Open the inferior Guix in @var{directory}, running
-@code{@var{directory}/@var{command} repl} or equivalent.  Return @code{#f} if
+@code{@var{directory}/@var{command} run} or equivalent.  Return @code{#f} if
 the inferior could not be launched.
 @end deffn
 
@@ -5346,7 +5346,8 @@ package definitions.
 * Derivations::                 Low-level interface to package derivations.
 * The Store Monad::             Purely functional interface to the store.
 * G-Expressions::               Manipulating build expressions.
-* Invoking guix repl::          Fiddling with Guix interactively.
+* Invoking guix run::           Programming Guix in Guile
+
 @end menu
 
 @node Package Modules
@@ -8057,25 +8058,43 @@ corresponding to @var{obj} for @var{system}, cross-compiling for
 has an associated gexp compiler, such as a @code{<package>}.
 @end deffn
 
-@node Invoking guix repl
-@section Invoking @command{guix repl}
+@node Invoking guix run
+@section Invoking @command{guix run}
+
+@cindex script, REPL, read-eval-print loop
+The @command{guix run} command makes it easier to program Guix in Guile,
+either by running Guile scripts (@pxref{Running Guile Scripts,,, guile,
+GNU Guile Reference Manual}) or by launching a Guile
+@dfn{read-eval-print loop} (REPL) for interactive programming
+(@pxref{Using Guile Interactively,,, guile, GNU Guile Reference Manual}).
+Compared to just launching the @command{guile}
+command, @command{guix run} guarantees that all the Guix modules and all its
+dependencies are available in the search path.
+
+The general syntax is:
+
+@example
+guix run @var{options} @var{file}
+@end example
+
+When a @var{file} argument is provided, @var{file} is executed as a Guile
+script:
+
+@example
+$ guix run my-script.scm
+@end example
 
-@cindex REPL, read-eval-print loop
-The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop}
-(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile,
-GNU Guile Reference Manual}).  Compared to just launching the @command{guile}
-command, @command{guix repl} guarantees that all the Guix modules and all its
-dependencies are available in the search path.  You can use it this way:
+Otherwise a Guile REPL is started:
 
 @example
-$ guix repl
+$ guix run
 scheme@@(guile-user)> ,use (gnu packages base)
 scheme@@(guile-user)> coreutils
 $1 = #<package coreutils@@8.29 gnu/packages/base.scm:327 3e28300>
 @end example
 
 @cindex inferiors
-In addition, @command{guix repl} implements a simple machine-readable REPL
+In addition, @command{guix run} implements a simple machine-readable REPL
 protocol for use by @code{(guix inferior)}, a facility to interact with
 @dfn{inferiors}, separate processes running a potentially different revision
 of Guix.
@@ -8114,7 +8133,7 @@ Add @var{directory} to the front of the package module search path
 (@pxref{Package Modules}).
 
 This allows users to define their own packages and make them visible to
-the command-line tool.
+the script or command-line tool.
 
 @item -q
 Inhibit loading of the @file{~/.guile} file.  By default, that
diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm
index ff1f208894..4feca9140a 100644
--- a/guix/scripts/repl.scm
+++ b/guix/scripts/repl.scm
@@ -1,6 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
-;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <[email-masked]>
-;;; Copyright © 2020 Simon Tournier <[email-masked]>
+;;; Copyright © 2020 Konrad Hinsen <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -18,159 +17,11 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (guix scripts repl)
-  #:use-module (guix ui)
-  #:use-module (guix scripts)
-  #:use-module (guix repl)
-  #:use-module (srfi srfi-1)
-  #:use-module (srfi srfi-37)
-  #:use-module (ice-9 match)
-  #:use-module (rnrs bytevectors)
-  #:autoload   (system repl repl) (start-repl)
-  #:autoload   (system repl server)
-                  (make-tcp-server-socket make-unix-domain-server-socket)
+  #:use-module (guix scripts run)
   #:export (guix-repl))
 
 ;;; Commentary:
 ;;;
-;;; This command provides a Guile REPL
+;;; This is an alias for "guix run", for backwards compatibility
 
-(define %default-options
-  `((type . guile)))
-
-(define %options
-  (list (option '(#\h "help") #f #f
-                (lambda args
-                  (show-help)
-                  (exit 0)))
-        (option '(#\V "version") #f #f
-                (lambda args
-                  (show-version-and-exit "guix repl")))
-        (option '(#\t "type") #t #f
-                (lambda (opt name arg result)
-                  (alist-cons 'type (string->symbol arg) result)))
-        (option '("listen") #t #f
-                (lambda (opt name arg result)
-                  (alist-cons 'listen arg result)))
-        (option '(#\q) #f #f
-                (lambda (opt name arg result)
-                  (alist-cons 'ignore-dot-guile? #t result)))
-        (option '(#\L "load-path") #t #f
-                (lambda (opt name arg result)
-                  ;; XXX: Imperatively modify the search paths.
-                  (set! %load-path (cons arg %load-path))
-                  (set! %load-compiled-path (cons arg %load-compiled-path))
-                  result))))
-
-
-(define (show-help)
-  (display (G_ "Usage: guix repl [OPTIONS...]
-Start a Guile REPL in the Guix execution environment.\n"))
-  (display (G_ "
-  -t, --type=TYPE        start a REPL of the given TYPE"))
-  (display (G_ "
-      --listen=ENDPOINT  listen to ENDPOINT instead of standard input"))
-  (display (G_ "
-  -q                     inhibit loading of ~/.guile"))
-  (newline)
-  (display (G_ "
-  -L, --load-path=DIR    prepend DIR to the package module search path"))
-  (newline)
-  (display (G_ "
-  -h, --help             display this help and exit"))
-  (display (G_ "
-  -V, --version          display version information and exit"))
-  (newline)
-  (show-bug-report-information))
-
-(define user-module
-  ;; Module where we execute user code.
-  (let ((module (resolve-module '(guix-user) #f #f #:ensure #t)))
-    (beautify-user-module! module)
-    module))
-
-(define (call-with-connection spec thunk)
-  "Dynamically-bind the current input and output ports according to SPEC and
-call THUNK."
-  (if (not spec)
-      (thunk)
-
-      ;; Note: the "PROTO:" prefix in SPEC is here so that we can eventually
-      ;; parse things like "fd:123" in a non-ambiguous way.
-      (match (string-index spec #\:)
-        (#f
-         (leave (G_ "~A: invalid listen specification~%") spec))
-        (index
-         (let ((protocol (string-take spec index))
-               (address  (string-drop spec (+ index 1))))
-           (define socket
-             (match protocol
-               ("tcp"
-                (make-tcp-server-socket #:port (string->number address)))
-               ("unix"
-                (make-unix-domain-server-socket #:path address))
-               (_
-                (leave (G_ "~A: unsupported protocol family~%")
-                       protocol))))
-
-           (listen socket 10)
-           (let loop ()
-             (match (accept socket)
-               ((connection . address)
-                (if (= AF_UNIX (sockaddr:fam address))
-                    (info (G_ "accepted connection~%"))
-                    (info (G_ "accepted connection from ~a~%")
-                          (inet-ntop (sockaddr:fam address)
-                                     (sockaddr:addr address))))
-                (dynamic-wind
-                  (const #t)
-                  (lambda ()
-                    (parameterize ((current-input-port connection)
-                                   (current-output-port connection))
-                      (thunk)))
-                  (lambda ()
-                    (false-if-exception (close-port connection))
-                    (info (G_ "connection closed~%"))))))
-             (loop)))))))
-
-
-(define (guix-repl . args)
-  (define opts
-    ;; Return the list of package names.
-    (args-fold* args %options
-                (lambda (opt name arg result)
-                  (leave (G_ "~A: unrecognized option~%") name))
-                (lambda (arg result)
-                  (leave (G_ "~A: extraneous argument~%") arg))
-                %default-options))
-
-  (define user-config
-    (and=> (getenv "HOME")
-           (lambda (home)
-             (string-append home "/.guile"))))
-
-  (with-error-handling
-    (let ((type (assoc-ref opts 'type)))
-      (call-with-connection (assoc-ref opts 'listen)
-        (lambda ()
-          (case type
-            ((guile)
-             (save-module-excursion
-              (lambda ()
-                (set-current-module user-module)
-                (when (and (not (assoc-ref opts 'ignore-dot-guile?))
-                           user-config
-                           (file-exists? user-config))
-                  (load user-config))
-
-                ;; Do not exit repl on SIGINT.
-                ((@@ (ice-9 top-repl) call-with-sigint)
-                 (lambda ()
-                   (start-repl))))))
-            ((machine)
-             (machine-repl))
-            (else
-             (leave (G_ "~a: unknown type of REPL~%") type))))))))
-
-;; Local Variables:
-;; eval: (put 'call-with-connection 'scheme-indent-function 1)
-;; End:
+(define guix-repl guix-run)
diff --git a/guix/scripts/run.scm b/guix/scripts/run.scm
new file mode 100644
index 0000000000..4f3cfb2c47
--- /dev/null
+++ b/guix/scripts/run.scm
@@ -0,0 +1,189 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2020 Simon Tournier <[email-masked]>
+;;; Copyright © 2020 Konrad Hinsen <[email-masked]>
+;;;
+;;; 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 (guix scripts run)
+  #:use-module (guix ui)
+  #:use-module (guix scripts)
+  #:use-module (guix repl)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 match)
+  #:use-module (rnrs bytevectors)
+  #:autoload   (system repl repl) (start-repl)
+  #:autoload   (system repl server)
+                  (make-tcp-server-socket make-unix-domain-server-socket)
+  #:export (guix-run))
+
+;;; Commentary:
+;;;
+;;; This command provides a Guile script runner and REPL in an environment
+;;; that contains all the modules comprising Guix.
+
+(define %default-options
+  `((type . guile)))
+
+(define %options
+  (list (option '(#\h "help") #f #f
+                (lambda args
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda args
+                  (show-version-and-exit "guix repl")))
+        (option '(#\t "type") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'type (string->symbol arg) result)))
+        (option '("listen") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'listen arg result)))
+        (option '(#\q) #f #f
+                (lambda (opt name arg result)
+                  (alist-cons 'ignore-dot-guile? #t result)))
+        (option '(#\L "load-path") #t #f
+                (lambda (opt name arg result)
+                  ;; XXX: Imperatively modify the search paths.
+                  (set! %load-path (cons arg %load-path))
+                  (set! %load-compiled-path (cons arg %load-compiled-path))
+                  result))))
+
+
+(define (show-help)
+  (display (G_ "Usage: guix run [OPTIONS...] [FILE]
+Run FILE as a Guile script, or start a Guile REPL, in the Guix
+execution environment.\n"))
+  (display (G_ "
+  -t, --type=TYPE        start a REPL of the given TYPE"))
+  (display (G_ "
+      --listen=ENDPOINT  listen to ENDPOINT instead of standard input"))
+  (display (G_ "
+  -q                     inhibit loading of ~/.guile"))
+  (newline)
+  (display (G_ "
+  -L, --load-path=DIR    prepend DIR to the package module search path"))
+  (newline)
+  (display (G_ "
+  -h, --help             display this help and exit"))
+  (display (G_ "
+  -V, --version          display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define user-module
+  ;; Module where we execute user code.
+  (let ((module (resolve-module '(guix-user) #f #f #:ensure #t)))
+    (beautify-user-module! module)
+    module))
+
+(define (call-with-connection spec thunk)
+  "Dynamically-bind the current input and output ports according to SPEC and
+call THUNK."
+  (if (not spec)
+      (thunk)
+
+      ;; Note: the "PROTO:" prefix in SPEC is here so that we can eventually
+      ;; parse things like "fd:123" in a non-ambiguous way.
+      (match (string-index spec #\:)
+        (#f
+         (leave (G_ "~A: invalid listen specification~%") spec))
+        (index
+         (let ((protocol (string-take spec index))
+               (address  (string-drop spec (+ index 1))))
+           (define socket
+             (match protocol
+               ("tcp"
+                (make-tcp-server-socket #:port (string->number address)))
+               ("unix"
+                (make-unix-domain-server-socket #:path address))
+               (_
+                (leave (G_ "~A: unsupported protocol family~%")
+                       protocol))))
+
+           (listen socket 10)
+           (let loop ()
+             (match (accept socket)
+               ((connection . address)
+                (if (= AF_UNIX (sockaddr:fam address))
+                    (info (G_ "accepted connection~%"))
+                    (info (G_ "accepted connection from ~a~%")
+                          (inet-ntop (sockaddr:fam address)
+                                     (sockaddr:addr address))))
+                (dynamic-wind
+                  (const #t)
+                  (lambda ()
+                    (parameterize ((current-input-port connection)
+                                   (current-output-port connection))
+                      (thunk)))
+                  (lambda ()
+                    (false-if-exception (close-port connection))
+                    (info (G_ "connection closed~%"))))))
+             (loop)))))))
+
+
+(define (guix-run . args)
+  (define opts
+    (args-fold* args %options
+                (lambda (opt name arg result)
+                  (leave (G_ "~A: unrecognized option~%") name))
+                (lambda (arg result)
+                  (when (assq 'argument result)
+                    (leave (G_ "~A: extraneous argument~%") arg))
+                  (alist-cons 'argument arg result))
+                %default-options))
+
+  (define user-config
+    (and=> (getenv "HOME")
+           (lambda (home)
+             (string-append home "/.guile"))))
+
+  (define (set-user-module)
+    (set-current-module user-module)
+    (when (and (not (assoc-ref opts 'ignore-dot-guile?))
+               user-config
+               (file-exists? user-config))
+      (load user-config)))
+  
+  (with-error-handling
+    (let ((script (assoc-ref opts 'argument))
+          (type (assoc-ref opts 'type)))
+      (if script
+          (save-module-excursion
+           (lambda ()
+             (set-user-module)
+             (load script)))
+          (call-with-connection (assoc-ref opts 'listen)
+            (lambda ()
+              (case type
+                ((guile)
+                 (save-module-excursion
+                  (lambda ()
+                    (set-user-module)
+                    ;; Do not exit repl on SIGINT.
+                    ((@@ (ice-9 top-repl) call-with-sigint)
+                     (lambda ()
+                       (start-repl))))))
+                ((machine)
+                 (machine-repl))
+                (else
+                 (leave (G_ "~a: unknown type of REPL~%") type))))))))
+
+  ;; Local Variables:
+  ;; eval: (put 'call-with-connection 'scheme-indent-function 1)
+  ;; End:
+  )
-- 
2.26.0