《elisp 每日一练》——深入第三方包管理机制

《elisp 每日一练》——深入第三方包管理机制

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

在前一篇文章中,我们了解了如何利用 package 的系列函数完成第三方包的下载安装、更新、删除、查看的相关功能。我们发现有时候必须调用 package-refresh-contents 刷新本地存储的信息。今天我带大家梳理一下Emacs是如何与 melpa、elpa 这种软件仓库交互的。让大家对此有一个更深的理解

背景知识

首先Emacs要查询远程软件包仓库,它查询的源来自 package-archives 中记录的url地址。所有的软件仓库都是通过一样的逻辑和Emacs进行交互。

我们发现在清华源的官网上提示,在配置好了 package-archives 之后需要加上一句 (package-initialize)。这是为了帮助我们初始化已经安装的包。在早期的Emacs中,init.el 加载的时机早于安装的第三方包的加载时机。因此我们需要加上这一句确保第三方包能正常加载,之后才是通过 require 引用。而27之后启动逻辑变了。变成了先加载 earyl-init.el,然后加载第三方包,之后才是 init.el, 此时再调用 (package-initialize) 已经没有必要了。

我们执行 package-list-packages 的时候,它会访问我们在 package-archives 中设置的远程服务器,获取包信息并在一个名为 *Packages* 的buffer中显示。如果网络不可用,它会访问本地的存储。同时它也会将获取的信息存储到本地。

那么 package-list-packages 函数是否等同于调用了 package-refresh-contents 了。某种程度上确实是这样的,它们都会刷新本地缓存。但是区别在于后者会强制刷新本地缓存。前者没有保证一定会刷新。因此在需要确保刷新的场合最好还是调用 package-refresh-contents。

本地缓存对于我们后面安装更新包十分重要。有时候我们会发现调用 package-list-packages 时显示包有新的版本,但是我们想执行 package-upgrade 时发现它无法更新。

实际上我们每次调用 package-refresh-contents 时都是在下载一个名为 archive-contents 的文件,文件是一个s表达式,以list的形式记录了服务器中记录的包信息。我们可以查看 package-archive-contents 变量,找到它的内容。我随便找了一个我本地的包:

(company-statistics
  #s(package-desc :name company-statistics :version (20250805 1524)
    :summary "Sort candidates using completion history"
    :reqs ((emacs (24 3)) (company (0 8 5))) 
        :kind tar
    :archive #1# :dir nil :extras
    ((:url
        . "https://github.com/company-mode/company-statistics")
    (:commit
        . "120e982f47e01945c044e0762ba376741c41b76c")
    (:revdesc . "120e982f47e0")
    (:keywords "abbrev" "convenience" "matching")
    (:authors ("Ingo Lohmar" . "[email protected]"))
    (:maintainers
        ("Ingo Lohmar" . "[email protected]"))
    (:maintainer "Ingo Lohmar" . "[email protected]"))
        :signed nil)

从这上面我们可以看出,它记录了包的版本、描述、url地址、依赖等等信息。后面Emacs要或者更新对应包都是读取本地信息然后去做的。

当我们要执行安装动作时,它大概会取这些数据:

  1. 根据 :archive 中记录的url地址下载对应的包
  2. 根据 :kind 得知这是一个 tar 包
  3. 将 tar 包下载并解压到 package-user-dir 记录的目录中,这个变量默认是 ~/.emacs.d/elpa ,以 包名-版本号 命名
  4. 在真正加载包之前,会获取包信息中 :reqs 中记录的依赖
  5. 按照上述过程安装依赖
  6. 当所有依赖都安装了之后,会执行字节编译和生成 autoloads 文件, 并且将该目录所在路径加入到 load-path 中
  7. 在安装成功之后,本次安装的所有包, 包括依赖包,信息都被记录在 package-desc 中

这里又出现了一个新的变量 package-desc,它与我们前面提到的 package-selected-packages 有什么区别呢?一个区别是前者信息比较详细,后者只保存了安装的包名。最重要的区别是前者保存所有安装的包,包括根据依赖被动被安装的包。而后者只记录用户明确要求安装的包

后续如果需要更新,Emacs会根据 package-desc 中记录的本地包的信息对比记录了远程服务器中各种包信息的 package-archive-contes,判断服务器上的版本是否比当前版本新。如果需要更新则按上面的流程进行更新。

总结

本篇我们详细讨论了Emacs在更新、安装包背后的一些动作。现在我们能知道 package-refresh-contents 背后实际在维护服务器上存储的包信息。所以每次安装和更新前都可以使用该命令进行一次信息的同步

0

评论 (0)

取消