nixpkgs 中 Lean 4 的构建体系:buildLakePackage、leanPackages 作用域与 Lake 集成实践
2026/9/17 23:36:53 网站建设 项目流程

nixpkgs 中 Lean 4 的构建体系:buildLakePackage、leanPackages 作用域与 Lake 集成实践

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

本文基于 nixpkgs 手册的 Lean 4 章节,系统讲解 nixpkgs 对 Lean 4 语言生态的支持:如何用leanPackages.buildLakePackage以无网络(hermetic)方式构建依赖 mathlib 的 Lean 项目,lakeHash参数如何支持 Nix 与 Lake 两种依赖解析的混合迁移,以及leanPackages作用域的工具链替换、LSP 二进制补丁机制和开发 shell 的使用方式。读完本文,你可以为 Lean 4 项目写出可复制的 Nix 构建表达式,并理解其与 2020–2025 年旧版 per-module 集成架构的本质区别。

概览:nixpkgs 中的两套 Lean 4 入口

nixpkgs 为 Lean 4 提供两个互补的入口:

  • leanPackages:一个独立的包集合(scope),内置自己的 Lean toolchain,并附带一组精选库——包括完整的 mathlib 依赖树;
  • pkgs.lean4:一个独立的 Lean 4 编译器 derivation,供在leanPackages包集合之外单独使用(例如通过overrideScope注入自定义工具链)。

这两者组合后,Lean 4 在 nixpkgs 中的定位与 Haskell 生态中haskellPackages+ghc的组合类似:作用域内所有库共享同一 toolchain,构建时复用已编译产物,保证 hermetic。

buildLakePackage构建 Lean 4 项目

构建 Lean 4 项目的标准入口是leanPackages.buildLakePackage。手册给出的最小可用表达式如下:

leanPackages.buildLakePackage { pname = "my-project"; version = "0.1.0"; src = ./.; leanDeps = with leanPackages; [ mathlib ]; lakeHash = null; # all deps nix-managed; set to lib.fakeHash for Lake-managed deps }

各参数的含义:

参数说明
pname/version项目的名称与版本号,用于生成 store 路径
src项目源码目录;此处./.指向包含 lakefile 的项目根
leanDepsNix 管理的依赖列表。用with leanPackages; [ mathlib ]的写法即从leanPackages作用域引入 mathlib(及其整个依赖树由作用域内部保证)
lakeHash控制 Lake 侧 fixed-output 拉取的行为,三种取值见下节

Hermetic 构建的实现机制

buildLakePackage的依赖来源分两处声明:依赖写在lakefile(供 Lake 识别)和Nix 表达式(供 Nix 识别)。其 hermetic 性来自一条明确的优先级规则:

依赖的.olean文件是 Lake library facet 的默认构建产物。leanDeps提供的 Nix 管理库直接复用这些.olean文件、不再重新编译buildLakePackage通过lake --packages把它们注入构建,该标志优先于 Lake 自身的依赖解析,从而产生 hermetic 构建。

从源码结构看,这意味着 lakefile 中为这些库声明的 fetch 配置在构建时不会触发网络访问——Lake 的包解析结果被 Nix 提供的.olean产物透明替换。

lakeHash:异构依赖解析与渐进式迁移

buildLakePackage在 nixpkgs 所有 builder 中属于独树一帜的设计:它支持异构依赖解析(heterogeneous dependency resolution),即 Nix 与 Lake 的依赖管理可以在同一个 derivation 内按包粒度组合

  • leanDeps:Nix 管理的依赖;
  • lakeHash:Lake 管理的依赖(走 fixed-output derivation 固定下来)。

lakeHash的三种取值语义:

  1. lakeHash = null(默认值):声明所有依赖都是 Nix 管理的,构建时不执行任何 fixed-output 拉取。这是完全 nix 化后的终态;
  2. lakeHash = lib.fakeHash:这是一个"探测"模式。构建会失败并报出 expected hash——该哈希对应的 fixed-output derivation 精确钉住了Lake 原本会拉取的全部依赖、减去 Nix 已管理的部分。把报告出的哈希回填到lakeHash即可固定 Lake 侧依赖;
  3. 具体的 hash 字符串:正式钉住 Lake 管理的依赖集合。

由此得到一条明确的渐进式采用路径(on-ramp)

  • Nix 管理依赖按名称优先(name precedence)。因此把某个依赖从lakeHash固定集合移入leanDeps会改变 expected hash——这正是探测模式的自校验机制;
  • 一个大型 Lean 项目可以先把不常变动的上游依赖用lakeHash钉住、把核心库(如 mathlib)迁入leanDeps,然后逐个库迁移,每次迁移后重新运行lib.fakeHash探测并更新哈希,直到lakeHash = null的完全 Nix 管理状态。

项目根必须存在lake-manifest.json

无论依赖走哪条解析路径,项目根都必须存在lake-manifest.json。当所有依赖均为 Nix 管理时,一个空清单即可满足 Lake:

{"version":"1.1.0","packagesDir":".lake/packages","packages":[]}

这对应 Lake 1.1.0 版本的清单格式,packages数组为空即表示没有任何 Lake 侧固定依赖。

开发 shell:nix develop下的工具链

nix develop中,作用域化的lean4buildLakePackage与 hermetic 构建使用同一套 toolchain,保证"开发时验证的构建"与"CI 中执行的构建"一致。需要注意一个行为差异:

开发 shell 中 Lake 的正常依赖解析是可用的——Lake 可能从网络拉取未被leanDeps覆盖的依赖。这与 Nix 开发 shell 的常规行为一致(shell 不承诺 hermetic),因此本地开发体验与 vanilla Lake 项目基本对等,nix develop/nix-shell在功能上对齐原生 Lake 开发流程。

leanPackages作用域:lib.makeScope与工具链替换

leanPackages实现为lib.makeScope,作用域内自带一个lean4属性。这意味着:替换作用域的lean4会传播到作用域内所有包以及buildLakePackage——与haskellPackages替换ghc的语义相同。标准写法:

leanPackages.overrideScope ( self: super: { lean4 = myCustomLean4; } )

myCustomLean4可以直接使用顶层的pkgs.lean4(独立编译器入口)或任何等价 derivation。

LSP 可用的关键:leanPackages.lean4的二进制补丁

leanPackages提供的lean4经过二进制补丁(binary-patched)的版本,目的是确保 Lean language server 能发现被包装的lake而非未包装的版本。其根源是 Lake 的serve子命令有一个棘手的调用模式:

  1. serveIO.appPath(Rust 运行时提供的可执行文件真实路径)推导出LAKE路径;
  2. 然后在派生出的环境(spawned environment)中无条件设置LAKE环境变量;
  3. 这一机制绕过任何 wrapper——即使你在PATH里放了包装版lake,language server 启动的子进程拿到的仍是 store 中未包装路径。

补丁的做法是改写二进制中的 store 路径引用,使IO.appPath发现机制直接命中包装后的lake。其收益是完整的 LSP 集成——包括InfoView(依赖 Lean 专属协议扩展),且不需要污染用户的项目目录(比如写一个假lake脚本进项目)。

缓存失效语义:让位于 Nix 的依赖模型

文档明确了一个重要的语义替换:

leanPackages.lean4取代了 Lake 对位于/nix/store/中依赖的内置缓存失效(cache invalidation)逻辑,完全交由 Nix 的依赖模型处理。Lake 的trace 校验(检查编译器 "hash"、平台和包身份)被 Nix 已有的保证优雅地吸收(subsumed)

用更直白的话说:Lake 本会自己校验"上次编译依赖的编译器版本、目标平台、包身份是否变化"来决定是否复用缓存;而在 Nix 下,.olean产物本身就在 store 里按内容哈希寻址,compiler/platform/依赖任一变化都会改变产物路径,因此这类一致性检查无需重复执行。缓存一致性责任"委托给整合了流畅 Nix 集成的编排者"——即 Nix 求值与构建系统本身。这也是前文"leanDeps.olean直接复用、不重编译"能成立的底层原因。

编辑器支持

Lean 4 的编辑器集成包PATH中发现工具链,因此只要nix developleanPackages提供的lean4/lakePATH里即可工作:

  • EmacsemacsPackages.naelemacsPackages.nael-lsp(前者基于 eglot,后者基于 lsp-mode,均可经 MELPA 安装),提供 Lean 4 支持,包括通过 eldoc 显示证明状态(proof state);
  • VSCode(unfree)/ VSCodiumvscode-extensions.leanprover.lean4

与早期 Lean 4 Nix 集成的关系(2020–2025 旧架构)

熟悉旧版集成的读者应注意:buildLakePackage遵循完全不同的架构

  • 旧架构(2020–2025,per-module derivation):在求值期通过 import-from-derivation(IFD)发现依赖——那是一次调和声明式包管理与细粒度构建语义的大胆尝试,但最终被 Nix 自身的求值模型所掣肘,Lean 上游已将其移除(见 Lean 上游提交 535435955b482176e8d62a54deebcacdec0827db);
  • 新架构(buildLakePackage:把Lake 当作构建驱动(build driver),Nix 只负责**包级(package-level)**边界。IFD 的求值期依赖发现被消除,依赖发现发生在构建期(lake --packages注入),求值保持廉价且可缓存;nix develop/nix-shell则负责与原生 Lake 开发体验的功能对等。

小结与要点核对

场景用法
构建依赖 mathlib 的项目leanPackages.buildLakePackage+leanDeps = with leanPackages; [ mathlib ]
全部 Nix 管理lakeHash = null(默认),项目根放空lake-manifest.json
部分 Lake 管理lakeHash = lib.fakeHash探测 → 回填真实 hash → 逐步把库迁入leanDeps
自定义 toolchainleanPackages.overrideScope (self: super: { lean4 = myCustomLean4; }),传播到所有包与buildLakePackage
本地开发nix develop,注意 shell 中 Lake 仍可能走网络解析
编辑器Emacsnael/nael-lsp;VSCode/VSCodiumleanprover.lean4,均从PATH发现工具链

以上内容的依据是手册章节 doc/languages-frameworks/lean4.section.md(属于 doc/languages-frameworks/index.md 的"语言和框架"一章)。适用前提:文中所有leanPackagesbuildLakePackagelakeHash行为均以该文档描述为准,且lake-manifest.json的格式示例对应 Lake 1.1.0 清单版本;若使用其他 Lake 版本,需确认其清单格式兼容性。

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询