services.scm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2021-2023 Andrew Tropin <andrew@trop.in>
  3. ;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
  4. ;;; Copyright © 2022-2023 Ludovic Courtès <ludo@gnu.org>
  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 (gnu home services)
  21. #:use-module (gnu services)
  22. #:use-module ((gnu packages package-management) #:select (guix))
  23. #:use-module ((gnu packages base) #:select (coreutils))
  24. #:use-module (guix channels)
  25. #:use-module (guix monads)
  26. #:use-module (guix store)
  27. #:use-module (guix gexp)
  28. #:use-module (guix profiles)
  29. #:use-module (guix sets)
  30. #:use-module (guix ui)
  31. #:use-module (guix discovery)
  32. #:use-module (guix diagnostics)
  33. #:use-module (guix i18n)
  34. #:use-module (guix modules)
  35. #:use-module (guix memoization)
  36. #:use-module (srfi srfi-1)
  37. #:use-module (srfi srfi-9)
  38. #:use-module (ice-9 match)
  39. #:use-module (ice-9 vlist)
  40. #:export (home-service-type
  41. home-profile-service-type
  42. home-environment-variables-service-type
  43. home-files-service-type
  44. home-xdg-configuration-files-service-type
  45. home-xdg-data-files-service-type
  46. home-run-on-first-login-service-type
  47. home-activation-service-type
  48. home-run-on-change-service-type
  49. home-provenance-service-type
  50. literal-string
  51. literal-string?
  52. literal-string-value
  53. with-shell-quotation-bindings
  54. environment-variable-shell-definitions
  55. home-files-directory
  56. xdg-configuration-files-directory
  57. xdg-data-files-directory
  58. fold-home-service-types
  59. lookup-home-service-types
  60. home-provenance
  61. define-service-type-mapping
  62. system->home-service-type
  63. %initialize-gettext)
  64. #:re-export (service
  65. service-type
  66. service-extension
  67. for-home
  68. for-home?))
  69. ;;; Comment:
  70. ;;;
  71. ;;; This module is similar to (gnu system services) module, but
  72. ;;; provides Home Services, which are supposed to be used for building
  73. ;;; home-environment.
  74. ;;;
  75. ;;; Home Services use the same extension as System Services. Consult
  76. ;;; (gnu system services) module or manual for more information.
  77. ;;;
  78. ;;; home-service-type is a root of home services DAG.
  79. ;;;
  80. ;;; home-profile-service-type is almost the same as profile-service-type, at least
  81. ;;; for now.
  82. ;;;
  83. ;;; home-environment-variables-service-type generates a @file{setup-environment}
  84. ;;; shell script, which is expected to be sourced by login shell or other program,
  85. ;;; which starts early and spawns all other processes. Home services for shells
  86. ;;; automatically add code for sourcing this file, if person do not use those home
  87. ;;; services they have to source this script manually in their's shell *profile
  88. ;;; file (details described in the manual).
  89. ;;;
  90. ;;; home-files-service-type is similar to etc-service-type, but doesn't extend
  91. ;;; home-activation, because deploy mechanism for config files is pluggable
  92. ;;; and can be different for different home environments: The default one is
  93. ;;; called symlink-manager, which creates links for various dotfiles and xdg
  94. ;;; configuration files to store, but is possible to implement alternative
  95. ;;; approaches like read-only home from Julien's guix-home-manager.
  96. ;;;
  97. ;;; home-run-on-first-login-service-type provides an @file{on-first-login} guile
  98. ;;; script, which runs provided gexps once, when user makes first login. It can
  99. ;;; be used to start user's Shepherd and maybe some other process. It relies on
  100. ;;; assumption that /run/user/$UID will be created on login by some login
  101. ;;; manager (elogind for example).
  102. ;;;
  103. ;;; home-activation-service-type provides an @file{activate} guile script, which
  104. ;;; do three main things:
  105. ;;;
  106. ;;; - Sets environment variables to the values declared in
  107. ;;; @file{setup-environment} shell script. It's necessary, because user can set
  108. ;;; for example XDG_CONFIG_HOME and it should be respected by activation gexp of
  109. ;;; symlink-manager.
  110. ;;;
  111. ;;; - Sets GUIX_NEW_HOME and possibly GUIX_OLD_HOME vars to paths in the store.
  112. ;;; Later those variables can be used by activation gexps, for example by
  113. ;;; symlink-manager or run-on-change services.
  114. ;;;
  115. ;;; - Run all activation gexps provided by other home services.
  116. ;;;
  117. ;;; home-run-on-change-service-type allows to trigger actions during
  118. ;;; activation if file or directory specified by pattern is changed.
  119. ;;;
  120. ;;; Code:
  121. (define (home-derivation entries mextensions)
  122. "Return as a monadic value the derivation of the 'home'
  123. directory containing the given entries."
  124. (mlet %store-monad ((extensions (mapm/accumulate-builds identity
  125. mextensions)))
  126. (lower-object
  127. (file-union "home" (append entries (concatenate extensions))))))
  128. (define home-service-type
  129. ;; This is the ultimate service type, the root of the home service
  130. ;; DAG. The service of this type is extended by monadic name/item
  131. ;; pairs. These items end up in the "home-environment directory" as
  132. ;; returned by 'home-environment-derivation'.
  133. (service-type (name 'home)
  134. (extensions '())
  135. (compose identity)
  136. (extend home-derivation)
  137. (default-value '())
  138. (description
  139. "Build the home environment top-level directory,
  140. which in turn refers to everything the home environment needs: its
  141. packages, configuration files, activation script, and so on.")))
  142. (define (packages->profile-entry packages)
  143. "Return a system entry for the profile containing PACKAGES."
  144. ;; XXX: 'mlet' is needed here for one reason: to get the proper
  145. ;; '%current-target' and '%current-target-system' bindings when
  146. ;; 'packages->manifest' is called, and thus when the 'package-inputs'
  147. ;; etc. procedures are called on PACKAGES. That way, conditionals in those
  148. ;; inputs see the "correct" value of these two parameters. See
  149. ;; <https://issues.guix.gnu.org/44952>.
  150. (mlet %store-monad ((_ (current-target-system)))
  151. (return `(("profile" ,(profile
  152. (content (packages->manifest
  153. (map identity
  154. ;;(options->transformation transformations)
  155. (delete-duplicates packages eq?))))))))))
  156. ;; MAYBE: Add a list of transformations for packages. It's better to
  157. ;; place it in home-profile-service-type to affect all profile
  158. ;; packages and prevent conflicts, when other packages relies on
  159. ;; non-transformed version of package.
  160. (define home-profile-service-type
  161. (service-type (name 'home-profile)
  162. (extensions
  163. (list (service-extension home-service-type
  164. packages->profile-entry)))
  165. (compose concatenate)
  166. (extend append)
  167. (description
  168. "This is the @dfn{home profile} and can be found in
  169. @file{~/.guix-home/profile}. It contains packages and
  170. configuration files that the user has declared in their
  171. @code{home-environment} record.")))
  172. ;; Representation of a literal string.
  173. (define-record-type <literal-string>
  174. (literal-string str)
  175. literal-string?
  176. (str literal-string-value))
  177. (define (with-shell-quotation-bindings exp)
  178. "Insert EXP, a gexp, in a lexical environment providing the
  179. 'shell-single-quote' and 'shell-double-quote' bindings."
  180. #~(let* ((quote-string
  181. (lambda (value quoted-chars)
  182. (list->string (string-fold-right
  183. (lambda (chr lst)
  184. (if (memq chr quoted-chars)
  185. (append (list #\\ chr) lst)
  186. (cons chr lst)))
  187. '()
  188. value))))
  189. (shell-double-quote
  190. (lambda (value)
  191. ;; Double-quote VALUE, leaving dollar sign as is.
  192. (string-append "\"" (quote-string value '(#\" #\\))
  193. "\"")))
  194. (shell-single-quote
  195. (lambda (value)
  196. ;; Single-quote VALUE to enter a literal string.
  197. (string-append "'" (quote-string value '(#\'))
  198. "'"))))
  199. #$exp))
  200. (define (environment-variable-shell-definitions variables)
  201. "Return a gexp that evaluates to a list of POSIX shell statements defining
  202. VARIABLES, a list of environment variable name/value pairs. The returned code
  203. ensures variable values are properly quoted."
  204. (with-shell-quotation-bindings
  205. #~(string-append
  206. #$@(map (match-lambda
  207. ((key . #f)
  208. "")
  209. ((key . #t)
  210. #~(string-append "export " #$key "\n"))
  211. ((key . (or (? string? value)
  212. (? file-like? value)
  213. (? gexp? value)))
  214. #~(string-append "export " #$key "="
  215. (shell-double-quote #$value)
  216. "\n"))
  217. ((key . (? literal-string? value))
  218. #~(string-append "export " #$key "="
  219. (shell-single-quote
  220. #$(literal-string-value value))
  221. "\n")))
  222. variables))))
  223. (define (environment-variables->setup-environment-script vars)
  224. "Return a file that can be sourced by a POSIX compliant shell which
  225. initializes the environment. The file will source the home
  226. environment profile, set some default environment variables, and set
  227. environment variables provided in @code{vars}. @code{vars} is a list
  228. of pairs (@code{(key . value)}), @code{key} is a string and
  229. @code{value} is a string or gexp.
  230. If value is @code{#f} variable will be omitted.
  231. If value is @code{#t} variable will be just exported.
  232. For any other, value variable will be set to the @code{value} and
  233. exported."
  234. (define (warn-about-duplicate-definitions)
  235. (fold
  236. (lambda (x acc)
  237. (when (equal? (car x) (car acc))
  238. (warning
  239. (G_ "duplicate definition for `~a' environment variable ~%") (car x)))
  240. x)
  241. (cons "" "")
  242. (sort vars (lambda (a b)
  243. (string<? (car a) (car b))))))
  244. (warn-about-duplicate-definitions)
  245. (with-monad
  246. %store-monad
  247. (return
  248. `(("setup-environment"
  249. ;; TODO: It's necessary to source ~/.guix-profile too
  250. ;; on foreign distros
  251. ,(computed-file "setup-environment"
  252. #~(call-with-output-file #$output
  253. (lambda (port)
  254. (set-port-encoding! port "UTF-8")
  255. (display "\
  256. HOME_ENVIRONMENT=$HOME/.guix-home
  257. GUIX_PROFILE=\"$HOME_ENVIRONMENT/profile\"
  258. PROFILE_FILE=\"$HOME_ENVIRONMENT/profile/etc/profile\"
  259. [ -f $PROFILE_FILE ] && . $PROFILE_FILE
  260. case $XDG_DATA_DIRS in
  261. *$HOME_ENVIRONMENT/profile/share*) ;;
  262. *) export XDG_DATA_DIRS=$HOME_ENVIRONMENT/profile/share:$XDG_DATA_DIRS ;;
  263. esac
  264. case $MANPATH in
  265. *$HOME_ENVIRONMENT/profile/share/man*) ;;
  266. *) export MANPATH=$HOME_ENVIRONMENT/profile/share/man:$MANPATH
  267. esac
  268. case $INFOPATH in
  269. *$HOME_ENVIRONMENT/profile/share/info*) ;;
  270. *) export INFOPATH=$HOME_ENVIRONMENT/profile/share/info:$INFOPATH ;;
  271. esac
  272. case $XDG_CONFIG_DIRS in
  273. *$HOME_ENVIRONMENT/profile/etc/xdg*) ;;
  274. *) export XDG_CONFIG_DIRS=$HOME_ENVIRONMENT/profile/etc/xdg:$XDG_CONFIG_DIRS ;;
  275. esac
  276. case $XCURSOR_PATH in
  277. *$HOME_ENVIRONMENT/profile/share/icons*) ;;
  278. *) export XCURSOR_PATH=$HOME_ENVIRONMENT/profile/share/icons:$XCURSOR_PATH ;;
  279. esac
  280. " port)
  281. (display
  282. #$(environment-variable-shell-definitions vars)
  283. port)))))))))
  284. (define home-environment-variables-service-type
  285. (service-type (name 'home-environment-variables)
  286. (extensions
  287. (list (service-extension
  288. home-service-type
  289. environment-variables->setup-environment-script)))
  290. (compose concatenate)
  291. (extend append)
  292. (default-value '())
  293. (description "Set the environment variables.")))
  294. (define (files->files-directory files)
  295. "Return a @code{files} directory that contains FILES."
  296. (define (assert-no-duplicates files)
  297. (let loop ((files files)
  298. (seen (set)))
  299. (match files
  300. (() #t)
  301. (((file _) rest ...)
  302. (when (set-contains? seen file)
  303. (raise (formatted-message (G_ "duplicate '~a' entry for files/")
  304. file)))
  305. (loop rest (set-insert file seen))))))
  306. ;; Detect duplicates early instead of letting them through, eventually
  307. ;; leading to a build failure of "files.drv".
  308. (assert-no-duplicates files)
  309. (file-union "files" files))
  310. ;; Used by symlink-manager
  311. (define home-files-directory "files")
  312. (define (files-entry files)
  313. "Return an entry for the @file{~/.guix-home/files}
  314. directory containing FILES."
  315. (with-monad %store-monad
  316. (return `((,home-files-directory ,(files->files-directory files))))))
  317. (define home-files-service-type
  318. (service-type (name 'home-files)
  319. (extensions
  320. (list (service-extension home-service-type
  321. files-entry)))
  322. (compose concatenate)
  323. (extend append)
  324. (default-value '())
  325. (description "Files that will be put in
  326. @file{~/.guix-home/files}, and further processed during activation.")))
  327. (define xdg-configuration-files-directory ".config")
  328. (define (xdg-configuration-files files)
  329. "Add .config/ prefix to each file-path in FILES."
  330. (map (match-lambda
  331. ((file-path . rest)
  332. (cons (string-append xdg-configuration-files-directory "/" file-path)
  333. rest)))
  334. files))
  335. (define home-xdg-configuration-files-service-type
  336. (service-type (name 'home-xdg-configuration)
  337. (extensions
  338. (list (service-extension home-files-service-type
  339. xdg-configuration-files)))
  340. (compose concatenate)
  341. (extend append)
  342. (default-value '())
  343. (description "Files that will be put in
  344. @file{~/.guix-home/files/.config}, and further processed during activation.")))
  345. (define xdg-data-files-directory ".local/share")
  346. (define (xdg-data-files files)
  347. "Add .local/share prefix to each file-path in FILES."
  348. (map (match-lambda
  349. ((file-path . rest)
  350. (cons (string-append xdg-data-files-directory "/" file-path)
  351. rest)))
  352. files))
  353. (define home-xdg-data-files-service-type
  354. (service-type (name 'home-xdg-data)
  355. (extensions
  356. (list (service-extension home-files-service-type
  357. xdg-data-files)))
  358. (compose concatenate)
  359. (extend append)
  360. (default-value '())
  361. (description "Files that will be put in
  362. @file{~/.guix-home/files/.local/share}, and further processed during
  363. activation.")))
  364. (define %initialize-gettext
  365. #~(begin
  366. (bindtextdomain %gettext-domain
  367. (string-append #$guix "/share/locale"))
  368. (textdomain %gettext-domain)))
  369. (define (compute-on-first-login-script _ gexps)
  370. (program-file
  371. "on-first-login"
  372. (with-imported-modules (source-module-closure '((guix i18n)
  373. (guix diagnostics)))
  374. #~(begin
  375. (use-modules (guix i18n)
  376. (guix diagnostics))
  377. #$%initialize-gettext
  378. (let* ((xdg-runtime-dir (or (getenv "XDG_RUNTIME_DIR")
  379. (format #f "/run/user/~a" (getuid))))
  380. (flag-file-path (string-append
  381. xdg-runtime-dir "/on-first-login-executed"))
  382. (touch (lambda (file-name)
  383. (call-with-output-file file-name (const #t)))))
  384. ;; XDG_RUNTIME_DIR dissapears on logout, that means such trick
  385. ;; allows to launch on-first-login script on first login only
  386. ;; after complete logout/reboot.
  387. (if (file-exists? xdg-runtime-dir)
  388. (unless (file-exists? flag-file-path)
  389. (begin #$@gexps (touch flag-file-path)))
  390. ;; TRANSLATORS: 'on-first-login' is the name of a service and
  391. ;; shouldn't be translated
  392. (warning (G_ "XDG_RUNTIME_DIR doesn't exists, on-first-login script
  393. won't execute anything. You can check if xdg runtime directory exists,
  394. XDG_RUNTIME_DIR variable is set to appropriate value and manually execute the
  395. script by running '$HOME/.guix-home/on-first-login'"))))))))
  396. (define (on-first-login-script-entry on-first-login)
  397. "Return, as a monadic value, an entry for the on-first-login script
  398. in the home environment directory."
  399. (with-monad %store-monad
  400. (return `(("on-first-login" ,on-first-login)))))
  401. (define home-run-on-first-login-service-type
  402. (service-type (name 'home-run-on-first-login)
  403. (extensions
  404. (list (service-extension
  405. home-service-type
  406. on-first-login-script-entry)))
  407. (compose identity)
  408. (extend compute-on-first-login-script)
  409. (default-value #f)
  410. (description "Run gexps on first user login. Can be
  411. extended with one gexp.")))
  412. (define (compute-activation-script init-gexp gexps)
  413. (gexp->script
  414. "activate"
  415. #~(let* ((he-init-file (lambda (he) (string-append he "/setup-environment")))
  416. (he-path (string-append (getenv "HOME") "/.guix-home"))
  417. (new-home-env (getenv "GUIX_NEW_HOME"))
  418. (new-home (or new-home-env
  419. ;; Absolute path of the directory of the activation
  420. ;; file if called interactively.
  421. (canonicalize-path (dirname (car (command-line))))))
  422. (old-home-env (getenv "GUIX_OLD_HOME"))
  423. (old-home (or old-home-env
  424. (if (file-exists? (he-init-file he-path))
  425. (readlink he-path)
  426. #f))))
  427. (if (file-exists? (he-init-file new-home))
  428. (let* ((port ((@ (ice-9 popen) open-input-pipe)
  429. (format #f "source ~a && ~a -0"
  430. (he-init-file new-home)
  431. #$(file-append coreutils "/bin/env"))))
  432. (result ((@ (ice-9 rdelim) read-delimited) "" port))
  433. (vars (map (lambda (x)
  434. (let ((si (string-index x #\=)))
  435. (cons (string-take x si)
  436. (string-drop x (1+ si)))))
  437. ((@ (srfi srfi-1) remove)
  438. string-null?
  439. (string-split result #\nul)))))
  440. (close-port port)
  441. (map (lambda (x) (setenv (car x) (cdr x))) vars)
  442. (setenv "GUIX_NEW_HOME" new-home)
  443. (setenv "GUIX_OLD_HOME" old-home)
  444. #$@gexps
  445. ;; Do not unset env variable if it was set outside.
  446. (unless new-home-env (setenv "GUIX_NEW_HOME" #f))
  447. (unless old-home-env (setenv "GUIX_OLD_HOME" #f)))
  448. (format #t "\
  449. Activation script was either called or loaded by file from this directory:
  450. ~a
  451. It doesn't seem that home environment is somewhere around.
  452. Make sure that you call ./activate by symlink from -home store item.\n"
  453. new-home)))))
  454. (define (activation-script-entry m-activation)
  455. "Return, as a monadic value, an entry for the activation script
  456. in the home environment directory."
  457. (mlet %store-monad ((activation m-activation))
  458. (return `(("activate" ,activation)))))
  459. (define home-activation-service-type
  460. (service-type (name 'home-activation)
  461. (extensions
  462. (list (service-extension
  463. home-service-type
  464. activation-script-entry)))
  465. (compose identity)
  466. (extend compute-activation-script)
  467. (default-value #f)
  468. (description "Run gexps to activate the current
  469. generation of home environment and update the state of the home
  470. directory. @command{activate} script automatically called during
  471. reconfiguration or generation switching. This service can be extended
  472. with one gexp, but many times, and all gexps must be idempotent.")))
  473. ;;;
  474. ;;; Service type graph rewriting.
  475. ;;;
  476. (define (service-type-mapping proc)
  477. "Return a procedure that applies PROC to map a service type graph to another
  478. one."
  479. (define (rewrite extension)
  480. (match (proc (service-extension-target extension))
  481. (#f #f)
  482. (target
  483. (service-extension target
  484. (service-extension-compute extension)))))
  485. (define replace
  486. (mlambdaq (type)
  487. (service-type
  488. (inherit type)
  489. (name (symbol-append 'home- (service-type-name type)))
  490. (location (service-type-location type))
  491. (extensions (filter-map rewrite (service-type-extensions type))))))
  492. replace)
  493. (define %system/home-service-type-mapping
  494. ;; Mapping of System to Home services.
  495. (make-hash-table))
  496. (define system->home-service-type
  497. ;; Map the given System service type to the corresponding Home service type.
  498. (let ()
  499. (define (replace type)
  500. (define replacement
  501. (hashq-ref %system/home-service-type-mapping type
  502. *unspecified*))
  503. (if (eq? replacement *unspecified*)
  504. type
  505. replacement))
  506. (service-type-mapping replace)))
  507. (define-syntax define-service-type-mapping
  508. (syntax-rules (=>)
  509. ((_ system-type => home-type)
  510. (hashq-set! %system/home-service-type-mapping
  511. system-type home-type))))
  512. (define-syntax define-service-type-mappings
  513. (syntax-rules (=>)
  514. ((_ (system-type => home-type) ...)
  515. (begin
  516. (define-service-type-mapping system-type => home-type)
  517. ...))))
  518. (define-service-type-mappings
  519. (system-service-type => home-service-type)
  520. (activation-service-type => home-activation-service-type)
  521. (profile-service-type => home-profile-service-type))
  522. ;;;
  523. ;;; On-change.
  524. ;;;
  525. (define (compute-on-change-gexp eval-gexps? pattern-gexp-tuples)
  526. (with-imported-modules (source-module-closure '((guix i18n)))
  527. #~(begin
  528. (use-modules (guix i18n))
  529. #$%initialize-gettext
  530. (define (equal-regulars? file1 file2)
  531. "Check if FILE1 and FILE2 are bit for bit identical."
  532. (let* ((cmp-binary #$(file-append
  533. (@ (gnu packages base) diffutils) "/bin/cmp"))
  534. (stats1 (lstat file1))
  535. (stats2 (lstat file2)))
  536. (cond
  537. ((= (stat:ino stats1) (stat:ino stats2)) #t)
  538. ((not (= (stat:size stats1) (stat:size stats2))) #f)
  539. (else (= (system* cmp-binary file1 file2) 0)))))
  540. (define (equal-symlinks? symlink1 symlink2)
  541. "Check if SYMLINK1 and SYMLINK2 are pointing to the same target."
  542. (string=? (readlink symlink1) (readlink symlink2)))
  543. (define (equal-directories? dir1 dir2)
  544. "Check if DIR1 and DIR2 have the same content."
  545. (define (ordinary-file file)
  546. (not (or (string=? file ".")
  547. (string=? file ".."))))
  548. (let* ((files1 (scandir dir1 ordinary-file))
  549. (files2 (scandir dir2 ordinary-file)))
  550. (if (equal? files1 files2)
  551. (map (lambda (file)
  552. (equal-files?
  553. (string-append dir1 "/" file)
  554. (string-append dir2 "/" file)))
  555. files1)
  556. #f)))
  557. (define (equal-files? file1 file2)
  558. "Compares files, symlinks or directories of the same type."
  559. (case (file-type file1)
  560. ((directory) (equal-directories? file1 file2))
  561. ((symlink) (equal-symlinks? file1 file2))
  562. ((regular) (equal-regulars? file1 file2))
  563. (else
  564. (display "The file type is unsupported by on-change service.\n")
  565. #f)))
  566. (define (file-type file)
  567. (stat:type (lstat file)))
  568. (define (something-changed? file1 file2)
  569. (cond
  570. ((and (not (file-exists? file1))
  571. (not (file-exists? file2))) #f)
  572. ((or (not (file-exists? file1))
  573. (not (file-exists? file2))) #t)
  574. ((not (eq? (file-type file1) (file-type file2))) #t)
  575. (else
  576. (not (equal-files? file1 file2)))))
  577. (define expressions-to-eval
  578. (map
  579. (lambda (x)
  580. (let* ((file1 (string-append
  581. (or (getenv "GUIX_OLD_HOME")
  582. "/gnu/store/non-existing-generation")
  583. "/" (car x)))
  584. (file2 (string-append (getenv "GUIX_NEW_HOME") "/" (car x)))
  585. (_ (format #t (G_ "Comparing ~a and\n~10t~a...") file1 file2))
  586. (any-changes? (something-changed? file1 file2))
  587. (_ (format #t (G_ " done (~a)\n")
  588. (if any-changes? "changed" "same"))))
  589. (if any-changes? (cadr x) "")))
  590. '#$pattern-gexp-tuples))
  591. (if #$eval-gexps?
  592. (begin
  593. ;;; TRANSLATORS: 'on-change' is the name of a service type, it
  594. ;;; probably shouldn't be translated.
  595. (display (G_ "Evaluating on-change gexps.\n\n"))
  596. (for-each primitive-eval expressions-to-eval)
  597. (display (G_ "On-change gexps evaluation finished.\n\n")))
  598. (display "\
  599. On-change gexps won't be evaluated; evaluation has been disabled in the
  600. service configuration")))))
  601. (define home-run-on-change-service-type
  602. (service-type (name 'home-run-on-change)
  603. (extensions
  604. (list (service-extension
  605. home-activation-service-type
  606. identity)))
  607. (compose concatenate)
  608. (extend compute-on-change-gexp)
  609. (default-value #t)
  610. (description "\
  611. G-expressions to run if the specified files have changed since the
  612. last generation. The extension should be a list of lists where the
  613. first element is the pattern for file or directory that expected to be
  614. changed, and the second element is the G-expression to be evaluated.")))
  615. ;;;
  616. ;;; Provenance tracking.
  617. ;;;
  618. (define home-provenance-service-type
  619. (service-type
  620. (name 'home-provenance)
  621. (extensions
  622. (list (service-extension
  623. home-service-type
  624. (service-extension-compute
  625. (first (service-type-extensions provenance-service-type))))))
  626. (default-value #f) ;the HE config file
  627. (description "\
  628. Store provenance information about the home environment in the home
  629. environment itself: the channels used when building the home
  630. environment, and its configuration file, when available.")))
  631. (define sexp->home-provenance sexp->system-provenance)
  632. (define home-provenance system-provenance)
  633. ;;;
  634. ;;; Searching
  635. ;;;
  636. (define (parent-directory directory)
  637. "Get the parent directory of DIRECTORY"
  638. (string-join (drop-right (string-split directory #\/) 1) "/"))
  639. (define %guix-home-root-directory
  640. ;; Absolute file name of the module hierarchy.
  641. (parent-directory
  642. (dirname (dirname (search-path %load-path "gnu/home/services.scm")))))
  643. (define %service-type-path
  644. ;; Search path for service types.
  645. (make-parameter `((,%guix-home-root-directory . "gnu/home/services"))))
  646. (define (all-home-service-modules)
  647. "Return the default set of `home service' modules."
  648. (cons (resolve-interface '(gnu home services))
  649. (all-modules (%service-type-path)
  650. #:warn warn-about-load-error)))
  651. (define* (fold-home-service-types proc seed)
  652. (fold-service-types proc seed (all-home-service-modules)))
  653. (define lookup-home-service-types
  654. (let ((table
  655. (delay (fold-home-service-types (lambda (type result)
  656. (vhash-consq (service-type-name type)
  657. type result))
  658. vlist-null))))
  659. (lambda (name)
  660. "Return the list of services with the given NAME (a symbol)."
  661. (vhash-foldq* cons '() name (force table)))))