ENV工具的使用声明
env和scons工具在我第一次接触时候,就深深喜欢这样的配置方式。在env工具的加持下,配置工程变得轻而易举;学会使用scons工具不能局限于使用,制作出package才能活学活用,进一步改善开发环境。
按照我现在对ENV工具的简单理解,在RT-Thread下,env工具的menuconfig模式可以依照Kconfig文件来显示图形界面,配置生成rtconfig.h文件;而后scons工具依赖rtconfig.h文件和 SConstruct,SConscript文件来生成IDE工程。
目录
- ENV工具的使用声明
- Kconfig文件
- Kconfig与menuconfig的关系
- Kconfig语法
- config条目
- menu条目
- choices条目
- source条目
- Kconfig在RT-Thread工程中的位置
- SCons工具文件
- SConscript文件
- Sconscript文件的仿写
- 编写自动配置工程
- 例程:(音乐播放器)
Kconfig文件
Kconfig与menuconfig的关系
Kconfig按照一定的格式来书写,menuconfig程序可以识别这种格式,然后从中提取
出有效信息组成menuconfig中的配置项。用户配置相关配置项后会在rtconfig.h中生
成对应的宏。在menuconfig界面下的界面,取决于Kconfig文件的内容。Kconfig语言内容将决定menuconfig界面。在RT-Thread下,现在主要讲config条目,menu条目,choice条目,source条目。
Kconfig语法
config条目
config条目属性:
1. 类型属性:“bool”/“tristate”/“string”/“hex”/"int“
2. 输入提示:"prompt " prompt [“if” expr ]
3. 依赖关系:“depends on”/“requires” expr
4. 选择关系:“select” symbol [“if” expr ]
5. 帮助信息:“help” or “—help—”
6. 默认配置:“default” expr [“if” expr ]
config条目的类型多为bool,string,int,hex;而且用法常为基础配置,常嵌入到menu条目,choices条目里。除此之外,还有一种和menu结合的简易写法menuconfig,在menuconfig界面下显示为
menuconfig XIANGXISTUTEST
bool "xiangxistu menuconfig test"
select XIANGXISTU
default n
if XIANGXISTUTEST
config BSP_USING_CH1
bool "Enable Channel 1"
default n
config BSP_USING_CH2
bool "Enable Channel 2"
default n
config BSP_USING_CH3
bool "Enable Channel 3"
default n
config BSP_USING_CH4
bool "Enable Channel 4"
default n
endif
depend on的使用方法就是下面代码所示,具体现象是下面图片显示的。
config RT_USING_XIANGXISTU
bool "Test Depends on "
default n
help
This is help interface.
config RT_XIANGXISTU
int "int default is 2"
default 2
depends on RT_USING_XIANGXISTU
help
If RT_USING_XIANGXISTU is empty,you cann't see this interface.
select项更像是直接确定这个宏定义不需要配置,直接添加到列表里。下面是RT-Thread在配置Signal时的配置方式,一旦开启signal,就必定打开内存池。
config RT_USING_SIGNALS
bool "Enable signals"
select RT_USING_MEMPOOL
default n
help
A signal is an asynchronous notification sent to a specific thread
in order to notify it of an event that occurred.
config RT_USING_SIGNALS
config RT_USING_MEMPOOL
bool "Using memory pool"
default y
help
Using static memory fixed partition
一旦打开Signal,就可以发现这个的配置。
menu条目
menu条目为了展开到下一级,用法简单,不赘述。
menu "USB Host Config"
config USBH_USING_CONTROLLABLE_POWER
bool "using a gpio control usb power"
default y
if USBH_USING_CONTROLLABLE_POWER
config USBH_POWER_PIN
int "power control pin"
default 15
config USBH_CURRENT_PIN
int "current check pin"
default 86
endif
config USBH_USING_VBUS
bool "using vbus check pin"
default n
endmenu
choices条目
choice条目用法也比较单一,很容易理解。
choice
prompt "xiangxistu type"
default xiangxi_default
config SOC_one
bool "SOC_one"
config SOC_two
bool "SOC_two"
config SOC_three
bool "SOC_onethree"
endchoice
source条目
source条目是为了加载其他路径下的Kconfig文件。
config $BSP_DIR
string
option env="BSP_ROOT"
default "." #当前目录
config $RTT_DIR
string
option env="RTT_ROOT"
default "../.." #上上级目录
config $PKGS_DIR
string
option env="PKGS_ROOT" #env/packages/
default "packages"
source "$RTT_DIR/Kconfig"
source "$PKGS_DIR/Kconfig"
负责任的说,$PKGS_DIR/Kconfig的目录是env工具下的packages下的Konfig
menu "RT-Thread online packages"
source "$PKGS_DIR/packages/iot/Kconfig"
source "$PKGS_DIR/packages/security/Kconfig"
source "$PKGS_DIR/packages/language/Kconfig"
source "$PKGS_DIR/packages/multimedia/Kconfig"
source "$PKGS_DIR/packages/tools/Kconfig"
source "$PKGS_DIR/packages/system/Kconfig"
source "$PKGS_DIR/packages/peripherals/Kconfig"
source "$PKGS_DIR/packages/misc/Kconfig"
endmenu
Kconfig在RT-Thread工程中的位置
Kconfig在rt-thread工程中,有四个地方可以看到
“$RTT_DIR/src/Kconfig” #kernel,即src路径下
“$RTT_DIR/libcpu/Kconfig” #芯片内核选择,我总感觉这里没有用开配置,而是Sconscript直接写死的
“$RTT_DIR/components/Kconfig” #RT-Thread组件选择
“$PKGS_DIR/packages/Kconfig” #这个在env/packages路径下,不在rt-thread下
SCons工具文件
SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make。它采用不同于通常 Makefile 文件的方式,而是使用 SConstruct 和 SConscript 文件来替代。这些文件也是 Python 脚本,能够使用标准的 Python 语法来编写。
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
---bsp
------stm32f4xx-hal
---------applications
------------SConscript
---------drivers
------------SConscript
---------Libraries
------------SConscript
---------packages
------------SConscript
---------rtconfig.py
---------SConstruct
---------SConscript
---components
------SConscript
---libcpu
------SConscript
---src
------SConscript
---examples
------SConscript
我们通常都是在bsp下选定芯片位置来开启env工具,配置menuconfig,以用来修改rtconfig.h文件。配置menuconfig后保存退出,使用scons --target=mdk5 命令,用来生成可执行的工程。
不过,我现在对python这种语言在RT-Thread里的作用理解还很基础,而且,我对这门语言的水平还是菜鸡阶段,不能分析python.py和SConsstruct文件的每条语句的意思,对/tool目录下的*py脚本文件不熟悉。不过忽略掉这也很重要的细节,仅仅使用SConscript文件也可以配置好工程。
对rtconfig.py和SConstruct文件而言,这里是配置编译工具链,并且调用tools下的keil.py文件来生成工程。为了将SConscript文件收集的c文件和h文件的目录交给最后的工程生成工作。
SConscript文件
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() 相同。
- 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 工程所需要的源代码就被加入了编译列表中。
Import('rtconfig')
from building import *
cwd = GetCurrentDir()
# 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']
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 中追加源代码文件。
- CPPPATH = [cwd]: 将当前路径保存到一个列表变量 CPPPATH 中。
最后一行使用 DefineGroup 创建一个名为 Drivers 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏。- CPPPATH =CPPPATH 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。
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 目录里面的头文件在源代码的其他地方就可以引用了。
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] #如果编译工具是 keil,则变量 LINKFLAGS = '--keep *.o(FSymTab)' 否则置空
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')
- 将 finsh 目录加入到系统头文件目录中,这样我们就可以在其他源码中引用 finsh 目录下的头文件。
LINKFLAGS = LINKFLAGS 的含义与 CPPPATH = CPPPATH 类似。左边的 LINKFLAGS 表示链接参数,右边的 LINKFLAGS 则是前面 if else 语句所定义的值。也就是给工程指定链接参数。
Sconscript文件的仿写
from building import *
# get current dir path
cwd = GetCurrentDir()
# init src and inc vars
src = []
inc = []
inc = inc + [cwd]
src = src + ['./player.c']
# add group to IDE project
group = DefineGroup('Player', src, depend = ['MUSIC_PLAY'], CPPPATH = inc)
Return('group')
Python语法很有规律,至少在RT-Thread工程下,配置有章有法;而且语句不唯一,实现方式多种多样。这是自己改写的一个SConscript文件,用来自动配置工程。依赖项为MUSIC_PLAY,自己在rtconfig里添加,或者用menuconfig配置。
编写自动配置工程
//Kconfig下的增加内容
config MUSIC_PLAY
bool "Music_player"
default n
选中退出后,可以查看到rtconfig.h里的配置内容
使用 scons --target=mdk5 -j4 命令
例程:(音乐播放器)
例程是RT-Thread提供的,不包含自动配置部分。