home.scm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021 Andrew Tropin <andrew@trop.in>
  3. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  4. ;;; Copyright © 2021 Pierre Langlois <pierre.langlois@gmx.com>
  5. ;;; Copyright © 2021 Oleg Pykhalov <go.wigust@gmail.com>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (guix scripts home)
  22. #:use-module (gnu packages admin)
  23. #:use-module ((gnu services) #:hide (delete))
  24. #:use-module (gnu packages)
  25. #:use-module (gnu home)
  26. #:use-module (gnu home services)
  27. #:use-module (guix channels)
  28. #:use-module (guix derivations)
  29. #:use-module (guix ui)
  30. #:use-module (guix grafts)
  31. #:use-module (guix packages)
  32. #:use-module (guix profiles)
  33. #:use-module (guix store)
  34. #:use-module (guix utils)
  35. #:use-module (guix scripts)
  36. #:use-module (guix scripts package)
  37. #:use-module (guix scripts build)
  38. #:use-module (guix scripts system search)
  39. #:autoload (guix scripts pull) (channel-commit-hyperlink)
  40. #:use-module (guix scripts home import)
  41. #:use-module ((guix status) #:select (with-status-verbosity))
  42. #:use-module ((guix build utils) #:select (mkdir-p))
  43. #:use-module (guix gexp)
  44. #:use-module (guix monads)
  45. #:use-module (srfi srfi-1)
  46. #:use-module (srfi srfi-26)
  47. #:use-module (srfi srfi-35)
  48. #:use-module (srfi srfi-37)
  49. #:use-module (ice-9 match)
  50. #:export (guix-home))
  51. ;;;
  52. ;;; Options.
  53. ;;;
  54. (define %user-module
  55. (make-user-module '((gnu home))))
  56. (define %guix-home
  57. (string-append %profile-directory "/guix-home"))
  58. (define (show-help)
  59. (display (G_ "Usage: guix home [OPTION ...] ACTION [ARG ...] [FILE]
  60. Build the home environment declared in FILE according to ACTION.
  61. Some ACTIONS support additional ARGS.\n"))
  62. (newline)
  63. (display (G_ "The valid values for ACTION are:\n"))
  64. (newline)
  65. (display (G_ "\
  66. search search for existing service types\n"))
  67. (display (G_ "\
  68. reconfigure switch to a new home environment configuration\n"))
  69. (display (G_ "\
  70. roll-back switch to the previous home environment configuration\n"))
  71. (display (G_ "\
  72. describe describe the current home environment\n"))
  73. (display (G_ "\
  74. list-generations list the home environment generations\n"))
  75. (display (G_ "\
  76. switch-generation switch to an existing home environment configuration\n"))
  77. (display (G_ "\
  78. delete-generations delete old home environment generations\n"))
  79. (display (G_ "\
  80. build build the home environment without installing anything\n"))
  81. (display (G_ "\
  82. import generates a home environment definition from dotfiles\n"))
  83. (show-build-options-help)
  84. (display (G_ "
  85. -e, --expression=EXPR consider the home-environment EXPR evaluates to
  86. instead of reading FILE, when applicable"))
  87. (display (G_ "
  88. -v, --verbosity=LEVEL use the given verbosity LEVEL"))
  89. (newline)
  90. (display (G_ "
  91. -h, --help display this help and exit"))
  92. (display (G_ "
  93. -V, --version display version information and exit"))
  94. (newline)
  95. (show-bug-report-information))
  96. (define (verbosity-level opts)
  97. "Return the verbosity level based on OPTS, the alist of parsed options."
  98. (or (assoc-ref opts 'verbosity)
  99. (if (eq? (assoc-ref opts 'action) 'build)
  100. 3 1)))
  101. (define %options
  102. ;; Specification of the command-line options.
  103. (cons* (option '(#\h "help") #f #f
  104. (lambda args
  105. (show-help)
  106. (exit 0)))
  107. (option '(#\n "dry-run") #f #f
  108. (lambda (opt name arg result)
  109. (alist-cons 'dry-run? #t result)))
  110. (option '(#\V "version") #f #f
  111. (lambda args
  112. (show-version-and-exit "guix show")))
  113. (option '(#\v "verbosity") #t #f
  114. (lambda (opt name arg result)
  115. (let ((level (string->number* arg)))
  116. (alist-cons 'verbosity level
  117. (alist-delete 'verbosity result)))))
  118. (option '(#\e "expression") #t #f
  119. (lambda (opt name arg result)
  120. (alist-cons 'expression arg result)))
  121. %standard-build-options))
  122. (define %default-options
  123. `((build-mode . ,(build-mode normal))
  124. (graft? . #t)
  125. (substitutes? . #t)
  126. (offload? . #t)
  127. (print-build-trace? . #t)
  128. (print-extended-build-trace? . #t)
  129. (multiplexed-build-output? . #t)
  130. (verbosity . #f) ;default
  131. (debug . 0)))
  132. ;;;
  133. ;;; Actions.
  134. ;;;
  135. (define* (perform-action action he
  136. #:key
  137. dry-run?
  138. derivations-only?
  139. use-substitutes?)
  140. "Perform ACTION for home environment. "
  141. (define println
  142. (cut format #t "~a~%" <>))
  143. (mlet* %store-monad
  144. ((he-drv (home-environment-derivation he))
  145. (drvs (mapm/accumulate-builds lower-object (list he-drv)))
  146. (% (if derivations-only?
  147. (return
  148. (for-each (compose println derivation-file-name) drvs))
  149. (built-derivations drvs)))
  150. (he-out-path -> (derivation->output-path he-drv)))
  151. (if (or dry-run? derivations-only?)
  152. (return #f)
  153. (begin
  154. (for-each (compose println derivation->output-path) drvs)
  155. (case action
  156. ((reconfigure)
  157. (let* ((number (generation-number %guix-home))
  158. (generation (generation-file-name
  159. %guix-home (+ 1 number))))
  160. (switch-symlinks generation he-out-path)
  161. (switch-symlinks %guix-home generation)
  162. (setenv "GUIX_NEW_HOME" he-out-path)
  163. (primitive-load (string-append he-out-path "/activate"))
  164. (setenv "GUIX_NEW_HOME" #f)
  165. (return he-out-path)))
  166. (else
  167. (newline)
  168. (return he-out-path)))))))
  169. (define (process-action action args opts)
  170. "Process ACTION, a sub-command, with the arguments are listed in ARGS.
  171. ACTION must be one of the sub-commands that takes a home environment
  172. declaration as an argument (a file name.) OPTS is the raw alist of options
  173. resulting from command-line parsing."
  174. (define (ensure-home-environment file-or-exp obj)
  175. (ensure-profile-directory)
  176. (unless (home-environment? obj)
  177. (leave (G_ "'~a' does not return a home environment ~%")
  178. file-or-exp))
  179. obj)
  180. (let* ((file (match args
  181. (() #f)
  182. ((x . _) x)))
  183. (expr (assoc-ref opts 'expression))
  184. (system (assoc-ref opts 'system))
  185. (transform (lambda (obj)
  186. (home-environment-with-provenance obj file)))
  187. (home-environment
  188. (transform
  189. (ensure-home-environment
  190. (or file expr)
  191. (cond
  192. ((and expr file)
  193. (leave
  194. (G_ "both file and expression cannot be specified~%")))
  195. (expr
  196. (read/eval expr))
  197. (file
  198. (load* file %user-module
  199. #:on-error (assoc-ref opts 'on-error)))
  200. (else
  201. (leave (G_ "no configuration specified~%")))))))
  202. (dry? (assoc-ref opts 'dry-run?)))
  203. (with-store store
  204. (set-build-options-from-command-line store opts)
  205. (with-build-handler (build-notifier #:use-substitutes?
  206. (assoc-ref opts 'substitutes?)
  207. #:verbosity
  208. (verbosity-level opts)
  209. #:dry-run?
  210. (assoc-ref opts 'dry-run?))
  211. (run-with-store store
  212. (mbegin %store-monad
  213. (set-guile-for-build (default-guile))
  214. (case action
  215. (else
  216. (perform-action action home-environment
  217. #:dry-run? dry?
  218. #:derivations-only? (assoc-ref opts 'derivations-only?)
  219. #:use-substitutes? (assoc-ref opts 'substitutes?))
  220. ))))))
  221. (warn-about-disk-space)))
  222. (define (process-command command args opts)
  223. "Process COMMAND, one of the 'guix home' sub-commands. ARGS is its
  224. argument list and OPTS is the option alist."
  225. (define-syntax-rule (with-store* store exp ...)
  226. (with-store store
  227. (set-build-options-from-command-line store opts)
  228. exp ...))
  229. (case command
  230. ;; The following commands do not need to use the store, and they do not need
  231. ;; an home environment file.
  232. ((search)
  233. (apply search args))
  234. ((import)
  235. (let* ((profiles (delete-duplicates
  236. (match (filter-map (match-lambda
  237. (('profile . p) p)
  238. (_ #f))
  239. opts)
  240. (() (list %current-profile))
  241. (lst (reverse lst)))))
  242. (manifest (concatenate-manifests
  243. (map profile-manifest profiles)))
  244. (destination (match args
  245. ((destination) destination)
  246. (_ (leave (G_ "wrong number of arguments~%"))))))
  247. (unless (file-exists? destination)
  248. (mkdir-p destination))
  249. (call-with-output-file
  250. (string-append destination "/home-configuration.scm")
  251. (cut import-manifest manifest destination <>))
  252. (info (G_ "'~a' populated with all the Home configuration files~%")
  253. destination)
  254. (display-hint (format #f (G_ "\
  255. Run @command{guix home reconfigure ~a/home-configuration.scm} to effectively
  256. deploy the home environment described by these files.\n")
  257. destination))))
  258. ((describe)
  259. (match (generation-number %guix-home)
  260. (0
  261. (leave (G_ "no home environment generation, nothing to describe~%")))
  262. (generation
  263. (display-home-environment-generation generation))))
  264. ((list-generations)
  265. (let ((pattern (match args
  266. (() #f)
  267. ((pattern) pattern)
  268. (x (leave (G_ "wrong number of arguments~%"))))))
  269. (list-generations pattern)))
  270. ((switch-generation)
  271. (let ((pattern (match args
  272. ((pattern) pattern)
  273. (x (leave (G_ "wrong number of arguments~%"))))))
  274. (with-store* store
  275. (switch-to-home-environment-generation store pattern))))
  276. ((roll-back)
  277. (let ((pattern (match args
  278. (() "")
  279. (x (leave (G_ "wrong number of arguments~%"))))))
  280. (with-store* store
  281. (roll-back-home-environment store))))
  282. ((delete-generations)
  283. (let ((pattern (match args
  284. (() #f)
  285. ((pattern) pattern)
  286. (x (leave (G_ "wrong number of arguments~%"))))))
  287. (with-store*
  288. store
  289. (delete-matching-generations store %guix-home pattern))))
  290. (else (process-action command args opts))))
  291. (define-command (guix-home . args)
  292. (synopsis "build and deploy home environments")
  293. (define (parse-sub-command arg result)
  294. ;; Parse sub-command ARG and augment RESULT accordingly.
  295. (if (assoc-ref result 'action)
  296. (alist-cons 'argument arg result)
  297. (let ((action (string->symbol arg)))
  298. (case action
  299. ((build
  300. reconfigure
  301. extension-graph shepherd-graph
  302. list-generations describe
  303. delete-generations roll-back
  304. switch-generation search
  305. import)
  306. (alist-cons 'action action result))
  307. (else (leave (G_ "~a: unknown action~%") action))))))
  308. (define (match-pair car)
  309. ;; Return a procedure that matches a pair with CAR.
  310. (match-lambda
  311. ((head . tail)
  312. (and (eq? car head) tail))
  313. (_ #f)))
  314. (define (option-arguments opts)
  315. ;; Extract the plain arguments from OPTS.
  316. (let* ((args (reverse (filter-map (match-pair 'argument) opts)))
  317. (count (length args))
  318. (action (assoc-ref opts 'action))
  319. (expr (assoc-ref opts 'expression)))
  320. (define (fail)
  321. (leave (G_ "wrong number of arguments for action '~a'~%")
  322. action))
  323. (unless action
  324. (format (current-error-port)
  325. (G_ "guix home: missing command name~%"))
  326. (format (current-error-port)
  327. (G_ "Try 'guix home --help' for more information.~%"))
  328. (exit 1))
  329. (case action
  330. ((build reconfigure)
  331. (unless (or (= count 1)
  332. (and expr (= count 0)))
  333. (fail)))
  334. ((init)
  335. (unless (= count 2)
  336. (fail))))
  337. args))
  338. (with-error-handling
  339. (let* ((opts (parse-command-line args %options
  340. (list %default-options)
  341. #:argument-handler
  342. parse-sub-command))
  343. (args (option-arguments opts))
  344. (command (assoc-ref opts 'action)))
  345. (parameterize ((%graft? (assoc-ref opts 'graft?)))
  346. (with-status-verbosity (verbosity-level opts)
  347. (process-command command args opts))))))
  348. ;;;
  349. ;;; Searching.
  350. ;;;
  351. (define service-type-name*
  352. (compose symbol->string service-type-name))
  353. (define (service-type-description-string type)
  354. "Return the rendered and localised description of TYPE, a service type."
  355. (and=> (service-type-description type)
  356. (compose texi->plain-text P_)))
  357. (define %service-type-metrics
  358. ;; Metrics used to estimate the relevance of a search result.
  359. `((,service-type-name* . 3)
  360. (,service-type-description-string . 2)
  361. (,(lambda (type)
  362. (match (and=> (service-type-location type) location-file)
  363. ((? string? file)
  364. (basename file ".scm"))
  365. (#f
  366. "")))
  367. . 1)))
  368. (define (find-service-types regexps)
  369. "Return a list of service type/score pairs: service types whose name or
  370. description matches REGEXPS sorted by relevance, and their score."
  371. (let ((matches (fold-home-service-types
  372. (lambda (type result)
  373. (match (relevance type regexps
  374. %service-type-metrics)
  375. ((? zero?)
  376. result)
  377. (score
  378. (cons (cons type score) result))))
  379. '())))
  380. (sort matches
  381. (lambda (m1 m2)
  382. (match m1
  383. ((type1 . score1)
  384. (match m2
  385. ((type2 . score2)
  386. (if (= score1 score2)
  387. (string>? (service-type-name* type1)
  388. (service-type-name* type2))
  389. (> score1 score2))))))))))
  390. (define (search . args)
  391. (with-error-handling
  392. (let* ((regexps (map (cut make-regexp* <> regexp/icase) args))
  393. (matches (find-service-types regexps)))
  394. (leave-on-EPIPE
  395. (display-search-results matches (current-output-port)
  396. #:print service-type->recutils
  397. #:command "guix home search")))))
  398. ;;;
  399. ;;; Generations.
  400. ;;;
  401. (define* (display-home-environment-generation
  402. number
  403. #:optional (profile %guix-home))
  404. "Display a summary of home-environment generation NUMBER in a
  405. human-readable format."
  406. (define (display-channel channel)
  407. (format #t " ~a:~%" (channel-name channel))
  408. (format #t (G_ " repository URL: ~a~%") (channel-url channel))
  409. (when (channel-branch channel)
  410. (format #t (G_ " branch: ~a~%") (channel-branch channel)))
  411. (format #t (G_ " commit: ~a~%")
  412. (if (supports-hyperlinks?)
  413. (channel-commit-hyperlink channel)
  414. (channel-commit channel))))
  415. (unless (zero? number)
  416. (let* ((generation (generation-file-name profile number)))
  417. (define-values (channels config-file)
  418. ;; The function will work for home environments too, we just
  419. ;; need to keep provenance file.
  420. (system-provenance generation))
  421. (display-generation profile number)
  422. (format #t (G_ " file name: ~a~%") generation)
  423. (format #t (G_ " canonical file name: ~a~%") (readlink* generation))
  424. ;; TRANSLATORS: Please preserve the two-space indentation.
  425. (unless (null? channels)
  426. ;; TRANSLATORS: Here "channel" is the same terminology as used in
  427. ;; "guix describe" and "guix pull --channels".
  428. (format #t (G_ " channels:~%"))
  429. (for-each display-channel channels))
  430. (when config-file
  431. (format #t (G_ " configuration file: ~a~%")
  432. (if (supports-hyperlinks?)
  433. (file-hyperlink config-file)
  434. config-file))))))
  435. (define* (list-generations pattern #:optional (profile %guix-home))
  436. "Display in a human-readable format all the home environment
  437. generations matching PATTERN, a string. When PATTERN is #f, display
  438. all the home environment generations."
  439. (cond ((not (file-exists? profile)) ; XXX: race condition
  440. (raise (condition (&profile-not-found-error
  441. (profile profile)))))
  442. ((not pattern)
  443. (for-each display-home-environment-generation (profile-generations profile)))
  444. ((matching-generations pattern profile)
  445. =>
  446. (lambda (numbers)
  447. (if (null-list? numbers)
  448. (exit 1)
  449. (leave-on-EPIPE
  450. (for-each display-home-environment-generation numbers)))))))
  451. ;;;
  452. ;;; Switch generations.
  453. ;;;
  454. ;; TODO: Make it public in (guix scripts system)
  455. (define-syntax-rule (unless-file-not-found exp)
  456. (catch 'system-error
  457. (lambda ()
  458. exp)
  459. (lambda args
  460. (if (= ENOENT (system-error-errno args))
  461. #f
  462. (apply throw args)))))
  463. (define (switch-to-home-environment-generation store spec)
  464. "Switch the home-environment profile to the generation specified by
  465. SPEC. STORE is an open connection to the store."
  466. (let* ((number (relative-generation-spec->number %guix-home spec))
  467. (generation (generation-file-name %guix-home number))
  468. (activate (string-append generation "/activate")))
  469. (if number
  470. (begin
  471. (setenv "GUIX_NEW_HOME" (readlink generation))
  472. (switch-to-generation* %guix-home number)
  473. (unless-file-not-found (primitive-load activate))
  474. (setenv "GUIX_NEW_HOME" #f))
  475. (leave (G_ "cannot switch to home environment generation '~a'~%") spec))))
  476. ;;;
  477. ;;; Roll-back.
  478. ;;;
  479. (define (roll-back-home-environment store)
  480. "Roll back the home-environment profile to its previous generation.
  481. STORE is an open connection to the store."
  482. (switch-to-home-environment-generation store "-1"))