结论:openpilot 的发布构建有两条 Git 主链:build_stripped.sh 生成精简源分支,build_release.sh 在固定设备工作树内编译并产出设备预编译分支。文件选择由 release_files.py 按 git ls-files 的受跟踪路径清单过滤,实际复制的是工作树文件,因此本地未提交修改可能进入产物。路径存在、blob 不超限、LFS 已安装,都不证明模型内容与哈希正确。本文为固定源码快照 c8fb906 的静态阅读,未执行任何发布命令。
两条主链的入口与时序
build_stripped.sh 的默认目标目录由 mktemp 生成后又被删除重建,显式指定的 TARGET_DIR 同样会被删除;它复制源 .git、新建 orphan tmp、移除旧文件及子模块目录,写入源提交元数据后提交,再检查子模块、LFS 与尺寸,只在有 BRANCH 时强推。它不运行 SCons,也不检查运行时模型是否可用。
build_release.sh 则在固定的设备工作树目录中操作:先强制移除 worktree,失败则整体删除;随后添加共享引用的 worktree、删除本地 staging 引用、把当前 HEAD 指向它并清空 index。它运行 scons,再按 PANDA_DEBUG_BUILD 选择 release 证书构建或 debug panda;INCLUDE_BIG_MODEL 时仅用 test -f 检查三种模型路径。最后删除中间产物与发布工具、生成 prebuilt、提交、做尺寸检查、运行 test_onroad.py,成功后对一个或多个分支强推。
两条链路共同的数据流是:git ls-files -z --recurse-submodules 取清单,正则过滤,xargs -0 cp -pR --parents 复制,再在目标目录提交。
| 阶段 | 精简源分支 | 设备预编译分支 |
|---|---|---|
| 调用 | build_stripped.sh,可由 BRANCH 决定是否推送 |
build_release.sh,必须给 RELEASE_BRANCH |
| 目标目录 | 临时目录或 TARGET_DIR,都会被删除重建 |
固定的设备工作树目录,先移除 worktree |
| 构建与模型 | 不运行 SCons,不检查运行时模型 | scons,INCLUDE_BIG_MODEL 时仅 test -f 检查模型路径 |
| 提交前后 | 写源提交元数据、提交、检查子模块/LFS/尺寸 | 提交、尺寸检查、test_onroad.py、强推 |
文件选择与共享辅助
release_files.py 排除 .git/、.venv/、CI 工作流、.gitmodules、.gitattributes 与默认 .lfsconfig 等;未设置 INCLUDE_BIG_MODEL 时还按 big_ 前缀排除大模型。选择依据是跟踪路径清单,实际复制的是工作树文件,所以已跟踪但已从工作树删除的路径可能让复制失败。check-dirty.sh 是单独的辅助门,并不在两个构建脚本内被调用。
共享辅助各管一段:setup_lfs.sh 在 INCLUDE_BIG_MODEL 时重建匹配大模型的 .gitattributes、设置目标 Git 的 hooksPath 并运行 git lfs install --local --force,但它不负责下载模型实体;identity.sh 固定发布提交的作者与提交者,覆盖调用者身份环境变量。
| 组件/函数 | 作用与边界 |
|---|---|
release_files.py |
按受跟踪路径清单过滤;实际复制工作树文件,未在脚本内强制检查 clean |
setup_lfs.sh |
重建大模型 .gitattributes 与 hooks;不下载模型实体 |
check_file_sizes.sh |
拒绝当前 HEAD 中超过 95 MiB 的 blob;在提交之后运行 |
check-submodules.sh |
检查子模块固定提交可达;未设 set -e,且不是两个构建脚本的内联门 |
check-dirty.sh |
检查工作树 git status --porcelain;Jenkins 常规 onroad 阶段调用,发布脚本不调用 |
pack.py |
按扩展名白名单 zipapp 打包;不覆盖发布主链的测试与尺寸门 |
门禁层次与元数据
check_file_sizes.sh 的尺寸检查发生在提交之后,不能证明外部 LFS 对象已就绪。SConstruct 以 .gitmodules 是否存在推断 release,这与文件选择器主动排除 .gitmodules 相配,但它不是签名或测试证明。version.py 把 prebuilt 文件当作预构建标记,而构建元数据另按 channel 判断 tested 与 release,两者是不同维度,不能因为有 prebuilt 就认定来自受测分支。
在流水线侧,Jenkins 的设备阶段调用 build_release.sh,目标包括 staging 设备;nightly 分别构建普通与 PANDA_DEBUG_BUILD=1 版本;Chestnut nightly 另设 INCLUDE_BIG_MODEL=1 并以对应车端测试作为参数。车端测试位于提交和尺寸检查之后、强推之前,而精简脚本本身不运行该测试。
可见风险与插件化约束
第一类是破坏性路径与引用:目标目录、固定设备路径、本地 staging 引用和目标远端分支都可能被覆盖,这些入口只应由受控 CI 身份调用,并对目标路径与分支做独立白名单校验。第二类是来源一致性:目前按 Git 跟踪名单拷贝工作树,而非直接从已审计提交导出,插件构建应绑定主仓与子模块提交、干净状态、依赖锁和产物哈希,避免「提交 ID 一样、拷贝内容不同」。
第三类是 LFS 与大模型:.lfsconfig、.gitattributes 和模型实体是不同对象,路径存在、blob 不超限、LFS 已安装都不证明内容正确。第四类是安全固件模式:PANDA_DEBUG_BUILD 使 panda 构建走非 release 证书分支,nightly-dev 与普通 nightly 不能互作安全权限等价物,插件框架不能允许普通软件插件选择或绕过固件安全模式。
验证范围
上述结论来自固定归档的脚本与直接调用点;未运行 Git worktree、LFS、SCons、panda 构建、设备测试、强推或安装。README 里的发布步骤是人工清单,不能当作流水线已执行证据。后续还需逐项审查 Jenkins 设备锁与凭据,以及构建产物的实际内容,并在隔离 Git 仓库中验证工作树复制与 LFS 规则。强推前应保留独立审核与可回滚来源,pack.py 只是 Python 分发辅助,不覆盖 CAN 安全、模型运行或车端依赖。
常见问题
发布脚本复制的文件来自提交还是工作树?
来自工作树。选择依据是 git ls-files 的受跟踪路径清单,实际复制的是工作树文件,所以本地未提交修改可能进入产物,而已跟踪但已从工作树删除的路径可能让复制失败;check-dirty.sh 并不在两个构建脚本内被调用。
95 MiB 尺寸门能证明 LFS 模型已就绪吗?
不能。check_file_sizes.sh 只拒绝当前 HEAD 中超过 95 MiB 的 blob,且检查发生在提交之后;它不能证明外部 LFS 对象已就绪,路径存在、blob 不超限、LFS 已安装都不证明模型内容与哈希正确。
PANDA_DEBUG_BUILD 构建能当发布固件用吗?
不能。该开关使 panda 构建走非 release 证书分支,nightly-dev 与普通 nightly 不能互作安全权限等价物。插件框架也不应允许普通软件插件选择或绕过固件安全模式。
pack.py 打出的产物是整车可部署包吗?
不是。pack.py 只按扩展名白名单用 zipapp 打包,没有拷贝独立子模块,也没有运行发布主链的测试与尺寸门,因此只是 Python 分发辅助,不能视作整车可部署产物。
延伸阅读
- Ed25519 签名加 96 字节事务记录:让 MCU 升级不怕中途断电 —— 对照本文 LFS 与固件内容校验的不足。
- 原生 Git 安装器:分支、缓存与激活 —— 承接本文 worktree、引用切换与激活语义。
- 更新器:staging 与激活的分层 —— 解释本文预编译分支与后续车端验证的关系。
- 安全测试基线与回放证据 —— 说明为何尺寸门与车端测试各验证不同性质。
参考资料
- 项目 02 ——
openpilot-research/RELEASE_BUILD_FILE_SELECTION_LFS_PUSH_CONTRACT_DEEP_DIVE.md(取用了两条主链时序、文件选择器、共享辅助门、可见风险与验证范围)。 - 项目 02 ——
openpilot-research/BUILD_METADATA_VERSION_PROVENANCE_DEEP_DIVE.md(关联的版本与构建元数据专题,用于区分 prebuilt 标记与分支分类,本文未展开其探针细节)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复