Emacs 有自己的包管理系统 package.el。使用 package.el 类似于使用 VSCode 的 Extension。用户通过 M-x package-list-packages
弹出一个 UI 列表,选择想要安装的 package。
通过一定的配置,package.el 也可以程序化安装包,比如 purcell 著名的 require-package
函数:
(require-package 'treemacs) ; 从 MELPA 安装 treemacs
(require-package '...)
这种程序化安装比 UI 模式好不少。每当需要重新部署 Emacs 时,会自动安装之前的包,不必手动一个一个通过 UI 来安装。
看起来一切都很好。
为什么需要替代 package.el?
package.el 的问题是缺乏可复现性(reproducibility),具体的:
-
require-package
只能确定安装了哪些包,却不能确定包的版本(注:Emacs 包 version 不支持定义 upper bound)-
同一个配置 1 年前没问题,1 年后再次部署后有问题。
-
直接拿 pullcell 的配置,pullcell 没问题,但你有问题。
-
-
安装新包容易出现问题
-
忘记
M-x package-refresh-contents
导致找不到包 -
安装新包,导致老包更新,导致进一步 breaking change。
-
-
修改 elpa 包麻烦
有时,我们希望修改包的源码。被 package.el 安装的包存在于
~/.emacs.d/elpa
,比如:treemacs-20240518.932。该目录下同时包含了 .el 和 .elc 文件。一种比较脏的方法是把这些包移至另一个目录(如:site-lisp)并删除 .elc 文件。这样做会导致 autoloads 失效,由于它们将不再被识别为已安装。另外,像 treemacs-20240518.932 这样的名字,完全看不出包的 git 版本,进一步对修复和汇报 BUG 造成困难。
-
备份麻烦
为了实现可复现性,你可能不得不备份整个
~/.emacs.d
目录。-
文件臃肿。
-
升级 Emacs 后 .elc 可能不再兼容。
-
straight.el 能够解决上面所有问题
初始设置
early-init.el
(setq package-enable-at-startup nil)
init.el
(setq straight-vc-git-default-clone-depth 1)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
(or (bound-and-true-p straight-base-dir)
user-emacs-directory)))
(bootstrap-version 7))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
是的,你不需要手动下载 straight.el 到 ~.emacs.d
目录。
类似 require-package,straight.el 提供 straight-use-package
-
安装最新版本的包
(straight-use-package 'treemacs)
straight.el 会下载 treemacs 源码到
~/.emacs.d/straight/repos/treemacs
目录;编译后的 .elc 文件存放在~/.emacs.d/straight/build/treemacs
目录,从而实现源码和产物分离。你可能好奇 straight.el 从哪里下载包?
从包的来源上 package.el 和 straight.el 并没有太大区别。前者是从 MELPA 下载 tar(但 MELPA 也是从包的 recipe 上找到 GitHub 地址);后者则是从 MELPA 获取 recipe 找到对应 GitHub 地址,然后直接 checkout 代码。
-
安装一个特定版本的包
当你发现了一个包的 BUG 并自己修复了它,包的作者还没那么快接受你的 PR。
这种情况,可以安装一个自己的 branch。
(straight-use-package '(rg :type git :host github :repo "chansey97/rg.el" :branch "unicode-on-windows"))
-
安装一个本地包
当你创建了自己的包或对某个包进行了较大改动,但不想为它单独建一个 repository。
(straight-use-package '(company :type nil :local-repo "local-company"))
将你的 company 源文件复制到
~/.emacs.d/straight/repos/local-company
目录。
注:通过 straight.el 安装的包,均能享受 autoloads 的 lazy 加载功能。同时你可以注释相应的 straight-use-package
语句来禁用包(这一点要比 pullcell 的 require-package
高明,require-package
只负责安装包,但注释 require-package
并不能真正禁用包)。
实现可复现性
当配置稳定后,执行 M-x straight-freeze-versions
冻结所有包版本。包的版本信息(git SHA-1)被保存在 ~/.emacs.d/straight/versions/default.el
文件中。
如果你使用 GitHub 来维护 Emacs 配置,需要提交以下文件:
-
~/.emacs.d/straight/repos/
下的所有本地包(default.el 不会跟踪本地包) -
~/.emacs.d/straight/versions/default.el
这样,每次重新部署 Emacs 时,straight 会下载对应版本的包并编译,从而保持与之前完全一致的环境。