跳转至

源码版本、开发环境与可复现构建

开发环境的第一目标不是“在我的电脑上编译成功”,而是能够回答:谁在什么环境中,用哪一份源码、哪些依赖和哪个板级目标,生成了哪一个固件文件。

可复现固件构建链

建立隔离的工作副本

不要直接在唯一的稳定源码副本或主分支上实验。基本流程是:

  1. 从项目官方仓库取得源码;
  2. 核对仓库地址,避免使用来源不明的镜像;
  3. 切换到明确的发布标签或提交;
  4. 初始化并固定子模块;
  5. 创建本地开发分支;
  6. 记录工作区是否存在未提交修改。

通用Git记录命令示例:

git remote -v
git rev-parse HEAD
git status --short
git submodule status --recursive

分支名不能替代提交哈希,因为分支指针会移动。发布标签也应同时记录其最终解析到的提交。

本专题的固定教学基线

以下示范固定为PX4 v1.17.0,对应提交d6f12ad1c4f70ad3230afd7d86e971421e02fef4。该版本只用于让本文命令可复查,不表示任何任务都应选择PX4或该版本。

本专题参考主机为Ubuntu 24.04 x86_64。克隆前先检查:

uname -a
df -h .
command -v git curl bash python3
git --version
curl --version
python3 --version
sudo -v
curl -I https://github.com/PX4/PX4-Autopilot

教学环境建议预留至少20GB可用空间,以容纳源码、依赖、主机测试和多个构建目录。任一基础命令缺失、sudo -v失败、网络无法访问官方仓库或磁盘空间不足时,先修复主机环境。

Ubuntu上缺少Git、curl或ripgrep时可以先执行:

sudo apt update
sudo apt install git curl ripgrep
git clone --branch v1.17.0 --recursive \
  https://github.com/PX4/PX4-Autopilot.git
cd PX4-Autopilot
git rev-parse HEAD
git tag --points-at HEAD
git submodule status --recursive
git status --short
git switch -c learning/read-only-diagnostic

核验结果应满足:

  • git rev-parse HEAD输出上述完整提交;
  • git tag --points-at HEAD包含v1.17.0
  • 子模块每行应以空格开头;
  • 新建分支后git status --short为空。

如果任一结果不同,先记录实际版本并解释差异,不继续套用本专题后面的固定示例。

git submodule status --recursive的首字符含义为:

首字符 含义 处理
空格 当前子模块提交与父仓库记录一致 通过
- 子模块尚未初始化 初始化后复核
+ 当前子模块提交与父仓库记录不一致 同步并更新到记录提交
U 子模块存在合并冲突 停止,不继续构建

-+可执行:

git submodule sync --recursive
git submodule update --init --recursive
git submodule status --recursive

若仍出现+,检查是否在子模块中存在主动检出的工作;若出现U或任何不属于自己的修改,不得强制覆盖,应修复冲突或重新取得干净工作副本。

不要混用项目的构建说明

不同飞控项目可能使用不同工具:

项目 常见构建入口 必须以什么为准
Betaflight GNU Make及项目提供的工具链安装目标 当前分支的开发文档和Makefile
INAV CMake、Ninja或项目包装命令 当前分支的开发目录和目标定义
ArduPilot Waf构建系统 当前分支开发文档和waf目标列表
PX4 CMake及Make包装入口 当前分支开发环境、板级目标和仿真文档

网上旧文章中的命令可能仍能运行,也可能构建出错误目标或缺少新依赖。命令、目录和工具链版本必须与正在使用的源码分支配套。

工具与产物速查

名称 作用
工具链 编译器、汇编器、链接器、标准库和二进制工具的组合
Make、CMake、Ninja、Waf 读取项目构建定义并组织编译、链接和测试
容器镜像摘要 标识不可变容器内容,比可移动标签更适合复现
编译数据库 记录每个源文件的实际编译命令,常供编辑器和静态分析使用
可执行与可链接格式(ELF,Executable and Linkable Format)带符号文件 保留代码、数据、符号和调试信息,用于定位崩溃
映射文件 展示符号被链接到的地址和占用空间
栈预算 每个任务调用链和局部变量可使用的栈空间上界
堆预算 运行时动态分配可使用的内存上界

构建环境记录

建议为每次可验证构建保存:

字段 示例内容
主机系统 操作系统名称、版本和处理器架构
容器或虚拟环境 镜像名称及不可变摘要
编译器 名称、完整版本和目标架构
构建工具 Make、CMake、Ninja、Waf等版本
脚本 项目提供的环境安装脚本提交
板级目标 完整目标名称和硬件版本
构建选项 功能开关、优化级别和自定义定义
源码 主仓库和全部子模块提交
输出 文件名、大小和SHA-256校验值

安全散列算法256位(SHA-256,Secure Hash Algorithm 256-bit)校验值可以识别产物是否发生变化,但不能证明产物安全或来源可信。

先构建未修改基线

修改任何代码前:

  1. 按官方文档准备工具链;
  2. 构建项目支持的仿真目标或主机测试;
  3. 构建目标飞控板的官方配置;
  4. 记录编译警告、产物大小和校验值;
  5. 运行项目已有测试;
  6. 确认未修改基线可以启动或进入仿真。

如果官方基线不能构建,先修复环境问题。不要同时修改业务代码,否则无法区分环境错误和代码错误。

可执行的PX4软件在环基线

以下命令以官方支持的Ubuntu 24.04开发环境为例。安装脚本会修改系统软件包,执行前应先查看脚本,并在专用开发机、虚拟机或容器中运行;macOS和其他受支持环境应改用对应版本的官方安装页。

cd PX4-Autopilot
mkdir -p ../px4-evidence
set -o pipefail
bash ./Tools/setup/ubuntu.sh 2>&1 | \
  tee ../px4-evidence/toolchain-install.log
INSTALL_STATUS=${PIPESTATUS[0]}
printf 'installer_exit=%s\n' "$INSTALL_STATUS"
test "$INSTALL_STATUS" -eq 0 || exit "$INSTALL_STATUS"

installer_exit=0才表示安装脚本成功。包下载、网络、权限或空间错误应先根据日志中的第一条错误处理,不要直接继续构建。

安装完成并重新打开终端后核验:

cmake --version
ninja --version
gcc --version
arm-none-eabi-gcc --version
python3 --version

任何命令不存在时,回到对应主机的PX4 v1.17开发环境文档,不使用来源不明的工具链补丁。然后运行:

cd PX4-Autopilot
BASELINE_DIR="../px4-evidence/baseline-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BASELINE_DIR"
set -o pipefail
make tests 2>&1 | tee "$BASELINE_DIR/tests.log"
TEST_STATUS=${PIPESTATUS[0]}
printf 'tests_exit=%s\n' "$TEST_STATUS" | \
  tee "$BASELINE_DIR/tests.exit"
test "$TEST_STATUS" -eq 0 || exit "$TEST_STATUS"
make px4_sitl sihsim_quadx 2>&1 | \
  tee "$BASELINE_DIR/sitl.log"
SITL_STATUS=${PIPESTATUS[0]}
printf 'sitl_exit=%s\n' "$SITL_STATUS" | \
  tee "$BASELINE_DIR/sitl.exit"
test "$SITL_STATUS" -eq 0 || exit "$SITL_STATUS"
sha256sum build/px4_sitl_default/bin/px4 | \
  tee "$BASELINE_DIR/px4.sha256"
git rev-parse HEAD > "$BASELINE_DIR/head.txt"
git submodule status --recursive > "$BASELINE_DIR/submodules.txt"

出现pxh>后依次执行:

ver all
hello start
shutdown

等待goodbye出现后再执行shutdown

通过标准:

  • make tests后的tests_exit为0;
  • 仿真构建没有编译或链接错误;
  • 终端出现pxh>控制台提示符;
  • 在控制台执行ver all能够看到固件与构建信息;
  • 执行hello start后出现hello、五次Doing work...goodbye
  • 执行shutdown后仿真正常退出。

SITL_STATUS和SHA-256命令会在shutdown退出仿真后继续执行。BASELINE_DIR位于源码目录之外,保存了测试、仿真、版本、子模块和产物校验值。

硬件内仿真(SIH,Simulation In Hardware)把物理模型作为PX4内部模块运行。这里使用主机上的px4_sitl构建,是为了避开三维仿真器依赖并先验证源码、工具链和飞控进程;此时仍属于软件在环,不代表真实飞控硬件已经验证。

目标板固件只能在已确定真实板级目标后构建。可先列出当前提交中的目标:

make list_config_targets

不要为了得到一个固件文件而随意选择名称相近的目标。

目标名称必须来自当前源码

板级目标名称可能因版本变化而新增、重命名、合并或移除。应从当前提交的目标列表、构建帮助或板级目录中取得名称,不凭商品名称猜测。

需要同时核对:

  • 厂商与型号;
  • 硬件版本;
  • 处理器和闪存容量;
  • 引导加载程序;
  • 板级标识;
  • 默认传感器;
  • 构建功能裁剪;
  • 产物格式。

构建一个“相同处理器”的目标,不能证明产物适用于另一块板。

可复现不一定等于逐字节相同

构建时间、绝对路径、编译器版本、链接顺序或嵌入式版本信息可能让二进制字节不同。因此要区分:

  • 过程可复现:同一记录可以再次完成构建;
  • 功能可复现:测试结果和运行行为一致;
  • 位级可复现:产物每个字节完全一致。

安全发布应尽量追求位级可复现;做不到时,至少解释差异来源,并保证源码、工具链、选项和测试可追溯。

构建输出不能只保存一个固件文件

建议归档:

  • 固件镜像;
  • 带符号的可执行文件;
  • 映射文件;
  • 编译数据库;
  • 版本清单;
  • 构建日志;
  • 内存占用报告;
  • 测试报告;
  • SHA-256校验值;
  • 对应参数和硬件清单。

带符号文件和映射文件对崩溃地址解析、栈分析和内存布局检查很重要,不能只保留用于刷写的压缩镜像。

检查资源变化

每次改动至少比较:

  • 闪存占用;
  • 静态随机存取存储器占用;
  • 栈和堆预算;
  • 中断和任务数量;
  • 日志带宽;
  • CPU负载;
  • 控制周期和截止期限违约;
  • 启动时间。

“只增加一个日志字段”也可能改变存储、协议、CPU和带宽。

构建记录模板

项目:
仓库:
提交哈希:
子模块状态:
本地补丁:
主机系统:
工具链:
构建命令:
板级目标:
构建选项:
输出文件:
SHA-256:
闪存/内存占用:
测试结果:
记录日期:

检查理解

  1. 为什么分支名不能替代提交哈希?
  2. 为什么修改代码前必须先构建未修改基线?
  3. 板级目标名称为什么不能根据商品名称猜测?
  4. 过程可复现和位级可复现有什么区别?
  5. 为什么应保留带符号文件和映射文件?

主要参考