How to automatically install Emacs packages by specifying a list of package names?
Asked Answered
L

13

134

I am using package to manage my Emacs extensions. In order to synchronize my Emacs settings on different computers, I'd like a way to specify a list of package names in .emacs file and then package could automatically search and install the packages, so that I don't need to install them manually by calling M-x package-list-packages. How to do that?

Letti answered 10/4, 2012 at 16:7 Comment(1)
If you are relying upon the package manager to install your configuration, you will probably want to specify the exact versions (and if that's not possible, consider storing everything in version control yourself), as otherwise you are not protected when libraries are updated and start to conflict.Kite
L
45

Based on comments by Profpatsch and answers below:

(defun ensure-package-installed (&rest packages)
  "Assure every package is installed, ask for installation if it’s not.

Return a list of installed packages or nil for every skipped package."
  (mapcar
   (lambda (package)
     ;; (package-installed-p 'evil)
     (if (package-installed-p package)
         nil
       (if (y-or-n-p (format "Package %s is missing. Install it? " package))
           (package-install package)
         package)))
   packages))

;; make sure to have downloaded archive description.
;; Or use package-archive-contents as suggested by Nicolas Dudebout
(or (file-exists-p package-user-dir)
    (package-refresh-contents))

(ensure-package-installed 'iedit 'magit) ;  --> (nil nil) if iedit and magit are already installed

;; activate installed packages
(package-initialize)
Letti answered 10/4, 2012 at 20:18 Comment(9)
Is that … a map with side effects? And misusing the laziness of or? Oh, wow.Bunyip
Well, mapc is for side effects. But why not use unless?Bunyip
Previously I used this code and sometimes it did not work for some unknown reason saying "Package blah-blah is not available for installation" (here blah-blah is always the first element of the list). If I install the first package manually, everything works fine, but it is not a solution. Anyway, the answer from Nicolas Dudebois works fine.Crumpton
I needed (package-initialize) before the reference to package-user-dirGutshall
So where do we actually list the packages we want installed?Maraud
@drozzy The list that is contained in package-activated-list after calling package-initialize should be set to a variable like (setq packages '(all yaml-mode)) and then (apply #'ensure-package-installed packages)Cyprinoid
Two questions/improvements: 1) How can I defer the y-or-n-p query until a window is presented to the user, i.e. do not run this function when starting the emacs daemon. 2) How can I always update the package-list with the latest before trying to install? Both are problems I have encountered while using this code snippet. Thanks.Inunction
This doesn't seem to work tells me the package isn't available for installation...Mayamayakovski
found issue, the elpa dir existed and that prevented script from refreshing the index of packages....Mayamayakovski
G
116
; list the packages you want
(setq package-list '(package1 package2))

; list the repositories containing them
(setq package-archives '(("elpa" . "http://tromey.com/elpa/")
                         ("gnu" . "http://elpa.gnu.org/packages/")
                         ("marmalade" . "http://marmalade-repo.org/packages/")))

; activate all the packages (in particular autoloads)
(package-initialize)

; fetch the list of packages available 
(unless package-archive-contents
  (package-refresh-contents))

; install the missing packages
(dolist (package package-list)
  (unless (package-installed-p package)
    (package-install package)))
Gog answered 10/4, 2012 at 17:16 Comment(6)
I prefer: (or (file-exists-p package-user-dir) (package-refresh-contents)) from the accepted answer. The package refresh here increases startup time on systems that already have the packages installed. The rest of this answer is perfect, though.Erythrocytometer
Symbol's value as variable is void: package-archive-contents. Is there any way that I can make a list in .emacs and use a function defined in it to install all the packages in the list(skip if installed, update if old) like Vundle for Vim. Because I don't want to push all packages in elpa/ to github, I have to do it every time a package is updated in package.Attribution
What do you mean @Erythrocytometer ? It looks like package-refresh-contents would only be run if the package is not installed? How is (or (file-exists-p package-user-dir)) better / how does it even check if packages are installed?Neoplasty
@Neoplasty yes you are correct! It checks to see whether the user's package directory exists, and if it doesn't it runs package-refresh-contents. This will probably only be run the first time you open emacs on a new computer, and I'm fine with that. If a package needs updating that can be done manually.Erythrocytometer
If you're already using use-package, you can use the :ensure keyword to install packages automatically. This also sets up package-selected-packages if you need to access the package list through customize or programatically.Hatpin
What's the difference between this and using emacs auto install?Araceli
H
51

Emacs 25.1+ will automatically keep track of user-installed packages in the customizable package-selected-packages variable. package-install will update the customize variable, and you can install all selected packages with the package-install-selected-packages function.

Another convenient advantage of this approach is that you can use package-autoremove to automatically remove packages that are not included in package-selected-packages (though it will preserve dependencies).

(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))
(package-install-selected-packages)

Source: http://endlessparentheses.com/new-in-package-el-in-emacs-25-1-user-selected-packages.html

Hatpin answered 6/10, 2016 at 8:29 Comment(0)
L
45

Based on comments by Profpatsch and answers below:

(defun ensure-package-installed (&rest packages)
  "Assure every package is installed, ask for installation if it’s not.

Return a list of installed packages or nil for every skipped package."
  (mapcar
   (lambda (package)
     ;; (package-installed-p 'evil)
     (if (package-installed-p package)
         nil
       (if (y-or-n-p (format "Package %s is missing. Install it? " package))
           (package-install package)
         package)))
   packages))

;; make sure to have downloaded archive description.
;; Or use package-archive-contents as suggested by Nicolas Dudebout
(or (file-exists-p package-user-dir)
    (package-refresh-contents))

(ensure-package-installed 'iedit 'magit) ;  --> (nil nil) if iedit and magit are already installed

;; activate installed packages
(package-initialize)
Letti answered 10/4, 2012 at 20:18 Comment(9)
Is that … a map with side effects? And misusing the laziness of or? Oh, wow.Bunyip
Well, mapc is for side effects. But why not use unless?Bunyip
Previously I used this code and sometimes it did not work for some unknown reason saying "Package blah-blah is not available for installation" (here blah-blah is always the first element of the list). If I install the first package manually, everything works fine, but it is not a solution. Anyway, the answer from Nicolas Dudebois works fine.Crumpton
I needed (package-initialize) before the reference to package-user-dirGutshall
So where do we actually list the packages we want installed?Maraud
@drozzy The list that is contained in package-activated-list after calling package-initialize should be set to a variable like (setq packages '(all yaml-mode)) and then (apply #'ensure-package-installed packages)Cyprinoid
Two questions/improvements: 1) How can I defer the y-or-n-p query until a window is presented to the user, i.e. do not run this function when starting the emacs daemon. 2) How can I always update the package-list with the latest before trying to install? Both are problems I have encountered while using this code snippet. Thanks.Inunction
This doesn't seem to work tells me the package isn't available for installation...Mayamayakovski
found issue, the elpa dir existed and that prevented script from refreshing the index of packages....Mayamayakovski
F
19

Here's the code I use for Emacs Prelude:

(require 'package)
(require 'melpa)
(add-to-list 'package-archives
             '("melpa" . "http://melpa.milkbox.net/packages/") t)
(package-initialize)

(setq url-http-attempt-keepalives nil)

(defvar prelude-packages
  '(ack-and-a-half auctex clojure-mode coffee-mode deft expand-region
                   gist haml-mode haskell-mode helm helm-projectile inf-ruby
                   magit magithub markdown-mode paredit projectile
                   python sass-mode rainbow-mode scss-mode solarized-theme
                   volatile-highlights yaml-mode yari yasnippet zenburn-theme)
  "A list of packages to ensure are installed at launch.")

(defun prelude-packages-installed-p ()
  (loop for p in prelude-packages
        when (not (package-installed-p p)) do (return nil)
        finally (return t)))

(unless (prelude-packages-installed-p)
  ;; check for new packages (package versions)
  (message "%s" "Emacs Prelude is now refreshing its package database...")
  (package-refresh-contents)
  (message "%s" " done.")
  ;; install the missing packages
  (dolist (p prelude-packages)
    (when (not (package-installed-p p))
      (package-install p))))

(provide 'prelude-packages)

If you're not using MELPA you don't need to require it (and if you do melpa.el has got to be on your load-path (or installed via MELPA). The package db is not refreshed each time (as this would slow down the startup significantly) - only where there are uninstalled packages present.

Fitzpatrick answered 11/4, 2012 at 8:6 Comment(2)
Based your answer, I've modified it a bit and removed the use of 'loop' github.com/slipset/emacs/blob/master/ensure-packages.elPlacet
Yeah, this example is really more complex than it needs to be. The code I currently use in Prelude is much simpler.Fitzpatrick
I
6

No one has mentioned Cask yet, but it is quite suitable for this task.

Basically you create ~/.emacs.d/Cask listing the packages you want to install. For example:

(source melpa)
(depends-on "expand-region")
(depends-on "goto-last-change")
; ... etc

Running cask from the command line will install these packages for you, and any dependencies they need.

Also, you can automatically update installed packages using cask update.

Illustrator answered 2/3, 2014 at 21:19 Comment(4)
I have been using cask in my dotfiles for some time now, works great.Illustrator
A pity Cask seems to require Python. I wonder if there is an elisp-only alternative? (That's in a package; obviously the answers on this page meet the elisp requirement.)Puerperium
The python script is a thin wrapper around cask-cli.el, which you can invoke directly if you like: /path/to/emacs -Q --script /path/to/cask/cask-cli.el -- [args]Illustrator
Interesting! Isn't it possible to use it from inside of Emacs? I guess it's because it's also a dev tool, but it's kinda unusual to have to step outside of Emacs to a CLI to manage Emacs.Puerperium
A
4

Call package-install with the package name as a symbol. You can find the package names for your packages by calling package-install interactively and completing on the name. The function package-installed-p will let you know if it's already been installed.

For example:

(mapc
 (lambda (package)
   (or (package-installed-p package)
       (package-install package)))
 '(package1 package2 package3))
Aiglet answered 10/4, 2012 at 16:23 Comment(7)
Thanks, but I got an error error: Package dired+' is not available for installation`. dired+ is a package I tried with your code.Letti
Does dired+ show up when you run package-list-packages? I believe you'll need to add either marmalade or melpa to your package-archives. If so, can you run (package-install 'dired+)?Aiglet
In that case, (package-installed-p 'dired+) should return t and it will be skipped in the above code.Aiglet
The package-installed-p alone works fine, but the whole block of code doesn't. I've tried several packages.Letti
Make sure you don't quote package names inside a quoted list. Use something like '(dired+), not '('dired+)Aiglet
Though I am not so familiar with elisp, I won't make that mistake. I think the problem is that the data of package list is not loaded automatically. The error is gone if I add package-initialize or package-refresh-contents before your codeLetti
Looks like the prelude in Nicolas Dudebout's answer will solve that.Aiglet
A
4
(require 'cl)
(require 'package)

(setq cfg-var:packages '(
       emmet-mode
       ergoemacs-mode
       flycheck
       flycheck-pyflakes
       monokai-theme
       py-autopep8
       py-isort
       rainbow-mode
       yafolding
       yasnippet))

(defun cfg:install-packages ()
    (let ((pkgs (remove-if #'package-installed-p cfg-var:packages)))
        (when pkgs
            (message "%s" "Emacs refresh packages database...")
            (package-refresh-contents)
            (message "%s" " done.")
            (dolist (p cfg-var:packages)
                (package-install p)))))

(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t)
(package-initialize)

(cfg:install-packages)
Actinolite answered 10/9, 2015 at 6:25 Comment(0)
G
3

I like checking if the user wants to install the packages first as done in this answer. Also I'm refreshing my package contents once before installing anything. I'm not sure if this is the best way, but I don't think the top answers were doing it for me.

(setq required-pkgs '(jedi flycheck cider clojure-mode paredit markdown-mode jsx-mode company))

(require 'cl)

(setq pkgs-to-install
      (let ((uninstalled-pkgs (remove-if 'package-installed-p required-pkgs)))
        (remove-if-not '(lambda (pkg) (y-or-n-p (format "Package %s is missing. Install it? " pkg))) uninstalled-pkgs)))

(when (> (length pkgs-to-install) 0)
  (package-refresh-contents)
  (dolist (pkg pkgs-to-install)
    (package-install pkg)))
Gutshall answered 18/6, 2015 at 21:20 Comment(0)
M
1

Here's mine, it's shorter :)

(mapc
 (lambda (package)
   (unless (package-installed-p package)
     (progn (message "installing %s" package)
            (package-refresh-contents)
            (package-install package))))
 '(browse-kill-ring flycheck less-css-mode tabbar org auto-complete undo-tree clojure-mode markdown-mode yasnippet paredit paredit-menu php-mode haml-mode rainbow-mode fontawesome))
Money answered 20/7, 2017 at 18:23 Comment(0)
L
1

I ran into a problem that nothing happened after adding (package-install 'org) into .emacs. I wanted to install the up-to-date version of org-mode and the built-in org-mode is quite old.

I dug out the source code of package-install from Emacs 25.3.1. The function self already checks if a package is installed or not and refuses to install it if the package is already installed. So the check (unless (package-installed-p package) ...) from answer 10093312 is in fact uncalled for.

(defun package-install (pkg &optional dont-select)
  "Install the package PKG.
PKG can be a package-desc or a symbol naming one of the available packages
in an archive in `package-archives'.  Interactively, prompt for its name.

If called interactively or if DONT-SELECT nil, add PKG to
`package-selected-packages'.

If PKG is a package-desc and it is already installed, don't try
to install it but still mark it as selected."
  (interactive
   (progn
     ;; Initialize the package system to get the list of package
     ;; symbols for completion.
     (unless package--initialized
       (package-initialize t))
     (unless package-archive-contents
       (package-refresh-contents))
     (list (intern (completing-read
                    "Install package: "
                    (delq nil
                          (mapcar (lambda (elt)
                                    (unless (package-installed-p (car elt))
                                      (symbol-name (car elt))))
                                  package-archive-contents))
                    nil t))
           nil)))
  (add-hook 'post-command-hook #'package-menu--post-refresh)
  (let ((name (if (package-desc-p pkg)
                  (package-desc-name pkg)
                pkg)))
    (unless (or dont-select (package--user-selected-p name))
      (package--save-selected-packages
       (cons name package-selected-packages)))
    (if-let ((transaction
              (if (package-desc-p pkg)
                  (unless (package-installed-p pkg)
                    (package-compute-transaction (list pkg)
                                                 (package-desc-reqs pkg)))
                (package-compute-transaction () (list (list pkg))))))
        (package-download-transaction transaction)
      (message "`%s' is already installed" name))))

The built-in org-mode also counts as installed and package-install refuses to install the newer version from ELPA. After spending some time reading package.el, I came up with the following solution.

(dolist (package (package-compute-transaction
                  () (list (list 'python '(0 25 1))
                           (list 'org '(20171211)))))
  ;; package-download-transaction may be more suitable here and
  ;; I don't have time to check it
  (package-install package))

The reason why it works is that package-* family functions handle the arguments differently based on whether if it is a symbol or a package-desc object. You can only specify version info for package-install via a package-desc object.

Laccolith answered 17/12, 2017 at 2:57 Comment(0)
V
1

Here's another way.

;; assure every package is installed
(defun ensure-package-installed (&rest packages)
  (let ((user-required-packages
         (seq-remove
          (lambda (package) (package-installed-p package))
          packages)))
    (when user-required-packages
      (package-refresh-contents)
      (dolist (package user-required-packages)
        (package-install package)))))

;; list of packages to install
(ensure-package-installed
 'try
 'which-key)
Vernellvernen answered 11/5, 2020 at 5:16 Comment(0)
R
1

Close to Nicholas's answer:

Packages package-1, package-2, and package-3 are installed if they're not present locally. If they are present, Emacs loads without any delay.

(setq package-archives                                      ;
      '(("gnu" . "https://elpa.gnu.org/packages/")          ; declare repositories
        ("melpa" . "https://melpa.org/packages/")))         ;

(require 'package)                                          ; activate packages
(package-initialize)                                        ; initialize package facility

(setq my-packages
      '(package-1
        package-2
        package-3))

(unless package-archive-contents                            ; unless packages are not available locally, dont refresh package archives
  (package-refresh-contents))                               ; refreshing package contents is time-consuming and should be done on demand

(dolist (pkg my-packages)                                   ;
  (unless (package-installed-p pkg)                         ; iterate over packages and install missing ones
    (package-install pkg)))                                 ;

;; other config below
Runt answered 4/6, 2022 at 4:17 Comment(0)
A
0

if you just want to keep a list of package names in your init.el

and don't mind manully run some commands

example:

(setq package-selected-packages '(
evil
evil-commentary
color-theme-sanityinc-solarized
xclip
))
;; M-x package-refresh-contents
;; M-x package-install-selected-packages
;; M-x package-autoremove

put these in your init.el and

  1. replace package names
(setq package-selected-packages '(
<your-favourite-package-name-1>
<your-favourite-package-name-2>
<your-favourite-package-name-3>
<your-favourite-package-name-4>
))
  1. rum commands
M-x package-refresh-contents
# will sync package information to local
M-x package-install-selected-packages
# will install packages in the list above
M-x package-autoremove
# will remove packages beyond the list above

this method is already suggested by other answers

as a newbie, i spend some time to figure it out, just want to make it even more clear

Apophyge answered 18/8, 2023 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.