推荐使用 straight.el 管理 package(面向初学者)

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),具体的:

  1. require-package 只能确定安装了哪些包,却不能确定包的版本(注:Emacs 包 version 不支持定义 upper bound)

    • 同一个配置 1 年前没问题,1 年后再次部署后有问题。

    • 直接拿 pullcell 的配置,pullcell 没问题,但你有问题。

  2. 安装新包容易出现问题

    • 忘记 M-x package-refresh-contents 导致找不到包

    • 安装新包,导致老包更新,导致进一步 breaking change。

  3. 修改 elpa 包麻烦

    有时,我们希望修改包的源码。被 package.el 安装的包存在于 ~/.emacs.d/elpa,比如:treemacs-20240518.932。该目录下同时包含了 .el 和 .elc 文件。一种比较脏的方法是把这些包移至另一个目录(如:site-lisp)并删除 .elc 文件。这样做会导致 autoloads 失效,由于它们将不再被识别为已安装。

    另外,像 treemacs-20240518.932 这样的名字,完全看不出包的 git 版本,进一步对修复和汇报 BUG 造成困难。

  4. 备份麻烦

    为了实现可复现性,你可能不得不备份整个 ~/.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

  1. 安装最新版本的包

    (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 代码。

  2. 安装一个特定版本的包

    当你发现了一个包的 BUG 并自己修复了它,包的作者还没那么快接受你的 PR。

    这种情况,可以安装一个自己的 branch。

    (straight-use-package '(rg :type git :host github :repo "chansey97/rg.el" :branch "unicode-on-windows"))
    
  3. 安装一个本地包

    当你创建了自己的包或对某个包进行了较大改动,但不想为它单独建一个 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 会下载对应版本的包并编译,从而保持与之前完全一致的环境。

5 个赞

点赞。

straight 用起来要复杂一些,但是支持 lockfile,这一个 feature 就足够我继续选择 straight 了。

虽然可重复环境的方法有很多,但是 lockfile 是最常见的的做法。cargo nix-flake poetry uv 太多的包管理器都选择使用 lockfile 了。

1 个赞

依赖git吗

依赖。除了本地路径的包,所有的包都是通过 git 下载。

1 个赞