2018-07-10 14:18:36 +02:00
|
|
|
|
;;; GNU Guix --- Functional package management for GNU
|
2022-01-13 17:31:53 +01:00
|
|
|
|
;;; Copyright © 2018-2022 Ludovic Courtès <ludo@gnu.org>
|
2018-07-10 14:18:36 +02:00
|
|
|
|
;;;
|
|
|
|
|
;;; 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)
|
2019-09-20 22:26:53 +02:00
|
|
|
|
#:use-module (srfi srfi-34)
|
|
|
|
|
#:use-module (srfi srfi-35)
|
2020-07-24 22:58:08 +02:00
|
|
|
|
#:use-module ((guix diagnostics)
|
|
|
|
|
#:select (source-properties->location))
|
2018-09-14 17:30:06 +02:00
|
|
|
|
#:use-module ((guix utils)
|
|
|
|
|
#:select (%current-system
|
2022-01-27 00:20:12 +01:00
|
|
|
|
call-with-temporary-directory
|
2018-09-18 13:30:48 +02:00
|
|
|
|
version>? version-prefix?
|
|
|
|
|
cache-directory))
|
2018-09-14 17:30:06 +02:00
|
|
|
|
#:use-module ((guix store)
|
2019-01-21 15:32:35 +01:00
|
|
|
|
#:select (store-connection-socket
|
|
|
|
|
store-connection-major-version
|
|
|
|
|
store-connection-minor-version
|
2019-09-20 22:26:53 +02:00
|
|
|
|
store-lift
|
|
|
|
|
&store-protocol-error))
|
2018-09-14 17:30:06 +02:00
|
|
|
|
#:use-module ((guix derivations)
|
|
|
|
|
#:select (read-derivation-from-file))
|
|
|
|
|
#:use-module (guix gexp)
|
2018-09-17 10:04:15 +02:00
|
|
|
|
#:use-module (guix search-paths)
|
2018-09-18 09:56:34 +02:00
|
|
|
|
#:use-module (guix profiles)
|
2018-09-18 13:30:48 +02:00
|
|
|
|
#:use-module (guix channels)
|
2021-01-28 22:48:21 +01:00
|
|
|
|
#:use-module ((guix git) #:select (update-cached-checkout))
|
2018-09-18 13:30:48 +02:00
|
|
|
|
#:use-module (guix monads)
|
|
|
|
|
#:use-module (guix store)
|
|
|
|
|
#:use-module (guix derivations)
|
|
|
|
|
#:use-module (guix base32)
|
|
|
|
|
#:use-module (gcrypt hash)
|
2020-01-15 15:04:40 +01:00
|
|
|
|
#:autoload (guix cache) (maybe-remove-expired-cache-entries
|
|
|
|
|
file-expiration-time)
|
2021-08-09 17:37:54 +02:00
|
|
|
|
#:autoload (guix ui) (build-notifier)
|
2018-09-18 13:30:48 +02:00
|
|
|
|
#:autoload (guix build utils) (mkdir-p)
|
2018-09-15 14:50:14 +02:00
|
|
|
|
#:use-module (srfi srfi-1)
|
2018-09-17 09:55:31 +02:00
|
|
|
|
#:use-module (srfi srfi-26)
|
2021-01-28 22:48:21 +01:00
|
|
|
|
#:use-module (srfi srfi-71)
|
2018-09-18 13:30:48 +02:00
|
|
|
|
#:autoload (ice-9 ftw) (scandir)
|
2018-07-10 14:18:36 +02:00
|
|
|
|
#:use-module (ice-9 match)
|
2018-09-15 14:50:14 +02:00
|
|
|
|
#:use-module (ice-9 vlist)
|
2018-09-14 17:30:06 +02:00
|
|
|
|
#:use-module (ice-9 binary-ports)
|
2018-09-18 13:30:48 +02:00
|
|
|
|
#:use-module ((rnrs bytevectors) #:select (string->utf8))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
#:export (inferior?
|
|
|
|
|
open-inferior
|
2018-12-24 00:55:07 +01:00
|
|
|
|
port->inferior
|
2018-07-10 14:18:36 +02:00
|
|
|
|
close-inferior
|
|
|
|
|
inferior-eval
|
2018-11-26 11:48:33 +01:00
|
|
|
|
inferior-eval-with-store
|
2018-07-10 14:18:36 +02:00
|
|
|
|
inferior-object?
|
2020-03-10 16:45:57 +01:00
|
|
|
|
inferior-exception?
|
|
|
|
|
inferior-exception-arguments
|
|
|
|
|
inferior-exception-inferior
|
2020-03-15 17:26:45 +01:00
|
|
|
|
inferior-exception-stack
|
2019-06-10 17:11:43 +02:00
|
|
|
|
read-repl-response
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
2018-09-18 09:56:34 +02:00
|
|
|
|
inferior-packages
|
2019-02-12 22:17:11 +01:00
|
|
|
|
inferior-available-packages
|
2018-09-18 09:56:34 +02:00
|
|
|
|
lookup-inferior-packages
|
|
|
|
|
|
2018-07-10 14:18:36 +02:00
|
|
|
|
inferior-package?
|
|
|
|
|
inferior-package-name
|
|
|
|
|
inferior-package-version
|
|
|
|
|
inferior-package-synopsis
|
2018-09-04 17:22:55 +02:00
|
|
|
|
inferior-package-description
|
|
|
|
|
inferior-package-home-page
|
2018-09-14 17:30:06 +02:00
|
|
|
|
inferior-package-location
|
2018-09-17 09:55:31 +02:00
|
|
|
|
inferior-package-inputs
|
|
|
|
|
inferior-package-native-inputs
|
|
|
|
|
inferior-package-propagated-inputs
|
|
|
|
|
inferior-package-transitive-propagated-inputs
|
2018-09-17 10:04:15 +02:00
|
|
|
|
inferior-package-native-search-paths
|
|
|
|
|
inferior-package-transitive-native-search-paths
|
|
|
|
|
inferior-package-search-paths
|
2021-04-24 07:43:46 +02:00
|
|
|
|
inferior-package-replacement
|
2019-12-29 17:35:56 +01:00
|
|
|
|
inferior-package-provenance
|
2018-09-18 09:56:34 +02:00
|
|
|
|
inferior-package-derivation
|
|
|
|
|
|
2018-09-18 13:30:48 +02:00
|
|
|
|
inferior-package->manifest-entry
|
|
|
|
|
|
2019-01-12 18:19:13 +01:00
|
|
|
|
gexp->derivation-in-inferior
|
|
|
|
|
|
2018-09-18 13:30:48 +02:00
|
|
|
|
%inferior-cache-directory
|
2021-03-10 11:28:35 +01:00
|
|
|
|
cached-channel-instance
|
2018-09-18 13:30:48 +02:00
|
|
|
|
inferior-for-channels))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
|
|
|
|
;;; 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>
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(inferior pid socket close version packages table
|
2022-01-27 00:20:12 +01:00
|
|
|
|
bridge-socket)
|
2018-07-10 14:18:36 +02:00
|
|
|
|
inferior?
|
|
|
|
|
(pid inferior-pid)
|
|
|
|
|
(socket inferior-socket)
|
2018-12-24 00:55:07 +01:00
|
|
|
|
(close inferior-close-socket) ;procedure
|
2018-09-15 14:50:14 +02:00
|
|
|
|
(version inferior-version) ;REPL protocol version
|
|
|
|
|
(packages inferior-package-promise) ;promise of inferior packages
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(table inferior-package-table) ;promise of vhash
|
|
|
|
|
|
|
|
|
|
;; Bridging with a store.
|
|
|
|
|
(bridge-socket inferior-bridge-socket ;#f | port
|
|
|
|
|
set-inferior-bridge-socket!))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
2021-03-13 11:56:52 +01:00
|
|
|
|
(define (write-inferior inferior port)
|
|
|
|
|
(match inferior
|
|
|
|
|
(($ <inferior> pid _ _ version)
|
|
|
|
|
(format port "#<inferior ~a ~a ~a>"
|
|
|
|
|
pid version
|
|
|
|
|
(number->string (object-address inferior) 16)))))
|
|
|
|
|
|
|
|
|
|
(set-record-type-printer! <inferior> write-inferior)
|
|
|
|
|
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(define (open-bidirectional-pipe command . args)
|
|
|
|
|
"Open a bidirectional pipe to COMMAND invoked with ARGS and return it, as a
|
|
|
|
|
regular file port (socket).
|
|
|
|
|
|
|
|
|
|
This is equivalent to (open-pipe* OPEN_BOTH ...) except that the result is a
|
|
|
|
|
regular file port that can be passed to 'select' ('open-pipe*' returns a
|
|
|
|
|
custom binary port)."
|
2022-05-20 17:12:01 +02:00
|
|
|
|
;; Make sure the sockets are close-on-exec; failing to do that, a second
|
|
|
|
|
;; inferior (for instance) would inherit the underlying file descriptor, and
|
|
|
|
|
;; thus (close-port PARENT) in the original process would have no effect:
|
|
|
|
|
;; the REPL process wouldn't get EOF on standard input.
|
|
|
|
|
(match (socketpair AF_UNIX (logior SOCK_STREAM SOCK_CLOEXEC) 0)
|
2022-01-27 00:20:12 +01:00
|
|
|
|
((parent . child)
|
|
|
|
|
(match (primitive-fork)
|
|
|
|
|
(0
|
|
|
|
|
(dynamic-wind
|
|
|
|
|
(lambda ()
|
|
|
|
|
#t)
|
|
|
|
|
(lambda ()
|
|
|
|
|
(close-port parent)
|
|
|
|
|
(close-fdes 0)
|
|
|
|
|
(close-fdes 1)
|
2022-06-25 19:14:07 +02:00
|
|
|
|
(close-fdes 2)
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(dup2 (fileno child) 0)
|
|
|
|
|
(dup2 (fileno child) 1)
|
|
|
|
|
;; Mimic 'open-pipe*'.
|
2022-06-25 19:14:07 +02:00
|
|
|
|
(if (file-port? (current-error-port))
|
|
|
|
|
(let ((error-port-fileno
|
|
|
|
|
(fileno (current-error-port))))
|
|
|
|
|
(unless (eq? error-port-fileno 2)
|
|
|
|
|
(dup2 error-port-fileno
|
|
|
|
|
2)))
|
|
|
|
|
(dup2 (open-fdes "/dev/null" O_WRONLY)
|
|
|
|
|
2))
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(apply execlp command command args))
|
|
|
|
|
(lambda ()
|
|
|
|
|
(primitive-_exit 127))))
|
|
|
|
|
(pid
|
|
|
|
|
(close-port child)
|
|
|
|
|
(values parent pid))))))
|
|
|
|
|
|
2019-10-02 20:12:38 +02:00
|
|
|
|
(define* (inferior-pipe directory command error-port)
|
2022-01-27 00:20:12 +01:00
|
|
|
|
"Return two values: an input/output pipe on the Guix instance in DIRECTORY
|
|
|
|
|
and its PID. This runs 'DIRECTORY/COMMAND repl' if it exists, or falls back
|
|
|
|
|
to some other method if it's an old Guix."
|
|
|
|
|
(let ((pipe pid (with-error-to-port error-port
|
|
|
|
|
(lambda ()
|
|
|
|
|
(open-bidirectional-pipe
|
|
|
|
|
(string-append directory "/" command)
|
|
|
|
|
"repl" "-t" "machine")))))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(if (eof-object? (peek-char pipe))
|
|
|
|
|
(begin
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(close-port pipe)
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
|
|
|
|
;; Older versions of Guix didn't have a 'guix repl' command, so
|
|
|
|
|
;; emulate it.
|
2019-10-02 20:14:05 +02:00
|
|
|
|
(with-error-to-port error-port
|
|
|
|
|
(lambda ()
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(open-bidirectional-pipe
|
|
|
|
|
"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/repl.scm"))
|
|
|
|
|
((@ (guix repl) machine-repl))))))))
|
|
|
|
|
(values pipe pid))))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
2018-12-24 00:55:07 +01:00
|
|
|
|
(define* (port->inferior pipe #:optional (close close-port))
|
|
|
|
|
"Given PIPE, an input/output port, return an inferior that talks over PIPE.
|
|
|
|
|
PIPE is closed with CLOSE when 'close-inferior' is called on the returned
|
|
|
|
|
inferior."
|
2019-01-06 22:02:40 +01:00
|
|
|
|
(setvbuf pipe 'line)
|
2018-08-21 14:28:03 +02:00
|
|
|
|
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(match (read pipe)
|
|
|
|
|
(('repl-version 0 rest ...)
|
2018-12-24 00:55:07 +01:00
|
|
|
|
(letrec ((result (inferior 'pipe pipe close (cons 0 rest)
|
2018-09-15 14:50:14 +02:00
|
|
|
|
(delay (%inferior-packages result))
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(delay (%inferior-package-table result))
|
2022-01-27 00:20:12 +01:00
|
|
|
|
#f)))
|
2020-03-15 14:34:01 +01:00
|
|
|
|
|
|
|
|
|
;; For protocol (0 1) and later, send the protocol version we support.
|
|
|
|
|
(match rest
|
|
|
|
|
((n _ ...)
|
|
|
|
|
(when (>= n 1)
|
2020-03-15 17:26:45 +01:00
|
|
|
|
(send-inferior-request '(() repl-version 0 1 1) result)))
|
2020-03-15 14:34:01 +01:00
|
|
|
|
(_
|
|
|
|
|
#t))
|
|
|
|
|
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(inferior-eval '(use-modules (guix)) result)
|
|
|
|
|
(inferior-eval '(use-modules (gnu)) result)
|
2018-09-17 09:55:31 +02:00
|
|
|
|
(inferior-eval '(use-modules (ice-9 match)) result)
|
2019-09-20 22:26:53 +02:00
|
|
|
|
(inferior-eval '(use-modules (srfi srfi-34)) result)
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(inferior-eval '(define %package-table (make-hash-table))
|
|
|
|
|
result)
|
2022-01-27 09:20:40 +01:00
|
|
|
|
(inferior-eval '(begin
|
|
|
|
|
(define %store-table (make-hash-table))
|
|
|
|
|
(define (cached-store-connection store-id version)
|
|
|
|
|
;; Cache connections to store ID. This ensures that
|
|
|
|
|
;; the caches within <store-connection> (in
|
|
|
|
|
;; particular the object cache) are reused across
|
|
|
|
|
;; calls to 'inferior-eval-with-store', which makes a
|
|
|
|
|
;; significant difference when it is called
|
|
|
|
|
;; repeatedly.
|
|
|
|
|
(or (hashv-ref %store-table store-id)
|
|
|
|
|
|
|
|
|
|
;; 'port->connection' appeared in June 2018 and
|
|
|
|
|
;; we can hardly emulate it on older versions.
|
|
|
|
|
;; Thus fall back to 'open-connection', at the
|
|
|
|
|
;; risk of talking to the wrong daemon or having
|
|
|
|
|
;; our build result reclaimed (XXX).
|
|
|
|
|
(let ((store (if (defined? 'port->connection)
|
|
|
|
|
(port->connection %bridge-socket
|
|
|
|
|
#:version
|
|
|
|
|
version)
|
|
|
|
|
(open-connection))))
|
|
|
|
|
(hashv-set! %store-table store-id store)
|
|
|
|
|
store))))
|
|
|
|
|
result)
|
|
|
|
|
(inferior-eval '(begin
|
|
|
|
|
(define store-protocol-error?
|
|
|
|
|
(if (defined? 'store-protocol-error?)
|
|
|
|
|
store-protocol-error?
|
|
|
|
|
nix-protocol-error?))
|
|
|
|
|
(define store-protocol-error-message
|
|
|
|
|
(if (defined? 'store-protocol-error-message)
|
|
|
|
|
store-protocol-error-message
|
|
|
|
|
nix-protocol-error-message)))
|
2022-01-27 08:55:59 +01:00
|
|
|
|
result)
|
2018-07-10 14:18:36 +02:00
|
|
|
|
result))
|
|
|
|
|
(_
|
|
|
|
|
#f)))
|
|
|
|
|
|
2019-10-02 20:12:38 +02:00
|
|
|
|
(define* (open-inferior directory
|
|
|
|
|
#:key (command "bin/guix")
|
|
|
|
|
(error-port (%make-void-port "w")))
|
2018-12-24 00:55:07 +01:00
|
|
|
|
"Open the inferior Guix in DIRECTORY, running 'DIRECTORY/COMMAND repl' or
|
|
|
|
|
equivalent. Return #f if the inferior could not be launched."
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(let ((pipe pid (inferior-pipe directory command error-port)))
|
|
|
|
|
(port->inferior pipe
|
|
|
|
|
(lambda (port)
|
|
|
|
|
(close-port port)
|
|
|
|
|
(waitpid pid)))))
|
2018-12-24 00:55:07 +01:00
|
|
|
|
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(define (close-inferior inferior)
|
|
|
|
|
"Close INFERIOR."
|
2018-12-24 00:55:07 +01:00
|
|
|
|
(let ((close (inferior-close-socket inferior)))
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(close (inferior-socket inferior))
|
|
|
|
|
|
|
|
|
|
;; Close and delete the store bridge, if any.
|
|
|
|
|
(when (inferior-bridge-socket inferior)
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(close-port (inferior-bridge-socket inferior)))))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
|
|
|
|
;; 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)
|
|
|
|
|
|
2020-03-10 16:45:57 +01:00
|
|
|
|
;; Reified exception thrown by an inferior.
|
|
|
|
|
(define-condition-type &inferior-exception &error
|
|
|
|
|
inferior-exception?
|
|
|
|
|
(arguments inferior-exception-arguments) ;key + arguments
|
2020-03-15 17:26:45 +01:00
|
|
|
|
(inferior inferior-exception-inferior) ;<inferior> | #f
|
|
|
|
|
(stack inferior-exception-stack)) ;list of (FILE COLUMN LINE)
|
2020-03-10 16:45:57 +01:00
|
|
|
|
|
|
|
|
|
(define* (read-repl-response port #:optional inferior)
|
|
|
|
|
"Read a (guix repl) response from PORT and return it as a Scheme object.
|
|
|
|
|
Raise '&inferior-exception' when an exception is read from PORT."
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(define sexp->object
|
|
|
|
|
(match-lambda
|
|
|
|
|
(('value value)
|
|
|
|
|
value)
|
|
|
|
|
(('non-self-quoting address string)
|
|
|
|
|
(inferior-object address string))))
|
|
|
|
|
|
2019-06-10 17:11:43 +02:00
|
|
|
|
(match (read port)
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(('values objects ...)
|
|
|
|
|
(apply values (map sexp->object objects)))
|
2020-03-15 17:26:45 +01:00
|
|
|
|
(('exception ('arguments key objects ...)
|
|
|
|
|
('stack frames ...))
|
|
|
|
|
;; Protocol (0 1 1) and later.
|
|
|
|
|
(raise (condition (&inferior-exception
|
|
|
|
|
(arguments (cons key (map sexp->object objects)))
|
|
|
|
|
(inferior inferior)
|
|
|
|
|
(stack frames)))))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(('exception key objects ...)
|
2020-03-15 17:26:45 +01:00
|
|
|
|
;; Protocol (0 0).
|
2020-03-10 16:45:57 +01:00
|
|
|
|
(raise (condition (&inferior-exception
|
|
|
|
|
(arguments (cons key (map sexp->object objects)))
|
2020-03-15 17:26:45 +01:00
|
|
|
|
(inferior inferior)
|
|
|
|
|
(stack '())))))))
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
2019-06-10 17:11:43 +02:00
|
|
|
|
(define (read-inferior-response inferior)
|
2020-03-10 16:45:57 +01:00
|
|
|
|
(read-repl-response (inferior-socket inferior)
|
|
|
|
|
inferior))
|
2019-06-10 17:11:43 +02:00
|
|
|
|
|
2018-09-14 17:30:06 +02:00
|
|
|
|
(define (send-inferior-request exp inferior)
|
|
|
|
|
(write exp (inferior-socket inferior))
|
|
|
|
|
(newline (inferior-socket inferior)))
|
|
|
|
|
|
|
|
|
|
(define (inferior-eval exp inferior)
|
|
|
|
|
"Evaluate EXP in INFERIOR."
|
|
|
|
|
(send-inferior-request exp inferior)
|
|
|
|
|
(read-inferior-response inferior))
|
|
|
|
|
|
2018-07-10 14:18:36 +02:00
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; 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)
|
|
|
|
|
|
2018-09-15 14:50:14 +02:00
|
|
|
|
(define (%inferior-packages inferior)
|
|
|
|
|
"Compute the list of inferior packages from INFERIOR."
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(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)))
|
|
|
|
|
|
2018-09-15 14:50:14 +02:00
|
|
|
|
(define (inferior-packages inferior)
|
|
|
|
|
"Return the list of packages known to INFERIOR."
|
|
|
|
|
(force (inferior-package-promise inferior)))
|
|
|
|
|
|
|
|
|
|
(define (%inferior-package-table inferior)
|
|
|
|
|
"Compute a package lookup table for INFERIOR."
|
|
|
|
|
(fold (lambda (package table)
|
|
|
|
|
(vhash-cons (inferior-package-name package) package
|
|
|
|
|
table))
|
|
|
|
|
vlist-null
|
|
|
|
|
(inferior-packages inferior)))
|
|
|
|
|
|
2019-02-12 22:17:11 +01:00
|
|
|
|
(define (inferior-available-packages inferior)
|
|
|
|
|
"Return the list of name/version pairs corresponding to the set of packages
|
|
|
|
|
available in INFERIOR.
|
|
|
|
|
|
2021-01-22 21:31:51 +01:00
|
|
|
|
This is faster and less resource-intensive than calling 'inferior-packages'."
|
2019-02-12 22:17:11 +01:00
|
|
|
|
(if (inferior-eval '(defined? 'fold-available-packages)
|
|
|
|
|
inferior)
|
|
|
|
|
(inferior-eval '(fold-available-packages
|
|
|
|
|
(lambda* (name version result
|
|
|
|
|
#:key supported? deprecated?
|
|
|
|
|
#:allow-other-keys)
|
|
|
|
|
(if (and supported? (not deprecated?))
|
|
|
|
|
(acons name version result)
|
|
|
|
|
result))
|
|
|
|
|
'())
|
|
|
|
|
inferior)
|
|
|
|
|
|
|
|
|
|
;; As a last resort, if INFERIOR is old and lacks
|
|
|
|
|
;; 'fold-available-packages', fall back to 'inferior-packages'.
|
|
|
|
|
(map (lambda (package)
|
|
|
|
|
(cons (inferior-package-name package)
|
|
|
|
|
(inferior-package-version package)))
|
|
|
|
|
(inferior-packages inferior))))
|
|
|
|
|
|
2018-09-15 14:50:14 +02:00
|
|
|
|
(define* (lookup-inferior-packages inferior name #:optional version)
|
|
|
|
|
"Return the sorted list of inferior packages matching NAME in INFERIOR, with
|
|
|
|
|
highest version numbers first. If VERSION is true, return only packages with
|
|
|
|
|
a version number prefixed by VERSION."
|
|
|
|
|
;; This is the counterpart of 'find-packages-by-name'.
|
|
|
|
|
(sort (filter (lambda (package)
|
|
|
|
|
(or (not version)
|
|
|
|
|
(version-prefix? version
|
|
|
|
|
(inferior-package-version package))))
|
|
|
|
|
(vhash-fold* cons '() name
|
|
|
|
|
(force (inferior-package-table inferior))))
|
|
|
|
|
(lambda (p1 p2)
|
|
|
|
|
(version>? (inferior-package-version p1)
|
|
|
|
|
(inferior-package-version p2)))))
|
|
|
|
|
|
2018-07-10 14:18:36 +02:00
|
|
|
|
(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)))
|
2018-09-04 17:22:55 +02:00
|
|
|
|
|
|
|
|
|
(define (inferior-package-home-page package)
|
|
|
|
|
"Return the home page of PACKAGE."
|
|
|
|
|
(inferior-package-field package 'package-home-page))
|
|
|
|
|
|
|
|
|
|
(define (inferior-package-location package)
|
|
|
|
|
"Return the source code location of PACKAGE, either #f or a <location>
|
|
|
|
|
record."
|
|
|
|
|
(source-properties->location
|
|
|
|
|
(inferior-package-field package
|
|
|
|
|
'(compose (lambda (loc)
|
|
|
|
|
(and loc
|
|
|
|
|
(location->source-properties
|
|
|
|
|
loc)))
|
|
|
|
|
package-location))))
|
2018-09-14 17:30:06 +02:00
|
|
|
|
|
2018-09-17 09:55:31 +02:00
|
|
|
|
(define (inferior-package-input-field package field)
|
|
|
|
|
"Return the input field FIELD (e.g., 'native-inputs') of PACKAGE, an
|
|
|
|
|
inferior package."
|
|
|
|
|
(define field*
|
|
|
|
|
`(compose (lambda (inputs)
|
|
|
|
|
(map (match-lambda
|
|
|
|
|
;; XXX: Origins are not handled.
|
|
|
|
|
((label (? package? package) rest ...)
|
|
|
|
|
(let ((id (object-address package)))
|
|
|
|
|
(hashv-set! %package-table id package)
|
|
|
|
|
`(,label (package ,id
|
|
|
|
|
,(package-name package)
|
|
|
|
|
,(package-version package))
|
|
|
|
|
,@rest)))
|
|
|
|
|
(x
|
|
|
|
|
x))
|
|
|
|
|
inputs))
|
|
|
|
|
,field))
|
|
|
|
|
|
|
|
|
|
(define inputs
|
|
|
|
|
(inferior-package-field package field*))
|
|
|
|
|
|
|
|
|
|
(define inferior
|
|
|
|
|
(inferior-package-inferior package))
|
|
|
|
|
|
|
|
|
|
(map (match-lambda
|
|
|
|
|
((label ('package id name version) . rest)
|
|
|
|
|
;; XXX: eq?-ness of inferior packages is not preserved here.
|
|
|
|
|
`(,label ,(inferior-package inferior name version id)
|
|
|
|
|
,@rest))
|
|
|
|
|
(x x))
|
|
|
|
|
inputs))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-inputs
|
|
|
|
|
(cut inferior-package-input-field <> 'package-inputs))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-native-inputs
|
|
|
|
|
(cut inferior-package-input-field <> 'package-native-inputs))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-propagated-inputs
|
|
|
|
|
(cut inferior-package-input-field <> 'package-propagated-inputs))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-transitive-propagated-inputs
|
|
|
|
|
(cut inferior-package-input-field <> 'package-transitive-propagated-inputs))
|
|
|
|
|
|
2018-09-17 10:04:15 +02:00
|
|
|
|
(define (%inferior-package-search-paths package field)
|
2019-09-23 21:02:33 +02:00
|
|
|
|
"Return the list of search path specifications of PACKAGE, an inferior
|
2018-09-17 10:04:15 +02:00
|
|
|
|
package."
|
|
|
|
|
(define paths
|
|
|
|
|
(inferior-package-field package
|
|
|
|
|
`(compose (lambda (paths)
|
|
|
|
|
(map (@ (guix search-paths)
|
|
|
|
|
search-path-specification->sexp)
|
|
|
|
|
paths))
|
|
|
|
|
,field)))
|
|
|
|
|
|
|
|
|
|
(map sexp->search-path-specification paths))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-native-search-paths
|
|
|
|
|
(cut %inferior-package-search-paths <> 'package-native-search-paths))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-search-paths
|
|
|
|
|
(cut %inferior-package-search-paths <> 'package-search-paths))
|
|
|
|
|
|
|
|
|
|
(define inferior-package-transitive-native-search-paths
|
|
|
|
|
(cut %inferior-package-search-paths <> 'package-transitive-native-search-paths))
|
|
|
|
|
|
2021-04-24 07:43:46 +02:00
|
|
|
|
(define (inferior-package-replacement package)
|
|
|
|
|
"Return the replacement for PACKAGE. This will either be an inferior
|
|
|
|
|
package, or #f."
|
|
|
|
|
(match (inferior-package-field
|
|
|
|
|
package
|
|
|
|
|
'(compose (match-lambda
|
|
|
|
|
((? package? package)
|
|
|
|
|
(let ((id (object-address package)))
|
|
|
|
|
(hashv-set! %package-table id package)
|
|
|
|
|
(list id
|
|
|
|
|
(package-name package)
|
|
|
|
|
(package-version package))))
|
|
|
|
|
(#f #f))
|
|
|
|
|
package-replacement))
|
|
|
|
|
(#f #f)
|
|
|
|
|
((id name version)
|
|
|
|
|
(inferior-package (inferior-package-inferior package)
|
|
|
|
|
name
|
|
|
|
|
version
|
|
|
|
|
id))))
|
|
|
|
|
|
2019-12-29 17:35:56 +01:00
|
|
|
|
(define (inferior-package-provenance package)
|
|
|
|
|
"Return a \"provenance sexp\" for PACKAGE, an inferior package. The result
|
|
|
|
|
is similar to the sexp returned by 'package-provenance' for regular packages."
|
|
|
|
|
(inferior-package-field package
|
|
|
|
|
'(let* ((describe
|
|
|
|
|
(false-if-exception
|
|
|
|
|
(resolve-interface '(guix describe))))
|
|
|
|
|
(provenance
|
|
|
|
|
(false-if-exception
|
|
|
|
|
(module-ref describe
|
|
|
|
|
'package-provenance))))
|
|
|
|
|
(or provenance (const #f)))))
|
|
|
|
|
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(define (proxy inferior store) ;adapted from (guix ssh)
|
|
|
|
|
"Proxy communication between INFERIOR and STORE, until the connection to
|
|
|
|
|
STORE is closed or INFERIOR has data available for input (a REPL response)."
|
|
|
|
|
(define client
|
|
|
|
|
(inferior-bridge-socket inferior))
|
|
|
|
|
(define backend
|
|
|
|
|
(store-connection-socket store))
|
|
|
|
|
(define response-port
|
|
|
|
|
(inferior-socket inferior))
|
|
|
|
|
|
2018-09-14 17:30:06 +02:00
|
|
|
|
;; Use buffered ports so that 'get-bytevector-some' returns up to the
|
|
|
|
|
;; whole buffer like read(2) would--see <https://bugs.gnu.org/30066>.
|
2019-01-07 10:57:18 +01:00
|
|
|
|
(setvbuf client 'block 65536)
|
|
|
|
|
(setvbuf backend 'block 65536)
|
2018-09-14 17:30:06 +02:00
|
|
|
|
|
2022-01-27 00:20:12 +01:00
|
|
|
|
;; RESPONSE-PORT may typically contain a leftover newline that 'read' didn't
|
|
|
|
|
;; consume. Drain it so that 'select' doesn't immediately stop.
|
|
|
|
|
(drain-input response-port)
|
|
|
|
|
|
2018-09-14 17:30:06 +02:00
|
|
|
|
(let loop ()
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(match (select (list client backend response-port) '() '())
|
2018-09-14 17:30:06 +02:00
|
|
|
|
((reads () ())
|
|
|
|
|
(when (memq client reads)
|
|
|
|
|
(match (get-bytevector-some client)
|
|
|
|
|
((? eof-object?)
|
2022-01-27 00:20:12 +01:00
|
|
|
|
#t)
|
2018-09-14 17:30:06 +02:00
|
|
|
|
(bv
|
|
|
|
|
(put-bytevector backend bv)
|
|
|
|
|
(force-output backend))))
|
|
|
|
|
(when (memq backend reads)
|
|
|
|
|
(match (get-bytevector-some backend)
|
|
|
|
|
(bv
|
|
|
|
|
(put-bytevector client bv)
|
|
|
|
|
(force-output client))))
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(unless (or (port-closed? client)
|
|
|
|
|
(memq response-port reads))
|
2018-09-14 17:30:06 +02:00
|
|
|
|
(loop))))))
|
|
|
|
|
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(define (open-store-bridge! inferior)
|
|
|
|
|
"Open a \"store bridge\" for INFERIOR--a named socket in /tmp that will be
|
|
|
|
|
used to proxy store RPCs from the inferior to the store of the calling
|
|
|
|
|
process."
|
|
|
|
|
;; Create a named socket in /tmp to let INFERIOR connect to it and use it as
|
|
|
|
|
;; its store. This ensures the inferior uses the same store, with the same
|
|
|
|
|
;; options, the same per-session GC roots, etc.
|
|
|
|
|
;; FIXME: This strategy doesn't work for remote inferiors (SSH).
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(call-with-temporary-directory
|
|
|
|
|
(lambda (directory)
|
|
|
|
|
(chmod directory #o700)
|
|
|
|
|
(let ((name (string-append directory "/inferior"))
|
|
|
|
|
(socket (socket AF_UNIX SOCK_STREAM 0)))
|
|
|
|
|
(bind socket AF_UNIX name)
|
|
|
|
|
(listen socket 2)
|
|
|
|
|
|
|
|
|
|
(send-inferior-request
|
|
|
|
|
`(define %bridge-socket
|
|
|
|
|
(let ((socket (socket AF_UNIX SOCK_STREAM 0)))
|
|
|
|
|
(connect socket AF_UNIX ,name)
|
|
|
|
|
socket))
|
|
|
|
|
inferior)
|
|
|
|
|
(match (accept socket)
|
|
|
|
|
((client . address)
|
|
|
|
|
(close-port socket)
|
|
|
|
|
(set-inferior-bridge-socket! inferior client)))
|
|
|
|
|
(read-inferior-response inferior)))))
|
2022-01-26 23:10:51 +01:00
|
|
|
|
|
|
|
|
|
(define (ensure-store-bridge! inferior)
|
|
|
|
|
"Ensure INFERIOR has a connected bridge."
|
|
|
|
|
(or (inferior-bridge-socket inferior)
|
|
|
|
|
(begin
|
|
|
|
|
(open-store-bridge! inferior)
|
|
|
|
|
(inferior-bridge-socket inferior))))
|
|
|
|
|
|
2018-11-26 11:48:33 +01:00
|
|
|
|
(define (inferior-eval-with-store inferior store code)
|
|
|
|
|
"Evaluate CODE in INFERIOR, passing it STORE as its argument. CODE must
|
|
|
|
|
thus be the code of a one-argument procedure that accepts a store."
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(let* ((major (store-connection-major-version store))
|
|
|
|
|
(minor (store-connection-minor-version store))
|
2022-01-27 08:55:59 +01:00
|
|
|
|
(proto (logior major minor))
|
|
|
|
|
|
|
|
|
|
;; The address of STORE itself is not a good identifier because it
|
|
|
|
|
;; keeps changing through the use of "functional caches". The
|
|
|
|
|
;; address of its socket port makes more sense.
|
|
|
|
|
(store-id (object-address (store-connection-socket store))))
|
2022-01-26 23:10:51 +01:00
|
|
|
|
(ensure-store-bridge! inferior)
|
|
|
|
|
(send-inferior-request
|
2022-01-27 09:20:40 +01:00
|
|
|
|
`(let ((proc ,code)
|
|
|
|
|
(store (cached-store-connection ,store-id ,proto)))
|
|
|
|
|
;; Serialize '&store-protocol-error' conditions. The exception
|
|
|
|
|
;; serialization mechanism that 'read-repl-response' expects is
|
|
|
|
|
;; unsuitable for SRFI-35 error conditions, hence this special case.
|
|
|
|
|
(guard (c ((store-protocol-error? c)
|
|
|
|
|
`(store-protocol-error
|
|
|
|
|
,(store-protocol-error-message c))))
|
|
|
|
|
`(result ,(proc store))))
|
2022-01-26 23:10:51 +01:00
|
|
|
|
inferior)
|
2022-01-27 00:20:12 +01:00
|
|
|
|
(proxy inferior store)
|
2022-01-26 23:10:51 +01:00
|
|
|
|
|
|
|
|
|
(match (read-inferior-response inferior)
|
|
|
|
|
(('store-protocol-error message)
|
|
|
|
|
(raise (condition
|
|
|
|
|
(&store-protocol-error (message message)
|
|
|
|
|
(status 1)))))
|
|
|
|
|
(('result result)
|
|
|
|
|
result))))
|
2018-11-26 11:48:33 +01:00
|
|
|
|
|
|
|
|
|
(define* (inferior-package-derivation store package
|
|
|
|
|
#:optional
|
|
|
|
|
(system (%current-system))
|
|
|
|
|
#:key target)
|
|
|
|
|
"Return the derivation for PACKAGE, an inferior package, built for SYSTEM
|
|
|
|
|
and cross-built for TARGET if TARGET is true. The inferior corresponding to
|
|
|
|
|
PACKAGE must be live."
|
|
|
|
|
(define proc
|
|
|
|
|
`(lambda (store)
|
|
|
|
|
(let* ((package (hashv-ref %package-table
|
|
|
|
|
,(inferior-package-id package)))
|
|
|
|
|
(drv ,(if target
|
|
|
|
|
`(package-cross-derivation store package
|
|
|
|
|
,target
|
|
|
|
|
,system)
|
|
|
|
|
`(package-derivation store package
|
|
|
|
|
,system))))
|
|
|
|
|
(derivation-file-name drv))))
|
|
|
|
|
|
|
|
|
|
(and=> (inferior-eval-with-store (inferior-package-inferior package) store
|
|
|
|
|
proc)
|
|
|
|
|
read-derivation-from-file))
|
2018-09-14 17:30:06 +02:00
|
|
|
|
|
|
|
|
|
(define inferior-package->derivation
|
|
|
|
|
(store-lift inferior-package-derivation))
|
|
|
|
|
|
|
|
|
|
(define-gexp-compiler (package-compiler (package <inferior-package>) system
|
|
|
|
|
target)
|
|
|
|
|
;; Compile PACKAGE for SYSTEM, optionally cross-building for TARGET.
|
|
|
|
|
(inferior-package->derivation package system #:target target))
|
2018-09-18 09:56:34 +02:00
|
|
|
|
|
2019-01-12 18:19:13 +01:00
|
|
|
|
(define* (gexp->derivation-in-inferior name exp guix
|
2019-03-08 12:25:25 +01:00
|
|
|
|
#:key silent-failure?
|
|
|
|
|
#:allow-other-keys
|
2019-01-12 18:19:13 +01:00
|
|
|
|
#:rest rest)
|
|
|
|
|
"Return a derivation that evaluates EXP with GUIX, an instance of Guix as
|
|
|
|
|
returned for example by 'channel-instances->derivation'. Other arguments are
|
2019-03-08 12:25:25 +01:00
|
|
|
|
passed as-is to 'gexp->derivation'.
|
|
|
|
|
|
|
|
|
|
When SILENT-FAILURE? is true, create an empty output directory instead of
|
|
|
|
|
failing when GUIX is too old and lacks the 'guix repl' command."
|
2019-01-18 10:01:37 +01:00
|
|
|
|
(define script
|
|
|
|
|
;; EXP wrapped with a proper (set! %load-path …) prologue.
|
|
|
|
|
(scheme-file "inferior-script.scm" exp))
|
|
|
|
|
|
2019-01-12 18:19:13 +01:00
|
|
|
|
(define trampoline
|
|
|
|
|
;; This is a crude way to run EXP on GUIX. TODO: use 'raw-derivation' and
|
|
|
|
|
;; make 'guix repl' the "builder"; this will require "opening up" the
|
|
|
|
|
;; mechanisms behind 'gexp->derivation', and adding '-l' to 'guix repl'.
|
|
|
|
|
#~(begin
|
|
|
|
|
(use-modules (ice-9 popen))
|
|
|
|
|
|
|
|
|
|
(let ((pipe (open-pipe* OPEN_WRITE
|
|
|
|
|
#+(file-append guix "/bin/guix")
|
|
|
|
|
"repl" "-t" "machine")))
|
2019-01-18 10:01:37 +01:00
|
|
|
|
|
|
|
|
|
;; XXX: EXP presumably refers to #$output but that reference is lost
|
|
|
|
|
;; so explicitly reference it here.
|
|
|
|
|
#$output
|
|
|
|
|
|
|
|
|
|
(write `(primitive-load #$script) pipe)
|
2019-01-12 18:19:13 +01:00
|
|
|
|
|
|
|
|
|
(unless (zero? (close-pipe pipe))
|
2019-03-08 12:25:25 +01:00
|
|
|
|
(if #$silent-failure?
|
|
|
|
|
(mkdir #$output)
|
|
|
|
|
(error "inferior failed" #+guix))))))
|
|
|
|
|
|
|
|
|
|
(define (drop-extra-keyword lst)
|
|
|
|
|
(let loop ((lst lst)
|
|
|
|
|
(result '()))
|
|
|
|
|
(match lst
|
|
|
|
|
(()
|
|
|
|
|
(reverse result))
|
|
|
|
|
((#:silent-failure? _ . rest)
|
|
|
|
|
(loop rest result))
|
|
|
|
|
((kw value . tail)
|
|
|
|
|
(loop tail (cons* value kw result))))))
|
|
|
|
|
|
|
|
|
|
(apply gexp->derivation name trampoline
|
|
|
|
|
(drop-extra-keyword rest)))
|
2019-01-12 18:19:13 +01:00
|
|
|
|
|
2018-09-18 09:56:34 +02:00
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Manifest entries.
|
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
(define* (inferior-package->manifest-entry package
|
|
|
|
|
#:optional (output "out")
|
2021-01-27 23:03:06 +01:00
|
|
|
|
#:key (properties '()))
|
2018-09-18 09:56:34 +02:00
|
|
|
|
"Return a manifest entry for the OUTPUT of package PACKAGE."
|
2021-01-27 23:03:06 +01:00
|
|
|
|
(define cache
|
|
|
|
|
(make-hash-table))
|
|
|
|
|
|
|
|
|
|
(define-syntax-rule (memoized package output exp)
|
|
|
|
|
;; Memoize the entry returned by EXP for PACKAGE/OUTPUT. This is
|
|
|
|
|
;; important as the same package may be traversed many times through
|
|
|
|
|
;; propagated inputs, and querying the inferior is costly. Use
|
|
|
|
|
;; 'hash'/'equal?', which is okay since <inferior-package> is simple.
|
|
|
|
|
(let ((compute (lambda () exp))
|
|
|
|
|
(key (cons package output)))
|
|
|
|
|
(or (hash-ref cache key)
|
|
|
|
|
(let ((result (compute)))
|
|
|
|
|
(hash-set! cache key result)
|
|
|
|
|
result))))
|
|
|
|
|
|
|
|
|
|
(let loop ((package package)
|
|
|
|
|
(output output)
|
|
|
|
|
(parent (delay #f)))
|
|
|
|
|
(memoized package output
|
|
|
|
|
;; For each dependency, keep a promise pointing to its "parent" entry.
|
|
|
|
|
(letrec* ((deps (map (match-lambda
|
|
|
|
|
((label package)
|
|
|
|
|
(loop package "out" (delay entry)))
|
|
|
|
|
((label package output)
|
|
|
|
|
(loop package output (delay entry))))
|
|
|
|
|
(inferior-package-propagated-inputs package)))
|
|
|
|
|
(entry (manifest-entry
|
|
|
|
|
(name (inferior-package-name package))
|
|
|
|
|
(version (inferior-package-version package))
|
|
|
|
|
(output output)
|
|
|
|
|
(item package)
|
|
|
|
|
(dependencies (delete-duplicates deps))
|
|
|
|
|
(search-paths
|
|
|
|
|
(inferior-package-transitive-native-search-paths package))
|
|
|
|
|
(parent parent)
|
|
|
|
|
(properties properties))))
|
|
|
|
|
entry))))
|
2018-09-18 13:30:48 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;
|
|
|
|
|
;;; Cached inferiors.
|
|
|
|
|
;;;
|
|
|
|
|
|
|
|
|
|
(define %inferior-cache-directory
|
|
|
|
|
;; Directory for cached inferiors (GC roots).
|
|
|
|
|
(make-parameter (string-append (cache-directory #:ensure? #f)
|
|
|
|
|
"/inferiors")))
|
|
|
|
|
|
2021-01-28 22:48:21 +01:00
|
|
|
|
(define (channel-full-commit channel)
|
|
|
|
|
"Return the commit designated by CHANNEL as quickly as possible. If
|
|
|
|
|
CHANNEL's 'commit' field is a full SHA1, return it as-is; if it's a SHA1
|
|
|
|
|
prefix, resolve it; and if 'commit' is unset, fetch CHANNEL's branch tip."
|
|
|
|
|
(let ((commit (channel-commit channel))
|
|
|
|
|
(branch (channel-branch channel)))
|
|
|
|
|
(if (and commit (= (string-length commit) 40))
|
|
|
|
|
commit
|
|
|
|
|
(let* ((ref (if commit `(commit . ,commit) `(branch . ,branch)))
|
|
|
|
|
(cache commit relation
|
|
|
|
|
(update-cached-checkout (channel-url channel)
|
|
|
|
|
#:ref ref
|
|
|
|
|
#:check-out? #f)))
|
|
|
|
|
commit))))
|
|
|
|
|
|
2021-03-10 11:28:35 +01:00
|
|
|
|
(define* (cached-channel-instance store
|
|
|
|
|
channels
|
|
|
|
|
#:key
|
|
|
|
|
(authenticate? #t)
|
|
|
|
|
(cache-directory (%inferior-cache-directory))
|
|
|
|
|
(ttl (* 3600 24 30)))
|
|
|
|
|
"Return a directory containing a guix filetree defined by CHANNELS, a list of channels.
|
|
|
|
|
The directory is a subdirectory of CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds.
|
|
|
|
|
This procedure opens a new connection to the build daemon. AUTHENTICATE?
|
|
|
|
|
determines whether CHANNELS are authenticated."
|
|
|
|
|
(define commits
|
|
|
|
|
;; Since computing the instances of CHANNELS is I/O-intensive, use a
|
|
|
|
|
;; cheaper way to get the commit list of CHANNELS. This limits overhead
|
|
|
|
|
;; to the minimum in case of a cache hit.
|
|
|
|
|
(map channel-full-commit channels))
|
|
|
|
|
|
2019-11-12 16:39:46 +01:00
|
|
|
|
(define key
|
|
|
|
|
(bytevector->base32-string
|
|
|
|
|
(sha256
|
2021-01-28 22:48:21 +01:00
|
|
|
|
(string->utf8 (string-concatenate commits)))))
|
2019-11-12 16:39:46 +01:00
|
|
|
|
|
|
|
|
|
(define cached
|
|
|
|
|
(string-append cache-directory "/" key))
|
|
|
|
|
|
|
|
|
|
(define (base32-encoded-sha256? str)
|
|
|
|
|
(= (string-length str) 52))
|
|
|
|
|
|
|
|
|
|
(define (cache-entries directory)
|
|
|
|
|
(map (lambda (file)
|
|
|
|
|
(string-append directory "/" file))
|
|
|
|
|
(scandir directory base32-encoded-sha256?)))
|
|
|
|
|
|
2021-03-10 12:08:10 +01:00
|
|
|
|
(define (symlink/safe old new)
|
|
|
|
|
(catch 'system-error
|
|
|
|
|
(lambda ()
|
|
|
|
|
(symlink old new))
|
|
|
|
|
(lambda args
|
|
|
|
|
(unless (= EEXIST (system-error-errno args))
|
|
|
|
|
(apply throw args)))))
|
|
|
|
|
|
2019-11-12 16:39:46 +01:00
|
|
|
|
(define symlink*
|
2021-03-10 12:08:10 +01:00
|
|
|
|
(lift2 symlink/safe %store-monad))
|
2019-11-12 16:39:46 +01:00
|
|
|
|
|
|
|
|
|
(define add-indirect-root*
|
|
|
|
|
(store-lift add-indirect-root))
|
|
|
|
|
|
2021-11-30 11:19:30 +01:00
|
|
|
|
(define add-temp-root*
|
|
|
|
|
(store-lift add-temp-root))
|
|
|
|
|
|
2019-11-12 16:39:46 +01:00
|
|
|
|
(mkdir-p cache-directory)
|
|
|
|
|
(maybe-remove-expired-cache-entries cache-directory
|
|
|
|
|
cache-entries
|
|
|
|
|
#:entry-expiration
|
|
|
|
|
(file-expiration-time ttl))
|
|
|
|
|
|
|
|
|
|
(if (file-exists? cached)
|
|
|
|
|
cached
|
|
|
|
|
(run-with-store store
|
2021-03-10 11:28:35 +01:00
|
|
|
|
(mlet* %store-monad ((instances
|
|
|
|
|
-> (latest-channel-instances store channels
|
|
|
|
|
#:authenticate?
|
|
|
|
|
authenticate?))
|
|
|
|
|
(profile
|
|
|
|
|
(channel-instances->derivation instances)))
|
2019-11-12 16:39:46 +01:00
|
|
|
|
(mbegin %store-monad
|
2021-08-09 17:37:54 +02:00
|
|
|
|
;; It's up to the caller to install a build handler to report
|
|
|
|
|
;; what's going to be built.
|
2019-11-12 16:39:46 +01:00
|
|
|
|
(built-derivations (list profile))
|
2021-08-09 17:37:54 +02:00
|
|
|
|
|
2021-11-30 11:19:30 +01:00
|
|
|
|
;; Cache if and only if AUTHENTICATE? is true.
|
|
|
|
|
(if authenticate?
|
|
|
|
|
(mbegin %store-monad
|
|
|
|
|
(symlink* (derivation->output-path profile) cached)
|
|
|
|
|
(add-indirect-root* cached)
|
|
|
|
|
(return cached))
|
|
|
|
|
(mbegin %store-monad
|
2022-01-13 17:31:53 +01:00
|
|
|
|
(add-temp-root* (derivation->output-path profile))
|
2022-01-17 14:53:04 +01:00
|
|
|
|
(return (derivation->output-path profile)))))))))
|
2019-10-25 17:42:21 +02:00
|
|
|
|
|
|
|
|
|
(define* (inferior-for-channels channels
|
|
|
|
|
#:key
|
|
|
|
|
(cache-directory (%inferior-cache-directory))
|
|
|
|
|
(ttl (* 3600 24 30)))
|
|
|
|
|
"Return an inferior for CHANNELS, a list of channels. Use the cache at
|
|
|
|
|
CACHE-DIRECTORY, where entries can be reclaimed after TTL seconds. This
|
|
|
|
|
procedure opens a new connection to the build daemon.
|
|
|
|
|
|
|
|
|
|
This is a convenience procedure that people may use in manifests passed to
|
|
|
|
|
'guix package -m', for instance."
|
|
|
|
|
(define cached
|
2019-11-12 16:39:46 +01:00
|
|
|
|
(with-store store
|
2021-08-09 17:37:54 +02:00
|
|
|
|
;; XXX: Install a build notifier out of convenience, so users know
|
|
|
|
|
;; what's going on. However, we cannot be sure that its options, such
|
|
|
|
|
;; as #:use-substitutes?, correspond to the daemon's default settings.
|
|
|
|
|
(with-build-handler (build-notifier)
|
|
|
|
|
(cached-channel-instance store
|
|
|
|
|
channels
|
|
|
|
|
#:cache-directory cache-directory
|
|
|
|
|
#:ttl ttl))))
|
2019-10-25 17:42:21 +02:00
|
|
|
|
(open-inferior cached))
|
2021-01-27 23:03:06 +01:00
|
|
|
|
|
|
|
|
|
;;; Local Variables:
|
|
|
|
|
;;; eval: (put 'memoized 'scheme-indent-function 1)
|
|
|
|
|
;;; End:
|