- 将 Python 环境集成到 holo-layer 包中
在 Nix 的各种 mkDerivation 函数中,有几个字段是用来处理外部依赖的,其
中最基础的就是 buildInputs 字段。这个字段的值应该是一个列表,列表的每
一项都应该是一个 derivation。Nix 会先构建 buildInputs 中的 derivation,
再去构建我们写的 derivation。在构建我们的 derivation 时,可以使用
buildInputs 中的derivation(例如将编译器放到 buildInputs 中,就可以在
构建时用它来编译我们的源码了);并且构建完成后,我们的程序也可以直接使
用 buildInputs 中的 derivation(例如使用python3
命令调用 Python 解释
器)。
在正式开始之前,先介绍一下 Nix 中的 let 语法,举个例子:
let
a = 1;
b = a + 1;
in
{
item1 = a;
item2 = b;
}
可以认为我们在let
和in
之间写了一个没有大括号的属性集,而在in
后面
的属性集中,我们可以直接使用let
中的键,它会被替换为对应的值,所以上
面的代码等价于{ item1 = 1; item2 = 2; }
。in
后面除了跟一个属性集,
还可以跟一个列表,或者直接跟一个let
中的键,效果相同。
使用 let 语句来将 Python 环境加到 我们的 derivation 的 buildInputs 中,
效果大概是这样的:
{
stdenvNoCC,
fetchFromGitHub,
python3,
...
}:
let
python-pkgs =
ps: with ps; [
epc
inflect
pyqt6
pyqt6-sip
sexpdata
six
xlib
];
python-env = python3.withPackages python-pkgs;
in
stdenvNoCC.mkDerivation rec {
### 省略
buildInputs = [ python-env ];
### 省略
}
我们把 Python 的依赖库列表(虽然在这里,它形式上是一个抽象而不是列表,
但还是叫依赖库列表比较符合习惯)单独拆为python-pkgs
字段,在
withPackages
后面使用这个字段,使得代码架构更加清晰。注意在这里我们引
入了型参python3
,这也是callPackage
能自动注入的参数之一。我们将添加
好了各种库的完整 Python 环境用字段python-env
表示,最后用buildInputs = [ python-env ];
指定其为 buildInputs。
当我们完成这一切操作,并且将系统的 Python 环境移除,兴致勃勃地进行测试
的时候,我们就会发现这根本没用:holo-layer 还是找不到 Python(悲)。
让我们分析一下:设置 buildInputs 相当于把我们的 Python 环境中的那些可
执行的文件——例如python3
——所在的路径放到环境变量 PATH 中,但是这只对
我们自己写的 holo-layer 包起效果。事实上,是 Emacs 读取 holo-layer 中
的 elisp 脚本,由 Emacs 去调用python3
,而 Emacs 包的 PATH 并没有受影
响,自然找不到 Python。
去修改 Emacs 包的 PATH 也许并不是一个好主意,因为将来其他的包,比如我
们的 EAF,也会构建一个的 Python 环境,其中的依赖库是不同的。我们可以选
择构建一个 Python 环境包含所有的依赖库,但是那操作起来十分麻烦,感觉不
是很优雅。我们也不用担心两个分开的 Python 环境会占用很多空间,因为如果
有相同的依赖库,Nix 只会构建一次,然后把它软链接到不同的环境。
那么我提出了什么解决方法呢?答案是打补丁。不要忘了前面说的 Nix
derivation 的构建流程中是有“打补丁”这么一步的。只要我们将 holo-layer中
调用python3
命令的相关 elisp 代码,替换为 python3 的绝对路径,那么
Emacs 就一定可以找到。我的实现方法如下:
patchPhase = ''
substituteInPlace holo-layer.el \
--replace "\"python3\"" \
"\"${python-env.interpreter}\""
'';
查看 holo-layer 的 elisp 代码,发现调用 Python 的命令是由变量
holo-layer-python-command
控制的,在这个变量的定义处,可以看到其在
Linux 平台下的默认值是"python3"
,只需要把它改为绝对路径即可。因此我
们指定 patchPhase 字段,它的值也应该是一段 bash 脚本——和 installPhase
一样,substituteInPlace
好像是 Nix 的特殊命令,它原地修改一个文件,这
里我们指定 holo-layer.el 文件,然后调用--replace
替换操作,匹配文件中
的"python3"
字符串(带双引号),替换为绝对路径(带双引号)。
如果你还记得前面的知识,就会知到我们在外边定义的 python-env 字段,会自
动变成 bash 中的环境变量,因此可以用${python-env}
调用。而后面的
interpreter
字段则是该环境中的 Python 解释器的绝对路径,是预定义好的;
但如果我们不知道这个字段,还有另一个办法:python-env 字段的值是我们搭
建的 Python 环境,是一个 derivation,它是可以自动转换为字符串的,结果
为最终仓库的绝对路径;因此在它后面拼接上/bin/python3
就得到了
python3
命令的绝对路径。
再次测试,发现终于成功了。我们最终完成了一个 standalone 的、含有
Python 依赖的 Emacs 插件的 derivation 的编写,可喜可贺,可喜可贺。
在下一节,我们将开始讨论如何为 Emacs Application Framework 写
derivation。