guix-profiles.el 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. ;;; guix-profiles.el --- Guix profiles
  2. ;; Copyright © 2014–2018 Alex Kost <alezost@gmail.com>
  3. ;; Copyright © 2015 Mathieu Lirzin <mthl@openmailbox.org>
  4. ;; This file is part of Emacs-Guix.
  5. ;; Emacs-Guix is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;;
  10. ;; Emacs-Guix is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;;
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with Emacs-Guix. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; This file provides a general code related to location and contents of
  19. ;; Guix profiles.
  20. ;;; Code:
  21. (require 'guix-config)
  22. (require 'guix-utils)
  23. (defun guix-profiles-directory ()
  24. "Return default directory with Guix profiles."
  25. (expand-file-name "profiles" guix-state-directory))
  26. (defun guix-user-profiles-directory (&optional user)
  27. "Return default directory with USER Guix profiles."
  28. (expand-file-name (concat "per-user/"
  29. (or user
  30. (getenv "USER")
  31. (getenv "LOGNAME")))
  32. (guix-profiles-directory)))
  33. (defvar guix-user-profile
  34. (expand-file-name "~/.guix-profile")
  35. "User profile.")
  36. (defvar guix-system-profile
  37. (expand-file-name "system" (guix-profiles-directory))
  38. "System profile.")
  39. (defvar guix-pulled-profile
  40. ;; XXX There is `xdg-config-home' in "xdg.el" in Emacs 26.
  41. (expand-file-name "guix/current"
  42. (or (getenv "XDG_CONFIG_HOME")
  43. (expand-file-name "~/.config")))
  44. "Profile populated by 'guix pull' command.")
  45. (defvar guix-default-user-profile
  46. (or (file-symlink-p guix-user-profile)
  47. (expand-file-name "guix-profile"
  48. (guix-user-profiles-directory)))
  49. "Default user profile.
  50. Unlike `guix-user-profile', directory with this profile should
  51. also contain profile generations.")
  52. (defvar guix-default-pulled-profile
  53. (or (file-symlink-p guix-pulled-profile)
  54. (expand-file-name "current-guix"
  55. (guix-user-profiles-directory)))
  56. "Default profile populated by 'guix pull' command.
  57. Unlike `guix-pulled-profile', directory with this profile should
  58. also contain profile generations.")
  59. (defvar guix-current-profile guix-default-user-profile
  60. "Current Guix profile.
  61. It is used by various commands as the default working profile.")
  62. (defvar guix-system-profile-regexp
  63. (rx-to-string `(and string-start
  64. (or ,guix-system-profile
  65. "/run/booted-system"
  66. "/run/current-system"))
  67. t)
  68. "Regexp matching system profiles.")
  69. (defvar guix-pulled-profile-regexp
  70. ;; XXX Should profiles from other users (HOME directories) be handled?
  71. (rx-to-string `(or ,guix-pulled-profile
  72. ,guix-default-pulled-profile)
  73. t)
  74. "Regexp matching 'guix pull'-ed profile.")
  75. (defvar guix-generation-file-name-regexp
  76. (rx (group (one-or-more any))
  77. "-" (one-or-more digit) "-link")
  78. "Regexp matching file names of profile generations.
  79. The first parenthesized group should match profile file name.")
  80. (defun guix-current-profile? (profile)
  81. "Return non-nil, if PROFILE is `guix-current-profile'."
  82. (string= (guix-profile profile)
  83. (guix-profile guix-current-profile)))
  84. (defun guix-system-profile? (profile)
  85. "Return non-nil, if PROFILE is a system one."
  86. (string-match-p guix-system-profile-regexp profile))
  87. (defun guix-pulled-profile? (profile)
  88. "Return non-nil, if PROFILE is populated by 'guix pull'."
  89. (string-match-p guix-pulled-profile-regexp profile))
  90. (defun guix-assert-non-system-profile (profile)
  91. "Raise an error when PROFILE is a system one."
  92. (when (guix-system-profile? profile)
  93. (user-error "\
  94. Packages cannot be installed or removed to/from profile '%s'.
  95. Use 'guix system reconfigure' shell command to modify a system profile."
  96. profile)))
  97. (defun guix-generation-file (profile generation)
  98. "Return the file name of a PROFILE's GENERATION."
  99. (format "%s-%s-link" profile generation))
  100. (defun guix-generation-file-name->profile (file-name)
  101. "Return profile file name by generation FILE-NAME.
  102. Return nil if FILE-NAME does not look like a generation file name."
  103. (when (string-match guix-generation-file-name-regexp file-name)
  104. (match-string-no-properties 1 file-name)))
  105. (defun guix-profile (profile)
  106. "Return normalized file name of PROFILE.
  107. \"Normalized\" means the returned file name is expanded, does not
  108. have a trailing slash and special profiles are handled:
  109. `guix-default-pulled-profile' instead of `guix-pulled-profile'
  110. and `guix-default-user-profile' instead of `guix-user-profile'."
  111. (let ((profile (guix-file-name profile)))
  112. (cond
  113. ((string= profile guix-user-profile)
  114. guix-default-user-profile)
  115. ((string= profile guix-pulled-profile)
  116. guix-default-pulled-profile)
  117. (t profile))))
  118. (defun guix-generation-profile (profile &optional generation)
  119. "Return file name of PROFILE or its GENERATION.
  120. The returned file name is the one that have generations in the
  121. same parent directory.
  122. If PROFILE matches `guix-system-profile-regexp', then it is
  123. considered to be a system profile. Unlike usual profiles, for a
  124. system profile, packages are placed in 'profile' sub-directory,
  125. so the returned file name does not contain this potential
  126. trailing '/profile'."
  127. (let* ((profile (guix-profile profile))
  128. (profile (if (and (guix-system-profile? profile)
  129. (string-match (rx (group (* any))
  130. "/profile" string-end)
  131. profile))
  132. (match-string 1 profile)
  133. profile)))
  134. (if generation
  135. (guix-generation-file profile generation)
  136. profile)))
  137. (defun guix-package-profile (profile &optional generation)
  138. "Return file name of PROFILE or its GENERATION.
  139. The returned file name is the one where packages are installed.
  140. If PROFILE is a system one (see `guix-generation-profile'), then
  141. the returned file name ends with '/profile'."
  142. (let* ((profile (guix-generation-profile profile))
  143. (profile (if generation
  144. (guix-generation-file profile generation)
  145. profile)))
  146. (if (guix-system-profile? profile)
  147. (expand-file-name "profile" profile)
  148. profile)))
  149. (defun guix-manifest-file (profile &optional generation)
  150. "Return manifest file name of PROFILE or its GENERATION."
  151. (expand-file-name "manifest"
  152. (guix-package-profile profile generation)))
  153. (defun guix-profile-number-of-packages (profile &optional generation)
  154. "Return the number of packages installed in PROFILE or its GENERATION.
  155. Return nil if packages are not found (presumably because PROFILE
  156. is not a guix profile)."
  157. (let ((manifest (guix-manifest-file profile generation)))
  158. ;; Just count a number of sexps inside (packages ...) of manifest
  159. ;; file. It should be much faster than running the REPL and
  160. ;; calculating manifest entries on the Scheme side.
  161. (when (file-exists-p manifest)
  162. (with-temp-buffer
  163. (insert-file-contents-literally manifest)
  164. (goto-char (point-min))
  165. (re-search-forward "(packages" nil t)
  166. (down-list)
  167. (let ((num 0)
  168. (pos (point)))
  169. (while (setq pos (condition-case nil
  170. (scan-sexps pos 1)
  171. (error nil)))
  172. (setq num (1+ num)))
  173. num)))))
  174. (defun guix-profile-number-of-generations (profile)
  175. "Return the number of generations of PROFILE."
  176. (let* ((profile (guix-generation-profile profile))
  177. (dir-name (file-name-directory profile))
  178. (base-name (file-name-nondirectory profile))
  179. (regexp (concat (regexp-quote base-name)
  180. "-[[:digit:]]+-link")))
  181. (when (file-exists-p profile)
  182. (length (directory-files dir-name nil regexp 'no-sort)))))
  183. ;;; Minibuffer readers
  184. (defun guix-read-profile (&optional default)
  185. "Prompt for profile and return it.
  186. Use DEFAULT as a start directory. If it is nil, use
  187. `guix-current-profile'."
  188. (guix-read-file-name "Profile: "
  189. (file-name-directory
  190. (or default guix-current-profile))))
  191. (defun guix-read-package-profile (&optional default)
  192. "Prompt for a package profile and return it.
  193. See `guix-read-profile' for the meaning of DEFAULT, and
  194. `guix-package-profile' for the meaning of package profile."
  195. (guix-package-profile (guix-read-profile default)))
  196. (defun guix-read-generation-profile (&optional default)
  197. "Prompt for a generation profile and return it.
  198. See `guix-read-profile' for the meaning of DEFAULT, and
  199. `guix-generation-profile' for the meaning of generation profile."
  200. (guix-generation-profile (guix-read-profile default)))
  201. (defun guix-read-manifest-file-name (&optional prompt)
  202. "Prompt for a manifest file name and return it."
  203. (guix-read-file-name (or prompt "File with manifest: ")))
  204. ;;;###autoload
  205. (defun guix-set-current-profile (file-name)
  206. "Set `guix-current-profile' to FILE-NAME.
  207. Interactively, prompt for FILE-NAME. With prefix, use
  208. `guix-user-profile'."
  209. (interactive
  210. (list (if current-prefix-arg
  211. guix-user-profile
  212. (guix-read-package-profile))))
  213. (setq guix-current-profile file-name)
  214. (message "Current profile has been set to '%s'."
  215. guix-current-profile))
  216. (provide 'guix-profiles)
  217. ;;; guix-profiles.el ends here