在batch模式下载入Evil包报错

环境为macOS 10.15.2; GNU Emacs 26.3。以下是完整的~/.emacs.d/init.el文件内容:

(setq package-archives
      '(("gnu" . "http://elpa.emacs-china.org/gnu/")
        ("melpa" . "http://elpa.emacs-china.org/melpa/")
        ("org" . "http://elpa.emacs-china.org/org/")))
(package-initialize)

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(eval-when-compile (require 'use-package))
(setq use-package-always-ensure t)

(use-package evil
  :config (evil-mode))

通过该配置文件可以正常开启图形界面的Emacs并正确启用Evil模式。但是,如果是在终端下运行emacs --batch --load="~/.emacs.d/init.el"就会报以下的错误:

Wrong type argument: number-or-marker-p, nil

有没有知道原因的,还有,这种情况应该怎么调试?谢过!

意思:参数类型不匹配,预期一个数字,却收到 nil

打开 Lisp Debugger,查看出错时的 Backtrace(就是导致该错误的一系列函数调用),如:

~ $ emacs -Q --batch -f toggle-debug-on-error --eval '(+ 1 (car 2))'
Debug on Error enabled globally
Debugger entered--Lisp error: (wrong-type-argument listp 2)
  car(2)
  (+ 1 (car 2))
  eval((+ 1 (car 2)))
  command-line-1(("-f" "toggle-debug-on-error" "--eval" "(+ 1 (car 2))"))
  command-line()
  normal-top-level()

Backtrace 从上往下一行一行看

Debugger entered--Lisp error: (wrong-type-argument listp 2)

报错,参数类型不匹配,要求 list,获得 2。这是哪来的呢?

   car(2)

原来代码执行了 (car 2),这又是哪来的呢?

  (+ 1 (car 2))

原来是这里。


用 M-x toggle-debug-on-error 或 (setq debug-on-error t) 开启 Lisp Debugger。

多谢回复!不幸的是如此执行仍然无法得到更多的错误消息,在Evil的Repo上提了Issue #1243,开发者回复说是undo-tree的问题。

可复现该问题的最小化init.el

(require 'package)
(package-initialize)

(unless (package-installed-p 'undo-tree)
 (package-refresh-contents)
 (package-install 'undo-tree))

(require 'undo-tree)
(global-undo-tree-mode)

执行emacs -Q --batch -f toggle-debug-on-error --load="~/.emacs.d/init.el"后提示错误:Wrong type argument: number-or-marker-p, nil

想到最近确实更新了undo-tree(2020-01-09)。

怎么可能?总是得有 Backctrace 吧?

为什么要在 batch 模式下加载 evil 呢?可以只加载那些你用到的包,写一个专门用于 batch 模式的 init.el 很简单的。

解决问题了,是新版undo-tree的bug,邮件联系了开发者,目前该问题已经解决了。

P.S. 我也奇怪,但是使用前文提供的init.el,再运行emacs -Q --batch -f toggle-debug-on-error --load="~/.emacs.d/init.el"确实只产生了如下两行输出:

Debug on Error enabled globally
Wrong type argument: number-or-marker-p, nil

嗯,场景是比较特殊,搜索引擎上也搜索不到相关问题。比较好奇为什么同样的配置文件,batch模式和正常模式下会产生不同的结果。

我也遇到这个问题,undo-tree最近老出问题,正在尝试undo-fu。

没看到 backtrace,说明没有触发 debugger,试试清空 debug-ignored-errors

~ $ emacs -Q --batch -f toggle-debug-on-error --eval '(user-error "hello")'
Debug on Error enabled globally
hello
~ $ emacs -Q --batch -f toggle-debug-on-error --eval '(setq debug-ignored-errors nil)' --eval '(user-error "hello")'
Debug on Error enabled globally
Debugger entered--Lisp error: (user-error "hello")
  signal(user-error ("hello"))
  user-error("hello")
  eval((user-error "hello"))
  command-line-1(("-f" "toggle-debug-on-error" "--eval" "(setq debug-ignored-errors nil)" "--eval" "(user-error \"hello\")"))
  command-line()
  normal-top-level()
2 个赞

源文件编译之后往往会掩盖/造成一些问题。

.elc 文件删掉之后,错误信息就更明确一些了:

Debug on Error enabled globally
Package cl is deprecated
Error: (error (void-function undo-tree-save-history-from-hook))
  (undo-tree-save-history-from-hook)
  (kill-buffer #<buffer  *temp*-191149>)
  ...

先不纠结 undo-tree-save-history-from-hook 为什么这个函数不存在,因为它看起来跟最早的 number-or-marker-p 错误不太相干。所以定义一个空的,让其执行通过看看会发生什么:

(when noninteractive
  (defun undo-tree-save-history-from-hook ()
    (message "==> undo-tree-save-history-from-hook")))

再次运行,可以看到 message 打印出来了,出错信息进一步明确:

Debug on Error enabled globally
Package cl is deprecated
==> undo-tree-save-history-from-hook
Error: (error (wrong-type-argument number-or-marker-p nil))
  (max nil 360000000)
  (if undo-tree-limit (max undo-outer-limit undo-tree-outer-limit) most-positive-fixnum)
  (set (make-local-variable 'undo-outer-limit) (if undo-tree-limit (max undo-outer-limit undo-tree-outer-limit) most-positive-fixnum))
  ...

真正出错的位置就是 undo-tree-mode 函数里的第三条 (max ...) 语句:

(set (make-local-variable 'undo-outer-limit)
	 (if undo-tree-limit
	     (max undo-outer-limit undo-tree-outer-limit)
	   most-positive-fixnum))

undo-outer-limit 是在 undo.c 里面定义的变量,并且初始值为 24000000。但是 emacs.c 又把它置为了 nil,这也许就是 batch 模式下报错的原因:

我不知道你是不是要在 batch 模式下做什么测试,是否真的会用到 undo-tree 的功能。如果只是单纯的想让 init.el 通过,可以这样修复:

(when noninteractive
  (defun undo-tree-save-history-from-hook ())
  (setq undo-outer-limit 24000000))

既然 emacs.c 有意把 undo-outer-limit 置为 nil,就是为了不让在 batch 模式下使用。虽然强行设置可以使得 init.el 通过,但此时如果去调用 undo-tree 的函数依然可能会有问题。

4 个赞

多谢,这个调试技巧学习到了!

其实是有个脚本要运行一些elisp代码,但是又不想多维护一份init.el,就直接载入了原来的init.el,所以并不是用undo-tree的功能,只是想让它完成载入。这个脚本之前运行得一直挺好的,最近突然出现就想问一下。

FYI,在undo-tree的新版本中,这个问题是这样解决的: Undo-tree bug-fix release.

还未测试,不过猜想极有可能是 twlz0ne 说的,编译后的elc文件导致没有产生backtrace。