顶层 Makefile 与编译目标
本文档介绍 fw-AW31N_BLE_SDK 仓库根目录下顶层 Makefile 的结构、支持的编译目标(aw31n_transfer / aw31n_hid 等),以及它们如何分发到各子工程 Makefile 完成编译、链接与烧录的完整构建链路。
Purpose and Scope
本页覆盖的内容:
- 仓库根目录
Makefile的全部目标(all、clean、aw31n_transfer、aw31n_hid及对应的 clean 目标)与其分发逻辑; - 子工程 Makefile(
apps/demo/transfer/board/bd47/Makefile与apps/demo/hid/board/bd47/Makefile)中的工具链切换(Windows / Linux)、编译参数(CFLAGS / DEFINES / INCLUDES)与构建产物; - 构建前置条件(工具链安装路径、
ulimit -n限制)与常见失败模式。
不纳入本页的内容(属于兄弟页面):
- 各 demo 应用的功能实现(transfer 数据透传、HID 人机交互设备等);
- bd47 板级的硬件驱动与 BSP 细节;
apps/app/post_build/bd47/下 download.sh / download.bat 烧录脚本的内部实现。
Overview
该 SDK 采用「顶层分发器 + 子工程 Makefile」的两级构建结构。仓库根目录只有一个 30 行的 Makefile,它本身不直接编译任何代码,而是通过 $(MAKE) -C <子工程目录> -f Makefile 递归调用各 demo 子工程的 Makefile。
当前支持两个子工程:
| 目标名 | 对应子工程目录 | 说明 |
|---|---|---|
aw31n_transfer | apps/demo/transfer/board/bd47 | BLE 数据透传(transfer)demo |
aw31n_hid | apps/demo/hid/board/bd47 | HID 设备 demo |
子工程 Makefile 负责真正的构建工作:按操作系统选择 clang 工具链(Windows 下位于 C:/JL/pi32/bin,Linux 下位于 /opt/jieli/q32s/bin)、编译 c_SRC_FILES 中列出的全部 .c 源文件、链接生成 sdk.elf,并调用后处理脚本(download.bat / download.sh)完成固件烧录。
这种设计意图在于:一个仓库内同时维护多个 demo 工程时,根目录只需提供统一、简单的入口;新增一个 demo 只需在顶层 Makefile 增加一个目标与一次递归调用,构建逻辑集中在各子工程内,互不干扰。
Architecture
下图展示从顶层目标到最终烧录的完整构建架构:
flowchart TD
subgraph sg_Top["顶层 Makefile(仓库根目录)"]
T_all["all"]
T_transfer["aw31n_transfer"]
T_hid["aw31n_hid"]
T_clean["clean"]
end
subgraph sg_Sub["子工程 Makefile(bd47 板级)"]
M_transfer["apps/demo/transfer/board/bd47/Makefile"]
M_hid["apps/demo/hid/board/bd47/Makefile"]
end
subgraph sg_Toolchain["工具链与系统库"]
TC["clang / lto-wrapper / lto-ar"]
LIBS["SYS_LIB_DIR / SYS_INC_DIR"]
end
subgraph sg_Output["构建产物与后处理"]
OBJ["objs/ 中间目标文件"]
ELF["sdk.elf"]
POST["download.sh / download.bat"]
end
T_all --> T_transfer
T_all --> T_hid
T_transfer --> M_transfer
T_hid --> M_hid
T_clean -->|"clean 目标"| M_transfer
T_clean -->|"clean 目标"| M_hid
M_transfer --> TC
M_hid --> TC
TC --> LIBS
M_transfer --> OBJ
M_hid --> OBJ
OBJ --> ELF
ELF --> POST
架构要点:
- 顶层 Makefile 只做目标聚合与递归分发,
all聚合两个 demo,clean聚合两个清理目标; - 子工程 Makefile 是唯一的构建逻辑所有者,负责工具链选择、编译参数、源文件清单与产物路径;
- 工具链 由操作系统环境变量
OS决定,Windows 使用.exe工具,Linux 使用同名 ELF 工具; - 后处理脚本 位于
apps/app/post_build/bd47/,输出固件路径为同目录下的sdk.elf,最终由 download 脚本烧录到芯片。
顶层 Makefile 解析
根目录 Makefile 全文只有 30 行,其核心内容如下:
# 总的 Makefile,用于调用目录下各个子工程对应的 Makefile
# 支持的目标
# make aw31n_transfer
# make aw31n_hid
.PHONY: all clean aw31n_transfer aw31n_hid clean_aw31n_transfer clean_aw31n_hid
all: aw31n_transfer aw31n_hid
@echo +ALL DONE
clean: clean_aw31n_transfer clean_aw31n_hid
@echo +CLEAN DONE
aw31n_transfer:
$(MAKE) -C apps/demo/transfer/board/bd47 -f Makefile
clean_aw31n_transfer:
$(MAKE) -C apps/demo/transfer/board/bd47 -f Makefile clean
aw31n_hid:
$(MAKE) -C apps/demo/hid/board/bd47 -f Makefile
clean_aw31n_hid:
$(MAKE) -C apps/demo/hid/board/bd47 -f Makefile clean
Source: Makefile
目标一览
| 目标 | 前置目标 | 行为 |
|---|---|---|
all(默认目标) | aw31n_transfer、aw31n_hid | 依次构建全部两个子工程,完成后打印 +ALL DONE |
clean | clean_aw31n_transfer、clean_aw31n_hid | 清理全部子工程,完成后打印 +CLEAN DONE |
aw31n_transfer | — | 递归进入 transfer 子工程目录执行 make |
clean_aw31n_transfer | — | 递归进入 transfer 子工程目录执行 make clean |
aw31n_hid | — | 递归进入 hid 子工程目录执行 make |
clean_aw31n_hid | — | 递归进入 hid 子工程目录执行 make clean |
设计细节说明:
- 所有目标均声明为
.PHONY,因为目标名(如aw31n_transfer)与磁盘上的目录名不同,且all/clean是约定俗成的伪目标,必须避免与同名文件冲突导致 make 误判「无需更新」; - 使用
$(MAKE)而非直接写make,是为了透传命令行变量与-j等参数到子进程,并保证递归 make 的可追踪性; -C apps/demo/transfer/board/bd47指定工作目录,-f Makefile显式指定构建文件——子工程目录里就是默认文件名,这里的写法保证了入口明确。
编译前置条件(文件头注释)
顶层 Makefile 头部的注释是构建系统最重要的运维文档,它记录了 Linux 下的三条硬性要求:
- 从
http://pkgman.jieliapp.com/doc/all获取工具链下载链接; - 解压到
/opt/jieli目录,确保/opt/jieli/common/bin/clang存在(注意目录层次,不同版本的 SDK 工具链安装路径可能不同); - 确认
ulimit -n(文件描述符上限)足够大(建议 > 8096),否则链接阶段可能因为打开文件过多而失败;可通过ulimit -n 8096临时调大。
Source: Makefile
子工程 Makefile 解析
两个子工程的 Makefile 在前 100 行完全相同(工具链与环境部分),差异体现在后续的 DEFINES、INCLUDES 与 c_SRC_FILES 列表(例如 transfer 工程定义了 -DAPP_CASE_TRANSFER,且头文件路径包含 apps/demo/transfer 与 rcsp_update 等透传相关目录)。下面以 transfer 工程为例说明。
双平台工具链切换
# 工具路径设置
ifeq ($(OS), Windows_NT)
# Windows 下工具链位置
TOOL_DIR := C:/JL/pi32/bin
CC := clang.exe
CXX := clang.exe
LD := q32s-lto-wrapper.exe
AR := llvm-ar.exe
MKDIR := mkdir_win -p
RM := rm -rf
SYS_LIB_DIR := C:/JL/pi32/q32s-lib
SYS_INC_DIR := C:/JL/pi32/q32s-include
EXT_CFLAGS := # Windows 下不需要 -D__SHELL__
export PATH:=$(TOOL_DIR);$(PATH)
else
# Linux 下工具链位置
TOOL_DIR := /opt/jieli/q32s/bin
CC := clang
CXX := clang
LD := lto-wrapper
AR := lto-ar
MKDIR := mkdir -p
RM := rm -rf
export OBJDUMP := $(TOOL_DIR)/objdump
export OBJCOPY := $(TOOL_DIR)/objcopy
export OBJSIZEDUMP := $(TOOL_DIR)/objsizedump
SYS_LIB_DIR := $(TOOL_DIR)/../lib
SYS_INC_DIR := $(TOOL_DIR)/../include
EXT_CFLAGS := -D__SHELL__ # Linux 下需要这个保证正确处理 download.c
export PATH:=$(TOOL_DIR):$(PATH)
endif
该分支的设计意图:
- 通过 make 内置变量
OS(Windows 下 make 会将其设为Windows_NT)做平台判断,一套 Makefile 同时支持 Windows 与 Linux 开发环境; - Windows 工具链各工具带
.exe后缀,且链接器为q32s-lto-wrapper.exe(LLVM LTO 链接包装器),Linux 下对应lto-wrapper;归档器分别为llvm-ar.exe与lto-ar; - Linux 分支额外导出
OBJDUMP/OBJCOPY/OBJSIZEDUMP,供后续链接/后处理脚本使用; EXT_CFLAGS的差异是关键:Linux 下必须追加-D__SHELL__,用于保证download.c中烧录相关代码在 Linux 下被正确编译;Windows 下则为空;- 两种平台都把工具链目录
export到PATH,使构建中调用的其他辅助程序(如 post_build 脚本)能找到工具。
构建产物与编译参数
CC := $(TOOL_DIR)/$(CC)
CXX := $(TOOL_DIR)/$(CXX)
LD := $(TOOL_DIR)/$(LD)
AR := $(TOOL_DIR)/$(AR)
# 输出文件设置
OUT_ELF := ../../../../../apps/app/post_build/bd47/sdk.elf
OBJ_FILE := $(OUT_ELF).objs.txt
# 编译路径设置
BUILD_DIR := objs
# 工程路径前缀
ROOT_PREFIX := ../../../../..
要点:
- 产物路径统一收敛:
OUT_ELF指向apps/app/post_build/bd47/sdk.elf,即最终固件 ELF 与后处理脚本放在同一目录,方便 download 脚本就近取用;OBJ_FILE(sdk.elf.objs.txt)用于记录链接输入的目标文件清单,供 LTO 链接器使用; ROOT_PREFIX := ../../../../..:从apps/demo/transfer/board/bd47回到仓库根目录的相对路径,后续所有源文件与头文件路径都以此为前缀拼接,保证无论在哪个子工程构建都能定位到仓库内的共享代码;BUILD_DIR := objs为当前目录下的中间文件目录。
CFLAGS 采用 LLVM/clang 的 q32s 目标:
CFLAGS := \
-flto \
-target q32s \
-integrated-as \
-fno-builtin \
-mllvm -inline-threshold=5 \
-Oz \
-integrated-as \
-g \
-O0 \
-flto \
-Os \
-Wcast-align \
-fallow-pointer-null \
-Wincompatible-pointer-types \
-Wundef \
-fprefer-gnu-section \
-Wframe-larger-than=256 \
-Wreturn-type \
-Wimplicit-function-declaration \
-Werror=jl-arg-struct-check \
-Werror \
-Werror=implicit-function-declaration \
-Werror=return-type \
-Werror=undef \
-fms-extensions \
-Wno-format
参数意图解读:
-target q32s指定交叉编译目标架构(杰理 q32s CPU 内核);-integrated-as使用 clang 内置汇编器,避免依赖外部 as;-flto开启链接期优化(Link-Time Optimization),配合lto-wrapper/q32s-lto-wrapper.exe链接器使用;-mllvm -inline-threshold=5压低内联阈值以控制代码体积;-Oz(优化尺寸)与-Os同时出现,且保留了-g -O0,说明该构建以代码体积优先、兼顾可调试为原则——BLE 固件通常受 Flash/RAM 容量约束;- 大量
-Werror=...将常见错误(隐式函数声明、未定义宏、错误返回类型)升级为编译失败,保证 SDK 代码质量;-Werror=jl-arg-struct-check是杰理自定义的检查(结构体参数传递约定),-Wframe-larger-than=256限制栈帧大小以适配受限 RAM; -fno-builtin、-fprefer-gnu-section服务于链接期的裁剪与重排。
宏定义与头文件搜索路径
DEFINES := \
-D__FPGA=0 \
-DCONFIG_CPU_BD47=1 \
-DAPP_BT_BLE=1 \
-DSUPPORT_MS_EXTENSIONS \
-DSUPPORT_RESERVATION_RAM \
-DAPP_CASE_TRANSFER \
-DSDK_VERSION_CFG_DEFINE=0x130001 \
-DSDK_VERSION_DATE_DEFINE=20250822 \
-DCONFIG_RELEASE_ENABLE \
-DHAS_VFS_EN=1 \
-DNOFLOAT
DEFINES += $(EXT_CFLAGS) # 额外的一些定义
宏定义含义:
CONFIG_CPU_BD47=1声明目标 CPU 为 BD47;APP_BT_BLE=1使能 BLE 应用;APP_CASE_TRANSFER标识本工程为「充电仓/透传盒」应用场景,驱动代码中对应场景的条件编译;SDK_VERSION_CFG_DEFINE=0x130001与SDK_VERSION_DATE_DEFINE=20250822把 SDK 版本与日期编译进固件,供运行时上报/升级校验;SUPPORT_RESERVATION_RAM保留 RAM 段;NOFLOAT禁用浮点以减小体积;HAS_VFS_EN=1使能虚拟文件系统;__FPGA=0表示非 FPGA 验证平台;- 末尾
DEFINES += $(EXT_CFLAGS)把平台相关的-D__SHELL__(Linux)并入总宏定义。
头文件搜索路径(INCLUDES)则以 ROOT_PREFIX 为基址展开约 60 个 -I 目录,覆盖:
apps/include_lib/*(各功能模块对外头文件:bt_include、device、fs、audio、msg、update 等);apps/app/bsp/*(BSP 源码自带头文件:common、cpu/bd47、start、usb、key、power_manage 等);apps/demo/transfer(本工程私有头文件);- 系统目录
$(SYS_INC_DIR)。
源文件清单
c_SRC_FILES 以 ROOT_PREFIX 前缀列出本工程参与编译的全部 .c 文件,transfer 工程覆盖了 apps/app/bsp/common/ 下的共享模块(bt_common、code_switch、common_uart、fs/vfs、ir、key、led、mouse_sensor、msg、power_manage、third_party_profile/jieli(含 JL_rcsp 的 rcsp_update / rcsp_hid)、update、usb 设备类等),以及 apps/demo/transfer 下的 demo 代码。
该文件在 240 行之后继续罗列剩余源文件、C++ 源文件、汇编源文件,并定义目标文件生成规则与 all / clean / download 等实际执行目标(make 默认行为即「编译并下载」,make clean 清除 objs/ 等临时文件,make VERBOSE=1 显示详细编译过程,见文件头部注释)。
核心构建流程
以执行 make aw31n_transfer 为例,完整调用链如下:
sequenceDiagram
participant U as 开发者
participant T as 顶层 Makefile
participant S as 子工程 Makefile
participant CC as clang 工具链
participant P as post_build 脚本
U->>T: make aw31n_transfer
T->>S: $(MAKE) -C apps/demo/transfer/board/bd47 -f Makefile
activate S
S->>S: 判断 OS 选择工具链(Windows / Linux)
S->>CC: 按 c_SRC_FILES 逐个编译 .c → objs/*.o
CC-->>S: 目标文件 + sdk.elf.objs.txt
S->>S: lto-wrapper 链接生成 sdk.elf
S->>P: 调用 download.sh / download.bat
deactivate S
P-->>U: 固件烧录完成
关键路径说明:
- 开发者执行
make aw31n_transfer(或直接make触发默认目标all,顺序构建两个工程); - 顶层 Makefile 用
$(MAKE) -C ... -f Makefile递归调用子工程,工作目录切换到apps/demo/transfer/board/bd47; - 子工程 Makefile 解析
OS变量选择工具链目录,将工具链加入PATH; - 对
c_SRC_FILES中的每个源文件调用$(CC)(clang,-target q32s -flto -Os/-Oz)生成目标文件到objs/,同时生成链接输入清单sdk.elf.objs.txt; - 链接器
lto-wrapper进行 LTO 链接,输出apps/app/post_build/bd47/sdk.elf; - 最后执行后处理脚本(Linux 为
bash download.sh,Windows 为download.bat,且 Windows 下先用fixbat.exe处理脚本的 utf8→gbk 编码问题)完成固件烧录。
平台分支逻辑可简化为下图:
flowchart TD
Q{"OS == Windows_NT ?"}
Q -->|"是"| W["工具链 C:/JL/pi32/bin<br/>clang.exe / q32s-lto-wrapper.exe"]
Q -->|"否(Linux)"| L["工具链 /opt/jieli/q32s/bin<br/>clang / lto-wrapper"]
W --> W2["download.bat + fixbat.exe<br/>EXT_CFLAGS 为空"]
L --> L2["download.sh<br/>EXT_CFLAGS += -D__SHELL__"]
使用示例
构建全部子工程
在仓库根目录执行:
make
# 等价于 make all,依次构建 aw31n_transfer 与 aw31n_hid
# 完成后输出 +ALL DONE
单独构建并烧录某个工程
make aw31n_transfer # 构建 transfer 工程并烧录
make aw31n_hid # 构建 hid 工程并烧录
清理构建产物
make clean # 清理全部子工程
make clean_aw31n_transfer # 仅清理 transfer 工程
Source: Makefile
子工程内的高级用法
进入子工程目录后可直接调用其 Makefile:
cd apps/demo/transfer/board/bd47
make # 编译并下载(默认行为,见文件头注释)
make VERBOSE=1 # 显示详细编译过程
make clean # 清除编译临时文件
配置选项
以下为构建系统的关键可配置项:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OS(环境变量) | string | 由系统决定 | Windows_NT 时走 Windows 工具链分支,否则按 Linux 处理 |
VERBOSE=1(命令行变量) | bool | 关闭 | 子工程 make 显示详细编译过程 |
TOOL_DIR | path | Windows: C:/JL/pi32/bin;Linux: /opt/jieli/q32s/bin | clang 交叉工具链目录,会被 export 进 PATH |
SYS_LIB_DIR | path | Windows: C:/JL/pi32/q32s-lib;Linux: $(TOOL_DIR)/../lib | 系统库目录 |
SYS_INC_DIR | path | Windows: C:/JL/pi32/q32s-include;Linux: $(TOOL_DIR)/../include | 系统头文件目录 |
EXT_CFLAGS | string | Windows: 空;Linux: -D__SHELL__ | 平台额外宏定义,追加到 DEFINES |
OUT_ELF | path | apps/app/post_build/bd47/sdk.elf | 链接产物路径 |
BUILD_DIR | path | objs | 中间目标文件目录 |
ROOT_PREFIX | path | ../../../../.. | 从子工程目录回到仓库根的相对前缀 |
ulimit -n(系统限制) | int | 视系统而定 | 需 > 8096,否则链接阶段可能因打开文件过多失败 |
失败模式、边界情况与并发注意
依据 Makefile 源码注释与结构,构建系统存在以下已知失败模式与边界情况:
工具链未安装或路径不符
顶层 Makefile 注释明确要求 Linux 下 clang 必须位于 /opt/jieli/common/bin/clang,而子工程 Makefile 使用的 Linux 工具链目录是 /opt/jieli/q32s/bin。二者不一致是最常见的坑:工具链解压层次不对(例如多套了一层目录),会导致 make 报 clang: command not found 或链接器找不到。解决方法是严格按 pkgman 文档解压,确保两个路径都存在。
Source: Makefile
链接期文件描述符耗尽
文件头注释明确警告:ulimit -n 结果不够大(建议大于 8096)时,链接阶段会因打开文件太多而失败。LTO 链接需要同时打开大量目标文件与 IR 文件,尤其在大工程下容易触顶。出现 Too many open files 类错误时应先执行 ulimit -n 8096 再重试。
Source: Makefile
平台差异导致的编译行为不同
EXT_CFLAGS 仅在 Linux 分支追加 -D__SHELL__。若在 Linux 下移除了该宏(或误改 Makefile),download.c 中依赖 __SHELL__ 的代码路径将不会被编译,可能导致烧录功能异常。同理,Windows 下不应添加该宏。这是刻意设计的平台差异,修改时需保持两分支一致语义。
Windows 下脚本编码问题
Windows 分支引入 FIXBAT := ../../../../../tools/utils/fixbat.exe,专门处理 post_build 脚本的 utf8→gbk 编码问题;Linux 下 FIXBAT := touch 为空操作。如果 Windows 构建出现批处理乱码/语法错误,应确认 fixbat.exe 存在且被执行。
并发构建
顶层 all 目标按依赖顺序(先 transfer 后 hid)串行递归调用子 make,两个子工程产物路径不同(同名的 objs/ 各自位于子目录内),因此不会相互覆盖。但两个工程都向 apps/app/post_build/bd47/ 输出同名 sdk.elf——若用户手动并行执行 make -j2 aw31n_transfer aw31n_hid,后写者会覆盖前者的 ELF,因此不应并行构建两个子工程;顶层默认串行行为是安全的。
扩展点
新增 demo 子工程
顶层 Makefile 的设计使新增工程成本极低:在 apps/demo/<name>/board/bd47/ 下创建仿照现有工程的 Makefile(复用工具链分支、CFLAGS、INCLUDES 模板),然后在顶层 Makefile 的 .PHONY 中增加 <name> 与 clean_<name> 两个目标,并加入 all / clean 的依赖列表即可。子工程内的 ROOT_PREFIX、OUT_ELF、POST_SCRIPT 相对路径按目录层级对应调整。
修改构建参数
- 切换优化策略:修改
CFLAGS中的-Oz / -Os / -O0组合; - 裁剪功能:通过调整
DEFINES中的宏(如APP_CASE_TRANSFER、NOFLOAT)或增删c_SRC_FILES条目控制固件内容与体积; - 版本号:
SDK_VERSION_CFG_DEFINE与SDK_VERSION_DATE_DEFINE决定编译进固件的版本信息,发版前需同步更新。
相关链接
- 顶层 Makefile
- transfer 子工程 Makefile
- hid 子工程 Makefile
- 相关目录:
apps/demo/transfer(透传 demo)、apps/demo/hid(HID demo)、apps/app/post_build/bd47/(烧录脚本与产物)、apps/app/bsp(BSP 源码)