分享一个 quelpa 安装包名的坑

TL;DR

当 elisp 源文件中头尾注释中使用的文件名与实际文件名不符时, quelpa 安装需要以文件注释中的为准。 比如 mvn.el 中写的是 maven.el ,则安装包名为 maven

踩坑过程

最近使用了一个 maven 构建工具的辅助包 mvn-el ,因为它没有进 melpa ,我就通过 quelpa 来安装了:

(quelpa '(mvn :fetcher github :repo "apg/mvn-el"))

正常情况下没什么问题,但今天网络出了一些状况,无法上网,结果启动 Emacs 时报错了:

Debugger entered--Lisp error: (error "Invalid version syntax: ‘Command ’(env LC_ALL=C /usr/bin/timeout -k 60 600 git fetch --all --tags)’ exited with non-zero status 1: Fetching origin\nfatal: unable to access 'https://github.com/melpa/melpa.git/': Could not resolve host: github.com\nerror: Could not fetch origin\nFetching origin\nfatal: unable to access 'https://github.com/apg/mvn-el.git/': Could not resolve host: github.com\nerror: Could not fetch origin\n’ (must start with a number)")
  signal(error ("Invalid version syntax: ‘Command ’(env LC_ALL=C /usr/bin/timeout -k 60 600 git fetch --all --tags)’ exited with non-zero status 1: Fetching origin\nfatal: unable to access 'https://github.com/melpa/melpa.git/': Could not resolve host: github.com\nerror: Could not fetch origin\nFetching origin\nfatal: unable to access 'https://github.com/apg/mvn-el.git/': Could not resolve host: github.com\nerror: Could not fetch origin\n’ (must start with a number)"))
  error("Invalid version syntax: `%s' (must start with a number)" "Command ’(env LC_ALL=C /usr/bin/timeout -k 60 600 git fetch --all --tags)’ exited with non-zero status 1: Fetching origin\nfatal: unable to access 'https://github.com/melpa/melpa.git/': Could not resolve host: github.com\nerror: Could not fetch origin\nFetching origin\nfatal: unable to access 'https://github.com/apg/mvn-el.git/': Could not resolve host: github.com\nerror: Could not fetch origin\n")
  version-to-list("Command ’(env LC_ALL=C /usr/bin/timeout -k 60 600 git fetch --all --tags)’ exited with non-zero status 1: Fetching origin\nfatal: unable to access 'https://github.com/melpa/melpa.git/': Could not resolve host: github.com\nerror: Could not fetch origin\nFetching origin\nfatal: unable to access 'https://github.com/apg/mvn-el.git/': Could not resolve host: github.com\nerror: Could not fetch origin\n")
  quelpa-version>-p(mvn "Command ’(env LC_ALL=C /usr/bin/timeout -k 60 600 git fetch --all --tags)’ exited with non-zero status 1: Fetching origin\nfatal: unable to access 'https://github.com/melpa/melpa.git/': Could not resolve host: github.com\nerror: Could not fetch origin\nFetching origin\nfatal: unable to access 'https://github.com/apg/mvn-el.git/': Could not resolve host: github.com\nerror: Could not fetch origin\n")
  quelpa-checkout((mvn :fetcher github :repo "apg/mvn-el") "/home/whatacold/.emacs.d/quelpa/build/mvn")
  quelpa-build((mvn :fetcher github :repo "apg/mvn-el"))
  quelpa-package-install((mvn :fetcher github :repo "apg/mvn-el"))
  quelpa((mvn :fetcher github :repo "apg/mvn-el"))
  eval-buffer(#<buffer  *load*-63098> nil "/home/whatacold/.emacs.d/lisp/init-java.el" nil t)  ; Reading at buffer position 760
  load-with-code-conversion("/home/whatacold/.emacs.d/lisp/init-java.el" "/home/whatacold/.emacs.d/lisp/init-java.el" nil nil)
  load("/home/whatacold/.emacs.d/lisp/init-java")
  (let ((file-name-handler-alist nil)) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-autoload))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-modeline))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-compat))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-utils))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-elpa))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-exec-path))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-spelling))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-gui-frames))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-uniquify))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-ibuffer))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-ivy))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-hippie-expand))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-windows))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-markdown))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-erlang))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-javascript))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-org))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-css))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-python-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-haskell))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-ruby-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-lisp))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-elisp))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-yasnippet))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-cc-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-gud))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-linum-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-git))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-gtags))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-clipboard))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-evil))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-multiple-cursors))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-sh))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-ctags))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-bbdb))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-gnus))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-lua-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-workgroups2))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-term-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-web-mode))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-company))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-chinese))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-keyfreq))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-httpd))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-misc))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-emacs-w3m))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-hydra))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-shackle))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-graphviz))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-elfeed))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-java))) (add-to-list 'load-path (expand-file-name "~/.emacs.d/lisp")) (load (file-truename (format "~/.emacs.d/site-lisp/%s/%s" 'idle-require 'idle-require))) (setq idle-require-idle-delay 2) (setq idle-require-symbols '(init-perforce init-slime init-misc-lazy init-which-func init-fonts init-hs-minor-mode init-writting init-pomodoro init-dired init-artbollocks-mode init-eshell init-semantic)) (idle-require-mode 1) (if (require 'time-date nil t) (progn (message "Emacs startup time: %d seconds." (time-to-seconds (time-since emacs-load-start-time))))) (load (file-truename (format "~/.emacs.d/lisp/%s" 'init-site-lisp))) (if (file-exists-p "~/.custom.el") (load-file "~/.custom.el")))
  eval-buffer(#<buffer  *load*> nil "/home/whatacold/.emacs.d/init.el" nil t)  ; Reading at buffer position 7241
  load-with-code-conversion("/home/whatacold/.emacs.d/init.el" "/home/whatacold/.emacs.d/init.el" t t)
  load("/home/whatacold/.emacs.d/init" t t)
  #f(compiled-function () #<bytecode 0x261c01>)()
  command-line()
  normal-top-level()

我已经设置了默认不更新包,按道理不需要联网获取了才对:

(setq quelpa-checkout-melpa-p nil
      quelpa-update-melpa-p nil
      quelpa-melpa-recipe-stores nil
      quelpa-self-upgrade-p nil)

通过跟踪查看 backstrace ,发现 quelpa-checkout 会通过 package-alist 来判断是否需要安装或升级:

(defun quelpa-checkout (rcp dir)
  "Return the version of the new package given a RCP.
Return nil if the package is already installed and should not be upgraded."
  (pcase-let ((`(,name . ,config) rcp)
              (quelpa-build-stable quelpa-stable-p))
    (unless (or (and (assq name package-alist) (not quelpa-upgrade-p)) ;; <--- XXX 应该已经安装了,返回 t
                (and (not config)
                     (quelpa-message t "no recipe found for package `%s'" name)))
      (let ((version (condition-case err
                         (quelpa-build-checkout name config dir)
                       (error "Failed to checkout `%s': `%s'"
                              name (error-message-string err)))))
        (when (quelpa-version>-p name version)
          version)))))

检查 package-alist 发现并没有 mvn ,但是却有一个 maven

 (maven #s(package-desc :name maven :version
                         (20160211 2343)
                         :summary "helpers for compiling with maven" :reqs nil :kind nil :archive nil :dir "/home/whatacold/.emacs.d/elpa/maven-20160211.2343" :extras
                         ((:url . "https://github.com/apgwoz/mvn-el")
                          (:maintainer "Andrew Gwozdziewycz" . "[email protected]")
                          (:authors
                            ("Andrew Gwozdziewycz" . "[email protected]")))
                         :signed nil))

包描述与我安装的及其相似,于是试着修改为安装 maven ,问题得到解决:

(quelpa '(maven :fetcher github :repo "apg/mvn-el")) ; change `mvn' to `maven'

那包名是如何定义的呢?

之前我以为包名就是 github repo 名称或者源码文件名,现在看来不是这样。 再去看 apg/mvn-el 代码, mvn.el 在开头和结尾注释中使用了 maven.el

;;; maven.el --- helpers for compiling with maven

;; Copyright (C) 2013 Andrew Gwozdziewycz <[email protected]>

;; ... many lines omitted

(provide 'mvn)

;;; maven.el ends here

应该就是通过这些“标准”的注释提供的 package 信息的,最后找到 quelpa 是通过 package-buffer-info 解析源码文件得到 package 信息:

(defun quelpa-build--package-buffer-info-vec ()
  "Return a vector of package info.
`package-buffer-info' returns a vector in older Emacs versions,
and a cl struct in Emacs HEAD.  This wrapper normalises the results."
  (let ((desc (package-buffer-info)) ;; XXX <--- HERE
        (keywords (lm-keywords-list)))
    (if (fboundp 'package-desc-create)
        (let ((extras (package-desc-extras desc)))
          (when (and keywords (not (assq :keywords extras)))

为了验证,我把 mvn.el 中的 maven.el 更改为 mvn.el 后,执行 (package-buffer-info) 得到的结果确实由 maven 变为 mvn 了(且头尾中的文件名必须一致):

#s(package-desc mvn (0 1) "helpers for compiling with maven" nil single nil nil ((:authors ("Andrew Gwozdziewycz" . "[email protected]")) (:maintainer "Andrew Gwozdziewycz" . "[email protected]") (:url . "https://github.com/apgwoz/mvn-el")) nil)

最后查看了下 info ,库文件有相关的规范,见: https://www.gnu.org/software/emacs/manual/html_node/elisp/Simple-Packages.html

综上,这其实是包代码不规范的坑,但用 quelpa 容易踩到。

5 个赞

emacs可以用来java开发吗?唉,小白刚入坑,折腾起来好麻烦

建议循序渐进,找一个别人的配置用着,慢慢了解, java 环境搞不定就先用别的 ide ,后续再逐步迁到 emacs 。