双域联合量产发布:智能驾驶与座舱软件的版本管理与CI落地指南

发布时间:2026/8/29 6:56:03
双域联合量产发布:智能驾驶与座舱软件的版本管理与CI落地指南 “双车进国可以发了”第一眼看到这个标题很多人可能会以为是在说某款新车型引入国内的消息。但这里不聊具体品牌和车型而是把它当作一个软件项目的代号来拆解双车指的是智能驾驶域控制器和智能座舱域控制器这两个软件域进国指的是这两套软件栈从原型阶段走向国内量产项目时必须完成的本地化适配可以发了则是指经过版本冻结、联合验证和发布评审之后软件正式具备发布条件。这个场景在智能汽车行业非常典型。单看一个域控制器开发节奏往往还可控一旦把智驾和座舱两个域放到同一台车、同一个时间窗口里交付难度会成倍增加。两个域的代码仓库、编译链、中间件、验证工具都不一样却又必须用同一个软件版本组合去支撑整车下线。任何一边出问题最后都可能导致整个项目延期。这篇文章想重点讲清楚三件事双域软件量产发布到底难在哪里一个可以复用的版本管理和持续集成方案怎么落地从台架到实车验证时哪些环节最容易出问题。如果你正在做智驾、座舱、车载软件平台或者整车软件集成这篇内容可以当成一份工程模板来参考。1. 双车量产交付为什么往往卡在最后一步很多开发团队在项目早期会有一个错觉功能在单域 demo 上都已经跑通了剩下的事情无非是集成测试和修 bug。真正走到量产节点时才发现双域联合发布的问题不是“两个系统各自稳定”而是“两个系统组合起来之后仍然稳定”。先说组合带来的爆炸式增长。两个域各自可能有自己的版本分支、依赖库、配置文件、通信协议。单域做回归时测试矩阵是一维的双域做回归时至少要验证多组版本组合、多组通信协议、多组配置参数的交叉影响。A 域升级了中间件B 域还没有适配握手协议一变整个车机系统的状态就可能错乱。这类问题往往不在单元测试中暴露而是在整车集成阶段才出现。再说时间上的耦合。智驾域和座舱域虽然硬件独立但在整车里却共享以太网总线、电源管理、日志通道和诊断链路。启动时序、唤醒时序、休眠时序都必须对齐。很多量产项目在早期开发时两个域各自用独立的环境联调到了样车阶段才发现某一个域比另一个域启动慢几百毫秒结果导致网络报文丢失或者日志服务没有及时拉起。最后是发布节奏的同步问题。智能驾驶域通常对安全性和稳定性要求极高希望尽量减少变更智能座舱域则为了满足用户需求可能需要频繁迭代应用和交互。两条不同的节奏要在同一个时间窗口收敛成一个版本组合必须有人对“整体可发布版本”负责而不是仅仅对单域版本负责。这里真正容易踩坑的地方是把发布当成“代码合并完成”的标志。实际上代码合并只是开始后续的版本基线固化、双域联调、自动化验证、安全审查、回滚演练每一项都比单域开发时更重。量产发布的结果不是某一个 CI 构建通过而是一整套组合包和验证记录都被证明可用。2. “双车”到底是什么智能驾驶域与智能座舱域的技术拆解要理解双域联合发布的难度先要把两个域的底子看清楚。智能驾驶域控制器通常负责感知、定位、融合、预测、规划和控制。它要实时处理摄像头、激光雷达、毫米波雷达、卫星定位等多路传感器数据输出车辆控制指令。这类系统对实时性、安全性和确定性要求很高操作系统多以 Linux、QNX 或安全内核为基础底层还会配合 AUTOSAR 等标准软件架构。从软件交付角度看它的核心特征是算法代码比重高算力需求大验证依赖仿真和实车场景任何一次模型或驱动升级都需要做严格的回归。智能座舱域控制器则负责仪表、中控、副驾屏、后排屏、语音、导航、多媒体和车联网服务。它更贴近用户体验通常以 Android 或 Linux 为基础上层运行大量生态应用和自研 HMI。这个域的特点是迭代快、涉及 API 多、外部依赖多比如地图、天气、支付、账号、手机互联等云服务都会带来版本变化。两个域之间的技术差异可以简单用下面这张表来做对比对比维度智能驾驶域智能座舱域核心任务感知、规划、控制HMI、导航、娱乐、车联网典型操作系统Linux、QNX 等Android、Linux 等实时性要求高要求确定性中但要保证交互流畅主要验证手段仿真、HIL、实车场景自动化 UI 测试、人工体验验证最怕出问题的点任务超时、死锁、安全事件卡顿、无声、导航漂移、升级失败这两个域不是各自封闭的盒子。整车 E/E 架构中它们通过以太网、CAN/CAN FD、LIN 等总线互联需要共享时间同步信号交换拍平后的车控数据和路况信息还要在诊断时统一上报故障码。任何一个域的版本变化都可能会影响另一个域的通信行为和整车的电源管理策略。所以双域联合发布的关键不是把两个安装包放进同一个 OTA 包而是从设计阶段就把“双域作为一个整体系统”来对待。谁负责接口兼容性谁负责启动时序谁负责日志汇总这些问题如果等到样车阶段才回答项目大概率会进入反复定位问题、反复改配置的循环。3. 所谓“进国”进的是什么本地化量产对软件的真实要求“进国”如果放在软件工程的语境里并不是一次简单的跨区发布。它意味着这套软件要适配一个新的市场环境做一些从研发原型到量产准出之间必须完成的工作。首先是芯片和硬件平台切换。海外项目可能使用了另一套 SoC 方案进入国内量产项目后可能要换成国产芯片或另一家供应商的平台。这不只是改一行target_arch那么简单还要重编编链、重适配 BSP、重验驱动、重新排查中断和时序问题。很多在原来平台上稳定的模块换一个平台之后就会出现内存对齐、外设中断、GPU 驱动等奇怪问题。其次是地图、导航和语音服务的本地化适配。国内使用的导航引擎、高精度地图格式、语音识别能力和海外版本往往不是同一套服务。地图数据的更新策略、日活量计费接口、合规过滤逻辑都要重新对接。还有通信模组、车联网账号体系、远程控制指令集这些“看不见”的服务链路也会成为发布前置条件。再次是法规和安全要求的对齐。量产软件在进入国内车型项目时数据合规、隐私保护、功能安全、信息安全、整车型式认证等要求都需要在软件研发中提前落实。日志和上报数据不能包含敏感位置信息诊断接口要有访问控制OTA 升级包必须具备签名和完整性校验安全漏洞要有漏洞管理流程。更务实的点是用户场景和测试工况也可能不同。国内城市的交通场景、停车场环境、高速公路标识、方言语音识别、极端天气下传感器的表现都要求开发团队补充相应的仿真用例和实车测试用例。这些工作并不在最初的原型代码里却会直接影响用户体验和量产质量。因此“进国”在工程上意味着功能代码不能“原封不动搬过去”而是要做一轮配置适配、按需求量裁、安全评审和回归测试。这个工作量和风险应该在项目立项时就写进计划而不是等到联调后期才意识到。4. 版本管理双域联合发布的软件基线与配置双域联合发布的第一件事是把两个域各自的构建产物固化成一份可追踪的版本基线。生产环境中常见的问题是开发分支上最新代码没问题但集成线上一旦换了某个中间件版本另一个域就开始超时。如果没有统一基线排查问题会变得非常痛苦。推荐的做法是维护一个 release manifest 文件单独放在发布工程仓库中。它记录两个域的固件路径、哈希值、接口版本、地图包版本、中间件版本、构建时间和签名信息。这个文件本身要入库、签名、走评审相当于整个版本组合的“身份证”。下面是一个简化但完整的 manifest 示例# 文件路径release/2025.06.01-RC1/release-manifest.yaml release: 2025.06.01-RC1 created_by: release-robot build: pilot_domain: artifact: adas_domain_v3.2.0.bin sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 interface_version: 3.2 map_package: cn_map_2025Q2 middleware: v2.1.4 cockpit_domain: artifact: cockpit_domain_v4.0.2.bin sha256: 5c78f1b8b8d7e2f0b1a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70 interface_version: 1.8 hmi_version: 4.0.2 middleware: v2.1.4 signature: algorithm: ECDSA-SHA256 key_id: release-key-2025这个文件的价值在于它把“双域版本组合”变成了一个不可拆分的整体。后续不管是台架测试、实车测试、OTA 预发布还是生产导入全部以该 manifest 为准而不是依赖某个工程师口头告诉你“这个版本应该没问题”。版本号规范也很重要。单域可以继续使用各自的内部版本号但联合发布的 release 号一定要采用全局唯一标识例如2025.06.01-RC1、2025.06.15-RELEASE。接口版本尤其要严格管理。interface_version一旦变更代表与其他域通信的协议发生了改变必须触发双方联合测试不能只由一侧自行升级。我在实际项目中看到比较多的错误是两个域各自用v1.8、v3.2这类版本号没有把它放进同一个发布计划里。一旦两边同时升级最后连“当前车上是什么版本组合”都很难说清楚。因此发布清单要尽早建立并且从第一次集成测试开始就持续维护。5. 构建与持续集成用 CI 把两套工具链统一管控有了版本基线接下来要让构建过程可重复、可追踪。双域项目通常有不同的编译工具链智驾域可能用交叉编译工具链座舱域可能用 Android 构建系统。如果每台开发机的环境都不一样构建产物就很难复现。一个有说服力的方案是把所有编译依赖做成 Docker 镜像按版本固化构建时只使用标准镜像和固定版本的工具链。CI 流程中至少包含四个阶段构建、测试、打包、签名。任何一次提交都尽可能在这个流程里完成单元测试和静态检查而不只是把代码编过去就算成功。下面是一个 GitLab CI 的简化示例演示如何在一个流水线中同时构建两个域并分别归档产物# 文件路径.gitlab-ci.yml stages: - build - test - package - sign variables: TOOLCHAIN_IMAGE: registry.example.com/car-software/toolchain:v3.2 build:pilot: stage: build image: $TOOLCHAIN_IMAGE tags: [arm-runner] script: - cd adas - make -j$(nproc) release artifacts: paths: - adas/out/adas_domain_v3.2.0.bin expire_in: 7 days build:cockpit: stage: build image: $TOOLCHAIN_IMAGE tags: [linux-runner] script: - cd cockpit - python3 scripts/build_apk.py --release artifacts: paths: - cockpit/out/cockpit_domain_v4.0.2.bin expire_in: 7 days test:unit: stage: test image: $TOOLCHAIN_IMAGE needs: [build:pilot, build:cockpit] script: - cd adas make test - cd ../cockpit python3 -m pytest tests/ when: on_success package:manifest: stage: package image: $TOOLCHAIN_IMAGE script: - python3 tools/gen_manifest.py --release $CI_COMMIT_TAG artifacts: paths: - release-manifest.yaml sign:release: stage: sign image: $TOOLCHAIN_IMAGE script: - python3 tools/sign_manifest.py release-manifest.yaml artifacts: paths: - release-manifest.signed.yaml使用这个流水线时建议把ada和cockpit两个项目放在同一个 Group 下并设置 release 分支保护。只有 release 分支或打 tag 时才生成正式 manifest普通开发分支不触发签名流程。这样做的原因是签名的对象必须是经过验证的产物组合而不是开发中随机变化的代码。构建产物也要上传到制品库并记录哈希值。CI 完成后用下面命令核对产物是否有损坏sha256sum adas_domain_v3.2.0.bin # 输出应等于 manifest 中记录的哈希值 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 adas_domain_v3.2.0.bin sha256sum cockpit_domain_v4.0.2.bin 5c78f1b8b8d7e2f0b1a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70 cockpit_domain_v4.0.2.bin这里真正难的点不是写一个 CI 文件而是让两个域的团队都愿意把构建环境容器化。很多座舱应用开发者习惯在自己的 Android Studio 上直接构建智驾算法工程师习惯在本地 Python 环境跑实验。量产项目的 CI 策略应该尽量保留本地开发体验但发布构建必须统一在容器化环境中完成。否则最终交付版本的复现性和可追溯性都无法保证。6. 核心代码示例配置校验、日志留存与 OTA 包生成双域联合发布里至少有三类代码/脚本是每个量产项目都应该提前准备好的配置一致性校验脚本、日志留存模块、OTA 包生成和校验工具。下面分别给出一个可运行的简化示例。6.1 配置一致性校验脚本这个脚本的作用是在发布前检查 manifest 中声明的文件和实际文件是否一致。如果哈希不一致说明上传、拷贝或者构建过程出了问题必须中止发布。#!/usr/bin/env python3 # 文件路径tools/check_package.py # 依赖pip install pyyaml import hashlib import sys from pathlib import Path import yaml def sha256_file(path: Path) - str: h hashlib.sha256() with open(path, rb) as f: for chunk in iter(lambda: f.read(1024 * 1024), b): h.update(chunk) return h.hexdigest() def main() - int: if len(sys.argv) ! 2: print(usage: python3 check_package.py release-manifest.yaml) return 2 manifest yaml.safe_load(Path(sys.argv[1]).read_text(encodingutf-8)) domains [pilot_domain, cockpit_domain] ok True for domain in domains: item manifest.get(build, {}).get(domain) if not item: print(f[FAIL] missing {domain} in manifest) ok False continue artifact Path(item[artifact]) expected item[sha256].lower() if not artifact.exists(): print(f[FAIL] {artifact} not found) ok False continue actual sha256_file(artifact) if actual ! expected: print(f[FAIL] {domain} sha256 mismatch) print(f expected: {expected}) print(f actual: {actual}) ok False else: print(f[PASS] {domain} sha256 ok) return 0 if ok else 1 if __name__ __main__: sys.exit(main())运行方式和预期输出python3 tools/check_package.py release/2025.06.01-RC1/release-manifest.yaml # 期望输出 [PASS] pilot_domain sha256 ok [PASS] cockpit_domain sha256 ok如果某个域镜像文件被误替换脚本会在发布前直接报FAIL避免把不一致的版本组合带上车。6.2 日志留存与滚动写入量产车机上日志会非常大底层日志服务必须考虑文件大小控制和多线程安全性。下面给出一段演示级别的 C 日志模块线程安全追加写入超过指定大小后滚动为.old文件。// 文件路径sdk/log/rolling_log.cpp #include cstdio #include filesystem #include fstream #include mutex #include string #include system_error class RollingLog { public: explicit RollingLog(std::string path, std::size_t max_bytes 1024 * 1024) : path_(std::move(path)), max_bytes_(max_bytes) {} void Write(const std::string line) { std::lock_guardstd::mutex lock(mutex_); std::ofstream out(path_, std::ios::app); if (!out.is_open()) { return; } out line \n; out.flush(); std::error_code ec; auto current_size std::filesystem::file_size(path_, ec); if (!ec current_size max_bytes_) { std::filesystem::rename(path_, path_ .old, ec); } } private: std::string path_; std::size_t max_bytes_; std::mutex mutex_; };这段代码在生产环境中还可以进一步优化比如改成按天滚动、异步批量写入、压缩上传、加上日志级别过滤。但核心设计原则是一致的多线程写入需要加锁日志文件必须限制大小防止某个异常场景把整块存储写满。6.3 OTA 包生成与安装策略双域 OTA 包必须包含两个域的安装文件、哈希和回滚分区信息。一个典型的 JSON 清单如下{ package_id: 2025.06.01-RC1, signature_algorithm: ECDSA-SHA256, items: [ { domain: adas, target_partition: pilot_a, file: adas_domain_v3.2.0.bin, sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08, rollback_partition: pilot_b }, { domain: cockpit, target_partition: cockpit_a, file: cockpit_domain_v4.0.2.bin, sha256: 5c78f1b8b8d7e2f0b1a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f70, rollback_partition: cockpit_b } ], install_strategy: A/B }这里的核心思想是OTA 包不是简单把两个二进制文件打包在一起而是要明确目标分区、回滚分区和完整性哈希。车辆升级时先下载整个包校验通过后才允许写入。如果升级过程失败可以立即切回原分区降低“刷成砖”的风险。回滚路径必须提前做演练不能只在文档里写“支持回滚”。7. 运行验证与发布判断从台架到实车的效果评估版本基线、CI 构建、OTA 包都就绪后还必须通过一套分层的验证体系才能最终判断“可以发了”。第一层是自动化单元测试和静态检查。这一层在 CI 中执行主要确认单域内部没有低级错误。第二层是台架集成测试。把双域软件部署