推荐使用 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 会下载对应版本的包并编译,从而保持与之前完全一致的环境。

8 个赞

点赞。

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

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

1 个赞

依赖git吗

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

1 个赞

看来在Windows上的速度不会快了……

额 不就是个 git clone git checkout 的事情。Windows 的 git 再慢,不至于 clone 和 checkout 慢吧。

另外 straight 是同步的,装的包多 (emacs 用户装的包算上附带的依赖大几十个是正常的吧),每次更新包,就算你是 unix,git 再快也没用,一样得等。

自带的 package.el 不用 git,但是它也是同步的啊,装的包一多照样也是等。

如果想要装包快速,就去用 elpacca,虽然是基于 git 的,但是它是纯异步的,并行装包,所以装多少包都不会卡。不过 elpacca 最近才刚刚加入 lockfile,我准备再等等,再看看 lockfile 的实现完善程度再说。

lockfile 是我考虑包管理器最重要的要素。

On Microsoft Windows, find(1) is generally not available, so the default value of straight-check-for-modifications is instead (check-on-save) .

看了下官方文档,另一个地方也慢

这个的意思是因为考虑到 find 在 windows 很慢,所以把默认值换了,从而避免了调用 find,从而影响启动速度的意思。因此这个其实没有影响。

而且这个选项在 unix 上影响也是很大的。我为了启动速度已经把 check-modification 完全关掉了。关掉后我的启动速度 0.25 秒,开启后 0.8s。

1 个赞

我不怎么关心 Emacs 的启动速度,除了调试 Emacs,每天只启动 Emacs 一次。

另外,首次部署的话,设置 (setq straight-vc-git-default-clone-depth 1),下载代码也不会太慢。

在 Windows 上想体验好的话,就不要使用基于 Git 的包管理器。使用 Windows 11 最新的 Dev Drive 对 Git 的性能会有一点点提升。

1 个赞

用use-package和package.el基本能解决楼主说的问题。

  1. use-package可以pin melpa-stable。
  2. 用package-vc可以固定commit
  3. use-package中用:load-path可以方便固定到其他目录,调试包很方便。
  4. 用mirror没必要备份,如果需要升级后byte-compile下elpa目录即可。见 byte-recompile-directory
  5. 如果嫌弃安装慢,可以用 async方式。网上有很多办法,async compile 包甚至nativ-compile都是可以的。

BTW:用git方式不太方便,备份量更大,尤其在不能翻墙的情况下,安装极其不便。个人感受,仅供参考。

6 个赞

用package-vc可以固定commit

package-vc 确实能固定 commit,但似乎不支持本地包安装?之前用过一段时间 package-vc,没有达到我的需求。

所谓本地包安装,即:你有一个包的源码(没有被任何 vc 维护),copy 到某个目录下,执行某个 “本地安装” 命令,它不但被加到 load-path,还能生成 -autoloads.el。

use-package中用:load-path可以方便固定到其他目录,调试包很方便。

在 straight.el 里,本地包是真正被安装的,也就是会生成 -autoloads.el 文件。

我不用 use-package,不确定 use-package 的 :load-path 是否支持。

用mirror没必要备份,如果需要升级后byte-compile下elpa目录即可。见 byte-recompile-directory

不确定你说的 mirror 啥意思?

老方案之所以要备份,是为了再次部署 Emacs 时,能安装和之前一模一样的包,否则 package.el 会下载最新的包。这导致你必须备份所有包的源码(甚至 .elc)。

用git方式不太方便,备份量更大

用 striaght.el 不需要备份(除了本地包),defaut.el 里记录了 git SHA-1。重新部署 Emacs 时,会自动 checkout 对应 commit。

1 个赞

package-install-filepackage-vc-install-from-checkout?

(defun package-install-file (file)
"Install a package from FILE.
The file can either be a tar file, an Emacs Lisp file, or a
directory."
")

这里的问题在于:当 file 是一个 directory 时,package-install-file 是把该 directory 作为安装目录,还是把这个 directory 统一地 “安装” 到一个地方。

如果是后者,可能会对维护带来不便(如果没记错的话,package.el 应该是后者):

你的源码是在 file 里,但 xref-find-definitions 可能会跳到那个统一的 “安装” 目录里去了。

P.S. straight.el 对此是专门做了处理的 Symbolic links

是这样的。

不过 package-vc-install-from-checkout 倒是做了软链接,但是要求目录本身在版本管理之下,大多数时候应该是够用了:

  • 开发包的时候一般会用到版本管理
  • 仅下载包安装一般可能用不上软链接

当然能提供软链接选项 staright.el 肯定是好的。

1 个赞

我目前用坚果云同步.emacs.d,elpa文件夹只有25M

package-vc 没有 lockfile,需要自己手动维护包的版本。straight 不需要手动维护包的版本。

备份 elpa mirror 这个我我觉得也没有 lockfile 好用啊。lockfile 每次都只储存 git commit hash 你可以很方便的用 git version control。elpa mirror 用 git version control 的话每次的变化量就太大了。

换句话说,cargo uv 等包管理器都使用的都是 lockfile。没见过要自己去备份 crates mirror, pip mirror 啥的。

你误解了,我并没有说备份mirror。有了固定的版本,或者release/tag,用镜像源就能满足快速固定安装。不过,用质量高的package,升级基本也还好。两者都深入使用过,对我而言用straight带来的烦恼比收益大罢了。每个人有自己的选择和判断。

嗯嗯,straight 也有自己的毛病, package.el 也很好用。我是想说 straight 在当下依然有其独特的特点不可替代,并不是 package-vc / package.el 可以替代的。各有利弊,这点就要看个人的取舍了。

package-vc 我用下来其实也不是想像的那样好用,有些包会安装不了。比如安装 nerd-icons-corfu 会出错,需要先安装 nerd-icons。