尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

RT-Thread构建与配置系统全解析:SCons、Kconfig与menuconfig实战

发布时间:2026/9/29 9:30:34

资讯中心
01
ARTICLE

RT-Thread构建与配置系统全解析:SCons、Kconfig与menuconfig实战

RT-Thread构建与配置系统全解析:SCons、Kconfig与menuconfig实战
做了这么多年嵌入式接触过各种RTOS和工程构建方式RT-Thread的构建与配置系统是我用过最顺手的一套组合。它把SCons、Kconfig、menuconfig和env工具串成了一条完整链路编译、裁剪、扩展组件基本可以做到一条命令完成不用像以前那样手改Makefile或在一个个IDE工程里来回勾选。这篇文章把RT-Thread的构建与配置系统从原理到实操都拆开讲一遍适合刚入门RT-Thread、想把项目从STM32裸机迁移到RTOS的开发者也适合想搞明白“scons到底在干什么”的进阶用户。我会先讲整体的设计思路再拆解menuconfig和rtconfig.h的关系、SConscript脚本的写法最后给一个完整的驱动模块添加流程和排错方案看完你就能动手改造自己的板子。1. RT-Thread的构建与配置系统全景1.1 你要会的那三板斧env、scons、menuconfig如果你用过RT-Thread一定听过“用env工具编译工程”这个说法。env不是编译器不是一个IDE它是一个集成环境外壳帮我们临时配置好Python、SCons、编译工具链这些环境变量然后在当前目录下执行scons或者scons -j8就能把固件编译出来。你还可以通过scons --targetmdk5直接生成Keil MDK5工程生成完用Keil打开继续开发调试。这套流程用顺手之后非常舒服。整个系统的角色分配是这样的SCons真正的构建引擎类似Makefile里的make但它用Python脚本描述构建过程跨平台、可编程能力极强。Kconfig/menuconfig负责“配置裁剪”让你通过一个树状菜单勾选要哪些功能、不要哪些功能。SConscript以脚本文件形式定义每个组件/目录怎么参与编译负责把源文件、头文件路径、编译选项交给很多人。rtconfig.hmenuconfig配置后生成的头文件是源码里判断功能是否开启的唯一依据。整个系统的逻辑就用#ifdef RT_USING_XXX这样的宏来展开或关闭代码。env工具把以上命令聚合起来还提供menuconfig命令的调用入口和pkgs包管理器的支持。看到这里你应该明白RT-Thread的构建与配置系统不是一个单一的编译脚本而是一套覆盖“配置→生成→依赖解析→编译→导出工程”全流程的工具链组合。单独看scons你会莫名其妙单独用menuconfig你也感受不到它的威力只有把它们串起来理解你才算真正入了门。1.2 为什么RT-Thread选SCons而不是纯Makefile说到嵌入式构建大家第一时间想到的是Makefile或者Keil/IAR里的自动编译。RT-Thread没有走这条路而是选了SCons我刚开始也觉得奇怪踩过几个坑之后才理解这个选择有多聪明。Makefile的老问题是跨平台能力弱Windows/Linux/macOS上的写法有差异递归make的时候依赖关系经常出问题。而且嵌入式工程有大量条件判断某颗芯片要不要这个驱动、某个组件依赖另一个组件、头文件搜索路径要随配置动态变化。写Makefile的人都知道这些逻辑一旦复杂起来就变成了“能跑就行但没人敢改”的状态。SCons本质上是用Python写构建规则天然具备编程能力跨平台运行也做得很好。RT-Thread在SCons之上又封装了一层让我们只需要写一个SConscript文件里面用DefineGroup声明一个组件然后用Glob(*.c)自动收集当前目录的源文件再指定头文件路径和依赖项剩下的交给框架去处理。这个设计让“新增一个驱动目录”的成本变得很低这也是RT-Thread能做成组件化生态的一个重要基础。有人会问那我直接用Keil工程不是更方便吗确实很多新手还是习惯打开Keil直接Add Group、Add Files。但问题是一旦你的应用代码量大、外设驱动多、软件包依赖深手动维护工程几乎不可能。RT-Thread的处理方式是你先用scons管理源码层面的构建最后再通过scons --targetmdk5导出工程给IDE使用配置文件和源文件的增删都由脚本自动完成。你只在IDE里做编译调试工程生成的脏活累活全交给脚本。2. 配置系统Kconfig和menuconfig是怎么工作的2.1 menuconfig从启动到生成rtconfig.h的完整流程很多人第一次用RT-Thread都是在BSP目录下执行menuconfig看到蓝色界面一堆选项以为这就是个配置工具其实背后发生的事情比你想象的多。menuconfig命令本身来自Linux内核社区那套Kconfig配置系统RT-Thread把它移植了过来。你在源码树的顶层通常是BSP目录下也就是rtconfig.h所在的目录执行menuconfig它会读取根目录下的Kconfig文件这个Kconfig文件是一棵逻辑树的开端它通过source指令把各个子模块的Kconfig文件引入进来层层展开成菜单。接下来你在界面里做的每一次切换本质上是修改一个叫.config的文件。这个文件保存了当前所有配置项的值比如CONFIG_RT_USING_XXXy CONFIG_XXX_ENABLE1当你选择“保存并退出”环境工具会调用内部脚本把.config和rtconfig.h做一次同步。同步的结果就是生成一个全新的rtconfig.h头文件里面全是#define RT_USING_XXX这样的宏定义。此后源码里所有#ifdef RT_USING_XXX的地方才开始真正生效。这里有个关键点menuconfig本身不会编译任何东西它只负责产出配置文件和头文件。真正吃这些配置的是SConscript和代码本身。所以在menuconfig改完配置之后一定要重新运行scons -j8才会生效而且建议在重新编译前先执行scons -c清理一次防止旧的编译产物导致一些莫名其妙的链接错误。我自己的做法是在BSP目录下执行menuconfig改完保存退出后直接执行scons -j8如果只是调整宏配置基本都能正确增量编译。但如果我改了文件目录结构、增删了源文件我会先scons -c清理再全量编译一把避免残留的.o文件“骗”过构建系统。2.2 Kconfig语法与配置项如何被源码消费理解Kconfig语法不需要学得很深你会看几个关键词就够了。一个最小的配置项长这样menu Demo Device Driver config RT_USING_DEMO bool Enable Demo Device default y help Say Y here to enable demo device. endmenu这个配置项在menuconfig里会显示成一个开关选项名为“Enable Demo Device”。它对应的环境变量是CONFIG_RT_USING_DEMO最终生成到rtconfig.h里就是#define RT_USING_DEMO 1默认值是y表示默认开启。你还可以用depends on表达依赖关系比如config RT_USING_DEMO bool Enable Demo Device depends on RT_USING_SERIAL default y意思是只有打开了串口这个Demo设备才可选择。这种依赖关系在复杂系统里非常有用它能够在配置阶段就避免你选择“不可能存在”的组合省去很多编译期的报错。那rtconfig.h里的宏是怎么被消费的有两个方向。第一个方向是给源代码用的比如你的驱动文件里#ifdef RT_USING_DEMO static int demo_device_init(void) { // 注册设备相关代码 } INIT_BOARD_EXPORT(demo_device_init); #endif当宏没定义时这段代码直接不会参与编译。第二个方向是给SConscript用的后续会讲到SConscript可以通过GetDepend([RT_USING_DEMO])判断这个宏是否打开从而决定要不要把当前目录下的源文件加入编译列表。所以整个逻辑链就是Kconfig定义选项→menuconfig修改选项→生成rtconfig.h宏定义→源码和SConscript按宏定义决定编不编译。这一环扣一环的链路就是整套配置系统运行的核心机制。3. SConscript脚本RT-Thread组件化的底座3.1 group把源文件打包成可复用的组件在RT-Thread的源码树里几乎每个组件目录都放着一个SConscript文件。这个文件的核心工作就是调用DefineGroup把一个目录下的源文件、头文件、定义和依赖打包成一个逻辑组件。一个最常见的SConscript模板是这样import os from building import * cwd GetCurrentDir() src Glob(*.c) CPPPATH [cwd] group DefineGroup(demo_driver, src, depend [RT_USING_DEMO], CPPPATH CPPPATH) Return(group)这里逐行解释一下GetCurrentDir()返回当前脚本所在的目录路径这个路径后面会被添加到头文件搜索路径里。Glob(*.c)自动匹配当前目录下所有.c文件生成源文件列表。如果你有子目录需要递归可以用Glob(*.c) Glob(subdir/*.c)或者直接用Glob(**/*.c)匹配所有子目录。CPPPATH [cwd]意思是把当前目录作为头文件搜索路径。DefineGroup的第一个参数是组件名称第二个是源文件列表第四个参数是关键表示这个组件依赖RT_USING_DEMO这个宏只有在rtconfig.h里打开这个宏源文件才会被真正加入编译。这种写法把“哪些文件要编”和“这个组件依赖什么功能”维护在同一个文件里逻辑非常直观。我自己新增驱动模块的时候都是直接复制一个现成的SConscript改一下路径和依赖宏几分钟就能接入构建系统。3.2 条件编译与依赖管理depend的本质DefineGroup里的depend参数本质上是在帮你管理编译列表。RT-Thread的构建框架在收集所有组件后会根据rtconfig.h里实际的宏定义过滤掉那些依赖未开启的组件。例如你定义了一个组件依赖RT_USING_DEMO但配置系统里没有打开这个宏那么整个组件的源文件都不会出现在编译列表里头文件搜索路径也不会加入工程。这个过滤机制的实现原理其实不复杂SCons构建框架会维护一个所有组件的大列表然后通过GetDepend函数获取配置项当前的值最终决定组件的去留。不过我们大多数时候不需要深究内部实现只要知道一个源文件能参与到编译中需要同时满足两个条件一是它的SConscript被正常执行并返回了group二是它的depend依赖在rtconfig.h中已定义。如果你在添加自己代码的时候遇到“明明文件在但编译没包含”的问题十有八九是depend里写的宏没打开。排查办法很简单打开rtconfig.h搜索对应的宏名确认它是否存在。如果不存在就去menuconfig里找到对应配置项打开再重新生成头文件。3.3 SConscript的目录层级递归机制RT-Thread的工程树很深从BSP目录往下是Libraries、drivers、applications再往上是components、examples等。这么多目录SCons是怎么知道每个目录都要执行里面的SConscript的关键在于各个层级都会有一个顶层的SConscript文件通过objs objs SConscript(子目录/SConscript)这种形式把子目录的构建对象串联起来。你可以在任意一个BSP目录下的顶层SConscript里看到类似结构objs [] objs objs SConscript(../../Libraries/HAL_Drivers/SConscript) objs objs SConscript(drivers/SConscript) objs objs SConscript(applications/SConscript)这样一层一层递归下去最终把整个工程里所有需要编译的目录都汇集到一棵“对象树”里SCons再根据这棵树生成最终编译和链接规则。这个机制的好处是组件边界非常清晰你完全可以在不影响其他组件的情况下新增一个独立目录并在顶层SConscript里加上一行就能集成进整个工程。4. 一次完整的构建流程拆解4.1 工具链与rtconfig.py交叉编译如何生效在真正执行scons之前你要先确认工具链配置正确。RT-Thread通过rtconfig.py文件来声明编译器路径、编译器前缀和编译选项。这个文件通常放在BSP目录下由env工具自动生成也可以手动修改。以常见的ARM GCC为例rtconfig.py里会有一段类似这样的内容import os if os.getenv(RTT_CC): CROSS_TOOL os.getenv(RTT_CC) else: CROSS_TOOL gcc if os.getenv(RTT_ROOT): RTT_ROOT os.getenv(RTT_ROOT) else: RTT_ROOT rD:\RT-ThreadStudio\repo\rt-thread PLATFORM gcc EXEC_PATH rD:\RT-ThreadStudio\tools\gnu_gcc\arm_gcc\mingw\bin PREFIX arm-none-eabi-EXEC_PATH告诉你编译器可执行文件在哪。PREFIX是交叉编译工具链的前缀比如arm-none-eabi-gcc里的arm-none-eabi-。RTT_ROOT指向RT-Thread源码根目录env工具会通过环境变量设置它手动搭建环境时经常在这里出错。如果你用env工具它会自动把EXEC_PATH和RTT_ROOT配置好不需要手动改。如果你在Linux服务器上做CI编译那多半需要手动修改这份文件的路径和前缀指向你服务器上安装的交叉编译工具链。改完记得用scons --verbose验证一下实际调用的编译器路径是否正确。4.2 scons -j8到固件输出一次实际编译的完整命令链当你执行scons -j8RT-Thread的构建系统做的事情远不止“把C文件编译成.o再链接”这么简单。整个过程大致可以分为这几步第一步SCons读取顶层SConscript然后递归执行所有子目录的SConscript收集全部源文件、头文件路径、宏定义和库路径。这个过程非常快因为是纯Python解析没有调用外部编译器。第二步SCons根据收集到的信息生成编译任务核心是生成大量的.o文件。每条编译命令大概长这样arm-none-eabi-gcc -c -mcpucortex-m7 -mthumb -mfpufpv5-d16 -mfloat-abihard -DSTM32H743xx -DRT_USING_H743 -I. -Iapplications -Idrivers -I.Libraries/HAL_Drivers -IC:/RT-Thread/repo/include ...这里面能看到芯片型号的宏定义、头文件搜索路径、优化选项等全部是根据你的配置和SConscript自动组合出来的。第三步所有目标文件收集完成后SCons生成链接命令调用arm-none-eabi-gcc的链接器把所有.o文件、静态库一起链接生成最终固件rtthread.elf然后再调用arm-none-eabi-objcopy生成rtthread.bin和rtthread.hex方便烧录。想要看到每一条真实编译命令执行scons --verbose即可。遇到编译报错时我通常都是先跑一次--verbose把实际的命令提出来手动执行一遍这样能快速区分是命令行问题、头文件路径问题还是源码本身的问题。4.3 工程导出scons如何生成Keil/IAR工程RT-Thread有一个非常实用的能力导出IDE工程。你只需要在配置完成、源码就绪后执行scons --targetmdk5就能在当前目录下生成.uvprojx工程文件直接用Keil打开编译。同理scons --targetiar生成IAR工程scons --targetvs生成Visual Studio工程。由于RT-Thread的构建系统已经有完整的源文件、头文件目录和宏定义信息它生成的目标IDE工程是完全自洽的不需要你在IDE里手工调整任何东西。这个导出功能的底层机制并不神秘SCons的project生成器会把收集到的信息转换成IDE工程文件的XML格式。但它在实际开发中有很强的实用价值。我的习惯是在Linux服务器上维护一份完整的源码和配置导出工程到本地Windows用Keil调试两边共用一份源码不用同步两份工程文件。如果你对Keil比较熟完全可以把它当成RT-Thread的“前端”而SCons是“后端”后端管逻辑前端管调试。5. 手把手从零添加一个自己的硬件驱动模块5.1 规划目录与依赖关系理论讲完直接上手一个最典型的场景给一块新板子添加一个自定义驱动模块。假设这个驱动的名字叫demo_sensor功能大概是从某个I2C传感器读到数据然后通过RT-Thread的设备框架注册成标准设备。第一步是规划目录。在BSP目录下的drivers子目录里新建一个demo_sensor文件夹里面放四个文件drivers/demo_sensor/ ├── Kconfig ├── SConscript ├── demo_sensor.c └── demo_sensor.h依赖关系也要先想清楚这个驱动基于I2C总线所以要在配置层面保证RT_USING_I2C是打开的设备注册依赖RT-Thread的设备框架所以要依赖RT_USING_DEVICE。随后在Kconfig里把这两个依赖写清楚menuconfig会自动帮你做校验如果没开I2C这个驱动选项会直接置灰。5.2 编写Kconfig和SConscript然后写Kconfig放在drivers/demo_sensor/Kconfig里menuconfig RT_USING_DEMO_SENSOR bool Enable Demo Sensor Driver default n depends on RT_USING_I2C if RT_USING_DEMO_SENSOR config DEMO_SENSOR_I2C_BUS string I2C bus name default i2c1 endif菜单让人能选择是否启用这个驱动还预留一个配置项来填写I2C总线的名字。default n表示默认关闭防止一开始引入没做好的代码就把工程带崩。编写SConscriptimport os from building import * cwd GetCurrentDir() src Glob(*.c) CPPPATH [cwd, str(Dir(.))] group DefineGroup(DemoSensor, src, depend [RT_USING_DEMO_SENSOR], CPPPATH CPPPATH) Return(group)这里有个地方值得说明depend [RT_USING_DEMO_SENSOR]这个依赖项必须和Kconfig里的宏名完全一致否则会出现“你在menuconfig里开了但SConscript还是不编这个驱动”的问题。拼写不一致是这类问题里最高频的坑没有之一。最后把新的子目录挂进上层SConscript。在drivers目录的SConscript里加上objs objs SConscript(demo_sensor/SConscript)这一步千万别漏漏了之后整个demo_sensor目录根本不会被执行到源码也就不会被收集进工程。5.3 menuconfig启用并编译验证完成规划和脚本编写后回到BSP目录下执行menuconfig在菜单中进入Drivers → 找到“Enable Demo Sensor Driver”按空格勾选如果I2C没开它会显示灰色要先回到I2C选项打开。保存退出然后执行scons -c scons -j8编译时重点观察终端输出里是否有.o文件和.d文件的生成以及链接阶段是否有符号缺失。编完之后强烈建议再导一次工程scons --targetmdk5打开Keil确认demo_sensor目录确实出现在工程树里以及rtconfig.h中自动生成了RT_USING_DEMO_SENSOR宏。这一步能确认配置系统、构建脚本和IDE工程三方完全同步。我早年踩过一次坑只改menuconfig没重新导出工程结果Keil里编译的还是一份旧代码排查了一个多小时才发现是工程文件没刷新。6. 高频问题与排查技巧实录6.1 环境与工具链问题问题1cmd里找不到scons命令这是最常见的环境问题。env工具的本质是帮你设置环境变量如果你在普通的命令行终端执行scons大概率会提示“不是内部或外部命令”。正确做法是在env工具打开的终端里操作或者手动把env工具目录下的Python和Scripts目录加入系统环境变量PATH。如果不想每次开env也可以手动配置好Python、SCons、编译工具链的PATH就能在自己的终端里编译了。问题2编译时出现cc1.exe无法执行这个报错通常意味着EXEC_PATH配错了或者交叉编译器和你当前系统架构不匹配。比如在Windows下用了Linux版本的工具链肯定跑不起来。解决方案是打开rtconfig.py检查EXEC_PATH是否指向真实的编译器目录PREFIX是否正确然后重新打开终端让环境变量生效。6.2 配置与编译问题问题3menuconfig保存后rtconfig.h没有更新正常情况下保存退出后rtconfig.h会同步更新但偶尔也会出现没刷新。先检查终端里是否有报错信息常见原因是路径中包含中文字符或空格导致Kconfig生成脚本无法写入。解决方法是把整个工程放到纯英文、无空格的目录下。另外如果你手动改过rtconfig.hmenuconfig会提示确认覆盖选择同意后才能同步。问题4源码里明明写了#ifdef RT_USING_DEMO却总是编译不进去首先确认rtconfig.h里真的生成了这个宏其次确认这个宏出现在正确的位置不要在Kconfig里定义成RT_USING_DEMO但代码里用的是RT_USING_DEMO_SENSOR对不上肯定不行。如果宏都对再来查SConscript的depend参数它同样决定了这个模块要不要编译。这两个位置必须同时满足条件。6.3 构建脚本踩坑问题5新增源文件后编译没生效在SCons里Glob(*.c)是在解析SConscript时立即匹配的。如果你在编译执行之后才往目录里丢文件建议先执行scons -c清理再重新编译。SCons的依赖系统虽然会检查文件变化但很多情况下源文件列表的刷新不如全量清理来得干净尤其是你把源文件从一个目录移动到另一个目录时。遇到这种情况不要犹豫清理重编永远是最快的解法。问题6链接时提示重复定义大多数情况是同一个源文件被多个SConscript同时收集到了。比如你在驱动目录的SConscript里用Glob(*.c)又在它父目录的SConscript里用递归匹配包含了一遍。排查时打开--verbose查看编译列表找出重复的.o文件然后把多余的一组收集方式去掉即可。另外如果目录下有xxx.bak.c这样的文件被Glob匹配进去了也会出现重复定义。所以建议不要用Glob(*.c)全量收集的目录放置任何非参与编译的c后缀文件临时文件用.txt后缀保存。6.4 常见问题速查表问题描述可能原因排查顺序scons不是内部或外部命令环境变量未设置确认在env工具终端运行cc1.exe无法执行编译工具链路径或版本错误检查rtconfig.py里的EXEC_PATH和PREFIX编译不包含新增源文件Glob匹配未刷新或SConscript未挂载scons -c后重新编译检查父目录SConscript链接时重复定义源文件被多个SConscript收集--verbose查看.o列表去除重复收集menuconfig后rtconfig.h无变化路径含中文/空格或手动修改过清理目录路径切换到纯英文目录depend依赖宏没定义Kconfig和SConscript拼写不一致对比rtconfig.h与SConscript中宏名链接时找不到某函数定义该模块依赖未打开或源码未包含用GetDepend检查依赖确认源文件列表Keil工程里找不到新增目录导出的工程文件未刷新重新执行scons --targetmdk5编译报错fatal error: rtdef.h No such fileCPPPATH未包含RT-Thread核心头文件目录检查SConscript里的CPPPATH或RTT_ROOT是否配置这套速查表是我在实际开发里一分一分整理出来的碰到问题先对着表过一遍多数情况不用深挖就能定位。RT-Thread的构建与配置系统是很成熟的工具组合出错大多不是系统本身的问题而是环境、路径、宏名这类“人祸”。顺带分享一个小技巧我习惯在项目根目录写一个构建脚本里面先做环境检查再执行scons -c和scons -j8最后自动跑--targetmdk5导出工程。这样一条命令处理所有事既方便本地开发也方便丢到CI服务器上做自动构建。把构建过程从“手工操作”变成“自动化工具”之后整个项目的迭代效率会有非常明显的提升。RT-Thread这套构建与配置体系初学会觉得命令多、文件杂但只要把Kconfig、SConscript、rtconfig.h这三个文件的关系理清楚用起来会非常顺手。我第一次用scons导出Keil工程的时候还挺惊讶的原来嵌入式工程的构建自动化可以做到这个程度。后来维护的项目多了凡是涉及多芯片、多板卡、多个软件包的场景我基本都会优先考虑用RT-Thread的方案因为它把“这个平台要不要编这个文件”这种琐碎问题彻底变成了一套可声明、可继承、可复用的规则。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。