之前我们介绍了Emacs的 package 包管理系统,可以进行包的日常维护工作。但是在现在的Emacs中,普通使用的是一个名为 use-package 的包。它专门用来管理我们安装的第三方包,从本篇开始,我们将逐步讨论它的一些相关机制
背景知识
在早期的Emacs版本中,use-package 还只是一个"编外" 的包,需要通过 package-install 事先下载。但是从 Emacs 29.1 开始它被收编为Emacs内置的软件包,可以直接使用。
首先我们要明白一点,use-package 并没有发明一套全新的包管理机制,它是对旧有的包管理系统通过宏进行的封装。底层仍然是 package-install、package-uprade 这样的函数。
它的签名如下:
(use-package NAME &rest ARGS)使用原版的包管理系统管理包的时候,体验是比较割裂的。我们确认包不是内置的时候,需要先通过编写 (package-install 'name) 这样的代码进行下载,之后再通过 (require 'name) 进行引用, 之后通过代码进行配置和使用。使用 use-package 的一个好处是,它将二者统一起来了,我们只需要写下 (use-package name) 这样的代码,它可以帮助我们完成从下载、更新、引用、配置这样的活。我们仅仅通过use-package 这样一个宏就搞定了。
我们按照之前分析 define-minor-mode 的思路来通过展开宏进行分析,看看use-package 背后真正帮我们干了什么。我们先使用最简单的形式:
(use-package org)它展开的代码如下:
(progn
(defvar use-package--warning0
#'(lambda (keyword err)
(let
((msg
(format "%s/%s: %s" 'org keyword
(error-message-string err))))
(display-warning 'use-package msg :error))))
(condition-case-unless-debug err
(if (not (require 'org nil t))
(display-warning 'use-package (format "Cannot load %s" 'org)
:error))
(error (funcall use-package--warning0 :catch err))))这段代码主要分为两个部分:
首先它定义了一个警告函数 use-package--warning0,该函数接受一个关键字和一个err对象。它的作用是将参数格式化,并通过 display-warning 打印。然后通过 display-warning 打印这么一条错误信息。
这里我觉得有两点小巧思:
这里使用 defvar 定义了一个lambda 函数绑定到变量。而没有使用 defun 直接定义函数。因为 use-package 作为常用的宏,不可能只执行一次。如果使用defun 的方式,就会出现重复定义的情况,而利后缀进行区分的话也没有必要,因为封装的报错函数代码基本雷同。所以这里利用 defvar 定义时只有第一次执行会生效的特性,确保使用 use-package 的不同地方只定义一个报错函数
第二点其实算不上什么小巧思,就是这里报错使用 display-warning 而没有使用我们在前面时时常使用的 message 进行调试,或者使用 use-error。首先我们说message 是常规信息输出,用于显示信息。而 display-warning 类似于Emacs中的日志系统。它会往 *Warnnings* 缓冲区中写一条信息。对于这种错误更适合使用日志系统进行记录,方便日后排查。
第二部分才是它的重头戏。这里我们可以看到 (require 'org) 加载 org 包的内容。也就是说use-pacakge 此时已经代替了 require。
require 的外层经过了严格的异常处理和条件判断。在加载时如果未找到对应的包或者加载时有其他问题,都会使用 display-wwarning 报错,只不过一个是直接报错,而另一个是通过异常处理调用我们事先定义好的函数。
从这段分析可以看到 use-package 对 require 外层进行了包装,确保了加载包的错误不会导致Emacs崩溃,影响其他功能的使用。
总结
本篇我们对 use-package 有了一个基本的认识。知道了它其实就是对 package 系列函数和 require 进行了一层外部封装。将它们有机结合起来,我们可以通过一个use-package 完成包的安装、加载、配置。使代码更加紧凑。
评论 (0)