201 lines
7.3 KiB
Scheme
201 lines
7.3 KiB
Scheme
;;; GNU Guix --- Functional package management for GNU
|
||
;;; Copyright © 2018 Ludovic Courtès <ludo@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 (guix inferior)
|
||
#:use-module (srfi srfi-9)
|
||
#:use-module (srfi srfi-9 gnu)
|
||
#:use-module (ice-9 match)
|
||
#:use-module (ice-9 popen)
|
||
#:export (inferior?
|
||
open-inferior
|
||
close-inferior
|
||
inferior-eval
|
||
inferior-object?
|
||
|
||
inferior-package?
|
||
inferior-package-name
|
||
inferior-package-version
|
||
|
||
inferior-packages
|
||
inferior-package-synopsis
|
||
inferior-package-description))
|
||
|
||
;;; Commentary:
|
||
;;;
|
||
;;; This module provides a way to spawn Guix "inferior" processes and to talk
|
||
;;; to them. It allows us, from one instance of Guix, to interact with
|
||
;;; another instance of Guix coming from a different commit.
|
||
;;;
|
||
;;; Code:
|
||
|
||
;; Inferior Guix process.
|
||
(define-record-type <inferior>
|
||
(inferior pid socket version)
|
||
inferior?
|
||
(pid inferior-pid)
|
||
(socket inferior-socket)
|
||
(version inferior-version)) ;REPL protocol version
|
||
|
||
(define (inferior-pipe directory command)
|
||
"Return an input/output pipe on the Guix instance in DIRECTORY. This runs
|
||
'DIRECTORY/COMMAND repl' if it exists, or falls back to some other method if
|
||
it's an old Guix."
|
||
(let ((pipe (with-error-to-port (%make-void-port "w")
|
||
(lambda ()
|
||
(open-pipe* OPEN_BOTH
|
||
(string-append directory "/" command)
|
||
"repl" "-t" "machine")))))
|
||
(if (eof-object? (peek-char pipe))
|
||
(begin
|
||
(close-pipe pipe)
|
||
|
||
;; Older versions of Guix didn't have a 'guix repl' command, so
|
||
;; emulate it.
|
||
(open-pipe* OPEN_BOTH "guile"
|
||
"-L" (string-append directory "/share/guile/site/"
|
||
(effective-version))
|
||
"-C" (string-append directory "/share/guile/site/"
|
||
(effective-version))
|
||
"-C" (string-append directory "/lib/guile/"
|
||
(effective-version) "/site-ccache")
|
||
"-c"
|
||
(object->string
|
||
`(begin
|
||
(primitive-load ,(search-path %load-path
|
||
"guix/scripts/repl.scm"))
|
||
((@ (guix scripts repl) machine-repl))))))
|
||
pipe)))
|
||
|
||
(define* (open-inferior directory #:key (command "bin/guix"))
|
||
"Open the inferior Guix in DIRECTORY, running 'DIRECTORY/COMMAND repl' or
|
||
equivalent. Return #f if the inferior could not be launched."
|
||
(define pipe
|
||
(inferior-pipe directory command))
|
||
|
||
(cond-expand
|
||
((and guile-2 (not guile-2.2)) #t)
|
||
(else (setvbuf pipe 'line)))
|
||
|
||
(match (read pipe)
|
||
(('repl-version 0 rest ...)
|
||
(let ((result (inferior 'pipe pipe (cons 0 rest))))
|
||
(inferior-eval '(use-modules (guix)) result)
|
||
(inferior-eval '(use-modules (gnu)) result)
|
||
(inferior-eval '(define %package-table (make-hash-table))
|
||
result)
|
||
result))
|
||
(_
|
||
#f)))
|
||
|
||
(define (close-inferior inferior)
|
||
"Close INFERIOR."
|
||
(close-pipe (inferior-socket inferior)))
|
||
|
||
;; Non-self-quoting object of the inferior.
|
||
(define-record-type <inferior-object>
|
||
(inferior-object address appearance)
|
||
inferior-object?
|
||
(address inferior-object-address)
|
||
(appearance inferior-object-appearance))
|
||
|
||
(define (write-inferior-object object port)
|
||
(match object
|
||
(($ <inferior-object> _ appearance)
|
||
(format port "#<inferior-object ~a>" appearance))))
|
||
|
||
(set-record-type-printer! <inferior-object> write-inferior-object)
|
||
|
||
(define (inferior-eval exp inferior)
|
||
"Evaluate EXP in INFERIOR."
|
||
(define sexp->object
|
||
(match-lambda
|
||
(('value value)
|
||
value)
|
||
(('non-self-quoting address string)
|
||
(inferior-object address string))))
|
||
|
||
(write exp (inferior-socket inferior))
|
||
(newline (inferior-socket inferior))
|
||
(match (read (inferior-socket inferior))
|
||
(('values objects ...)
|
||
(apply values (map sexp->object objects)))
|
||
(('exception key objects ...)
|
||
(apply throw key (map sexp->object objects)))))
|
||
|
||
|
||
;;;
|
||
;;; Inferior packages.
|
||
;;;
|
||
|
||
(define-record-type <inferior-package>
|
||
(inferior-package inferior name version id)
|
||
inferior-package?
|
||
(inferior inferior-package-inferior)
|
||
(name inferior-package-name)
|
||
(version inferior-package-version)
|
||
(id inferior-package-id))
|
||
|
||
(define (write-inferior-package package port)
|
||
(match package
|
||
(($ <inferior-package> _ name version)
|
||
(format port "#<inferior-package ~a@~a ~a>"
|
||
name version
|
||
(number->string (object-address package) 16)))))
|
||
|
||
(set-record-type-printer! <inferior-package> write-inferior-package)
|
||
|
||
(define (inferior-packages inferior)
|
||
"Return the list of packages known to INFERIOR."
|
||
(let ((result (inferior-eval
|
||
'(fold-packages (lambda (package result)
|
||
(let ((id (object-address package)))
|
||
(hashv-set! %package-table id package)
|
||
(cons (list (package-name package)
|
||
(package-version package)
|
||
id)
|
||
result)))
|
||
'())
|
||
inferior)))
|
||
(map (match-lambda
|
||
((name version id)
|
||
(inferior-package inferior name version id)))
|
||
result)))
|
||
|
||
(define (inferior-package-field package getter)
|
||
"Return the field of PACKAGE, an inferior package, accessed with GETTER."
|
||
(let ((inferior (inferior-package-inferior package))
|
||
(id (inferior-package-id package)))
|
||
(inferior-eval `(,getter (hashv-ref %package-table ,id))
|
||
inferior)))
|
||
|
||
(define* (inferior-package-synopsis package #:key (translate? #t))
|
||
"Return the Texinfo synopsis of PACKAGE, an inferior package. When
|
||
TRANSLATE? is true, translate it to the current locale's language."
|
||
(inferior-package-field package
|
||
(if translate?
|
||
'(compose (@ (guix ui) P_) package-synopsis)
|
||
'package-synopsis)))
|
||
|
||
(define* (inferior-package-description package #:key (translate? #t))
|
||
"Return the Texinfo description of PACKAGE, an inferior package. When
|
||
TRANSLATE? is true, translate it to the current locale's language."
|
||
(inferior-package-field package
|
||
(if translate?
|
||
'(compose (@ (guix ui) P_) package-description)
|
||
'package-description)))
|