16

After recently learning about use-package I decided to port my configuration to it but found myself reluctant to give up the convenience of using package.el to install packages and keep them updated. I've found it a little tricky to combine use-package and package.el.

I'm generally interested in learning how people combine use-package with the package.el system, but for a more specific question, keep reading.

Here's what I want:

  1. To have packages installed by the package manager so I can easily browse for packages and keep them updated through list-packages.
  2. To configure and load packages exclusively through use-package, so I can easily see in my init file exactly what I'm loading and how it is configured.
  3. Optionally, I'd like to also be able to install packages through use-package's :ensure keyword.

If I'm understanding correctly I want very little of what package-initialize does, basically only the way it sets up the load-path. Currently I have this in my configuration:

;(package-initialize)
(setq package-enable-at-startup nil)
(let ((default-directory "~/.emacs.d/elpa"))
  (normal-top-level-add-subdirs-to-load-path))
(require 'use-package)

The first, commented, line is so Emacs 25 doesn't helpfully add a (package-initialize) to my init file. The bit with normal-top-level-add-subdirs-to-load-path is an approximation to what package-initialize would make the load-path, an approximation that seems good enough.

This seems to achieve my desires 1 and 2, but not 3. If I try to use :ensure, I get an error message saying that package.el is not initialized. Calling package-initialize would fix that, but I wish to avoid that since a) I don't want all the myriad autoloads to be loaded (I prefer to use use-package to create precisely the autoloads I need), and b) I want to be able to easily avoid loading certain installed packages whenever I want (which is easy to do with use-package).

Does anyone have a recommendation for how to do this?

Omar
  • 4,732
  • 1
  • 17
  • 32

2 Answers2

17

With your current configuration, you've effectively disabled package.el, as you don't initialise the package manager and prevent Emacs from automatically initialising it. All you do in return is to add ELPA to the load-path, but that's just a small subset of what package.el does. I'm not sure why you do that, but it's not a setup that I'd recommend.

Specifically, you'll not get package autoloads with your approach, which means that initially no commands from any package will be available.

In other words, M-x will only offer you built-in commands. To add in commands from your packages you'd have to add explicit :commands definitions to all your use-package declarations, which amounts to a lot of maintenance effort—particularly for large packages such as Magit—for essentially zero gain—package.el gives you autoloads for free.


Combining use-package with package.el is actually very simple—y entire setup is based on this combination—but it's much better to let package.el actually to its job. Just initialise package.el at the very beginning of your init file:

(require 'package)
(setq package-enable-at-startup nil)   ; To prevent initialising twice
(add-to-list 'package-archives '("melpa" . "https://stable.melpa.org/packages/"))

(package-initialize)

For convenience you may subsequently want to bootstrap use-package, if it's not already installed:

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

This let's you start an Emacs session on a fresh system, and your init.el will automatically install use-package.

Ultimately you need to load use-package:

(eval-when-compile
  (require 'use-package))

Now you can use use-package to install and configure packages:

(use-package magit                      ; The one and only Git frontend
  :ensure t
  :bind (("C-c v c" . magit-clone)
         ("C-c v v" . magit-status)
         ("C-c v g" . magit-blame)
         ("C-c v l" . magit-log-buffer-file)
         ("C-c v p" . magit-pull))
   :config (setq magit-save-repository-buffers 'dontask))

When Emacs now evaluates this form during startup, use-package will check whether Magit is already installed, and automatically install it if necessary.

  • 3
    "I'm not sure why you do that": the only reason I can see is about startup times: `package-initialize` takes some time to populate the path, define autoloads and do the rest of its stuff. I think I read somewhere that Jon Wiegley himself (the author of `use-package`) prefers declaring all autoloaded commands in `use-package` stanzas rather than relying on `package.el`. – François Févotte Sep 22 '15 at 08:44
  • Last time I looked he did not use package.el at all, and in any case, I don't think you'll gain a lot. You need to populate the `load-path` and add autoloads in either case, whether via `use-package` or via `package.el`. I doubt that there's a measurable difference, particularly if you've got a modern system with a fast disk. –  Sep 22 '15 at 09:03
  • 3
    Agreed. I did the timings myself. With a fast disk, you effectively don't see much of a difference. With a slow disk, the startup can be noticeably slower (something like 0.2s) with `package-initialize` than a custom list in `load-path`. I attribute this to the "exploration" of the filesystem that `package.el` does. However, I never measured any significant difference in performance between loading `autoload` definitions from files and having them in `use-package` stanzas. – François Févotte Sep 22 '15 at 09:32
  • Well, I wouldn't say I've *disabled* the `package.el` system, I'd say I only disabled `package-initialize`! The reason is that while I like `list-packages` to browse for new packages and specially to update all my currently installed packages, I think I prefer the targeted loading of `use-package`. For me having autoloads only for commands I use sounds like a good thing! – Omar Sep 22 '15 at 13:59
  • Also, less philosophically, I don't like the interaction between `package-initialize` and `:diminish` (for deferred `use-package` declarations): `package-initialize` will give me the default configuration, typically including advertising on mode lines; and diminish only kicks in the first time I use a command I `:bind` or declare with `:commands`. – Omar Sep 22 '15 at 14:03
  • Is it fair to say you think my goal of having `use-package` load *and* configure my packages is misguided and you would recommend having `package-initialize` load and `use-package` only configure? – Omar Sep 22 '15 at 14:05
  • @OmarAntolín-Camarena Uhm, `:diminish` doesn't have anything to do with how you enable a package. I'd not say your goal is “misguided”, but it's creating a lot of needless redundant work for you :) –  Sep 22 '15 at 14:34
  • I didn't explain my complaint about `:diminish` well. I should ask a separate question about it. I'm not sure what you mean by "needless redundant work". Do you mean that with `use-package` I'd have to use `:commands` occasionally to get some autoloads that `package-initialize` would give me? If so, that's minimal, usually `:bind` gives me all the autoloads I need. – Omar Sep 22 '15 at 14:49
  • @OmarAntolín-Camarena You'll also need `:modes` for all auto mode entries that major mode packages would normally add via autoloads. Also, you need to add `:commands` not only for your own needs, but also for autoloads that other packages expect. –  Oct 05 '15 at 12:52
  • Right, I forgot to mention `:modes`, which I think are reasonable to expect I should add by hand, but the other thing you mentions is troublesome: I didn't know that sometimes packages expect certain autoloads from *other* packages! That sounds awful. I don't think package authors should do that, but if they do I certainly don't want to deal with the consequences. I think you've convinced me to use `package-initialize` defensively. – Omar Oct 05 '15 at 18:57
  • 1
    @OmarAntolín-Camarena Why not? Autoloads are essentially the public interface of a user-facing package, and since package.el became the standard way of distributing packages we can rely upon their presence. –  Oct 05 '15 at 20:23
10

IIUC what you want to do is:

(package-initialize t)

Note the t argument, which is the key to your happiness here since it will (or should, at least) initialize package.el without activating all the installed packages.

Stefan
  • 26,154
  • 3
  • 46
  • 84
  • 1
    This answers my question, even though now I'm leaning towards using `package-initialize` which renders my question moot. – Omar Oct 05 '15 at 18:59