nav-map
raku-pm 上手与排障地图(贡献者速查)
目标:让你改代码或排查问题时,第一眼就知道该打开哪个文件。 配套详细设计见
docs/architecture.md(更密,适合通读);本文件是「速查表」。
0. 一句话心智模型
把 raku-pm 想成 一辆自行车 + 一堆可拆的摩托车附件:
自行车本体(核心链路,每次必走):
bin 薄壳 → CLI 派发 → Resolver 解析 → Repository 取包 → Store 缓存 → Installer 装进 site/ → Ledger/Lock 记账。约 7 个模块,是「装个包」真正需要的全部。摩托车附件(重型子系统):事务/世代回滚、跨进程文件锁、单后端 HTTP(curl)、测试超时+失败复验、
verify/doctor诊断、自我升级、作者侧发布、本地库探测。
你「觉得复杂、不敢动」的根因:附件太多,且最大的那个模块以前叫 Reporter 却装着写命令(名字骗人)。
1. 改动 X / 出了问题 → 去哪个文件
| 你想改 / 症状 | 文件 | 行数(约) |
|---|---|---|
命令列表、别名、help 文案 | lib/RakuPM/CliSpec.rakumod + lib/RakuPM/Help.rakumod + lib/RakuPM/CLI.rakumod(派发) | 297 / 718 / 970 |
解析 / 依赖 / 版本约束(Foo:ver<1.0+>) | lib/RakuPM/Resolver.rakumod + Version + Spec + Repository/Matching | 586 / 225 / 130 / 65 |
| 仓库接入 / 索引 / 下载 / 代理 / 某索引拉不动 | Repository/Ecosystem + HTTP(+HTTP/Tinyish) + Repositories | 595 / 294 / 285 |
| 安装落盘 / 多版本共存 / 与 zef 共存 / bin wrapper | Installer + Installer/ShellTemplates + Distribution | 1248 / 162 / 828 |
| 事务 / 世代回滚 / 账本 | Installer::Generations + Ledger | 141 / 152 |
provides / 模块 ↔ 磁盘(幽灵安装) | Distribution:@MODULE-EXTS(扩展名唯一口径)→ scan-provides / provides-with-paths / provides-from-disk;护栏在 Store.put;历史条目由 verify/doctor 第 ⑥ 段查 | — |
| 路径拼装 / 内容寻址 store / 路径安全 | Fs(唯一净化入口 path-component:穿越类 die / Windows 保留类 百分号编码;assert-under 第二道防线)+ Store | 224 / 596 |
| 测试执行(串行)/ 超时 / 进度 / 失败复验 | Client/Tester | 511 |
| 写操作(安装 / 升级 / 重装 / 测试 / 构建 / 卸载 / 回滚 / 回收) | lib/RakuPM/Client.rakumod | 1415 |
| 只读查询 / 诊断(list / which / why / depends / rdepends / locate / browse / info / store / doctor / verify / search / list-available / env / lock / generations)+ native 库检查 | lib/RakuPM/Client/Query.rakumod | 1600 |
| 清理 / 全清 | Cleaner + Client/Flusher | 381 / 203 |
| 锁 / 并发写入 / 子进程令牌 | FileLock | 231 |
| 发布 / 打包 / bump / 登录 | Author | 1236 |
| native 库检测 | NativeLib | 503 |
| 自我升级 | Client/SelfManager | 426 |
| 平台差异(路径/进程/后缀/强杀) | Platform(唯一出处,别处不准写) | 390 |
| 启动入口 / 早期旗标 | bin/raku-pm.raku + CLI.run-cli | 13 |
| 门禁 / 测试纪律 | tools/run-suite.raku + t/ xt/ | — |
命名约定(0.79.4 起):原
Client::Reporter改名Client::Query。 它只装只读查询 + 诊断 + native 检查;uninstall/rollback/autoremove三个写命令已挪回Client(编排器)。所以「改查询去 Query,改写操作去 Client」。路径安全不变式(0.87.0 起,别拆):发行版
name/version是包自己声明的 (发布者可控),任何把它们拼进路径的地方都必须先过RakuPM::Fs.path-component—— 唯一净化入口,非法即 die。当前 7 处:①Store.path-for;②Client/Tester日志目录; ③Repository/Ecosystem下载缓存;④Installer临时文件名;⑤Author归档名 / 发布落点; ⑥Client/Git.!fetch-source的fetch --to输出目录;⑦Client/Git.!git-cache-key的 git 缓存目录名(0.88.6 补;地址可来自恶意发行版的git-deps)。(另有Author.new一处冗余防御——那里的名字已被正则校验,留着是防日后放宽正则。) ★assert-under对..天然无效(字面前缀比对 + Raku 不折叠..)⇒ 净化只能 在生成路径时做,别指望事后断言。模块源扩展名只有一份清单(0.87.1 起,别内联):
.rakumod/.pm6/.pm定义在RakuPM::Distribution的@MODULE-EXTS,跨模块经module-extensions取。 这份清单原先写了两遍(provides-with-paths、!walk-source-files)且都漏了.pm—— 于是整个.pm时代的包全部幽灵安装。t/ghost-provides.t有一条静态守卫盯着 「别处不许再出现'.pm6'字面量」。 落盘前再用assert-under断言"在根之内"。加一处新路径 = 加一处净化; 回归守卫在t/path-safety.t(含"旧实现的逃逸落点必须不存在"的端到端断言)。
2. 端到端:raku-pm install Foo 走了什么
你敲命令
→ bin/raku-pm.raku(13 行薄壳,只解析 --target/--offline 早期旗标)
→ RakuPM::CLI.run-cli 派发到 install
→ Client 抢跨进程文件锁(FileLock,防并发写坏 store)
→ 解析仓库配置 + 目标前缀(Repositories / Prefix)
→ Resolver:找能提供 Foo 的发行版,按约束挑最高版,递归展开依赖 → 拓扑排序
→ 对每个发行版串行执行:
fetch (Repository 下载 tar → cache/dist)
build (Builder 跑 Build.pm,native 包在此编 .so/.dll)
store (Store 算 MD5 指纹,存进 store/)
test (Tester 串行跑 t/*.t;失败则整树回滚)
install (Installer 交给 CUR::Installation 装进 site/,写 bin wrapper)
→ 整段包在事务里:成功落一个「世代」,失败回滚到执行前
→ Ledger 写 installed.json + Lock 写 raku-pm.lock
→ Query 负责把「计划 / 结果」打印出来(show-* 系列)一句话:核心链路是「解析 → 取 → 存 → 装 → 记」五步;灰色附件全是「让这五步更稳/更快/更可诊断」才套上去的。
3. 模块分层与「哪些可砍」
| 类别 | 模块 | 对「自行车」是否必要 |
|---|---|---|
| 核心链路 5 模块 | Resolver / Repository / Store / Installer / Ledger | ✅ 必留(本体) |
| 多版本共存(CUR)+ 与 zef 共存 | Installer 内 | ✅ 必留,Raku 包管理命脉 |
| 平台抽象 | Platform | ✅ 必留且已是最佳实践(唯一出处) |
| 事务 / 世代回滚 | Installer::Generations + Ledger | ⚠️ 高级但值——半装态是真痛点 |
| verify / doctor | 在 Query | ✅ 值得留,就是「坏了能查」的工具 |
| 文件锁 | FileLock | ⚠️ 防御性,去掉会真损坏 store |
| HTTP 层 | HTTP + Repository | ✅ 已简化(0.82.0):单后端 curl,可插拔注册表/回退已删 |
| 测试执行 | Client/Tester | ✅ 已简化(0.80.0):并发机制 + 预热已删,只留串行 + 失败复验 |
| 作者侧 | Author(1228 行) | ✅ 不必动(0.84.0 判定):模块面本就独立(独立文件 + 独立命名空间 + CLI 懒加载),只剩命令面;命令面保留,只补了 help <组名> 导航 |
| native 库检测 | NativeLib(503 行) | ✅ 不必动(0.84.1 修订):不只是 native 查询命令的后端 —— 安装链每次都调它查 :from<native> / :from<bin> 依赖(见 Client/Git 的 !check-native-libs)。实测加载成本 ≈ 加载任意模块(~276ms vs ~275ms,而空 raku 仅 ~4ms),故「改按需 require」收益≈0 |
| self-upgrade 路径 | Client/SelfManager | ✅ 已简化(0.83.0):三路径→两路(删「就地 pull 工作树」,收尾消息去重) |
4. 命令分类(41 个命令里,大多数很便宜)
查询 / 诊断(都在
Query):listwhichwhydependsrdependslocatebrowseinfostoredoctorverifysearchlist-availableenvlockgenerations。写操作(都在
Client):installupgradereinstalltestbuilduninstallrollbackautoremoveself-upgradepublish(publish 在Author)。
5. 本地怎么验证改动没搞坏
raku tools/run-suite.raku --docs-only # 秒级:版本号/provides/个人痕迹
raku tools/run-suite.raku --no-xt # 跑 t/ 全量(不跑 xt/)
raku tools/run-suite.raku # 全量(含 xt/,较慢)
raku tools/run-suite.raku --no-timings # 同上,但不打印每用例耗时与最慢排行门禁默认给出耗时小结(每用例墙钟 + 最慢 15 + 实际墙钟 + 并行加速比 + raku 启动基线
占比):判断"慢在进程数还是慢在某个测试"用它,别靠感觉。
提交前务必跑 --no-xt(与预提交钩子同一口径)。套件默认并行(按 CPU 核数、上限 8),
且 t/ 与 xt/ 合成一个池、按历史耗时降序调度(LPT):长任务先起、短任务填空槽,
专治"批尾只剩一两个长任务在跑"的空转;任务分到 $jobs 条道上各跑各的(不设层屏障,
谁先空谁接下一个)。跑前只预热一次 precomp,跑完对失败用例串行复验。
别加 --jobs=1(那是把多核当单核用)。历史耗时缓存在 ~/.raku-pm/suite-timings.json,
只是排序提示(删掉不影响判定,RAKUPM_SUITE_TIMINGS 可改路径)。
门禁纪律见 docs/architecture.md 与 tools/run-suite.raku 顶部注释。