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 容易踩到。