My literate config didn’t work in guix home and as it turned out (org-babel-load-file (expand-file-name "~/.emacs.d/config.org")) isn’t working on Guix as config.org is a symlink into the store and because the timestamp is 1970 config.el will not get updated.
I decided to advice org-babel-load-file so that tangling adds a comment with the guix store hash of the original file on top of the tangled file and every time org-babel-load-file gets triggered it will compare the hashes.
The end of my current init.el:
;; Add personal modules directory to load path
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
;; Load the Guix aware org-babel advice
(require 'guix-org-hash)
;; This is the actual config file
(org-babel-load-file (expand-file-name "~/.emacs.d/config.org"))
;;; guix-org-hash.el - Guix aware org-babel-load-file advice -*- lexical-binding: t; -*-
(require 'subr-x) ;; for when-let, string-trim, etc.
(defun guix--store-hash (path)
"Extract the Guix store hash from PATH, if it points into /gnu/store.
Return nil otherwise."
(when (string-match "^/gnu/store/\\([a-z0-9]+\\)-" path)
(match-string 1 path)))
(defun guix--read-embedded-hash (el-file)
"Read embedded Guix hash comment from EL-FILE, or nil if not present."
(when (file-exists-p el-file)
(with-temp-buffer
(insert-file-contents el-file nil 0 200) ;; just the first few lines
(when (re-search-forward "^;; Guix-hash: \\([a-z0-9]+\\)" nil t)
(match-string 1)))))
(defun guix--maybe-retangle (org-file el-file)
"Re-tangle ORG-FILE into EL-FILE if Guix store hash changed."
(let* ((truename (file-truename org-file))
(store-hash (guix--store-hash truename))
(old-hash (guix--read-embedded-hash el-file)))
(when (and store-hash (not (equal store-hash old-hash)))
(message "[Guix] Re-tangling %s (hash %s → %s)"
org-file (or old-hash "none") store-hash)
(require 'org)
(require 'ob-tangle)
(org-babel-tangle-file org-file el-file)
(with-current-buffer (find-file-noselect el-file)
(goto-char (point-min))
(insert (format ";; Guix-hash: %s\n" store-hash))
(save-buffer))
t))) ;; return t if retangled
(defun guix--babel-load-file-around (orig-fun org-file &rest args)
"Advice around `org-babel-load-file' that is aware of Guix store hashes."
(let* ((el-file (concat (file-name-sans-extension org-file) ".el")))
(unless (guix--maybe-retangle org-file el-file)
;; if no retangle triggered, fall back to standard behavior
(message "[Guix] Using cached %s" el-file))
(apply orig-fun org-file args)))
(advice-add 'org-babel-load-file :around #'guix--babel-load-file-around)
(provide 'guix-org-hash)