深入 Lean 01:Lake 项目结构与依赖管理
Lake 是 Lean 4 的官方构建系统与包管理器,随 lean 工具链一同分发。形式化方法系列的第 07-08 篇已覆盖 elan 安装与 lake new 起手骨架;本篇从那个骨架向前推进,聚焦生产级项目需要的部分:toolchain 锁定、Mathlib4 依赖引入、lake-manifest.json 版本固定、多目标 lakefile 设计,以及可直接套用的 GitHub Actions CI 配置。
lean-toolchain:锁定工具链版本
项目根目录的 lean-toolchain 是一行纯文本文件,内容就是 toolchain 字符串:
1 | |
elan 读取该文件,自动下载并激活对应版本的 lean 与 lake。项目目录内的所有 lake 命令都在该 toolchain 下执行,与机器全局版本无关。
版本字符串的两种合法格式:
| 格式 | 示例 | 说明 |
|---|---|---|
| 正式 release | leanprover/lean4:v4.12.0 |
推荐生产项目使用 |
| nightly | leanprover/lean4:nightly-2025-01-15 |
追踪最新特性时使用,稳定性较低 |
Mathlib4 本身也带一个 lean-toolchain,声明它当前支持的 Lean 版本。引入 Mathlib 时,本项目的 lean-toolchain 必须与 Mathlib 的版本匹配,否则 lake build 会因 ABI 不兼容报错。确认方式:
1 | |
lakefile.lean:项目定义 DSL
lakefile.lean 用 Lean 4 本身编写,Lake 提供一组 DSL 关键字。最小有意义的 lakefile:
1 | |
package «MyProject»:整个项目的顶层标识符,名称对应文件系统中的库目录。require mathlib from git ...:声明对 Mathlib4 的 Git 依赖;@后面是 tag 或 commit hash。lean_lib «MyProject»:声明一个 Lean 库目标,Lake 会按MyProject/目录下的文件自动发现模块。lean_exe «myproject»:声明一个可执行目标,root指向入口模块(Main.lean)。@[default_target]:标记默认构建目标,裸跑lake build时自动构建该目标。
多目标项目
一个 lakefile 可以包含多个库和可执行:
1 | |
lake build MyProject 只构建 MyProject 库;lake build 只构建带 @[default_target] 的目标。
目录结构:模块发现规则
Lake 的模块发现基于文件路径到模块名的直接映射:
1 | |
MyProject.lean 是库的根模块,通常只做集中 import:
1 | |
其他项目 require 本项目时,import MyProject 会拉取这里列出的所有子模块。
lake-manifest.json:依赖锁文件
首次执行 lake update 后,Lake 生成 lake-manifest.json,记录所有依赖的精确 commit hash:
1 | |
rev 字段是 Git commit hash,而非浮动 tag。即使上游 v4.12.0 tag 被移动(这种情况罕见但存在),本项目重新 clone 后也会还原到完全相同的依赖快照。
lake-manifest.json 必须提交 git,语义与 yarn.lock / Cargo.lock 相同:它确保团队成员和 CI 得到完全一致的依赖树。
引入 Mathlib4:缓存优化
Mathlib4 包含约 16 万条定理,完整编译需要数小时。正确做法是下载预编译的 .olean 缓存:
1 | |
lake exe cache get 从 Mathlib 社区的 S3 存储桶拉取 .olean 文件,匹配规则是 lean-toolchain 版本 + Mathlib commit hash 的组合键。若缓存命中,整个 Mathlib 的"编译时间"降至几十秒(主要是网络传输)。
引入 Mathlib 后,可立即在 Lean 文件里验证:
1 | |
#print axioms 展示一个定理依赖的公理集合,是确认证明"真正封闭"的常用探针。Nat.add_comm 依赖 propext、Classical.choice、Quot.sound——这是 Lean 4 / Mathlib 的标准公理基,可接受。
将光标停在 example 内任意位置,InfoView 右栏显示当前目标:
1 | |
exact Nat.add_comm n m 消解后 InfoView 显示 No goals,证明完成。
核心 Lake 命令
1 | |
lake clean 与 lake clean -rfl 的区别:前者只删构建产物,依赖包缓存保留(下次 build 不需重新下载);后者全部清除,适合排查缓存污染问题。
GitHub Actions CI 配置
1 | |
--default-toolchain none防止 elan 在 CI 里装一份多余的默认 toolchain;实际版本由lean-toolchain文件驱动。actions/cache的 key 绑定lake-manifest.json的 hash,确保依赖锁变更时缓存自动失效。lake exe cache get在本地构建目录不含 Mathlib olean 时下载预编译版本,与本地工作流一致。
若项目不依赖 Mathlib,删去 lake exe cache get 一行即可,CI 时间通常在 1-3 分钟内。
汇总
| 文件 / 命令 | 职责 |
|---|---|
lean-toolchain |
锁定 Lean + Lake 版本,elan 自动读取 |
lakefile.lean |
声明包、依赖、库、可执行 |
lake-manifest.json |
依赖 commit hash 快照,必须提交 git |
lake update |
解析依赖,更新 manifest |
lake exe cache get |
下载 Mathlib 预编译缓存,避免本地重编 |
lake build |
构建 default_target |
lake clean |
删除构建产物,保留依赖包 |
CI actions/cache |
用 manifest hash 做 key,跨 CI 复用依赖包 |
给读者的练习
-
从零创建一个
ProofSandbox项目,lean-toolchain锁定到leanprover/lean4:v4.12.0,在 lakefile 里require mathlib,执行lake update与lake exe cache get,最后lake build零错误。记录整个过程的耗时,对比有无lake exe cache get的差异。 -
在已引入 Mathlib 的项目里,新建
MyProject/Explore.lean,写下面两行并观察 InfoView 输出:1
2#check @List.map
#print axioms List.length_map解释
List.length_map的公理依赖与Nat.add_comm相比多了什么(如果有的话)。 -
修改 lakefile,在同一个项目里同时声明
lean_lib «MyProject»和一个不带@[default_target]的lean_exe «verify»(入口为Verify.lean,内容只写#eval "verify ok")。确认lake build只构建库,lake build verify构建可执行并打印"verify ok"。
参考资料
- Lake 文档:https://github.com/leanprover/lake
- Mathlib4 贡献指南:https://leanprover-community.github.io/contribute/index.html
- Mathlib4 文档:https://leanprover-community.github.io/mathlib4_docs/
- Theorem Proving in Lean 4:https://leanprover.github.io/theorem_proving_in_lean4/
- elan GitHub:https://github.com/leanprover/elan
- 本系列前置篇:形式化方法 07-08——准备 Lean 4 实验环境、在 Lean 4 中证明经典命题
