《elisp 每日一练》——包管理入门

《elisp 每日一练》——包管理入门

Masimaro
2026-11-04 / 0 评论 / 1 阅读 / 正在检测是否收录...

在前面一系列文章中,我们慢慢深入了elisp 编程的各个方面,相信各位读者现在应该有能力编写一些自己的脚本。对自己的工作流进行一些本地化的适配。但是在配置Emacs的过程中我们需要依赖大量的第三方包(当然不排除某些牛人用原生Emacs功能也能高效的工作)。所以我们接下来就是要面对安装第三方包的内容

背景知识

Emacs 内置了包管理系统 package.el, 我们在上一节了解到,package.el 包管理系统在启动时会扫描 ~/.emacs.d/elpa 目录,帮助我们配置并加速加载其中的插件。但是实际上它帮助我们实现包的下载、安装、更新等等一系列工作。我们先来看看它是如何帮助我们下载包的

一般来讲,包是从互联网中专门负责管理包的服务器上下载下来的。在Emacs中有官方的 elpa 和非官方的 melpa。

ELPA 通常特指 GNU ELPA (Emacs Lisp Package Archive),是 Emacs 的官方默认仓库, 它主要提供稳定的发布版本,更新相对稳健。

MELPA (Milkypostman’s Emacs Lisp Package Archive), 由社区驱动, 它通常直接从上游源码(如 GitHub)自动构建。相对活跃,但是对包的稳健性要求不那么高。我们可以获取最新的开发版,但是这通常意味着一些bug。

通常还有一个仓库 —— NonGNU ELPA,相对于 GNU ELPA 来说,它里面的包法律要求相对宽松。一般我们都会设置这三个仓库。

由于众所周知的原因,连接内置的仓库地址不那么稳定。一般的选择是连接国内的镜像站。我一般喜欢选择清华的镜像。

我们可以在 清华elpa镜像帮助页 看到配置的代码

(setq package-archives '(("gnu"    . "https://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
                         ("nongnu" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/nongnu/")
                         ("melpa"  . "https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")))
(package-initialize) ;; You might already have this line

至于为什么后面要使用 package-initialize 函数,我们会在后面讲解。

在这里请各位读者暂时停下来思考一下,这段代码是应该放到 early-init.el 还是放在 init.el 中呢?

根据官方文档的说明, package-archives 只影响新软件包的安装来源,不影响Emacs如何加载已经安装好的包。我们可以直接放在 init.el 中。early-init.el 设计的初衷是我们可以干预启动时包的加载过程,对于安装过程,我们无需干预。

在配置完了package仓库之后,我们可以通过 package-list-packages 在交互式界面上查看仓库中存在的包。在该界面中可以使用下面的一些快捷键:

  • i:标记(Install)某个选中的包准备安装。
  • d:标记(Delete)某个包准备卸载。
  • U:标记所有有新版本的包准备升级。
  • x:执行(Execute)所有挂起的标记操作(真正开始下载、编译或删除)。
  • r:刷新本地的包索引缓存(Refresh)。

虽然有界面可以方便的进行操作,但是作为自己的配置来讲,还是希望能将安装的包固化下来,在新机器上能自动安装,否则安装的包一多,需要一个个的根据报错或者查看原来的配置代码来确定自己安装了哪些包,实在是一件费时的事。

我们可以使用 package-install 命令来安装一个包。它接收一个包名的符号作为参数。

如果想要更新已安装的包需要事先手动执行 package-refresh-contents,因为Emacs并不会主动同步服务器信息。Emacs更新包的时候是根据本地保存的版本信息来判断是否需要更新的。有时候Emacs存储的包信息落后于服务器,导致无法成功更新, 所以需要调用该函数重新获取服务器中保存的包的最新版本等信息。然后调用 package-upgrade 或者 package-upgrade-all。

一个简单的想法就此产生了,我们可以在配置文件中写下类似的代码:

(package-refresh-contents)
(package-upgrade-all)

确保每次Emacs启动时都自动更新所有包。但是刷新缓存和下载更新包是一个很缓慢的过程,在一定程度上会拖累我们的启动速度。而且相对尝鲜,稳定可能更重要。个中取舍由读者自行判断了。

至于删除已经安装的包,我们可以使用 package-delete函数。

另外随着我们使用Emacs的时间不断变长,肯定会出现某些包我们不再需要了。要么是出现更好的替代,要么包的质量有问题,又或者其他原因。总之有些包我们不再需要了,想要Emacs自动帮我们删除它。这个时候可以使用 package-autoremove。正常情况下我们通过 package-install 安装的包会被记录在变量 package-selected-packages。这个函数找出不在 package-selected-packages 中, 也不被其他包依赖的包,进行删除。所以要正常使用这个函数,需要手动的维护 package-selected-packages 变量。

最后, 判断一个包是否安装,我们可以使用 package-installed-p,如果安装了,它会返回一个非nil值。否则返回nil

练习

我们利用今天所学,实现一个函数 my-require 它具有这样一个功能:会自动下载它所引用的包,如果已经安装,它会自动更新。

(defun my-require(package &optional filename noerror)
  "自动安装或者更新所依赖的包"
  (unless (package-installed-p package)
    (package-refresh-contents t)
    (package-install package))
  (if (package-installed-p package)
      (progn
        (package-refresh-contents t)
        (package-upgrade package)))
  (require package filename noerror))

总结

今天我们初步介绍了Emacs中使用package 相关函数进行包管理的一些操作,它们主要是:

  • package-install
  • package-refresh-contents
  • package-upgrade
  • package-delete
  • package-autoremove
  • package-installed-p
0

评论 (0)

取消