Rationale
It turns out that the Emacs package management system, package.el, doesn’t perform SSL certificate verification without some fairly involved wrangling. My Emacs configuration is something that I want to be able to clone and run on systems where it might be a real pain to perform the wrangling needed to ensure packages may be downloaded securely over encrypted HTTP.
Another issue with downloading packages from MELPA, the most popular repository for package.el, is that some packages are pulled into that repository from the EmacsWiki over unencrypted HTTP.
A further problem with MELPA is that it moves very fast, and new versions of packages that are not compatible with each other or perhaps your configuration means that you can find yourself with a broken editor in the middle of trying to get work done. To deal with this issue there is MELPA Stable, which contains hopefully-stable releases of packages that are more likely to be compatible with other packages. The problem is that many packages are in MELPA but not MELPA Stable because the author has not tagged any releases, and of the packages that are in MELPA Stable, many require newer versions of their dependencies than the versions of those dependencies available in MELPA Stable.
In short, package.el and MELPA are not dpkg, apt and the Debian Stable archive. Hopefully someday they will be. But for the moment, I don’t want to manage my Emacs packages this way.
Managing packages as git subtrees
An alternative is to manage package repositories as
git subtrees.
Assuming that your ~/.emacs.d/
is kept in a git repository, we can
run
$ cd ~/.emacs.d
$ git subtree add --squash -P pkg/magit https://github.com/magit/magit 2.3.0
and then Magit becomes available in ~/.emacs.d/pkg/magit
. The
following lisp will add all the dirs ~/.emacs.d/pkg/*
and
~/.emacs.d/pkg/*/lisp
to your load-path; you can modify this by
changing the variable globs
:
;;;; ---- package management ----
;; be sure not to load stale bytecode-compiled lisp
(setq load-prefer-newer t)
;; this is where all subtree packages are
(defconst emacs-pkg-dir (concat user-emacs-directory "pkg"))
;; load up f, and its dependencies s and dash, so we can use `f-glob'
;; and `f-join'
(dolist (pkg '("f.el" "dash.el" "s.el"))
(add-to-list 'load-path (concat emacs-pkg-dir "/" pkg)))
(require 'f) (require 's) (require 'dash)
;; helper function
(defun expand-all-globs (root globs)
(let ((do-glob (lambda (glob) (f-glob (f-join root glob)))))
(apply 'nconc (mapcar do-glob globs))))
;; now add all my pkg lisp directories
(let* ((globs '("*" "*/lisp"))
(dirs (expand-all-globs emacs-pkg-dir globs)))
(dolist (dir dirs)
(when (file-directory-p dir)
(add-to-list 'load-path dir))))
;; finally put my own site-lisp at the front of `load-path'
(add-to-list 'load-path (concat user-emacs-directory "site-lisp"))
;; we will use use-package to load everything else
(require 'use-package)
When you want to update to a new version of a package,
$ cd ~/.emacs.d
$ git subtree pull --squash -P pkg/magit https://github.com/magit/magit 2.3.1
Note:
- This commits the source code of Magit to your
~/.emacs.d/
git repository. So when you clone your config to a new machine, all your packages will already be there and Emacs won’t have to download them (potentially insecurely). - There’s no dependency management. You’ll have to add subtrees for every dependency. At present, if you don’t update your packages often, this is not too onerous.
- You should run
C-u 0 M-x byte-recompile-directory ~/.emacs.d/pkg RET
periodically (normally, package.el would do this for you).
Shell script
Here is a shell script to reduce typing in adding and updating
subtrees. It also logs git repository clone URIs and versions fetched
to a file ~/.emacs.d/pkg/subtrees
so that you can find the URI to
use when you want to do an update:
$ cat ~/.emacs.d/pkg/subtrees
https://github.com/magit/magit 2.3.1
https://github.com/lewang/flx v0.6.1
Use it like this:
$ emacs-pkg-subtree add https://github.com/magit/magit 2.3.0
$ emacs-pkg-subtree pull https://github.com/magit/magit 2.3.1
#!/bin/bash
# emacs-pkg-subtree --- manage Emacs packages as git subtrees in your dotfiles git repo
# Author/maintainer : Sean Whitton <spwhitton //ANTI-SPAM \\ spwhitton.name>
# Instructions for use : https://spwhitton.name/blog/entry/emacs-pkg-subtree/
# Copyright (C) 2015 Sean Whitton. Released under the GNU GPL 3.
DEST="$HOME/.emacs.d/pkg"
set -e
if [ "$3" = "" ]; then
echo "$(basename $0): usage: $(basename $0) add|pull git_clone_uri ref" >&2
exit 1
fi
cd $DEST
op="$1"
uri="$2"
repo="$(basename $2)"
pkg="${repo%%\.git}"
ref="$3"
top="$(git rev-parse --show-toplevel)"
prefix="${DEST##$top/}/$pkg"
cd $top
clean="$(git status --porcelain)"
if [ ! -z "$clean" ]; then
echo "commit first" >&2
exit 1
fi
if [ "$op" = "add" ]; then
if [ ! -e "$DEST/$pkg" ]; then
git subtree add --squash --prefix $prefix $uri $ref
echo "$uri $ref" >> $DEST/subtrees
git add $DEST/subtrees
git commit -m "updated Emacs packages record"
else
echo "you already have a subtree by that name" >&2
exit 1
fi
elif [ "$op" = "pull" ]; then
git subtree pull --squash --prefix $prefix $uri $ref
sed -i -e "s|^${uri} .*$|${uri} ${ref}|" $DEST/subtrees
git add $DEST/subtrees
git commit -m "updated Emacs packages record"
else
echo "$(basename $0): usage: $(basename $0) add|pull git_clone_uri ref" >&2
exit 1
fi