How to manage Emacs packages effectively
Here are a few techniques I developed after reading Steve Purcell's setup.
The techniques are compatible with use-package because it uses Emacs API.
Mid-level Lisp knowledge is required to read this article.
Do NOT use package.el for certain packages
Create the directory ~/.emacs.d/site-lisp
. Then insert below code into ~/.emacs
,
(if (fboundp 'normal-top-level-add-to-load-path)
(let* ((my-lisp-dir "~/.emacs.d/site-lisp/")
(default-directory my-lisp-dir))
(progn
(setq load-path
(append
(loop for dir in (directory-files my-lisp-dir)
unless (string-match "^\\." dir)
collecting (expand-file-name dir))
load-path)))))
You can place a package's source code at sub-directory of ~/.emacs.d/site-lisp/
. That's all you need to do to install packages.
Create your own package repository
Step 1, Place two files "archive-contents" and "hello-1.0.0.el" in any directory. Say ~/.emacs.d/localelpa
.
Here is the content of archive-contents
:
(1
(hello . [(1 0 0) nil "Say hello" single])
)
Here is the content of hello-1.0.0.el
:
;;;###autoload
(defun hello-say ()
(interactive)
(message "Hi, hello!"))
(provide 'hello)
Step 2, insert below code into ~/.emacs
,
(add-to-list 'package-archives '("localelpa" . "~/.emacs.d/localelpa"))
Step 3, restart Emacs and run M-x list-packages
. As you can see, you can install package named "hello" now!
Here is a real world example how I apply this technique. I use rainbow-mode
from https://elpa.gnu.org/ which shuts down sometimes. So I built a local repository to host rainbow-mode
and a few other packages to remove dependency on GNU site.
I also create a elpa-mirror which creates a local repository from the installed packages. This local repository could be converted to remote repository using Dropbox and Github easily.
Orphan package issue is also resolved by elpa-mirror
. You can delete everything from ~/.emacs.d/elpa
and set the repository to the local repository created by elpa-mirror
. It only takes 30 seconds to install 300 packages.
Advice package--add-to-archive-contents
to filter packages
Insert below code into ~/.emacs,
;; List of VISIBLE packages from melpa-unstable (http://melpa.org)
;; Feel free to add more packages!
(defvar melpa-include-packages
'(bbdb
color-theme
company-c-headers)
"Don't install any mELPA packages except these packages")
(defvar package-filter-function nil
"Optional predicate function used to internally filter packages used by package.el.
The function is called with the arguments PACKAGE VERSION ARCHIVE, where
PACKAGE is a symbol, VERSION is a vector as produced by `version-to-list', and
ARCHIVE is the string name of the package archive.")
;; Don't take MELPA versions of certain packages
(setq package-filter-function
(lambda (package version archive)
(or (not (string-equal archive "melpa"))
;; install package in whitelist
(memq package melpa-include-packages)
;; use all color themes
(string-match (format "%s" package) "-theme"))))
(defadvice package--add-to-archive-contents
(around filter-packages (package archive) activate)
"Add filtering of available packages using `package-filter-function', if non-nil."
(when (or (null package-filter-function)
(funcall package-filter-function
(car package)
(funcall (if (fboundp 'package-desc-version)
'package--ac-desc-version
'package-desc-vers)
(cdr package))
archive))
ad-do-it))
The above code builds the filter defined in package-filter-function
to get the final version of packages list.
The filter accepts the package if it's NOT from melpa-unstable OR it's listed in melpa-include-packages
OR its name contains "-theme".
Surely you can build your own filter.
This solution is copied from Steve Purcell's setup with a little modification.
Summary
You can combine above techniques to solve any package issue.
For example, package A is dependent on package B. Both A and B have two versions, 1.0 and 2.0:
- A 2.0 can use B 1.0 and B 2.0, but A 1.0 can ONLY use B 1.0
- A 2.0 can ONLY use B 2.0, and A 1.0 can only use B 1.0
The solution is simple. We create a local repository to host B 1.0
and A 1.0
. As I said, package-filter-function
only returns a boolean expression. So you can design any strategy.
I know some one believs "Emacs package manager sucks" after mastering it for seven years. That's certainly not the truth as I have proved. I spent 15 minutes to reach the opposite conclusion when I was a Emacs dummy.
It's possibly I started my journey by learning from experts instead of "studiyng" by myself.