From 6ee78a679f601a392d1b4e3c517d93ad8a7195d1 Mon Sep 17 00:00:00 2001 From: shinu Date: Thu, 7 Apr 2022 18:33:23 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=AE=8C=E5=96=84scons=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- development-tools/kconfig/kconfig.md | 65 +++-- development-tools/scons/scons.md | 340 +++++++++------------------ 2 files changed, 151 insertions(+), 254 deletions(-) diff --git a/development-tools/kconfig/kconfig.md b/development-tools/kconfig/kconfig.md index ba8e43d..e3443f9 100644 --- a/development-tools/kconfig/kconfig.md +++ b/development-tools/kconfig/kconfig.md @@ -2,7 +2,31 @@ ## Kconfig 简介 -RT-Thread 借助 Kconfig 文件生成的配置文件 rtconfig.h 来配置系统,Kconfig 文件是各种配置界面的源文件。当在 bsp 目录下使用 env 工具执行 menuconfig 命令时会出现 RT-Thread 系统的配置界面,所有配置工具都是通过读取当前 bsp 目录下的 Kconfig 文件来生成配置界面的,这个文件就是所有配置的总入口,它会包含其他目录的 Kconfig 文件。配置工具读取各个 Kconfig 文件,生成配置界面供开发人员配置系统,最终生成 RT-Thread 系统的配置文件 rtconfig.h。 +C语言项目的裁剪配置本质上通过条件编译和宏的展开来实现的,RT-Thread借助Kconfig文件更方便的实现了这一功能。Kconfig 文件是各种配置界面的源文件。当在 bsp 目录下使用 env 工具执行 menuconfig 命令时会出现 RT-Thread 系统的配置界面,所有配置工具都是通过读取当前 bsp 目录下的 Kconfig 文件来生成配置界面的,这个文件就是所有配置的总入口,它会包含其他目录的 Kconfig 文件。配置工具读取各个 Kconfig 文件,生成配置界面供开发人员配置系统,最终生成 RT-Thread 系统的配置文件 rtconfig.h。 + +```mermaid +flowchart LR + menuconifg(menuconifg)-- 遍历解析各级Kconfig文件\n并保存配置-->.config[(.config)] + .config[(.config)]<-->rtconfig.h(rtconfig.h) +``` + +menuconfig 命令提供 RT-Thread 系统的配置界面,.config 文件会保存当前工程的Kconfig配置信息,最终会转换成rtconfig.h,.config和rtconfig.h可以互相转换。 + +```ini +# .config片段 +CONFIG_RT_USING_TIMER_SOFT=y +CONFIG_RT_TIMER_THREAD_PRIO=4 +CONFIG_RT_TIMER_THREAD_STACK_SIZE=512 +CONFIG_RT_DEBUG=y +``` + +```C +// rtconfig.h片段 +#define RT_USING_TIMER_SOFT +#define RT_TIMER_THREAD_PRIO 4 +#define RT_TIMER_THREAD_STACK_SIZE 512 +#define RT_DEBUG +``` ## Kconfig 基本语法 @@ -30,19 +54,20 @@ config BSP_USING_GPIO ![help](figures/help.png) 语句分析: + - config 表示一个配置选项的开始,紧跟着的 BSP_USING_GPIO 是配置选项的名称,config 下面几行定义了该配置选项的属性。属性可以是该配置选项的 - - 类型 - - 输入提示 - - 依赖关系 - - 默认值 - - 帮助信息 + - 类型 + - 输入提示 + - 依赖关系 + - 默认值 + - 帮助信息 - bool 表示配置选项的类型,每个 config 菜单项都要有类型定义,变量有5种类型 - - bool 布尔类型 - - tristate 三态类型 - - string 字符串 - - hex 十六进制 - - int 整型 + - bool 布尔类型 + - tristate 三态类型 + - string 字符串 + - hex 十六进制 + - int 整型 - select 是反向依赖关系的意思,即当前配置选项被选中,则 RT_USING_PIN 就会被选中。 - default 表示配置选项的默认值,bool 类型的默认值可以是 y/n。 - help 帮助信息。 @@ -59,6 +84,7 @@ config BSP_USING_GPIO #### bool 类型 布尔类型变量的取值范围是 y/n ,其使用示例如下 + ```c config BSP_USING_WDT bool "Enable Watchdog Timer" @@ -66,7 +92,6 @@ config BSP_USING_WDT default n ``` - 上述语句对应的配置界面如下所示 ![bool](figures/bool.png) @@ -119,17 +144,14 @@ config BSP_I2C1_SCL_PIN #define BSP_I2C1_SCL_PIN 116 ``` - #### hex 类型 十六进制类型变量的取值范围是一个十六进制的数,其使用方法和整型变量用法一致,整型变量生成的是十进制的数,而十六进制生成的是十六进制的数。 - #### tristate 类型 三态类型变量的取值范围是 y、n 和 m。tristate 代表在内核中有三种状态,一种是不选中,一种是选中直接编译进内核,还有一种是 m 手动添加驱动,而布尔类型变量只有两种状态,即选中和不选中。其使用方法和布尔类型变量类似。 - ### menu/endmenu 语句 menu 语句用于生成菜单。 @@ -150,6 +172,7 @@ menu "Hardware Drivers Config" endmenu ``` + menu 之后的字符串是菜单名。menu 和 endmenu 间有很多 config 语句,以上代码对应的配置界面如下所示 ![menu1](figures/menu1.png) @@ -158,7 +181,6 @@ menu 之后的字符串是菜单名。menu 和 endmenu 间有很多 config 语 ![menu](figures/menu.png) - 通过 env 选中以上配置界面的所有选项后,最终可在 rtconfig.h 文件中生成如下五个宏 ```c @@ -194,16 +216,15 @@ endmenu 当上一级菜单选中 "Enable CAN" 时 - ![if11](figures/if11.png) ![if2](figures/if2.png) - ### menuconfig 语句 menuconfig 语句表示带菜单的配置项 以下为 RT-Thread 系统 menuconfig 语句的示例 + ```c menu "Hardware Drivers Config" menuconfig BSP_USING_UART @@ -232,6 +253,7 @@ endmenu ![menuconfig2](figures/menuconfig2.png) 语句分析: + - menuconfig 这个语句和 config 语句很相似,但它在 config 的基础上要求所有的子选项作为独立的行显示。 - depends on 表示依赖某个配置选项,`depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA` 表示只有当 BSP_USING_UART1 和 RT_SERIAL_USING_DMA 配置选项同时被选中时,当前配置选项的提示信息才会出现,才能设置当前配置选项 @@ -250,6 +272,7 @@ endmenu choice 语句将多个类似的配置选项组合在一起,供用户选择一组配置项 RT-Thread Kconfig 文件中 choice 代码示例如下 + ```c menu "Hardware Drivers Config" menuconfig BSP_USING_ONCHIP_RTC @@ -271,13 +294,16 @@ menu "Hardware Drivers Config" endif endmenu ``` + 以上代码对应的配置界面为 ![choice](figures/choice.png) 语句解析: + - choice/endchoice 给出选择项,中间可以定义多个配置项供选择,但是在配置界面只能选择一个配置项; - prompt 给出提示信息,光标选中后回车进入就可以看到多个 config 条目定义的配置选项,所以下面的两个例子是等价的: + ``` Kconfig bool "Networking support" ``` @@ -287,12 +313,12 @@ endmenu prompt "Networking support" ``` - ### comment 语句 comment 语句出现在界面的第一行,用于定义一些提示信息。 comment 代码示例如下 + ```c menu "Hardware Drivers Config" comment "uart2 pin conflict with Ethernet and PWM" @@ -333,6 +359,7 @@ config BSP_USING_GPIO ``` 将上述语句中的 `bool` 后面的注释去掉。 + ```c config BSP_USING_GPIO bool diff --git a/development-tools/scons/scons.md b/development-tools/scons/scons.md index 8aa5095..4cb5d1b 100644 --- a/development-tools/scons/scons.md +++ b/development-tools/scons/scons.md @@ -14,12 +14,22 @@ SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make 由于历史原因,Makefile 的语法比较混乱,不利于初学者学习。此外在 Windows 平台上使用 Make 也不方便,需要安装 Cygwin 环境。为了克服 Make 的种种缺点,人们开发了其他构建工具,如 CMake 和 SCons 等。 -### RT-Thread 构建工具 +### RT-Thread 构建系统 RT-Thread 早期使用 Make/Makefile 构建。从 0.3.x 开始,RT-Thread 开发团队逐渐引入了 SCons 构建系统,引入 SCons 唯一的目是:使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上。 有些读者可能会有些疑惑,这里介绍的构建工具与 IDE 有什么不同呢?IDE 通过图形化界面的操作来完成构建。大部分 IDE 会根据用户所添加的源码生成类似 Makefile 或 SConscript 的脚本文件,在底层调用类似 Make 或 SCons 的工具来构建源码。 +RT-Thread当前的构建系统由以下几个部分组成 + +```mermaid +flowchart LR + A(RT-Thread构建系统)-->Kconfig[Kconfig]-->D["kerne config 配置文件 (提供系统的配置裁剪功能)"] + A(RT-Thread构建系统)-->SCons[SCons]-->E[构建工具] + A(RT-Thread构建系统)-->Python拓展脚本[Python]-->F["与SCons结合提供各种需要的拓展功能"] + A(RT-Thread构建系统)-->env[env工具]-->G["主要提供构建系统所需的各种环境变量以及软件包的管理"] +``` + ### 安装 SCons 在使用 SCons 系统前需要在 PC 主机中安装它,因为它是 Python 语言编写的,所以在使用 SCons 之前需要安装 Python 运行环境。 @@ -140,278 +150,138 @@ er\inc -ILibraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x -IF:\Project\git\rt-thre ... ``` -## SCons 进阶 - -SCons 使用 SConscript 和 SConstruct 文件来组织源码结构,通常来说一个项目只有一 SConstruct,但是会有多个 SConscript。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 - -为了使 RT-Thread 更好的支持多种编译器,以及方便的调整编译参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的编译。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 - -RT-Thread 大部分源码文件夹下也存在 SConscript 文件,这些文件会被 BSP 目录下的 SConscript 文件 “找到” 从而将 rtconfig.h 中定义的宏对应的源代码加入到编译器中来。后文将以 stm32f10x-HAL BSP 为例,讲解 SCons 是如何构建工程。 - -### SCons 内置函数 - -如果想要将自己的一些源代码加入到 SCons 编译环境中,一般可以创建或修改已有 SConscript 文件。SConscript 文件可以控制源码文件的加入,并且可以指定文件的 Group(与 MDK/IAR 等 IDE 中的 Group 的概念类似)。 - -SCons 提供了很多内置函数可以帮助我们快速添加源码程序,利用这些函数,再配合一些简单的 Python 语句我们就能随心所欲向项目中添加或者删除源码。下面将简单介绍一些常用函数。 - -#### GetCurrentDir() - -获取当前路径。 - -#### Glob('\*.c') - -获取当前目录下的所有 C 文件。修改参数的值为其他后缀就可以匹配当前目录下的所有某类型的文件。 +## SCons 脚本指南 -#### GetDepend(macro) +SCons 使用 SConscript 和 SConstruct 文件来组织源码结构并进行构建,SConstruct是scons构建的主脚本,SConscript存放在源代码的子目录下,通常放在项目的子目录,以达到分层构建的目的。SConscript 需要显示地包含进构建过程中,例如通过调用SCons的内置方法。 -该函数定义在 tools 目录下的脚本文件中,它会从 rtconfig.h 文件读取配置信息,其参数为 rtconfig.h 中的宏名。如果 rtconfig.h 打开了某个宏,则这个方法(函数)返回真,否则返回假。 +为了使 RT-Thread 更好的支持多种编译器,以及方便的调整构建参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的‘配置’文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的构建。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 -#### Split(str) +## SCons 函数 -将字符串 str 分割成一个列表 list。 +RT-Thread的SCons构建脚本中的函数接口包括以下两个部分 -#### DefineGroup(name, src, depend,**parameters) - -这是 RT-Thread 基于 SCons 扩展的一个方法(函数)。DefineGroup 用于定义一个组件。组件可以是一个目录(下的文件或子目录),也是后续一些 IDE 工程文件中的一个 Group 或文件夹。 - -`DefineGroup() ` 函数的参数描述: - -|**参数**|**描述** | -|-------|------------------------------------| -| name | Group 的名字 | -| src | Group 中包含的文件,一般指的是 C/C++ 源文件。方便起见,也能够通过 Glob 函数采用通配符的方式列出 SConscript 文件所在目录中匹配的文件 | -| depend | Group 编译时所依赖的选项(例如 FinSH 组件依赖于 RT_USING_FINSH 宏定义)。编译选项一般指 rtconfig.h 中定义的 RT_USING_xxx 宏。当在 rtconfig.h 配置文件中定义了相应宏时,那么这个 Group 才会被加入到编译环境中进行编译。如果依赖的宏并没在 rtconfig.h 中被定义,那么这个 Group 将不会被加入编译。相类似的,在使用 scons 生成为 IDE 工程文件时,如果依赖的宏未被定义,相应的 Group 也不会在工程文件中出现 | -| parameters | 配置其他参数,可取值见下表,实际使用时不需要配置所有参数 | - -parameters 可加入的参数: - -|**参数**|**描述** | -|------------|--------------------------------------------------| -| CCFLAGS | C/CPP 源文件公共编译参数 | -| CFLAGS | C 源文件独有编译参数 | -| CXXFLAGS | CPP 源文件独有编译参数 | -| CPPPATH | 头文件路径 | -| CPPDEFINES | 编译时添加宏定义 | -| LIBRARY | 包含此参数,则会将组件生成的目标文件打包成库文件 | - -#### SConscript(dirs,variant_dir,duplicate) - -读取新的 SConscript 文件,SConscript() 函数的参数描述如下所示: - -|**参数** |**描述** | -|-------------|---------------------------------------| -| dirs | SConscript 文件路径 | -| variant_dir | 指定生成的目标文件的存放路径 | -| duiplicate | 设定是否拷贝或链接源文件到 variant_dir | +```mermaid +flowchart LR + SCons ---> scons标准接口 + SCons ---> RT-Thread自定义接口 + +``` -## SConscript 示例 +### 常用标准接口 + +| 函数 | 作用 | +| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Import**(vars) | 导入其他脚本定义的vars | +| **Export**(vars) | 导出vars,以供Import函数在其他scons脚本使用 | +| **SConscript**(scripts, \[exports, variant\_dir, duplicate]) | 导入SConscript,返回此脚本指定的参与构建的对象 参数: scripts:导入的SConscript,路径+名称 exports:导出一个变量(可选) variant\_dir:指定构建产物的生成路径(可选) duplicate:执行构建的目录中是否拷贝源码(可选) | | +| **Glob**(pattern) | 返回参与构建的对象,对象满足pattern模式匹配的列表 | +| IsDefined(depend) | 判断宏是否被定义 参数: depend:宏或宏列表 返回值:已定义为True,否则为False | +| **Split(str)** | 将字符串 str 分割成一个列表。 | + +### SCons 自定义函数 + +| 函数 | 作用 | +| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SrcRemove(src, remove) | 从构建列表中移除源文件 参数: src:构建列表 remove:移除的源文件列表 | +| DefineGroup(name, src, depend, \*\*parameters) | 定义一个参与构建的Group,并作为参与scons构建的对象返回。 参数: name:Group的名字 src:group的源文件 depend:Group的依赖,具体为rtconfig.h中的宏,如:RT\_USING\_FINSH parameters:构建参数 | +|GetCurrentDir( )|获取当前脚本所在路径| +|GetDepend(depend)|查看是否定义了宏依赖。 参数: depend:,依赖的宏,如:RT_USING_CPLUSPLUS| +|AddDepend(option)|添加一个宏定义。 参数: option:添加的宏| +|GetConfigValue(name)|获得配置的值(宏定义的值)。 参数: name:宏定义| +|GetVersion( )|获得RTT版本信息| +|GlobSubDir(sub_dir, ext_name)|对目录下所有文件(包含子目录)进行Glob| +|BuildPackage(package)|按照json提供的格式,定义一个Group。 参数: package:json文件| + +### 构建参数 + +| 构建参数 | 意义 | +| ---------- | --------------- | +| CCFLAGS | C/CPP 源文件公共编译参数 | +| CFLAGS | C 源文件独有编译参数 | +| CXXFLAGS | CPP 源文件独有编译参数 | +| CPPPATH | 头文件路径 | +| CPPDEFINES | 编译时添加宏定义 | +| LIBRARY | 是否将Group构建为静态库 | + +## SSConscript最佳实践 下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 -### SConscript 示例 1 +### 构建组件 -我们先从 stm32f10x-HAL BSP 目录下的 SConcript 文件开始讲解,这个文件管理 BSP 下面的所有其他 SConscript 文件,内容如下所示。 +```python +from building import * # 导入RT-Thread的自定义构建函数 -```c -import os -cwd = str(Dir('#')) -objs = [] -list = os.listdir(cwd) -for d in list: - path = os.path.join(cwd, d) - if os.path.isfile(os.path.join(path, 'SConscript')): - objs = objs + SConscript(os.path.join(d, 'SConscript')) -Return('objs') -``` - -* `import os:` 导入 Python 系统编程 os 模块,可以调用 os 模块提供的函数用于处理文件和目录。 - -* `cwd = str(Dir('#')):` 获取工程的顶级目录并赋值给字符串变量 cwd,也就是工程的 SConstruct 所在的目录,在这里它的效果与 `cwd = GetCurrentDir()` 相同。 +cwd = GetCurrentDir() # 获取当前脚本的路径 +src = Split(''' +shell.c +msh.c +''') -* `objs = []:` 定义了一个空的 list 型变量 objs。 +if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): # 判断是否启用MSH的内建命令 + src += ['cmd.c'] -* `list = os.listdir(cwd):` 得到当前目录下的所有子目录,并保存到变量 list 中。 +if GetDepend('DFS_USING_POSIX'): # 判断是否启用POSIX接口 + src += ['msh_file.c'] -* 随后是一个 python 的 for 循环,这个 for 循环会遍历一遍 BSP 的所有子目录并运行这些子目录的 SConscript 文件。具体操作是取出一个当前目录的子目录,利用 `os.path.join(cwd,d)` 拼接成一个完整路径,然后判断这个子目录是否存在一个名为 SConscript 的文件,若存在则执行 `objs = objs + SConscript(os.path.join(d,'SConscript'))。` 这一句中使用了 SCons 提供的一个内置函数 `SConscript()`,它可以读入一个新的 SConscript 文件,并将 SConscript 文件中所指明的源码加入到了源码编译列表 objs 中来。 +CPPPATH = [cwd] # 将当前路径加入构建搜索的头文件路径 -通过这个 SConscript 文件,BSP 工程所需要的源代码就被加入了编译列表中。 +group = DefineGroup('Finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH) +# 定义Finsh组件的group -### SConscript 示例 2 +Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 +``` -那么 stm32f10x-HAL BSP 其他的 SConcript 文件又是怎样的呢?我们再看一下 drivers 目录下 SConcript 文件,这个文件将管理 drivers 目录下面的源代码。drivers 目录用于存放根据 RT-Thread 提供的驱动框架实现的底层驱动代码。 +### 构建驱动 -```c -Import('rtconfig') -from building import * +```python +Import('RTT_ROOT') # 导入变量RTT_ROOT,即RT-Thread的根目录 +Import('rtconfig') # 导入rtconfig +from building import * # 导入RT-Thread的自定义构建函数 -cwd = GetCurrentDir() +cwd = GetCurrentDir() # 获取当前脚本的路径 # add the general drivers. src = Split(""" -board.c -stm32f1xx_it.c """) -if GetDepend(['RT_USING_PIN']): +if GetDepend(['RT_USING_PIN']): # 判断是否启用PING设备 src += ['drv_gpio.c'] -if GetDepend(['RT_USING_SERIAL']): - src += ['drv_usart.c'] -if GetDepend(['RT_USING_SPI']): - src += ['drv_spi.c'] -if GetDepend(['RT_USING_USB_DEVICE']): - src += ['drv_usb.c'] -if GetDepend(['RT_USING_SDCARD']): - src += ['drv_sdcard.c'] - -if rtconfig.CROSS_TOOL == 'gcc': - src += ['gcc_startup.s'] - -CPPPATH = [cwd] - -group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH) - -Return('group') - -``` - -* `Import('rtconfig'):` 导入 rtconfig 对象,后面用到的 rtconfig.CROSS_TOOL 定义在这个 rtconfig 模块。 - -* `from building import *:` 把 building 模块的所有内容全都导入到当前模块,后面用到的 DefineGroup 定义在这个模块。 - -* `cwd = GetCurrentDir():` 获得当前路径并保存到字符串变量 cwd 中。 - -后面一行使用 `Split()` 函数来将一个文件字符串分割成一个列表,其效果等价于 - -`src = ['board.c','stm32f1xx_it.c']` -后面使用了 if 判断和 `GetDepend()` 检查 rtconfig.h 中的某个宏是否打开,如果打开,则使用 `src += [src_name]` 来往列表变量 src 中追加源代码文件。 +if GetDepend(['RT_USING_HWTIMER']): # 判断是否启用HWTIMER设备 + src += ['drv_hwtimer.c'] -* `CPPPATH = [cwd]:` 将当前路径保存到一个列表变量 CPPPATH 中。 +src += ['drv_common.c'] -最后一行使用 DefineGroup 创建一个名为 Drivers 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏。 +path = [cwd] +path += [cwd + '/config'] -`CPPPATH =CPPPATH` 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。 +if GetDepend('BSP_USING_ON_CHIP_FLASH'): # 判断是否启用片上FLASH + path += [cwd + '/drv_flash'] -### SConscript 示例 3 +group = DefineGroup('Drivers', src, depend = [''], CPPPATH = path) +# 定义驱动的group -我们再看一下 applications 目录下的 SConcript 文件,这个文件将管理 applications 目录下面的源代码,用于存放用户自己的应用代码。 +Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 -```c -from building import * - -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd, str(Dir('#'))] - -group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) - -Return('group') -``` - -`src = Glob('*.c'):` 得到当前目录下所有的 C 文件。 - -`CPPPATH = [cwd, str(Dir('#'))]:` 将当前路径和工程的 SConstruct 所在的路径保存到列表变量 CPPPATH 中。 - -最后一行使用 DefineGroup 创建一个名为 Applications 的组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏,并将 CPPPATH 保存的路径添加到了系统头文件搜索路径中。这样 applications 目录和 stm32f10x-HAL BSP 目录里面的头文件在源代码的其他地方就可以引用了。 - -总结:这个源程序会将当前目录下的所有 c 程序加入到组 Applications 中,因此如果在这个目录下增加或者删除文件,就可以将文件加入工程或者从工程中删除。它适用于批量添加源码文件。 - -### SConscript 示例 4 - -下面是 RT-Thread 源代码 component/finsh/SConscript 文件的内容,这个文件将管理 finsh 目录下面的源代码。 - -```c -Import('rtconfig') -from building import * - -cwd = GetCurrentDir() -src = Split(''' -shell.c -symbol.c -cmd.c -''') - -fsh_src = Split(''' -finsh_compiler.c -finsh_error.c -finsh_heap.c -finsh_init.c -finsh_node.c -finsh_ops.c -finsh_parser.c -finsh_var.c -finsh_vm.c -finsh_token.c -''') - -msh_src = Split(''' -msh.c -msh_cmd.c -msh_file.c -''') - -CPPPATH = [cwd] -if rtconfig.CROSS_TOOL == 'keil': - LINKFLAGS = '--keep *.o(FSymTab)' - - if not GetDepend('FINSH_USING_MSH_ONLY'): - LINKFLAGS = LINKFLAGS + '--keep *.o(VSymTab)' -else: - LINKFLAGS = '' - -if GetDepend('FINSH_USING_MSH'): - src = src + msh_src -if not GetDepend('FINSH_USING_MSH_ONLY'): - src = src + fsh_src - -group = DefineGroup('finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH, LINKFLAGS = LINKFLAGS) - -Return('group') ``` -我们来看一下文件中第一个 Python 条件判断语句的内容,如果编译工具是 keil,则变量 `LINKFLAGS = '--keep *.o(FSymTab)'` 否则置空。 - -DefinGroup 同样将 finsh 目录下的 src 指定的文件创建为 finsh 组。`depend = ['RT_USING_FINSH']` 表示这个组依赖 rtconfig.h 中的宏 RT_USING_FINSH。当 rtconfig.h 中打开宏 RT_USING_FINSH 时,finsh 组内的源码才会被实际编译,否则 SCons 不会编译。 +### 构建应用 -然后将 finsh 目录加入到系统头文件目录中,这样我们就可以在其他源码中引用 finsh 目录下的头文件。 +```python +from building import * # 导入RT-Thread的自定义构建函数 -`LINKFLAGS = LINKFLAGS` 的含义与 `CPPPATH = CPPPATH` 类似。左边的 LINKFLAGS 表示链接参数,右边的 LINKFLAGS 则是前面 if else 语句所定义的值。也就是给工程指定链接参数。 - -## 使用 SCons 管理工程 - -前面小节对 RT-Thread 源代码的相关 SConscript 做了详细讲解,大家也应该知道了 SConscript 文件的一些常见写法,本小节将指导大家如何使用 SCons 管理自己的工程。 - -### 添加应用代码 - -前文提到过 BSP 下的 Applications 文件夹用于存放用户自己的应用代码,目前只有一个 main.c 文件。如果用户的应用代码不是很多,建议相关源文件都放在这个文件夹下面。在 Applications 文件夹下新增了 2 个简单的文件 hello.c 和 hello.h,内容如下所示。 - -```c -/* file: hello.h */ - -#ifndef _HELLO_H_ -#define _HELLO_H_ - -int hello_world(void); - -#endif /* _HELLO_H_ */ - -/* file: hello.c */ -#include -#include -#include +cwd = GetCurrentDir() # 获取当前脚本的路径 +src = Glob('*.c') +CPPPATH = [cwd, str(Dir('#'))] # 加入构建搜索的头文件路径 -int hello_world(void) -{ - rt_kprintf("Hello, world!\n"); +group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH, LIBRARY=True) +# 定义应用的group 并构建为静态库,然后构建ELF,hex,bin。 - return 0; -} +Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 -MSH_CMD_EXPORT(hello_world, Hello world!) ``` -applications 目录下的 SConcript 文件会把当前目录下的所有源文件都添加到工程中。需要使用 `scons --target=xxx` 命令才会把新增的 2 个文件添加到工程项目中。注意每次新增文件都要重新生成工程。 - ### 添加模块 前文提到在自己源代码文件不多的情况下,建议所有源代码文件都放在 applications 文件夹里面。如果用户源代码很多了,并且想创建自己的工程模块,或者需要使用自己获取的其他模块,怎么做会比较合适呢? @@ -465,9 +335,9 @@ Return('group') 如果要往工程中添加一个额外的库,需要注意不同的工具链对二进制库的命名。 -- ARMCC 工具链下的库名称应该是 xxx.lib,一个以 .lib 为后缀的文件。 -- IAR 工具链下的库名称应该是 xxx.a,一个以 .a 为后缀的文件。 -- GCC 工具链下的库名称应该是 libxxx.a,一个以 .a 为后缀的文件,并且有 lib 前缀。 +* ARMCC 工具链下的库名称应该是 xxx.lib,一个以 .lib 为后缀的文件。 +* IAR 工具链下的库名称应该是 xxx.a,一个以 .a 为后缀的文件。 +* GCC 工具链下的库名称应该是 libxxx.a,一个以 .a 为后缀的文件,并且有 lib 前缀。 ARMCC / IAR 工具链下,若添加库名为 libabc.lib / libabc_iar.a 时,在指定库时指定全名 libabc。 -- Gitee From 89575630cf336c10196511935e39c7df6bc316b9 Mon Sep 17 00:00:00 2001 From: shinu Date: Thu, 7 Apr 2022 18:36:45 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- development-tools/scons/scons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/development-tools/scons/scons.md b/development-tools/scons/scons.md index 4cb5d1b..23ff1db 100644 --- a/development-tools/scons/scons.md +++ b/development-tools/scons/scons.md @@ -203,7 +203,7 @@ flowchart LR | CPPDEFINES | 编译时添加宏定义 | | LIBRARY | 是否将Group构建为静态库 | -## SSConscript最佳实践 +## SConscript最佳实践 下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 -- Gitee From 383eff3051b1bd57eae8f2e359149bad3afc7a2b Mon Sep 17 00:00:00 2001 From: shinu Date: Fri, 8 Apr 2022 21:29:32 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=BF=98=E5=8E=9FSCONS=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- development-tools/scons/scons.md | 340 +++++++++++++++++++++---------- 1 file changed, 235 insertions(+), 105 deletions(-) diff --git a/development-tools/scons/scons.md b/development-tools/scons/scons.md index 23ff1db..8aa5095 100644 --- a/development-tools/scons/scons.md +++ b/development-tools/scons/scons.md @@ -14,22 +14,12 @@ SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make 由于历史原因,Makefile 的语法比较混乱,不利于初学者学习。此外在 Windows 平台上使用 Make 也不方便,需要安装 Cygwin 环境。为了克服 Make 的种种缺点,人们开发了其他构建工具,如 CMake 和 SCons 等。 -### RT-Thread 构建系统 +### RT-Thread 构建工具 RT-Thread 早期使用 Make/Makefile 构建。从 0.3.x 开始,RT-Thread 开发团队逐渐引入了 SCons 构建系统,引入 SCons 唯一的目是:使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上。 有些读者可能会有些疑惑,这里介绍的构建工具与 IDE 有什么不同呢?IDE 通过图形化界面的操作来完成构建。大部分 IDE 会根据用户所添加的源码生成类似 Makefile 或 SConscript 的脚本文件,在底层调用类似 Make 或 SCons 的工具来构建源码。 -RT-Thread当前的构建系统由以下几个部分组成 - -```mermaid -flowchart LR - A(RT-Thread构建系统)-->Kconfig[Kconfig]-->D["kerne config 配置文件 (提供系统的配置裁剪功能)"] - A(RT-Thread构建系统)-->SCons[SCons]-->E[构建工具] - A(RT-Thread构建系统)-->Python拓展脚本[Python]-->F["与SCons结合提供各种需要的拓展功能"] - A(RT-Thread构建系统)-->env[env工具]-->G["主要提供构建系统所需的各种环境变量以及软件包的管理"] -``` - ### 安装 SCons 在使用 SCons 系统前需要在 PC 主机中安装它,因为它是 Python 语言编写的,所以在使用 SCons 之前需要安装 Python 运行环境。 @@ -150,138 +140,278 @@ er\inc -ILibraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x -IF:\Project\git\rt-thre ... ``` -## SCons 脚本指南 +## SCons 进阶 -SCons 使用 SConscript 和 SConstruct 文件来组织源码结构并进行构建,SConstruct是scons构建的主脚本,SConscript存放在源代码的子目录下,通常放在项目的子目录,以达到分层构建的目的。SConscript 需要显示地包含进构建过程中,例如通过调用SCons的内置方法。 +SCons 使用 SConscript 和 SConstruct 文件来组织源码结构,通常来说一个项目只有一 SConstruct,但是会有多个 SConscript。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 -为了使 RT-Thread 更好的支持多种编译器,以及方便的调整构建参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的‘配置’文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的构建。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 +为了使 RT-Thread 更好的支持多种编译器,以及方便的调整编译参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的编译。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 -## SCons 函数 +RT-Thread 大部分源码文件夹下也存在 SConscript 文件,这些文件会被 BSP 目录下的 SConscript 文件 “找到” 从而将 rtconfig.h 中定义的宏对应的源代码加入到编译器中来。后文将以 stm32f10x-HAL BSP 为例,讲解 SCons 是如何构建工程。 -RT-Thread的SCons构建脚本中的函数接口包括以下两个部分 +### SCons 内置函数 -```mermaid -flowchart LR - SCons ---> scons标准接口 - SCons ---> RT-Thread自定义接口 - -``` +如果想要将自己的一些源代码加入到 SCons 编译环境中,一般可以创建或修改已有 SConscript 文件。SConscript 文件可以控制源码文件的加入,并且可以指定文件的 Group(与 MDK/IAR 等 IDE 中的 Group 的概念类似)。 -### 常用标准接口 - -| 函数 | 作用 | -| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Import**(vars) | 导入其他脚本定义的vars | -| **Export**(vars) | 导出vars,以供Import函数在其他scons脚本使用 | -| **SConscript**(scripts, \[exports, variant\_dir, duplicate]) | 导入SConscript,返回此脚本指定的参与构建的对象 参数: scripts:导入的SConscript,路径+名称 exports:导出一个变量(可选) variant\_dir:指定构建产物的生成路径(可选) duplicate:执行构建的目录中是否拷贝源码(可选) | | -| **Glob**(pattern) | 返回参与构建的对象,对象满足pattern模式匹配的列表 | -| IsDefined(depend) | 判断宏是否被定义 参数: depend:宏或宏列表 返回值:已定义为True,否则为False | -| **Split(str)** | 将字符串 str 分割成一个列表。 | - -### SCons 自定义函数 - -| 函数 | 作用 | -| ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| SrcRemove(src, remove) | 从构建列表中移除源文件 参数: src:构建列表 remove:移除的源文件列表 | -| DefineGroup(name, src, depend, \*\*parameters) | 定义一个参与构建的Group,并作为参与scons构建的对象返回。 参数: name:Group的名字 src:group的源文件 depend:Group的依赖,具体为rtconfig.h中的宏,如:RT\_USING\_FINSH parameters:构建参数 | -|GetCurrentDir( )|获取当前脚本所在路径| -|GetDepend(depend)|查看是否定义了宏依赖。 参数: depend:,依赖的宏,如:RT_USING_CPLUSPLUS| -|AddDepend(option)|添加一个宏定义。 参数: option:添加的宏| -|GetConfigValue(name)|获得配置的值(宏定义的值)。 参数: name:宏定义| -|GetVersion( )|获得RTT版本信息| -|GlobSubDir(sub_dir, ext_name)|对目录下所有文件(包含子目录)进行Glob| -|BuildPackage(package)|按照json提供的格式,定义一个Group。 参数: package:json文件| - -### 构建参数 - -| 构建参数 | 意义 | -| ---------- | --------------- | -| CCFLAGS | C/CPP 源文件公共编译参数 | -| CFLAGS | C 源文件独有编译参数 | -| CXXFLAGS | CPP 源文件独有编译参数 | -| CPPPATH | 头文件路径 | -| CPPDEFINES | 编译时添加宏定义 | -| LIBRARY | 是否将Group构建为静态库 | - -## SConscript最佳实践 +SCons 提供了很多内置函数可以帮助我们快速添加源码程序,利用这些函数,再配合一些简单的 Python 语句我们就能随心所欲向项目中添加或者删除源码。下面将简单介绍一些常用函数。 -下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 +#### GetCurrentDir() -### 构建组件 +获取当前路径。 -```python -from building import * # 导入RT-Thread的自定义构建函数 +#### Glob('\*.c') -cwd = GetCurrentDir() # 获取当前脚本的路径 -src = Split(''' -shell.c -msh.c -''') +获取当前目录下的所有 C 文件。修改参数的值为其他后缀就可以匹配当前目录下的所有某类型的文件。 + +#### GetDepend(macro) + +该函数定义在 tools 目录下的脚本文件中,它会从 rtconfig.h 文件读取配置信息,其参数为 rtconfig.h 中的宏名。如果 rtconfig.h 打开了某个宏,则这个方法(函数)返回真,否则返回假。 + +#### Split(str) + +将字符串 str 分割成一个列表 list。 -if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): # 判断是否启用MSH的内建命令 - src += ['cmd.c'] +#### DefineGroup(name, src, depend,**parameters) -if GetDepend('DFS_USING_POSIX'): # 判断是否启用POSIX接口 - src += ['msh_file.c'] +这是 RT-Thread 基于 SCons 扩展的一个方法(函数)。DefineGroup 用于定义一个组件。组件可以是一个目录(下的文件或子目录),也是后续一些 IDE 工程文件中的一个 Group 或文件夹。 -CPPPATH = [cwd] # 将当前路径加入构建搜索的头文件路径 +`DefineGroup() ` 函数的参数描述: -group = DefineGroup('Finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH) -# 定义Finsh组件的group +|**参数**|**描述** | +|-------|------------------------------------| +| name | Group 的名字 | +| src | Group 中包含的文件,一般指的是 C/C++ 源文件。方便起见,也能够通过 Glob 函数采用通配符的方式列出 SConscript 文件所在目录中匹配的文件 | +| depend | Group 编译时所依赖的选项(例如 FinSH 组件依赖于 RT_USING_FINSH 宏定义)。编译选项一般指 rtconfig.h 中定义的 RT_USING_xxx 宏。当在 rtconfig.h 配置文件中定义了相应宏时,那么这个 Group 才会被加入到编译环境中进行编译。如果依赖的宏并没在 rtconfig.h 中被定义,那么这个 Group 将不会被加入编译。相类似的,在使用 scons 生成为 IDE 工程文件时,如果依赖的宏未被定义,相应的 Group 也不会在工程文件中出现 | +| parameters | 配置其他参数,可取值见下表,实际使用时不需要配置所有参数 | -Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 +parameters 可加入的参数: + +|**参数**|**描述** | +|------------|--------------------------------------------------| +| CCFLAGS | C/CPP 源文件公共编译参数 | +| CFLAGS | C 源文件独有编译参数 | +| CXXFLAGS | CPP 源文件独有编译参数 | +| CPPPATH | 头文件路径 | +| CPPDEFINES | 编译时添加宏定义 | +| LIBRARY | 包含此参数,则会将组件生成的目标文件打包成库文件 | + +#### SConscript(dirs,variant_dir,duplicate) + +读取新的 SConscript 文件,SConscript() 函数的参数描述如下所示: + +|**参数** |**描述** | +|-------------|---------------------------------------| +| dirs | SConscript 文件路径 | +| variant_dir | 指定生成的目标文件的存放路径 | +| duiplicate | 设定是否拷贝或链接源文件到 variant_dir | + +## SConscript 示例 + +下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 + +### SConscript 示例 1 + +我们先从 stm32f10x-HAL BSP 目录下的 SConcript 文件开始讲解,这个文件管理 BSP 下面的所有其他 SConscript 文件,内容如下所示。 + +```c +import os +cwd = str(Dir('#')) +objs = [] +list = os.listdir(cwd) +for d in list: + path = os.path.join(cwd, d) + if os.path.isfile(os.path.join(path, 'SConscript')): + objs = objs + SConscript(os.path.join(d, 'SConscript')) +Return('objs') ``` -### 构建驱动 +* `import os:` 导入 Python 系统编程 os 模块,可以调用 os 模块提供的函数用于处理文件和目录。 -```python -Import('RTT_ROOT') # 导入变量RTT_ROOT,即RT-Thread的根目录 -Import('rtconfig') # 导入rtconfig -from building import * # 导入RT-Thread的自定义构建函数 +* `cwd = str(Dir('#')):` 获取工程的顶级目录并赋值给字符串变量 cwd,也就是工程的 SConstruct 所在的目录,在这里它的效果与 `cwd = GetCurrentDir()` 相同。 -cwd = GetCurrentDir() # 获取当前脚本的路径 +* `objs = []:` 定义了一个空的 list 型变量 objs。 + +* `list = os.listdir(cwd):` 得到当前目录下的所有子目录,并保存到变量 list 中。 + +* 随后是一个 python 的 for 循环,这个 for 循环会遍历一遍 BSP 的所有子目录并运行这些子目录的 SConscript 文件。具体操作是取出一个当前目录的子目录,利用 `os.path.join(cwd,d)` 拼接成一个完整路径,然后判断这个子目录是否存在一个名为 SConscript 的文件,若存在则执行 `objs = objs + SConscript(os.path.join(d,'SConscript'))。` 这一句中使用了 SCons 提供的一个内置函数 `SConscript()`,它可以读入一个新的 SConscript 文件,并将 SConscript 文件中所指明的源码加入到了源码编译列表 objs 中来。 + +通过这个 SConscript 文件,BSP 工程所需要的源代码就被加入了编译列表中。 + +### SConscript 示例 2 + +那么 stm32f10x-HAL BSP 其他的 SConcript 文件又是怎样的呢?我们再看一下 drivers 目录下 SConcript 文件,这个文件将管理 drivers 目录下面的源代码。drivers 目录用于存放根据 RT-Thread 提供的驱动框架实现的底层驱动代码。 + +```c +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() # add the general drivers. src = Split(""" +board.c +stm32f1xx_it.c """) -if GetDepend(['RT_USING_PIN']): # 判断是否启用PING设备 +if GetDepend(['RT_USING_PIN']): src += ['drv_gpio.c'] +if GetDepend(['RT_USING_SERIAL']): + src += ['drv_usart.c'] +if GetDepend(['RT_USING_SPI']): + src += ['drv_spi.c'] +if GetDepend(['RT_USING_USB_DEVICE']): + src += ['drv_usb.c'] +if GetDepend(['RT_USING_SDCARD']): + src += ['drv_sdcard.c'] + +if rtconfig.CROSS_TOOL == 'gcc': + src += ['gcc_startup.s'] + +CPPPATH = [cwd] + +group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH) -if GetDepend(['RT_USING_HWTIMER']): # 判断是否启用HWTIMER设备 - src += ['drv_hwtimer.c'] +Return('group') -src += ['drv_common.c'] +``` -path = [cwd] -path += [cwd + '/config'] +* `Import('rtconfig'):` 导入 rtconfig 对象,后面用到的 rtconfig.CROSS_TOOL 定义在这个 rtconfig 模块。 -if GetDepend('BSP_USING_ON_CHIP_FLASH'): # 判断是否启用片上FLASH - path += [cwd + '/drv_flash'] +* `from building import *:` 把 building 模块的所有内容全都导入到当前模块,后面用到的 DefineGroup 定义在这个模块。 -group = DefineGroup('Drivers', src, depend = [''], CPPPATH = path) -# 定义驱动的group +* `cwd = GetCurrentDir():` 获得当前路径并保存到字符串变量 cwd 中。 -Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 +后面一行使用 `Split()` 函数来将一个文件字符串分割成一个列表,其效果等价于 -``` +`src = ['board.c','stm32f1xx_it.c']` -### 构建应用 +后面使用了 if 判断和 `GetDepend()` 检查 rtconfig.h 中的某个宏是否打开,如果打开,则使用 `src += [src_name]` 来往列表变量 src 中追加源代码文件。 -```python -from building import * # 导入RT-Thread的自定义构建函数 +* `CPPPATH = [cwd]:` 将当前路径保存到一个列表变量 CPPPATH 中。 -cwd = GetCurrentDir() # 获取当前脚本的路径 +最后一行使用 DefineGroup 创建一个名为 Drivers 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏。 + +`CPPPATH =CPPPATH` 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。 + +### SConscript 示例 3 + +我们再看一下 applications 目录下的 SConcript 文件,这个文件将管理 applications 目录下面的源代码,用于存放用户自己的应用代码。 + +```c +from building import * + +cwd = GetCurrentDir() src = Glob('*.c') -CPPPATH = [cwd, str(Dir('#'))] # 加入构建搜索的头文件路径 +CPPPATH = [cwd, str(Dir('#'))] + +group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') +``` + +`src = Glob('*.c'):` 得到当前目录下所有的 C 文件。 + +`CPPPATH = [cwd, str(Dir('#'))]:` 将当前路径和工程的 SConstruct 所在的路径保存到列表变量 CPPPATH 中。 + +最后一行使用 DefineGroup 创建一个名为 Applications 的组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏,并将 CPPPATH 保存的路径添加到了系统头文件搜索路径中。这样 applications 目录和 stm32f10x-HAL BSP 目录里面的头文件在源代码的其他地方就可以引用了。 + +总结:这个源程序会将当前目录下的所有 c 程序加入到组 Applications 中,因此如果在这个目录下增加或者删除文件,就可以将文件加入工程或者从工程中删除。它适用于批量添加源码文件。 + +### SConscript 示例 4 + +下面是 RT-Thread 源代码 component/finsh/SConscript 文件的内容,这个文件将管理 finsh 目录下面的源代码。 + +```c +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = Split(''' +shell.c +symbol.c +cmd.c +''') + +fsh_src = Split(''' +finsh_compiler.c +finsh_error.c +finsh_heap.c +finsh_init.c +finsh_node.c +finsh_ops.c +finsh_parser.c +finsh_var.c +finsh_vm.c +finsh_token.c +''') + +msh_src = Split(''' +msh.c +msh_cmd.c +msh_file.c +''') + +CPPPATH = [cwd] +if rtconfig.CROSS_TOOL == 'keil': + LINKFLAGS = '--keep *.o(FSymTab)' + + if not GetDepend('FINSH_USING_MSH_ONLY'): + LINKFLAGS = LINKFLAGS + '--keep *.o(VSymTab)' +else: + LINKFLAGS = '' + +if GetDepend('FINSH_USING_MSH'): + src = src + msh_src +if not GetDepend('FINSH_USING_MSH_ONLY'): + src = src + fsh_src + +group = DefineGroup('finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH, LINKFLAGS = LINKFLAGS) + +Return('group') +``` + +我们来看一下文件中第一个 Python 条件判断语句的内容,如果编译工具是 keil,则变量 `LINKFLAGS = '--keep *.o(FSymTab)'` 否则置空。 + +DefinGroup 同样将 finsh 目录下的 src 指定的文件创建为 finsh 组。`depend = ['RT_USING_FINSH']` 表示这个组依赖 rtconfig.h 中的宏 RT_USING_FINSH。当 rtconfig.h 中打开宏 RT_USING_FINSH 时,finsh 组内的源码才会被实际编译,否则 SCons 不会编译。 + +然后将 finsh 目录加入到系统头文件目录中,这样我们就可以在其他源码中引用 finsh 目录下的头文件。 -group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH, LIBRARY=True) -# 定义应用的group 并构建为静态库,然后构建ELF,hex,bin。 +`LINKFLAGS = LINKFLAGS` 的含义与 `CPPPATH = CPPPATH` 类似。左边的 LINKFLAGS 表示链接参数,右边的 LINKFLAGS 则是前面 if else 语句所定义的值。也就是给工程指定链接参数。 -Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 +## 使用 SCons 管理工程 +前面小节对 RT-Thread 源代码的相关 SConscript 做了详细讲解,大家也应该知道了 SConscript 文件的一些常见写法,本小节将指导大家如何使用 SCons 管理自己的工程。 + +### 添加应用代码 + +前文提到过 BSP 下的 Applications 文件夹用于存放用户自己的应用代码,目前只有一个 main.c 文件。如果用户的应用代码不是很多,建议相关源文件都放在这个文件夹下面。在 Applications 文件夹下新增了 2 个简单的文件 hello.c 和 hello.h,内容如下所示。 + +```c +/* file: hello.h */ + +#ifndef _HELLO_H_ +#define _HELLO_H_ + +int hello_world(void); + +#endif /* _HELLO_H_ */ + +/* file: hello.c */ +#include +#include +#include + +int hello_world(void) +{ + rt_kprintf("Hello, world!\n"); + + return 0; +} + +MSH_CMD_EXPORT(hello_world, Hello world!) ``` +applications 目录下的 SConcript 文件会把当前目录下的所有源文件都添加到工程中。需要使用 `scons --target=xxx` 命令才会把新增的 2 个文件添加到工程项目中。注意每次新增文件都要重新生成工程。 + ### 添加模块 前文提到在自己源代码文件不多的情况下,建议所有源代码文件都放在 applications 文件夹里面。如果用户源代码很多了,并且想创建自己的工程模块,或者需要使用自己获取的其他模块,怎么做会比较合适呢? @@ -335,9 +465,9 @@ Return('group') 如果要往工程中添加一个额外的库,需要注意不同的工具链对二进制库的命名。 -* ARMCC 工具链下的库名称应该是 xxx.lib,一个以 .lib 为后缀的文件。 -* IAR 工具链下的库名称应该是 xxx.a,一个以 .a 为后缀的文件。 -* GCC 工具链下的库名称应该是 libxxx.a,一个以 .a 为后缀的文件,并且有 lib 前缀。 +- ARMCC 工具链下的库名称应该是 xxx.lib,一个以 .lib 为后缀的文件。 +- IAR 工具链下的库名称应该是 xxx.a,一个以 .a 为后缀的文件。 +- GCC 工具链下的库名称应该是 libxxx.a,一个以 .a 为后缀的文件,并且有 lib 前缀。 ARMCC / IAR 工具链下,若添加库名为 libabc.lib / libabc_iar.a 时,在指定库时指定全名 libabc。 -- Gitee