如何确定某个函数是哪一版本的Emacs引入的?

如题所示,或者说,如何确定自己写的el文件可以适用的最低版本的Emacs?

1 个赞

Changelog

有几种思路配合起来:

  1. 根据经验;
  2. M-x package-lint-current-buffer
  3. 用各种版本的 Emacs 进行 Byte Compile,检查 Warnings(一般在 CI 做这样的测试)
  4. C-u C-h n
1 个赞

我的 elisp-demos.el 也试图提供这样的信息:

* assoc-delete-all
:PROPERTIES:
:added:    26.2
:changes:  27.1 The TEST argument is added.
:END:

但是目前仅仅提供了几个。

package-lint-current-buffer 或其它文档提供的信息都有限。

最靠谱就是本地准备好各种版本的 Emacs,把 byte-compile 和测试用例先跑一遍,然后再推送到 github。

问题其实不止函数在哪个版本引入,还包括参数变更等等各种情况。例如 assoc 函数,最开始是这样的:(assoc KEY LIST),后来又增加了一个参数,变成了:(assoc KEY LIST &optional TESTFN)

⋊> /Applications/Emacs-25.3.1.app/Contents/MacOS/Emacs --batch --eval "(princ (assoc 'foo (list (cons 'foo 1)) 'eq))"
Wrong number of arguments: assoc, 3

⋊> /Applications/Emacs-26.1.app/Contents/MacOS/Emacs --batch --eval "(princ (assoc 'foo (list (cons 'foo 1)) 'eq))"
(foo . 1)
2 个赞

还有些函数本身也会有变化,比如下面这个表达式,Emacs 25 正常执行,而 Emacs 24.5 会报错

(pcase nil
  ('nil t))

Emacs 24 只能用:

(pcase nil
  (`nil t))

因为 pcase 从 Emacs 25 才开始支持 QUOTE Pattern。

不像上面提到的函数没定义或者参数数目多余,这样的问题 byte compiler 无法检查出来。

所以还是要写测试,测试覆盖到了,才有底气说没问题。

写代码的时侯,可能会记得某些个比较亮眼的特性/函数是从哪个版本开始引入的, 而实际上每个 Emacs 版本都可能会引入新的函数、增强/扩展/删除旧的函数,那些不太引人注意的函数也可能存在兼容性问题。

我一般会在本地用各个版本的 Emacs 把测试跑一遍,兼容性问题在这一步基本都能发现。

然后推到 githu 让 Travis 再验证一遍,这时如果还有问题,通常是因为环境差异,例如 travis 镜像默认没有安装一些必要资源。

2 个赞

能写测试最好了,尽管依旧可能会存在测试没覆盖到的情况。不过,大部分包连检查 Byte Compiler 都做不到,更别提测试了,我觉得主要是因为:

  • CI 下安装各个版本的 Emacs 比较困难;
  • Emacs 的包管理器跟不上,没法可靠地、无需用户介入地处理依赖;
  • Emacs 是个用户程序,很难测试包含用户交互的代码;
  • 开发者不够重视。

travis 可以啊,我看 Centaur Emacs 就这么搞的

这就是我说的「比较困难」,对比:

language: python
python:
  - "2.6"
  - "2.7"
  - "3.3"
  - "3.4"
  - "3.5"

EVM 提供的 Emacs 有个小问题是没有 libxml2 支持,所以没法测试 libxml-parse-html-region

有必要那么多么?我觉得大版本和master branch分别跑一次就可以了

指 Python 的版本么?(我只是举个例子来说明安装不同版本的 Python 比较容易)

官方支持的肯定不能比啦

个人觉得 CI 最有效,设置一次以后每次自动跑就好了。本地我简单用 docker 跑下不同版本,大多数还是用 travis。

27的help-fns.el加了一个函数,搜索emacs的NEWS文件,返回一个函数/变量可能的最早引入的版本

(defun help-fns--first-release (symbol)
  "Return the likely first release that defined SYMBOL, or nil."
  ;; Code below relies on the etc/NEWS* files.
  ;; FIXME: Maybe we should also use the */ChangeLog* files when available.
  ;; FIXME: Maybe we should also look for announcements of the addition
  ;; of the *packages* in which the function is defined.
  (let* ((name (symbol-name symbol))
         (re (concat "\\_<" (regexp-quote name) "\\_>"))
         (news (directory-files data-directory t "\\`NEWS.[1-9]"))
         (place nil)
         (first nil))
    (with-temp-buffer
      (dolist (f news)
        (erase-buffer)
        (insert-file-contents f)
        (goto-char (point-min))
        (search-forward "\n*")
        (while (re-search-forward re nil t)
          (let ((pos (match-beginning 0)))
            (save-excursion
              ;; Almost all entries are of the form "* ... in Emacs NN.MM."
              ;; but there are also a few in the form "* Emacs NN.MM is a bug
              ;; fix release ...".
              (if (not (re-search-backward "^\\*.* Emacs \\([0-9.]+[0-9]\\)"
                                           nil t))
                  (message "Ref found in non-versioned section in %S"
                           (file-name-nondirectory f))
                (let ((version (match-string 1)))
                  (when (or (null first) (version< version first))
                    (setq place (list f pos))
                    (setq first version)))))))))
    (when first
      (make-text-button first nil 'type 'help-news 'help-args place))
    first))
(help-fns--first-release 'pcase) ; => "24.1"
2 个赞

help-fns 也不靠谱啊,并不是所有的变更都记录在 NEWS 里。

有些移动/改名的函数查不到,比如在 25.1 有一大票 c-XXX 函数从 cl 转移到 cl-seq,还有 26.1 有一大票 cl-cXXXrcl-lib 转移到 subr 并改名为 cXXXr

也许最靠谱的方法就是启动 Emacs,加载所有内置的 feature,然后导出符号表到数据库。

package-lint 貌似比较靠谱,最近(几个月?不清楚多久了)加了一个约 600K 的数据文件,里面放着每个版本新增加的函数,因为很多,感觉是自动生成了,不过我没了解过它是怎么生成的。

~/.emacs.d/elpa-26.3/package-lint-20191103.1154/data/stdlib-changes

没法达到 100% 准确,关系也不大,出问题到时候再改就 OK 了。还有就是依赖比较高的版本的 Emacs 也 OK,比如用你自己在用的版本,自己写的插件可能就自己一个人用,如果别人要支持更低版本,大可等其发个 issue、pr 再考虑。

2 个赞

package-lint 的方法跟我说的类似,加载所有 lib,然后导出符号到数据库:

区别在于我原本设想是加载之后从 load-history 读取符号,而它是从 obarray 读取:

(defun sym-dump-filter-atoms (pred)
  (let (result)
    (mapatoms
     (lambda (f)
       (when (and (funcall pred f)
                  (not (string-prefix-p "sym-dump-" (symbol-name f))))
         (push f result))))
    (sort result (lambda (a b) (string< (symbol-name a) (symbol-name b))))))

的确可以利用这个现成的包和 data,来找出函数在 Emacs 哪个版本引入/删除。不过函数改名还是没法追踪,只能去查源代码 commit 信息了。

EDIT: package-lint 也漏掉了 caaar 等一系列函数。cXXXr 等函数转移到 subr.el 之后,在 cl-lib.el 中原先的 cl-cXXXr 则改为别名。这些别名在 package-lint 中倒是有。

https://emba.gnu.org/emacs/emacs/commit/43eba4955350b787c5567a31e2980ae70b9fb52f

1 个赞