Helpers for manifest management
I've recently written a blog article on "Guix Profiles in Practice" which make heavy use of manifests. (Should hit the official Guix blog soon.)
Emacs-Guix has guix-apply-manifest
, but I believe a few things could be done to improve the workflow:
-
Provide a default location for manifest files and complete over them.
-
Provide a default location for profile installation, e.g. the "foo" manifest should install to
guix-extra-profile-directory/foo/foo
, with "guix-extra-profile-directory" being the customizable location. -
Provide a default location to store the channel specifications (those returned by
guix describe -f channels
). -
After a successful FOO manifest install, generate the channel specification and store it in
guix-channel-spec-direocty/FOO-channel-spec.scm
for instance. This is important so the that user can keep track of which Guix commit / channels were used for a given profile. Without this information, it's impossible to reproduce a profile bit-per-bit on a different machine. -
With a prefix argument, ask for a channel specification file (complete over the default dir) when installing a manifest.
What do you think? Let me know if you'd like me to detail the workflow.
I've worked on a draft, here it is:
(defvar ambrevar/guix-extra-channels "~/.guix-extra-channels")
(defvar ambrevar/guix-extra-profiles "~/.guix-extra-profiles")
(defvar ambrevar/guix-manifest-directory "~/.package-lists")
(defvar ambrevar/guix-channel-spec-directory "~/.package-lists")
(defvar ambrevar/guix-always-use-channel-specs nil
"If non-nil, automatically use a channel specification matching the chosen manifest.
The channel specification is looked up in
`ambrevar/guix-channel-spec-directory'.")
(cl-defun ambrevar/guix-query-file (&key file directory
(filter ".")
(prompt "File: ")
(name-function #'identity))
"Query a file matching FILTER in DIRECTORY.
Return (NAME FILE).
If FILE is non-nil, then this function is useful to derive the name of the manifest.
NAME-FUNCTION take the file base name as argument and returns NAME."
(cl-flet ((name (file)
(replace-regexp-in-string
"-?guix-?" ""
(funcall name-function
(file-name-base file)))))
(if file
(list (name file) file)
(let ((files (mapcar (lambda (file)
(list (name file) file))
(directory-files directory 'full filter))))
(assoc (completing-read prompt (mapcar #'first files))
files)))))
(defun ambrevar/guix-query-manifest (&optional manifest)
"Query a manifest as found in `ambrevar/guix-manifest-directory'.
Return (NAME FILE).
If MANIFEST is non-nil, then this function is useful to derive the name of the manifest."
(ambrevar/guix-query-file
:file manifest
:directory ambrevar/guix-manifest-directory
:filter "manifest"
:prompt "Manifest: "
:name-function (lambda (name)
(replace-regexp-in-string "-?manifest-?" "" name))))
(defun ambrevar/guix-query-channel-spec (&optional channel-spec)
"Query a channel specification as found in `ambrevar/guix-channel-spec-directory'.
Return (NAME FILE).
If CHANNEL-SPEC is non-nil, then this function is useful to derive the name of
the channel specification."
(ambrevar/guix-query-file
:file channel-spec
:directory ambrevar/guix-channel-spec-directory
:filter "channels"
:prompt "Channel specification: "
:name-function (lambda (name)
(replace-regexp-in-string "-?channels?-?" "" name))))
(defun ambrevar/guix-edit-manifest (&optional manifest)
"Edit MANIFEST.
If MANIFEST is nil, it is queried from the manifests found in `ambrevar/guix-manifest-directory'."
(interactive)
(setq manifest (second (ambrevar/guix-query-manifest manifest)))
(find-file manifest))
(global-set-key (kbd "C-x c g") #'ambrevar/guix-edit-manifest)
(defun ambrevar/guix-save-channel-specs (dest)
"Save current Guix channel specification to DEST."
(call-process "guix"
nil `(:file ,dest) nil
"describe" "--format=channels"))
(defun ambrevar/guix-find-channel-from-manifest (pattern)
"Return the channel specification file matching PATTERN in
`ambrevar/guix-channel-spec-directory'."
(first (directory-files ambrevar/guix-channel-spec-directory 'full
(concat pattern "-channel"))))
(defun ambrevar/guix-install-manifest (&optional manifest channel)
"Install Guix manifest to `ambrevar/guix-extra-profiles'.
Manifest is queried from those found in `ambrevar/guix-manifest-directory'.
Guix channel specification is stored in `ambrevar/guix-channel-spec-directory'.
With a prefix argument, query for a channel specification file.
If CHANNEL is nil and `ambrevar/guix-always-use-channel-specs' is
non-nil, then try to use a channel specification file from
`ambrevar/guix-channel-spec-directory' if any."
(interactive)
(let* ((manifest-pair (ambrevar/guix-query-manifest manifest))
(manifest-name (first manifest-pair))
(manifest (second manifest-pair))
(channel (or channel
(and current-prefix-arg
(second (ambrevar/guix-query-channel-spec)))
(and ambrevar/guix-always-use-channel-specs
(ambrevar/guix-find-channel-from-manifest manifest-name))))
(guix (if channel
(let ((dest (expand-file-name
manifest-name
ambrevar/guix-extra-channels)))
(make-directory dest 'parents)
(format "guix pull --channels=%s --profile=%s/guix && %s/guix/bin/guix"
(shell-quote-argument channel)
(shell-quote-argument dest)
(shell-quote-argument dest)))
"guix")))
(make-directory (expand-file-name manifest-name
ambrevar/guix-extra-profiles)
'parents)
(let ((eshell-buffer-name "*guix*"))
(eshell)
(when (eshell-interactive-process)
(eshell t))
(eshell-interrupt-process)
(insert guix " package" (concat " --manifest=" manifest)
(if (string= "default" manifest-name)
""
(concat " --profile=" (expand-file-name ambrevar/guix-extra-profiles)
"/" manifest-name
"/" manifest-name)))
(eshell-send-input))
(unless channel
;; TODO: Only do this when manifest install has succeeded.
(ambrevar/guix-save-channel-specs
(format "%s/guix-%s-channels.scm"
ambrevar/guix-channel-spec-directory
manifest-name)))))
(global-set-key (kbd "C-x c G") #'ambrevar/guix-install-manifest)
I can clean it up and send a patch if you are interested.