openpilot 发布工程全解析:从 Release Checklist 到 Prebuilt 构建流水线
openpilot 的每次版本发布都依赖一套「清单驱动 + 脚本驱动」的工程流程:人工按 发布清单 执行 staging 与 release 两阶段操作,而 build_stripped.sh 与 build_release.sh 两个脚本则分别承担「源码瘦身包」与「设备预构建二进制包」的生成。读完本文,你将掌握 openpilot 版本从 master 到生产分发的完整链路:分支管理、文件裁剪、大模型分块、onroad 测试门禁,以及各环境变量(BRANCH、RELEASE_BRANCH、INCLUDE_BIG_MODEL、PANDA_DEBUG_BUILD)的实际作用。
发布总览:两阶段门禁模型
openpilot 的发布过程分为两个明确阶段,对应 发布清单 中的「Go to staging」与「Go to release」两节:
- Staging 阶段:从 master 切出发布分支,回退风险提交,构建并推送到 staging 分支,供设备端 Jenkins 自动构建、测试;
- Release 阶段:完成回归测试矩阵(升降级、全新安装、实车驾驶)后打 tag、更新工厂配置,最终对外发布。
版本号的唯一权威来源是 version.h 中的 COMMA_VERSION 宏(当前为 "0.11.2"),发布时需要同步更新该文件与 RELEASES.md 的发布说明——这是清单中「bump version on master」一项的具体落点。RELEASES.md 按版本倒序记录了每个 release 的模型更新、硬件改进与新增车型支持,例如 0.11.2 版本的 880M 参数大模型、comma connect 实时流摄像头等。
Staging 阶段:构建发布候选分支
清单「Go to staging」一节的操作步骤为:
- 建发布跟踪 issue:在 GitHub 创建一个 issue,把该清单作为 checklist 挂在上面,用于跟踪整个发布过程;
- 创建 release master 分支:
- 从上游 master 创建一个以版本命名的分支,清单中给出的命名示例是发布
v0.10.2时创建zerotentwo(zero-ten-two 的口语化拼写); - 回退风险提交(revert risky commits),并要求与 autonomy 团队二次确认;
- 推送新分支;
- 从上游 master 创建一个以版本命名的分支,清单中给出的命名示例是发布
- 推送到 staging:
- 确认自己处于新建的 release master 分支上;
- 运行
BRANCH=devel-staging tools/release/build_stripped.sh。随后 Jenkins 会在设备端自动完成构建,运行test_onroad并更新 staging 分支;
- 在 master 上提升版本号:修改
openpilot/common/version.h与RELEASES.md; - 在 Discord 通知,并 @release crew。
build_stripped.sh:为什么是「stripped」
staging 走的是「源码推送 → 设备端构建」路线,核心逻辑在 build_stripped.sh:
- 脚本先定位仓库根目录(
git rev-parse --show-toplevel),目标目录默认用mktemp -d生成,可用TARGET_DIR环境变量覆盖; - 将仓库
.git整体复制到目标目录,然后git checkout --orphan tmp创建一个孤立项分支——即与任何历史无关联的全新分支,这是「stripped」的关键:release 分支不继承 master 的提交历史; - 通过
git submodule deinit -f --all与git rm -rf --cached .清空一切,再执行find . -maxdepth 1 ... -exec rm -rf删除工作区文件,只保留.git; - 随后调用文件过滤脚本做有选择性的拷贝(见下文),并
rm -rf .git/modules/彻底移除子模块元数据——发布产物中不允许存在任何子模块; - 对超过 95MB 的
.onnx模型文件逐个执行 file_chunker.py,把它们切分为 45MB 的分片; - 提交信息会内嵌源码 commit 哈希、commit 时间戳与构建日期,例如:
同时把哈希写入openpilot v$VERSION release date: $DATETIME master commit: $GIT_HASHgit_src_commit、时间戳写入git_src_commit_date文件,方便设备端回溯「这个 release 是由哪个 master 构建的」; - 最后做两道硬性校验:
git lfs ls-files非空则直接退出(禁止 LFS 文件进入发布包);find . -size +95M发现超大文件也报错退出——因为 GitHub 单文件上限是 100MB,留 5MB 余量是刻意设计; - 若设置了
BRANCH环境变量(staging 场景即devel-staging),则以git -c pack.window=0 -c pack.depth=0 -c pack.compression=0 push -f origin tmp:$BRANCH推送。注释里说明了这种「大 pack 直接上传」的选择:在设备端,跳过 pack 优化、写出更大对象,比消耗 CPU 去压缩更快。
release_files.py:文件裁剪的黑白名单
两个构建脚本都依赖 release_files.py 决定哪些文件进入发布包。它的逻辑是:
- 用
git ls-files -z --recurse-submodules遍历所有被跟踪的文件; - 黑名单(正则)排除以下内容:
黑名单中专门针对 LFS 与子模块的文件(.git/ .venv/ .github/workflows/ matlab.*.md .lfsconfig .gitattributes .git$ .gitmodules.lfsconfig、.gitattributes、.gitmodules),与 build 脚本的「no submodules or LFS」约束互为呼应; - 有一个空的
whitelist列表用于放行被误伤的黑名单文件; - 特殊规则:除非设置了
INCLUDE_BIG_MODEL环境变量,openpilot/selfdrive/modeld/models/big_driving_supercombo.onnx这个大模型文件会被跳过。
输出以 NUL 分隔,两个 shell 脚本通过 xargs -0 cp -pR --parents -t "$TARGET_DIR" -- 按原目录结构复制进发布目录。
大模型分块:file_chunker.py 的设计
openpilot 的 driving model 体积远超 GitHub 的 100MB 单文件上限,file_chunker.py 给出了解法:
CHUNK_SIZE = 45 * 1024 * 1024,注释明确写着「45MB, under GitHub's 50MB limit」;chunk_file把原文件切成name.chunk01ofNN形式的分片,另写一个name.chunkmanifest清单文件记录分片数量,然后删除原文件;get_existing_chunks/open_file_chunked提供还原能力:读到 manifest 就按序打开各分片,ChunkStream实现io.RawIOBase,让上层代码像读取单个文件一样顺序读取分片流;- 命令行入口
python file_chunker.py <path>可直接对单个文件执行切分,这正是 build_stripped.sh 中find openpilot/selfdrive/modeld/models -name '*.onnx' -size +95M -exec ./openpilot/common/file_chunker.py {} \;调用的形式。
注意这里 95MB 的 find 阈值与 45MB 的分片大小配合:只要文件超过 95MB 就必然被切成多个分片,保证任何单文件都不会逼近 GitHub 上限。
Release 阶段:测试矩阵与正式分发
清单「Go to release」一节要求在正式发布前完成以下测试:
- 从上一个 release 升级到新 release(update from previous release -> new release);
- 从新 release 降级回上一个 release(update from new release -> previous release);
- 用
openpilot-test.comma.ai域名做全新安装(fresh install); - 全新安装后实际上车驾驶验证(drive on fresh install);
- 确认发布包中没有子模块和 LFS(no submodules or LFS);
- 检查 Sentry、MTBF 等线上指标;
- 生产环境的 stress test 通过。
测试通过后按顺序执行:发布博客 → git reset --hard origin/release-mici-staging → 打 tag 并推送(git tag v0.X.X <commit-hash> && git push origin v0.X.X)→ 创建 GitHub Release → 在 openpilot.comma.ai 上做最终测试安装 → 更新工厂预配置(factory provisioning)→ 关闭 milestone 和 issue → 在 Discord、X 等平台宣布。
build_release.sh:设备端 Prebuilt 构建
与 staging 的「推源码、设备端现场构建」不同,正式 release 走的是 build_release.sh 的预构建(prebuilt)路线:构建产物直接提交为 git 对象推送给设备,设备端只需 git reset --hard 即可完成升级,省去设备端的编译耗时。脚本的关键环节:
- 前置条件:必须设置
RELEASE_BRANCH环境变量(支持逗号分隔多个分支,脚本会for branch in ${RELEASE_BRANCH//,/ }展开,统一映射为release-mici-staging:<目标分支>的 refspec 强推); - worktree 构建目录:以
git worktree add --detach --no-checkout /data/openpilot创建独立构建区,将 HEAD 切到release-mici-staging分支(git symbolic-ref HEAD),并用git read-tree --empty清空暂存区,得到一个干净的空分支工作区; - 文件拷贝:与 stripped 流程相同,经 release_files.py 过滤后
cp -pR --parents入构建目录; - CPU 频率释放:把各
cpufreqpolicy 的scaling_max_freq提到硬件最大值以加速编译,注释说明后续test_onroad.py会重置频率; - 两级编译:
scons构建主体;若设置了INCLUDE_BIG_MODEL,则断言big_driving_tinygrad.pkl.chunkmanifest存在;- panda 固件构建:未设置
PANDA_DEBUG_BUILD时执行CERT=/data/pandaextra/certs/release RELEASE=1 scons panda/生成带 release 证书的正式固件;否则普通scons panda/构建调试固件(注释提示ALLOW_DEBUG=1会启用 experimental longitudinal 等调试特性);
- 子模块断言:
git submodule--helper list若发现任何子模块立即exit 1,把清单中「no submodules or LFS」的要求固化成脚本门禁; - 产物清理:删除
.a、.o、.os、.pyc、__pycache__、.sconsign.dblite、Jenkinsfile、tools/release/自身,以及openpilot/selfdrive/modeld/models/*.onnx*(ONNX 模型不进 prebuilt 包,运行时用的是 tinygrad pkl 格式); - prebuilt 标记:
touch prebuilt创建空文件作为「此分支是预构建产物」的标记; - 提交与压缩策略:版本号从 version.h 提取后以
openpilot v$VERSION提交;同样使用core.compression=0,注释解释「写出大对象比在设备上压缩更快」; - 发布前最终测试:
RELEASE=1 ./openpilot/selfdrive/test/test_onroad.py,通过后以pack.window=0 pack.depth=0 pack.compression=0的无优化 pack 强推到各目标分支。
test_onroad.py:release 门禁的实质
openpilot/selfdrive/test/test_onroad.py 是发布流程中反复出现的最后防线(staging 由 Jenkins 触发、prebuilt 构建末尾直接运行)。从源码看它覆盖三类约束:
- CPU 预算:
MAX_TOTAL_CPU = 350(8 核总预算),并对约 30 个进程逐一设定基线配额(如controlsd16%、ui40%、pandad40%),注释明确「每个进程至少 8%,总占用不得超过 MAX_TOTAL_CPU」; - 信号时序:
TIMINGS字典对can、carState、modelV2、controlsState等约 18 类消息给出rtols(max/min 波动上限)与rsd(相对标准差)两个统计阈值; - 日志体积:
LOGS_SIZE规定每 segment 的qlog.zst≤ 0.5MB、rlog.zst≤ 8.1MB、摄像头 HEVC 流 ≤ 76.5MB 等预算。
任何一项超预算都会让测试失败,从而阻断 release 分支的推送——这正是清单中「no submodules or LFS」「stress test passes in production」等验收项的自动化对应物。
辅助脚本与版本约定
tools/release/ 下还有三个小型配套脚本,值得单独说明:
- identity.sh:导出固定的 git 身份(
Vehicle Researcher <user@user@comma.ai>形式),保证 release 分支上由脚本产生的提交具有一致的作者信息,便于审计区分「人写的提交」与「构建产生的提交」; - check-dirty.sh:
git status --porcelain非空即退出 1,用于校验构建后工作区是否干净; - check-submodules.sh:遍历
git submodule status --recursive,对每个子模块 fetch 后验证其 pin 的 commit 位于origin/master上(tinygrad_repo被显式跳过),否则报错退出——保证发布基于各子模块主分支的真实状态,而非游离 commit。
另外 pack.py 与本目录 README 的发布清单无直接关系,它是一个基于 zipapp 的通用打包工具:给定一个模块名(如 openpilot.system.ui.spinner)与入口函数,把 openpilot/ 下按扩展名白名单(.png、.py、.ttf、.capnp、.json、.fnt、.mo、.po)筛选出的文件打成可独立执行的 zipapp,供 CI 或单机场景运行单个入口脚本。
使用方式与适用前提
把上述内容收敛为可操作的命令视图(均以仓库根目录为工作目录):
| 场景 | 命令 | 说明 |
|---|---|---|
| 推送 staging 源码包 | BRANCH=devel-staging tools/release/build_stripped.sh |
生成孤立项分支并推送到 devel-staging,由 Jenkins 在设备端构建并运行 test_onroad |
| 生成 staging 包到指定目录 | TARGET_DIR=/tmp/staging BRANCH=devel-staging tools/release/build_stripped.sh |
TARGET_DIR 覆盖默认 mktemp -d |
| 构建 prebuilt release | RELEASE_BRANCH=<目标分支列表> tools/release/build_release.sh |
需在有 scons 构建环境、panda 证书目录 /data/pandaextra/certs/release 的构建机上运行,构建目录固定为 /data/openpilot |
| 包含大模型 | 上述命令加 INCLUDE_BIG_MODEL=1 |
把 big_driving_supercombo.onnx 纳入发布包;prebuilt 构建会断言其 chunk manifest 存在 |
| 调试版 panda 固件 | 上述命令加 PANDA_DEBUG_BUILD=1 |
使用不带 release 证书的固件构建路径 |
适用前提与限制:
- 整套流程假设操作者拥有目标仓库(含
release-mici-staging等分支)的写权限,且设备端已接入 Jenkins 的自动构建与test_onroad链路; build_release.sh硬编码了/data/openpilot构建目录与/data/pandaextra/certs/release证书路径,这是 comma 构建机的约定,本地无此环境时脚本无法原样运行;- 发布产物被强制约束为「无子模块、无 LFS、单文件 < 100MB」,大模型一律以 file_chunker.py 的 45MB 分片形式存在;
- 版本号必须与 version.h 一致并在 RELEASES.md 中补记,清单中的「bump version」步骤只发生在 master,release 分支本身不改版本号。
小结
openpilot 的发布体系可以概括为三层:清单(README.md)定义人工门禁与责任分工;脚本(build_stripped.sh、build_release.sh、release_files.py、file_chunker.py)把「无子模块、无 LFS、大小受限」等约束固化为可执行的自动校验;测试(test_onroad.py 的 CPU 预算、信号时序与日志体积门禁)则在 staging 与 release 两个节点各拦截一次。这种「人工 checklist + 脚本硬门禁 + 自动化测试」的组合,使得升级/降级、全新安装与实车驾驶等高风险环节都有明确的验证入口,是车载软件发布流程中值得参考的工程范式。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00