shell.scm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021-2023 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix scripts shell)
  19. #:use-module (guix ui)
  20. #:use-module ((guix diagnostics) #:select (location))
  21. #:use-module (guix scripts environment)
  22. #:autoload (guix scripts build) (show-build-options-help
  23. show-native-build-options-help)
  24. #:autoload (guix transformations) (options->transformation
  25. transformation-option-key?
  26. show-transformation-options-help)
  27. #:use-module (guix scripts)
  28. #:use-module (guix packages)
  29. #:use-module (guix profiles)
  30. #:use-module (srfi srfi-1)
  31. #:use-module (srfi srfi-26)
  32. #:use-module (srfi srfi-37)
  33. #:use-module (srfi srfi-71)
  34. #:use-module (ice-9 match)
  35. #:autoload (ice-9 rdelim) (read-line)
  36. #:autoload (guix base32) (bytevector->base32-string)
  37. #:autoload (rnrs bytevectors) (string->utf8)
  38. #:autoload (guix utils) (config-directory cache-directory)
  39. #:autoload (guix describe) (current-channels)
  40. #:autoload (guix channels) (channel-commit)
  41. #:autoload (gcrypt hash) (sha256)
  42. #:use-module ((guix build utils) #:select (mkdir-p))
  43. #:use-module (guix cache)
  44. #:use-module ((ice-9 ftw) #:select (scandir))
  45. #:autoload (ice-9 pretty-print) (pretty-print)
  46. #:autoload (gnu packages) (cache-is-authoritative?
  47. package-unique-version-prefix
  48. specification->package
  49. specification->package+output
  50. specifications->manifest)
  51. #:export (guix-shell))
  52. (define (show-help)
  53. (display (G_ "Usage: guix shell [OPTION] PACKAGES... [-- COMMAND...]
  54. Build an environment that includes PACKAGES and execute COMMAND or an
  55. interactive shell in that environment.\n"))
  56. (newline)
  57. ;; These two options differ from 'guix environment'.
  58. (display (G_ "
  59. -D, --development include the development inputs of the next package"))
  60. (display (G_ "
  61. -f, --file=FILE add to the environment the package FILE evaluates to"))
  62. (display (G_ "
  63. -q inhibit loading of 'guix.scm' and 'manifest.scm'"))
  64. (display (G_ "
  65. --rebuild-cache rebuild cached environment, if any"))
  66. (display (G_ "
  67. --export-manifest print a manifest for the given options"))
  68. (display (G_ "
  69. -F, --emulate-fhs for containers, emulate the Filesystem Hierarchy
  70. Standard (FHS)"))
  71. (show-environment-options-help)
  72. (newline)
  73. (show-build-options-help)
  74. (newline)
  75. (show-native-build-options-help)
  76. (newline)
  77. (show-transformation-options-help)
  78. (newline)
  79. (display (G_ "
  80. -h, --help display this help and exit"))
  81. (display (G_ "
  82. -V, --version display version information and exit"))
  83. (newline)
  84. (show-bug-report-information))
  85. (define (tag-package-arg opts arg)
  86. "Return a two-element list with the form (TAG ARG) that tags ARG with either
  87. 'ad-hoc' in OPTS has the 'ad-hoc?' key set to #t, or 'inputs' otherwise."
  88. (if (assoc-ref opts 'ad-hoc?)
  89. `(ad-hoc-package ,arg)
  90. `(package ,arg)))
  91. (define (ensure-ad-hoc alist)
  92. (if (assq-ref alist 'ad-hoc?)
  93. alist
  94. `((ad-hoc? . #t) ,@alist)))
  95. (define (wrapped-option opt)
  96. "Wrap OPT, a SRFI-37 option, such that its processor always adds the
  97. 'ad-hoc?' flag to the resulting alist."
  98. (option (option-names opt)
  99. (option-required-arg? opt)
  100. (option-optional-arg? opt)
  101. (compose ensure-ad-hoc (option-processor opt))))
  102. (define %options
  103. ;; Specification of the command-line options.
  104. (let ((to-remove '("ad-hoc" "inherit" "load" "help" "version")))
  105. (append
  106. (list (option '(#\h "help") #f #f
  107. (lambda args
  108. (show-help)
  109. (exit 0)))
  110. (option '(#\V "version") #f #f
  111. (lambda args
  112. (show-version-and-exit "guix shell")))
  113. (option '(#\D "development") #f #f
  114. (lambda (opt name arg result)
  115. ;; Temporarily remove the 'ad-hoc?' flag from result.
  116. ;; The next option will put it back thanks to
  117. ;; 'wrapped-option'.
  118. (alist-delete 'ad-hoc? result)))
  119. (option '("export-manifest") #f #f
  120. (lambda (opt name arg result)
  121. (alist-cons 'export-manifest? #t result)))
  122. ;; For consistency with 'guix package', support '-f' rather than
  123. ;; '-l' like 'guix environment' does.
  124. (option '(#\f "file") #t #f
  125. (lambda (opt name arg result)
  126. (alist-cons 'load (tag-package-arg result arg)
  127. (ensure-ad-hoc result))))
  128. (option '(#\q) #f #f
  129. (lambda (opt name arg result)
  130. (alist-cons 'explicit-loading? #t result)))
  131. (option '("rebuild-cache") #f #f
  132. (lambda (opt name arg result)
  133. (alist-cons 'rebuild-cache? #t result)))
  134. (option '(#\F "emulate-fhs") #f #f
  135. (lambda (opt name arg result)
  136. (alist-cons 'emulate-fhs? #t result))))
  137. (filter-map (lambda (opt)
  138. (and (not (any (lambda (name)
  139. (member name to-remove))
  140. (option-names opt)))
  141. (wrapped-option opt)))
  142. %environment-options))))
  143. (define %default-options
  144. `((ad-hoc? . #t) ;always true
  145. ,@%environment-default-options))
  146. (define (parse-args args)
  147. "Parse the list of command line arguments ARGS."
  148. (define (handle-argument arg result)
  149. (alist-cons 'package (tag-package-arg result arg)
  150. (ensure-ad-hoc result)))
  151. ;; The '--' token is used to separate the command to run from the rest of
  152. ;; the operands.
  153. (let ((args command (break (cut string=? "--" <>) args)))
  154. (let* ((args-parsed (parse-command-line args %options (list %default-options)
  155. #:argument-handler handle-argument))
  156. ;; For an FHS-container, add the (hidden) package glibc-for-fhs
  157. ;; which uses the global cache at /etc/ld.so.cache. We handle
  158. ;; adding this package here to ensure it will always appear in the
  159. ;; container as it is the first package in OPTS.
  160. (opts (if (assoc-ref args-parsed 'emulate-fhs?)
  161. (alist-cons 'expression
  162. '(ad-hoc-package
  163. "(@@ (gnu packages base) glibc-for-fhs)")
  164. args-parsed)
  165. args-parsed)))
  166. (options-with-caching
  167. (auto-detect-manifest
  168. (match command
  169. (() opts)
  170. (("--") opts)
  171. (("--" command ...) (alist-cons 'exec command opts))))))))
  172. (define (find-file-in-parent-directories candidates)
  173. "Find one of CANDIDATES in the current directory or one of its ancestors."
  174. (define start (getcwd))
  175. (define device (stat:dev (stat start)))
  176. (let loop ((directory start))
  177. (let ((stat (stat directory)))
  178. (and (= (stat:uid stat) (getuid))
  179. (= (stat:dev stat) device)
  180. (or (any (lambda (candidate)
  181. (let ((candidate (string-append directory "/" candidate)))
  182. (and (file-exists? candidate) candidate)))
  183. candidates)
  184. (and (not (string=? directory "/"))
  185. (loop (dirname directory)))))))) ;lexical ".." resolution
  186. (define (authorized-directory-file)
  187. "Return the name of the file listing directories for which 'guix shell' may
  188. automatically load 'guix.scm' or 'manifest.scm' files."
  189. (string-append (config-directory) "/shell-authorized-directories"))
  190. (define (authorized-shell-directory? directory)
  191. "Return true if DIRECTORY is among the authorized directories for automatic
  192. loading. The list of authorized directories is read from
  193. 'authorized-directory-file'; each line must be either: an absolute file name,
  194. a hash-prefixed comment, or a blank line."
  195. (catch 'system-error
  196. (lambda ()
  197. (call-with-input-file (authorized-directory-file)
  198. (lambda (port)
  199. (let loop ()
  200. (match (read-line port)
  201. ((? eof-object?) #f)
  202. ((= string-trim line)
  203. (cond ((string-prefix? "#" line) ;comment
  204. (loop))
  205. ((string-prefix? "/" line) ;absolute file name
  206. (or (string=? line directory)
  207. (loop)))
  208. ((string-null? (string-trim-right line)) ;blank line
  209. (loop))
  210. (else ;bogus line
  211. (let ((loc (location (port-filename port)
  212. (port-line port)
  213. (port-column port))))
  214. (warning loc (G_ "ignoring invalid file name: '~a'~%")
  215. line))))))))))
  216. (const #f)))
  217. (define (options-with-caching opts)
  218. "If OPTS contains only options that allow us to compute a cache key,
  219. automatically add a 'profile' key (when a profile for that file is already in
  220. cache) or a 'gc-root' key (to add the profile to cache)."
  221. ;; Attempt to compute a file name for use as the cached profile GC root.
  222. (let* ((root timestamp (profile-cached-gc-root opts))
  223. (stat (and root (false-if-exception (lstat root)))))
  224. (if (and (not (assoc-ref opts 'rebuild-cache?))
  225. stat
  226. (<= timestamp (stat:mtime stat)))
  227. (let ((now (current-time)))
  228. ;; Update the atime on ROOT to reflect usage.
  229. (utime root
  230. now (stat:mtime stat) 0 (stat:mtimensec stat)
  231. AT_SYMLINK_NOFOLLOW)
  232. (alist-cons 'profile root
  233. (remove (match-lambda
  234. (('load . _) #t)
  235. (('manifest . _) #t)
  236. (('package . _) #t)
  237. (('ad-hoc-package . _) #t)
  238. (_ #f))
  239. opts))) ;load right away
  240. (if (and root (not (assq-ref opts 'gc-root)))
  241. (begin
  242. (if stat
  243. (delete-file root)
  244. (mkdir-p (dirname root)))
  245. (alist-cons 'gc-root root opts))
  246. opts))))
  247. (define (auto-detect-manifest opts)
  248. "If OPTS do not specify packages or a manifest, load a \"guix.scm\" or
  249. \"manifest.scm\" file from the current directory or one of its ancestors.
  250. Return the modified OPTS."
  251. (define (options-contain-payload? opts)
  252. (match opts
  253. (() #f)
  254. ((('package . _) . _) #t)
  255. ((('load . _) . _) #t)
  256. ((('manifest . _) . _) #t)
  257. ((('profile . _) . _) #t)
  258. ((('expression . _) . _) #t)
  259. ((_ . rest) (options-contain-payload? rest))))
  260. (define interactive?
  261. (not (assoc-ref opts 'exec)))
  262. (define disallow-implicit-load?
  263. (assoc-ref opts 'explicit-loading?))
  264. (if (or (not interactive?)
  265. disallow-implicit-load?
  266. (options-contain-payload? opts))
  267. opts
  268. (match (find-file-in-parent-directories '("manifest.scm" "guix.scm"))
  269. (#f
  270. (warning (G_ "no packages specified; creating an empty environment~%"))
  271. opts)
  272. (file
  273. (if (authorized-shell-directory? (dirname file))
  274. (begin
  275. (info (G_ "loading environment from '~a'...~%") file)
  276. (match (basename file)
  277. ("guix.scm" (alist-cons 'load `(package ,file) opts))
  278. ("manifest.scm" (alist-cons 'manifest file opts))))
  279. (begin
  280. (report-error
  281. (G_ "not loading '~a' because not authorized to do so~%")
  282. file)
  283. (display-hint (G_ "To allow automatic loading of
  284. @file{~a} when running @command{guix shell}, you must explicitly authorize its
  285. directory, like so:
  286. @example
  287. echo ~a >> ~a
  288. @end example\n")
  289. file
  290. (dirname file)
  291. (authorized-directory-file))
  292. (exit 1)))))))
  293. ;;;
  294. ;;; Profile cache.
  295. ;;;
  296. (define %profile-cache-directory
  297. ;; Directory where profiles created by 'guix shell' alone (without extra
  298. ;; options) are cached.
  299. (make-parameter (string-append (cache-directory #:ensure? #f)
  300. "/profiles")))
  301. (define (profile-cache-primary-key)
  302. "Return the \"primary key\" used when computing keys for the profile cache.
  303. Return #f if no such key can be obtained and caching cannot be
  304. performed--e.g., because the package cache is not authoritative."
  305. (and (cache-is-authoritative?)
  306. (match (current-channels)
  307. (()
  308. #f)
  309. (((= channel-commit commits) ...)
  310. (string-join commits)))))
  311. (define (profile-file-cache-key file system)
  312. "Return the cache key for the profile corresponding to FILE, a 'guix.scm' or
  313. 'manifest.scm' file, or #f if we lack channel information."
  314. (match (profile-cache-primary-key)
  315. (#f #f)
  316. (primary-key
  317. (let ((stat (stat file)))
  318. (bytevector->base32-string
  319. ;; Since FILE is not canonicalized, only include the device/inode
  320. ;; numbers. XXX: In some rare cases involving Btrfs and NFS, this can
  321. ;; be insufficient: <https://lwn.net/Articles/866582/>.
  322. (sha256 (string->utf8
  323. (string-append primary-key ":" system ":"
  324. (number->string (stat:dev stat)) ":"
  325. (number->string (stat:ino stat))))))))))
  326. (define (profile-spec-cache-key specs system)
  327. "Return the cache key corresponding to SPECS built for SYSTEM, where SPECS
  328. is a list of package specs. Return #f if caching is not possible."
  329. (match (profile-cache-primary-key)
  330. (#f #f)
  331. (primary-key
  332. (bytevector->base32-string
  333. (sha256 (string->utf8
  334. (string-append primary-key ":" system ":"
  335. (object->string specs))))))))
  336. (define (profile-cached-gc-root opts)
  337. "Return two values: the file name of a GC root for use as a profile cache
  338. for the options in OPTS, and a timestamp which, if greater than the GC root's
  339. mtime, indicates that the GC root is stale. If OPTS do not permit caching,
  340. return #f and #f."
  341. (define (key->file key)
  342. (string-append (%profile-cache-directory) "/" key))
  343. (let loop ((opts opts)
  344. (system (%current-system))
  345. (file #f)
  346. (specs '()))
  347. (match opts
  348. (()
  349. (if file
  350. (values (and=> (profile-file-cache-key file system) key->file)
  351. (stat:mtime (stat file)))
  352. (values (and=> (profile-spec-cache-key specs system) key->file)
  353. 0)))
  354. (((and spec ('package . _)) . rest)
  355. (if (not file)
  356. (loop rest system file (cons spec specs))
  357. (values #f #f)))
  358. ((('nesting? . #t) . rest)
  359. (loop rest system file (append specs '("nested guix"))))
  360. ((('load . ('package candidate)) . rest)
  361. (if (and (not file) (null? specs))
  362. (loop rest system candidate specs)
  363. (values #f #f)))
  364. ((('manifest . candidate) . rest)
  365. (if (and (not file) (null? specs))
  366. (loop rest system candidate specs)
  367. (values #f #f)))
  368. ((('expression . _) . _)
  369. ;; Arbitrary expressions might be non-deterministic or otherwise depend
  370. ;; on external state so do not cache when they're used.
  371. (values #f #f))
  372. ((((? transformation-option-key?) . _) . _)
  373. ;; Transformation options are potentially "non-deterministic", or at
  374. ;; least depending on external state (with-source, with-commit, etc.),
  375. ;; so do not cache anything when they're used.
  376. (values #f #f))
  377. ((('profile . _) . _)
  378. ;; If the user already specified a profile, there's nothing more to
  379. ;; cache.
  380. (values #f #f))
  381. ((('export-manifest? . #t) . _)
  382. ;; When exporting a manifest, compute it anew so that '-D' packages
  383. ;; lead to 'package-development-manifest' expressions rather than an
  384. ;; expanded list of inputs.
  385. (values #f #f))
  386. ((('system . system) . rest)
  387. (loop rest system file specs))
  388. ((_ . rest) (loop rest system file specs)))))
  389. ;;;
  390. ;;; Exporting a manifest.
  391. ;;;
  392. (define (manifest-entry-version-prefix entry)
  393. "Search among all the versions of ENTRY's package that are available, and
  394. return the shortest unambiguous version prefix for this package."
  395. (package-unique-version-prefix (manifest-entry-name entry)
  396. (manifest-entry-version entry)))
  397. (define (manifest->code* manifest extra-manifests)
  398. "Like 'manifest->code', but insert a 'concatenate-manifests' call that
  399. concatenates MANIFESTS, a list of expressions."
  400. (if (null? (manifest-entries manifest))
  401. (match extra-manifests
  402. ((one) one)
  403. (lst `(concatenate-manifests (list ,@extra-manifests))))
  404. (match (manifest->code manifest
  405. #:entry-package-version
  406. manifest-entry-version-prefix)
  407. (('begin exp ... last)
  408. `(begin
  409. ,@exp
  410. ,(match extra-manifests
  411. (() last)
  412. (_ `(concatenate-manifests
  413. (list ,last ,@extra-manifests)))))))))
  414. (define (export-manifest opts port)
  415. "Write to PORT a manifest corresponding to OPTS."
  416. (define (manifest-lift proc)
  417. (lambda (entry)
  418. (match (manifest-entry-item entry)
  419. ((? package? p)
  420. (manifest-entry
  421. (inherit (package->manifest-entry (proc p)))
  422. (output (manifest-entry-output entry))))
  423. (_
  424. entry))))
  425. (define (validated-spec spec)
  426. ;; Return SPEC if it's a valid package spec.
  427. (specification->package+output spec)
  428. spec)
  429. (let* ((transform (options->transformation opts))
  430. (specs (reverse
  431. (filter-map (match-lambda
  432. (('package 'ad-hoc-package spec)
  433. (validated-spec spec))
  434. (_ #f))
  435. opts)))
  436. (extras (reverse
  437. (filter-map (match-lambda
  438. (('package 'package spec)
  439. ;; Make sure SPEC is valid.
  440. (specification->package spec)
  441. ;; XXX: This is an approximation:
  442. ;; transformation options are not applied.
  443. `(package->development-manifest
  444. (specification->package ,spec)))
  445. (_ #f))
  446. opts)))
  447. (manifest (concatenate-manifests
  448. (cons (map-manifest-entries
  449. (manifest-lift transform)
  450. (specifications->manifest specs))
  451. (filter-map (match-lambda
  452. (('manifest . file)
  453. (load-manifest file))
  454. (('profile . file)
  455. (profile-manifest file))
  456. (_ #f))
  457. opts)))))
  458. (display (G_ "\
  459. ;; What follows is a \"manifest\" equivalent to the command line you gave.
  460. ;; You can store it in a file that you may then pass to any 'guix' command
  461. ;; that accepts a '--manifest' (or '-m') option.\n")
  462. port)
  463. (match (manifest->code* manifest extras)
  464. (('begin exp ...)
  465. (for-each (lambda (exp)
  466. (newline port)
  467. (pretty-print exp port))
  468. exp))
  469. (exp
  470. (pretty-print exp port)))))
  471. ;;;
  472. ;;; One-time hints.
  473. ;;;
  474. (define (hint-directory)
  475. "Return the directory name where previously given hints are recorded."
  476. (string-append (cache-directory #:ensure? #f) "/hints"))
  477. (define (hint-file hint)
  478. "Return the name of the file that marks HINT as already printed."
  479. (string-append (hint-directory) "/" (symbol->string hint)))
  480. (define (record-hint hint)
  481. "Mark HINT as already given."
  482. (let ((file (hint-file hint)))
  483. (mkdir-p (dirname file))
  484. (close-fdes (open-fdes file (logior O_CREAT O_WRONLY)))))
  485. (define (hint-given? hint)
  486. "Return true if HINT was already given."
  487. (file-exists? (hint-file hint)))
  488. (define-command (guix-shell . args)
  489. (category development)
  490. (synopsis "spawn one-off software environments")
  491. (with-error-handling
  492. (define (cache-entries directory)
  493. (filter-map (match-lambda
  494. ((or "." "..") #f)
  495. (file (string-append directory "/" file)))
  496. (or (scandir directory) '())))
  497. (define* (entry-expiration file)
  498. ;; Return the time at which FILE, a cached profile, is considered expired.
  499. (match (false-if-exception (lstat file))
  500. (#f 0) ;FILE may have been deleted in the meantime
  501. (st (+ (stat:atime st) (* 60 60 24 7)))))
  502. (define opts
  503. (parse-args args))
  504. (define interactive?
  505. (not (assoc-ref opts 'exec)))
  506. (if (assoc-ref opts 'check?)
  507. (record-hint 'shell-check)
  508. (when (and interactive?
  509. (not (hint-given? 'shell-check))
  510. (not (assoc-ref opts 'container?))
  511. (not (assoc-ref opts 'search-paths)))
  512. (display-hint (G_ "Consider passing the @option{--check} option once
  513. to make sure your shell does not clobber environment variables."))) )
  514. ;; Clean the cache in EXIT-HOOK so that (1) it happens after potential use
  515. ;; of cached profiles, and (2) cleanup actually happens, even when
  516. ;; 'guix-environment*' calls 'exit'.
  517. (add-hook! exit-hook
  518. (lambda _
  519. (maybe-remove-expired-cache-entries
  520. (%profile-cache-directory)
  521. cache-entries
  522. #:entry-expiration entry-expiration)))
  523. (if (assoc-ref opts 'export-manifest?)
  524. (export-manifest opts (current-output-port))
  525. (guix-environment* opts))))