1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
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.
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.
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/>.
19 (define-module (guix inferior)
20 #:use-module (srfi srfi-9)
21 #:use-module (srfi srfi-9 gnu)
22 #:use-module ((guix utils)
23 #:select (%current-system
24 source-properties->location
25 call-with-temporary-directory))
26 #:use-module ((guix store)
27 #:select (nix-server-socket
28 nix-server-major-version
29 nix-server-minor-version
31 #:use-module ((guix derivations)
32 #:select (read-derivation-from-file))
33 #:use-module (guix gexp)
34 #:use-module (ice-9 match)
35 #:use-module (ice-9 popen)
36 #:use-module (ice-9 binary-ports)
45 inferior-package-version
48 inferior-package-synopsis
49 inferior-package-description
50 inferior-package-home-page
51 inferior-package-location
52 inferior-package-derivation))
56 ;;; This module provides a way to spawn Guix "inferior" processes and to talk
57 ;;; to them. It allows us, from one instance of Guix, to interact with
58 ;;; another instance of Guix coming from a different commit.
62 ;; Inferior Guix process.
63 (define-record-type <inferior>
64 (inferior pid socket version)
67 (socket inferior-socket)
68 (version inferior-version)) ;REPL protocol version
70 (define (inferior-pipe directory command)
71 "Return an input/output pipe on the Guix instance in DIRECTORY. This runs
72 'DIRECTORY/COMMAND repl' if it exists, or falls back to some other method if
74 (let ((pipe (with-error-to-port (%make-void-port "w")
77 (string-append directory "/" command)
78 "repl" "-t" "machine")))))
79 (if (eof-object? (peek-char pipe))
83 ;; Older versions of Guix didn't have a 'guix repl' command, so
85 (open-pipe* OPEN_BOTH "guile"
86 "-L" (string-append directory "/share/guile/site/"
88 "-C" (string-append directory "/share/guile/site/"
90 "-C" (string-append directory "/lib/guile/"
91 (effective-version) "/site-ccache")
95 (primitive-load ,(search-path %load-path
96 "guix/scripts/repl.scm"))
97 ((@ (guix scripts repl) machine-repl))))))
100 (define* (open-inferior directory #:key (command "bin/guix"))
101 "Open the inferior Guix in DIRECTORY, running 'DIRECTORY/COMMAND repl' or
102 equivalent. Return #f if the inferior could not be launched."
104 (inferior-pipe directory command))
107 ((and guile-2 (not guile-2.2)) #t)
108 (else (setvbuf pipe 'line)))
111 (('repl-version 0 rest ...)
112 (let ((result (inferior 'pipe pipe (cons 0 rest))))
113 (inferior-eval '(use-modules (guix)) result)
114 (inferior-eval '(use-modules (gnu)) result)
115 (inferior-eval '(define %package-table (make-hash-table))
121 (define (close-inferior inferior)
123 (close-pipe (inferior-socket inferior)))
125 ;; Non-self-quoting object of the inferior.
126 (define-record-type <inferior-object>
127 (inferior-object address appearance)
129 (address inferior-object-address)
130 (appearance inferior-object-appearance))
132 (define (write-inferior-object object port)
134 (($ <inferior-object> _ appearance)
135 (format port "#<inferior-object ~a>" appearance))))
137 (set-record-type-printer! <inferior-object> write-inferior-object)
139 (define (read-inferior-response inferior)
144 (('non-self-quoting address string)
145 (inferior-object address string))))
147 (match (read (inferior-socket inferior))
148 (('values objects ...)
149 (apply values (map sexp->object objects)))
150 (('exception key objects ...)
151 (apply throw key (map sexp->object objects)))))
153 (define (send-inferior-request exp inferior)
154 (write exp (inferior-socket inferior))
155 (newline (inferior-socket inferior)))
157 (define (inferior-eval exp inferior)
158 "Evaluate EXP in INFERIOR."
159 (send-inferior-request exp inferior)
160 (read-inferior-response inferior))
164 ;;; Inferior packages.
167 (define-record-type <inferior-package>
168 (inferior-package inferior name version id)
170 (inferior inferior-package-inferior)
171 (name inferior-package-name)
172 (version inferior-package-version)
173 (id inferior-package-id))
175 (define (write-inferior-package package port)
177 (($ <inferior-package> _ name version)
178 (format port "#<inferior-package ~a@~a ~a>"
180 (number->string (object-address package) 16)))))
182 (set-record-type-printer! <inferior-package> write-inferior-package)
184 (define (inferior-packages inferior)
185 "Return the list of packages known to INFERIOR."
186 (let ((result (inferior-eval
187 '(fold-packages (lambda (package result)
188 (let ((id (object-address package)))
189 (hashv-set! %package-table id package)
190 (cons (list (package-name package)
191 (package-version package)
198 (inferior-package inferior name version id)))
201 (define (inferior-package-field package getter)
202 "Return the field of PACKAGE, an inferior package, accessed with GETTER."
203 (let ((inferior (inferior-package-inferior package))
204 (id (inferior-package-id package)))
205 (inferior-eval `(,getter (hashv-ref %package-table ,id))
208 (define* (inferior-package-synopsis package #:key (translate? #t))
209 "Return the Texinfo synopsis of PACKAGE, an inferior package. When
210 TRANSLATE? is true, translate it to the current locale's language."
211 (inferior-package-field package
213 '(compose (@ (guix ui) P_) package-synopsis)
216 (define* (inferior-package-description package #:key (translate? #t))
217 "Return the Texinfo description of PACKAGE, an inferior package. When
218 TRANSLATE? is true, translate it to the current locale's language."
219 (inferior-package-field package
221 '(compose (@ (guix ui) P_) package-description)
222 'package-description)))
224 (define (inferior-package-home-page package)
225 "Return the home page of PACKAGE."
226 (inferior-package-field package 'package-home-page))
228 (define (inferior-package-location package)
229 "Return the source code location of PACKAGE, either #f or a <location>
231 (source-properties->location
232 (inferior-package-field package
233 '(compose (lambda (loc)
235 (location->source-properties
239 (define (proxy client backend) ;adapted from (guix ssh)
240 "Proxy communication between CLIENT and BACKEND until CLIENT closes the
241 connection, at which point CLIENT is closed (both CLIENT and BACKEND must be
242 input/output ports.)"
243 (define (select* read write except)
244 ;; This is a workaround for <https://bugs.gnu.org/30365> in Guile < 2.2.4:
245 ;; since 'select' sometimes returns non-empty sets for no good reason,
246 ;; call 'select' a second time with a zero timeout to filter out incorrect
248 (match (select read write except)
250 (select read write except 0))))
252 ;; Use buffered ports so that 'get-bytevector-some' returns up to the
253 ;; whole buffer like read(2) would--see <https://bugs.gnu.org/30066>.
254 (setvbuf client _IOFBF 65536)
255 (setvbuf backend _IOFBF 65536)
258 (match (select* (list client backend) '() '())
260 (when (memq client reads)
261 (match (get-bytevector-some client)
265 (put-bytevector backend bv)
266 (force-output backend))))
267 (when (memq backend reads)
268 (match (get-bytevector-some backend)
270 (put-bytevector client bv)
271 (force-output client))))
272 (unless (port-closed? client)
275 (define* (inferior-package-derivation store package
277 (system (%current-system))
279 "Return the derivation for PACKAGE, an inferior package, built for SYSTEM
280 and cross-built for TARGET if TARGET is true. The inferior corresponding to
281 PACKAGE must be live."
282 ;; Create a named socket in /tmp and let the inferior of PACKAGE connect to
283 ;; it and use it as its store. This ensures the inferior uses the same
284 ;; store, with the same options, the same per-session GC roots, etc.
285 (call-with-temporary-directory
287 (chmod directory #o700)
288 (let* ((name (string-append directory "/inferior"))
289 (socket (socket AF_UNIX SOCK_STREAM 0))
290 (inferior (inferior-package-inferior package))
291 (major (nix-server-major-version store))
292 (minor (nix-server-minor-version store))
293 (proto (logior major minor)))
294 (bind socket AF_UNIX name)
296 (send-inferior-request
297 `(let ((socket (socket AF_UNIX SOCK_STREAM 0)))
298 (connect socket AF_UNIX ,name)
300 ;; 'port->connection' appeared in June 2018 and we can hardly
301 ;; emulate it on older versions. Thus fall back to
302 ;; 'open-connection', at the risk of talking to the wrong daemon or
303 ;; having our build result reclaimed (XXX).
304 (let* ((store (if (defined? 'port->connection)
305 (port->connection socket #:version ,proto)
307 (package (hashv-ref %package-table
308 ,(inferior-package-id package)))
310 `(package-cross-derivation store package
313 `(package-derivation store package
315 (close-connection store)
317 (derivation-file-name drv)))
319 (match (accept socket)
321 (proxy client (nix-server-socket store))))
323 (read-derivation-from-file (read-inferior-response inferior))))))
325 (define inferior-package->derivation
326 (store-lift inferior-package-derivation))
328 (define-gexp-compiler (package-compiler (package <inferior-package>) system
330 ;; Compile PACKAGE for SYSTEM, optionally cross-building for TARGET.
331 (inferior-package->derivation package system #:target target))