【求助】NixOS中home-manager与emacs包管理器的混用

国庆前折腾了一下NixOS,零零散散配置的勉强能用了,主要是为了尝试EAF和NixOS,就剩下Emacs和开发环境的配置。

Emacs为了配置文件的通用自然不会使用home-manager来配置,依旧使用 use-package集成straight

今天在配置emacs-rime过程中发现指定rime-librime-root和rime-emacs-module-header-root比较麻烦,很多解答也是在 ~/.nix-profile/ 目录下的解决方案,当不通过nix-env而通过home-manager配置安装的emacs及extraPackages不会在这个目录下。

后尝试使用epkgs.rime,说是不用指定上述设置,但在emacs运行中rime没被加载,疑似包管理器的原因,还是说通过extraPackages配置的包需要额外在emacs中进行加载配置?测试后为straight原因,导致extraPackages未加载

后续还会使用到python依赖包之类的,所以home-manager得用,,想问问坛友有没有在不改变我包管理器的情况下,混用的方法,目前的问题就是通过extraPackages配置的包在emacs中未被加载。

使用 (locate-library “rime”)返回nil

纯净emacs使用 (locate-library “rime”)返回 /nix/store/*emacs-packages-deps/share/emacs/site-lisp/elpa/rime-*/rime.elc

我建议所有 Emacs 包都用 straight.el 来装。说实话,除非你只用 Nix,否则没必要用 Nix 来混装 Emacs 的包。只要你还想在不同系统上用同一套 Emacs 配置,那维护一个干净的 straight.lockfile 这样在哪个平台上都能用。

如果你有些包(比如 notmuch)是用 Nix 装的,那怎么在 Emacs 里找到它需要的文件呢?很简单,把它的路径设成环境变量就行了。这样你的 Emacs 配置就能跨平台,改动也最小,基本上就是在配置里用 (getenv ...) 拿一下路径。

下面是我用 Nix 装 Notmuch 的例子,你可以参考下我是怎么找到它头文件的:

home.packages = with pkgs; [
  # 让 Universal Ctags 优先级比 Emacs 自带的高,
  # 所以我在系统和用户环境里都装了它。
  universal-ctags

  # vterm 的编译依赖
  cmake
  libtool

  # 我用 notmuch 当邮件客户端,msmtp 发邮件。
  # IMAP 同步用的是自己搭的 mbsync 容器。
  # 为啥这么折腾?主要是因为 Debian/Ubuntu 上的 mbsync 自带 OAuth2 支持,
  # 在其他系统上的 OAUTH2 插件装起来不仅麻烦,还得手动编译插件。
  msmtp
  notmuch
  librime
];

# 把 notmuch 的 elisp 文件目录传给 Emacs 的 load-path
# 具体可以看 nix-darwin 的 issue #342
home.sessionVariables = {
  NIX_NOTMUCH_EMACS_LOAD_PATH = "${pkgs.notmuch.emacs}/share/emacs/site-lisp";
  LIBRIME_PATH = "${pkgs.librime}";
  EMACS_PATH = "${pkgs.emacs}";
};

像上面这样配置好之后,Emacs 的头文件目录就是 $EMACS_PATH/include。至于 Rime 的头文件在哪,你直接 cd $LIBRIME_PATH 进去看一眼就知道了。

1 个赞

好办法,忘了可以用home.sessionVariables配置环境变量。感谢解答。

但对依赖比较复杂的包(EAF这类)我还是想尽量依靠nix隔离环境,让其在独立环境中使用,而不依靠环境变量,这既增加可复现性,也更省事些还不需要我去配置全局环境。其他情况下确实还是环境变量好些,更通用。

还是想解决straight下Nix配置的包不加载的问题,因为就算配置文件迁移到其他系统上也要重新配置环境,但给nix的话它可以直接搞定,不需要我去配置环境(虽然用nix文件去配全局环境也能够复现,但我总感觉会污染环境而且nix下可能会出问题。

关于 EAF 这种包,虽然 nix 已经打包好了 eaf,但是如果是我的话,我会采用另外一种做法。

我会写一个 flake.nix 然后把这个 flake.nix 用 nix build --profile .eaf 把这个包构建到 .eaf 目录底下,然后在 eaf 的 eaf-python-command 设置成 .eaf/bin/python 即可。

主要原因有两个,第一个是在其他非 nix 的平台,我可以把 .eaf 这个目录底下的 python 环境用 uv 去构建,还是为了实现跨平台性。当然你也可以选择用 uv2nix 在 nix 上构建 .eaf 目录。

第二个是, nixpkgs 即便是 unstable 更新频率速度较之上游仓库也是要慢很多的的。对于系统包,更新慢一些无所谓,但是对于 emacs 插件这种无非就是一堆 lisp 代码的,我会希望能够随时用 straight 保持较快的更新节奏,而不是跟着 nixpkgs/unstable 分支慢慢等。而且 eaf 如果用 striaght 的话,我可以随时切到 ~/.emacs.d/staight/repos/eaf-xxx 直接改代码,然后测试效果。用 nix 的话,只读环境就做不到这一点。]

另外,直接用 nix 装包,

programs. emacs = {
enable = true;
package = pkgs .emacs;
extraPackages = epkgs: [ epkgs.rime ];
}

这样有什么问题吗?为什么会加载不出来呢? C-h v 查看 load-path,rime 就在 load-path 里啊。我就是用 straight 的。应该是你 straight 设置了不改设置的变量,要不就是你没有用 home-manager 的 programs.emacs.enable

读了一下 rime 的源代码,emacs-rime 包依赖这个变量 rime-share-data-dir 这个变量并没有被正确的识别,因此即使你使用 nix 装的这个包,依然需要手动的设置这个变量到正确的值。

3 个赞

不是 rime-share-data-dir的问题,听到你的straight是正常加载我再去排查配置了,nix文件配置是一样的,emacs则是把init.el和early-init.el都注释掉后都没能读取,但把整个emacs配置文件夹删了,重启emacs,却又正常加载rime了。

你的方法很合理,我尽量去尝试使用hhh,现在我先排查下我的配置,之前都在mac上用的

emacs-rime 不是 eaf 这种有人专门打的包,就是自动生成的包,因此原包默认的检测路径的逻辑找不到 rime 正确的路径这个问题也是正常。

好的,感谢 :rose:,我再折腾一下看看,把配置文件都删了,没理由不给我加载hhh


破案了,i3wm快捷键启动 “${pkgs.emacs}/bin/emacs”,导致emacs没加载到包,用rofi启动就加载了。脑瓜子翁翁的。

我先尝试使用nix去加载包临时试试,后续转用你的配置方法。 又有nix build又有nv、nv2nix,感觉学习成本不小hhh

一套比较纯的配置确实比混合使用不同包管理器更易于管理。对于我来说,经过一段时间的使用,我决定彻底倒向NixOS,主要是考虑到目前我没有遇到“允许我安装Emacs,但是不允许我自己安装操作系统”的情况,因此我要么不用Emacs,要么就装NixOS;所以我的Emacs配置文件是“只配置不安装”的,就是说安装packages靠Nix完成,Emacs配置只做use-pacakge。之前我还是把要安装的包列在home-manager中的programs.emacs.extraPackages里,后来我干脆为我的Emacs配置文件写一个derivation,也就是把我的Emacs配置文件打成Nix包,把其他要安装的包设为其依赖,这样extraPackages里只用放我的“配置文件包”就行了,非常的简洁。最后在home-manager的programs.emacs.extraConfig中写一行use-package调用我的配置文件,这样装好系统后Emacs环境就不需要做任何更多配置了,非常方便。

1 个赞

我找到了我不能混用的原因 在early-init.el文件中的(setq package-enable-at-startup nil),要么后续package-initialized 要么手动require也行,另外就是i3的快捷键启动导致的加载丢失。排除这两者后nix与straight就可以正常混用了。

只靠nix管理packages确实可行,但太依赖于nix了,当然在能混用的情况下,你只要对系统环境进行判断就可以灵活判断是否需要使用straight来安装包了。

我的思路是只要我在nixos上,就用nix去安装依赖复杂的包,省点事,也更可复现。这样也不会过于依赖nix,即使转移到别的系统无非就是原来那一套包管理器的加载配置了。对插件的各自配置还是全靠elisp。

也许在以后我也会把packages都交由nix来安装,但目前来说只要我没能和你一样写个emacs的derivation出来,效果可能变化不大。

另外就是i3的快捷键启动导致的加载丢失

我觉得你可能需要先了解一下 nixpkgs 里 emacs 是怎么打包的。按你上面用 pkgs.emacs 肯定是不行的。因为这个只有 emacs,而 emacs with packages 是通过一个 shell 脚本的 wrapper 设置 EMACSLOADPATH 来指定路径的,这个路径下会有 site-start.el(会在 user init 之前被执行),来完成一些初始化。

我找到了我不能混用的原因 在early-init.el文件中的(setq package-enable-at-startup nil)

这个只是决定了什么时候做初始化而已。你关掉就要手动做,不关就会自动在 user init 前做。

只靠nix管理packages确实可行,但太依赖于nix了,当然在能混用的情况下,你只要对系统环境进行判断就可以灵活判断是否需要使用straight来安装包了。

nix 管理的原理就是加到 loadpath 里而已。和你用 emacs 的 package.el 是一样的,区别只是放置的目录不一样罢了。你完全可以 require 失败时回退到用 straight,而不需要有任何额外的检查(怎么回退你可以看 use-package 的 ensure 实现,或者看 require 的文档),而且我记得 straight 也有做检查,如果加载不到才会去拉代码。

进一步,你可以把自己的配置分成两部分,其中一部分是根据不同系统来单独加载的。nixos 则可以在 nix 里来生成这个文件,而在其它系统则用其它的文件。判断方法有很多,比如 /etc/os-release 配合 system-type 或者特殊加载文件是否存在,或者让这个加载文件变成可选项,或者你可以直接利用 custom-file

不同系统的差异主要就是二进制依赖的加载,可执行文件一般都是依靠 executable-find,所以问题不大,保证在路径里就行。nixos 则可以对 emacs 做二次包装,让 emacs 看到额外的可执行路径(通过 PATH 或者 exec-path 都行),其它系统也一样,你可以自己 alias emacs 加额外路径,或者在用户配置里加额外的 exec-path 来避免给 shell 里加入你不想要的可执行文件。 rime 则就是动态库查找,常用有 pdf-tools、rime、telega 这种。其实也就 rime 会麻烦点,因为其它两个你只要有个 cc,自己编译也不麻烦。

何况你分系统可能还会分工作区,比如工作和家用会有配置区分,独立、动态加载的配置文件肯定得要的。

应该可以通过 programs.emacs.extraPackages来安装包吧,现在配置的rime是正常使用的,还没来得及去了解emacs with packages

这个选项是因为使用straight设置的,毕竟包管理器会有冲突,所以提了一下需要后续手动require包,如果设置了(setq straight-use-package-by-default t) 除非明确:straight nil,不然总是会去拉取包而不加载本地包。

有道理,等基本配置完后我试着把不同系统、场景的部分拆分出来

这个选项是因为使用straight设置的,毕竟包管理器会有冲突,所以提了一下需要后续手动require包,如果设置了(setq straight-use-package-by-default t) 除非明确:straight nil ,不然总是会去拉取包而不加载本地包。

你可以这样

(straight-use-package '(org :type built-in)) # 把 org 换成别的你不需要 straight 去下载的包。

因此你只需要用这个选项 straight 就不会去自己下载包了。你用 straight 就没必要加载 pacakge.el 了,不然一个 emacs 里有三个包管理源,太混乱了。

这个有尝试过,不会自己去拉取包,但rime包也无法正常运行,还是需要去(require 'rime),不然会报 Can’t activate input method 'rime require 或者 packages-initialized 后正常运行,想着在:init部分加个判断,或者其他地方加个判断应该可以处理。

package-initialize 没有关系。你要检查一下 input-method-alist 中是否包含 rime 这个属性。如果你是通过 straightpackage.el 等包管理器安装的,那么该输入法的条目会通过 autoload 自动加载到 input-method-alist 中。当你激活输入法时,rime 会被自动 lazy 加载,用户无需手动加载。

如果在 input-method-alist 中没有看到 rime,说明通过 Nix 安装的 rime 包未正确处理作者在包中设置的 autoload 标记。因此,你需要在用户配置的 :init 部分手动加入以下代码:

(register-input-method "rime" "euc-cn" 'rime-activate rime-title)

更新:读了一下 package-initialize 的逻辑,原来是因为 nix 装的包生成 autoload 文件不会被 emacs 自动加载,而自动加载这些生成的 autoload 文件的任务被委派给了 package-initialize。

1 个赞
(register-input-method "rime" "euc-cn" 'rime-activate rime-title)

没有奏效,不过input-method-alist中确实没有rime。

居然是这样,那还是得用到package.el,等straight初始化完成在 packages-initialized 尽量避免冲突就是。有时间我也去看看 packages-initialized

没有奏效,不过input-method-alist中确实没有rime。

还漏了一个 autoload 需要手动加载 (defvar rime-title (char-to-string 12563)。 这样感觉自己手动模拟 autoload 确实不是一个很好的方式,还是交给 package-initialize 来吧。

居然是这样,那还是得用到package.el

nix 装的 emacs 包采取了一个省事的做法。他把所有的包的路径都放在了 elpa 底下,这样 package.el 就会在 package-initialize 的时候去加载生成的 autoload 文件。这样 nix 就避免了自己去实现加载 autoload 文件逻辑。话说不看代码也确实不知道,我之前以为 nix 装的包就只是把包路径放到 load-path 就完事了。

1 个赞