【ARM 嵌入式 番外篇 编译系列 8 -- RT-Thread 编译命令 Scons 详细讲解】


请阅读【ARM GCC 编译专栏导读】


上篇文章:ARM 嵌入式 编译系列 7.1 – GCC 链接脚本中节区及各个段的详细介绍
下篇文章:ARM 嵌入式 编译系列 9-- GCC 编译符号表(Symbol Table)的详细介绍

1.1 RT-Thread Scons 编译介绍

SCons 是一个开源的构建工具,类似于 Make,但 SCons 使用 Python 脚本作为构建脚本。在 RT-Thread 实时操作系统中,SCons 用于构建和编译整个 RT-Thread 系统。

以下是一些常用的 SCons 命令:

  • scons:执行 SConstruct 脚本,构建项目。如果没有指定目标,将构建默认目标。

  • scons -c 或 scons --clean:清理已构建的目标。

  • scons --help:显示描述当前 SCons 构建环境和可用选项的帮助信息。

  • scons -sscons --silent:静默模式,不显示正在执行的命令。

  • scons -j N:并行构建,N 是并发任务的数量,通常设置为 CPU 核心数。

需要注意:使用 SCons 构建 RT-Thread 时,需要在 RT-Thread 的根目录下创建一个名为 rtconfig.h 的配置文件,这个文件定义了 RT-Thread 的各种配置选项。

#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__

/* Automatically generated file; DO NOT EDIT. */
/* RT-Thread Configuration */

/* RT-Thread Kernel */

#define RT_NAME_MAX 16
#define RT_ALIGN_SIZE 4
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 1000
#define RT_USING_OVERFLOW_CHECK
#define RT_USING_HOOK
#define RT_USING_IDLE_HOOK
#define RT_IDLE_HOOK_LIST_SIZE 4
#define IDLE_THREAD_STACK_SIZE 2048
#define RT_USING_TIMER_SOFT
#define RT_TIMER_THREAD_PRIO 4
#define RT_TIMER_THREAD_STACK_SIZE 2048

/* kservice optimization */

#define RT_DEBUG
...

1.1.1 Scons 编译层级设置

  • 第一层目录下会看到类似下面内容的 SConscript 脚本,
# for module compiling
import os
from building import *

cwd  = GetCurrentDir()
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')

上面脚本的内容主要有以下几个方面:
1)导入 python 相关的模块
2)获取当前目录下子目录中的SConscript脚本

  • 在子目录的脚本SConscript脚本中可以看到类似下面的内容:
from building import *

cwd     = GetCurrentDir()
src     = Glob('*.c')
CPPPATH = [cwd]   //将当前目录累加到总的头文件路径
group   = DefineGroup('DeviceDrivers', src, depend=[''], CPPPATH=CPPPATH)
Return('group')

上面内容主要有以下几方面内容:
1)获取当前目录路径;
2)使用Glob(*.c)包含要编译的所有 c 文件;
3)将当前路径赋值给 CPPPATH,用于将当前路径配置为全局include路径;
4)当前路径编译配置,其中 depend = [''] 表示要编译当前路径中的内容所依赖的宏,在这个脚本中为空,也即不依赖任何宏。

1.1.2 Scons DependedSrc 的使用

在代码的编译过程中有时存在下面这种情况,也即编译某个 C 文件时需要依赖某一个宏,但是同一级目录下其他的C文件并不依赖这个宏,这个时候就可以用到 DependeSrc 这个函数了,如下脚本中的内容,表示编译 test.cc 时需要依赖宏 ”ARM_DEMO“,如果 ARM_DEMO 没有定义,那么 test.cc 不会加入编译中,而其他文件不受影响。

from building import *

cwd     = GetCurrentDir()
src = []

src += DependedSrc('ARM_DEMO', ['test.cc'])

group   = DefineGroup('ARM_DEMO', src, depend = ['DEMO'])

Return('group')

1.1.3 Scons CCFLAGS 的使用

在代码编译过程中有时会增加自己需要的一些内容,比如定义一个宏,这个时候就可以使用到Scons SConscript中的
CCFLGAS 功能,如下内容为定义一个 DEMO_TEST 宏:

import rtconfig
from building import *

cwd = GetCurrentDir()
group = []
src =  Glob('test1.c')
src += Glob('test2.c')

group += DefineGroup('DEMO', src, depend = [''], CCFLAGS = ' -DDEMO_TEST')

# add all SConscripts in sudir
if GetDepend(['DEMO_FOO']):  #如果配置了DEMO_FOO 宏下面的内容才会走到
    list = os.listdir(cwd)

    for d in list:
        sub_sconscript = os.path.join(cwd, d, 'SConscript')
        if os.path.isfile(sub_sconscript):
            vdir = BOARD_ROOT + "/build/" + os.path.relpath(cwd, start = TOP_ROOT) + "/" + d
            group += SConscript(sub_sconscript, variant_dir = vdir, duplicate = 0)

Return('group')

1.1.4 Scons defconfig 介绍

在芯片开发的不同阶段需要建立不同的 profile(板级),比如在前期验证阶段只需要建立 fpga 相关的 profile,在芯片回来之前需要建立 udp 的 profile,不同的profile所使用的 config文件也不同,但是 SoC 级别的内容是不变的,所以SoC 相关的内容会独立出来。

比如在 rt-thread/bsp/vendor/ 建立不同的 profile 目录:

fpga  evb common
  • fpga 为 fpga 验证阶段使用;
  • evb 为回片后所使用;
  • common 为 soc 内容,fpga 和 evb 共同都可以使用。

common 目录中通常会进行一些默认配置,在 rtthread 中,这些默认配置使用的 DEVICE_CONFIG_DEF

/**
 * DEVICE_CONFIG_DEF - define some default devices in a platform
 * @_compat: compatible flag, used to bind the device and driver
 * @_type: device config structure (device descriptor)
 * @...: body of device config array
 *
 * The parameter @_compat must be the same with it in macro DEVICE_INIT_CALL
 * or DEVICE_XXX_INIT.
 *
 * This macro helps to define some default devices in a platform.
 *
 * A sample usage:
 * DEVICE_CONFIG_DEF(sample, sample_type_t,
 *      { ... },
 *      { ... },
 *      ...
 * );
 */
#define DEVICE_CONFIG_DEF(_compat, _type, ...)                          \
        static _type _##_compat##_config_arr[] = { __VA_ARGS__ };       \
        RT_WEAK void *_##_compat##_config_ptr = _##_compat##_config_arr;\
        RT_WEAK int _##_compat##_config_step = sizeof(_type);           \
        RT_WEAK int _##_compat##_config_cnt = ARRAY_SIZE(_##_compat##_config_arr)

而在不同的 profile目录下的 device_config.c中会使用 DEVICE_CONFIG 宏来进行配置:

/**
 * DEVICE_CONFIG - define some real used devices in a BSP project
 * @_compat: compatible flag, used to bind the device and driver
 * @_type: device config structure (device descriptor)
 * @...: body of device config array
 *
 * The parameter @_compat must be the same with it in macro DEVICE_INIT_CALL
 * or DEVICE_XXX_INIT.
 *
 * This macro helps to define some real used devices in a BSP project, and
 * will override the definitions generated by DEVICE_CONFIG_DEF (if existed)
 * with the same @_compat.
 *
 * A sample usage:
 * DEVICE_CONFIG(sample, sample_type_t,
 *      { ... },
 *      { ... },
 * );
 */
#define DEVICE_CONFIG(_compat, _type, ...)                              \
        static _type _##_compat##_config_arr[] = { __VA_ARGS__ };       \
        void *_##_compat##_config_ptr = _##_compat##_config_arr;        \
        int _##_compat##_config_step = sizeof(_type);                   \
        int _##_compat##_config_cnt = ARRAY_SIZE(_##_compat##_config_arr)

从上面的定义可以看出,DEVICE_CONFIG 宏会覆盖 DEVICE_CONFIG_DEF所实现的内容,这个是因为不同 board 会使用不同的配置。

不同的场景需要使用不同的 .config 文件,我们知道.config的生成是通过menuconfig配置后生成,我们可以通过生成自己所需要的.config,然后再将其命名为 xxx_defconfig 文件,在编译的时候可以直接使用下面命令即可完成自己所需配置:

$ scons --defconfig="xxx_defconfig"

再看下 scons --defconfig 的使用:

--stackanalysis             thread stack static analysis
  --genconfig                 Generate .config from rtconfig.h
  --useconfig=USECONFIG       make rtconfig.h from config file.
  --defconfig=DEFCONFIG       build with the .config from
                                'configs/xxx_defconfig'
  --verbose                   print verbose information during build
  --menuconfig                make menuconfig for RT-Thread BSP

从上面的说明可以看到,这条命令会从 configs 目录下找 xxx_defconfig, 然后根据其再生成 .config。

我们可以在 rtthread 代码中可以看到类似下面的代码:

demo/board/SConscript
from building import *

if GetDepend('DEVICE_CONFIG_NAME'):
        device_config_file = GetConfigValue('DEVICE_CONFIG_NAME').strip('"') + '.c'
else:
        device_config_file = 'device_config.c'

其中GetDepnd 函数中的参数就是“xxx”, 在使用scons命令是如果后面跟有参数 “xxx” 就会使用xxx_defconfig的配置文件,如果没有参数,那么就使用默认的.config文件。

1.2 Scons Kconfig 的使用

通常情况都是 SConscript 配合 Kconfig 一块使用,如下:

$: ~/demo/rt-thread/bsp/demo/libraries$ ls
drivers  include kernel Kconfig  SConscript

其中 libraries 目录下的 Kconfig 中会包含 其子目录下的 Kconfig 文件:

source "$LIB_DIR/drivers/Kconfig"
source "$LIB_DIR/kernel/Kconfig"

在执行 scons --menuconfig 的时候会遍历所有的 Kconfig 文件,并根据 Kconfig 文件中的默认配置来生成 .config
在执行 scons 编译命令的时候就会使用 .config中配置的宏,通常在生成 .config 的时候会自动生成一个 .h文件,我们当前项目中自动生成的.h文件叫做 rtconfig.h,其内容如下:

#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__

/* Automatically generated file; DO NOT EDIT. */
/* RT-Thread Configuration */

/* RT-Thread Kernel */

#define RT_NAME_MAX 24
#define RT_ALIGN_SIZE 8
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 1000
#define RT_USING_OVERFLOW_CHECK
#define RT_USING_HOOK
#define RT_USING_IDLE_HOOK
#define RT_IDLE_HOOK_LIST_SIZE 4
#define IDLE_THREAD_STACK_SIZE 256
#define RT_USING_TIMER_SOFT
#define RT_TIMER_THREAD_PRIO 4
#define RT_TIMER_THREAD_STACK_SIZE 1024

/* kservice optimization */

#define RT_DEBUG

/* Inter-Thread communication */

#define RT_USING_SEMAPHORE
#define RT_USING_MUTEX
#define RT_USING_EVENT
#define RT_USING_MAILBOX
#define RT_USING_MESSAGEQUEUE
#define RT_USING_SIGNALS
...

.config 中所以配置生效的宏(配置為 “y” 或者配置為某個常数值)都会生成到 rtconfig.h中,所以这个.h会动态生成,会跟着scons --menuconfig的不同配置来生成的不同的rtconfig.h文件,如果继续跟踪就会发现 在rt-thread/include/rtthread.h 中会包含 rtconfig.h 这个头文件,对于 rtthread.h 这个头文件我们都知道他的功能像linux中的 linux/module.h,几乎所以地方都会用到。

1.2.1 Scons depend on 的使用

在编译代码的时候,有时一个模块A依赖另外一个模块B,如果B不编译的时候A也不能用,这个时候就需要一个依赖宏用来检查B模块是否参与了编译,如下:

menuconfig TEST_DEMO
    bool " DEMO TEST"
    defalult y

if TEST_DEMO
config TEST_DEMO1
    bool "Enable test demo1"
    depends on TEST_DEMO2
    default y
endif

上面的内容含义是:

  • 即便 TEST_DEMO1 的 默认值为 y, 但是如果没有配置 TEST_DEMO 那么 就不会出现 TEST_DEMO1
  • 即便 TEST_DEMO 定义了,如果 TEST_DEMO2 没有定义,那么TEST_DEMO1也无法生效。

上篇文章:ARM 嵌入式 编译系列 7.1 – GCC 链接脚本中节区及各个段的详细介绍
下篇文章:ARM 嵌入式 编译系列 9-- GCC 编译符号表(Symbol Table)的详细介绍

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
RT-Thread是一个开源的实时操作系统,而SconsRT-Thread中用于编译的工具。Scons编译层级设置可以通过设置Scons编译参数来指定编译的层级。\[1.1.1\] Scons DependedSrc的使用可以用于指定编译所依赖的源文件。\[1.1.2\] Scons CCFLAGS的使用可以用于指定编译时的编译选项。\[1.1.3\] Scons defconfig介绍是指通过使用Scons的defconfig命令来生成默认的配置文件。\[1.1.4\] Scons Kconfig的使用是指通过使用Scons的Kconfig命令来配置编译选项。\[1.2.1\] Scons depend on的使用是指通过使用Sconsdepend on命令来指定编译的依赖关系。\[2\] Scons编译指令是指根据Scons脚本的配置来组织编译代码。\[3\]在RT-Thread BSP目录下,通常会存在rtconfig.py、SConstruct和SConscript这三个文件,它们控制BSP的编译SConstruct文件是SCons默认解析的第一个脚本,而SConscript文件是用于组织SCons代码的脚本。\[4\] SConscript文件位于子目录中,用于指定子目录下的编译规则和依赖关系。\[5\] #### 引用[.reference_title] - *1* *2* [【RT-Thtread 编译入门及渐进 2-- Scons 命令介绍】](https://blog.csdn.net/sinat_32960911/article/details/128845812)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* *4* *5* [rtthread_scons简介](https://blog.csdn.net/weixin_51554391/article/details/119913404)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值