Как автоматически устанавливать пакеты Emacs, указав список имен пакетов?

123

Я использую packageдля управления своими расширениями Emacs. Чтобы синхронизировать мои настройки Emacs на разных компьютерах, я бы хотел указать список имен пакетов в .emacsфайле, а затем packageмог бы автоматически искать и устанавливать пакеты, так что мне не нужно устанавливать их вручную, вызывая M-x package-list-packages. Как это сделать?

РНК
источник
6
Если вы полагаетесь на диспетчер пакетов для установки вашей конфигурации, вы, вероятно, захотите указать точные версии (и если это невозможно, подумайте о том, чтобы сохранить все в системе контроля версий самостоятельно), так как в противном случае вы не защищены, когда библиотеки обновляются и запускаются. к конфликту.
phils

Ответы:

107
; 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)))
Николя Дудебу
источник
7
Я предпочитаю: (или (file-exists-p package-user-dir) (package-refresh-contents)) из принятого ответа. Обновление пакета здесь увеличивает время запуска в системах, в которых уже установлены пакеты. Однако в остальном этот ответ идеален.
rfinz
Значение символа как переменной недействительно: содержимое-архив-пакета. Есть ли способ составить список в .emacs и использовать определенную в нем функцию для установки всех пакетов в списке (пропустить, если установлен, обновить, если старый), например Vundle для Vim. Поскольку я не хочу отправлять все пакеты из elpa / в github, мне приходится делать это каждый раз, когда пакет обновляется в package.
CodyChan 08
Что ты имеешь в виду @rfinz? Похоже package-refresh-contents, запустится, только если пакет не установлен? Как (or (file-exists-p package-user-dir))лучше / как проверить, установлены ли пакеты?
Startec
@Startec да ты прав! Он проверяет, существует ли каталог пакета пользователя, и если нет, он запускается package-refresh-contents. Вероятно, это будет запущено только при первом открытии emacs на новом компьютере, и меня это устраивает. Если пакет требует обновления, это можно сделать вручную.
rfinz
2
Если вы уже используете use-package, вы можете использовать :ensureключевое слово для автоматической установки пакетов. Это также настраивает, package-selected-packagesесли вам нужно получить доступ к списку пакетов через настройку или программно.
Nick McCurdy
45

На основе комментариев Profpatsch и ответов ниже:

(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)
РНК
источник
2
Это… карта с побочными эффектами? А злоупотребление ленью or? Ух ты.
Profpatsch 07
1
Ну, mapcэто по побочным эффектам. Но почему бы не использовать unless?
Profpatsch
Раньше я использовал этот код, и иногда он не работал по неизвестной причине: «Пакет бла-бла недоступен для установки» (здесь бла-бла всегда является первым элементом списка). Если я установлю первый пакет вручную, все будет работать нормально, но это не решение. В любом случае, ответ Николя Дудебуа работает нормально.
avp
Мне нужно было (package-initialize)перед ссылкой наpackage-user-dir
Фрэнк Хенард
3
Итак, где мы на самом деле перечисляем пакеты, которые хотим установить?
Андрей Дроздюк
41

Emacs 25.1+ будет автоматически отслеживать пакеты, установленные пользователем, в настраиваемой package-selected-packagesпеременной. package-installобновит переменную настройки, и вы сможете установить все выбранные пакеты с помощью package-install-selected-packagesфункции.

Еще одно удобное преимущество этого подхода заключается в том, что вы можете использовать его package-autoremoveдля автоматического удаления пакетов, которые не включены package-selected-packages(хотя при этом сохранятся зависимости).

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

Источник: http://endlessparentheses.com/new-in-package-el-in-emacs-25-1-user-selected-packages.html

Ник МакКарди
источник
17

Вот код, который я использую для 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)

Если вы не используете MELPA, вам не нужно его требовать (а если вы это сделаете, melpa.elон должен быть на вашем load-path(или установлен через MELPA). Db пакета не обновляется каждый раз (так как это значительно замедлит запуск) ) - только там, где есть неустановленные пакеты.

Божидар Бацов
источник
Основываясь на вашем ответе, я немного изменил его и удалил использование «цикла» github.com/slipset/emacs/blob/master/ensure-packages.el
slipset
Да, этот пример действительно более сложный, чем должен быть. Код, который я сейчас использую в Prelude, намного проще.
Божидар Бацов
7

Еще никто не упомянул Cask , но он вполне подходит для этой задачи.

По сути, вы создаете ~/.emacs.d/Caskсписок пакетов, которые хотите установить. Например:

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

При запуске caskиз командной строки эти пакеты и все необходимые им зависимости будут установлены.

Также вы можете автоматически обновлять установленные пакеты с помощью cask update.

Аластер
источник
Я уже некоторое время использую cask в своих точечных файлах , отлично работает.
Alastair
Жаль, что Cask, похоже, требует Python. Интересно, есть ли альтернатива только для elisp? (Это в пакете; очевидно, что ответы на этой странице соответствуют требованиям elisp.)
Питер Ярич
1
Скрипт python представляет собой тонкую оболочку вокруг cask-cli.el, которую вы можете вызвать напрямую, если хотите:/path/to/emacs -Q --script /path/to/cask/cask-cli.el -- [args]
Alastair
Интересный! Разве нельзя использовать его изнутри Emacs? Я предполагаю, что это потому, что это также инструмент разработчика, но довольно необычно выходить за пределы Emacs в CLI для управления Emacs.
Peter Jaric 05
4

Вызовите package-installс именем пакета в виде символа. Вы можете найти имена пакетов для своих пакетов, позвонив в package-installинтерактивном режиме и введя имя. Функцияpackage-installed-p сообщит вам, если он уже установлен.

Например:

(mapc
 (lambda (package)
   (or (package-installed-p package)
       (package-install package)))
 '(package1 package2 package3))
ataylor
источник
1
Спасибо, но я получил ошибку error: Package dired + 'недоступен для установки'. dired + - это пакет, который я пробовал использовать с вашим кодом.
RNA
Появляется dired+, когда вы бежите package-list-packages? Я считаю, что вам нужно добавить в свой мармелад или мелпу package-archives. Если да, можешь ли ты бежать (package-install 'dired+)?
ataylor
В этом случае он (package-installed-p 'dired+)должен вернуться, tи он будет пропущен в приведенном выше коде.
ataylor
Один package-installed-pтолько код работает нормально, а весь блок кода - нет. Пробовал несколько пакетов.
RNA
2
Похоже, что прелюдия в ответе Николаса Дудебу решит эту проблему.
ataylor
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)
Дунаевский Максим
источник
3

Мне нравится проверять, хочет ли пользователь сначала установить пакеты, как это сделано в этом ответе . Также я обновляю содержимое своего пакета один раз перед установкой. Я не уверен, что это лучший способ, но я не думаю, что лучшие ответы помогли мне.

(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)))
Франк Хенард
источник
1

Я столкнулся с проблемой , что ничего не произошло после добавления (package-install 'org)в .emacs. Я хотел установить последнюю версию org-modeи встроенныйorg-mode довольно старая.

Я откопал исходный код package-installиз Emacs 25.3.1. Функция self уже проверяет, установлен ли пакет или нет, и отказывается установить его, если пакет уже установлен. Так что проверка (unless (package-installed-p package) ...)из ответа 10093312 фактически не требуется.

(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))))

Встроенный org-modeтакже считается установленным и package-installотказывается устанавливать более новую версию от ELPA. Потратив некоторое время на чтение package.el, я пришел к следующему решению.

(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))

Причина, по которой это работает, заключается в том, что package-*функции семейства обрабатывают аргументы по-разному в зависимости от того, является ли это символ или package-descобъект. Вы можете указать информацию о версии только package-installчерез package-descобъект.

Лэй Чжао
источник
0

Вот мой, он короче :)

(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))
yPhil
источник
0

Вот еще способ.

;; 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)
Йогеш Камат
источник