How to manage Emacs packages effectively

  |   Source

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.

Comments powered by Disqus