先回答一个问题:从“手里只有源码”到“能读本地站点、能做离线验收”,每个命令究竟交付什么?根目录 Makefile 不是一个自动推导完整依赖图的构建系统。它提供几个动作入口,把工作交给 Python、Bash、Go 和 pnpm;各入口之间的关键交接需要读者自己看清。
make setup-build → build/inputs/
↓
make guest → dist/spine.wasm
↓
make check → Go/Python 检查结果
make site → site/dist/
↓
make read → 127.0.0.1 上的静态阅读服务
make offline → 在 Linux/Ubuntu 禁网子进程中消费上述已有输入
setup-build 和 guest 是两个阶段,不是同一个目标的别名:前者允许下载并准备本地工具链,后者只使用已经存在的输入生成 Guest。site 负责生成静态站点,read 只读取已生成的目录。
1. 先看目标、依赖和 recipe
Makefile · L1–L58 · 打开完整源码
# Small local entrypoints. Only setup-build downloads toolchain inputs.
.PHONY: setup setup-build guest check-all bench-analyze bench-smoke bench-full
setup:
python3 tools/setup-guest.py
check-all: check
python3 -m unittest discover -s tools -p 'test_setup_guest.py'
cd learning/go-patterns && go test ./... -count=1 && go vet ./...
python3 -m unittest discover -s experiments/memory -p 'test_*.py'
python3 experiments/memory/run.py test
cd experiments/reusable-code && go test ./... -count=1 && go run . offline
bench-analyze:
python3 experiments/memory/run.py analyze
bench-smoke:
python3 experiments/memory/run.py smoke
bench-full:
python3 experiments/memory/run.py full
.PHONY: setup-build guest
setup-build:
python3 tools/setup-build-inputs.py
guest:
bash build-guest.sh
.PHONY: walkthrough demo explain prefix prepared prepared-cow check
walkthrough:
@bash demos/core.sh all
demo:
@go run ./cmd/demo -source examples/order.py
explain:
@go run ./cmd/demo -source examples/order.py -plm -show-transformed
prefix:
@go run ./cmd/demo -source examples/order.py -prefix -show-transformed
prepared:
@go run ./cmd/demo -source examples/order.py -prepared-copy -prefix
prepared-cow:
@go run ./cmd/demo -source examples/order.py -prepared-cow -prefix
check:
go test ./... -count=1 -v
PYTHONPATH=guest python3 -m unittest guest/test_plm.py guest/test_prefix.py
go vet ./...
.PHONY: site read offline
site:
cd site && pnpm build
read:
@bash tools/read-local.sh
offline:
@bash tools/offline-check.sh
Makefile 中的目标名是调用入口,冒号右边是 Make 层面的 prerequisite,缩进的命令是 recipe。check-all 声明了 prerequisite check,因此先执行核心检查,再检查教学与实验模块。setup 从仓库解压 Guest;bench-analyze 复算历史数据,bench-smoke / bench-full 采集 Linux 内存数据。其余入口如 setup-build、guest、site、read 和 offline 没有写 prerequisite;.PHONY 只说明这些名字代表动作,不要拿同名文件的时间戳决定是否运行。也就是说,make guest 不会因为 setup-build 写在上面就自动先跑它,make read 也不会因为 site 存在就自动重建站点。
这条规则解释了构建链里的显式边界:
| 入口 | 直接调用者 | 消费的输入 | 主要交付物 | 网络预期 |
|---|---|---|---|---|
setup-build |
Python 准备脚本 | 锁文件和远程归档 | build/inputs、CPython 交叉构建输出 |
首次或缓存失效时可联网 |
guest |
build-guest.sh |
build/inputs、C/Python 源文件 |
dist/spine.wasm |
脚本不下载 |
check |
Go、Python、vet | Go module cache、Guest artifact、测试源码 | 检查结果 | 正常不应需要网络 |
site |
pnpm build |
site/ 源码和 Node 依赖 |
site/dist |
依赖须先准备 |
offline |
离隔离子进程 | 上面已经存在的输入 | 禁网条件下的验收结果 | 子进程被阻断 |
read |
tools/read-local.sh |
site/dist/index.html |
本地 HTTP 服务 | 不下载、不重建 |
表中的“交付物”是阶段之间传递的状态。比如 setup-build 成功后,guest 才有机会找到 clang、CPython 静态库和 wasi-vfs;guest 成功后,check 才能走真实 Guest 测试;site 成功后,read 才有首页可提供。
2. 一次真实的构建顺序
make setup-build 调用 python3 tools/setup-build-inputs.py。Python 脚本负责平台检查、锁文件读取、归档下载与 SHA-256 校验、解压和 CPython WASI 构建;它将输入放到默认的 build/inputs,也可由 SPINE_BUILD_INPUTS 指定另一处目录。
随后执行 make guest,Make 调用 bash build-guest.sh。这个脚本检查同一输入根目录中的 clang 和 CPython 主静态库,然后编译 guest/runtime.c,链接 CPython/WASI 静态库,打包标准库和项目 Python 文件,验证 Wasm,最后才写入 dist/spine.wasm。输入根目录如果前后不一致,准备阶段的成果不会自动出现在 Guest 构建使用的位置。
make check 是检查阶段,不是 Guest 构建阶段。它顺序执行 go test ./... -count=1 -v、带 PYTHONPATH=guest 的两个 Python 单测和 go vet ./...。Go recipe 的非零退出会阻止下一条 recipe 继续执行;因此前一步失败不会被后续检查掩盖。它仍然消费当前文件树和已有 dist/spine.wasm,不会把 guest/plm.py 等文件自动重新打进 Wasm。
make site 在该条 recipe 的 shell 中进入 site/ 并执行 pnpm build,产物写入 site/dist。make read 只是启动 tools/read-local.sh,它检查首页后使用 Python 标准库 HTTP server 读取这个目录。
3. 演示目标和重建判断
walkthrough 是当前面试主入口,调用 demos/core.sh all,依次展示普通调用、PLM、prefix 与隔离。它编排现有实现和真实测试,不修改核心 runtime;详见 核心演示。
demo、explain、prefix、prepared 和 prepared-cow 都是 go run ./cmd/demo 的不同参数组合。-source examples/order.py 传入的是运行时用户程序;-plm、-prefix、-prepared-copy 和 -prepared-cow 选择运行方式或展示方式,不会触发 Guest 重新链接。
因此改动后的处理可以按输入归类:
- 改 Go Host:通常重新执行对应的
go run、定向 Go 测试或go vet即可; - 改
examples/*.py或-source指向的用户程序:这是运行时输入,不需要重建 Guest; - 改
guest/runtime.c、guest/bootstrap.py、guest/plm.py、guest/prefix.py或链接参数:重新执行make guest,再做真实 Guest 检查; - 改站点文档或布局:执行
make site,需要阅读时再执行make read; - 只改讲义且不改变发布站点输入:不需要下载工具链或重建大型 Wasm。
这些判断来自实际调用路径,而不是目标名的相似性。
4. 离线入口放在哪里
make offline 只调用 tools/offline-check.sh。脚本在隔离子进程中运行网络探针、make check、站点构建和几个 demo;它不把 offline 声明成 offline: check site,也不在禁网环境中调用 setup-build。完整离线检查假定 Go module cache、Node/pnpm 依赖、dist/spine.wasm 和 Guest 构建输入都已在线准备。
在 Linux/Ubuntu 路径中,真正的 socket 阻断由 user/network namespace 完成;Go 的 GOPROXY=off 等变量只是生态内的约束。若缺依赖,离线流程应明确失败,而不是删除隔离或临时联网把缺口补上。
5. 读完后应能回答
- 为什么
make guest不会自动调用make setup-build? - 改
guest/plm.py后,为什么go run和make guest分别证明不同的事情? - 为什么
make read成功只证明已有site/dist能被服务,不能证明站点是最新的?
下一步读构建输入,跟随锁文件和准备脚本进入真实的 Linux x86_64 本地工具链;再读Guest 构建看输入怎样变成 Wasm。本文覆盖当前 Makefile 全部非空源码行,未运行构建、测试、站点服务器或离线检查。