Guix by its nature conduits a change to a package along the dependency graph, producing a working profile containing the new dependency sub-graph. In the profile there can be packages depending on different versions of a conceptually same package, which might works because ideally different sub-graphs are isolated.
For Emacs sadly, a newer Emacs instance does not always work well with packages compiled with previous Emacs versions.
The solution to this is of course build all used Emacs packages with new Emacs version.
Attempt that failed
It is mentioned that we can use with-input
transformation or equivalent to accomplish this, see
- Fredrik Salomonsson and Nicolas Goaziou and Simon Tournier and Maxim Cournoyer: Implement a wrapper so users can build the Emacs packages using a version of their choosing(Salomonsson et al. 2020)
- Mekeor Melire and Liliana Marie Prikler and Simon Tournier: Emacs-Packages should contain native-compiled files(Melire, Prikler, and Tournier 2023)
- Mekeor Melire and Liliana Marie Prikler and Maxim Cournoyer and Simon Tournier: Allow for easily rewriting Emacs packages to use emacs-next(Melire et al. 2023)
In my home configuration, I have all Emacs packages declared as simple services extending home-profile-service-type, so I came up with
(define-module (hiecaq home emacs) #:use-module (gnu services) #:use-module (gnu packages) #:use-module (gnu home services) #:use-module (guix transformations) #:use-module (guix gexp)) (define transform-with-emacs-next (options->transformation (list '(with-input . "emacs-minimal=emacs-next-minimal") '(with-input . "emacs=emacs-next"))) (define-public services (map (lambda (serv) (if (service-value serv) (service (service-kind serv) (map transform-with-emacs-next (service-value serv))) serv)) (list ;; emacs package services here )))
Which does not work well, so I looked for other solutions.
Second attempt
So by searching around I discovered (Joseph 2024), which gives a hint on using package-input-rewriting
.
I also take inspiration from this configuration that I should try defining a service for Emacs, to both strengthen my Guix-fu and to solidify my configuration set-up.
It turns out to be pretty simple. Mostly I just need to follow how gnu/home/services/shells.scm
does it.
First, define a base configuration:
(define-configuration/no-serialization home-emacs-configuration (emacs (package upstream:emacs) "Emacs to use.") (emacs-compiler (package upstream:emacs-minimal) "Emacs used for compiling packages.") (packages (list '()) "List of Emacs packages to use.") (configs (alist '()) "Emacs configuration files."))
This is the actual configuration used for the very home-emacs-service-type
service.
To override Emacs used in the packages, I define the following helper function. The emacs package used for compilation is put into inputs
, and is referred from that during Emacs package compilation. There are actually 3 inputs needs to be overridden:
(define (home-emacs-transformed-package config) (package-input-rewriting `((,upstream:emacs-minimal . ,(home-emacs-configuration-emacs-compiler config)) (,upstream:emacs-no-x . ,(home-emacs-configuration-emacs config)) (,upstream:emacs . ,(home-emacs-configuration-emacs config)))))
Then I define a pseudo getter function for the transformed packages, plus the actual Emacs instance to use:
(define (home-emacs-profile config) `(,(home-emacs-configuration-emacs config) ,@(map (home-emacs-transformed-package config) (home-emacs-configuration-packages config))))
The next thing is to define the extension. All services extends home-emacs-service-type
will provide the following record:
(define-configuration/no-serialization home-emacs-extension (packages (list '()) "Extra list of Emacs packages to use.") (configs (alist '()) "Extra Emacs configuration files."))
How the actual single service of home-emacs-service-type
consumes these provided extension record is defined as the following helper function.
It takes the home-emacs-configuration
, which is declared when the single service of home-emacs-service-type
is defined, and the list of home-emacs-extension
records from the services extending home-emacs-service-type
. It simply just join packages
and configs
fields of these 2 records kind together.
(define (home-emacs-extensions original-config extension-configs) (let ((append-fields (lambda (config-getter extension-getter) (append (config-getter original-config) (append-map extension-getter extension-configs))))) (home-emacs-configuration (inherit original-config) (packages (append-fields home-emacs-configuration-packages home-emacs-extension-packages)) (configs (append-fields home-emacs-configuration-configs home-emacs-extension-configs)))))
Finally, the actual service type is
(define home-emacs-service-type (service-type (name 'home-emacs) (extensions (list (service-extension home-xdg-configuration-files-service-type home-emacs-configuration-configs) (service-extension home-profile-service-type home-emacs-profile))) (compose identity) (extend home-emacs-extensions) (default-value (home-emacs-configuration)) (description #f)))
Besides using the helper functions above, the only other thing worth noting is that compose
is using identity
, which means the list of home-emacs-extension
records are passed as-is.
How this works in reality
First you'll want a home-emacs-service-type
service that declare to use emacs-next.
(service home-emacs-service-type (home-emacs-configuration (emacs upstream:emacs-next) (emacs-compiler upstream:emacs-next-minimal)))
To declare a package usage, simply
(simple-service
'home-emacs-god-mode
home-emacs-service-type
(home-emacs-extension
(packages
(list
(specification->package
"emacs-god-mode")))))