;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2018 Ludovic Courtès ;;; ;;; 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 . (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 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)) (setvbuf pipe _IOLBF) (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 address appearance) inferior-object? (address inferior-object-address) (appearance inferior-object-appearance)) (define (write-inferior-object object port) (match object (($ _ appearance) (format port "#" appearance)))) (set-record-type-printer! 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 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 (($ _ name version) (format port "#" name version (number->string (object-address package) 16))))) (set-record-type-printer! 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)))