如何通过指定软件包名称列表自动安装Emacs软件包?

133

我正在使用package来管理我的Emacs扩展。为了在不同的计算机上同步我的Emacs设置,我想要一种方法在.emacs文件中指定一个包名称列表,然后package可以自动搜索和安装这些包,这样我就不需要手动调用M-x package-list-packages来安装它们。如何实现呢?


6
如果你依靠软件包管理器来安装配置,你可能需要指定确切的版本号(如果无法指定版本号,考虑自己将所有内容存储在版本控制中),否则当库更新并开始冲突时,你就没有保护措施了。 - phils
13个回答

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

8
我更倾向于接受的答案中的代码段:"(or (file-exists-p package-user-dir) (package-refresh-contents))"。这里的包刷新会增加已经安装了这些包的系统的启动时间。不过,该回答的其余部分是完美的。 - rfinz
符号的值作为变量是无效的:package-archive-contents。我是否可以在.emacs中创建一个列表,并使用其中定义的函数安装列表中的所有软件包(如果已安装,则跳过,如果旧版本则更新),就像Vim的Vundle一样。因为我不想将elpa/中的所有软件包都推送到github上,所以每次在“package”中更新软件包时都必须这样做。 - CodyChan
3
你的意思是什么,@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
这和使用emacs 自动安装有什么区别? - Oliver Angelil

51

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


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)

3
那是一张有副作用的地图吗?并且滥用了“或”运算符的懒惰特性?哇,太厉害了。 - Profpatsch
1
“mapc” 是用于产生副作用的。但是为什么不使用 “unless” 呢? - Profpatsch
之前我使用了这段代码,但有时由于某些未知原因它无法正常工作,会显示“包blah-blah无法安装”(这里的blah-blah始终是列表的第一个元素)。如果我手动安装第一个包,一切都能正常工作,但这不是一个解决方案。无论如何,Nicolas Dudebois的答案很好用。 - avp
3
我们实际上在哪里列出要安装的软件包? - Andriy Drozdyuk
这似乎不起作用,告诉我该软件包无法安装... - UpAndAdam
显示剩余4条评论

19

这是我用于 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安装)。每次启动时不会刷新包数据库(这会显著减慢启动速度),只有当存在未安装的包时才会刷新。

根据您的回答,我稍微修改了一下,并删除了“loop”的使用。https://github.com/slipset/emacs/blob/master/ensure-packages.el - slipset
是的,这个示例比它需要的复杂了很多。我在 Prelude 中使用的代码要简单得多。 - Bozhidar Batsov

6

目前还没有人提到 Cask,但它非常适合这个任务。

基本上,你需要创建 ~/.emacs.d/Cask 文件并列出你想要安装的软件包。例如:

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

通过命令行运行cask,您可以安装这些软件包以及它们所需的任何依赖项。

此外,您可以使用cask update自动更新已安装的软件包。


我已经在我的dotfiles中使用cask有一段时间了,效果很好。 - Alastair
很遗憾,Cask似乎需要Python。不知道是否有纯Elisp的替代品?(这是在一个包中;显然,这个页面上的答案符合Elisp要求。) - Peter Jaric
2
Python脚本是cask-cli.el的轻量级包装器,如果你愿意,可以直接调用它:/path/to/emacs -Q --script /path/to/cask/cask-cli.el -- [args] - Alastair
有趣!难道不能从Emacs内部使用它吗?我猜这是因为它也是一个开发工具,但是不得不走出Emacs到CLI来管理Emacs还是有点不寻常的。 - Peter Jaric

4

使用包名称作为符号调用package-install。您可以通过交互式调用package-install并在名称上完成来查找您的包的包名称。函数package-installed-p将让您知道它是否已安装。

例如:

(mapc
 (lambda (package)
   (or (package-installed-p package)
       (package-install package)))
 '(package1 package2 package3))

1
谢谢,但我遇到了一个错误 error: Package dired+' is not available for installation`。dired+ 是我尝试使用你的代码时出现的一个包。 - RNA
当您运行package-list-packages时,是否会显示dired+?我相信您需要将marmalade或melpa添加到您的package-archives中。如果是这样,您可以运行(package-install 'dired+)吗? - ataylor
在这种情况下,(package-installed-p 'dired+) 应该返回 t,并且它将在上面的代码中被跳过。 - ataylor
package-installed-p 单独使用没有问题,但整个代码块不起作用。我尝试了几个包。 - RNA
2
看起来Nicolas Dudebout的回答中的序言会解决这个问题。 - ataylor
显示剩余2条评论

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
这里是我的,它更短 :)
(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))

1
我遇到了一个问题,就是在将 (package-install 'org) 添加到 .emacs 后,没有任何反应。我想安装最新版本的 org-mode,因为内置的 org-mode 版本相当旧。
我找到了 Emacs 25.3.1 的 package-install 源代码。函数自身已经检查了包是否已安装,如果已安装,则拒绝安装。因此,答案 10093312 中的检查 (unless (package-installed-p package) ...) 实际上是不必要的。
(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-desc对象来指定package-install的版本信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接