关于魔法注释autoload的疑问

最近写配置用到了autoload,之前一直不是特别理解,今天查了一些资料还是没太搞清楚,赶紧来社区问一下大佬们。

最早看子龙山人的视频里说autoload需要加对应的配置语句类似(autoload functionA fileA),这样启动时可以不加载fileA,而调用functionA时才去加载fileA,并且也不需要去显式require这个fileA。为了避免写太多类似配置语句,可以使用魔法注释;;;###autoload直接放到对应配置文件的函数或变量前,emacs在init时会自动去load-path里收集这些autoload的信息。

我的疑问是:

1、我自己定义的配置文件给某些函数加了魔法注释,该文件也在load-path里,但为什么启动emacs后M-x找不到这个函数;

2、使用use-package加载这个文件,配置了:defer t,效果同上,难道是我缺少了哪个步骤吗,还是我对autoload的理解就不对?

我们已经知道 autoload 函数可以创建 autoload 对象,但为了让autoload能让开发者和 用户用起来更贴心,下列两个问题急需解决。

  1. 手动维护autoload定义十分麻烦,增删函数时容易忘记同时更新autoload定义。
  2. 缺乏机制来保证autoload定义文件在包加载前就被加载到用户的Emacs实例内。

对于问题一,Emacs引入了magic comment(魔法注释) ;;;###autoload 如下示

;;;###autoload
(defmacro cl-incf (place &optional x)
  "Increment PLACE by X (1 by default).
PLACE may be a symbol, or any generalized variable allowed by `setf'.
The return value is the incremented value of PLACE."
  (declare (debug (place &optional form)))
  (if (symbolp place)
      (list 'setq place (if x (list '+ place x) (list '1+ place)))
    (list 'cl-callf '+ place (or x 1))))

一个注释本身并不能掀起什么波澜。但当与配套的工具使用时,这将大大简化更新autoload 定义的流程。对此,Emacs 提供了 autoload.el。 它负责从源代码的magic comment中生 成真正的autoload定义。

到现在,我们可以理清楚Emacs的Autoload机制是如何和包管理器(package.el)结合服务 于用户了:

  • package.el 使用 update-directory-autoloads 从安装的包中提取 autoload文件, 提取出来的文件命名为 <PKGNAME>-autoloads.el

  • package-initialize 搜索所有已经安装的包,设置全局变量 load-path 并加载所 有的 <PKGNAME>-autoloads.el

  • 用户在配置里直接使用被 autoloaded 的函数(比如,用 add-hook 添加函数到钩子 里。实际运行的代码在函数被调用时触发加载。实现懒加载机制。


引用自本人一篇未发布博文

1赞

不好意思,没太看出问题,我是要把配置文件的PATH赋给哪个变量吗?

就是你要用update-directory-autoloads生成autoload定义文件, 然后在配置里load这个文件. 纯写注释没卵用

哦哦我好像明白点了,我删掉了一些之前抄的和package.el相关的配置,里面应该有update-directory-autoloads相关的内容,也就是说需要手动加上。

这个在配置时总报错:

(update-directory-autoloads “~/.emacs.d/lisp”)

Debugger entered–Lisp error: (wrong-type-argument stringp nil)

可后面参数分明是个string:rofl:

M-x调用

写 package 的时候才用 ;;;###autoload,安装包的时候会自动生成 xxx-autoloads.el,里面会把这些注释转换成 (autoload ....) 调用,Emacs 启动时会自动加载所有的 xxx-autoloads.el 文件。

自己配置的时候,直接写 (autoload ....),use-package 的 :commands 就是如此干的。

嗯嗯,自己写的包加了魔法注释,需要手动生成对应的xxx-autoload.el,然后在init.el里require它。

可以参考abo-abo 的回答

我是把自定义的函数都写在一个文件里, 比如 chiron-functions.el, 函数前加 ;;;###autoload 然后执行一下

(update-file-autoloads "chiron-functions.el" t (expand-file-name "chiron-functions-autoloads.el"))

在配置文件里 (require 'chiron-functions-autoloads)

ok,谢谢~

期待发布。 这两天在研究手动管理 package 的方式,稍微研究了下 autoload/load-file/require/use-package 之间的关系,之前一直在抄配置,没太理解。 贴几个觉得有用的链接:

贴一段在我这里一直可靠工作的代码(不少东西是从 straight.el 抄来的):

;;;; Site-lisp

;; The site-lisp directory is where we put our own packages.  We byte-compile
;; and generate an autoload file for them. We only do this when a package is
;; newer than its byte-compiled version.

;; This is needed, or `generated-autoload-file' will be not defined as a
;; variable at byte-compile time.  See the comments in
;; `straight--generate-package-autoloads'.
(eval-and-compile
  (require 'autoload)
  (require 'bytecomp))

(let* (;; Dir & files
       (site-lisp-dir (concat user-emacs-directory "site-lisp/"))
       (build-dir (progn (make-directory (concat site-lisp-dir ".build/") t)
                         (concat site-lisp-dir ".build/")))
       (build-files (directory-files build-dir))
       (newer-lisp-file nil)
       ;; Don't bother me.
       (inhibit-message t)
       ;; Prevent `update-directory-autoloads' from running hooks when visiting
       ;; the autoload file.
       (find-file-hook nil)
       (write-file-functions nil)
       ;; Prevent `update-directory-autoloads' from creating backup files.
       (backup-inhibited t)
       (version-control 'never)
       (generated-autoload-file (concat build-dir "autoloads.el")))
  (cl-letf (((symbol-function #'byte-compile-log-1) #'ignore)
            ((symbol-function #'byte-compile-log-file) #'ignore)
            ((symbol-function #'byte-compile-log-warning) #'ignore))
    (add-to-list 'load-path build-dir)
    (dolist (file (directory-files site-lisp-dir))
      (unless (string-prefix-p "." file)
        ;; Make symlinks of site-lisp files in build-dir.  This is needed for
        ;; `byte-compile-file' and `update-directory-autoloads'.
        (unless (member file build-files)
          (make-symbolic-link (concat site-lisp-dir file)
                              (concat build-dir file)))
        ;; Byte compile
        (let ((byte-file (concat build-dir
                                 (file-name-sans-extension file)
                                 ".elc")))
          (when (file-newer-than-file-p (concat site-lisp-dir file) byte-file)
            (setq newer-lisp-file t)
            (byte-compile-file (concat build-dir file))))))
    ;; Generate autoload file
    (when newer-lisp-file
      (unless (file-exists-p generated-autoload-file)
        (with-current-buffer (find-file-noselect generated-autoload-file)
          (insert ";; -*- lexical-binding: t -*-\n")
          (save-buffer)))
      (update-directory-autoloads build-dir)
      (byte-compile-file (concat build-dir "autoloads.el")))
    ;; Load autoload file
    (load (concat build-dir "autoloads") 'noerror 'nomessage)))

它会:

  • 自动扫描 .emacs.d/site-lisp/ 下的 .el 文件,然后把它编译到 .emacs.d/site-lisp/.build/ 下面。
  • 收集这些文件里加了 ;;;###autoload 的定义,创建.emacs.d/build/autoloads.el 并且自动加载。
  • .emacs.d/site-lisp/.build/ 加到 load-path,这样就可以用 requireuse-package 了。
  • 按需更新,在 site-lisp 下有任何 .el 文件有改动的情况下才会重做整个流程,平时几乎不耗时。
2赞

OK,我去试一试~