news 2026/9/18 12:59:59

在 nixpkgs 中打包、分发与测试 Fish Shell 插件:vendor 机制、buildFishPlugin 与 wrapFish 实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 nixpkgs 中打包、分发与测试 Fish Shell 插件:vendor 机制、buildFishPlugin 与 wrapFish 实战指南

在 nixpkgs 中打包、分发与测试 Fish Shell 插件:vendor 机制、buildFishPlugin 与 wrapFish 实战指南

【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs

Fish 是 nixpkgs 中一款"智能且用户友好"的命令行 Shell,官方文档以doc/packages/fish.section.md为核心,系统地介绍了 nixpkgs 如何以声明式方式打包 Fish 补全脚本、配置片段、函数与插件,并提供了buildFishPluginfishPlugins作用域和wrapFish包装器三套相互配合的工具链。读完本文,你将掌握:如何在任意 Nix 包中随包分发 Fish 资源(vendor 机制)、如何编写一个带单元测试的 Fish 插件、以及如何用wrapFish在隔离环境中调试 Fish 脚本而不污染系统配置。

从包到 Shell:Fish 的 vendor 资源分发机制

Fish 与大多数 Shell 的一个关键差异在于它的插件/扩展体系是"目录约定"驱动的:任何包只要把 Fish 脚本安装到约定的 vendor 目录,Fish 就会在启动时自动加载。nixpkgs 对这套约定做了完整支持,并把它上升为打包规范。

vendor 目录约定

根据 nixpkgs 文档(doc/packages/fish.section.md),任何包都可以随包携带 Fish 的补全(completions)、配置片段(configuration snippets)和函数(functions),它们应分别安装到以下三个目录:

  • $out/share/fish/vendor_completions.d:补全文件,挂载到fish_complete_path
  • $out/share/fish/vendor_conf.d:配置片段,启动时被自动 source
  • $out/share/fish/vendor_functions.d:函数文件,由 Fish 按需自动加载

NixOS 侧如何让 vendor 目录生效

在 NixOS 上,当模块选项programs.fish.enable以及programs.fish.vendor.{completions,config,functions}.enable被设置为 true 时,这些路径会被符号链接到当前系统环境中,并随 Fish 启动自动加载。

查看 nixos/modules/programs/fish.nix 第 109-131 行的选项定义,可以看到三个开关的默认值均为true

vendor.config.enable # 是否 source 其他包提供的配置片段(默认 true) vendor.completions.enable # 是否使用其他包提供的补全文件(默认 true) vendor.functions.enable # 是否自动加载其他包提供的 fish 函数(默认 true)

其底层实现是environment.pathsToLink:当开关打开时,NixOS 会把对应 vendor 子目录链接进/run/current-system/sw(见 nixos/modules/programs/fish.nix):

pathsToLink = lib.optional cfg.vendor.config.enable "/share/fish/vendor_conf.d" ++ lib.optional cfg.vendor.completions.enable "/share/fish/vendor_completions.d" ++ lib.optional cfg.vendor.functions.enable "/share/fish/vendor_functions.d";

这意味着:任何通过environment.systemPackages安装、且按约定在$out/share/fish/vendor_*.d安装了资源的包,其 Fish 集成都会被自动启用,无需逐包手工配置。从源码结构看,这正是 nixpkgs 让"包自带 Shell 集成"成为惯例的关键——上游包(如 fzf、git、gh)发布的 vendor 目录内容,会在 Fish 启动时被无缝吸收。

插件从何而来:fishPlugins 作用域

vendor 机制解决的是"随包分发";而纯 Fish 插件则集中托管在fishPlugins作用域中。文档明确了两条归类原则:

  • 提供独立可执行程序的包,属于 nixpkgs 顶层(top level);
  • 唯一目的就是扩展 Fish 的包,属于fishPlugins作用域,并应在 pkgs/shells/fish/plugins/default.nix 中注册。

fishPlugins是典型的lib.makeScope newScope作用域(见 pkgs/shells/fish/plugins/default.nix),内部通过callPackage逐个注册插件。截至当前仓库,它收录了 40+ 个插件,例如:

  • 提示符类:puretidebobthefishbobthefisherhydrogruvbox
  • 集成类:fzffzf-fishforgitbassforeign-envnvmaws
  • 工具类:fishtape(TAP 测试运行器)、fishtape_3(3.x 版本线)、done(长命令完成通知)、git-abbrwakatime-fish

值得注意的细节:fishtape2.x 与 3.x 互不兼容,但不同插件的测试各自依赖不同版本,因此两者被同时保留(见 pkgs/shells/fish/plugins/default.nix 的注释)。这也是 nixpkgs 对生态现实的一种务实处理。此外,作用域还通过lib.optionalAttrs config.allowAliases维护了别名(如autopair-fishautopair),用于兼容旧名。

buildFishPlugin:自动完成目录搬运与测试环境搭建

buildFishPlugin是打包 Fish 插件的核心工具函数,实现在 pkgs/shells/fish/plugins/build-fish-plugin.nix。文档对其职责的概括是:自动把$src/{completions,conf,conf.d,functions}下的 Fish 脚本复制到标准 vendor 安装路径,并搭建测试环境

installPhase:四目录自动搬运

看 pkgs/shells/fish/plugins/build-fish-plugin.nix 的实现,installPhase 内定义了一个install_vendor_files辅助函数,依次执行四次搬运:

install_vendor_files completions completions # $src/completions -> $out/share/fish/vendor_completions.d install_vendor_files functions functions # $src/functions -> $out/share/fish/vendor_functions.d install_vendor_files conf conf # $src/conf -> $out/share/fish/vendor_conf.d install_vendor_files conf.d conf # $src/conf.d -> $out/share/fish/vendor_conf.d

每个源目录若存在*.fish文件,就原样复制到对应 vendor 目录;源目录为空则跳过([ -n "$(shopt -s nullglob; echo $source/*.fish)" ] || return 0)。由此,上游仓库常见的completions/functions/conf.d/布局被统一映射到 Fish 的标准 vendor 路径,打包者无需手工编写安装脚本。

默认 phase 与扩展钩子

buildFishPlugin基于lib.extendMkDerivationstdenv.mkDerivation做扩展,默认行为是:

  • unpackPhase ? ""configurePhase ? ":"buildPhase ? ":":即"无构建过程",纯粹搬运源码
  • doCheck ? checkPhase != "":只要提供了checkPhase就自动启用测试

同时它允许像普通 derivation 一样叠加preInstallpostInstallpostPatchnativeCheckInputs等标准钩子,因此对"目录布局不标准"的插件也能轻易适配。例如 pkgs/shells/fish/plugins/tide.nix 中,由于 tide 的tide configure需要额外的函数子目录,就在postInstall里补充复制:

postInstall = '' cp -R functions/tide $out/share/fish/vendor_functions.d/ '';

又如 pkgs/shells/fish/plugins/foreign-env/default.nix 在preInstall阶段把脚本中硬编码的bash替换为 Nix store 中的 bash 绝对路径,保证运行时不依赖 PATH:

preInstall = '' sed -i -e "s|bash|${lib.getExe bash}|" functions/fenv.main.fish '';

一个完整的插件打包范例:pure

文档推荐的参考示例是 pkgs/shells/fish/plugins/pure.nix,它同时演示了buildFishPlugin与 fishtape 单元测试的组合:

buildFishPlugin (finalAttrs: { pname = "pure"; version = "4.19.0"; src = fetchFromGitHub { owner = "pure-fish"; repo = "pure"; tag = "v${finalAttrs.version}"; hash = "sha256-8rxCmKu1pvBMm+/Ski7q3ikNnnX3gAqQ0jo0f2mIXrI="; }; nativeCheckInputs = [ git ]; checkPlugins = [ fishtape_3 ]; checkPhase = '' rm tests/pure_tools_installer.test.fish rm tests/_pure_uninstall.test.fish fishtape tests/*.test.fish ''; passthru.updateScript = nix-update-script { }; meta = { description = "Pretty, minimal and fast Fish prompt, ported from zsh"; homepage = "https://github.com/pure-fish/pure"; license = lib.licenses.mit; maintainers = with lib.maintainers; [ euxane ]; }; })

要点拆解:

  • src直接指向上游 Git 仓库,buildFishPlugin负责把它解包并按 vendor 约定安装
  • checkPhase中排除了两个依赖特殊环境的测试文件,然后用fishtape跑其余全部测试
  • passthru.updateScript = nix-update-script { }接入 nixpkgs 的自动更新基础设施
  • meta提供 description/homepage/license/maintainers,是入库的必要元数据

测试环境:checkPlugins 与 checkFunctionDirs

文档特别强调,buildFishPlugin会把可选的checkPhase放到一个 Fish shell 中执行,并且这个测试 Shell 内"其他已打包插件"与"包内 Fish 函数"分别由checkPluginscheckFunctionDirs注入。看 pkgs/shells/fish/plugins/build-fish-plugin.nix 的实现:

nativeCheckInputs = [ writableTmpDirAsHomeHook (wrapFish { pluginPkgs = checkPlugins; functionDirs = checkFunctionDirs; }) ] ++ nativeCheckInputs; checkPhase = '' fish "${writeScript "${finalAttrs.name}-test" checkPhase}" '';

机制拆解:

  • 两个专有参数checkPluginscheckFunctionDirs通过excludeDrvArgNames从 mkDerivation 参数中剥离,不会泄漏为 derivation 属性
  • 它们被喂给wrapFish,生成一个预装了这些插件/函数目录的 fish 可执行文件,作为nativeCheckInputs
  • checkPhase把用户写的脚本包成脚本文件后用fish执行,因此测试运行在 Fish 语法环境中,可直接使用被测试插件的函数

实际用法示例:

  • pkgs/shells/fish/plugins/done.nix:checkPlugins = [ fishtape ];然后在checkPhase里执行fishtape test/done.fish——测试依赖的 fishtape 正是通过 checkPlugins 注入的
  • pkgs/shells/fish/plugins/fishtape.nix:checkFunctionDirs = [ "./" ];,注释说明 "fishtape is introspective"——它把当前目录加入函数路径来自省测试自己;同时在preInstall里先把fishtape.fish挪进functions/子目录,以满足 buildFishPlugin 的目录约定

writableTmpDirAsHomeHook的存在则保证测试期间 Fish 的 user data 目录(如~/.cache/fish)可写,避免在只读 HOME 环境下测试失败。

wrapFish:不改环境、即插即用的隔离 Fish

wrapFish是一个围绕 Fish 的包装器,可创建一个预置了指定插件、补全、配置片段与函数的 Fish Shell,为测试 Fish 插件和脚本提供了"无需改动环境"的便捷途径。它的完整类型签名与全部参数文档见 pkgs/shells/fish/wrapper.nix,实现基于lib.makeOverridable

参数一览

参数类型说明
pluginPkgs[Derivation]通常来自fishPlugins的插件包,其 vendor 目录会被并入
completionDirs[Path]追加到fish_complete_path的补全目录
functionDirs[Path]追加到fish_function_path的函数目录
confDirs[Path]由包装后的 fish 启动时 source 的配置目录
localConfigString一段被 source 的 fish 脚本字符串
shellAliases{ N :: String }在包装后的 fish 中可用的别名
runtimeInputs[Derivation]加入包装后 fish 的PATH的依赖包

pluginPkgs外所有参数均有默认值,全部参数都可选(见 pkgs/shells/fish/wrapper.nix)。

文档中的最小示例

原文档给出的基础用法(doc/packages/fish.section.md):

wrapFish { pluginPkgs = with fishPlugins; [ pure foreign-env ]; completionDirs = [ ]; functionDirs = [ ]; confDirs = [ "/path/to/some/fish/init/dir/" ]; }

完整参数示例与工作原理

wrapper.nix 自带的 doc 示例展示了全部参数的组合用法:

wrapFish { pluginPkgs = with fishPlugins; [ pure foreign-env ]; completionDirs = [ ]; functionDirs = [ ]; confDirs = [ "/path/to/some/fish/init/dir/" ]; shellAliases = { hello = "echo 'Hello World!'"; bye = "echo 'Bye World!'; exit"; }; runtimeInputs = with pkgs; [ curl w3m ]; }

它的底层实现值得展开(pkgs/shells/fish/wrapper.nix):

  1. 路径合成vendorDir kind plugin = "${plugin}/share/fish/vendor_${kind}.d",把所有pluginPkgs的三个 vendor 目录分别并入补全路径、函数路径与配置路径;localConfigshellAliases会被写成两个临时 vendor 配置片段(vendor_conf.d/aliases.fishvendor_conf.d/config.local.fish),一并加入配置路径
  2. 别名生成shellAliases通过lib.mapAttrsToList转成alias key value行,并包裹在status is-interactive; and begin ... end中,保证只在交互式会话生效
  3. 启动脚本:最终产物是writeShellApplication生成的fish可执行脚本,启动时执行:
${fish}/bin/fish --init-command " set --prepend fish_complete_path ${...complPath} set --prepend fish_function_path ${...funcPath} set --local fish_conf_source_path ${...confPath} for c in \$fish_conf_source_path/*; source \$c; end " "$@"

即通过--init-command把补全目录/函数目录 prepend 到对应变量,再把所有配置片段 source 一遍,最后透传用户的参数("$@")。runtimeInputs则进入生成脚本的 PATH。从源码结构看,这种方式与 NixOS 模块的 vendor 机制原理一致,但发生在隔离的、用户自定义的集合内——非常适合 CI、测试夹具或临时演示环境。

面向 NixOS 用户的完整 Fish 配置

虽然本节在 fish.section.md 中着墨不多,但 nixos/modules/programs/fish.nix 是与 vendor 机制直接协同的落地模块,值得串联说明。

核心选项

  • programs.fish.enable(bool,默认 false):启用 fish 作为交互式 Shell,同时把fish/run/current-system/sw/bin/fish加入shells(见 nixos/modules/programs/fish.nix)
  • programs.fish.package:覆盖 fish 本体
  • programs.fish.generateCompletions(默认 true):从系统包的 man page 自动生成补全。实现上会从 fish 二进制中提取内嵌的create_manpage_completions.py生成器,对systemPackagesextraCompletionPackages逐一生成补全并汇总到/etc/fish/generated_completions(见 nixos/modules/programs/fish.nix),并在交互式初始化时把它插入$fish_complete_path(nixos/modules/programs/fish.nix)
  • programs.fish.extraCompletionPackages:在 generateCompletions 开启时,额外为其生成补全的包列表
  • programs.fish.shellAliases(覆盖environment.shellAliases)、shellAbbrs(fish 特色缩写)、shellFunctions(含bodymodifiers的 submodule)、shellInit/loginShellInit/interactiveShellInit/promptInit:分段注入初始化代码,生成的/etc/fish/config.fish会按"通用 / 登录 / 交互"三个阶段用__fish_nixos_*_config_sourced变量保证每个阶段只执行一次(见 nixos/modules/programs/fish.nix)

useBabelfish 与 foreign-env 二选一

NixOS 的shellInit等片段本质是 bash/POSIX 语法,fish 无法直接执行,模块提供了两条翻译路径(useBabelfish,默认 false):

  • useBabelfish = false(默认):使用fishPlugins.foreign-envfenv source命令导入(nixos/modules/programs/fish.nix)
  • useBabelfish = true:使用 pkgs/shells/fish/babelfish.nix(基于 Go 的 bash→fish 翻译器)把环境脚本翻译为原生 fish 文件后写入/etc/fish/*.fish(nixos/modules/programs/fish.nix)

回归测试验证

仓库用 NixOS 集成测试锁定了这些机制的行为(nixos/tests/fish.nix):

  • 断言programs.fish.enable = true后,/etc/fish/generated_completions/chmod.fishkill.fish等生成补全确实存在
  • 断言$fish_complete_path按预期顺序包含/share/fish/vendor_completions.d/etc/fish/generated_completions~/.cache/fish/generated_completions
  • 断言/etc/fish/nixos-env-preinit.fish/etc/fish/config.fish均被生成,且 config 文件能被fish_indent合法格式化(machine.succeed("fish_indent -c /etc/fish/config.fish")

这套测试同时验证了 vendor 链接、补全生成与配置生成三者的端到端正确性。

总结:三条路径,覆盖 Fish 生态全生命周期

回顾 nixpkgs 中 Fish 支持的整体设计,可用一句话概括:vendor 目录约定负责"随包分发",fishPlugins+buildFishPlugin负责"集中打包与测试",wrapFish负责"隔离试运行"

  • 想让已有包顺便带上 Fish 补全/函数 → 按$out/share/fish/vendor_{completions,conf,functions}.d约定安装即可,NixOS 默认自动吸收
  • 想为某个 Fish 插件建立正式包 → 放入fishPlugins作用域(pkgs/shells/fish/plugins/default.nix),用buildFishPlugin自动搬运脚本,并用checkPlugins/checkFunctionDirs+ fishtape 补齐单元测试
  • 想在开发中快速验证一组插件/脚本的组合 → 用wrapFish声明pluginPkgscompletionDirsfunctionDirsconfDirslocalConfigshellAliasesruntimeInputs,得到一个开箱即用、不污染任何全局配置的 fish

这套从"打包规范 → 构建工具 → 测试设施 → 隔离运行"的完整链路,正是 nixpkgs 对 Fish 这一"插件友好型 Shell"生态的系统性支持。

【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 12:59:38

Vue+Spring Boot二手商城实战:前后端分离与权限控制

简介:本资源是一份面向计算机专业本科生的毕业设计论文文档,聚焦大学生二手电子产品交易平台的系统化设计与实现,适用于Java Web开发、前后端分离项目实践及毕业论文参考场景。全文基于VueSpringBoot技术栈展开,涵盖平台需求分析、…

作者头像 李华
网站建设 2026/9/18 12:59:09

MySQL忘记密码重置:skip-grant-tables与8.0避坑

1. 先把问题定位清楚:你丢的到底是哪一层密码MySQL 用户密码忘记这件事,听起来像一句话就能回答的问题,但真到现场,第一件事从来不是抄命令,而是搞清楚丢的到底是哪一层。我见过太多次“我密码忘了”,结果折…

作者头像 李华
网站建设 2026/9/18 12:57:36

低成本主从机械臂6D位姿采集与VLA训练实战

1. 这不是实验室Demo,是能落地的主从机械臂数据闭环系统“低成本主从机械臂系统构建:6D位姿数据采集与VLA模型训练实战”——这个标题里藏着三个被很多人忽略的关键事实:第一,“低成本”不是指用玩具级舵机拼凑,而是指…

作者头像 李华