init-guix.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. (defvar ambrevar/guix-checkout-directory (expand-file-name "~/projects/guix"))
  2. (with-eval-after-load 'geiser-guile
  3. (when (require 'yasnippet nil t)
  4. ;; This is not enough since COMMIT_MSG is not in scheme-mode.
  5. ;; TODO: Add to find-file-hook instead and check if parent folder is ~/projects/guix.
  6. ;; (add-hook 'scheme-mode-hook 'yas-minor-mode)
  7. (with-eval-after-load 'yasnippet
  8. (add-to-list 'yas-snippet-dirs
  9. (expand-file-name "etc/snippets"
  10. ambrevar/guix-checkout-directory)))
  11. (yas-global-mode 1))
  12. (add-to-list 'geiser-guile-load-path ambrevar/guix-checkout-directory)
  13. (dolist (dir '("~/projects/nonguix" "~/projects/games"))
  14. (when (file-directory-p dir)
  15. (add-to-list 'geiser-guile-load-path dir))))
  16. ;; To use package declaration from the local checkout:
  17. ;; (setq guix-load-path ambrevar/guix-checkout-directory)
  18. (defun ambrevar/init-guix ()
  19. (and buffer-file-name
  20. (string-match "\\<guix\\>" buffer-file-name)
  21. (guix-devel-mode)))
  22. (add-hook 'scheme-mode-hook 'ambrevar/init-guix)
  23. (defun ambrevar/guix-debbugs-gnu (&optional severities packages archivedp suppress tags)
  24. "Like `debbugs-gnu' but for the Guix project."
  25. (interactive)
  26. (let ((debbugs-gnu-default-packages '("guix-patches" "guix")))
  27. (if (called-interactively-p)
  28. (call-interactively 'debbugs-gnu)
  29. (debbugs-gnu severities packages archivedp suppress tags))))
  30. (require 'guix nil 'noerror)
  31. (defun ambrevar/guix-generations-list-diff-this ()
  32. "List guix-generation-list-diff but compare generation at point
  33. with previous."
  34. (interactive)
  35. (let ((diff-fun #'guix-diff)
  36. (gen-fun #'guix-profile-generation-packages-buffer))
  37. (funcall diff-fun
  38. (funcall gen-fun (1- (bui-list-current-id)))
  39. (funcall gen-fun (bui-list-current-id)))))
  40. (with-eval-after-load 'guix-ui-generation
  41. (define-key guix-generation-list-mode-map "=" #'ambrevar/guix-generations-list-diff-this))
  42. (defvar ambrevar/guix-extra-channels "~/.guix-extra-channels")
  43. (defvar ambrevar/guix-extra-profiles "~/.guix-extra-profiles")
  44. (defvar ambrevar/guix-manifest-directory "~/.package-lists")
  45. (defvar ambrevar/guix-system-directory "~/.config/guix/system")
  46. (defvar ambrevar/guix-channel-spec-directory "~/.package-lists")
  47. (defvar ambrevar/guix-always-use-channel-specs nil
  48. "If non-nil, automatically use a channel specification matching the chosen manifest.
  49. The channel specification is looked up in
  50. `ambrevar/guix-channel-spec-directory'.")
  51. (cl-defun ambrevar/guix-query-file (&key file directory
  52. (filter ".")
  53. (prompt "File: ")
  54. (name-function #'identity)
  55. (multiple? nil))
  56. "Query a file matching FILTER in DIRECTORY.
  57. Return (NAME FILE).
  58. If FILE is non-nil, then this function is useful to derive the name of the manifest.
  59. NAME-FUNCTION takes the file base name as argument and returns NAME.
  60. If MULTIPLE? is non-nil, return a list of (NAME FILE) of the selected manifests."
  61. (cl-flet ((name (file)
  62. (replace-regexp-in-string
  63. "guix-" ""
  64. (funcall name-function
  65. (file-name-base file)))))
  66. (if file
  67. (list (name file) file)
  68. (let ((files (mapcar (lambda (file)
  69. (list (name file) file))
  70. (directory-files directory 'full filter))))
  71. (if multiple?
  72. (mapcar (lambda (name)
  73. (assoc name files))
  74. (completing-read-multiple prompt (mapcar #'cl-first files)))
  75. (assoc (completing-read prompt (mapcar #'cl-first files))
  76. files))))))
  77. (defun ambrevar/guix-query-manifest (&optional manifest multiple?)
  78. "Query a manifest as found in `ambrevar/guix-manifest-directory'.
  79. Return (NAME FILE).
  80. If MANIFEST is non-nil, then this function is useful to derive the name of the manifest.
  81. If MULTIPLE? is non-nil, allow querying multiple manifests."
  82. (ambrevar/guix-query-file
  83. :file manifest
  84. :directory ambrevar/guix-manifest-directory
  85. :filter "manifest"
  86. :prompt "Manifest(s): "
  87. :name-function (lambda (name)
  88. (replace-regexp-in-string "-?manifest-?" "" name))
  89. :multiple? multiple?))
  90. (defun ambrevar/guix-query-system (&optional system)
  91. "Query a system as found in `ambrevar/guix-system-directory'.
  92. Return (NAME FILE).
  93. If SYSTEM is non-nil, then this function is useful to derive the name of the system. "
  94. (ambrevar/guix-query-file
  95. :file system
  96. :directory ambrevar/guix-system-directory
  97. :filter "scm"
  98. :prompt "System: "))
  99. (defun ambrevar/guix-query-channel-spec (&optional channel-spec)
  100. "Query a channel specification as found in `ambrevar/guix-channel-spec-directory'.
  101. Return (NAME FILE).
  102. If CHANNEL-SPEC is non-nil, then this function is useful to derive the name of
  103. the channel specification."
  104. (ambrevar/guix-query-file
  105. :file channel-spec
  106. :directory ambrevar/guix-channel-spec-directory
  107. :filter "channels"
  108. :prompt "Channel specification: "
  109. :name-function (lambda (name)
  110. (replace-regexp-in-string "-?channels?-?" "" name))))
  111. (defun ambrevar/guix-edit-system (&optional system)
  112. "Edit system.
  113. If SYSTEM is nil, it is queried from the systems found in `ambrevar/guix-system-directory'."
  114. (interactive)
  115. (setq system (cl-second (ambrevar/guix-query-system system)))
  116. (find-file system))
  117. (global-set-key (kbd "C-x c s") #'ambrevar/guix-edit-system)
  118. (with-eval-after-load 'evil
  119. ;; For some reason `global-set-key' does not work for Evil at this point.
  120. (dolist (mode '(normal insert))
  121. (evil-global-set-key mode (kbd "C-x c s") #'ambrevar/guix-edit-system)))
  122. (defun ambrevar/guix-edit-manifest (&optional manifest)
  123. "Edit MANIFEST.
  124. If MANIFEST is nil, it is queried from the manifests found in `ambrevar/guix-manifest-directory'."
  125. (interactive)
  126. (setq manifest (cl-second (ambrevar/guix-query-manifest manifest)))
  127. (find-file manifest))
  128. (global-set-key (kbd "C-x c g") #'ambrevar/guix-edit-manifest)
  129. (with-eval-after-load 'evil
  130. ;; For some reason `global-set-key' does not work for Evil at this point.
  131. (dolist (mode '(normal insert))
  132. (evil-global-set-key mode (kbd "C-x c g") #'ambrevar/guix-edit-manifest)))
  133. (defun ambrevar/guix-save-channel-specs (dest)
  134. "Save current Guix channel specification to DEST."
  135. (call-process "guix"
  136. nil `(:file ,dest) nil
  137. "describe" "--format=channels"))
  138. (defun ambrevar/guix-find-channel-from-manifest (pattern)
  139. "Return the channel specification file matching PATTERN in
  140. `ambrevar/guix-channel-spec-directory'."
  141. (cl-first (directory-files ambrevar/guix-channel-spec-directory 'full
  142. (concat pattern "-channel"))))
  143. (defun ambrevar/run-in-eshell (command)
  144. (let ((eshell-buffer-name "*guix*"))
  145. (eshell)
  146. (when (eshell-interactive-process)
  147. (eshell t))
  148. (if (get-buffer-process (current-buffer))
  149. (message "Try again after current process termination.")
  150. (eshell-interrupt-process)
  151. (insert command)
  152. (eshell-send-input))))
  153. (defun ambrevar/run-in-shell (command)
  154. (shell (get-buffer-create "*guix*"))
  155. (let ((process (get-buffer-process (current-buffer))))
  156. (when process
  157. (accept-process-output process 0.1)))
  158. (if (helm-ff-shell-alive-p major-mode)
  159. (message "Try again after current process termination.")
  160. (goto-char (point-max))
  161. (comint-delete-input)
  162. (insert command)
  163. (comint-send-input)))
  164. (defun %ambrevar/guix-install-manifest (&optional manifests channel)
  165. "Install list of (NAME MANIFEST-FILE) using CHANNEL."
  166. (require 'init-shell) ; For `helm-ff-preferred-shell-mode'.
  167. (let* ((guix (if channel
  168. (let ((dest (expand-file-name
  169. ;; TODO: What name should we use with multiple manifests?
  170. (cl-first (cl-first manifests))
  171. ambrevar/guix-extra-channels)))
  172. (make-directory dest 'parents)
  173. (format "guix pull --channels=%s --profile=%s/guix && %s/guix/bin/guix"
  174. (shell-quote-argument channel)
  175. (shell-quote-argument dest)
  176. (shell-quote-argument dest)))
  177. "guix")))
  178. (dolist (manifest-pair manifests)
  179. (let ((manifest-name (cl-first manifest-pair)))
  180. (make-directory (expand-file-name manifest-name
  181. ambrevar/guix-extra-profiles)
  182. 'parents)))
  183. (funcall (if (eq helm-ff-preferred-shell-mode 'eshell-mode)
  184. 'ambrevar/run-in-eshell
  185. 'ambrevar/run-in-shell)
  186. (mapconcat #'identity
  187. (mapcar (lambda (manifest-pair)
  188. (let ((manifest-name (cl-first manifest-pair))
  189. (manifest (cl-second manifest-pair))
  190. (profile (expand-file-name ambrevar/guix-extra-profiles)))
  191. (string-join
  192. (list "echo" (format "'==> Installing manifest %S to profile %S'"
  193. manifest-name profile)
  194. ";"
  195. guix "package" (concat "--manifest=" manifest)
  196. (if (string= "default" manifest-name)
  197. ""
  198. (concat "--profile=" profile
  199. "/" manifest-name
  200. "/" manifest-name)))
  201. " ")))
  202. manifests)
  203. " ; "))
  204. (unless channel
  205. ;; TODO: Only do this when manifest install has succeeded.
  206. (dolist (manifest-pair manifests)
  207. (let ((manifest-name (cl-first manifest-pair)))
  208. (ambrevar/guix-save-channel-specs
  209. (format "%s/guix-%s-channels.scm"
  210. ambrevar/guix-channel-spec-directory
  211. manifest-name)))))))
  212. (defun ambrevar/guix-install-manifest (&optional manifest channel)
  213. "Install Guix manifest to `ambrevar/guix-extra-profiles'.
  214. Manifest is queried from those found in `ambrevar/guix-manifest-directory'.
  215. Guix channel specification is stored in `ambrevar/guix-channel-spec-directory'.
  216. With a prefix argument, query for a channel specification file.
  217. If CHANNEL is nil and `ambrevar/guix-always-use-channel-specs' is
  218. non-nil, then try to use a channel specification file from
  219. `ambrevar/guix-channel-spec-directory' if any."
  220. (interactive)
  221. (let* ((manifest-pair (ambrevar/guix-query-manifest manifest))
  222. (manifest-name (cl-first manifest-pair))
  223. (manifest (cl-second manifest-pair))
  224. (channel (or channel
  225. (and current-prefix-arg
  226. (cl-second (ambrevar/guix-query-channel-spec)))
  227. (and ambrevar/guix-always-use-channel-specs
  228. (ambrevar/guix-find-channel-from-manifest manifest-name)))))
  229. (%ambrevar/guix-install-manifest (list manifest-pair) channel)))
  230. (defun ambrevar/guix-install-manifests (&optional manifests channel)
  231. "Install Guix manifests to `ambrevar/guix-extra-profiles'.
  232. Manifests are queried from those found in `ambrevar/guix-manifest-directory'.
  233. Guix channel specification is stored in `ambrevar/guix-channel-spec-directory'.
  234. With a prefix argument, query for a channel specification file."
  235. (interactive)
  236. (let* ((manifests (or manifests (ambrevar/guix-query-manifest nil :multiple)))
  237. (channel (or channel
  238. (and current-prefix-arg
  239. (cl-second (ambrevar/guix-query-channel-spec))))))
  240. (%ambrevar/guix-install-manifest manifests channel)))
  241. (global-set-key (kbd "C-x c G") #'ambrevar/guix-install-manifests)
  242. (with-eval-after-load 'evil
  243. (dolist (mode '(normal insert))
  244. (evil-global-set-key mode (kbd "C-x c G") #'ambrevar/guix-install-manifests)))
  245. (defun ambrevar/guix-install-system (&optional system)
  246. "Install Guix system.
  247. System is queried from those found in `ambrevar/guix-system-directory'. "
  248. (interactive)
  249. (require 'init-shell) ; For `helm-ff-preferred-shell-mode'.
  250. (let* ((system-pair (ambrevar/guix-query-system system))
  251. (system-name (cl-first system-pair))
  252. (system (cl-second system-pair)))
  253. (funcall (if (eq helm-ff-preferred-shell-mode 'eshell-mode)
  254. 'ambrevar/run-in-eshell
  255. 'ambrevar/run-in-shell)
  256. (string-join
  257. (list "sudo" "-E" "guix" "system" "-L" ambrevar/guix-system-directory
  258. "reconfigure" system)
  259. " "))))
  260. (global-set-key (kbd "C-x c S") #'ambrevar/guix-install-system)
  261. (with-eval-after-load 'evil
  262. ;; For some reason `global-set-key' does not work for Evil at this point.
  263. (dolist (mode '(normal insert))
  264. (evil-global-set-key mode (kbd "C-x c S") #'ambrevar/guix-install-system)))
  265. ;; TODO: See `guix-apply-manifest' and expand on it.
  266. ;; TODO: Use --max-jobs=N.
  267. (provide 'init-guix)