diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2018-07-10 14:18:36 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2018-07-13 17:28:39 +0200 |
| commit | 2ca299caf64489f4e1e665ec1158fb0309b0b565 (patch) | |
| tree | 29a657a53161916cc64b6d0365c712501d666919 | |
| parent | bc6e291ef0b3c71c07e50d88d7764e5dd334e8b1 (diff) | |
Add (guix inferior) and (guix scripts repl).
* guix/inferior.scm, guix/scripts/repl.scm, tests/inferior.scm: New
files.
* Makefile.am (MODULES): Add 'guix/scripts/repl.scm' and
'guix/inferior.scm'.
(SCM_TESTS): Add 'tests/inferior.scm'.
* doc/guix.texi (Invoking guix repl): New node.
| -rw-r--r-- | Makefile.am | 3 | ||||
| -rw-r--r-- | doc/guix.texi | 53 | ||||
| -rw-r--r-- | guix/inferior.scm | 197 | ||||
| -rw-r--r-- | guix/scripts/repl.scm | 199 | ||||
| -rw-r--r-- | tests/inferior.scm | 69 |
5 files changed, 521 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 618d1653e4c..134ab0f142b 100644 --- a/Makefile.am +++ b/Makefile.am | |||
| @@ -85,6 +85,7 @@ MODULES = \ | |||
| 85 | guix/nar.scm \ | 85 | guix/nar.scm \ |
| 86 | guix/derivations.scm \ | 86 | guix/derivations.scm \ |
| 87 | guix/grafts.scm \ | 87 | guix/grafts.scm \ |
| 88 | guix/inferior.scm \ | ||
| 88 | guix/gnu-maintenance.scm \ | 89 | guix/gnu-maintenance.scm \ |
| 89 | guix/self.scm \ | 90 | guix/self.scm \ |
| 90 | guix/upstream.scm \ | 91 | guix/upstream.scm \ |
| @@ -200,6 +201,7 @@ MODULES = \ | |||
| 200 | guix/scripts/substitute.scm \ | 201 | guix/scripts/substitute.scm \ |
| 201 | guix/scripts/authenticate.scm \ | 202 | guix/scripts/authenticate.scm \ |
| 202 | guix/scripts/refresh.scm \ | 203 | guix/scripts/refresh.scm \ |
| 204 | guix/scripts/repl.scm \ | ||
| 203 | guix/scripts/system.scm \ | 205 | guix/scripts/system.scm \ |
| 204 | guix/scripts/system/search.scm \ | 206 | guix/scripts/system/search.scm \ |
| 205 | guix/scripts/lint.scm \ | 207 | guix/scripts/lint.scm \ |
| @@ -357,6 +359,7 @@ SCM_TESTS = \ | |||
| 357 | tests/profiles.scm \ | 359 | tests/profiles.scm \ |
| 358 | tests/search-paths.scm \ | 360 | tests/search-paths.scm \ |
| 359 | tests/syscalls.scm \ | 361 | tests/syscalls.scm \ |
| 362 | tests/inferior.scm \ | ||
| 360 | tests/gremlin.scm \ | 363 | tests/gremlin.scm \ |
| 361 | tests/bournish.scm \ | 364 | tests/bournish.scm \ |
| 362 | tests/lint.scm \ | 365 | tests/lint.scm \ |
diff --git a/doc/guix.texi b/doc/guix.texi index eaec4c422be..7a5ddefd4e3 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -166,6 +166,7 @@ Programming Interface | |||
| 166 | * Derivations:: Low-level interface to package derivations. | 166 | * Derivations:: Low-level interface to package derivations. |
| 167 | * The Store Monad:: Purely functional interface to the store. | 167 | * The Store Monad:: Purely functional interface to the store. |
| 168 | * G-Expressions:: Manipulating build expressions. | 168 | * G-Expressions:: Manipulating build expressions. |
| 169 | * Invoking guix repl:: Fiddling with Guix interactively. | ||
| 169 | 170 | ||
| 170 | Defining Packages | 171 | Defining Packages |
| 171 | 172 | ||
| @@ -3267,6 +3268,7 @@ package definitions. | |||
| 3267 | * Derivations:: Low-level interface to package derivations. | 3268 | * Derivations:: Low-level interface to package derivations. |
| 3268 | * The Store Monad:: Purely functional interface to the store. | 3269 | * The Store Monad:: Purely functional interface to the store. |
| 3269 | * G-Expressions:: Manipulating build expressions. | 3270 | * G-Expressions:: Manipulating build expressions. |
| 3271 | * Invoking guix repl:: Fiddling with Guix interactively. | ||
| 3270 | @end menu | 3272 | @end menu |
| 3271 | 3273 | ||
| 3272 | @node Defining Packages | 3274 | @node Defining Packages |
| @@ -5544,6 +5546,57 @@ corresponding to @var{obj} for @var{system}, cross-compiling for | |||
| 5544 | has an associated gexp compiler, such as a @code{<package>}. | 5546 | has an associated gexp compiler, such as a @code{<package>}. |
| 5545 | @end deffn | 5547 | @end deffn |
| 5546 | 5548 | ||
| 5549 | @node Invoking guix repl | ||
| 5550 | @section Invoking @command{guix repl} | ||
| 5551 | |||
| 5552 | @cindex REPL, read-eval-print loop | ||
| 5553 | The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop} | ||
| 5554 | (REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile, | ||
| 5555 | GNU Guile Reference Manual}). Compared to just launching the @command{guile} | ||
| 5556 | command, @command{guix repl} guarantees that all the Guix modules and all its | ||
| 5557 | dependencies are available in the search path. You can use it this way: | ||
| 5558 | |||
| 5559 | @example | ||
| 5560 | $ guix repl | ||
| 5561 | scheme@@(guile-user)> ,use (gnu packages base) | ||
| 5562 | scheme@@(guile-user)> coreutils | ||
| 5563 | $1 = #<package coreutils@@8.29 gnu/packages/base.scm:327 3e28300> | ||
| 5564 | @end example | ||
| 5565 | |||
| 5566 | @cindex inferiors | ||
| 5567 | In addition, @command{guix repl} implements a simple machine-readable REPL | ||
| 5568 | protocol for use by @code{(guix inferior)}, a facility to interact with | ||
| 5569 | @dfn{inferiors}, separate processes running a potentially different revision | ||
| 5570 | of Guix. | ||
| 5571 | |||
| 5572 | The available options are as follows: | ||
| 5573 | |||
| 5574 | @table @code | ||
| 5575 | @item --type=@var{type} | ||
| 5576 | @itemx -t @var{type} | ||
| 5577 | Start a REPL of the given @var{TYPE}, which can be one of the following: | ||
| 5578 | |||
| 5579 | @table @code | ||
| 5580 | @item guile | ||
| 5581 | This is default, and it spawns a standard full-featured Guile REPL. | ||
| 5582 | @item machine | ||
| 5583 | Spawn a REPL that uses the machine-readable protocol. This is the protocol | ||
| 5584 | that the @code{(guix inferior)} module speaks. | ||
| 5585 | @end table | ||
| 5586 | |||
| 5587 | @item --listen=@var{endpoint} | ||
| 5588 | By default, @command{guix repl} reads from standard input and writes to | ||
| 5589 | standard output. When this option is passed, it will instead listen for | ||
| 5590 | connections on @var{endpoint}. Here are examples of valid options: | ||
| 5591 | |||
| 5592 | @table @code | ||
| 5593 | @item --listen=tcp:37146 | ||
| 5594 | Accept connections on localhost on port 37146. | ||
| 5595 | |||
| 5596 | @item --listen=unix:/tmp/socket | ||
| 5597 | Accept connections on the Unix-domain socket @file{/tmp/socket}. | ||
| 5598 | @end table | ||
| 5599 | @end table | ||
| 5547 | 5600 | ||
| 5548 | @c ********************************************************************* | 5601 | @c ********************************************************************* |
| 5549 | @node Utilities | 5602 | @node Utilities |
diff --git a/guix/inferior.scm b/guix/inferior.scm new file mode 100644 index 00000000000..629c2c43135 --- /dev/null +++ b/guix/inferior.scm | |||
| @@ -0,0 +1,197 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> | ||
| 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 | |||
| 19 | (define-module (guix inferior) | ||
| 20 | #:use-module (srfi srfi-9) | ||
| 21 | #:use-module (srfi srfi-9 gnu) | ||
| 22 | #:use-module (ice-9 match) | ||
| 23 | #:use-module (ice-9 popen) | ||
| 24 | #:export (inferior? | ||
| 25 | open-inferior | ||
| 26 | close-inferior | ||
| 27 | inferior-eval | ||
| 28 | inferior-object? | ||
| 29 | |||
| 30 | inferior-package? | ||
| 31 | inferior-package-name | ||
| 32 | inferior-package-version | ||
| 33 | |||
| 34 | inferior-packages | ||
| 35 | inferior-package-synopsis | ||
| 36 | inferior-package-description)) | ||
| 37 | |||
| 38 | ;;; Commentary: | ||
| 39 | ;;; | ||
| 40 | ;;; This module provides a way to spawn Guix "inferior" processes and to talk | ||
| 41 | ;;; to them. It allows us, from one instance of Guix, to interact with | ||
| 42 | ;;; another instance of Guix coming from a different commit. | ||
| 43 | ;;; | ||
| 44 | ;;; Code: | ||
| 45 | |||
| 46 | ;; Inferior Guix process. | ||
| 47 | (define-record-type <inferior> | ||
| 48 | (inferior pid socket version) | ||
| 49 | inferior? | ||
| 50 | (pid inferior-pid) | ||
| 51 | (socket inferior-socket) | ||
| 52 | (version inferior-version)) ;REPL protocol version | ||
| 53 | |||
| 54 | (define (inferior-pipe directory command) | ||
| 55 | "Return an input/output pipe on the Guix instance in DIRECTORY. This runs | ||
| 56 | 'DIRECTORY/COMMAND repl' if it exists, or falls back to some other method if | ||
| 57 | it's an old Guix." | ||
| 58 | (let ((pipe (with-error-to-port (%make-void-port "w") | ||
| 59 | (lambda () | ||
| 60 | (open-pipe* OPEN_BOTH | ||
| 61 | (string-append directory "/" command) | ||
| 62 | "repl" "-t" "machine"))))) | ||
| 63 | (if (eof-object? (peek-char pipe)) | ||
| 64 | (begin | ||
| 65 | (close-pipe pipe) | ||
| 66 | |||
| 67 | ;; Older versions of Guix didn't have a 'guix repl' command, so | ||
| 68 | ;; emulate it. | ||
| 69 | (open-pipe* OPEN_BOTH "guile" | ||
| 70 | "-L" (string-append directory "/share/guile/site/" | ||
| 71 | (effective-version)) | ||
| 72 | "-C" (string-append directory "/share/guile/site/" | ||
| 73 | (effective-version)) | ||
| 74 | "-C" (string-append directory "/lib/guile/" | ||
| 75 | (effective-version) "/site-ccache") | ||
| 76 | "-c" | ||
| 77 | (object->string | ||
| 78 | `(begin | ||
| 79 | (primitive-load ,(search-path %load-path | ||
| 80 | "guix/scripts/repl.scm")) | ||
| 81 | ((@ (guix scripts repl) machine-repl)))))) | ||
| 82 | pipe))) | ||
| 83 | |||
| 84 | (define* (open-inferior directory #:key (command "bin/guix")) | ||
| 85 | "Open the inferior Guix in DIRECTORY, running 'DIRECTORY/COMMAND repl' or | ||
| 86 | equivalent. Return #f if the inferior could not be launched." | ||
| 87 | (define pipe | ||
| 88 | (inferior-pipe directory command)) | ||
| 89 | |||
| 90 | (setvbuf pipe _IOLBF) | ||
| 91 | (match (read pipe) | ||
| 92 | (('repl-version 0 rest ...) | ||
| 93 | (let ((result (inferior 'pipe pipe (cons 0 rest)))) | ||
| 94 | (inferior-eval '(use-modules (guix)) result) | ||
| 95 | (inferior-eval '(use-modules (gnu)) result) | ||
| 96 | (inferior-eval '(define %package-table (make-hash-table)) | ||
| 97 | result) | ||
| 98 | result)) | ||
| 99 | (_ | ||
| 100 | #f))) | ||
| 101 | |||
| 102 | (define (close-inferior inferior) | ||
| 103 | "Close INFERIOR." | ||
| 104 | (close-pipe (inferior-socket inferior))) | ||
| 105 | |||
| 106 | ;; Non-self-quoting object of the inferior. | ||
| 107 | (define-record-type <inferior-object> | ||
| 108 | (inferior-object address appearance) | ||
| 109 | inferior-object? | ||
| 110 | (address inferior-object-address) | ||
| 111 | (appearance inferior-object-appearance)) | ||
| 112 | |||
| 113 | (define (write-inferior-object object port) | ||
| 114 | (match object | ||
| 115 | (($ <inferior-object> _ appearance) | ||
| 116 | (format port "#<inferior-object ~a>" appearance)))) | ||
| 117 | |||
| 118 | (set-record-type-printer! <inferior-object> write-inferior-object) | ||
| 119 | |||
| 120 | (define (inferior-eval exp inferior) | ||
| 121 | "Evaluate EXP in INFERIOR." | ||
| 122 | (define sexp->object | ||
| 123 | (match-lambda | ||
| 124 | (('value value) | ||
| 125 | value) | ||
| 126 | (('non-self-quoting address string) | ||
| 127 | (inferior-object address string)))) | ||
| 128 | |||
| 129 | (write exp (inferior-socket inferior)) | ||
| 130 | (newline (inferior-socket inferior)) | ||
| 131 | (match (read (inferior-socket inferior)) | ||
| 132 | (('values objects ...) | ||
| 133 | (apply values (map sexp->object objects))) | ||
| 134 | (('exception key objects ...) | ||
| 135 | (apply throw key (map sexp->object objects))))) | ||
| 136 | |||
| 137 | |||
| 138 | ;;; | ||
| 139 | ;;; Inferior packages. | ||
| 140 | ;;; | ||
| 141 | |||
| 142 | (define-record-type <inferior-package> | ||
| 143 | (inferior-package inferior name version id) | ||
| 144 | inferior-package? | ||
| 145 | (inferior inferior-package-inferior) | ||
| 146 | (name inferior-package-name) | ||
| 147 | (version inferior-package-version) | ||
| 148 | (id inferior-package-id)) | ||
| 149 | |||
| 150 | (define (write-inferior-package package port) | ||
| 151 | (match package | ||
| 152 | (($ <inferior-package> _ name version) | ||
| 153 | (format port "#<inferior-package ~a@~a ~a>" | ||
| 154 | name version | ||
| 155 | (number->string (object-address package) 16))))) | ||
| 156 | |||
| 157 | (set-record-type-printer! <inferior-package> write-inferior-package) | ||
| 158 | |||
| 159 | (define (inferior-packages inferior) | ||
| 160 | "Return the list of packages known to INFERIOR." | ||
| 161 | (let ((result (inferior-eval | ||
| 162 | '(fold-packages (lambda (package result) | ||
| 163 | (let ((id (object-address package))) | ||
| 164 | (hashv-set! %package-table id package) | ||
| 165 | (cons (list (package-name package) | ||
| 166 | (package-version package) | ||
| 167 | id) | ||
| 168 | result))) | ||
| 169 | '()) | ||
| 170 | inferior))) | ||
| 171 | (map (match-lambda | ||
| 172 | ((name version id) | ||
| 173 | (inferior-package inferior name version id))) | ||
| 174 | result))) | ||
| 175 | |||
| 176 | (define (inferior-package-field package getter) | ||
| 177 | "Return the field of PACKAGE, an inferior package, accessed with GETTER." | ||
| 178 | (let ((inferior (inferior-package-inferior package)) | ||
| 179 | (id (inferior-package-id package))) | ||
| 180 | (inferior-eval `(,getter (hashv-ref %package-table ,id)) | ||
| 181 | inferior))) | ||
| 182 | |||
| 183 | (define* (inferior-package-synopsis package #:key (translate? #t)) | ||
| 184 | "Return the Texinfo synopsis of PACKAGE, an inferior package. When | ||
| 185 | TRANSLATE? is true, translate it to the current locale's language." | ||
| 186 | (inferior-package-field package | ||
| 187 | (if translate? | ||
| 188 | '(compose (@ (guix ui) P_) package-synopsis) | ||
| 189 | 'package-synopsis))) | ||
| 190 | |||
| 191 | (define* (inferior-package-description package #:key (translate? #t)) | ||
| 192 | "Return the Texinfo description of PACKAGE, an inferior package. When | ||
| 193 | TRANSLATE? is true, translate it to the current locale's language." | ||
| 194 | (inferior-package-field package | ||
| 195 | (if translate? | ||
| 196 | '(compose (@ (guix ui) P_) package-description) | ||
| 197 | 'package-description))) | ||
diff --git a/guix/scripts/repl.scm b/guix/scripts/repl.scm new file mode 100644 index 00000000000..b157833a496 --- /dev/null +++ b/guix/scripts/repl.scm | |||
| @@ -0,0 +1,199 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> | ||
| 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 | |||
| 19 | (define-module (guix scripts repl) | ||
| 20 | #:use-module (guix ui) | ||
| 21 | #:use-module (guix scripts) | ||
| 22 | #:use-module (guix utils) | ||
| 23 | #:use-module (guix packages) | ||
| 24 | #:use-module (gnu packages) | ||
| 25 | #:use-module (srfi srfi-1) | ||
| 26 | #:use-module (srfi srfi-37) | ||
| 27 | #:use-module (ice-9 match) | ||
| 28 | #:use-module (rnrs bytevectors) | ||
| 29 | #:autoload (system repl repl) (start-repl) | ||
| 30 | #:autoload (system repl server) | ||
| 31 | (make-tcp-server-socket make-unix-domain-server-socket) | ||
| 32 | #:export (machine-repl | ||
| 33 | guix-repl)) | ||
| 34 | |||
| 35 | ;;; Commentary: | ||
| 36 | ;;; | ||
| 37 | ;;; This command provides a Guile REPL | ||
| 38 | |||
| 39 | (define %default-options | ||
| 40 | `((type . guile))) | ||
| 41 | |||
| 42 | (define %options | ||
| 43 | (list (option '(#\h "help") #f #f | ||
| 44 | (lambda args | ||
| 45 | (show-help) | ||
| 46 | (exit 0))) | ||
| 47 | (option '(#\V "version") #f #f | ||
| 48 | (lambda args | ||
| 49 | (show-version-and-exit "guix repl"))) | ||
| 50 | (option '(#\t "type") #t #f | ||
| 51 | (lambda (opt name arg result) | ||
| 52 | (alist-cons 'type (string->symbol arg) result))) | ||
| 53 | (option '("listen") #t #f | ||
| 54 | (lambda (opt name arg result) | ||
| 55 | (alist-cons 'listen arg result))))) | ||
| 56 | |||
| 57 | |||
| 58 | (define (show-help) | ||
| 59 | (display (G_ "Usage: guix repl [OPTIONS...] | ||
| 60 | Start a Guile REPL in the Guix execution environment.\n")) | ||
| 61 | (display (G_ " | ||
| 62 | -t, --type=TYPE start a REPL of the given TYPE")) | ||
| 63 | (newline) | ||
| 64 | (display (G_ " | ||
| 65 | -h, --help display this help and exit")) | ||
| 66 | (display (G_ " | ||
| 67 | -V, --version display version information and exit")) | ||
| 68 | (newline) | ||
| 69 | (show-bug-report-information)) | ||
| 70 | |||
| 71 | (define (self-quoting? x) | ||
| 72 | "Return #t if X is self-quoting." | ||
| 73 | (letrec-syntax ((one-of (syntax-rules () | ||
| 74 | ((_) #f) | ||
| 75 | ((_ pred rest ...) | ||
| 76 | (or (pred x) | ||
| 77 | (one-of rest ...)))))) | ||
| 78 | (one-of symbol? string? pair? null? vector? | ||
| 79 | bytevector? number? boolean?))) | ||
| 80 | |||
| 81 | (define user-module | ||
| 82 | ;; Module where we execute user code. | ||
| 83 | (let ((module (resolve-module '(guix-user) #f #f #:ensure #t))) | ||
| 84 | (beautify-user-module! module) | ||
| 85 | module)) | ||
| 86 | |||
| 87 | (define* (machine-repl #:optional | ||
| 88 | (input (current-input-port)) | ||
| 89 | (output (current-output-port))) | ||
| 90 | "Run a machine-usable REPL over ports INPUT and OUTPUT. | ||
| 91 | |||
| 92 | The protocol of this REPL is meant to be machine-readable and provides proper | ||
| 93 | support to represent multiple-value returns, exceptions, objects that lack a | ||
| 94 | read syntax, and so on. As such it is more convenient and robust than parsing | ||
| 95 | Guile's REPL prompt." | ||
| 96 | (define (value->sexp value) | ||
| 97 | (if (self-quoting? value) | ||
| 98 | `(value ,value) | ||
| 99 | `(non-self-quoting ,(object-address value) | ||
| 100 | ,(object->string value)))) | ||
| 101 | |||
| 102 | (write `(repl-version 0 0) output) | ||
| 103 | (newline output) | ||
| 104 | (force-output output) | ||
| 105 | |||
| 106 | (let loop () | ||
| 107 | (match (read input) | ||
| 108 | ((? eof-object?) #t) | ||
| 109 | (exp | ||
| 110 | (catch #t | ||
| 111 | (lambda () | ||
| 112 | (let ((results (call-with-values | ||
| 113 | (lambda () | ||
| 114 | |||
| 115 | (primitive-eval exp)) | ||
| 116 | list))) | ||
| 117 | (write `(values ,@(map value->sexp results)) | ||
| 118 | output) | ||
| 119 | (newline output) | ||
| 120 | (force-output output))) | ||
| 121 | (lambda (key . args) | ||
| 122 | (write `(exception ,key ,@(map value->sexp args))) | ||
| 123 | (newline output) | ||
| 124 | (force-output output))) | ||
| 125 | (loop))))) | ||
| 126 | |||
| 127 | (define (call-with-connection spec thunk) | ||
| 128 | "Dynamically-bind the current input and output ports according to SPEC and | ||
| 129 | call THUNK." | ||
| 130 | (if (not spec) | ||
| 131 | (thunk) | ||
| 132 | |||
| 133 | ;; Note: the "PROTO:" prefix in SPEC is here so that we can eventually | ||
| 134 | ;; parse things like "fd:123" in a non-ambiguous way. | ||
| 135 | (match (string-index spec #\:) | ||
| 136 | (#f | ||
| 137 | (leave (G_ "~A: invalid listen specification~%") spec)) | ||
| 138 | (index | ||
| 139 | (let ((protocol (string-take spec index)) | ||
| 140 | (address (string-drop spec (+ index 1)))) | ||
| 141 | (define socket | ||
| 142 | (match protocol | ||
| 143 | ("tcp" | ||
| 144 | (make-tcp-server-socket #:port (string->number address))) | ||
| 145 | ("unix" | ||
| 146 | (make-unix-domain-server-socket #:path address)) | ||
| 147 | (_ | ||
| 148 | (leave (G_ "~A: unsupported protocol family~%") | ||
| 149 | protocol)))) | ||
| 150 | |||
| 151 | (listen socket 10) | ||
| 152 | (let loop () | ||
| 153 | (match (accept socket) | ||
| 154 | ((connection . address) | ||
| 155 | (if (= AF_UNIX (sockaddr:fam address)) | ||
| 156 | (info (G_ "accepted connection~%")) | ||
| 157 | (info (G_ "accepted connection from ~a~%") | ||
| 158 | (inet-ntop (sockaddr:fam address) | ||
| 159 | (sockaddr:addr address)))) | ||
| 160 | (dynamic-wind | ||
| 161 | (const #t) | ||
| 162 | (lambda () | ||
| 163 | (parameterize ((current-input-port connection) | ||
| 164 | (current-output-port connection)) | ||
| 165 | (thunk))) | ||
| 166 | (lambda () | ||
| 167 | (false-if-exception (close-port connection)) | ||
| 168 | (info (G_ "connection closed~%")))))) | ||
| 169 | (loop))))))) | ||
| 170 | |||
| 171 | |||
| 172 | (define (guix-repl . args) | ||
| 173 | (define opts | ||
| 174 | ;; Return the list of package names. | ||
| 175 | (args-fold* args %options | ||
| 176 | (lambda (opt name arg result) | ||
| 177 | (leave (G_ "~A: unrecognized option~%") name)) | ||
| 178 | (lambda (arg result) | ||
| 179 | (leave (G_ "~A: extraneous argument~%") arg)) | ||
| 180 | %default-options)) | ||
| 181 | |||
| 182 | (with-error-handling | ||
| 183 | (let ((type (assoc-ref opts 'type))) | ||
| 184 | (call-with-connection (assoc-ref opts 'listen) | ||
| 185 | (lambda () | ||
| 186 | (case type | ||
| 187 | ((guile) | ||
| 188 | (save-module-excursion | ||
| 189 | (lambda () | ||
| 190 | (set-current-module user-module) | ||
| 191 | (start-repl)))) | ||
| 192 | ((machine) | ||
| 193 | (machine-repl)) | ||
| 194 | (else | ||
| 195 | (leave (G_ "~a: unknown type of REPL~%") type)))))))) | ||
| 196 | |||
| 197 | ;; Local Variables: | ||
| 198 | ;; eval: (put 'call-with-connection 'scheme-indent-function 1) | ||
| 199 | ;; End: | ||
diff --git a/tests/inferior.scm b/tests/inferior.scm new file mode 100644 index 00000000000..5e0f8ae66e0 --- /dev/null +++ b/tests/inferior.scm | |||
| @@ -0,0 +1,69 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | ||
| 2 | ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org> | ||
| 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 | |||
| 19 | (define-module (test-inferior) | ||
| 20 | #:use-module (guix inferior) | ||
| 21 | #:use-module (guix packages) | ||
| 22 | #:use-module (gnu packages) | ||
| 23 | #:use-module (srfi srfi-1) | ||
| 24 | #:use-module (srfi srfi-64)) | ||
| 25 | |||
| 26 | (define %top-srcdir | ||
| 27 | (dirname (search-path %load-path "guix.scm"))) | ||
| 28 | |||
| 29 | (define %top-builddir | ||
| 30 | (dirname (search-path %load-compiled-path "guix.go"))) | ||
| 31 | |||
| 32 | |||
| 33 | (test-begin "inferior") | ||
| 34 | |||
| 35 | (test-equal "open-inferior" | ||
| 36 | '(42 #t) | ||
| 37 | (let ((inferior (open-inferior %top-builddir | ||
| 38 | #:command "scripts/guix"))) | ||
| 39 | (and (inferior? inferior) | ||
| 40 | (let ((a (inferior-eval '(apply * '(6 7)) inferior)) | ||
| 41 | (b (inferior-eval '(@ (gnu packages base) coreutils) | ||
| 42 | inferior))) | ||
| 43 | (close-inferior inferior) | ||
| 44 | (list a (inferior-object? b)))))) | ||
| 45 | |||
| 46 | (test-equal "inferior-packages" | ||
| 47 | (take (sort (fold-packages (lambda (package lst) | ||
| 48 | (alist-cons (package-name package) | ||
| 49 | (package-version package) | ||
| 50 | lst)) | ||
| 51 | '()) | ||
| 52 | (lambda (x y) | ||
| 53 | (string<? (car x) (car y)))) | ||
| 54 | 10) | ||
| 55 | (let* ((inferior (open-inferior %top-builddir | ||
| 56 | #:command "scripts/guix")) | ||
| 57 | (packages (inferior-packages inferior))) | ||
| 58 | (and (every string? (map inferior-package-synopsis packages)) | ||
| 59 | (begin | ||
| 60 | (close-inferior inferior) | ||
| 61 | (take (sort (map (lambda (package) | ||
| 62 | (cons (inferior-package-name package) | ||
| 63 | (inferior-package-version package))) | ||
| 64 | packages) | ||
| 65 | (lambda (x y) | ||
| 66 | (string<? (car x) (car y)))) | ||
| 67 | 10))))) | ||
| 68 | |||
| 69 | (test-end "inferior") | ||
