首页
/ openpilot 发布工程全解析:从 Release Checklist 到 Prebuilt 构建流水线

openpilot 发布工程全解析:从 Release Checklist 到 Prebuilt 构建流水线

2026-09-04 22:28:50作者:宗隆裙

openpilot 的每次版本发布都依赖一套「清单驱动 + 脚本驱动」的工程流程:人工按 发布清单 执行 staging 与 release 两阶段操作,而 build_stripped.shbuild_release.sh 两个脚本则分别承担「源码瘦身包」与「设备预构建二进制包」的生成。读完本文,你将掌握 openpilot 版本从 master 到生产分发的完整链路:分支管理、文件裁剪、大模型分块、onroad 测试门禁,以及各环境变量(BRANCHRELEASE_BRANCHINCLUDE_BIG_MODELPANDA_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」一节的操作步骤为:

  1. 建发布跟踪 issue:在 GitHub 创建一个 issue,把该清单作为 checklist 挂在上面,用于跟踪整个发布过程;
  2. 创建 release master 分支
    • 从上游 master 创建一个以版本命名的分支,清单中给出的命名示例是发布 v0.10.2 时创建 zerotentwo(zero-ten-two 的口语化拼写);
    • 回退风险提交(revert risky commits),并要求与 autonomy 团队二次确认;
    • 推送新分支;
  3. 推送到 staging
    • 确认自己处于新建的 release master 分支上;
    • 运行 BRANCH=devel-staging tools/release/build_stripped.sh。随后 Jenkins 会在设备端自动完成构建,运行 test_onroad 并更新 staging 分支;
  4. 在 master 上提升版本号:修改 openpilot/common/version.hRELEASES.md
  5. 在 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 --allgit 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_HASH
    
    同时把哈希写入 git_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 决定哪些文件进入发布包。它的逻辑是:

  1. git ls-files -z --recurse-submodules 遍历所有被跟踪的文件;
  2. 黑名单(正则)排除以下内容:
    .git/
    .venv/
    .github/workflows/
    matlab.*.md
    .lfsconfig
    .gitattributes
    .git$
    .gitmodules
    
    黑名单中专门针对 LFS 与子模块的文件(.lfsconfig.gitattributes.gitmodules),与 build 脚本的「no submodules or LFS」约束互为呼应;
  3. 有一个空的 whitelist 列表用于放行被误伤的黑名单文件;
  4. 特殊规则:除非设置了 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.shfind 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 频率释放:把各 cpufreq policy 的 scaling_max_freq 提到硬件最大值以加速编译,注释说明后续 test_onroad.py 会重置频率;
  • 两级编译
    1. scons 构建主体;若设置了 INCLUDE_BIG_MODEL,则断言 big_driving_tinygrad.pkl.chunkmanifest 存在;
    2. 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.dbliteJenkinsfiletools/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 个进程逐一设定基线配额(如 controlsd 16%、ui 40%、pandad 40%),注释明确「每个进程至少 8%,总占用不得超过 MAX_TOTAL_CPU」;
  • 信号时序TIMINGS 字典对 cancarStatemodelV2controlsState 等约 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.shgit 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.shbuild_release.shrelease_files.pyfile_chunker.py)把「无子模块、无 LFS、大小受限」等约束固化为可执行的自动校验;测试test_onroad.py 的 CPU 预算、信号时序与日志体积门禁)则在 staging 与 release 两个节点各拦截一次。这种「人工 checklist + 脚本硬门禁 + 自动化测试」的组合,使得升级/降级、全新安装与实车驾驶等高风险环节都有明确的验证入口,是车载软件发布流程中值得参考的工程范式。

登录后查看全文
热门项目推荐
相关项目推荐