2.3. SConstruct
Sconstruct是一种 Python 编写的改进的,跨平台的 gnu make 的替代工具,是一种更简便,更可靠,更高效的编译系统解决方案,一般简称 SCons
2.3.1. 特点
SCons 是一种更现代的自动化构建解决方案,相比传统的 gnu make, 其特点更明显:
使用 Python 脚本做为配置文件,良好的夸平台性
内建可靠的自动依赖分析
支持 C, C++, D, Java, Fortran, Yacc, Lex, Qt,SWIG 以及 Tex/Latex, 扩展性好,支持用户自扩展编程语言
支持 make -j 风格的并行构建,可以同时运行 N 个工作,而 不用担心代码的层次结构
使用 Autoconf 风格查找头文件,函数库,函数和类型定义
基于MD5识别构建文件的改变,更精准和安全
2.3.2. 环境安装
2.3.2.1. Windows
因为其小,兼容性好,快速等特点,因此对于命令行的开发和构建,我们更推荐 Windows 系统
Windows 下的对应的各种工具已经存放在 tools/env/tools 目录当中,不需要安装
2.3.2.2. Linux
固件构建需要依赖
python2 + SCons
python3 + pycryptodomex
推荐使用 apt-get 命令直接进行目标软件和依赖的安装
2.3.2.2.1. 安装SCons
sudo apt-get install scons
2.3.2.2.2. 安装pycryptodomex
pycryptodomex 是一个 python 编写的加密包,SDK 中有源码包可以进行编译安装
sudo apt install pip
cd tools/env/local_pkgs/
tar xvf pycryptodomex-3.11.0.tar.gz
cd pycryptodomex-3.11.0
sudo python3 setup.py install
2.3.3. 基础用法
SCons 使用 SConscript 和 SConstruct 文件来组织源码结构,通常来说一个项目只有一个 SConstruct,但是会有多个 SConscript。
原则上每个存放有源代码的子目录下都会放置一个 SConscript,但譬如 BSP 的驱动开发等会集合所有的驱动源码到一个 SConscript 中。
一些常用的 SConscript 方法有:
2.3.3.1. Program
Program 用于生成可执行文件
Program('hello.c') 编译hello.c可执行文件,根据系统自动生成(hello.exe on Windows; hello on POSIX)
Program('hello','hello.c') 指定Output文件名(hello.exe on Windows; hello on POSIX)
Program(['hello.c', 'file1.c', 'file2.c']) 编译多个文件,Output文件名以第一个文件命名
Program(source = "hello.c",target = "hello")
Program(target = "hello" , source = "hello.c")
Program('hello', Split('hello.c file1.c file2.c')) 编译多个文件
Program(Glob("*.c"))
src = ["hello.c","foo.c"];
Program(src)
2.3.3.2. Object
Object 用于生成目标文件
Object('hello.c') 编译hello.c目标文件,根据系统自动生成(hello.obj on Windows; hello.o on POSIX)
2.3.3.3. Library
Library 用于生成静态/动态库文件
Library('foo', ['f1.c', 'f2.c', 'f3.c']) 编译library
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c']) 编译 shared library
StaticLibrary('bar', ['f4.c', 'f5.c', 'f6.c']) 编译 static library
库的使用:
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.') 连接库,不需加后缀或是前缀
2.3.3.4. Depends
Depends 用于明确依赖关系
Depends(hello, 'other_file') //hello 依赖于other_file
2.3.4. SConstruct
ZX-RTT 的 SConstruct 在 SDK 根目录下,主要用来配置编译逻辑和一些全局环境变量的设置,用户可以添加自己的私有的 ENV
import os
import sys
# ZX-RTT root directory
AIC_ROOT = os.path.normpath(os.getcwd())
# zx-rtt custom scripts
aic_script_path = os.path.join(AIC_ROOT, 'tools/scripts/')
sys.path.append(aic_script_path)
from aic_build import *
chk_prj_config(AIC_ROOT)
PRJ_CHIP,PRJ_BOARD,PRJ_KERNEL,PRJ_APP,PRJ_DEFCONFIG_NAME,PRJ_CUSTOM_LDS,MKIMAGE_POST_ACTION = get_prj_config(AIC_ROOT)
PRJ_NAME = PRJ_DEFCONFIG_NAME.replace('_defconfig','')
PRJ_OUT_DIR = 'output/' + PRJ_NAME + '/images/'
AIC_SCRIPT_DIR = aic_script_path
AIC_COMMON_DIR = os.path.join(AIC_ROOT, 'bsp/zx/sys/' + PRJ_CHIP)
AIC_PACK_DIR = os.path.join(AIC_ROOT, 'target/' + PRJ_CHIP + '/' + PRJ_BOARD + '/pack/')
# Var tranfer to SConscript
Export('AIC_ROOT')
Export('AIC_SCRIPT_DIR')
Export('AIC_COMMON_DIR')
Export('AIC_PACK_DIR')
Export('PRJ_CHIP')
Export('PRJ_BOARD')
Export('PRJ_KERNEL')
Export('PRJ_APP')
Export('PRJ_NAME')
Export('PRJ_DEFCONFIG_NAME')
Export('PRJ_OUT_DIR')
# Var tranfer to Kconfig 'option env=xxx'
os.environ["AIC_ROOT"] = AIC_ROOT
os.environ["AIC_SCRIPT_DIR"] = AIC_SCRIPT_DIR
os.environ["AIC_COMMON_DIR"] = AIC_COMMON_DIR
os.environ["AIC_PACK_DIR"] = AIC_PACK_DIR
os.environ["PRJ_CHIP"] = PRJ_CHIP
os.environ["PRJ_BOARD"] = PRJ_BOARD
os.environ["PRJ_KERNEL"] = PRJ_KERNEL
os.environ["PRJ_APP"] = PRJ_APP
os.environ["PRJ_NAME"] = PRJ_NAME
os.environ["PRJ_DEFCONFIG_NAME"] = PRJ_DEFCONFIG_NAME
os.environ["PRJ_OUT_DIR"] = PRJ_OUT_DIR
# rtconfig
chip_path = os.path.join(AIC_ROOT, 'bsp/zx/sys/' + PRJ_CHIP)
sys.path.append(chip_path)
import rtconfig
# RTT_ROOT
if os.getenv('RTT_ROOT'):
RTT_ROOT = os.getenv('RTT_ROOT')
else:
RTT_ROOT = os.path.join(AIC_ROOT, 'kernel/rt-thread/')
os.environ["RTT_ROOT"] = RTT_ROOT
sys.path.append(os.path.join(RTT_ROOT, 'tools'))
from building import *
# ENV_ROOT
if os.getenv('ENV_ROOT') is None:
ENV_ROOT = RTT_ROOT + '/../../tools/env'
os.environ["ENV_ROOT"] = ENV_ROOT
# TARGET
TARGET = PRJ_OUT_DIR + rtconfig.SOC + '.' + rtconfig.TARGET_EXT
rtconfig.LFLAGS += ' -T ' + ld
# add post action
rtconfig.POST_ACTION += MKIMAGE_POST_ACTION
# create env
env = Environment(tools = ['mingw'],
AS = rtconfig.AS, ASFLAGS = rtconfig.AFLAGS,
CC = rtconfig.CC, CFLAGS = rtconfig.CFLAGS,
CXX = rtconfig.CXX, CXXFLAGS = rtconfig.CXXFLAGS,
AR = rtconfig.AR, ARFLAGS = '-rc',
LINK = rtconfig.LINK, LINKFLAGS = rtconfig.LFLAGS)
env.PrependENVPath('PATH', rtconfig.EXEC_PATH)
# add --start-group and --end-group for GNU GCC
env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS -Wl,--start-group $_LIBFLAGS -Wl,--end-group'
env['ASCOM'] = env['ASPPCOM']
# signature database
env.SConsignFile(PRJ_OUT_DIR + ".sconsign.dblite")
Export('RTT_ROOT')
Export('rtconfig')
# Var tranfer to building.py
env['AIC_ROOT'] = AIC_ROOT
env['AIC_SCRIPT_DIR'] = AIC_SCRIPT_DIR
env['AIC_COMMON_DIR'] = AIC_COMMON_DIR
env['AIC_PACK_DIR'] = AIC_PACK_DIR
env['PRJ_CHIP'] = PRJ_CHIP
env['PRJ_BOARD'] = PRJ_BOARD
env['PRJ_KERNEL'] = PRJ_KERNEL
env['PRJ_NAME'] = PRJ_NAME
env['PRJ_APP'] = PRJ_APP
env['PRJ_DEFCONFIG_NAME'] = PRJ_DEFCONFIG_NAME
env['PRJ_OUT_DIR'] = PRJ_OUT_DIR
# prepare building environment
objs = PrepareBuilding(env, RTT_ROOT, has_libcpu=False)
# make a building
DoBuilding(TARGET, objs)
2.3.5. SConscript
SConscript 是 SCons 构建系统的配置文件
2.3.5.1. 驱动
一个典型的驱动程序的 SConscript 示例如下:
Import('AIC_ROOT')
Import('PRJ_KERNEL')
from building import *
cwd = GetCurrentDir()
src = Glob('*.c')
CPPPATH = []
if GetDepend('DRIVER_DRV_EN'):
CPPPATH.append(cwd + '/include/drv')
if GetDepend('DRIVER_HAL_EN'):
CPPPATH.append(cwd + '/include/hal')
CPPPATH.append(cwd + '/include/uapi')
# UART driver
if GetDepend('AIC_UART_DRV'):
if GetDepend('DRIVER_DRV_EN'):
src += Glob('drv/uart/*.c')
if GetDepend('DRIVER_HAL_EN'):
src += Glob('hal/uart/*.c')
LOCAL_CCFLAGS += ' -O0'
//DefineGroup(name, src, depend,**parameters)
group = DefineGroup('aic_osal', src, depend=[''], CPPPATH=CPPPATH, LOCAL_CCFLAGS=LOCAL_CCFLAGS)
Return('group')
2.3.5.2. 应用
一个典型的应用程序的 SConscript 示例如下:
Import('AIC_ROOT')
Import('PRJ_KERNEL')
from building import *
cwd = GetCurrentDir()
path = [cwd + '/include']
path += [cwd + '/base/include']
path += [cwd + '/ge/include']
path += [cwd + '/ve/include']
path += [cwd + '../../../bsp/zx/include/uapi']
path += [cwd + '/mpp_test']
if GetDepend(['AIC_MPP_PLAYER_INTERFACE']):
#audio decoder
path += [cwd + '/middle_media/audio_decoder/include']
path += [cwd + '/middle_media/audio_decoder/decoder']
#base
path += [cwd + '/middle_media/base/include']
path += [cwd + '/middle_media/base/parser/mov']
path += [cwd + '/middle_media/base/parser/rawdata']
path += [cwd + '/middle_media/base/stream/file']
src = []
CPPDEFINES = []
# mpp
if GetDepend(['LPKG_MPP']):
src += Glob('./base/memory/*.c')
src += Glob('./ge/*.c')
src += Glob('./fb/*.c')
src += Glob('ve/decoder/*.c')
src += Glob('ve/common/*.c')
src += Glob('ve/decoder/jpeg/*.c')
src += Glob('ve/decoder/png/*.c')
src += Glob('ve/decoder/h264/*.c')
src += Glob('./mpp_test/*.c')
if GetDepend(['AIC_MPP_PLAYER_INTERFACE']):
#audio decoder
src += Glob('middle_media/audio_decoder/decoder/*.c')
src += Glob('middle_media/audio_decoder/decoder/mp3/mp3_decoder.c')
//DefineGroup(name, src, depend,**parameters)
group = DefineGroup('mpp', src, depend = [''], CPPPATH = path, CPPDEFINES = CPPDEFINES)
Return('group')