268 lines
9.4 KiB
Markdown
268 lines
9.4 KiB
Markdown
# 带版本号固件产物生成说明
|
||
|
||
## 1. 对应提交
|
||
|
||
```text
|
||
f5b4bd8 build: add versioned firmware artifact
|
||
```
|
||
|
||
该提交在不改变 ESP-IDF 原有应用镜像和烧录流程的前提下,为每次构建额外生成一份带项目版本号的固件文件,方便 OTA 发布、固件归档和版本追踪。
|
||
|
||
## 2. 问题原因
|
||
|
||
### 2.1 默认应用镜像名称无法区分版本
|
||
|
||
ESP-IDF 默认按照项目名称生成应用镜像。本项目的项目名为 `TQ_Cat_Code`,因此默认产物固定为:
|
||
|
||
```text
|
||
build/TQ_Cat_Code.bin
|
||
```
|
||
|
||
无论当前固件版本是 `1.0.1`、`1.0.2` 还是后续版本,文件名都不会变化。连续构建或复制不同版本时,新文件容易覆盖旧文件,单看文件名也无法判断固件版本。
|
||
|
||
### 2.2 OTA 发布和固件归档依赖人工改名
|
||
|
||
在增加本次构建逻辑之前,如果需要向 OTA 服务上传带版本标识的固件,只能在构建完成后手工复制或重命名,例如:
|
||
|
||
```text
|
||
TQ_Cat_Code.bin -> TQ_Cat_Code-1.0.2.bin
|
||
```
|
||
|
||
这种方式存在以下问题:
|
||
|
||
1. 人工填写的版本号可能与固件内部版本不一致。
|
||
2. 重命名时可能选错构建目录或上传旧文件。
|
||
3. 自动发布脚本无法依赖稳定、可预测的版本化文件名。
|
||
4. 多个版本放在同一目录时,固定名称不能提供有效区分。
|
||
|
||
### 2.3 不能直接替换 ESP-IDF 的默认产物
|
||
|
||
ESP-IDF 的烧录参数和项目描述仍引用 `TQ_Cat_Code.bin`。如果直接修改默认应用镜像名称,可能影响:
|
||
|
||
- `idf.py flash` 的默认烧录流程;
|
||
- `flasher_args.json` 等构建元数据;
|
||
- 已经依赖固定文件名的开发和生产脚本。
|
||
|
||
因此,本次修改选择保留默认产物,并额外生成版本化副本,而不是改变 ESP-IDF 的标准输出名称。
|
||
|
||
## 3. 解决方式
|
||
|
||
### 3.1 从 ESP-IDF 构建属性读取项目版本
|
||
|
||
提交在 `project()` 完成项目初始化后读取 `PROJECT_VER`:
|
||
|
||
```cmake
|
||
idf_build_get_property(TQ_PROJECT_VERSION PROJECT_VER)
|
||
```
|
||
|
||
本项目通过根目录的 `version.txt` 管理版本。ESP-IDF 配置项目时会解析该文件,并将结果写入 `PROJECT_VER`。提交创建时的项目版本为:
|
||
|
||
```text
|
||
1.0.2
|
||
```
|
||
|
||
构建逻辑直接使用 ESP-IDF 已解析的版本属性,而不是在 CMake 中重复读取或硬编码版本号。这样可以保证文件名中的版本与固件构建版本来自同一数据源。
|
||
|
||
### 3.2 组合版本化固件路径
|
||
|
||
版本化产物使用“项目名-项目版本.bin”的命名规则:
|
||
|
||
```cmake
|
||
set(TQ_VERSIONED_FIRMWARE
|
||
"${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}-${TQ_PROJECT_VERSION}.bin")
|
||
```
|
||
|
||
各变量在当前项目中的值为:
|
||
|
||
```text
|
||
CMAKE_BINARY_DIR = build 目录
|
||
CMAKE_PROJECT_NAME = TQ_Cat_Code
|
||
TQ_PROJECT_VERSION = 1.0.2
|
||
```
|
||
|
||
因此最终输出路径为:
|
||
|
||
```text
|
||
build/TQ_Cat_Code-1.0.2.bin
|
||
```
|
||
|
||
版本升级后不需要修改构建脚本。例如将 `version.txt` 更新为 `1.0.3`,重新配置并构建后,产物会自动变为:
|
||
|
||
```text
|
||
build/TQ_Cat_Code-1.0.3.bin
|
||
```
|
||
|
||
### 3.3 在应用镜像生成后复制版本化副本
|
||
|
||
提交新增了一个默认参与构建的自定义目标:
|
||
|
||
```cmake
|
||
add_custom_target(versioned_firmware ALL
|
||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||
"${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin"
|
||
"${TQ_VERSIONED_FIRMWARE}"
|
||
DEPENDS app
|
||
BYPRODUCTS "${TQ_VERSIONED_FIRMWARE}"
|
||
COMMENT "Creating versioned firmware ${CMAKE_PROJECT_NAME}-${TQ_PROJECT_VERSION}.bin"
|
||
VERBATIM
|
||
)
|
||
```
|
||
|
||
这段代码的行为如下。
|
||
|
||
#### `versioned_firmware ALL`
|
||
|
||
定义名为 `versioned_firmware` 的构建目标。`ALL` 表示执行常规项目构建时会自动执行该目标,无需额外运行单独命令。
|
||
|
||
#### `copy_if_different`
|
||
|
||
使用 CMake 自带的跨平台复制命令,将默认应用镜像复制为版本化文件:
|
||
|
||
```text
|
||
build/TQ_Cat_Code.bin
|
||
-> build/TQ_Cat_Code-1.0.2.bin
|
||
```
|
||
|
||
只有源文件与目标文件内容不同时才会重新写入目标文件,可以避免未发生变化时进行无意义覆盖。该操作只是字节复制,不会再次编译、链接或修改固件内容。
|
||
|
||
#### `DEPENDS app`
|
||
|
||
声明版本化目标依赖 ESP-IDF 的 `app` 目标。CMake 必须先完成应用镜像构建,随后才能复制文件,避免在源 `.bin` 尚未生成时执行复制。
|
||
|
||
#### `BYPRODUCTS`
|
||
|
||
将版本化 `.bin` 声明为该目标的构建副产物,使 CMake 和底层构建工具能够识别这个文件由构建系统生成,而不是人工放入构建目录的未知文件。
|
||
|
||
#### `COMMENT`
|
||
|
||
构建过程中输出明确提示,例如:
|
||
|
||
```text
|
||
Creating versioned firmware TQ_Cat_Code-1.0.2.bin
|
||
```
|
||
|
||
开发人员可以通过构建日志确认版本化产物生成步骤已执行。
|
||
|
||
#### `VERBATIM`
|
||
|
||
要求 CMake 按参数原意生成命令,正确处理路径中的空格、引号和平台差异,降低命令转义导致复制失败的风险。
|
||
|
||
## 4. 完整执行流程
|
||
|
||
构建过程变为:
|
||
|
||
```text
|
||
读取 version.txt
|
||
|
|
||
v
|
||
ESP-IDF 设置 PROJECT_VER
|
||
|
|
||
v
|
||
编译并链接 app
|
||
|
|
||
v
|
||
生成 build/TQ_Cat_Code.bin
|
||
|
|
||
v
|
||
versioned_firmware 目标执行复制
|
||
|
|
||
v
|
||
生成 build/TQ_Cat_Code-1.0.2.bin
|
||
```
|
||
|
||
最终两个应用镜像同时存在:
|
||
|
||
```text
|
||
build/TQ_Cat_Code.bin
|
||
build/TQ_Cat_Code-1.0.2.bin
|
||
```
|
||
|
||
两者内容完全相同,区别仅在文件名。当前工作区的实际构建结果中,两份文件大小一致,SHA-256 也一致:
|
||
|
||
```text
|
||
baa959fda9572c249ceaf6b8937d35940bc4c7dece2814fa2daebe1c616abc6d
|
||
```
|
||
|
||
## 5. 解决成果
|
||
|
||
### 5.1 固件版本可以直接从文件名识别
|
||
|
||
版本信息进入构建产物名称后,无需打开固件或查询构建记录,即可识别文件对应的项目版本。
|
||
|
||
### 5.2 降低 OTA 上传选错版本的概率
|
||
|
||
OTA 发布人员或自动化脚本可以直接选择带版本号的固件文件,避免手工改名时版本号与实际固件不一致。
|
||
|
||
### 5.3 支持多个版本并行归档
|
||
|
||
不同版本使用不同文件名,可以在发布目录、测试目录或制品库中同时保存:
|
||
|
||
```text
|
||
TQ_Cat_Code-1.0.1.bin
|
||
TQ_Cat_Code-1.0.2.bin
|
||
TQ_Cat_Code-1.0.3.bin
|
||
```
|
||
|
||
这有利于版本回溯、测试对比和问题复现。
|
||
|
||
### 5.4 保持原有开发与烧录流程兼容
|
||
|
||
默认的 `TQ_Cat_Code.bin` 仍然生成,ESP-IDF 的烧录参数没有改变。因此原有的构建、串口烧录和调试命令无需调整。
|
||
|
||
### 5.5 不增加固件运行时开销
|
||
|
||
修改只发生在构建阶段,没有增加设备端代码、任务、内存、Flash 占用或启动时间。版本化文件也不是另一份不同的固件镜像,只是原应用镜像的副本。
|
||
|
||
## 6. 后续影响
|
||
|
||
### 6.1 构建目录磁盘占用增加
|
||
|
||
构建目录会额外保存一份与应用镜像等大的文件。当前应用镜像约为 2.1 MiB,因此单次当前版本构建会增加约 2.1 MiB 的磁盘占用。
|
||
|
||
该影响只存在于开发机或构建服务器,不会使设备 Flash 中出现两份应用镜像,也不会增加 OTA 下载大小。
|
||
|
||
### 6.2 旧版本产物不会被自动删除
|
||
|
||
当版本从 `1.0.2` 更新为 `1.0.3` 后,新构建会生成新文件,但原来的版本化文件可能继续保留:
|
||
|
||
```text
|
||
TQ_Cat_Code-1.0.2.bin
|
||
TQ_Cat_Code-1.0.3.bin
|
||
```
|
||
|
||
这便于本地归档,但也可能让长期复用的构建目录逐渐积累旧产物。发布脚本不应简单选择目录中任意一个带版本号的 `.bin`,而应根据当前 `PROJECT_VER` 计算准确文件名。需要清理时,可以通过完整清理构建目录移除历史副产物。
|
||
|
||
### 6.3 版本变化后必须重新配置构建系统
|
||
|
||
版本化路径在 CMake 配置阶段根据 `PROJECT_VER` 生成。修改 `version.txt` 后,应通过正常的 ESP-IDF 构建流程触发重新配置,确保目标文件名使用新版本。持续集成环境使用干净构建目录时不会受到这一问题影响。
|
||
|
||
### 6.4 该文件只是应用镜像,不是完整烧录包
|
||
|
||
`TQ_Cat_Code-1.0.2.bin` 只对应应用程序分区。它不包含以下独立镜像或资源:
|
||
|
||
- Bootloader;
|
||
- 分区表;
|
||
- OTA data 初始化镜像;
|
||
- LittleFS/LCD/音频资源镜像。
|
||
|
||
该文件适合应用分区 OTA,不能被误认为包含全部分区的整机合并烧录镜像。工厂烧录仍需按照 ESP-IDF 生成的烧录参数写入各个分区。
|
||
|
||
### 6.5 OTA 服务和发布脚本需要明确选择规则
|
||
|
||
新增文件不会自动改变 OTA 上传流程,也不会自动把固件发布到服务器。后续自动化应明确:
|
||
|
||
1. 从当前项目版本计算版本化文件名;
|
||
2. 上传前校验文件存在且构建成功;
|
||
3. 计算并记录文件大小与 SHA-256;
|
||
4. 保证 OTA 元数据中的版本、大小和摘要与该文件一致。
|
||
|
||
### 6.6 项目名或版本字符串会直接进入文件名
|
||
|
||
文件名由 `CMAKE_PROJECT_NAME` 和 `PROJECT_VER` 直接组合。当前值只包含适合文件名的字母、数字、下划线、点和连字符,不存在兼容性问题。后续如果引入带斜杠、空格或其他特殊字符的版本规则,需要额外确认制品库、上传脚本和目标文件系统是否支持。
|
||
|
||
## 7. 结论
|
||
|
||
该提交以较小的构建配置改动解决了固件产物缺少版本标识的问题。它复用 ESP-IDF 的统一项目版本,保留原始应用镜像和烧录流程,并自动生成内容完全一致的版本化副本。
|
||
|
||
该方案不会影响设备运行行为,主要后续影响是构建目录增加一份应用镜像、历史版本文件可能累积,以及发布流程必须正确区分“应用 OTA 镜像”和“完整烧录镜像”。
|