Compiling Emacs 29 for Debian/Ubuntu/Mint
I wanted to explore some of the new features in Emacs 29 (in particular Eglot). The version of Emacs that ships with my version of Linux Mint is Emacs 27, so I decided to build Emacs 29 from scratch.
This turned out to be absolutely painless; but installing the new version seamlessly into the existing environment did cause some hang-ups.
Compilation
The source tarball can be downloaded from one of the mirrors. To handle the dependencies, I used a build container with bind mounds, as described in a previous post.
The commands to run inside the container:
# Enable source repos
sed -i 's/# deb-src/deb-src/' /etc/apt/sources.list
apt update
apt build-dep emacs
apt install libgnutls28-dev libgtk-3-dev libwebkit2gtk-4.1-dev \
gnutls-bin gnutls-dev libgccjit0 libgccjit-10-dev \
libjansson4 libjansson-dev gnutls-bin libtree-sitter-dev \
gcc-10 imagemagick libmagick++-dev libwebp-dev webp libxft-dev libxft2
export CC=/usr/bin/gcc-10 && export CXX=/usr/bin/gcc-10 && ./autogen.sh
./configure --prefix=/opt/emacs29
make
make install
Don’t ask me about the list of dependencies — it is based on this
gist,
but changes the dependency on gnutls
to the separate packages
gnutls-bin
and gnutls-dev
as required for my distro.
The --prefix=/opt
flag selects /opt
as the installation directory,
instead of the default of /usr/local
. The installation will create
a directory emacs29
underneath /opt
and place all its artifacts
there. The container’s /opt
directory was mapped, via a bind mount,
to a directory on the host system.
Installation
I prefer to install third-party (non-distro) packages into /opt
,
rather than scattering them throughout /usr
or /usr/local
. In
this case, I placed the directory emacs29
, containing all build
artifacts, into /opt
on the host system. The main binary is at
/opt/emacs29/emacs
and starts up without any problems.
Not surprisingly, emacs29 does not find any previously installed
packages: they are in their default location /usr/share/emacs/site-lisp
.
Unfortunately, adding this location to the load-path
, using
(add-to-list 'load-path "/usr/share/emacs/site-lisp/")
does not really work.
Packages in site-lisp
are installed by my distro, or through my
distro’s package manager. They are usually autoloaded. For instance,
the package for rainbow-mode
is installed in site-lisp
, and M-x rainbow- TAB
will autocomplete just fine, without loading or
requiring anything in my .emacs
. I can also refer to 'rainbow-mode
in my .emacs
and the expression will be recognized. However, in
emacs29, this does not work, not even after adding the directory to
the load-path
as described above: Emacs still does not know anything about
rainbow-mode
; I need to require the package explicitly in .emacs
.
(Adding to load-path
in the
early init file
also does not work.)
Apparently, Emacs treats files underneath its default location(s)
differently than arbitrary directories on its load-path
. I guess
this must be mentioned somewhere in the documentation, but I have
not been able to find a reference to it. It is also not totally clear
whether this is an Emacs feature, or due to some Debian packaging. (The
Debian Emacs Policy
makes for fascinating, although slightly intimidating, reading.
Another interesting reference point is
/usr/share/emacs/NN.N/lisp/startup.el
, which contains the
command Emacs executes when launched.)
By the way, I don’t think this problem has anything to do with
descendant directories not being mentioned explicitly in the
load-path
: the site-lisp
directory contains a subdirs.el
file that should take care of that.
My solution is practical, but a little hacky: I created a symlink
from the old site-lisp
directory to the location of the new
emacs29 installation:
cd /opt/emacs29/share/emacs
sudo rm -rf site-lisp
sudo ln -s /usr/share/emacs/site-lisp/ .
Now all the packages in the “old” site-lisp
directory are
detected and autoloaded when emacs29 starts up. (I don’t think
this will re-enable the Debian start-up modifications, but so
far I have not noticed any ill effects to them missing.)
Customization
Finally, I broke my customizations for each version of Emacs
into separate files, and let the .emacs
file load whichever
one is appropriate:
(if (version< (number-to-string emacs-major-version) "29")
(load-file (expand-file-name "~/.emacs.d/init27.el"))
(load-file (expand-file-name "~/.emacs.d/init29.el")))
Of course it is also possible to retain a single .emacs
file
and use conditional expressions inside of it.