summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am3
-rw-r--r--doc/guix.texi53
-rw-r--r--guix/inferior.scm197
-rw-r--r--guix/scripts/repl.scm199
-rw-r--r--tests/inferior.scm69
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
170Defining Packages 171Defining 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
5544has an associated gexp compiler, such as a @code{<package>}. 5546has 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
5553The @command{guix repl} command spawns a Guile @dfn{read-eval-print loop}
5554(REPL) for interactive programming (@pxref{Using Guile Interactively,,, guile,
5555GNU Guile Reference Manual}). Compared to just launching the @command{guile}
5556command, @command{guix repl} guarantees that all the Guix modules and all its
5557dependencies are available in the search path. You can use it this way:
5558
5559@example
5560$ guix repl
5561scheme@@(guile-user)> ,use (gnu packages base)
5562scheme@@(guile-user)> coreutils
5563$1 = #<package coreutils@@8.29 gnu/packages/base.scm:327 3e28300>
5564@end example
5565
5566@cindex inferiors
5567In addition, @command{guix repl} implements a simple machine-readable REPL
5568protocol for use by @code{(guix inferior)}, a facility to interact with
5569@dfn{inferiors}, separate processes running a potentially different revision
5570of Guix.
5571
5572The available options are as follows:
5573
5574@table @code
5575@item --type=@var{type}
5576@itemx -t @var{type}
5577Start a REPL of the given @var{TYPE}, which can be one of the following:
5578
5579@table @code
5580@item guile
5581This is default, and it spawns a standard full-featured Guile REPL.
5582@item machine
5583Spawn a REPL that uses the machine-readable protocol. This is the protocol
5584that the @code{(guix inferior)} module speaks.
5585@end table
5586
5587@item --listen=@var{endpoint}
5588By default, @command{guix repl} reads from standard input and writes to
5589standard output. When this option is passed, it will instead listen for
5590connections on @var{endpoint}. Here are examples of valid options:
5591
5592@table @code
5593@item --listen=tcp:37146
5594Accept connections on localhost on port 37146.
5595
5596@item --listen=unix:/tmp/socket
5597Accept 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
57it'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
86equivalent. 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
185TRANSLATE? 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
193TRANSLATE? 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...]
60Start 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
92The protocol of this REPL is meant to be machine-readable and provides proper
93support to represent multiple-value returns, exceptions, objects that lack a
94read syntax, and so on. As such it is more convenient and robust than parsing
95Guile'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
129call 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")