设置软件仓库源

Emacs 默认使用的是 https://elpa.gnu.org/https://melpa.org 。 由于 FW 的原因,访问速度非常慢,经常超时,所以需要设置国内源。

需求

  • 通过自定义配置文件指定仓库源
  • 解决包 git 地址问题
  • 解决多源管理问题
  • 解决包升级时下载阻塞问题

基础使用

Emacs 默认使用 list-packages 浏览官方的仓库列表,可使用的包比较少,主要是因为 LICENSE 不是 GPL 自由软件授权,都不会加入该仓库。

软件包列表

使用 M-x list-packages RET 即可,会打开一个 *Packages*buffer 显示软件包列表。这个时候可以使用快捷键进行管理。

快捷键 说明
i 标记需要安装的包
u 取消标记的安装包
x 安装已标记的安装包
n 下一个包
p 上一个包
d 删除
H 正则过滤包并隐藏
g 刷新列表
/ 清除过滤
k 基于关键字过滤
n 基于名字过滤
h 帮助文档

安装

使用安装命令 M-x package-install RET <package_name> 可以直接输入包名进行安装。下载安装的包会自动存放在 /.emacs.d/elpa/~目录下。可以通过设置 ~package-user-dir 变量,指定下载安装的包的目录位置。

1
(setq package-user-dir "your package dir")

重新安装

重新安装使用 M-x package-reinstall RET <package_name> 就可以了。

更新

升级包输入 M-x upgrade-package RET 即可自动更新已安装的软件包。

删除

如果需要删除,可以直接输入 M-x package-delete RET <package_name> 指定包名,进行删除。

刷新列表

在获取包列表后,会自动缓存信息,如果需要刷新包列表缓存,需要使用 package-refresh-contents 刷新即可

解决方案

由于使用默认的方式,正常情况下是不够用的,很多第三方包是在 melpa 仓库中,还有的可能需要手动下载,这导致包管理起来比较麻烦,特别是升级时。因为 emacser 们喜欢折腾,很多时候包根本不稳定,会持续性更新和 bug 的修复。这个时候就需要解决仓库源和包升级的问题了,同时还需要解决包下载网络速度慢的问题。 Emacs 默认提供了一个 package-archives 变量,来设置仓库源,可以通过设置该变量,解决仓库源和网络问题(在特殊情况下,还是没办法一些包的问题,比如需要的包只有 git 仓库地址,并未发布至软件仓库)。

1
2
3
4
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ;; GNU ELPA repository (Offical)
                         ("melpa" . "https://melpa.org/packages/") ;; MELPA repository
                         ("melpa-stable" . "https://stable.melpa.org/packages/") ;; MELPA Stable repository
                         ("org" . "http://orgmode.org/elpa/"))) ;; Org-mode's repository

需要引入 use-packagestraight.el 两个包管理扩展来解决包 git 下载地址和管理更新等问题了。这两个包即可配合也可单独使用,如果单独使用, straight.el 需要配合 straight.el 来使用了。

这里就只介绍 use-packagestraight.el 的配合使用,来解决下载地址和包升级管理。在引入这两个包前,需要回顾一下,前面基础配置时,有一个禁止加载已安装包的变量,它就是 package-enable-at-startup ,我们需要结合该变量进行对包管理的升级改造。

解决包下载升级时阻塞,导致无法正常工作,必须等待完成的问题。需要引入一个第三方 paradox 包,来解决该问题,注意使用该包后,对应的快捷键也有变化,因为它实际是对默认的管理器做了升级改造。

代码实现

打开 init-package.el 添加以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
(defcustom dotfile-package-archives-alist
  (let* ((no-ssl (and (memq system-type '(windows-nt ms-dos))
                      (not (gnutls-available-p))))
         (proto (if no-ssl "http" "https")))
    `(,(cons 'melpa
             `(,(cons "gnu"   (concat proto "://elpa.gnu.org/packages/"))
               ,(cons "melpa" (concat proto "://melpa.org/packages/"))))
      ,(cons 'emacs-china
             `(,(cons "gnu"   (concat proto "://elpa.emacs-china.org/gnu/"))
               ,(cons "melpa" (concat proto "://elpa.emacs-china.org/melpa/"))))
      ,(cons 'netease
             `(,(cons "gnu"   (concat proto "://mirrors.163.com/elpa/gnu/"))
               ,(cons "melpa" (concat proto "://mirrors.163.com/elpa/melpa/"))))
      ,(cons 'ustc
             `(,(cons "gnu"   (concat proto "://mirrors.ustc.edu.cn/elpa/gnu/"))
               ,(cons "melpa" (concat proto "://mirrors.ustc.edu.cn/elpa/melpa/"))))
      ,(cons 'tencent
             `(,(cons "gnu"   (concat proto "://mirrors.cloud.tencent.com/elpa/gnu/"))
               ,(cons "melpa" (concat proto "://mirrors.cloud.tencent.com/elpa/melpa/"))))
      ,(cons 'tuna
             `(,(cons "gnu"   (concat proto "://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/"))
               ,(cons "melpa" (concat proto "://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/"))))))
  "The package archives group list."
  :group 'dotfile
  :type '(alist :key-type (symbol :tag "Archive group name")
                :value-type (alist :key-type (string :tag "Archive name")
                                   :value-type (string :tag "URL or directory name"))))

(defcustom dotfile-package-archives 'melpa
  "Set package archives from which to fetch."
  :group 'dotfile
  :set (lambda (symbol value)
         (set symbol value)
         ()
         (setq package-archives
               (or (alist-get value dotfile-package-archives-alist)
                   (error "Unknown package archives: `%s'" value))))
  :type `(choice ,@(mapcar
                    (lambda (item)
                      (let ((name (car item)))
                        (list 'const
                              :tag (capitalize (symbol-name name))
                              name)))
                    dotfile-package-archives-alist)))

(defun set-package-archives (archives &optional refresh async)
  "Set the package archives (ELPA).
REFRESH is non-nil, will refresh archive contents.
ASYNC specifies whether to perform the downloads in the background.
Save to `custom-file' if NO-SAVE is nil."
  (interactive
   (list
    (intern (completing-read "Select package archives: "
                             (mapcar #'car dotfile-package-archives-alist)))))
  ;; Set option
  (customize-set-variable 'dotfile-package-archives archives)
  ;; Refresh if need
  (and refresh (package-refresh-contents async))
  (message "Set package archives to `%s'" archives))

(set-package-archives 'ustc t t)

这里使用了以前没见过的 defcustom 1宏,后面会经常看到,或者在其他第三方配置中常见的,配置自定义变量,经常用于提供自定义的配置,指定变量值的类型,在使用 customize-set-variable 设置自定义变量时,会调用 :set 属性,该属性是一个 setfunction ,所以,会看到这里的 dotfile-package-archives:set 中定义了一个匿名函数,在函数中去真正给 package-archives 设置指定的仓库源。 set-package-archives 是我们设置仓库源的入口函数,该函数的作用是去设置仓库源,并判断是否更新 package-list ,并指定是否异步请求。

接下来需要安装 use-package ,并使用它安装需要的软件包。在安装该包时,需要先使用 package-initialize 初始化。如果已经初始化,将禁止包加载并重新初始化。 如果 use-package 没有安装,更新 package-list 使用 package-install 命令安装,并修改 use-package 的默认参数。由于安装时需要公钥,需要安装 gnu-elpa-keyring-update

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
;; Initialize packages
(unless (bound-and-true-p package--initialized) ; To avoid warnings in 27
  (setq package-enable-at-startup nil)          ; To prevent initializing twice
  (package-initialize))
;; Setup `use-package'
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(eval-and-compile
  (setq use-package-always-ensure t)
  (setq use-package-always-defer t)
  (setq use-package-expand-minimally t)
  (setq use-package-enable-imenu-support t))

;; Update GPG keyring for GNU ELPA
(use-package gnu-elpa-keyring-update)

安装 straight.el 需要去 github 源码仓库下载,所以国内的用户,在这个时候需要使用到前面的代理了,否则无法安装(这也是为什么会先配置代理)。启动自己的代理服务,然后通过命令 proxy-http-toggle 设置代理环境。以下代码来自 straight.el 官方2。并配置使用 use-package 通过 straight 属性安装第三方包,详细使用方法参考官方文档2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)

由于默认的 package-list 在更新安装时会阻塞,导致无法继续其他工作,所以可以通过安装 paradox 来优化默认的列表 buffer ,并使用 paradox-enable 替换 package-list 命令。注意快捷键也有明显的变化。可以通过按键 h 查看。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
;; A modern Packages Menu
(use-package paradox
  :init
  (setq paradox-execute-asynchronously t
        paradox-github-token t
        paradox-display-star-count nil)
  ;; Replace default `list-packages'
  (defun my-paradox-enable (&rest _)
    "Enable paradox, overriding the default package-menu."
    (paradox-enable))
  (advice-add #'list-packages :before #'my-paradox-enable)
  )

愉快的玩耍

接下来就可以愉快的玩耍了。