summary refs log tree commit diff
path: root/guix/remote.scm
blob: 5fecd954e947355a405d57e32c65e29f69097a97 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 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 remote)
  #:use-module (guix ssh)
  #:use-module (guix gexp)
  #:use-module (guix inferior)
  #:use-module (guix store)
  #:use-module (guix monads)
  #:use-module (guix modules)
  #:use-module (guix derivations)
  #:use-module (ssh popen)
  #:use-module (srfi srfi-1)
  #:use-module (ice-9 match)
  #:export (remote-eval))

;;; Commentary:
;;;
;;; Note: This API is experimental and subject to change!
;;;
;;; Evaluate a gexp on a remote machine, over SSH, ensuring that all the
;;; elements the gexp refers to are deployed beforehand.  This is useful for
;;; expressions that have side effects; for pure expressions, you would rather
;;; build a derivation remotely or offload it.
;;;
;;; Code:

(define (remote-pipe-for-gexp lowered session)
  "Return a remote pipe for the given SESSION to evaluate LOWERED."
  (define shell-quote
    (compose object->string object->string))

  (apply open-remote-pipe* session OPEN_READ
         (string-append (derivation-input-output-path
                         (lowered-gexp-guile lowered))
                        "/bin/guile")
         "--no-auto-compile"
         (append (append-map (lambda (directory)
                               `("-L" ,directory))
                             (lowered-gexp-load-path lowered))
                 (append-map (lambda (directory)
                               `("-C" ,directory))
                             (lowered-gexp-load-path lowered))
                 `("-c"
                   ,(shell-quote (lowered-gexp-sexp lowered))))))

(define (%remote-eval lowered session)
  "Evaluate LOWERED, a lowered gexp, in SESSION.  This assumes that all the
prerequisites of EXP are already available on the host at SESSION."
  (let* ((pipe   (remote-pipe-for-gexp lowered session))
         (result (read-repl-response pipe)))
    (close-port pipe)
    result))

(define (trampoline exp)
  "Return a \"trampoline\" gexp that evaluates EXP and writes the evaluation
result to the current output port using the (guix repl) protocol."
  (define program
    (scheme-file "remote-exp.scm" exp))

  (with-imported-modules (source-module-closure '((guix repl)))
    #~(begin
        (use-modules (guix repl))

        ;; We use CURRENT-OUTPUT-PORT for REPL messages, so redirect PROGRAM's
        ;; output to CURRENT-ERROR-PORT so that it does not interfere.
        (send-repl-response '(with-output-to-port (current-error-port)
                               (lambda ()
                                 (primitive-load #$program)))
                            (current-output-port))

        (force-output))))

(define* (remote-eval exp session
                      #:key
                      (build-locally? #t)
                      (module-path %load-path)
                      (socket-name "/var/guix/daemon-socket/socket"))
  "Evaluate EXP, a gexp, on the host at SESSION, an SSH session.  Ensure that
all the elements EXP refers to are built and deployed to SESSION beforehand.
When BUILD-LOCALLY? is true, said dependencies are built locally and sent to
the remote store afterwards; otherwise, dependencies are built directly on the
remote store."
  (mlet %store-monad ((lowered (lower-gexp (trampoline exp)
                                           #:module-path %load-path))
                      (remote -> (connect-to-remote-daemon session
                                                           socket-name)))
    (define inputs
      (cons (lowered-gexp-guile lowered)
            (lowered-gexp-inputs lowered)))

    (define sources
      (lowered-gexp-sources lowered))

    (if build-locally?
        (let ((to-send (append (append-map derivation-input-output-paths
                                           inputs)
                               sources)))
          (mbegin %store-monad
            (built-derivations inputs)
            ((store-lift send-files) to-send remote #:recursive? #t)
            (return (close-connection remote))
            (return (%remote-eval lowered session))))
        (let ((to-send (append (map (compose derivation-file-name
                                             derivation-input-derivation)
                                    inputs)
                               sources)))
          (mbegin %store-monad
            ((store-lift send-files) to-send remote #:recursive? #t)
            (return (build-derivations remote inputs))
            (return (close-connection remote))
            (return (%remote-eval lowered session)))))))