fix: harden artifact rebuilds and profile verification
hvisor-build 是独立的 hvisor 源码取得、构建、产物发布和运行编排工具。默认源码锁定到 edfa0b4e266183d1a1b4938491ced70e2372da66,也可以用本地只读输入覆盖:
hvisor-build
edfa0b4e266183d1a1b4938491ced70e2372da66
cargo build --release target/release/hvisor-build list target/release/hvisor-build riscv64-linux plan --hvisor-source ../hvisor --hvisor-tool-source ../hvisor-tool target/release/hvisor-build riscv64-linux build --hvisor-source ../hvisor --hvisor-tool-source ../hvisor-tool target/release/hvisor-build riscv64-linux run --hvisor-source ../hvisor --hvisor-tool-source ../hvisor-tool target/release/hvisor-build riscv64-linux test --hvisor-source ../hvisor --hvisor-tool-source ../hvisor-tool
list 同时显示 profile 声明状态和当前 data root 中最新的本地验证结果;没有 TestRecord 时显示 not-tested。plan 只读取 profile 并展示声明式 DAG,不取得源码、不解析镜像且不创建 workspace; doctor 只检查宿主工具、Docker daemon/image 和 KVM。只有 build/run/test 等执行命令才解析完整 Execution Identity 并准备受管源码与 workspace。
list
not-tested
plan
doctor
不指定 --source 时,工具从固定远端 revision 创建受管快照。.cache/ 保存可重建数据, work/<build-id>/ 是唯一可修改的源码副本,output/<profile>/<build-id>/ 保存 ArtifactSet 和 build-record.json,节点日志写入 logs/。这些目录均带有 ownership marker;用户 hvisor checkout 不会被 reset、clean 或直接构建。
--source
.cache/
work/<build-id>/
output/<profile>/<build-id>/
build-record.json
logs/
每个 work/<build-id>/workspace-record.json 都明确记录该 workspace 对应的 profile 和 Build ID。新 workspace 在发布前写入记录;旧的受管 workspace 在下次使用时自动补写,已有记录 若与当前身份不一致则拒绝复用。
work/<build-id>/workspace-record.json
清理分为当前构建和历史回收两层。clean 删除当前 build identity 的可重建 workspace 与节点 cache stamp,但保留已经发布的 ArtifactSet、日志和共享下载缓存:
clean
target/release/hvisor-build clean --profile riscv64-linux
prune 回收历史 build identity,默认按 profile 保留最近一个,并默认保留发布产物。 prune --all --keep 0 还会清理跨 profile 的 .cache/build-artifacts/ 节点产物缓存;普通 clean 和保留 identity 的 prune 不会删除共享下载、Git 或节点产物缓存。 节点 stamp 按 <profile>/<node-id> 保存;clean 和 prune --keep 0 会删除该 profile 的 节点 stamp。 建议先用 --dry-run 查看范围;只有显式传入 --delete-outputs 才删除发布产物:
prune
prune --all --keep 0
.cache/build-artifacts/
<profile>/<node-id>
prune --keep 0
--dry-run
--delete-outputs
target/release/hvisor-build prune --profile riscv64-linux --dry-run target/release/hvisor-build prune --profile riscv64-linux --keep 1 target/release/hvisor-build prune --all --keep 1 target/release/hvisor-build prune --profile riscv64-linux --keep 0 --delete-outputs
两种清理都会先校验 hvisor-build ownership marker;日志还会校验所属的受管 profile 目录和 build-id 格式。它们不会清理用户提供的 hvisor/hvisor-tool checkout。prune 不解析或取得 源码,因此不会为了回收历史目录而创建新的 build workspace。
每个 profile 的 [sources.*] 是 zone 构建依赖的来源清单,明确记录 kind、url 和 revision。Git source 的 revision 必须是完整 40 位 commit;可选 version 保存便于阅读的 tag 名。构建任务直接读取这些字段,不在 Rust 实现中隐藏版本。本地 --hvisor-source 和 --hvisor-tool-source 会覆盖两个仓库的默认获取方式;本地仓库的 commit 与 dirty digest 都会进入 build identity。
[sources.*]
kind
url
revision
version
--hvisor-source
--hvisor-tool-source
Profile schema v5 使用 [[nodes]] 直接声明构建 DAG;每个节点拥有独立的 needs、runner、commands 或 script、resources 和 cache。commands 适合精确 argv, script 适合条件、trap、管道和生成文件,并固定以 bash -euo pipefail -c 执行。两者互斥。 调度器不预设 base、prepare、 zone 或 deploy 等节点名,因此新的构建图不需要修改 Rust 调度代码。
[[nodes]]
needs
runner
commands
script
resources
cache
bash -euo pipefail -c
[[zones]] 只描述 zone 拓扑和元数据。system、kernel_source、rootfs_source 与 contains 均由 profile 声明,调度器不会根据这些值选择 builder、推导文件名或决定部署方式。 Zone0、Zone1 和额外的 ZoneN 都只是 profile 自己定义的 DAG 节点与缓存边界。
[[zones]]
system
kernel_source
rootfs_source
contains
Zone0 的构建进一步拆成独立节点和缓存边界:
zone0-system ──┬─> assemble-zone0 ──> hypervisor ──┐ └─> control-tools ─────────────────┼─> deploy-zone1 zone1-system ──────────────────────────────────────┘
这些只是 shipped profile 采用的节点职责,不是 Rust 识别的 action。每个缓存输出只能由一个 节点声明;会被客户机部署修改的最终 rootfs 归最终部署节点所有,避免部署后反向使上游缓存失效。
runner 会原样执行 profile 声明的 commands 或内联 script,不存在 @task、recipe、 内部 action 或 PlatformBackend 分派。平台、系统、Zone、镜像布局和启动策略全部位于单个 profile TOML 中;不允许 profile 依赖额外的编排 .sh 文件。重复脚本优先于把策略抽回 Rust。
@task
PlatformBackend
.sh
内联脚本可调用两个通用且不含平台策略的源码原语:
hvisor-build source fetch <source-name> <destination> hvisor-build source fetch <source-name> --extract-to <directory> [--strip-components N] hvisor-build source checkout <source-name> <destination>
fetch 保留 SHA-256 校验、共享缓存锁、损坏删除重下、.download 续传和重试;checkout 通过 .cache/git/ 中按仓库 URL 寻址的共享 bare cache 浅获取锁定 revision,再物化独立 checkout; 它也会纠正 origin、递归初始化 submodule,并识别受管本地源码快照。 归档只需展开时使用 --extract-to;工具直接从共享缓存读取 tar.xz、tar.gz 或 zip,不在 workspace 中保留归档副本。内容先展开到同目录临时树,成功后才替换目标;失败时保留原目录。 归档的展开目录和可选 --strip-components 仍由 profile 明确声明。
fetch
.download
checkout
.cache/git/
--extract-to
--strip-components
执行准备阶段会把 profile 中的 Docker tag 解析为不可变 image ID;该映射进入 build identity、 节点缓存 key 和 BuildRecord,后续 Docker 命令也直接使用解析后的 ID,避免可变 tag 在解析后漂移。 缓存验证需要在源码、profile、镜像和 data root 不变时连续执行两次相同的 build。每次 build 都会进入 DAG 并执行发布流程;第一次生成产物和 stamp,第二次各构建节点应打印 cache hit。缓存是透明的执行优化,不提供手动绕过选项;修改本地源码或更换 Docker 镜像会 生成新的 build identity。节点成功后还会把声明的文件产物发布到 .cache/build-artifacts/<input-digest>/;另一个 profile 或新 workspace 的节点具有相同输入、 命令、runner、依赖和源码锁时,可校验并恢复这些产物,打印 shared cache hit。 shared 节点产物和 ArtifactSet 在恢复或使用前始终校验 SHA-256。
build
cache hit
.cache/build-artifacts/<input-digest>/
shared cache hit
ZoneN 不使用调度器内置的命名约定。profile 需要显式声明它的构建、部署命令和缓存路径,例如 zone2-system -> deploy-zone2;rootfs3.ext4、zone2.json 或其他名称只是该 profile 的选择。 因此新增 Zone2、其他系统或其他平台无需修改 Rust 调度代码。
zone2-system -> deploy-zone2
rootfs3.ext4
zone2.json
仓库内的 .claude/skills/designing-profile-driven-builds/ 固化了这一职责边界。修改 profile、 平台配方、ZoneN、runner 或缓存声明时,可用该 skill 检查是否把策略重新写回调度器。
.claude/skills/designing-profile-driven-builds/
固定工具链和 rootfs 压缩包保存在 .cache/downloads/,由不同 build identity 共享;下载使用 .download 断点文件,成功后才发布到缓存。普通 fetch 会物化归档文件,--extract-to 则直接 从缓存展开。受管 workspace、发布产物、下载物化和节点缓存恢复的文件复制统一使用 reflink 优先、普通复制兜底。 Docker runner 还将 Cargo registry/git 数据和 rustup 下载、toolchain 分别保存在 .cache/cargo/ 与 .cache/rustup/,避免隔离容器在重试或新 identity 中重复下载 Rust 依赖。
.cache/downloads/
.cache/cargo/
.cache/rustup/
ArtifactSet 先在 workspace 的 staging/ 中完整复制并校验,随后按 build identity 原子发布; 同一 build identity 再次构建成功时会原子更新对应 ArtifactSet。 BuildRecord schema v5 记录完整 SourceLock、zone/contains 拓扑、hvisor 与本地 hvisor-tool 源码、dirty digest、工具二进制摘要、节点命令、环境 白名单、输入/输出摘要以及 Docker image ID。run 不依赖易变的 build cache,只接受匹配当前 identity 且摘要有效的发布集;受管 workspace 产物损坏时会从发布集恢复。
staging/
run
test 同样只使用经过验证的发布集,并按 profile 的串口步骤自动等待启动标志、发送命令和 检查 guest 输出。完整控制台日志与 run-verified/failed JSON 记录保存在 logs/<profile>/<build-id>/tests/;无论成功、超时或匹配失败都会强制清理测试容器。
test
run-verified
failed
logs/<profile>/<build-id>/tests/
hvisor 和 hvisor-tool 使用相同的源码策略:本地构建直接反映本地仓库修改,远程构建严格使用 profile 锁定的 revision。hvisor-build 不对这两个仓库应用隐式兼容补丁;需要的修复必须进入对应 源码仓库,并由 profile 更新到包含修复的 revision。
开发检查:
cargo test cargo clippy --all-targets -- -D warnings
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802047560号
hvisor-build
hvisor-build是独立的 hvisor 源码取得、构建、产物发布和运行编排工具。默认源码锁定到edfa0b4e266183d1a1b4938491ced70e2372da66,也可以用本地只读输入覆盖:list同时显示 profile 声明状态和当前 data root 中最新的本地验证结果;没有 TestRecord 时显示not-tested。plan只读取 profile 并展示声明式 DAG,不取得源码、不解析镜像且不创建 workspace;doctor只检查宿主工具、Docker daemon/image 和 KVM。只有 build/run/test 等执行命令才解析完整 Execution Identity 并准备受管源码与 workspace。不指定
--source时,工具从固定远端 revision 创建受管快照。.cache/保存可重建数据,work/<build-id>/是唯一可修改的源码副本,output/<profile>/<build-id>/保存 ArtifactSet 和build-record.json,节点日志写入logs/。这些目录均带有 ownership marker;用户 hvisor checkout 不会被 reset、clean 或直接构建。每个
work/<build-id>/workspace-record.json都明确记录该 workspace 对应的 profile 和 Build ID。新 workspace 在发布前写入记录;旧的受管 workspace 在下次使用时自动补写,已有记录 若与当前身份不一致则拒绝复用。清理分为当前构建和历史回收两层。
clean删除当前 build identity 的可重建 workspace 与节点 cache stamp,但保留已经发布的 ArtifactSet、日志和共享下载缓存:prune回收历史 build identity,默认按 profile 保留最近一个,并默认保留发布产物。prune --all --keep 0还会清理跨 profile 的.cache/build-artifacts/节点产物缓存;普通clean和保留 identity 的prune不会删除共享下载、Git 或节点产物缓存。 节点 stamp 按<profile>/<node-id>保存;clean和prune --keep 0会删除该 profile 的 节点 stamp。 建议先用--dry-run查看范围;只有显式传入--delete-outputs才删除发布产物:两种清理都会先校验 hvisor-build ownership marker;日志还会校验所属的受管 profile 目录和 build-id 格式。它们不会清理用户提供的 hvisor/hvisor-tool checkout。
prune不解析或取得 源码,因此不会为了回收历史目录而创建新的 build workspace。每个 profile 的
[sources.*]是 zone 构建依赖的来源清单,明确记录kind、url和revision。Git source 的revision必须是完整 40 位 commit;可选version保存便于阅读的 tag 名。构建任务直接读取这些字段,不在 Rust 实现中隐藏版本。本地--hvisor-source和--hvisor-tool-source会覆盖两个仓库的默认获取方式;本地仓库的 commit 与 dirty digest 都会进入 build identity。Profile schema v5 使用
[[nodes]]直接声明构建 DAG;每个节点拥有独立的needs、runner、commands或script、resources和cache。commands适合精确 argv,script适合条件、trap、管道和生成文件,并固定以bash -euo pipefail -c执行。两者互斥。 调度器不预设 base、prepare、 zone 或 deploy 等节点名,因此新的构建图不需要修改 Rust 调度代码。[[zones]]只描述 zone 拓扑和元数据。system、kernel_source、rootfs_source与contains均由 profile 声明,调度器不会根据这些值选择 builder、推导文件名或决定部署方式。 Zone0、Zone1 和额外的 ZoneN 都只是 profile 自己定义的 DAG 节点与缓存边界。Zone0 的构建进一步拆成独立节点和缓存边界:
这些只是 shipped profile 采用的节点职责,不是 Rust 识别的 action。每个缓存输出只能由一个 节点声明;会被客户机部署修改的最终 rootfs 归最终部署节点所有,避免部署后反向使上游缓存失效。
runner 会原样执行 profile 声明的
commands或内联script,不存在@task、recipe、 内部 action 或PlatformBackend分派。平台、系统、Zone、镜像布局和启动策略全部位于单个 profile TOML 中;不允许 profile 依赖额外的编排.sh文件。重复脚本优先于把策略抽回 Rust。内联脚本可调用两个通用且不含平台策略的源码原语:
fetch保留 SHA-256 校验、共享缓存锁、损坏删除重下、.download续传和重试;checkout通过.cache/git/中按仓库 URL 寻址的共享 bare cache 浅获取锁定 revision,再物化独立 checkout; 它也会纠正 origin、递归初始化 submodule,并识别受管本地源码快照。 归档只需展开时使用--extract-to;工具直接从共享缓存读取 tar.xz、tar.gz 或 zip,不在 workspace 中保留归档副本。内容先展开到同目录临时树,成功后才替换目标;失败时保留原目录。 归档的展开目录和可选--strip-components仍由 profile 明确声明。执行准备阶段会把 profile 中的 Docker tag 解析为不可变 image ID;该映射进入 build identity、 节点缓存 key 和 BuildRecord,后续 Docker 命令也直接使用解析后的 ID,避免可变 tag 在解析后漂移。 缓存验证需要在源码、profile、镜像和 data root 不变时连续执行两次相同的
build。每次build都会进入 DAG 并执行发布流程;第一次生成产物和 stamp,第二次各构建节点应打印cache hit。缓存是透明的执行优化,不提供手动绕过选项;修改本地源码或更换 Docker 镜像会 生成新的 build identity。节点成功后还会把声明的文件产物发布到.cache/build-artifacts/<input-digest>/;另一个 profile 或新 workspace 的节点具有相同输入、 命令、runner、依赖和源码锁时,可校验并恢复这些产物,打印shared cache hit。 shared 节点产物和 ArtifactSet 在恢复或使用前始终校验 SHA-256。ZoneN 不使用调度器内置的命名约定。profile 需要显式声明它的构建、部署命令和缓存路径,例如
zone2-system -> deploy-zone2;rootfs3.ext4、zone2.json或其他名称只是该 profile 的选择。 因此新增 Zone2、其他系统或其他平台无需修改 Rust 调度代码。仓库内的
.claude/skills/designing-profile-driven-builds/固化了这一职责边界。修改 profile、 平台配方、ZoneN、runner 或缓存声明时,可用该 skill 检查是否把策略重新写回调度器。固定工具链和 rootfs 压缩包保存在
.cache/downloads/,由不同 build identity 共享;下载使用.download断点文件,成功后才发布到缓存。普通fetch会物化归档文件,--extract-to则直接 从缓存展开。受管 workspace、发布产物、下载物化和节点缓存恢复的文件复制统一使用 reflink 优先、普通复制兜底。 Docker runner 还将 Cargo registry/git 数据和 rustup 下载、toolchain 分别保存在.cache/cargo/与.cache/rustup/,避免隔离容器在重试或新 identity 中重复下载 Rust 依赖。ArtifactSet 先在 workspace 的
staging/中完整复制并校验,随后按 build identity 原子发布; 同一 build identity 再次构建成功时会原子更新对应 ArtifactSet。 BuildRecord schema v5 记录完整 SourceLock、zone/contains 拓扑、hvisor 与本地 hvisor-tool 源码、dirty digest、工具二进制摘要、节点命令、环境 白名单、输入/输出摘要以及 Docker image ID。run不依赖易变的 build cache,只接受匹配当前 identity 且摘要有效的发布集;受管 workspace 产物损坏时会从发布集恢复。test同样只使用经过验证的发布集,并按 profile 的串口步骤自动等待启动标志、发送命令和 检查 guest 输出。完整控制台日志与run-verified/failedJSON 记录保存在logs/<profile>/<build-id>/tests/;无论成功、超时或匹配失败都会强制清理测试容器。hvisor 和 hvisor-tool 使用相同的源码策略:本地构建直接反映本地仓库修改,远程构建严格使用 profile 锁定的 revision。hvisor-build 不对这两个仓库应用隐式兼容补丁;需要的修复必须进入对应 源码仓库,并由 profile 更新到包含修复的 revision。
开发检查: