tpl
2025 年5 月 30 日 00:51
1
最近改动了一下自己的配置文件,发现一个 bug:
(setopt package-enable-at-startup nil)
我的 early-init.el 中的这行代码属于搬起石头砸自己的脚—— setopt
直接加载了 package-enable-at-startup
所属的 package.el 包,导致我 (with-eval-after-load 'package ...)
中修改 package-user-dir
的代码失效。
当初之所以从 setq
改成 setopt
是源于看到了 设定 user option 应该用 setq, setopt, custom-set-default, customize-set-value, 还是 customize-set-variable? - #5,来自 LdBeth 这篇介绍,于是决定把所有 customizable 的变量都改成用 setopt
来设置,没想到破坏了加载顺序。
让我困惑的是,实际查看 package-enable-at-startup
的 help 的时候,发现该变量是 autoload 的,而且也没有 :set
之类的东西,为什么会引起整个 package.el 的加载呢?同理,平时在使用 setopt
改动各种包变量的时候,如何保证不会破坏设计好的加载顺序?
我目前的做法是尽量把 setopt
都尽量放在 :config
中延迟运行,但如上所述,不能覆盖所有情况。难道要把所有非延迟运行的 setopt
都改成 setq
吗?
Update : 将标题从 “关于 setopt 和 setq 的使用问题” 改为更贴切于贴内讨论的 “关于 setopt
和 :custom
的模块加载行为问题”
setopt 会运行 setter 函数,如果该变量的 defcustom 有声明函数的话。否则就和 setopt 差不多。因此你的情况就是当调用了 setopt 的时候,就运行了该变量对应的 setter 函数。
我个人的用法依然是只用 setq,只有当需要使用 setopt 也就是会调用 :set 函数的时候。
tpl
2025 年5 月 30 日 03:14
3
我原本的理解也是 setopt
调用 :set
导致的过早加载,但是 package-enable-at-startup
并没有定义 setter 函数。因此我对 setopt
到底什么情况下会加载整个 module 的行为有点不确定了。
FYI: 删掉注释之后,package.el 中的代码如下:
;;;###autoload
(defcustom package-enable-at-startup t
:type 'boolean
:version "24.1")
检查了一下 setopt 宏展开的结果以及调用链,发现 setopt 会调用 custom-load-symbol 这个函数,而这个函数会 require 需要 autoload 定义所在的包。
由此可见,setopt 的正确使用姿势应该在 config 或者 with-eval-after-load 的代码块里使用。其实用 use-package 的话,如果需要用到 setter,感觉可能用 :custom 应该是更好的选择。
3 个赞
tpl
2025 年5 月 30 日 15:57
5
根据您的提示,我刚抽空研究了一下:
setopt
宏展开后调用 setopt--set
,后者调用 custom-load-symbol
custom-load-symbol
会加载变量的 custom-loads
symbol property 中的所有包
我对于 custom-loads
不太了解,而且在我上面贴的 package-enable-at-startup
的定义中没有看到和这个相关的 keyword。因此,我抽样了几个配置文件中的 customizable 变量想实验一下,发现基本都是 (get SYMBOL 'custom-loads) => nil
的,包括其它的一些在 package.el 中定义的 customizable 变量。
在整个 emacs-mirror 的代码库中搜索 package-enable-at-startup
也没有看到有 put
的行为。
于是我猜测是不是只有 autoload 的变量才会有 custom-loads
property,经实验:
自己随手写的 test module 中的 autoload 的 customizable 变量没有 custom-loads
property
package.el 中其它的 autoload 的 customizable 变量,如 package-user-dir
是有 custom-loads
property 的
关于这个区别,我又发现 Emacs 会自动生成一个 cus-load.el 文件,里面有大量诸如
...
(custom--add-custom-loads 'package '(package package-vc package-x))
...
的调用,然后 custom--add-custom-loads
也确实有 put
的行为:
(defun custom--add-custom-loads (symbol loads)
;; Don't overwrite existing `custom-loads'.
(dolist (load (get symbol 'custom-loads))
(unless (memq load loads)
(push load loads)))
(put symbol 'custom-loads loads))
从代码层面能调查到的就这些了,我对 Emacs 本身的了解不足以有更多的宏观上的 insights 了(比如整个 customization 系统的依赖机制等)。
tpl
2025 年5 月 30 日 16:09
6
另外关于 use-package
的 :custom
关键字,我之前的理解一直是把它视作 :init (setopt ...)
+ theme 的 toggle 机制,而且我也在多个场合看到有人提到这个是 setopt
诞生之前的“时代产物”。
趁此机会我也看了一下 :custom
所依赖的 custom-theme-set-variable
函数,其中也调用了 custom-load-symbol
(虽然我不是百分百确定,因为我不太了解 standard-value
和 custom-autoload
这两个 symbol property):
...
(dolist (entry args)
(let* ((symbol (indirect-variable (nth 0 entry))))
(unless (or (get symbol 'standard-value)
(memq (get symbol 'custom-autoload) '(nil noset)))
;; This symbol needs to be autoloaded, even just for a `set'.
(custom-load-symbol symbol))))
...
因此我觉得我的模糊理解还算恰当,:custom
相对于 setopt
并没有除了可以随 theme 开关以外的好处,并且还有着固定在包加载前设置变量的弊端(也可能不算弊端?但是从目前我们的讨论看来这个有同样的破坏加载顺序的问题)。
:custom
相对于 setopt
并没有除了可以随 theme 开关以外的好处,并且还有着固定在包加载前设置变量的弊端(也可能不算弊端?但是从目前我们的讨论看来这个有同样的破坏加载顺序的问题)。
检查了一下 use-package 的 :custom 的宏展开,你的理解是正确的。我之前以为 :custom 关键字后面的变量是会默认懒加载的 (展开后放入 eval-after-load 的 block 里面)。但是看起来并不是这样。