# scons_template **Repository Path**: tools_group/scons_template ## Basic Information - **Project Name**: scons_template - **Description**: 使用scons的编译模板 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-01-02 - **Last Updated**: 2025-12-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # scons概述及相关资料 scons实现的功能同makefile或cmake,是一种基于python的软件构建工具。makefile有自己独特的语法规则,编写别的软件程序时也不会使用,不用就会忘记,时间一久就看不懂了。而scons使用python编写,python本身的可读性就比makefile好,再加些注释,长时间后再打开也还能看懂,这是我认为scons最大的优势。 [本工程的码云链接](https://gitee.com/bibang-wang/scons_template) ## 相关资料 以下是我收集到的scons资料,如果你看不懂本文,可以先学习以下文章。 如果你对scons中的概念和API不甚了解,可以学习此篇文章[Scons使用教程_sconstruct-CSDN博客](https://blog.csdn.net/MOU_IT/article/details/95229790?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-95229790-blog-106690660.235%5Ev38%5Epc_relevant_anti_vip_base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-95229790-blog-106690660.235%5Ev38%5Epc_relevant_anti_vip_base&utm_relevant_index=2) 我在网上找到的另一个模板,本模板在此基础上改进。[Scons环境搭建和编译原理概述及嵌入式开发常用模板_scons构建c开发环境教程](https://blog.csdn.net/wenbo13579/article/details/126881034) RT-Thread将scons作为构建工具,可以学习其脚本来完善自己的脚本。但RT-Thread的脚本真的太复杂了,这是我找到的RT-Thread的scons脚本学习资料。[RT-Thread 文档中心](https://www.rt-thread.org/document/site/#/development-tools/build-config-system/SCons)[SCons - Python 给 C++ 做的烤饼,香!-技术专区-AR眼镜圈](http://arclub.cc/?news/345.html) 第一手资料还要看官方文档了([scons官方文档](https://scons.org/documentation.html)),看User Guide能快速入门。如果你不想看英文,这个专栏是对部分内容的翻译[scons_jeek_we的博客-CSDN博客](https://blog.csdn.net/jeek_we/category_12034807.html) # 模板 ## 工程目录 ```shell . ├── binary │   ├── demo.elf │   └── gdbserver ├── build │   └── source │   ├── app │   │   └── main.o │   └── middle │   ├── m1.o │   └── m2.o ├── library │   ├── easyLogger │   ├── libeasyLogger.so │   ├── libsqlite3.so │   └── sqlite3 │   ├── SConscript │   ├── sqlite3.c │   └── sqlite3.h ├── SConstruct └── source ├── app │   ├── main.c │   ├── main.h │   └── SConscript ├── driver │   ├── easyLogger │   │   ├── elog_cfg.h │   │   ├── elog_file_cfg.h │   │   ├── elog_file.h │   │   └── elog.h │   └── sqlite3 │   └── sqlite3.h └── middle ├── m1.c ├── m1.h ├── m2.c ├── m2.h └── SConscript ``` binary:工程运行所需要的所有可执行文件。demo.elf为本工程编译获得,gdbserver为调试本工程使用。 build:编译demo.elf时产生的中间文件,build下中间文件的结构同source library:工程所用动态库及库源码。若动态库没有提供构建工具,则使用用户提供的SConscript构建。若动态库提供构建工具,则编译完后,将动态库拷贝至library目录下。所有动态库只能放在library下,不能放在其子目录下。 source:工程源码。本模板将源文件和头文件都放在source目录下,也可以分开存放,后续介绍方法。source下的子目录又给工程源码分级,用户根据实际需求分级即可。 SConscript:scons构建编译的入口,一个工程,只能有一个SConscript文件,可以有多个SConscript文件 ### main.c内容 内容很简单,print_build_time()打印编译的时间,打印sqlite3的版本。show_m1(),show_m2()的定义在m1.c和m2.c中。 ```c #include #include #include #include //sleep() #include "sqlite3.h" #include "m1.h" #include "m2.h" void print_build_time() { int year = 0, month = 0, day = 0; int hour = 0, minute = 0, seconds = 0; char m[4] = {0}; char *abbreviation_month[] = {"Jan","Feb","Mar","Apr" \ ,"May","Jun","July","Aug" \ ,"Sept","Oct","Nov","Dec"}; sscanf(__DATE__, "%3s %2d %4d", m, &day, &year); // 将月份缩写转换为数字 for (month = 0; month < 12; month++) { if (strcmp(m, abbreviation_month[month]) == 0) { month++; break; } } sscanf(__TIME__, "%2d:%2d:%2d", &hour, &minute, &seconds); char compile_time[32]; snprintf(compile_time,sizeof(compile_time)-1,"%04u-%02u-%02u %02u:%02u:%02u", year, month, day, hour, minute,seconds); printf("build time:%s \n",compile_time); } int main(int argc, char* argv[]) { print_build_time(); printf("sqlite3 version:%s \n",sqlite3_version); show_m1(); show_m2(); return 0; } ``` ### m1.c的内容 ```c #include void show_m1(void) { printf("my name is m1 \n"); } ``` ### m2.c的内容 ```c #include void show_m2(void) { printf("my name is m2 \n"); } ``` ## 模板的详细介绍 如果你想学习使用本模板,直接看下一章节。 ### 工程目录下的SConstruct ```python import os import time from SCons.Environment import Environment from SCons.Subst import Literal from SCons.Script import SConscript,AddOption,GetOption from SCons.Script.SConscript import SConsEnvironment,Return # AddOption的功能同optparse库中的add_option,可以使用户自定义命令行选项。当用户在终端输入 # scons --buildlib=sqlite3时,变量buildlib的值为sqlite3. # ----------配置自定义命令行选项---------- AddOption('--buildlib', dest = 'buildlib', type = 'string', help = 'Enter the name of the library that you want to compile') # ----------配置编译参数---------- TARGET_NAME = 'binary/demo' #主程序名 SOURCE_DIR = 'source' #源文件所在目录.搜索SConscript时用 INCLUDE_DIR = 'source' #头文件所在目录.搜索头文件时用 LIBRARY_DIR = 'library' #库文件所在目录.库文件只能在此目录,不能在子目录 VERSION_FILE = 'source/app/main.c' #保存软件版本,编译时间的文件。如果不需要此类信息,则可不指定VERSION_FILE # VERSION_FILE = '' # 选择目标平台 # PLATFORM = 'arm_linux' # PLATFORM = 'arm_none' PLATFORM = 'x86-64' # !!!这里根据实际情况设置 # 根据目标平台,选择编译器及所在路径 if PLATFORM == 'arm_linux': COMPILER_PATH = '/home/用户名/mytools/arm-cortex_a8-linux-gnueabi-4.7.3/bin' COMPILER_PREFIX = 'arm-cortex_a8-linux-gnueabi-' elif PLATFORM == 'arm_none': COMPILER_PATH = '/usr/bin' COMPILER_PREFIX = 'arm-none-eabi-' elif PLATFORM == 'x86-64': COMPILER_PATH = '/usr/bin' COMPILER_PREFIX = '' else: print("error:please Select the build platform ") SConsEnvironment.Exit(-1) # ----------配置scons编译环境---------- env = Environment() # print(env.Dump()) # scons编译时的环境变量(执行环境)和系统环境变量隔离。此处将编译器路径加入执行环境,否则编译时找不到编译器 env.AppendENVPath('PATH',COMPILER_PATH) # 修改文件的编译依赖为:当文件的访问时间更新后,将编译源文件 env.Decider('timestamp-newer') if len(VERSION_FILE) > 0: # VERSION_FILE中保存编译时间。每次工程更改,无论VERSION_FILE有没有修改,都需要重新编译VERSION_FILE以更新编译时间。 current_time = time.time() os.utime(VERSION_FILE, (current_time, current_time)) # 修改文件的访问时间和修改时间为当前时间 env['CC'] = COMPILER_PREFIX + 'gcc' env['AS'] = COMPILER_PREFIX + 'gcc' env['AR'] = COMPILER_PREFIX + 'ar' env['CXX'] = COMPILER_PREFIX + 'g++' env['LINK'] = COMPILER_PREFIX + 'gcc'#链接时,gcc会自动调用ld,并传递相关参数 env['OBJSUFFIX'] = '.o'#指定目标文件后缀,也可以不指定,scons将根据平台自动选择 env['PROGSUFFIX'] = '.elf'#指定可执行文件后缀 # 编译库的工作由库中的SConscript具体执行。不要在此之前在env中添加编译选项,否则会传递到库中SConscript buildlib_option = GetOption('buildlib') if buildlib_option: libraries = [] if buildlib_option == 'all':# 编译全部的动态库 for file in os.listdir(LIBRARY_DIR): dir = os.path.join(LIBRARY_DIR,file) if os.path.isdir(dir): libraries.append(dir) else:# 编译指定的动态库 dir = os.path.join(LIBRARY_DIR,buildlib_option) if os.path.isdir(dir): libraries.append(dir) if not libraries: print('没有在{}目录内搜索到需要编译的动态库'.format(LIBRARY_DIR)) else: print('需要编译的动态库为:') for line in libraries: print(line) for library in libraries: script = os.path.join(library,'SConscript') if os.path.isfile(script): #执行script路径下的SConscript,传递变量env到SConscript中 SConscript(script,exports = 'env') else: print('Error:{} 不存在.'.format(script)) #退出SConstruct,编译库就不编译工程主程序了 Return() # ----------编译主程序---------- # C编译选项 C_FLAGS = [] C_FLAGS.append('-O0') #若想调试,代码不能优化 C_FLAGS.append('-g') C_FLAGS.append('-std=c99') env['CFLAGS'] = C_FLAGS # C和C++都可以使用的编译选项 CC_FLAGS = [] CC_FLAGS.append('-Wall') env['CCFLAGS'] = CC_FLAGS # C++的编译选项 CXX_FLAGS = [] env['CXXFLAGS'] = CXX_FLAGS # 前往指定目录下寻找SConscript、头文件路径、动态库 SCRIPTS = [] INCLUDE_PATH = [] LIBRARIES = [] for root,dirs,files in os.walk(SOURCE_DIR): for file in files: if file == 'SConscript': script_path = os.path.join(root,file) SCRIPTS.append(script_path) else: print('{}目录下的SConscript文件为:'.format(SOURCE_DIR)) for line in SCRIPTS: print(line) for root,dirs,files in os.walk(INCLUDE_DIR): for file in files: if file.endswith('.h'): if root not in INCLUDE_PATH: INCLUDE_PATH.append(root) else: print('编译时的搜索的头文件路径为:') for line in INCLUDE_PATH: print(line) shared_library_prefix = env['LIBPREFIX'] #动态库的前缀 shared_library_suffix = env['SHLIBSUFFIX'] #动态库的后缀 for file in os.listdir(LIBRARY_DIR): if file.endswith(shared_library_suffix) and file[0:3] == shared_library_prefix: path,ext = os.path.splitext(file)# 去除动态库前缀和后缀 LIBRARIES.append(path[3:]) else: print('编译时链接的工程内的动态库为:') for line in LIBRARIES: print(line) # -I,编译时头文件的搜索路径 env['CPPPATH'] = INCLUDE_PATH # -l,编译时需要链接的动态库。只添加LIBRARY_DIR下没有的库 LIBRARIES.append('pthread') env['LIBS'] = LIBRARIES # -L,编译时动态库的搜索路径 env['LIBPATH'] = LIBRARY_DIR # 假设LIBRARY_DIR = 'library'.则最后拼出来的效果为 '${ORIGIN}/../library' rpath = "'$${ORIGIN}/../" + LIBRARY_DIR + "'" # 程序运行时动态库的搜索路径 # Literal()使字符串按原样传递,而不展开变量。效果,-Wl,-rpath='${ORIGIN}/../library' env['RPATH'] = Literal(rpath) SOURCES = [] for script in SCRIPTS: build_dir = os.path.join('build',os.path.dirname(script)) # script:执行script路径下的SConscript # exports = 'env':传递变量env到SConscript中 # variant_dir = build_dir,指定中间文件生成在build_dir目录内 # duplicate = False:不复制源文件到build_dir目录内 SOURCES = SOURCES + SConscript(script,exports = 'env',variant_dir = build_dir,duplicate = False) # 生成中间文件 objs = env.Object(SOURCES) # 生成可执行文件。 # target = TARGET_NAME:可执行文件名为TARGET_NAME # source=[objs]:所依赖的源文件为objs target = env.Program(target = TARGET_NAME, source=[objs]) # 使用size工具,查看可执行文件中.text .data .bss段所占字节 env.Command(TARGET_NAME + '.size', target, COMPILER_PREFIX + 'size' + ' --format=berkeley $SOURCE') # 可以通过objcopy和objdump生成.bin和.lst文件. # ----------清理---------- # 遍历库目录中的SConscript,会将库目录中的中间文件也删除 if GetOption('clean'): for root,dirs,files in os.walk(LIBRARY_DIR): for file in files: if file == 'SConscript': script_path = os.path.join(root,file) SConscript(script_path,exports = 'env') print('-'*30) ``` ### source目录内的SConscript ```python import os Import('env') # 编译全部源文件。搜索全部后缀为.c的文件 SOURCE_ALL = Glob('*.c') # 指定要编译的源文件 SOURCE_SPLIT = [] SOURCE_SPLIT.append(File('m1.c')) SOURCE_SPLIT.append(File('m2.c')) # 返回SOURCE_SPLIT或SOURCE_ALL Return('SOURCE_ALL') ``` ### library目录内的SConscript ```python # for module compiling import os Import('env') LIBRARY_NAME = '../sqlite3' #动态库的名称 # 在当前目录下,搜索头文件所在路径 INCLUDE_PATH = [] for root,dirs,files in os.walk('.'): for file in files: if file.endswith('.h'): if root not in INCLUDE_PATH: INCLUDE_PATH.append(root) else: print('编译sqlite3时头文件的搜索路径为:{}'.format(INCLUDE_PATH)) env['CPPPATH'] = INCLUDE_PATH # 本动态库所依赖的动态库 LIBRARIES = [] LIBRARIES.append('pthread') LIBRARIES.append('dl') env['LIBS'] = LIBRARIES # 这里假设源文件都在当前目录下,如果源文件在子目录内,仿照SConscript内搜索源文件的方法 SOURCES = Glob('*.c') lib = env.SharedLibrary(target = LIBRARY_NAME, source=[SOURCES])# 生成动态库 # scons -c时不要将生成的库文件删除 env.NoClean(lib) ``` ## 模板使用方法 ### 编译主程序 命令行中输入 `scons` ### 编译动态库 命令行中输入 `scons --buildlib=all` 将编译全部动态库。 命令行中输入 `scons --buildlib=sqlite3` 将只编译sqlite3动态库。指定库的名称必须与library下库源码所在目录的名称相同。 ### 指定每次都要编译的文件 修改VERSION_FILE所指定的文件 `VERSION_FILE = 'source/app/main.c'` 只要工程主程序重新编译,无论main.c有没有修改内容,main.c也将编译。原理是修改main.c的访问时间,并将编译依赖设置为env.Decider('timestamp-newer') ### 手动指定待编译的源文件 [参考"source目录内的SConscript"中内容](#yUTO6) ### 清理中间文件 命令行中输入 `scons -c` 主程序和动态库的中间文件都将删除