构建系统与编译流程
本页介绍 fw-AC630N_BT_SDK(AC630/1N 系列通用蓝牙固件 SDK)的构建系统与编译流程:从根 Makefile 的顶层编排、rule.mk 的通用编译/链接/归档规则,到 SoC 平台配置、Windows 构建入口以及库(lib)与应用(apps)的递归构建机制。
Purpose and Scope
本页聚焦"如何构建固件"这一条端到端链路,覆盖:
- 根目录
Makefile提供的全部构建目标(make、make clean、make libs、make debug、make cbp等)及其编排逻辑; rule.mk中共享的编译、依赖生成、链接、静态库归档规则;- SoC 平台选择机制(
tools/platform/Makefile.$(SoC))与预编译库接入(include_lib/Makefile.include); - Windows 下的构建环境(
make_prompt.bat)以及 CodeBlocks 工程生成流程(cbp/winmake/winrelease); - 链接脚本
sdk.ld与后处理脚本的预处理生成机制。
不在本页范围、由其他页面承载的内容:具体蓝牙协议栈功能(SPP/HID/Mesh 等应用逻辑)、各外设驱动的内部实现、工具链的安装与下载方式(见仓库 README.md 的 Toolchain 章节)、固件烧录工具的使用细节。
Overview
fw-AC630N_BT_SDK 是基于 Zephyr RTOS 的 Jieli 源码增强仓库,必须与 lib.a(预编译静态库)以及遵循相同命名约定的其他仓库组合后才能构建出可烧录固件(见 README.md)。因此构建系统需要同时处理三类输入:
- 源码:仓库内的
apps/(应用层)、lib/(库源码)、cpu/$(CPU)/(芯片相关代码); - 预编译库:
include_lib/liba/$(CPU)/下的lib.a; - 工具链:JL Toolchain(基于 clang/LLVM 的 pi32 交叉工具链)。
构建系统采用经典的 GNU Make 递归构建 架构:根 Makefile 负责平台检测、SoC 选择与目标分发;rule.mk 被各子目录 Makefile 包含,提供统一的编译/链接/归档规则;每个 lib/ 子目录与应用目录各自维护独立 Makefile,由顶层目标递归调用。关键设计意图:
- 一套规则、多处复用:
rule.mk中的模式规则(pattern rule)对.c/.s/.S/.cpp统一处理,任何子工程包含该文件即可获得一致的编译行为; - 链接脚本动态生成:
sdk.ld不是手写文件,而是通过预处理器(-E -P)展开cpu/$(CPU)/sdk_ld.c生成,从而可以根据$(CPU)、$(SoC)等变量自动裁剪内存布局; - 双平台一致体验:同一套 Makefile 在 Linux 与 Windows 上均可工作,
HOST_OS自动检测差异(路径分隔符、依赖顺序、后处理脚本执行方式); - 增量构建:每个目标文件同时生成
.d依赖文件并通过-include $(deps)纳入构建图,头文件变化可触发精确重编。
SDK 官方支持两种构建方式(见 README.md):
- Codeblocks 构建:进入工程目录双击
.cbp文件直接构建; - Makefile 构建:在
apps/app_cfg选择目标,双击make_prompt打开构建环境后执行make。
Architecture
构建系统的整体架构如下:
flowchart TD
subgraph sg_Entry["构建入口"]
Make["make(根 Makefile)"]
Prompt["make_prompt.bat<br/>Windows 环境"]
end
subgraph sg_Top["顶层编排(Makefile)"]
All["all / app"]
Libs["libs / lib target=..."]
Clean["clean / clean_libs"]
Debug["debug(GDB)"]
CBP["cbp / winmake / winrelease"]
end
subgraph sg_Rules["通用构建规则(rule.mk)"]
Object["object(编译)"]
Out["out(链接)"]
Archive["archive(归档)"]
end
subgraph sg_Sub["递归子工程"]
Apps["apps/ 各应用 Makefile"]
Lib["lib/ 各库 Makefile"]
end
subgraph sg_Platform["平台与库配置"]
PlatMK["tools/platform/Makefile.$(SoC)"]
IncMK["include_lib/Makefile.include"]
end
subgraph sg_Tools["工具链与输出"]
CC["clang(CC)"]
LD["ld(LD)"]
AR["ar(AR)"]
ELF["OUTPUT_ELF 固件"]
Script["POST_BUILD_SCRIPT 后处理"]
end
Prompt -->|"设置 PATH 并打开 cmd"| Make
Make --> All
Make --> Libs
Make --> Clean
Make --> Debug
Make --> CBP
All --> Apps
Libs --> Lib
Apps --> Object
Lib --> Archive
Object --> Out
Archive --> Out
Out --> CC
Out --> LD
Out --> Script
Archive --> AR
LD --> ELF
PlatMK -->|"-include"| Make
IncMK -->|"-include"| Make
架构分层说明:
- 入口层:用户执行
make(Linux 或 Windows cmd);Windows 下可先运行make_prompt.bat将tools\utils加入PATH,保证make及辅助脚本可用。 - 顶层编排层(根
Makefile):负责检测HOST_OS、读取SoC变量、定义公共变量(ROOT、MAKE_RULE、AR_DIR等),并通过-include引入平台 Makefile 与库配置,然后按目标分发到apps/或lib/。 - 规则层(
rule.mk):被各子工程 Makefile 包含,定义了object(编译)、out(链接)、archive(归档)、version等通用目标以及.c/.s/.S/.cpp → .o的模式规则。根Makefile通过export MAKE_RULE = $(ROOT)/rule.mk暴露该文件路径。 - 子工程层:
apps/下的应用工程与lib/下的每个库各自提供 Makefile,它们定义本工程的源文件列表(objs、obj_ls、obj_bs、objs_cxx),并复用rule.mk完成实际构建。 - 平台/库配置层:
tools/platform/Makefile.$(SoC)定义芯片相关的工具链路径、CPU、CC/LD/AR、OUTPUT_ELF、DIR_OUTPUT、POST_BUILD_SCRIPT等;include_lib/Makefile.include负责接入include_lib/liba/$(CPU)下的预编译静态库(SYS_LIBS/LIBS)。
顶层 Makefile:构建编排入口
根目录 Makefile 是构建系统的总控文件。文件开头以注释形式给出了全部常用用法(Makefile):
# -----------------Usage --------------------------
# 1.编译apps: make;
# 2.清理apps: make clean;
# 3.编译全部libs: make libs;
# 4.清理全部libs: make clean_libs;
# 5.编译单独lib: make lib target=...;
# 6.清理单独lib: make clean_lib target=...;
# 7.GDB 调试: make debug;
来源:Makefile
主机平台检测与公共变量
构建系统在进入任何子工程前,先完成主机平台检测与公共变量导出(Makefile):
# --------------basic setting-----------------------
ifeq ($(shell uname),Linux)
export HOST_OS = linux
export SLASH=/
export ADDITION_DEP=libs
else
export HOST_OS = windows
export SLASH=\\
export ADDITION_DEP=
endif
# 默认注释编译信息(None/@)
export V=@
export jtag ?=n
# --------------common var begin-----------------------
export ROOT=$(abspath .)
export MAKE_RULE = $(ROOT)/rule.mk
export AR_DIR = \
$(ROOT)/include_lib/liba/$(CPU)
export AR_TARGET = $(shell find lib -maxdepth 1 -type d)
# --------------common var end-----------------------
关键设计点:
HOST_OS自动检测:通过uname区分 Linux/Windows。SLASH随之切换(/或\),保证路径拼接在两种平台都正确。ADDITION_DEP的平台差异:Linux 上为libs,Windows 上为空。这意味着 Linux 下执行make会先构建全部 libs 再构建 apps(因为all: pre_make $(ADDITION_DEP)),而 Windows 下预编译库已随 SDK 提供、无需重编,因此依赖为空——避免在 Windows 上重复编译大量库源码。V=@静默模式:默认用@抑制命令回显,只有+ CC xxx/+ AS xxx这类摘要信息;需要完整输出时可覆盖V为空。jtag ?=n:?=赋值允许用户在命令行覆盖(make jtag=y),用于切换 GDB/JTAG 调试模式。ROOT=$(abspath .):以仓库根目录为基准,所有子工程通过$(ROOT)引用绝对路径,避免递归 make 时相对路径错乱。AR_TARGET:通过find lib -maxdepth 1 -type d自动发现lib/下的全部库子目录,供dry_run等目标批量处理。
SoC 目标芯片选择
芯片型号通过 SoC 变量控制,默认 bd29(Makefile):
#配置下载目标SoC(br18/br21/br22/br23/br25/br26/bd29)
#export前面不要有空格,会导致文件sync异常
#export SoC?=br18
#export SoC?=br21
#export SoC?=br22
#export SoC?=br23
#export SoC?=br25
#export SoC?=br26
export SoC?=bd29
# export SoC?=br30
# export SoC?=br34
# export SoC?=br28
支持的芯片包括 br18/br21/br22/br23/br25/br26/bd29/br30/br34/br28。SoC 变量随后被 -include $(ROOT)/tools/platform/Makefile.$(SoC) 使用,即选择 tools/platform/Makefile.bd29 等平台文件。注释中特别强调"export 前面不要有空格,会导致文件 sync 异常"——这是 Makefile 语法陷阱(export 前空白会使该行不被识别为 export 指令),体现了该 SDK 对构建产物同步流程的敏感依赖。
核心目标定义
根 Makefile 声明了全部 phony 目标并定义了主要目标(Makefile):
.PHONY: all clean lib clean_lib libs clean_libs dry_run debug usage cbp winmake app
all: pre_make $(ADDITION_DEP)
@$(MAKE) -C apps || exit 1
app: pre_make
@$(MAKE) -C apps || exit 1
libs:
@$(MAKE) -C lib || exit 1
clean:
@echo "Clean ..."
@find . -name "*.d" -delete
@find . -name "*.o" -delete
uboot_bt:
@$(MAKE) clean -C lib/btctrler -f Makefile_uboot || exit
@$(MAKE) archive -C lib/btctrler -f Makefile_uboot || exit 1
all是默认目标:先执行pre_make(由平台 Makefile 提供的准备步骤),再按需构建libs(仅 Linux),最后递归进入apps/执行make;任何一步失败都通过|| exit 1立即中断并传递错误码。app只构建应用层,跳过库依赖。libs递归进入lib/构建全部库;clean用find全仓删除.d与.o,实现全量清理。uboot_bt是特例:用-f Makefile_uboot指定备用 makefile 构建lib/btctrler的 uboot 版本(先 clean 后 archive),说明同一库可以通过不同 makefile 产出不同变体。
调试与工程生成目标
TERMINAL :=gnome-terminal
debug:
$(SHELL) $(DIR_OUTPUT)/run_jtag.sh $(OUTPUT_ELF)
debug 目标直接执行平台 Makefile 生成的 run_jtag.sh 脚本,把 $(OUTPUT_ELF)(ELF 可执行文件)传给 GDB 调试环境;被注释掉的 gnome-terminal 行保留了旧版启动远程 GDB 的方式,供参考。
cbp、winmake、winrelease 三个目标用于为 Windows/CodeBlocks 用户生成独立工程(Makefile):
cbp:
$(V) python3 /opt/utils/gen_cbp.py --cc_path $(CC) --ar_path $(AR) \
--ld_path $(LD) --make_log make_log.txt \
--prefix `realpath .` \
--ignore_dirs `realpath lib` \
--gen_ld gen_sdk_ld.bat \
--gen_ld_cc C:\\JL\\pi32\\bin\\clang.exe \
--gen_ld_incs C:\\JL\\pi32\\pi32v2-include \
--outdir `realpath ../cbp_out` \
--cbp_title $(CBP_TITLE) \
--cbp_compiler $(CBP_COMPILER) \
$(CBP_ADD_OPT) \
--cbp_position $(CBP_POSITION) \
--post_build $(DIR_OUTPUT)/download.bat \
--other_files $(DIR_OUTPUT) $(OTHER_FILES)
$(V) python3 /opt/utils/gen_winmake.py --cc_path $(CC) --ar_path $(AR) \
--ld_path $(LD) --make_log make_log.txt \
--prefix `realpath .` \
--ignore_dirs `realpath lib` \
--outdir `realpath ../cbp_out` \
--other_files $(DIR_OUTPUT)/uboot.bin \
$(DIR_OUTPUT)/download.bat \
$(OTHER_FILES) \
--generate_entry make_prompt.bat \
--entry_template make_prompt.bat \
--other_dirs tools/utils
这些目标调用 /opt/utils/gen_cbp.py 与 /opt/utils/gen_winmake.py 两个 Python 生成器:
gen_cbp.py依据当前编译参数(CC/AR/LD路径、头文件、OTHER_FILES)生成 CodeBlocks 工程(.cbp),并附带gen_sdk_ld.bat(Windows 下重新生成sdk.ld的批处理)、download.bat(烧录脚本)等配套文件,输出到../cbp_out;gen_winmake.py进一步生成 Windows 版 make 环境(make_prompt.bat入口、uboot.bin、download.bat等),输出到../winmake_out;winrelease将cbp与winmake的输出合并到../merge_out,形成分发给最终用户的完整工程包。
OTHER_FILES 变量集中声明了必须随工程同步的额外文件(Makefile):
OTHER_FILES = apps/config/eq_tab.h
OTHER_FILES += apps/config/eq_tab_coeff.h
ifeq ($(APP_CASE),dongle)
OTHER_FILES += \
else
OTHER_FILES += apps/$(APP_CASE)/include/bt_ble.h \
endif
OTHER_FILES 会原样传给 Python 生成器,保证 eq_tab.h(EQ 参数表)、bt_ble.h(蓝牙配置头)等"生成/同步文件"也进入导出工程。APP_CASE 变量区分不同应用场景(如 dongle 与普通 app 的头文件差异),说明同一 SDK 可以通过该变量裁剪应用变体。
平台与库配置的挂载
根 Makefile 的末尾通过 -include 挂载平台与库配置(Makefile):
-include $(ROOT)/tools/platform/Makefile.$(SoC)
-include $(ROOT)/include_lib/Makefile.include
Makefile.$(SoC):按芯片型号加载平台定义(工具链路径、CPU、编译参数CC_ARGS/LD_ARGS、输出路径DIR_OUTPUT/OUTPUT_ELF、后处理脚本POST_BUILD_SCRIPT等),这也是pre_make、run_jtag.sh等目标的定义来源;Makefile.include:接入预编译库,定义SYS_LIBS/LIBS供链接阶段使用。
-include 前缀表示文件不存在时静默忽略,避免在缺少平台文件时直接报错中断。
rule.mk:通用编译与链接规则
rule.mk 是被各子工程(apps/ 与每个 lib/ 子目录)包含的共享规则文件,是"编译流程"的心脏。它假定调用方已定义 objs(C 目标)、objs_cxx(C++ 目标)、obj_ls(小写 .s 汇编目标)、obj_bs(大写 .S 汇编目标)四类源文件列表。
依赖文件推导
文件开头将所有目标转换为绝对路径,并由 .o 推导出 .d 依赖文件(rule.mk):
objs:= $(abspath $(objs))
objs_cxx:= $(abspath $(objs_cxx))
obj_ls:= $(abspath $(obj_ls))
obj_bs:= $(abspath $(obj_bs))
deps = $(objs:.o=.d)
deps += $(objs_cxx:.o=.d)
deps += $(obj_ls:.o=.d)
deps += $(obj_bs:.o=.d)
deps 是四类目标文件对应 .d 文件的并集。文件末尾的 -include $(deps)(rule.mk)将这些依赖文件纳入构建图——这是增量构建的基础:头文件变更时,make 依据 .d 中记录的依赖关系精确重编受影响的源文件。
目标依赖链
.PHONY: out archive clean dry_run
out: object
...(链接阶段,见下文)
archive: object
...(归档阶段,见下文)
run: object
@echo "dry run for YCM server"
object: version $(obj_ls) $(obj_bs) $(objs) $(objs_cxx) rm_lib
依赖链为 object → version + 全部目标文件 + rm_lib,即:先处理版本文件,再编译所有源码,最后按需清理旧库;out(链接)与 archive(归档)都依赖 object,保证任何产物生成前源码已是最新。
版本文件生成
version:
@[ -f cpu/$(CPU)/dual_uvc_version.z ] && $(VER) cpu/$(CPU)/dual_uvc_version.z cpu/$(CPU)/dual_uvc_version.S || true
如果存在 dual_uvc_version.z(版本描述文件),则用 $(VER) 工具把它展开成汇编文件 dual_uvc_version.S,使固件内嵌版本信息;文件不存在时 || true 容忍跳过。
编译模式规则
四类源文件对应四条模式规则,以 C 规则为例(rule.mk):
$(objs):%.o:%.c
@$(CC) $(CC_ARGS) $(CC_DEFINE) -MM -MT "$(<:.c=.o)" $(SYS_INCLUDES) $(includes) $< > $(@:.o=.d)
@echo + CC $<
ifeq ($(GEN_LIB),y)
$(V) -$(CC) $(SYS_INCLUDES) $(includes) $(CC_ARGS) $(CC_DEFINE) -c $< -o $@
else
$(V) $(CC) $(SYS_INCLUDES) $(includes) $(CC_ARGS) $(CC_DEFINE) -c $< -o $@
endif
汇编规则(.s/.S)与 C++ 规则(.cpp)结构相同,差异仅在预处理宏与回显前缀(+ AS、+ CXX)(rule.mk)。每个编译步骤的设计意图:
- 先产依赖后编译:
-MM -MT生成.d文件,-MT "$(<:.c=.o)"明确指定依赖文件描述的目标,避免路径推导歧义; -D__ASSEMBLY__:汇编规则额外定义__ASSEMBLY__宏,供头文件区分 C/汇编语境;GEN_LIB=y容错编译:库构建模式下命令前缀加-(-$(CC)),编译失败不中断 make——配合后续rm_lib删除旧库,宁可产出缺库也不让构建系统在中间库上继续链接,同时避免误报导致整个libs目标失败;- 静默回显:
@echo + CC $<在V=@下输出人类可读的编译进度。
链接流程(out 目标)
out 是应用工程的最终产物目标,完成链接脚本生成、后处理脚本生成与链接三步(rule.mk):
out: object
@$(CC) -MM $(SYS_INCLUDES) $(includes) -D__LD__ $(CC_DEFINE) $(ROOT)/cpu/$(CPU)/sdk_ld.c > $(ROOT)/cpu/$(CPU)/sdk_ld.d
@[ -f $(ROOT)/cpu/$(CPU)/tools/download.c ] && \
$(CC) -MM $(SYS_INCLUDES) $(includes) -D__LD__ $(CC_DEFINE) $(ROOT)/cpu/$(CPU)/tools/download.c > $(ROOT)/cpu/$(CPU)/tools/download.d || true
ifeq ($(NEED_USED_LIST),y)
@$(CC) -MM $(SYS_INCLUDES) $(includes) -D__LD__ $(CC_DEFINE) $(ROOT)/apps/$(APP_CASE)/sdk_used_list.c > $(ROOT)/apps/$(APP_CASE)/sdk_used_list.d
$(V) $(CC) $(SYS_INCLUDES) $(includes) -E -D__LD__ $(CC_DEFINE) -P $(ROOT)/apps/$(APP_CASE)/sdk_used_list.c -o $(ROOT)/apps/$(APP_CASE)/sdk_used_list.used
endif
$(V) $(CC) $(SYS_INCLUDES) $(includes) -E -D__LD__ $(CC_DEFINE) -P $(ROOT)/cpu/$(CPU)/sdk_ld.c -o $(ROOT)/cpu/$(CPU)/sdk.ld
ifneq (,$(wildcard $(ROOT)/cpu/$(CPU)/tools/download.c))
$(V) $(CC) $(SYS_INCLUDES) $(includes) -E -D__LD__ $(CC_DEFINE) -P $(ROOT)/cpu/$(CPU)/tools/download.c -o $(POST_BUILD_SCRIPT) || true
endif
$(V) $(LD) $(LD_ARGS) -o $(OUTPUT_ELF) $(objs) $(obj_ls) $(obj_bs) $(SYS_LIBS) $(LIBS) $(LINKER)
- 链接脚本生成:
sdk_ld.c是一个"用 C 语法书写的链接脚本模板",通过-E -P(预处理、去行号)展开为真正的sdk.ld;-D__LD__宏让同一文件在"作为链接脚本模板"与"作为普通 C 文件"两种语境下取不同分支。这是本 SDK 构建系统最有特色的设计:内存布局可以用 C 的宏与条件编译动态推导,按CPU/SoC自动适配。 - 后处理脚本生成:
cpu/$(CPU)/tools/download.c以同样的预处理方式生成$(POST_BUILD_SCRIPT)(即下载/烧录脚本,如download.bat)。文件不存在时用|| true/wildcard双重保护,保证可裁剪平台也能通过。 NEED_USED_LIST=y可选环节:为 app 生成sdk_used_list.used(被使用的 SDK 符号清单),供裁剪/审计使用。- 链接:
$(LD) $(LD_ARGS)将四类目标文件与$(SYS_LIBS)(系统库)、$(LIBS)(预编译 lib.a)以及$(LINKER)(脚本文件)链接为$(OUTPUT_ELF)。
链接后的收尾动作(rule.mk):
ifneq ($(cibuild),y)
ifneq ($(HOST_OS),windows)
$(V) /opt/utils/check-mix-diff-cpu $(OUTPUT_ELF).0.5.precodegen.bc || (/opt/utils/view-target-cpu $(LD_ARGS) -o $(objs) $(obj_ls) $(obj_bs) $(SYS_LIBS) $(LIBS) $(LINKER) && exit 1)
ifeq ($(jtag),n)
@cd $(DIR_OUTPUT) && bash $(POST_BUILD_SCRIPT) $(ELF)
else
@cd $(DIR_OUTPUT) && bash $(POST_BUILD_SCRIPT) $(ELF) "_jtag"
endif
endif
endif
ifeq ($(HOST_OS),windows)
@cd $(DIR_OUTPUT) && $(POST_BUILD_SCRIPT) $(ELF)
endif
- CPU 混用检查:
check-mix-diff-cpu校验precodegen.bc(LLVM 位码)的 CPU 目标;不一致时用view-target-cpu打印实际目标并exit 1中止——防止把为其他芯片编译的中间产物混入链接; - 后处理执行:Linux 下用
bash执行$(POST_BUILD_SCRIPT)并传入$(ELF)参数(jtag 模式下附加"_jtag"参数,生成带调试符号的变体);Windows 下直接执行; cibuild=y:CI 构建模式跳过本机后处理,说明该规则同时服务本地开发与 CI 流水线。
静态库归档(archive 目标)
lib/ 下的库工程使用 archive 目标产出 .a(rule.mk):
archive: object
$(V) $(AR) $(AR_ARGS) $(AR_OUT) $(objs) $(obj_ls) $(obj_bs) $(objs_cxx)
ifeq ($(OVERRIDE),y)
$(V) $(OVERRIDE_SEG) --input $(AR_OUT) --output $(AR_OUT_NEW) --code_seg ".$(MOUDLE_NAME).$(ORSEG_NAME).text"
endif
归档时把 C/C++/汇编目标一并打包进 $(AR_OUT)。OVERRIDE=y 时额外调用 $(OVERRIDE_SEG) 工具按 --code_seg ".$(MOUDLE_NAME).$(ORSEG_NAME).text" 重排代码段——这是 Jieli 的"段覆盖"机制:允许库模块的代码段独立命名,链接时可用同名段覆盖默认实现(典型用于 ROM 化/补丁场景)。
配套的 rm_lib 目标(rule.mk)在 GEN_LIB=y 时先删除旧归档文件,避免增量编译残留的过期目标污染新库:
rm_lib:
ifeq ($(GEN_LIB),y)
@[ -f $(AR_OUT) ] && rm $(AR_OUT) || true
endif
Windows 构建环境与入口
make_prompt.bat 是 Windows 用户进入构建环境的入口(make_prompt.bat):
SET SCRIPT_PATH=%~dp0%
set PATH=%SCRIPT_PATH%\tools\utils;%PATH%
cmd
脚本仅做两件事:把自身所在目录下的 tools\utils 加入 PATH(保证 make、Python 辅助脚本等工具可被直接调用),然后打开一个交互式 cmd 窗口。用户在 README 指引下于该窗口执行 make 即可构建(见 README.md)。tools\utils 同时被 cbp/winmake 目标通过 --other_dirs tools/utils 复制进导出工程,保证导出的 Windows 工程自包含。
核心流程:从 make 到固件
一次典型的 make(Linux 环境)端到端时序如下:
sequenceDiagram
participant Dev as 开发者
participant Top as 根 Makefile
participant Rule as rule.mk
participant CC as clang(CC)
participant LD as 链接器(LD)
participant Post as POST_BUILD_SCRIPT
Dev->>Top: make
Top->>Top: 检测 HOST_OS(uname)
Top->>Top: 读取 SoC=bd29 → include Makefile.bd29
Top->>Top: include include_lib/Makefile.include
Top->>Top: all → pre_make + libs(Linux)
Top->>Rule: $(MAKE) -C lib(libs 目标)
Rule->>Rule: object → version + 编译全部源文件
Rule->>CC: 模式规则编译 .c/.s/.S/.cpp → .o + .d
Rule->>Rule: archive → ar 打包 lib.a
Top->>Rule: $(MAKE) -C apps
Rule->>Rule: object 重新编译应用层
Rule->>CC: 预处理 sdk_ld.c → sdk.ld(-E -P -D__LD__)
Rule->>CC: 预处理 download.c → POST_BUILD_SCRIPT
Rule->>LD: 链接 objs + SYS_LIBS + LIBS → OUTPUT_ELF
Rule->>Post: check-mix-diff-cpu 校验
Rule->>Post: bash download.bat $(ELF)
Post-->>Dev: 生成可烧录固件 / 下载到目标板
流程要点解读:
- 环境准备:根 Makefile 先确定
HOST_OS与SoC,据此加载tools/platform/Makefile.bd29和include_lib/Makefile.include,得到工具链路径、编译/链接参数与库列表; - 库优先(Linux):
all的$(ADDITION_DEP)=libs使lib/全部库先被编译归档,之后应用链接时才能引用最新lib.a; - 应用编译:
$(MAKE) -C apps进入应用工程,应用 Makefile 包含rule.mk,走object → out依赖链; - 脚本生成:
out中用预处理方式从sdk_ld.c生成sdk.ld、从download.c生成烧录脚本——构建配置(内存布局、下载流程)本身由源码驱动; - 链接与后处理:链接产出
OUTPUT_ELF,经 CPU 混用校验后执行POST_BUILD_SCRIPT,最终得到可烧录镜像。
构建目标的分发关系可归纳为:
flowchart TD
M["make"] --> Q{"ADDITION_DEP?"}
Q -->|"linux: libs"| L["$(MAKE) -C lib<br/>(全部库 archive)"]
Q -->|"windows: 空"| A["$(MAKE) -C apps"]
L --> A
A --> O["object 编译"]
O --> U["out 链接"]
U --> P["后处理 + 烧录"]
M2["make lib target=xxx"] --> L2["仅编译指定库"]
M3["make clean"] --> C["删除全仓 .d/.o"]
M4["make debug"] --> D["run_jtag.sh + GDB"]
M5["make cbp/winmake"] --> G["Python 生成器导出工程"]
使用示例
基本用法:全量构建
按 README 指引,先在 apps/app_cfg 选择目标,再执行 make(README.md):
# 在仓库根目录执行(Windows 下先在 make_prompt 中打开 cmd)
make
等价于执行根 Makefile 的 all 目标(Makefile):
all: pre_make $(ADDITION_DEP)
@$(MAKE) -C apps || exit 1
清理与库操作
make clean # 删除全仓 *.d 与 *.o(增量构建痕迹)
make libs # 编译 lib/ 下全部库
make clean_libs # 清理全部库
make lib target=btctrler # 只编译指定库
make clean_lib target=btctrler
make usage # 打印全部目标用法
工程导出(为 Windows/CodeBlocks 用户)
make cbp # 生成 CodeBlocks 工程到 ../cbp_out
make winmake # 生成 Windows make 工程到 ../winmake_out
make winrelease # 合并两者到 ../merge_out
cbp 目标调用 gen_cbp.py 时会把当前编译环境参数固化为工程配置(Makefile):
cbp:
$(V) python3 /opt/utils/gen_cbp.py --cc_path $(CC) --ar_path $(AR) \
--ld_path $(LD) --make_log make_log.txt \
--prefix `realpath .` \
--ignore_dirs `realpath lib` \
--gen_ld gen_sdk_ld.bat \
--gen_ld_cc C:\\JL\\pi32\\bin\\clang.exe \
--gen_ld_incs C:\\JL\\pi32\\pi32v2-include \
--outdir `realpath ../cbp_out` \
--post_build $(DIR_OUTPUT)/download.bat \
--other_files $(DIR_OUTPUT) $(OTHER_FILES)
调试
make jtag=y debug # 通过 run_jtag.sh 启动 GDB 调试(可附加 jtag 变体)
debug 目标(Makefile):
debug:
$(SHELL) $(DIR_OUTPUT)/run_jtag.sh $(OUTPUT_ELF)
配置选项
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
SoC | string | bd29 | 目标芯片型号,可选 br18/br21/br22/br23/br25/br26/bd29/br30/br34/br28;决定加载 tools/platform/Makefile.$(SoC) |
HOST_OS | string | 自动检测(uname) | linux 或 windows;决定 SLASH、ADDITION_DEP 与后处理执行方式 |
SLASH | string | /(Linux)/ \(Windows) | 路径分隔符 |
ADDITION_DEP | string | libs(Linux)/ 空(Windows) | all 目标的额外前置依赖,控制是否先编译全部库 |
V | string | @ | 命令回显控制;置空可显示完整编译命令 |
jtag | string | n | y 时后处理脚本附加 _jtag 参数,产出 JTAG 调试变体 |
APP_CASE | string | 由应用 Makefile 定义 | 应用场景标识(如 dongle),影响 OTHER_FILES 等 |
GEN_LIB | string | 未设置 | y 时编译失败不中断(容错),且归档前删除旧 AR_OUT |
OVERRIDE | string | 未设置 | y 时启用 OVERRIDE_SEG 代码段覆盖机制 |
NEED_USED_LIST | string | 未设置 | y 时生成 sdk_used_list.used 符号使用清单 |
cibuild | string | 未设置 | y 时跳过本机后处理,适配 CI 环境 |
CPU | string | 由 Makefile.$(SoC) 定义 | 芯片内核标识,决定 cpu/$(CPU) 目录与 AR_DIR 路径 |
CC / LD / AR | path | 由 Makefile.$(SoC) 定义 | 工具链可执行文件路径(clang / ld / ar) |
OUTPUT_ELF | path | 由 Makefile.$(SoC) 定义 | 链接输出的 ELF 文件路径 |
DIR_OUTPUT | path | 由 Makefile.$(SoC) 定义 | 构建输出目录 |
POST_BUILD_SCRIPT | path | 由 Makefile.$(SoC) 定义 | 由 download.c 预处理生成的后处理脚本 |
LIBS / SYS_LIBS | list | 由 include_lib/Makefile.include 定义 | 链接阶段使用的预编译静态库 |
ROOT | path | $(abspath .) | 仓库根目录绝对路径 |
MAKE_RULE | path | $(ROOT)/rule.mk | 共享规则文件路径(export 给子工程) |
AR_TARGET | list | find lib -maxdepth 1 -type d | lib/ 下全部库子目录 |
OTHER_FILES | list | eq_tab.h、eq_tab_coeff.h、bt_ble.h | 随工程导出/同步的额外文件 |
API 参考:Make 目标
| 目标 | 命令 | 行为 |
|---|---|---|
all | make | 默认目标:pre_make + 按 ADDITION_DEP 构建 libs,再递归 $(MAKE) -C apps;失败即 exit 1 |
app | make app | 仅编译应用层(跳过 libs) |
libs | make libs | 递归 $(MAKE) -C lib 编译全部库 |
clean | make clean | find 删除全仓 *.d 与 *.o |
lib | make lib target=NAME | 编译单个指定库(target 指定库目录) |
clean_lib | make clean_lib target=NAME | 清理单个指定库 |
clean_libs | make clean_libs | 清理全部库 |
debug | make debug | 执行 $(DIR_OUTPUT)/run_jtag.sh $(OUTPUT_ELF) 启动 GDB |
uboot_bt | make uboot_bt | 以 -f Makefile_uboot 构建 lib/btctrler 的 uboot 版本 |
dry_run | make dry_run | 对所有库与应用执行编译干跑(供 YCM 服务器索引) |
cbp | make cbp | 调用 gen_cbp.py/gen_winmake.py 生成 CodeBlocks 工程到 ../cbp_out |
winmake | make winmake | 生成 Windows make 工程到 ../winmake_out |
winrelease | make winrelease | 合并 cbp 与 winmake 输出到 ../merge_out |
usage | make usage | 打印全部目标用法说明 |
rule.mk 内部目标(子工程使用):
| 目标 | 前置依赖 | 行为 |
|---|---|---|
object | version + 全部源文件 + rm_lib | 编译所有目标文件并生成 .d 依赖 |
out | object | 生成 sdk.ld 与后处理脚本,链接产出 OUTPUT_ELF,执行后处理 |
archive | object | 用 ar 打包目标文件为 AR_OUT,可选 OVERRIDE_SEG 段覆盖 |
version | — | 用 $(VER) 将 dual_uvc_version.z 展开为 dual_uvc_version.S |
run | object | YCM 干跑占位 |
rm_lib | — | GEN_LIB=y 时删除旧归档 |
失败模式、边界情况与并发
编译失败的中断语义
- 应用构建是严格失败的:
$(MAKE) -C apps || exit 1与$(MAKE) -C lib || exit 1保证任何子工程失败都会向上传递错误码,make立即中止(Makefile)。 - 库构建是容错失败的:
GEN_LIB=y时编译命令加-前缀(-$(CC)),单文件编译失败不中断(rule.mk)。此时配合rm_lib删除旧库,避免"半新半旧"的库被后续链接误用——代价是链接阶段可能因缺库报错,但错误信号更真实。
平台差异导致的边界行为
- Windows 上
all的ADDITION_DEP为空,不会预编译 libs,直接构建 apps;若用户修改了库源码却未重编库,链接将使用旧lib.a。这是设计取舍:Windows 默认假设预编译库是交付物的一部分。 - Windows 上跳过
check-mix-diff-cpuCPU 校验(ifneq ($(HOST_OS),windows)),而 Linux 上强制校验(rule.mk)。 sdk_ld.c/download.c的依赖文件生成使用&& ... || true与wildcard双保险:文件不存在时静默跳过,保证裁剪平台(无tools/download.c)也能通过编译。
并发构建
Makefile 未在内部强制 -j 并行度,依赖 .d 的增量机制天然支持用户自行执行 make -jN 并行编译。需要关注的并发风险:
object依赖链中version会写cpu/$(CPU)/dual_uvc_version.S,而该文件若同时是其他目标的源文件,并行构建下存在竞争写入风险;SDK 通过把version置于所有源文件之前(object: version $(obj_ls) ...)串行化此步骤来规避;out目标中多个-MM/-E步骤共享sdk.ld输出路径,同一时刻仅允许一个out在执行——链接与脚本生成天然串行;rm_lib删除AR_OUT与archive打包之间若被并行调用会互相干扰,因此archive: object的依赖顺序保证清理先于打包完成。
其他已知边界
export SoC?=bd29注释明确警告"export 前面不要有空格,会导致文件 sync 异常":Makefile 语法要求export必须是行首指令,否则变量不会导出到子 make,构建产物同步流程将失效;OTHER_FILES在APP_CASE=dongle时条件为空(OTHER_FILES += \后无内容),此时若make cbp导出工程,EQ 头文件不会被同步——这是面向 dongle 变体的显式裁剪;dry_run目标对每个库依次执行clean → dry_run → clean,其目的是为 YCM 生成索引数据库,不是真实构建;中途失败不会中断(循环体内无exit),符合"干跑"语义。
性能与运维注意
- 增量构建:每文件生成
.d,头文件变更触发精确重编,避免全量重编;make clean删除全部.d/.o即回到全量构建状态。 - 静默输出:
V=@默认仅打印+ CC/+ AS摘要,大量文件构建时输出量小、日志可读;排查问题时可make V= clean观察完整命令。 - CPU 混用防线:
check-mix-diff-cpu在链接后校验 LLVM 位码的 CPU 目标,防止include_lib中混入错误芯片的预编译产物导致固件无法启动——这是固件类项目特有的可靠性闸门。 - CI 适配:
cibuild=y跳过本机后处理脚本,服务器上只需产出OUTPUT_ELF与镜像,烧录步骤留给后续流水线。 make debug依赖$(DIR_OUTPUT)/run_jtag.sh(由平台 Makefile 提供),GDB 调试前需确保 JTAG 代理已启动。
扩展点
构建系统为二次开发预留了清晰的自定义入口:
- 新增芯片:在
tools/platform/下新增Makefile.$(新SoC),定义CPU、工具链、输出路径与后处理脚本;在根Makefile的 SoC 列表中启用即可。 - 新增库:在
lib/下新建目录并放置 Makefile(定义objs等变量、包含$(MAKE_RULE)、声明archive目标);AR_TARGET的find自动发现机制与libs目标会将其纳入构建。 - 新增应用:在
apps/下新增应用目录,复用rule.mk的out目标完成链接;APP_CASE变量区分应用变体并驱动OTHER_FILES同步。 - 段覆盖(patch/ROM 化):库 Makefile 设置
OVERRIDE=y并配合OVERRIDE_SEG的--code_seg ".$(MOUDLE_NAME).$(ORSEG_NAME).text",可将模块代码段独立命名,供链接期覆盖。 - 工程导出定制:
cbp/winmake支持CBP_TITLE、CBP_COMPILER、CBP_ADD_OPT、CBP_POSITION等变量,以及OTHER_FILES追加同步文件,可定制导出的 IDE 工程。 - 链接脚本模板:修改
cpu/$(CPU)/sdk_ld.c即可用 C 宏/条件编译调整内存布局,无需手工维护sdk.ld。
测试与验证
仓库根目录未提供独立的构建测试套件;构建系统的"测试"即对 apps/spp_and_le、apps/hid、apps/mesh 三个官方示例工程的编译验证(见 README.md):
apps/spp_and_le:SPP + LE 双模示例;apps/hid:HID 设备示例;apps/mesh:蓝牙 Mesh 示例。
对任何自定义修改,建议至少执行 make clean && make 全量重编并确认 check-mix-diff-cpu 通过、POST_BUILD_SCRIPT 正常产出烧录镜像。
相关链接
- README.md(构建说明与 Toolchain 获取)
- 根 Makefile(顶层编排与全部目标)
- rule.mk(通用编译/链接/归档规则)
- make_prompt.bat(Windows 构建环境入口)
- 平台配置:
tools/platform/Makefile.$(SoC)(如Makefile.bd29) - 库配置:
include_lib/Makefile.include与include_lib/liba/$(CPU)/ - 官方示例工程:
apps/spp_and_le、apps/hid、apps/mesh