Skip to content

Latest commit

 

History

History
213 lines (182 loc) · 10.5 KB

20240724.org

File metadata and controls

213 lines (182 loc) · 10.5 KB

Use Guix to build & install an older Emacs version

I use Guix to manage the installation of some of my development tools, Emacs being one of them. At the time of writing, Guix gives me Emacs 29.4 but what if I need an older version? This note describes how I use Guix to install older Emacs versions next to the most-recent one.

Before I continue, a big shoutout to Steve George, whose Futurile blog contains a treasure trove of Guix documentation.

Background

Sometimes a client requires me to work in a Linux development VM with limited internet access. That limitation can make it difficult to install Spacemacs, which needs to download packages during installation. To work around that, I install Spacemacs on my personal development machine, which does have full internet access, and transfer the Spacemacs installation directory to the machine with limited internet access.

For this to work, both machines need to use the same Emacs version and I assume, also the same architecture. My personal development machine tends to run the most recent Emacs version that Guix provides. What if the target machine is running an older version which I cannot update easily?

How to build an older Emacs version

This Stack Overflow question ask the same question, “how to use a old version of a package, no longer in the channel” and its first answer points to Guix inferiors. From the Guix inferiors documentation,

Sometimes you might need to mix packages from the revision of Guix you’re currently running with packages available in a different revision of Guix. […] inferiors provide a simple way to interact with a separate revision of Guix.

To make this concrete, you create a manifest that specifies the older package version and that also specifies a revision of the Guix channel that provides that older version. The Guix documentation itself provides an example of a manifest to install the latest version (that Guix provides) of guile in combination with an older version of guile-json. I adapted that example for Emacs 29.2[fn:1]:

(use-modules (guix inferior) (guix channels)
             (srfi srfi-1))   ;for 'first'

(define channels
  (list (channel
         (name 'guix)
         (url "https://git.savannah.gnu.org/git/guix.git")
         (commit
          "aae61f54ff6acf5cc0e0355dc85babf29f625660"))))

(define inferior
  ;; An inferior representing the above revision.
  (inferior-for-channels channels))

(packages->manifest
 (list (first (lookup-inferior-packages inferior "[email protected]"))))

My (version of the) manifest only specifies a single channel, viz. the older revision of the Guix channel. Why not just specify that channel, or use guix timemachine? Well, the use of inferiors allows you to install the most-recent versions of other packages, which is something the other options do not allow you. My manifest above doesn’t install these other packages, but it could.

The commit hash in the above snippet is the commit hash for the first Guix revision that provides Emacs 29.2. To find that hash, I searched the git log of Git Guix repo file gnu/packages/emacs.scm for the commit whose message contained “Update to 29.2”.

The Stack Overflow answer used guix shell to create a temporary profile for the Guix environment that uses the older Guix channel revision. I did the same and the following snippet shows the output of that process:

~/repos/swinkels/emacs-29.2 guix shell --manifest=emacs.29.2.scm
Updating channel 'guix' from Git repository at 'https://git.savannah.gnu.org/git/guix.git'...
substitute: updating substitutes from 'https://ci.guix.gnu.org'... 100.0%
substitute: updating substitutes from 'https://bordeaux.guix.gnu.org'... 100.0%
substitute: updating substitutes from 'https://ci.guix.gnu.org'... 100.0%
...
Generating package cache for '/gnu/store/n9zi5lnvv1r3li2w0rr5rczdvf0v18pp-profile'...
(values (value "/gnu/store/6wa2b6rxa85jj7yklk24an3g19p69vz9-guix-package-cache/lib/guix/package.cache"))
building path(s) `/gnu/store/7c9l257g6i6kwn9m1s6v24pkapdvzvc8-profile'
The following derivation will be built:
  /gnu/store/p8ih04xhlm0jx7dvd85lzk56nq1zl4rc-profile.drv

After that command, you are in a Guix shell that provides Emacs 29.2:

~/repos/swinkels/emacs-29.2 emacs --version
GNU Emacs 29.2
Copyright (C) 2024 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.
➜  ~/repos/swinkels/emacs-29.2

Great, we have successfully build the older Emacs 29.2!

The not-so-good thing is that the rebuild takes quite some time… I don’t have the exact times but it took somewhere between 20 and 30 minutes on my ThinkPad T470. I think it rebuilds the exact version as was stored in the inferior, including all of its dependencies. To a certain extent it is the same reason Guix time-machine takes a lot of time.

In the example above we build Emacs 29.2 in a Guix shell. Such a shell is ephemeral, that is, its profile is temporary and can be garbage collected. I assume that when the shell is garbage collected, every package installed in it is also candidate for deletion. When that happens, we have to rebuild Emacs 29.2.

How to install your newly-build Emacs

The shell in which you’ve build Emacs 29.2 provides access to that version. How do you install it in another shell, or in a permanent profile? To start with the former, I found one way to do install it in another shell, namely using guix shell with the manifest with which Emacs 29.2 was build:

~/tmp # activate a shell that provides access to Emacs 29.2~/tmp guix shell --manifest=$HOME/repos/swinkels/emacs-29.2/manifest.scm
➜  ~/tmp emacs --version
GNU Emacs 29.2
Copyright (C) 2024 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of GNU Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

I assume that you can use another manifest as long as it specifies the relevant parts of the original manifest (but I haven’t tested this).

By the way, notice the use of the HOME environment variable to specify the full path to the manifest. Unfortunately Guix doesn’t allow you to use the ~~~ for that.

It can be that Guix first needs to compute the required derivation, which can take several minutes:

~/tmp guix shell --manifest=$HOME/repos/swinkels/emacs-29.2/manifest.scm
Updating channel 'guix' from Git repository at 'https://git.savannah.gnu.org/git/guix.git'...
Computing Guix derivation for 'x86_64-linux'... -
➜  ~/tmp

I saw this behavior when I hadn’t used the manifest for more than a month. Subsequent invocations of that shell were instantaneous.

To install 29.2 in another profile, use guix package install --profile=<profile path>. The following snippet creates a new profile named emacs-29.2 and installs in it:

~/ # create the directory to hold (the generations of) new profile emacs-29.2~/ mkdir -p $GUIX_EXTRA_PROFILES/emacs-29.2
➜  ~/ # install Emacs 29.2 from its manifest file to the new profile~/ guix package install --manifest=$HOME/repos/swinkels/emacs-29.2/manifest.scm --profile=$GUIX_EXTRA_PROFILES/emacs-29.2/emacs-29.2

Don’t forget to specify the profile! If you don’t specify the profile, it installs Emacs 29.2 to your default profile. It will downgrade (or upgrade) the Emacs version in that profile.

If you know the path in your Guix store to Emacs 29.2, you can use that path instead of the manifest. You can find that path when you’re in an active shell that provides access to Emacs 29.2. Inside an active shell, environment variable GUIX_ENVIRONMENT holds the path to the (temporary) profile of that shell[fn:2]:

~/tmp # activate a shell that provides access to Emacs 29.2~/tmp guix shell --manifest=$HOME/repos/swinkels/emacs-29.2/manifest.scm
➜  ~/tmp echo $GUIX_ENVIRONMENT
/gnu/store/j952l584aagwx1j6w97qwbicjyissgxs-profile

You can use that profile path to find the name of Emacs 29.2:

~/tmp guix package --list-installed --profile=$GUIX_ENVIRONMENT
emacs   29.2    out     /gnu/store/05c52ss0v3pxr4n2pvp4fs0ig8fab418-emacs-29.2

You can use this path instead of the manifest to install Emacs 29.2:

~/ # install Emacs 29.2 from its path in the Guix store~/ guix package --manifest=$HOME/repos/swinkels/emacs-29.2/manifest.scm --profile=$GUIX_EXTRA_PROFILES/emacs-29.2/emacs-29.2

Note that you cannot install Emacs 29.2 to an existing shell profile. A shell profile is considered read-only and this makes sense. The hash in its name is determined by the contents of the manifest (or command-line) from which it was created.

Closing thoughts

For a previous note I built a Python version that was newer than the most recent available in the main Guix channel. To do so I used a modified version of the (then) current Python package specification. That’s not always easy or even possible. For this note I rebuild an older Emacs version that isn’t available anymore in the main Guix channel. To do so I used an older version of the main Guix channel through inferiors. This always works but takes quite some (build) time.

There is a 3rd approach I want to try: use Guix to build the software using its own build procedure and create a new Guix package for it. This should be less work than adapting a version of its package specification and quicker than building it using inferiors. Of course, the drawback is that you lose reproducability. However, that approach is for another note and time.

Footnotes

[fn:1] You can find that manifest here.

[fn:2] GUIX_ENVIRONMENT is only defined when you’re in an active Guix shell. You can use this when your shell environment doesn’t inform you about that (such as my Oh My Zsh environment).