rtthread_scons简介

env工具基本构成在这里插入图片描述

  1. 命令行环境Cmder: Cmder是一款免费的DOS系统仿真器,体积小巧,界面清爽,支持多标签操作,兼容dos原有的指令。
  2. 系统配置工具menuconfig: 即linux menuconfig的python实现版本,使用方法与linux menuconfig基本一致。遵循兼容LINUX的Kconfig语法,用户可以方便的沿用以前的kconfig配置文件对代码进行宏管理。
  3. 开源软件scons: scons是一个Python写的自动化构建工具,从构建这个角度说,它跟GNU make是同一类的工具,是一种改进,并跨平台的gnu make替代工具,其集成功能类似于autoconf/automake。scons是一个更简便,更可靠,更高效的编译软件。
  4. Python环境与.py文件: 主要目的是将scons, menuconfig, MinGW等有机的整合到Cmder中,以及添加scons的扩展选项,支持生成KEIL等可直接使用的项目文件。
  5. 交叉编译工具链MinGW: MinGW是Minimalist GNUfor Windows的缩写。它是一个可自由使用和自由发布的Windows特定头文件和使用GNU工具集导入库的集合,允许你在GNU/Linux和Windows平台生成本地的Windows程序而不需要第三方C运行时(C Runtime)库。

打开ENV工具进入具体工程目录
C:\Users\Admin\Desktop\data\rt-thread\bsp\stm32\stm32f407-atk-explorer

scons 					//使用默认的 ARM_GCC 工具链编译 bsp, scons 命令会根据 rtconfig.h文件编译工程
scons -c				//清除编译目标。这个命令会清除执行 scons 时生成的临时文件和目标文件
scons --target=iar		//重新生成工程
scons --target=mdk5
scons --verbose			//默认情况下,使用 scons 命令编译的输出不会显示编译参数

menuconfig工作机制

  1. menuconfig启动后默认到当前目录下寻找Kconfig文件,并解析。Kconfig中可以通过source指定加载子Kconfig文件,这样根据Kconfig文件的内部调用结构,menuconfig依次解析所有被引用的Kconfig文件,生成内部的配置选项数据库
  2. 解析.config文件,根据上一次的配置结果初始化各个配置选项的初值。
  3. 给用户展示配置界面,并根据用户的选择更新内部数据库。

Kconfig文件
(目录:C:\Users\Admin\Desktop\data\rt-thread\bsp\stm32\stm32f407-atk-explorer)

mainmenu "RT-Thread Configuration" //定义主菜单标题

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"
    default "packages"
 
source "$RTT_DIR/Kconfig"  
source "$PKGS_DIR/Kconfig"
source "../libraries/Kconfig"  //加载其他目录下的Kconfig
source "board/Kconfig"
  1. BSP_DIR变量定义了BSP根目录,默认是. (因为Kconfig文件放置于BSP板级支持包目录下),除非系统中定义了BSP_ROOT的环境变量;
  2. RTT_DIR变量定义了RT-Thread 根目录,因为板级包目录默认放置在rt-thread/bsp目录下,所以这个变量的默认值是…/…,除非系统中定义了RTT_ROOT的环境变量;
  3. PKGS_DIR变量定义了RT-Thread包根目录,一般它会从系统的环境变量PKGS_ROOT中获得,而如果使用RT-Thread/env工具,env工具在启动console终端时会默认地定义这个环境变量;
  4. 这份Kconfig文件的后面部分则把相关的Kconfig文件都包含到这个Kconfig文件中来,最关键的是RT-Thread主干Kconfig文件 $RTT_DIR/Kconfig 和包Kconfig文件 $PKGS_DIR/Kconfig 。

用户退出交互界面并保存配置,menuconfig根据内部数据库内容,生成新的.config文件。
.config文件:

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

#
# RT-Thread Kernel
#
CONFIG_RT_NAME_MAX=8
# CONFIG_RT_USING_ARCH_DATA_TYPE is not set
# CONFIG_RT_USING_SMP is not set
CONFIG_RT_ALIGN_SIZE=4
# CONFIG_RT_THREAD_PRIORITY_8 is not set
CONFIG_RT_THREAD_PRIORITY_32=y
# CONFIG_RT_THREAD_PRIORITY_256 is not set
CONFIG_RT_THREAD_PRIORITY_MAX=32
CONFIG_RT_TICK_PER_SECOND=1000
CONFIG_RT_USING_OVERFLOW_CHECK=y
CONFIG_RT_USING_HOOK=y
CONFIG_RT_USING_IDLE_HOOK=y
CONFIG_RT_IDLE_HOOK_LIST_SIZE=4
CONFIG_IDLE_THREAD_STACK_SIZE=1024

将.config文件翻译成c语言能够识别的rtconfig.h文件,位于当前工作目录下。
通过上面的步骤,menuconfig根据用户的配置,最终生成了rtconfig.h文件,用户便可以直接引用rtconfig.h文件来使用系统宏。从下面截取的示例可以看到rtconfig.h文件和.config文件是一 一对应的关系
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 8
#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 1024

SCons基本命令

scons
编译指令,根据scons脚本(SConstruct, SConscript)的配置,组织编译代码。scons命令必须在工程路径下执行,因为scons默认会在当前路径下寻找SConstruct文件作为第一个解析的脚本

scons支持增量编译,对已经编译过的文件,且编译后没有进行过修改,那scons不会重新编译这些文件,从而达到提高编译效率的目的。

scons --verbose
直接使用scons编译时,默认是不会输出编译选项等信息的。使用scons --verbose进行编译时,将会打印编译器选项等辅助信息。

scons -c
清除上一次编译的临时文件。执行该命令后,下一次编译将能保证所有源文件都会被重新编译。

scons --help
帮助命令,该命令会例举出scons支持的所有操作选项,包括原生scons自带的选项与OneOS扩展的操作选项。scons支持的操作选项很丰富,推荐读者都去了解一下。

scons --ide=xxx
生成第三方IDE工程命令,其中“XXX”对应想要生成的工程类型。具体支持的工程类型可以通过scons --help查看。举例生成keil5工程的命令如下:

scons --ide=mdk5

需要注意的是,要生产指定的IDE工程文件,一般需要提供一个空的工程文件作为模版,用户应该在相应的模版文件中配置好硬件相关的编译选项。具体可以参考示例开发板的工程目录中的文件。

SCons编译构造概述

Scons是构造工具,通过读取SConstruct/Sconstruct/sconstruct和SConscript脚本来组织管理源码,并调用指定的编译链接器来对源文件进行编译。

scons目前支持的编译器包括 ARM GCC、MDK、IAR、VisualStudio、Visual DSP。对流行的ARM Cortex M0、M3、M4 等架构,一般都同时支持ARM GCC、MDK、IAR 等编译器。用户可以通过 project 目录下的osconfig.py 里的 CROSS_TOOL 选项查看相关的编译器配置。

使用SCons工具时,会使用到SConstruct/Sconstruct/sconstruct和SConscript 2种类型的文件。这2种类型的文件是Scons工具必备的,是编译的配置脚本文件,相当于make中的makefile文件。

Scons将在当前目录下按SConstruct、Sconstruct、sconstruct次序来搜索配置文件,从读取的第1个文件中读取相关配置。

在配置文件SConstruct中,可以使用SConscript()函数来读取附属的配置文件。按惯例,这些附属配置文件被命名为“SConscript”。

SCons的扩展性

scons的配置文件都是基于python脚本语法的。因此可以在SConstruct文件中使用python语言很方便的调用自定义的python脚本与模块。

同时,scons自身也是python实现的,在自己的python脚本文件中通过添加语句:
from SCons.Script import *

可以很方便的直接使用scons内置类与操作。正因为scons与python脚本可以方便的互相调用,我们可以对scons方便的进行自定义与扩展。

SCons常用函数

脚本涉及到的函数分为两部分:标准函数,自定义函数。

Import()、Export()函数
Import(vars)

导入其它脚本定义的变量

‘vars’变量名,变量名是字符串,可以是一个变量或一个变量列表

Export(vars)

导出变量,供其它脚本使用

‘vars’变量名,变量名是字符串,可以是一个变量或一个变量列表

SConscript()函数
SConscript(scripts, [exports, variant_dir, duplicate])

读取一个或多个sconscript脚本,返回一个node列表,node是指一个编译对象

‘scripts’:指定要加载的sconscript脚本名称与路径

‘exports’:可选参数,导出一个变量,此变量比一般Export()导出的变量有更高优先权

‘variant_dir’:可选参数,指定一个目录,编译动作都在该目录中进行,不影响源码路径

‘duplicate’:可选参数,指定编译目录中是否拷贝源码

PresentDir()函数
PresentDir()

获取当前脚本所在路径

Glob()函数
Glob(pattern)

返回满足pattern指定条件的Node(编译对象)

‘pattern’:指定匹配条件,支持unix shell的通配符替换。支持当前脚本所在路径进行相对路径索引

IsDefined()函数
IsDefined(depend)

判断依赖的宏或宏列表是否被定义,被定义返回True,如果存在未被定义的宏返回False

‘depend’:指定依赖的宏,或宏列表

DeleteSrcFile()函数
DeleteSrcFile(src, remove)

将指定的文件从编译列表中移除

‘src’:编译node列表

‘remove’:指定被移除的文件

AddCodeGroup()函数
AddCodeGroup(name, src, depend, **parameters)

将编译对象添加到一个代码组中进行编译管理,返回编译对象列表

‘name’:指定代码组名称,如果该名称已经存在,则将添加到已经存在的组中。在keil工程中以该名称呈现一个工作组

‘src’:指定要被添加的编译对象列表

‘depend’:关键字参数,指定创建该组依赖的宏,如果条件宏不满足,则直接返回空列表

‘parameters’:可变关键字参数,指定相应的编译行为,支持的关键字如下:

CPPPATH指定头文件路径,对应gcc的-I

CCFLAGS对应gcc的–include选项

CPPDEFINES定义编译宏,对应gcc的-D

LINKFLAGS定义链接选项

LOCAL_CPPPATH

定义仅对当前组有效的头文件路径选项

SCons的编译环境类

scons中的一个基本类是构造环境Environment,scons对编译源码的管理,以及对编译行为的控制都是通过配置构造环境(Env)来管理的。通过调用Environment()可以创建一个构造环境对象(Env)

配置Env对象中的各个变量,我们可以方便的定义需要的编译行为。简单举例:

通过CPPPATH变量可以添加gcc编译的-I选项

通过CPPDEFINES变量可以添加gcc的-D选项

通过CC变量可以指定C语言的交叉编译器

通过RESULT_SUFFIX变量可以指定生成目标文件的类型

通过env[‘ENV’][‘PATH’]变量可以指定交叉编译工具链的路径

python基础知识

os函数

  • os.system() #运行shell命令
  • os.name #返回当前使用平台的代表字符,Windows用’nt’表示,Linux用’posix’表示
  • os.sep #返回当前操作系统特定的路径分隔符,window和Linux通常不一样
  • os.path.split(path) #将path的目录和文件名分开为元组
  • os.path.join(path1,path2,…) #将path1,path2,…进行组合,若path2为绝对路径,则会将path1删除
  • os.path.dirname(path) #返回path中的目录(文件夹部分),结果不包含’’
  • os.path.basename(path) #返回path中的文件名

scons内置函数

每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的编译。SConstruct文件是SCons默认解析的第一个脚本,因此scons命令必须在它所在的路径下执行。一般建议将SConstruct脚本与rtconfig.py脚本置于用户工程目录中。下面以一个SConstruct为例进行简单分析:

SConstruct文件(SCons环境配置类脚本):

import os       //加载python库os,os是python自带的系统模块,需要import使用os 源于英文Operating System(操作系统)的缩写
import sys      //加载python库sys
import rtconfig //加载编译选项配置脚本rtconfig.py

if os.getenv('RTT_ROOT'):
    RTT_ROOT = os.getenv('RTT_ROOT')
else:
    RTT_ROOT = os.path.normpath(os.getcwd() + '/../../..') //cwd(Current Working Directory),含义为当前工作目录,os.getcwd() 指获取当前工作目录。定义从SConstruct所在目录到rtthread源码根目录的相对路径

sys.path = sys.path + [os.path.join(RTT_ROOT, 'tools')]
try:
    from building import *
except:
    print('Cannot found RT-Thread root directory, please check RTT_ROOT')
    print(RTT_ROOT)
    exit(-1)

TARGET = 'rt-thread.' + rtconfig.TARGET_EXT             //指定编译生成二进制文件的名字

//将rtconfig.py中配置的编译选项信息传递到scons内置编译对象env中
DefaultEnvironment(tools=[])                            
env = Environment(tools = ['mingw'],
    AS = rtconfig.AS, ASFLAGS = rtconfig.AFLAGS,
    CC = rtconfig.CC, CCFLAGS = rtconfig.CFLAGS,
    AR = rtconfig.AR, ARFLAGS = '-rc',
    CXX = rtconfig.CXX, CXXFLAGS = rtconfig.CXXFLAGS,
    LINK = rtconfig.LINK, LINKFLAGS = rtconfig.LFLAGS)      
env.PrependENVPath('PATH', rtconfig.EXEC_PATH)

if rtconfig.PLATFORM == 'iar':
    env.Replace(CCCOM = ['$CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -o $TARGET $SOURCES'])
    env.Replace(ARFLAGS = [''])
    env.Replace(LINKCOM = env["LINKCOM"] + ' --map rt-thread.map')

Export('RTT_ROOT')      //导出 RTT_ROOT 变量,供其他脚本使用
Export('rtconfig')      //导出 rtconfig 变量,供其他脚本使用

SDK_ROOT = os.path.abspath('./')

if os.path.exists(SDK_ROOT + '/libraries'):
    libraries_path_prefix = SDK_ROOT + '/libraries'
else:
    libraries_path_prefix = os.path.dirname(SDK_ROOT) + '/libraries'

SDK_LIB = libraries_path_prefix
Export('SDK_LIB')

# prepare building environment
objs = PrepareBuilding(env, RTT_ROOT, has_libcpu=False) //调用(某脚本文件).py中的工具函数,其中会对scons选项,以及相应的编译选项适配进行处理,通过sconscript()调用sconscript脚本文件

stm32_library = 'STM32F4xx_HAL'
rtconfig.BSP_LIBRARY_TYPE = stm32_library

# include libraries
objs.extend(SConscript(os.path.join(libraries_path_prefix, stm32_library, 'SConscript')))

# include drivers
objs.extend(SConscript(os.path.join(libraries_path_prefix, 'HAL_Drivers', 'SConscript')))

# make a building
DoBuilding(TARGET, objs)

由上示例可以看出,SConstruct与rtconfig.py与用户的具体应用环境和编译配置紧密相关

SConscript文件(SCons中间加载类脚本):
(目录:C:\Users\Admin\Desktop\data\rt-thread\bsp\stm32\stm32f407-atk-explorer)

# for module compiling
import os
Import('RTT_ROOT')
from building import *

cwd = GetCurrentDir()  //获取当前目录
objs = []
list = os.listdir(cwd) //获取当前目录下的子目录列表

//遍历整个列表,判断子目录下是否存在SConscript脚本,如果存在则通过SConscript()方法加载该脚本
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')

SConscript文件(SCons代码组织类脚本):
此SConscript脚本位于子目录
(目录:C:\Users\Admin\Desktop\data\rt-thread\bsp\stm32\stm32f407-atk-explorer\applications)

DefineGroup()中的名字参数必须指定,如果该名字的组已经存在,则会进行追加合并。键值参数不是必选的,可以缺省。

import rtconfig
from building import *

cwd     = GetCurrentDir()
CPPPATH = [cwd, str(Dir('#'))]     #添加搜寻头文件路径
src     = Split("""
main.c
""")                                #定义要添加的源文件

group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)  #创建代码管理组

Return('group') #将定义的组返回给上一级调用脚本

rtconfig.py文件:
rtconfig.py文件,主要用于指定编译器以及安装路径。除此之外,该文件中定义了大量的变量,这些变量包括编译选项,汇编选项,链接选项。

import os

# toolchains options
ARCH='arm'
CPU='cortex-m4'
CROSS_TOOL='gcc'

# bsp lib config
BSP_LIBRARY_TYPE = None

if os.getenv('RTT_CC'):
    CROSS_TOOL = os.getenv('RTT_CC')
if os.getenv('RTT_ROOT'):
    RTT_ROOT = os.getenv('RTT_ROOT')

# cross_tool provides the cross compiler
# EXEC_PATH is the compiler execute path, for example, CodeSourcery, Keil MDK, IAR
if  CROSS_TOOL == 'gcc':
    PLATFORM    = 'gcc'
    EXEC_PATH   = r'C:\Users\XXYYZZ'
elif CROSS_TOOL == 'keil':
    PLATFORM    = 'armcc'
    EXEC_PATH   = r'C:/Keil_v5'
elif CROSS_TOOL == 'iar':
    PLATFORM    = 'iar'
    EXEC_PATH   = r'C:/Program Files (x86)/IAR Systems/Embedded Workbench 8.0'

if os.getenv('RTT_EXEC_PATH'):
    EXEC_PATH = os.getenv('RTT_EXEC_PATH')

BUILD = 'debug'

if PLATFORM == 'gcc':
    # toolchains
    PREFIX = 'arm-none-eabi-'
    CC = PREFIX + 'gcc'
    AS = PREFIX + 'gcc'
    AR = PREFIX + 'ar'
    CXX = PREFIX + 'g++'
    LINK = PREFIX + 'gcc'
    TARGET_EXT = 'elf'
    SIZE = PREFIX + 'size'
    OBJDUMP = PREFIX + 'objdump'
    OBJCPY = PREFIX + 'objcopy'

    DEVICE = ' -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections'
    CFLAGS = DEVICE + ' -Dgcc'
    AFLAGS = ' -c' + DEVICE + ' -x assembler-with-cpp -Wa,-mimplicit-it=thumb '
    LFLAGS = DEVICE + ' -Wl,--gc-sections,-Map=rt-thread.map,-cref,-u,Reset_Handler -T board/linker_scripts/link.lds'

    CPATH = ''
    LPATH = ''

    if BUILD == 'debug':
        CFLAGS += ' -O0 -gdwarf-2 -g'
        AFLAGS += ' -gdwarf-2'
    else:
        CFLAGS += ' -O2'

    CXXFLAGS = CFLAGS 

    POST_ACTION = OBJCPY + ' -O binary $TARGET rtthread.bin\n' + SIZE + ' $TARGET \n'

elif PLATFORM == 'armcc':
    # toolchains
    CC = 'armcc'
    CXX = 'armcc'
    AS = 'armasm'
    AR = 'armar'
    LINK = 'armlink'
    TARGET_EXT = 'axf'

    DEVICE = ' --cpu Cortex-M4.fp '
    CFLAGS = '-c ' + DEVICE + ' --apcs=interwork --c99'
    AFLAGS = DEVICE + ' --apcs=interwork '
    LFLAGS = DEVICE + ' --scatter "board\linker_scripts\link.sct" --info sizes --info totals --info unused --info veneers --list rt-thread.map --strict'
    CFLAGS += ' -I' + EXEC_PATH + '/ARM/ARMCC/include'
    LFLAGS += ' --libpath=' + EXEC_PATH + '/ARM/ARMCC/lib'

    CFLAGS += ' -D__MICROLIB '
    AFLAGS += ' --pd "__MICROLIB SETA 1" '
    LFLAGS += ' --library_type=microlib '
    EXEC_PATH += '/ARM/ARMCC/bin/'

    if BUILD == 'debug':
        CFLAGS += ' -g -O0'
        AFLAGS += ' -g'
    else:
        CFLAGS += ' -O2'

    CXXFLAGS = CFLAGS 
    CFLAGS += ' -std=c99'

    POST_ACTION = 'fromelf --bin $TARGET --output rtthread.bin \nfromelf -z $TARGET'

elif PLATFORM == 'iar':
    # toolchains
    CC = 'iccarm'
    CXX = 'iccarm'
    AS = 'iasmarm'
    AR = 'iarchive'
    LINK = 'ilinkarm'
    TARGET_EXT = 'out'

    DEVICE = '-Dewarm'

    CFLAGS = DEVICE
    CFLAGS += ' --diag_suppress Pa050'
    CFLAGS += ' --no_cse'
    CFLAGS += ' --no_unroll'
    CFLAGS += ' --no_inline'
    CFLAGS += ' --no_code_motion'
    CFLAGS += ' --no_tbaa'
    CFLAGS += ' --no_clustering'
    CFLAGS += ' --no_scheduling'
    CFLAGS += ' --endian=little'
    CFLAGS += ' --cpu=Cortex-M4'
    CFLAGS += ' -e'
    CFLAGS += ' --fpu=VFPv4_sp'
    CFLAGS += ' --dlib_config "' + EXEC_PATH + '/arm/INC/c/DLib_Config_Normal.h"'
    CFLAGS += ' --silent'

    AFLAGS = DEVICE
    AFLAGS += ' -s+'
    AFLAGS += ' -w+'
    AFLAGS += ' -r'
    AFLAGS += ' --cpu Cortex-M4'
    AFLAGS += ' --fpu VFPv4_sp'
    AFLAGS += ' -S'

    if BUILD == 'debug':
        CFLAGS += ' --debug'
        CFLAGS += ' -On'
    else:
        CFLAGS += ' -Oh'

    LFLAGS = ' --config "board/linker_scripts/link.icf"'
    LFLAGS += ' --entry __iar_program_start'

    CXXFLAGS = CFLAGS
    
    EXEC_PATH = EXEC_PATH + '/arm/bin/'
    POST_ACTION = 'ielftool --bin $TARGET rtthread.bin'

def dist_handle(BSP_ROOT, dist_dir):
    import sys
    cwd_path = os.getcwd()
    sys.path.append(os.path.join(os.path.dirname(BSP_ROOT), 'tools'))
    from sdk_dist import dist_do_building
    dist_do_building(BSP_ROOT, dist_dir)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值