你好,这个生成大的 autoloads.el 的功能在 borg 里已经实现了吗?我自己用的时候观察到 borg-activate 函数中,每个 clone 的 autoloads/loaddefs 仍然是分开被加载的,而您的 fork 里应该也是这么做的,所以想请教一下
应该没有,订阅了borg的邮件的,没看到相关的更新。
哈哈,那我去尝试自己实现一下
您引用的帖子我曾经反复阅读过,我自己的配置代码就是用和这个帖子里类似方法生成一个大的 loaddefs.el,来一次加载完所有的 autoload 的。
问题在于,Borg 在 build 每个 clone 时,是使用 borg-update-autoloads 这个函数来生成 autoload 的,每个 clone 的 autoload 均生成一个单独的 xxx-autoloads.el 文件,这是写死在代码中的。每次配置加载时,borg 的做法就是使用 borg-activate 函数一个一个地加载各个 clone,每个 clone 的 xxx-autoloads.el 文件也是分开加载的。
所以,如果想要生成一个单独的 loaddefs.el,我有如下两个方向可走:
- 用对待我自己配置代码的方法来对待本应由 Borg 管理的诸 clone,一次性生成所有 Lisp 代码的 autoload 和 load-path。这么做的话,在我的配置中就完全不需要调用 borg-initialize 函数了。
- 从源码上修改 Borg,让 Borg 本身就支持把所有 autoload 生成进一个文件。这么做可能会比上面的方法更麻烦一些,但是可能能够帮助到更多人。
所以我想尝试的是第二个方向,来把 Borg 这个神奇的包管理方式做得更好嘛
我记得 doom就是生成一个大的autoload 文件。
我现在的做法是:对比.gitmodules这个文件的时间戳(因包管理操作涉及到修改这个文件),将其作为大的 autoload 文件名一部分,当时间较新时,调用一次 borg-initialize,紧接着生成最新的 single autoload 文件,以后就可以直接 load 这个文件。大概逻辑这样:
(if (file-exists-p current-single-autoload-file)
(load (string-remove-suffix ".el" current-single-autoload-file))
(borg-initialize)
(lld-collect-autoloads current-single-autoload-file)
(with-no-warnings
(byte-compile-file current-single-autoload-file)))
其中 lld-collect-autoloads
大多是从 doom 里面抄的:
(defun lld-collect-autoloads (file)
"insert all enabled drone's autoloads file to a single file."
(make-directory (file-name-directory file) 'parents)
;; cleanup obsolete autoloads file
(dolist (f (directory-files single-autoload-path t "autoload-[0-9]+-[0-9]+\\.elc?\\'"))
(unless (string= file f)
(delete-file f)))
(message "Generating single big autoload file.")
(condition-case-unless-debug e
(with-temp-file file
(setq-local coding-system-for-write 'utf-8)
(let ((standard-output (current-buffer))
(print-quoted t)
(print-level nil)
(print-length nil)
(home (expand-file-name "~"))
path-list
theme-path-list
drones-path
auto)
(insert ";; -*- lexical-binding: t; coding: utf-8; no-native-compile: t -*-\n"
";; This file is generated from enabled drones.\n")
;; replace absolute path to ~
(dolist (p load-path)
;; collect all drone's load-path
(when (string-prefix-p (expand-file-name user-emacs-directory) (expand-file-name p))
(push p drones-path))
(if (string-prefix-p home p)
(push (concat "~" (string-remove-prefix home p)) path-list)
(push p path-list)))
(dolist (p custom-theme-load-path)
(if (and (stringp p)
(string-prefix-p home p))
(push (concat "~" (string-remove-prefix home p)) theme-path-list)
(push p theme-path-list)))
(prin1 `(set `load-path ',(nreverse path-list)))
(insert "\n")
(print `(set `custom-theme-load-path ',(nreverse theme-path-list)))
(insert "\n")
;; insert all drone's autoloads.el to this file
(dolist (p drones-path)
(when (file-exists-p p)
(setq auto (car (directory-files p t ".*-autoloads.el\\'")))
(when (and auto
(file-exists-p auto))
(insert-file-contents auto))))
;; remove all #$ load code
(goto-char (point-min))
(while (re-search-forward "\(add-to-list 'load-path.*#$.*\n" nil t)
(replace-match ""))
;; write local variables region
(goto-char (point-max))
(insert "\n\"
"\n;; Local Variables:"
"\n;; version-control: never"
"\n;; no-update-autoloads: t"
"\n;; End:"
))
t)
(error (delete-file file)
(signal 'collect-autoload-error (list file e)))))
是否可以把这个想法直接加进 borg-initialize 里面,就可以直接 load single autoload-xxx.el 文件,而不用每次调用 borg-initialize 去执行一堆 git 命令了。
减少了 0.02s(4%) 的启动时间,感谢。 顺便捉个虫,这里多了个 \
我觉得完全可以啊,这样就方便多了。我之前还没想到可以直接把所有生成好的 autoload 文件合并呢。
我之前的做法大致是这样的(模仿 manateelazycat 的做法):使用 zy/-genload-for
函数来为一个目录生成所有 loaddefs。此函数会为该目录下的所有 .el
文件生成 autoload;如果该目录下存在后缀在 zy/useful-exts
中的文件,则将它加入到 load-path
;对其所有子目录递归地进行这些操作。
(defvar zy/loaddefs-file (expand-file-name "loaddefs.el" user-emacs-directory)
"File that contains all loaddefs of ZyEmacs.")
(defvar zy/useful-exts '("el" "so" "dll")
"ZyEmacs considers files with these extensions \"useful\".")
(defun zy/-genload-for (dir &optional nogen)
"Recursively populate `load-path' and loaddefs for DIR.
A \"useful\" subdirectory is a subdirectory containing any file
that ends with one of the extensions in `zy/useful-exts'. This
function searches recursively inside DIR, add any useful
subdirectory (including DIR itself) to `load-path', and generate
loaddefs for any Emacs Lisp file inside it.
If NOGEN is non-nil, do not generate loaddefs."
(let* ((dir (file-name-as-directory dir))
(subdirs nil)
(dir-useful-p nil))
(require 'autoload)
;; Filter subdirectories, Emacs Lisp files, and other useful files.
(dolist (file (directory-files dir))
(if (file-directory-p (concat dir file))
;; Collect subdirectories except the version control ones.
(when (not (member file '("." ".." "dist" "node_modules"
"__pycache__" "RCS" "CVS" "rcs" "cvs"
".git" ".github")))
(push file subdirs))
(when (file-regular-p (concat dir file))
(let ((ext (file-name-extension file)))
;; Generate loaddefs for Emacs Lisp files.
(unless nogen
(when (string= ext "el")
(let ((generated-autoload-load-name file))
(update-file-autoloads (concat dir file) t zy/loaddefs-file))))
;; Determine the usefulness of DIR.
(when (member ext zy/useful-exts)
(setq dir-useful-p t))))))
;; Dive into subdirectories.
(dolist (subdir subdirs)
(zy/-genload-for (concat dir subdir) nogen))
;; Populate `load-path'.
(when dir-useful-p
(add-to-list 'load-path dir))
(unless inhibit-message
(message "Genload for \"%s\" complete" dir))))
然后再用一个总的 zy/genload
函数来给所有想要处理的目录进行生成操作。在初始化时,只有当 loaddefs.el
不存在时,才会生成它;如果使用 M-x zy/genload
调用之,则会覆盖生成新的 loaddefs.el
。
(defvar zy/genload-dirs '("lisp" "site-lisp")
"`zy/genload' operates on these directories.")
(defun zy/genload (&optional nogen)
"Populate `load-path' and generate loaddefs.
If NOGEN is non-nil, do not re-generate loaddefs if there is
one."
(interactive)
(let ((regen
;; Regenerate loaddefs only when there is no loaddefs file
;; available, or when NOGEN is nil
(or (not (file-exists-p zy/loaddefs-file))
(not nogen))))
;; Remove the old loaddefs file if necessary
(when (and regen
(file-exists-p zy/loaddefs-file))
(delete-file zy/loaddefs-file)
(make-empty-file zy/loaddefs-file))
;; Genload for each directory in `zy/genload-dirs'.
(dolist (dir zy/genload-dirs)
(zy/-genload-for (expand-file-name dir user-emacs-directory)
(not regen)))
;; Compile the newly generated file
(when regen
(if (native-comp-available-p)
(native-compile zy/loaddefs-file)
(byte-compile-file zy/loaddefs-file)))
;; Load the loaddefs file
(load zy/loaddefs-file nil inhibit-message)))
;; Genload on need at startup
(let ((inhibit-message t))
(zy/genload 'nogen))
这样的方法,用于我自己的少量脚本(lisp)和少量第三方脚本(site-lisp)倒还好,今天我把本应由 Borg 管理的文件夹 lib 放进去一起处理后,光这个递归搜索、添加路径就要花一秒多 不过我是在 WSL1 上用的 Emacs,在真正的 Linux 上应该会更快。
我觉我这样做的问题是,递归搜索对 lib 这样错综复杂的文件夹来说,效率确实太低了。用你展示的方法确实会更好地解决问题,不过最好给 borg-activate 函数给 override 一下,把里面有关 autoload 的部分删去,来避免做重复工作。
我试用 Borg 也就这两天的事情,现在感觉手动管理依赖好麻烦 已经动摇了,想去用 straight + lockfile 的模式了。
其实没必要为了那 0.02s 折腾这个,直接用 Borg 默认的就好了。
配置稳定了以后,也很少需要去折腾包管理器了。
Submodule 管理还是很靠谱,保证滚挂后尽快恢复。 比如,前几天,aggressive-indent-mode的一个更新把我的配置都整挂了,新的 PR 现在都没合并(估计是作者忙)。
用 straight 又会有新的问题😄
Borg 的缺点是安装依赖多的包会比较累人,我是尽量不用那种依赖很多的包。
那里不是那样 ,是这样 ^L,其实我也不明白为什么,直接抄得:
记得是因为每个包的 autoload.el 文件都有这么一段,不想全部删除,所以就在最后插入一段,编译也没报错。
不明白什么意思。生成的单个 autoload.el 文件正常,并且可以编译成 .elc,后面就没再管了。
你说得对,我应该坚持下去
其实之前我也就用的 straight,但我一直没有用明白它,只是知道用 straight-use-package 来安装包罢了。现在使用 Borg,才需要真正地去仔细阅读文档、弄明白底层的机理。在此激励下我又返回去阅读了 straight 的 README,发现 straight 的设计真的是好精妙,而以前居然从来都没有想过好好读一下它的文档。
不过,使用 submodule 的方式确实很赞,除了配置的时候麻烦一点,稳定性几乎是最好的。像懒猫这样的大佬甚至是直接全手动管理各个包,自然有它的道理。如果只有 0.02 秒的提升的话,那我也还是不去折腾这个东西了
我现在就是有个小问题,Borg 该怎么使用不在 Emacsmirror 上的包呢?我想安装 lsp-bridge,但是如果直接提供 clone 名和 URL 的话,可以拷贝下来,但好像不会 build,也不会被当作是一个 Borg clone。所以我目前的做法是把它放进 site-lisp 里,用我前面列出来的函数来手动管理它的 load-path 和 loaddefs。我觉得我这个做法应该多少是有点蠢的吧,但是确实没弄明白正确的做法是什么。
我的意思是,(borg-initialize)
函数中调用了 (borg-activate)
函数,对每个 clone 做处理,其处理为:将各个 clone 的相关路径加入到 load-path
,并且 (load)
它们的 xxx-autoloads.el
或 xxx-loaddefs.el
文件,这些在源代码里都能够看到。也就是说,在执行 (borg-initialize)
函数之后,每个包的 autoloads/loaddefs 已经全部被加载过了。如果再在此基础上去加载一遍多合一的 autoloads/loaddefs 文件的话,可能就画蛇添足、不见得能够提升效率了。
所以我认为,正确的做法应该是修改一下 borg-activate
函数(使用 :override
形式的 advice),把它里面涉及到加载 autoloads/loaddefs 的部分删去,这样就能实现真正的多合一加载了。
Borg 不一定要使用 Emacsmirror的包,我是尽量用包的官方仓库,不用镜像(但是像 cmake-mode 这种的就只能用镜像了,原仓库包含太多不需要的东西),它可以用 github,gitlab ,codeberg 等等任何的托管平台。
以安装 lsp-bridge
为例,这个包在Emacsmirror 根本不存在,你可以用 C-u M-x borg-clone lsp-bridge
然后输入 [email protected]:manateelazycat/lsp-bridge.git
来克隆仓库,然后在你的配置中执行 M-x magit-status
,在 untracked files
下面的 lib/lsp-bridge
按一下 s
按键,就会自动编译 lsp-bridge
了(当然你要提前安装好相应的依赖,不然编译会有错误;如果有错误,安装好依赖后再执行 M-x borg-build lsp-bridge
就可以重新编译了)
你说的 load-path 问题,已经通过以下两句写入到单个 autoloads文件。而各个包中的 xxx-autoloads.el,也已经通过后面的合并在一起,所以不用调用 borg-activate
了。
我这个方法的目的是:安装(或者删除、更新)完包后,emacs第一遍按原来的 borg-initialize
执行一遍,可以生成一个单独的 autoloads.el 文件,并且编译好。后续启动就直接 load,不用每次都 borg-initialize
了。好处是不用每次调用 git 进程。相当于配置不依赖于 git。而我把相对个人的配置放在一起,作为一个 drone。这样包管理和个人配置完全独立,不怕搞挂也避免了 windows 下 submodule慢的问题。因为日常使用包管理属少数情况,慢就少慢几次了。
好的,感觉你的方法非常有价值。我也会有在 Windows 上用 Emacs 的需求,学习了。
你结合这段看一下。如果存在单个 autoload.el 文件,完全没调用 borg-initialize
,更不会涉及 borg-activate
,直接 load 。所以不存在多加载一遍的问题。这是我现在用的文件,240左右个包,上一次升级包是6月了。
不过你的问题提醒了我,既然存在这个autoload文件,可能 borg 也不用提前 require 了,包管理的时候再 require。
Emacs master 已经合并了 feature/package+vc 分支,你这个需求可以不用 Borg 了。
试了一下package-vc,似乎没办法用这个来装package-archives里面没有的包,有点鸡肋。。。