Subject: [PATCH 1/2] Add (guix inferior).

From: Ludovic Courtès <[email-masked]>
Date: Tue, 10 Jul 2018 18:48:05 +0200

* guix/inferior.scm, tests/inferior.scm: New files.
---
 Makefile.am        |   2 +
 guix/inferior.scm  | 197 +++++++++++++++++++++++++++++++++++++++++++++
 tests/inferior.scm |  69 ++++++++++++++++
 3 files changed, 268 insertions(+)
 create mode 100644 guix/inferior.scm
 create mode 100644 tests/inferior.scm

diff --git a/Makefile.am b/Makefile.am
index 5dc04de35..25b1d501f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -85,6 +85,7 @@ MODULES =					\
   guix/nar.scm					\
   guix/derivations.scm				\
   guix/grafts.scm				\
+  guix/inferior.scm				\
   guix/gnu-maintenance.scm			\
   guix/self.scm					\
   guix/upstream.scm				\
@@ -355,6 +356,7 @@ SCM_TESTS =					\
   tests/profiles.scm				\
   tests/search-paths.scm			\
   tests/syscalls.scm				\
+  tests/inferior.scm				\
   tests/gremlin.scm				\
   tests/bournish.scm				\
   tests/lint.scm				\
diff --git a/guix/inferior.scm b/guix/inferior.scm
new file mode 100644
index 000000000..629c2c431
--- /dev/null
+++ b/guix/inferior.scm
@@ -0,0 +1,197 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2018 Ludovic Courtès <[email-masked]>
+;;;
+;;; 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))
+
+  (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>
+  (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)))
diff --git a/tests/inferior.scm b/tests/inferior.scm
new file mode 100644
index 000000000..5e0f8ae66
--- /dev/null
+++ b/tests/inferior.scm
@@ -0,0 +1,69 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2018 Ludovic Courtès <[email-masked]>
+;;;
+;;; 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 (test-inferior)
+  #:use-module (guix inferior)
+  #:use-module (guix packages)
+  #:use-module (gnu packages)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-64))
+
+(define %top-srcdir
+  (dirname (search-path %load-path "guix.scm")))
+
+(define %top-builddir
+  (dirname (search-path %load-compiled-path "guix.go")))
+
+
+(test-begin "inferior")
+
+(test-equal "open-inferior"
+  '(42 #t)
+  (let ((inferior (open-inferior %top-builddir
+                                 #:command "scripts/guix")))
+    (and (inferior? inferior)
+         (let ((a (inferior-eval '(apply * '(6 7)) inferior))
+               (b (inferior-eval '(@ (gnu packages base) coreutils)
+                                 inferior)))
+           (close-inferior inferior)
+           (list a (inferior-object? b))))))
+
+(test-equal "inferior-packages"
+  (take (sort (fold-packages (lambda (package lst)
+                               (alist-cons (package-name package)
+                                           (package-version package)
+                                           lst))
+                             '())
+              (lambda (x y)
+                (string<? (car x) (car y))))
+        10)
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (packages (inferior-packages inferior)))
+    (and (every string? (map inferior-package-synopsis packages))
+         (begin
+           (close-inferior inferior)
+           (take (sort (map (lambda (package)
+                              (cons (inferior-package-name package)
+                                    (inferior-package-version package)))
+                            packages)
+                       (lambda (x y)
+                         (string<? (car x) (car y))))
+                 10)))))
+
+(test-end "inferior")
-- 
2.18.0