repl.scm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2020, 2021 Simon Tournier <zimon.toutoune@gmail.com>
  4. ;;; Copyright © 2020 Konrad Hinsen <konrad.hinsen@fastmail.net>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (guix scripts repl)
  21. #:use-module (guix ui)
  22. #:use-module (guix scripts)
  23. #:use-module (guix repl)
  24. #:use-module (srfi srfi-1)
  25. #:use-module (srfi srfi-37)
  26. #:use-module (ice-9 match)
  27. #:autoload (guix describe) (current-profile)
  28. #:autoload (system repl repl) (start-repl)
  29. #:autoload (system repl server)
  30. (make-tcp-server-socket make-unix-domain-server-socket)
  31. #:export (guix-repl))
  32. ;;; Commentary:
  33. ;;;
  34. ;;; This command provides a Guile script runner and REPL in an environment
  35. ;;; that contains all the modules comprising Guix.
  36. (define %default-options
  37. `((type . guile)))
  38. (define %options
  39. (list (option '(#\h "help") #f #f
  40. (lambda args
  41. (show-help)
  42. (exit 0)))
  43. (option '(#\V "version") #f #f
  44. (lambda args
  45. (show-version-and-exit "guix repl")))
  46. (option '(#\t "type") #t #f
  47. (lambda (opt name arg result)
  48. (alist-cons 'type (string->symbol arg) result)))
  49. (option '("list-types") #f #f
  50. (lambda (opt name arg result)
  51. (display (string-join '("guile" "machine") "\n" 'suffix))
  52. (exit 0)))
  53. (option '("listen") #t #f
  54. (lambda (opt name arg result)
  55. (alist-cons 'listen arg result)))
  56. (option '(#\q) #f #f
  57. (lambda (opt name arg result)
  58. (alist-cons 'ignore-dot-guile? #t result)))
  59. (option '(#\i "interactive") #f #f
  60. (lambda (opt name arg result)
  61. (alist-cons 'interactive? #t result)))
  62. (option '(#\L "load-path") #t #f
  63. (lambda (opt name arg result)
  64. ;; XXX: Imperatively modify the search paths.
  65. (set! %load-path (cons arg %load-path))
  66. (set! %load-compiled-path (cons arg %load-compiled-path))
  67. result))))
  68. (define (show-help)
  69. (display (G_ "Usage: guix repl [OPTIONS...] [-- FILE ARGS...]
  70. In the Guix execution environment, run FILE as a Guile script with
  71. command-line arguments ARGS. If no FILE is given, start a Guile REPL.\n"))
  72. (display (G_ "
  73. --list-types display REPL types and exit"))
  74. (display (G_ "
  75. -t, --type=TYPE start a REPL of the given TYPE"))
  76. (display (G_ "
  77. --listen=ENDPOINT listen to ENDPOINT instead of standard input"))
  78. (display (G_ "
  79. -q inhibit loading of ~/.guile"))
  80. (newline)
  81. (display (G_ "
  82. -i, --interactive launch REPL after evaluating FILE"))
  83. (newline)
  84. (display (G_ "
  85. -L, --load-path=DIR prepend DIR to the package module search path"))
  86. (newline)
  87. (display (G_ "
  88. -h, --help display this help and exit"))
  89. (display (G_ "
  90. -V, --version display version information and exit"))
  91. (newline)
  92. (show-bug-report-information))
  93. (define user-module
  94. ;; Module where we execute user code.
  95. (let ((module (resolve-module '(guix-user) #f #f #:ensure #t)))
  96. (beautify-user-module! module)
  97. module))
  98. (define (call-with-connection spec thunk)
  99. "Dynamically-bind the current input and output ports according to SPEC and
  100. call THUNK."
  101. (if (not spec)
  102. (thunk)
  103. ;; Note: the "PROTO:" prefix in SPEC is here so that we can eventually
  104. ;; parse things like "fd:123" in a non-ambiguous way.
  105. (match (string-index spec #\:)
  106. (#f
  107. (leave (G_ "~A: invalid listen specification~%") spec))
  108. (index
  109. (let ((protocol (string-take spec index))
  110. (address (string-drop spec (+ index 1))))
  111. (define socket
  112. (match protocol
  113. ("tcp"
  114. (make-tcp-server-socket #:port (string->number address)))
  115. ("unix"
  116. (make-unix-domain-server-socket #:path address))
  117. (_
  118. (leave (G_ "~A: unsupported protocol family~%")
  119. protocol))))
  120. (listen socket 10)
  121. (let loop ()
  122. (match (accept socket)
  123. ((connection . address)
  124. (if (= AF_UNIX (sockaddr:fam address))
  125. (info (G_ "accepted connection~%"))
  126. (info (G_ "accepted connection from ~a~%")
  127. (inet-ntop (sockaddr:fam address)
  128. (sockaddr:addr address))))
  129. (dynamic-wind
  130. (const #t)
  131. (lambda ()
  132. (parameterize ((current-input-port connection)
  133. (current-output-port connection))
  134. (thunk)))
  135. (lambda ()
  136. (false-if-exception (close-port connection))
  137. (info (G_ "connection closed~%"))))))
  138. (loop)))))))
  139. (define-command (guix-repl . args)
  140. (category plumbing)
  141. (synopsis "read-eval-print loop (REPL) for interactive programming")
  142. (define opts
  143. (parse-command-line args %options (list %default-options)
  144. #:build-options? #f
  145. #:argument-handler
  146. (lambda (arg result)
  147. (append `((script . ,arg)
  148. (ignore-dot-guile? . #t))
  149. result))))
  150. (define user-config
  151. (and=> (getenv "HOME")
  152. (lambda (home)
  153. (string-append home "/.guile"))))
  154. (define (set-user-module)
  155. (set-current-module user-module)
  156. (when (and (not (assoc-ref opts 'ignore-dot-guile?))
  157. user-config
  158. (file-exists? user-config))
  159. (load user-config)))
  160. (define script
  161. (reverse
  162. (filter-map (match-lambda
  163. (('script . script) script)
  164. (_ #f))
  165. opts)))
  166. (with-error-handling
  167. (unless (null? script)
  168. ;; Run script
  169. (save-module-excursion
  170. (lambda ()
  171. ;; Invoke 'current-profile' so that it memoizes the correct value
  172. ;; based on (program-arguments), before we call
  173. ;; 'set-program-arguments'. This in turn ensures that
  174. ;; (%package-module-path) will contain entries for the channels
  175. ;; available in the current profile.
  176. (current-profile)
  177. (set-program-arguments script)
  178. (set-user-module)
  179. ;; When passed a relative file name, 'load-in-vicinity' searches the
  180. ;; file in %LOAD-PATH. Thus, pass (getcwd) instead of ".".
  181. (load-in-vicinity (getcwd) (car script)))))
  182. (when (or (null? script) (assoc-ref opts 'interactive?))
  183. ;; Start REPL
  184. (let ((type (assoc-ref opts 'type)))
  185. (call-with-connection (assoc-ref opts 'listen)
  186. (lambda ()
  187. (case type
  188. ((guile)
  189. (save-module-excursion
  190. (lambda ()
  191. (current-profile) ;populate (%package-module-path); see above
  192. (set-user-module)
  193. ;; Do not exit repl on SIGINT.
  194. ((@@ (ice-9 top-repl) call-with-sigint)
  195. (lambda ()
  196. (start-repl))))))
  197. ((machine)
  198. (machine-repl))
  199. (else
  200. (leave (G_ "~a: unknown type of REPL~%") type)))))))))
  201. ;; Local Variables:
  202. ;; eval: (put 'call-with-connection 'scheme-indent-function 1)
  203. ;; End: