diff --git a/development-tools/scons/figures/exec_path.png b/development-tools/scons/figures/exec_path.png new file mode 100644 index 0000000000000000000000000000000000000000..2d9ee549935e090bd211c3dfd59782dc8de2b6b9 Binary files /dev/null and b/development-tools/scons/figures/exec_path.png differ diff --git a/development-tools/scons/scons.md b/development-tools/scons/scons.md index 8aa5095a1b2d7b1e26985b4f3d5ad4b807625ebe..192ac5ee530cbb7f911491e3c5742d21bd63b877 100644 --- a/development-tools/scons/scons.md +++ b/development-tools/scons/scons.md @@ -1,12 +1,12 @@ # SCons 构建工具 -## SCons 简介 +## 1 SCons 简介 SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make。它采用不同于通常 Makefile 文件的方式,而是使用 SConstruct 和 SConscript 文件来替代。这些文件也是 Python 脚本,能够使用标准的 Python 语法来编写。所以在 SConstruct、SConscript 文件中可以调用 Python 标准库进行各类复杂的处理,而不局限于 Makefile 设定的规则。 在 [SCons](http://www.scons.org/doc/production/HTML/scons-user/index.html) 的网站上可以找到详细的 SCons 用户手册,本章节讲述 SCons 的基本用法,以及如何在 RT-Thread 中用好 SCons 工具。 -### 什么是构建工具 +### 1.1 什么是构建工具 构建工具 (software construction tool) 是一种软件,它可以根据一定的规则或指令,将源代码编译成可执行的二进制程序。这是构建工具最基本也是最重要的功能。实际上构建工具的功能不止于此,通常这些规则有一定的语法,并组织成文件。这些文件用来控制构建工具的行为,在完成软件构建之外,也可以做其他事情。 @@ -14,27 +14,35 @@ SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make 由于历史原因,Makefile 的语法比较混乱,不利于初学者学习。此外在 Windows 平台上使用 Make 也不方便,需要安装 Cygwin 环境。为了克服 Make 的种种缺点,人们开发了其他构建工具,如 CMake 和 SCons 等。 -### RT-Thread 构建工具 +### 1.2 RT-Thread 构建工具 -RT-Thread 早期使用 Make/Makefile 构建。从 0.3.x 开始,RT-Thread 开发团队逐渐引入了 SCons 构建系统,引入 SCons 唯一的目是:使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上。 +RT-Thread 早期使用 Make/Makefile 构建。从 RT-Thread 0.3.0 开始,RT-Thread 开发团队逐渐引入了 SCons 构建系统,引入 SCons 唯一的目是:使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上。 有些读者可能会有些疑惑,这里介绍的构建工具与 IDE 有什么不同呢?IDE 通过图形化界面的操作来完成构建。大部分 IDE 会根据用户所添加的源码生成类似 Makefile 或 SConscript 的脚本文件,在底层调用类似 Make 或 SCons 的工具来构建源码。 -### 安装 SCons +SCons 使用 SConscript 和 SConstruct 文件来组织源码结构并进行构建,SConstruct是scons构建的主脚本,SConscript存放在源代码的子目录下,通常放在项目的子目录,以达到分层构建的目的。一个项目 (BSP) 只有一 SConstruct,但是会有多个 SConscript。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 -在使用 SCons 系统前需要在 PC 主机中安装它,因为它是 Python 语言编写的,所以在使用 SCons 之前需要安装 Python 运行环境。 +为了使 RT-Thread 更好的支持多种编译器,以及方便的调整构建参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的配置文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的构建。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 -RT-Thread 提供的 Env 配置工具带有 SCons 和 Python,因此在 windows 平台使用 SCons 则不需要安装这两个软件。 +RT-Thread当前的构建系统由以下几个部分组成: -在 Linux、BSD 环境中 Python 应该已经默认安装了,一般也是 2.x 版本系列的 Python 环境。这时只需要安装 SCons 即可,例如在 Ubuntu 中可以使用如下命令安装 SCons: - -`sudo apt-get install scons` +```mermaid +flowchart LR + A(RT-Thread构建系统)-->Kconfig + A(RT-Thread构建系统)-->rtconfig.py + A(RT-Thread构建系统)-->SCons + SCons --> SConscript + SCons --> SConstruct + SConscript --> Scons标准函数 + SConscript --> RT-Thread自定义函数 + SConscript --> Python函数 +``` -## SCons 基本功能 +## 2 SCons 基本命令 RT-Thread 构建系统支持多种编译器。目前支持的编译器包括 ARM GCC、MDK、IAR、VisualStudio、Visual DSP。主流的 ARM Cortex M0、M3、M4 平台,基本上 ARM GCC、MDK、IAR 都是支持的。有一些 BSP 可能仅支持一种,读者可以阅读该 BSP 目录下的 rtconfig.py 里的 CROSS_TOOL 选项查看当前支持的编译器。 -如果是 ARM 平台的芯片,则可以使用 Env 工具,输入 scons 命令直接编译 BSP,这时候默认使用的是 ARM GCC 编译器,因为 Env 工具带有 ARM GCC 编译器。 如下图所示使用 `scons` 命令编译 stm32f10x-HAL BSP,后文讲解 SCons 也将基于这个 BSP。 +打开 Env 工具,如果是 ARM 平台的芯片,输入 scons 命令直接编译 BSP,这时候默认使用的是 ARM GCC 编译器,因为 Env 工具带有 ARM GCC 编译器。 如下图所示使用 `scons` 命令编译 BSP。 ![使用 scons 命令编译 stm32f10x-HAL BSP](figures/b1e0b39d090bfc4640958ffdb11d9741.png) @@ -52,11 +60,7 @@ set RTT_CC=gcc set RTT_EXEC_PATH=D:\software\RaspberryPi-Pico\gcc\2020-q4-major\bin ``` -### SCons 基本命令 - -本节介绍 RT-Thread 中常用的 SCons 命令。SCons 不仅完成基本的编译,还可以生成 MDK/IAR/VS 工程。 - -#### scons +### 2.1 scons 在 Env 命令行窗口进入要编译的 BSP 工程目录,然后使用此命令可以直接编译工程。如果执行过 `scons` 命令后修改了一些源文件,再次执行 scons 命令时,则 SCons 会进行增量编译,仅编译修改过的源文件并链接。 @@ -70,13 +74,24 @@ scons: warning: No version of Visual Studio compiler found - C/C++ compilers mos `scons` 命令后面还可以增加一个 - s 参数,即命令 `scons -s`,和 scons 命令不同的是此命令不会打印具体的内部命令。 -#### scons -c +### 2.2 scons -c 清除编译目标。这个命令会清除执行 scons 时生成的临时文件和目标文件。 -#### scons --target=XXX +### 2.3 scons --target=XXX -如果使用 mdk/iar 来进行项目开发,当修改了 rtconfig.h 打开或者关闭某些组件时,需要使用以下命令中的其中一种重新生成对应的定制化的工程,然后在 mdk/iar 进行编译下载。 +这个命令后面同样可以增加一个 -s 参数,如命令 `scons –target=mdk5 -s`,执行此命令时不会打印具体的内部命令。 + +> [!NOTE] +> 注:要生成 MDK 或者 IAR 的工程文件,前提条件是 BSP 目录存在一个工程模版文件,然后 scons 才会根据这份模版文件加入相关的源码,头文件搜索路径,编译参数,链接参数等。而至于这个工程是针对哪颗芯片的,则直接由这份工程模版文件指定。所以大多数情况下,这个模版文件是一份空的工程文件,用于辅助 SCons 生成 project.uvprojx 或者 project.eww。 + +#### 2.3.1 生成 Keil-MDK / IAR 工程 + +如果使用 MDK/IAR 来进行项目开发,在生成 MDK 或者 IAR 工程前,需要检查 rtconfig.py 文件中 `EXEC_PATH` 变量所保存的对应 IDE 的安装路径是否正确: + +![exec_path](figures/exec_path.png) + +当修改了 rtconfig.h 打开或者关闭某些组件时,需要使用以下命令中的其中一种重新生成对应的定制化的工程,然后在 MDK/IAR 进行编译下载: ```c scons --target=iar @@ -84,9 +99,20 @@ scons --target=mdk4 scons --target=mdk5 ``` -在命令行窗口进入要编译的 BSP 工程目录,使用 `scons --target=mdk5` 命令后会在 BSP 目录生成一个新的 MDK 工程文件名为 project.uvprojx。双击它打开,就可以使用 MDK 来编译、调试。使用 `scons --target=iar` 命令后则会生成一个新的 IAR 工程文件名为 project.eww。不习惯 SCons 的用户可以使用这种方式。如果打开 project.uvproj 失败,请删除 project.uvopt 后,重新生成工程。 +在命令行窗口进入要编译的 BSP 工程目录,使用 `scons --target=mdk5`或者`scons --target=mdk4` 命令后会在 BSP 目录生成一个新的 MDK 工程文件名为 project.uvprojx。双击它打开,就可以使用 MDK 来编译、调试。使用 `scons --target=iar` 命令后则会生成一个新的 IAR 工程文件名为 project.eww。不习惯 SCons 的用户可以使用这种方式。如果打开 project.uvproj 失败,请删除 project.uvopt 后,重新生成工程。 + +#### 2.3.2 生成 CMake / Makefile 工程 -在 bsp/simulator 下,可以使用下面的命令生成 vs2012 的工程或 vs2005 的工程。 +对于习惯 CMake 或者 Makefile 的用户,可以通过以下两个命令来分别生成 CMake 和 Makefile 脚本: + +``` +scons --target=cmake +Scons --target=makefile +``` + +#### 2.3.3 生成 Vsiual Studio 工程 + +在 bsp/simulator 下,可以使用下面的命令生成 vs2012 的工程或 vs2005 的工程: ```c scons --target=vs2012 @@ -95,23 +121,22 @@ Scons --target=vs2005 如果 BSP 目录下提供其他 IDE 工程的模板文件也可以使用此命令生成对应的新工程,比如 ua、vs、cb、cdk。 -这个命令后面同样可以增加一个 -s 参数,如命令 `scons –target=mdk5 -s`,执行此命令时不会打印具体的内部命令。 - -> [!NOTE] -> 注:要生成 MDK 或者 IAR 的工程文件,前提条件是 BSP 目录存在一个工程模版文件,然后 scons 才会根据这份模版文件加入相关的源码,头文件搜索路径,编译参数,链接参数等。而至于这个工程是针对哪颗芯片的,则直接由这份工程模版文件指定。所以大多数情况下,这个模版文件是一份空的工程文件,用于辅助 SCons 生成 project.uvprojx 或者 project.eww。 - -#### scons -jN +### 2.4 scons -jN 多线程编译目标,在多核计算机上可以使用此命令加快编译速度。一般来说一颗 cpu 核心可以支持 2 个线程。双核机器上使用 `scons -j4` 命令即可。 > [!NOTE] > 注:如果你只是想看看编译错误或警告,最好是不使用 - j 参数,这样错误信息不会因为多个文件并行编译而导致出错信息夹杂在一起。 -#### scons --dist +### 2.5 scons --dist 搭建项目框架,使用此命令会在 BSP 目录下生成 dist 目录,这便是开发项目的目录结构,包含了RT-Thread源码及BSP相关工程,不相关的BSP文件夹及libcpu都会被移除,并且可以随意拷贝此工程到任何目录下使用。 -#### scons --verbose +### 2.6 scons --dist-ide + +可以将该BSP导出一份可以被 RT-Thread Studio 导入的工程文件夹,RT-Thread Studio 已经内置该功能,已无需手动敲这个命令。 + +### 2.7 scons --verbose 默认情况下,使用 scons 命令编译的输出不会显示编译参数,如下所示: @@ -140,279 +165,276 @@ er\inc -ILibraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x -IF:\Project\git\rt-thre ... ``` -## SCons 进阶 +## 3 SCons 函数 -SCons 使用 SConscript 和 SConstruct 文件来组织源码结构,通常来说一个项目只有一 SConstruct,但是会有多个 SConscript。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 +### 3.1 Scons 标准函数 -为了使 RT-Thread 更好的支持多种编译器,以及方便的调整编译参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的编译。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 +#### 3.1.1 Import(vars) -RT-Thread 大部分源码文件夹下也存在 SConscript 文件,这些文件会被 BSP 目录下的 SConscript 文件 “找到” 从而将 rtconfig.h 中定义的宏对应的源代码加入到编译器中来。后文将以 stm32f10x-HAL BSP 为例,讲解 SCons 是如何构建工程。 +导入其他脚本定义的vars -### SCons 内置函数 +#### 3.1.2 Export(vars) -如果想要将自己的一些源代码加入到 SCons 编译环境中,一般可以创建或修改已有 SConscript 文件。SConscript 文件可以控制源码文件的加入,并且可以指定文件的 Group(与 MDK/IAR 等 IDE 中的 Group 的概念类似)。 +导出vars,以供Import函数在其他scons脚本使用 -SCons 提供了很多内置函数可以帮助我们快速添加源码程序,利用这些函数,再配合一些简单的 Python 语句我们就能随心所欲向项目中添加或者删除源码。下面将简单介绍一些常用函数。 +#### 3.1.3 SConscript(scripts, \[exports, variant\_dir, duplicate]) -#### GetCurrentDir() - -获取当前路径。 - -#### Glob('\*.c') +读取新的 SConscript 文件,SConscript() 函数的参数描述如下所示: -获取当前目录下的所有 C 文件。修改参数的值为其他后缀就可以匹配当前目录下的所有某类型的文件。 +| **参数** | **描述** | +| ----------- | ----------------------------------------------- | +| dirs | 导入的SConscript,路径+名称 | +| exports | 导出一个变量(可选) | +| variant_dir | 指定生成的目标文件的存放路径(可选) | +| duiplicate | 设定是否拷贝或链接源文件到 variant_dir (可选) | -#### GetDepend(macro) +#### 3.1.4 Glob(pattern) -该函数定义在 tools 目录下的脚本文件中,它会从 rtconfig.h 文件读取配置信息,其参数为 rtconfig.h 中的宏名。如果 rtconfig.h 打开了某个宏,则这个方法(函数)返回真,否则返回假。 +返回参与构建的对象,对象满足pattern模式匹配的列表。例如,获取当前Sconscript所在路径下的所有 .c 文件: -#### Split(str) +```python +src = Glob('*.c') +``` -将字符串 str 分割成一个列表 list。 +#### 3.1.5 IsDefined(depend) -#### DefineGroup(name, src, depend,**parameters) +判断宏是否被定义 参数: depend:宏或宏列表 返回值:已定义为True,否则为False -这是 RT-Thread 基于 SCons 扩展的一个方法(函数)。DefineGroup 用于定义一个组件。组件可以是一个目录(下的文件或子目录),也是后续一些 IDE 工程文件中的一个 Group 或文件夹。 +#### 3.1.6 Split(str) -`DefineGroup() ` 函数的参数描述: +将字符串 str 分割成一个列表,例如: -|**参数**|**描述** | -|-------|------------------------------------| -| 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 | 配置其他参数,可取值见下表,实际使用时不需要配置所有参数 | +```python +src = Split(''' +shell.c +msh.c +''') +``` -parameters 可加入的参数: +上例中,也可以等效写成: -|**参数**|**描述** | -|------------|--------------------------------------------------| -| CCFLAGS | C/CPP 源文件公共编译参数 | -| CFLAGS | C 源文件独有编译参数 | -| CXXFLAGS | CPP 源文件独有编译参数 | -| CPPPATH | 头文件路径 | -| CPPDEFINES | 编译时添加宏定义 | -| LIBRARY | 包含此参数,则会将组件生成的目标文件打包成库文件 | +```python +src = ['shell.c', 'msh.c'] +``` -#### SConscript(dirs,variant_dir,duplicate) +不推荐这么写: -读取新的 SConscript 文件,SConscript() 函数的参数描述如下所示: +```python +src = Glob('shell.c') +``` -|**参数** |**描述** | -|-------------|---------------------------------------| -| dirs | SConscript 文件路径 | -| variant_dir | 指定生成的目标文件的存放路径 | -| duiplicate | 设定是否拷贝或链接源文件到 variant_dir | +### 3.2 RT-Thread 自定义 Scons 函数 -## SConscript 示例 +#### 3.2.1 SrcRemove(src, remove) -下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 +从构建列表中移除源文件 参数: src:构建列表 remove:移除的源文件列表 -### SConscript 示例 1 +#### 3.2.2 DefineGroup(name, src, depend, \*\*parameters) -我们先从 stm32f10x-HAL BSP 目录下的 SConcript 文件开始讲解,这个文件管理 BSP 下面的所有其他 SConscript 文件,内容如下所示。 +定义一个参与构建的Group,并作为参与 Scons 构建的对象返回。Group 可以是一个目录(下的文件或子目录),也是后续一些 IDE 工程文件中的一个 Group 或文件夹。 -```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') -``` +| **参数** | **描述** | +| ---------- | ------------------------------------------------------------ | +| 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 | 配置其他参数,可取值见下表,实际使用时不需要配置所有参数 | -* `import os:` 导入 Python 系统编程 os 模块,可以调用 os 模块提供的函数用于处理文件和目录。 +parameters 可加入的参数: -* `cwd = str(Dir('#')):` 获取工程的顶级目录并赋值给字符串变量 cwd,也就是工程的 SConstruct 所在的目录,在这里它的效果与 `cwd = GetCurrentDir()` 相同。 +| 构建参数 | 意义 | +| ---------- | ------------------------------ | +| CCFLAGS | C/CPP 源文件公共编译参数 | +| CFLAGS | C 源文件独有编译参数 | +| CXXFLAGS | CPP 源文件独有编译参数 | +| CPPPATH | 头文件路径 | +| CPPDEFINES | 编译时添加宏定义,即全局宏定义 | +| LIBRARY | 是否将Group构建为静态库 | -* `objs = []:` 定义了一个空的 list 型变量 objs。 +#### 3.2.3 GetCurrentDir() -* `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 中来。 +```python +cwd = GetCurrentDir() # 获取当前脚本的路径 +``` -通过这个 SConscript 文件,BSP 工程所需要的源代码就被加入了编译列表中。 +#### 3.2.4 GetDepend(depend) -### SConscript 示例 2 +查看是否定义了宏依赖,例如: -那么 stm32f10x-HAL BSP 其他的 SConcript 文件又是怎样的呢?我们再看一下 drivers 目录下 SConcript 文件,这个文件将管理 drivers 目录下面的源代码。drivers 目录用于存放根据 RT-Thread 提供的驱动框架实现的底层驱动代码。 +```python +if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): # 判断是否启用MSH的内建命令 + src += ['cmd.c'] +``` -```c -Import('rtconfig') -from building import * +#### 3.2.5 AddDepend(option) -cwd = GetCurrentDir() +添加一个宏定义。 参数: option:添加的宏 -# add the general drivers. -src = Split(""" -board.c -stm32f1xx_it.c -""") - -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'] +#### 3.2.6 GetConfigValue(name) -if rtconfig.CROSS_TOOL == 'gcc': - src += ['gcc_startup.s'] +获得配置的值(宏定义的值)。 参数: name:宏定义 -CPPPATH = [cwd] +#### 3.2.7 GetVersion() -group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH) +获得RTT版本信息 -Return('group') +#### 3.2.8 GlobSubDir(sub_dir, ext_name) -``` +对目录下所有文件(包含子目录)进行Glob -* `Import('rtconfig'):` 导入 rtconfig 对象,后面用到的 rtconfig.CROSS_TOOL 定义在这个 rtconfig 模块。 +#### 3.2.9 BuildPackage(package) -* `from building import *:` 把 building 模块的所有内容全都导入到当前模块,后面用到的 DefineGroup 定义在这个模块。 +按照json提供的格式,定义一个Group。 参数: package:json文件 -* `cwd = GetCurrentDir():` 获得当前路径并保存到字符串变量 cwd 中。 +### 3.3 Python 函数 -后面一行使用 `Split()` 函数来将一个文件字符串分割成一个列表,其效果等价于 +Sconscript 脚本中可以使用任何 Python 库函数,使用方式和正常的 Python 脚本无异。这也是 Scons 构建工具强大之处所在,可以借助 Python 灵活的语法和丰富的库,完成 CMake 或者 Makefile 无法做到的事情。例如可以导入 os 标准库,实现对文件和目录的相关操作。 -`src = ['board.c','stm32f1xx_it.c']` -后面使用了 if 判断和 `GetDepend()` 检查 rtconfig.h 中的某个宏是否打开,如果打开,则使用 `src += [src_name]` 来往列表变量 src 中追加源代码文件。 +## 4 Scons 函数在 SConscript 中的经典示例 -* `CPPPATH = [cwd]:` 将当前路径保存到一个列表变量 CPPPATH 中。 +下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 -最后一行使用 DefineGroup 创建一个名为 Drivers 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏。 +### 4.1 构建一个基本的 Group -`CPPPATH =CPPPATH` 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。 +```python +# 导入RT-Thread的自定义构建函数,几乎每一个RT-Thread的Sconscript都需要这么做。 +# 只有导入了building模块,才可以使用2.2.2介绍的RT-Thread自定义Scons函数 +from building import * -### SConscript 示例 3 +cwd = GetCurrentDir() # 获取当前脚本的路径 +CPPPATH = [cwd] # 将当前路径加入构建搜索的头文件路径 +src = Split(''' +shell.c +msh.c +''') -我们再看一下 applications 目录下的 SConcript 文件,这个文件将管理 applications 目录下面的源代码,用于存放用户自己的应用代码。 +#也可以等效成:src = ['shell.c', 'msh.c'] -```c -from building import * +if GetDepend('MSH_USING_BUILT_IN_COMMANDS'): # 判断是否启用MSH的内建命令 + src += ['cmd.c'] -cwd = GetCurrentDir() -src = Glob('*.c') -CPPPATH = [cwd, str(Dir('#'))] +if GetDepend('DFS_USING_POSIX'): # 判断是否启用文件系统的POSIX接口 + src += ['msh_file.c'] -group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) +# 使用DefineGroup创建一个名为Finsh的组 +# 该Group是否被添加到工程中,参与编译,取决于depend的宏是否在Kconfig中被使能。 +# 即上述的头文件路径和C文件是否被编译,取决于用户在Kconfig中是否使能了RT_USING_FINSH +group = DefineGroup('Finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH) -Return('group') +Return('group') # 将当前脚本指定的构建对象返回上级SCons脚本 ``` -`src = Glob('*.c'):` 得到当前目录下所有的 C 文件。 +这里需要注意: -`CPPPATH = [cwd, str(Dir('#'))]:` 将当前路径和工程的 SConstruct 所在的路径保存到列表变量 CPPPATH 中。 +1. src 和 depends参数是必选项,即便你不想添加任何C文件,也需要定义一个空的列表。例如: -最后一行使用 DefineGroup 创建一个名为 Applications 的组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏,并将 CPPPATH 保存的路径添加到了系统头文件搜索路径中。这样 applications 目录和 stm32f10x-HAL BSP 目录里面的头文件在源代码的其他地方就可以引用了。 + ```python + src = [] + group = DefineGroup('Finsh', src, depend = [''], CPPPATH = CPPPATH) + ``` -总结:这个源程序会将当前目录下的所有 c 程序加入到组 Applications 中,因此如果在这个目录下增加或者删除文件,就可以将文件加入工程或者从工程中删除。它适用于批量添加源码文件。 +2. 头文件路径的参数不是必选项,如果不想添加头文件目录,就不需要填写 `CPPPATH` 参数 -### SConscript 示例 4 +3. 最后一行使用 DefineGroup 创建一个名为 Finsh 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,如果depend 为空表示该组不依赖任何 rtconfig.h 的宏。 -下面是 RT-Thread 源代码 component/finsh/SConscript 文件的内容,这个文件将管理 finsh 目录下面的源代码。 +4. `CPPPATH =CPPPATH` 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。 -```c -Import('rtconfig') -from building import * +5. 如果有其他 Sconscript 文件也创建了相同的组名(例如,都叫 `Finsh`),Scons 会自动将这两个组的信息并入到一起。 -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 -''') +### 4.2 桥接文件 -msh_src = Split(''' -msh.c -msh_cmd.c -msh_file.c -''') +在一些文件夹内的 Sconscript 脚本中,会发现有如下的代码。这些代码并没有实际去将某些C文件或者头文件路径加入到工程中。它所起到的作用是桥接,即让 Scons 继续读取其子文件夹内的 Sconscript 脚本,进而继续构建工程: -CPPPATH = [cwd] -if rtconfig.CROSS_TOOL == 'keil': - LINKFLAGS = '--keep *.o(FSymTab)' +```python +# RT-Thread building script for bridge - if not GetDepend('FINSH_USING_MSH_ONLY'): - LINKFLAGS = LINKFLAGS + '--keep *.o(VSymTab)' -else: - LINKFLAGS = '' +import os # 使用Python标准库,用于文件以及路径相关的操作 +from building import * -if GetDepend('FINSH_USING_MSH'): - src = src + msh_src -if not GetDepend('FINSH_USING_MSH_ONLY'): - src = src + fsh_src +cwd = GetCurrentDir() +objs = [] +list = os.listdir(cwd) -group = DefineGroup('finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH, LINKFLAGS = LINKFLAGS) +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('group') +Return('objs') ``` -我们来看一下文件中第一个 Python 条件判断语句的内容,如果编译工具是 keil,则变量 `LINKFLAGS = '--keep *.o(FSymTab)'` 否则置空。 +### 4.3 全局宏定义的定义 -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 * -`LINKFLAGS = LINKFLAGS` 的含义与 `CPPPATH = CPPPATH` 类似。左边的 LINKFLAGS 表示链接参数,右边的 LINKFLAGS 则是前面 if else 语句所定义的值。也就是给工程指定链接参数。 +src = ['ipc.c'] # 添加C文件 -## 使用 SCons 管理工程 +CPPDEFINES = ['__RTTHREAD__'] # 全局宏定义 -前面小节对 RT-Thread 源代码的相关 SConscript 做了详细讲解,大家也应该知道了 SConscript 文件的一些常见写法,本小节将指导大家如何使用 SCons 管理自己的工程。 +# depend 为空表示该组不依赖任何 rtconfig.h 的宏 +group = DefineGroup('Kernel', src, depend = [''], CPPDEFINES = CPPDEFINES) -### 添加应用代码 +Return('group') +``` -前文提到过 BSP 下的 Applications 文件夹用于存放用户自己的应用代码,目前只有一个 main.c 文件。如果用户的应用代码不是很多,建议相关源文件都放在这个文件夹下面。在 Applications 文件夹下新增了 2 个简单的文件 hello.c 和 hello.h,内容如下所示。 +所谓全局宏定义,就是即便引用任何 RT-Thread 的头文件,你也可以使用直接使用 `__RTTHREAD__` 这个宏定义。 -```c -/* file: hello.h */ +### 4.4 与编译器相关 -#ifndef _HELLO_H_ -#define _HELLO_H_ +如下的 Sconscript 脚本展示了如何判断不同的编译器,并根据不同的编译器来进行不同的处理: -int hello_world(void); +```python +if rtconfig.CROSS_TOOL == 'iar': # 判断是否使用IAR编译器 +if rtconfig.CROSS_TOOL == 'keil': # 判断是否使用Keil编译器 +if rtconfig.CROSS_TOOL == 'msvc': # 判断是否使用Visual Studio编译器 +if rtconfig.PLATFORM == 'armcc': # 判断是否使用Keil-AC5,即armcc编译工具链 +if rtconfig.PLATFORM == 'armclang': # 判断是否使用Keil-AC6,即armclang编译工具链 +if rtconfig.PLATFORM == 'iar': # 判断是否使用IAR编译器的工具链 +if rtconfig.PLATFORM == 'gcc': # 判断是否使用gcc编译工具链 +``` -#endif /* _HELLO_H_ */ +下面的例子中,展示了针对不同的编译器/工具链,来进行设置不同的编译标志: -/* file: hello.c */ -#include -#include -#include +源码位置:https://github.com/mysterywolf/RTduino/blob/master/core/SConscript -int hello_world(void) -{ - rt_kprintf("Hello, world!\n"); +```python +from building import * +import rtconfig - return 0; -} +cwd = GetCurrentDir() +src = Glob('*.c') + Glob('*.cpp') +inc = [cwd] + +LOCAL_CCFLAGS = '' +LOCAL_CFLAGS = '' +LOCAL_CXXFLAGS = '' + +if rtconfig.PLATFORM == 'gcc' or rtconfig.PLATFORM == 'armclang': # GCC or Keil AC6 + LOCAL_CFLAGS += ' -std=c99' + LOCAL_CXXFLAGS += ' -std=c++11' # support C++11, like non-static data member initializers +elif rtconfig.PLATFORM == 'armcc': # Keil AC5 + LOCAL_CCFLAGS += ' --gnu -g -W' + LOCAL_CFLAGS += ' --c99' # cannot use --c99 symbol for C++ files, pertically in Keil + LOCAL_CXXFLAGS += ' --cpp11' # support C++11 + +group = DefineGroup('Arduino', src, + depend = ['PKG_USING_RTDUINO'], + CPPPATH = inc, + CPPDEFINES = ['ARDUINO=100', 'ARDUINO_ARCH_RTTHREAD'], + LOCAL_CCFLAGS = LOCAL_CCFLAGS, + LOCAL_CFLAGS = LOCAL_CFLAGS, + LOCAL_CXXFLAGS = LOCAL_CXXFLAGS) -MSH_CMD_EXPORT(hello_world, Hello world!) +Return('group') ``` -applications 目录下的 SConcript 文件会把当前目录下的所有源文件都添加到工程中。需要使用 `scons --target=xxx` 命令才会把新增的 2 个文件添加到工程项目中。注意每次新增文件都要重新生成工程。 - -### 添加模块 +### 4.5 添加模块 前文提到在自己源代码文件不多的情况下,建议所有源代码文件都放在 applications 文件夹里面。如果用户源代码很多了,并且想创建自己的工程模块,或者需要使用自己获取的其他模块,怎么做会比较合适呢? @@ -461,13 +483,13 @@ Return('group') 上面只是简单列举了在 Kconfig 文件中添加自己模块的配置选项,用户还可以参考[《Env 用户手册》](../env/env.md),里面也有对配置选项修改和添加的讲解,也可以自己百度查看 Kconfig 的相关文档,实现其他更复杂的配置选项。 -### 添加库 +### 4.6 添加库 如果要往工程中添加一个额外的库,需要注意不同的工具链对二进制库的命名。 -- 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。 @@ -483,7 +505,7 @@ libabc_gcc.a 则对应的 SConscript 如下: -``` +```python Import('rtconfig') from building import * @@ -505,7 +527,7 @@ group = DefineGroup('ABC', src, depend = [''], LIBS = LIBS, LIBPATH=LIBPATH) Return('group') ``` -### 编译器选项 +## 5 编译器选项 (rtconfig.py) rtconfig.py 是一个 RT-Thread 标准的编译器配置文件,控制了大部分编译选项,是一个使用 python 语言编写的脚本文件,主要用于完成以下工作: @@ -608,11 +630,13 @@ if os.getenv('RTT_EXEC_PATH'): 编译器配置完成之后,我们就可以使用 SCons 来编译 RT-Thread 的 BSP 了。在 BSP 目录打开命令行窗口,执行 `scons` 命令就会启动编译过程。 -### RT-Thread 辅助编译脚本 +## 6 其他 + +### 6.1 RT-Thread 辅助编译脚本 在 RT-Thread 源代码的 tools 目录下存放有 RT-Thread 自己定义的一些辅助编译的脚本,例如用于自动生成 RT-Thread 针对一些 IDE 集成开发环境的工程文件。其中最主要的是 building.py 脚本。 -### SCons 更多使用 +### 6.2 SCons 更多使用 对于复杂、大型的系统,显然不仅仅是一个目录下的几个文件就可以搞定的,很可能是由数个文件夹一级一级组合而成。