SCons 构建工具
SCons 簡介
SCons 是一套由 Python 語言編寫的開源構建系統,類似于 GNU Make。它采用不同于通常 Makefile 文件的方式,而是使用 SConstruct 和 SConscript 文件來替代。這些文件也是 Python 腳本,能夠使用標準的 Python 語法來編寫。所以在 SConstruct、SConscript 文件中可以調用 Python 標準庫進行各類復雜的處理,而不局限于 Makefile 設定的規則。
在?SCons?的網站上可以找到詳細的 SCons 用戶手冊,本章節講述 SCons 的基本用法,以及如何在 RT-Thread 中用好 SCons 工具。
什么是構建工具
構建工具 (software construction tool) 是一種軟件,它可以根據一定的規則或指令,將源代碼編譯成可執行的二進制程序。這是構建工具最基本也是最重要的功能。實際上構建工具的功能不止于此,通常這些規則有一定的語法,并組織成文件。這些文件用來控制構建工具的行為,在完成軟件構建之外,也可以做其他事情。
目前最流行的構建工具是 GNU Make。很多知名開源軟件,如 Linux 內核就采用 Make 構建。Make 通過讀取 Makefile 文件來檢測文件的組織結構和依賴關系,并完成 Makefile 中所指定的命令。
由于歷史原因,Makefile 的語法比較混亂,不利于初學者學習。此外在 Windows 平臺上使用 Make 也不方便,需要安裝 Cygwin 環境。為了克服 Make 的種種缺點,人們開發了其他構建工具,如 CMake 和 SCons 等。
RT-Thread 構建工具
RT-Thread 早期使用 Make/Makefile 構建。從 0.3.x 開始,RT-Thread 開發團隊逐漸引入了 SCons 構建系統,引入 SCons 唯一的目是:使大家從復雜的 Makefile 配置、IDE 配置中脫離出來,把精力集中在 RT-Thread 功能開發上。
有些讀者可能會有些疑惑,這里介紹的構建工具與 IDE 有什么不同呢?IDE 通過圖形化界面的操作來完成構建。大部分 IDE 會根據用戶所添加的源碼生成類似 Makefile 或 SConscript 的腳本文件,在底層調用類似 Make 或 SCons 的工具來構建源碼。
安裝 SCons
在使用 SCons 系統前需要在 PC 主機中安裝它,因為它是 Python 語言編寫的,所以在使用 SCons 之前需要安裝 Python 運行環境。
RT-Thread 提供的 Env 配置工具帶有 SCons 和 Python,因此在 windows 平臺使用 SCons 則不需要安裝這兩個軟件。
在 Linux、BSD 環境中 Python 應該已經默認安裝了,一般也是 2.x 版本系列的 Python 環境。這時只需要安裝 SCons 即可,例如在 Ubuntu 中可以使用如下命令安裝 SCons:
sudo apt-get install scons
SCons 基本功能
RT-Thread 構建系統支持多種編譯器。目前支持的編譯器包括 ARM GCC、MDK、IAR、VisualStudio、Visual DSP。主流的 ARM Cortex M0、M3、M4 平臺,基本上 ARM GCC、MDK、IAR 都是支持的。有一些 BSP 可能僅支持一種,讀者可以閱讀該 BSP 目錄下的 rtconfig.py 里的 CROSS_TOOL 選項查看當前支持的編譯器。
如果是 ARM 平臺的芯片,則可以使用 Env 工具,輸入 scons 命令直接編譯 BSP,這時候默認使用的是 ARM GCC 編譯器,因為 Env 工具帶有 ARM GCC 編譯器。 如下圖所示使用?scons?命令編譯 stm32f10x-HAL BSP,后文講解 SCons 也將基于這個 BSP。
如果用戶要使用其他的 BSP 已經支持的編譯器編譯工程,或者 BSP 為非 ARM 平臺的芯片,那么不能直接使用 scons 命令編譯工程,需要自己安裝對應的編譯器,并且指定使用的編譯器路徑。在編譯工程前,可以在 Env 命令行界面使用下面的 2 個命令指定編譯器為 MDK 和編譯器路徑為 MDK 的安裝路徑。
set RTT_CC=keil set RTT_EXEC_PATH=C:/Keilv5SCons 基本命令
本節介紹 RT-Thread 中常用的 SCons 命令。SCons 不僅完成基本的編譯,還可以生成 MDK/IAR/VS 工程。
scons
在 Env 命令行窗口進入要編譯的 BSP 工程目錄,然后使用此命令可以直接編譯工程。如果執行過?scons?命令后修改了一些源文件,再次執行 scons 命令時,則 SCons 會進行增量編譯,僅編譯修改過的源文件并鏈接。
如果在 Windows 上執行?scons?輸出以下的警告信息:
scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly.說明 scons 并沒在你的機器上找到 Visual Studio 編譯器,但實際上我們主要是針對設備開發,和 Windows 本地沒什么關系,請直接忽略掉它。
scons?命令后面還可以增加一個 - s 參數,即命令?scons -s,和 scons 命令不同的是此命令不會打印具體的內部命令。
scons -c
清除編譯目標。這個命令會清除執行 scons 時生成的臨時文件和目標文件。
scons --target=XXX
如果使用 mdk/iar 來進行項目開發,當修改了 rtconfig.h 打開或者關閉某些組件時,需要使用以下命令中的其中一種重新生成對應的定制化的工程,然后在 mdk/iar 進行編譯下載。
scons --target=iar scons --target=mdk4 scons --target=mdk5在命令行窗口進入要編譯的 BSP 工程目錄,使用?scons --target=mdk5?命令后會在 BSP 目錄生成一個新的 MDK 工程文件名為 project.uvprojx。雙擊它打開,就可以使用 MDK 來編譯、調試。使用?scons --target=iar?命令后則會生成一個新的 IAR 工程文件名為 project.eww。不習慣 SCons 的用戶可以使用這種方式。如果打開 project.uvproj 失敗,請刪除 project.uvopt 后,重新生成工程。
在 bsp/simulator 下,可以使用下面的命令生成 vs2012 的工程或 vs2005 的工程。
scons --target=vs2012 Scons --target=vs2005如果 BSP 目錄下提供其他 IDE 工程的模板文件也可以使用此命令生成對應的新工程,比如 ua、vs、cb、cdk。
這個命令后面同樣可以增加一個 -s 參數,如命令?scons –target=mdk5 -s,執行此命令時不會打印具體的內部命令。
注意事項
要生成 MDK 或者 IAR 的工程文件,前提條件是 BSP 目錄存在一個工程模版文件,然后 scons 才會根據這份模版文件加入相關的源碼,頭文件搜索路徑,編譯參數,鏈接參數等。而至于這個工程是針對哪顆芯片的,則直接由這份工程模版文件指定。所以大多數情況下,這個模版文件是一份空的工程文件,用于輔助 SCons 生成 project.uvprojx 或者 project.eww。
scons -jN
多線程編譯目標,在多核計算機上可以使用此命令加快編譯速度。一般來說一顆 cpu 核心可以支持 2 個線程。雙核機器上使用?scons -j4?命令即可。
注意事項
如果你只是想看看編譯錯誤或警告,最好是不使用 - j 參數,這樣錯誤信息不會因為多個文件并行編譯而導致出錯信息夾雜在一起。
scons --dist
搭建項目框架,使用此命令會在 BSP 目錄下生成 dist 目錄,這便是開發項目的目錄結構,包含了RT-Thread源碼及BSP相關工程,不相關的BSP文件夾及libcpu都會被移除,并且可以隨意拷貝此工程到任何目錄下使用。
scons --verbose
默認情況下,使用 scons 命令編譯的輸出不會顯示編譯參數,如下所示:
D:\repository\rt-thread\bsp\stm32f10x>scons scons: Reading SConscript files ... scons: done reading SConscript files. scons: Building targets ... scons: building associated VariantDir targets: build CC build\applications\application.o CC build\applications\startup.o CC build\components\drivers\serial\serial.o ...使用 scons –verbose 命令的效果如下:
armcc -o build\src\mempool.o -c --device DARMSTM --apcs=interwork -ID:/Keil/ARM/ RV31/INC -g -O0 -DUSE_STDPERIPH_DRIVER -DSTM32F10X_HD -Iapplications -IF:\Projec t\git\rt-thread\applications -I. -IF:\Project\git\rt-thread -Idrivers -IF:\Proje ct\git\rt-thread\drivers -ILibraries\STM32F10x_StdPeriph_Driver\inc -IF:\Project \git\rt-thread\Libraries\STM32F10x_StdPeriph_Driver\inc -ILibraries\STM32_USB-FS -Device_Driver\inc -IF:\Project\git\rt-thread\Libraries\STM32_USB-FS-Device_Driv er\inc -ILibraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x -IF:\Project\git\rt-thre ...SCons 進階
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 大部分源碼文件夾下也存在 SConscript 文件,這些文件會被 BSP 目錄下的 SConscript 文件 “找到” 從而將 rtconfig.h 中定義的宏對應的源代碼加入到編譯器中來。后文將以 stm32f10x-HAL BSP 為例,講解 SCons 是如何構建工程。
SCons 內置函數
如果想要將自己的一些源代碼加入到 SCons 編譯環境中,一般可以創建或修改已有 SConscript 文件。SConscript 文件可以控制源碼文件的加入,并且可以指定文件的 Group(與 MDK/IAR 等 IDE 中的 Group 的概念類似)。
SCons 提供了很多內置函數可以幫助我們快速添加源碼程序,利用這些函數,再配合一些簡單的 Python 語句我們就能隨心所欲向項目中添加或者刪除源碼。下面將簡單介紹一些常用函數。
GetCurrentDir()
獲取當前路徑。
Glob('*.c')
獲取當前目錄下的所有 C 文件。修改參數的值為其他后綴就可以匹配當前目錄下的所有某類型的文件。
GetDepend(macro)
該函數定義在 tools 目錄下的腳本文件中,它會從 rtconfig.h 文件讀取配置信息,其參數為 rtconfig.h 中的宏名。如果 rtconfig.h 打開了某個宏,則這個方法(函數)返回真,否則返回假。
Split(str)
將字符串 str 分割成一個列表 list。
DefineGroup(name, src, depend,**parameters)
這是 RT-Thread 基于 SCons 擴展的一個方法(函數)。DefineGroup 用于定義一個組件。組件可以是一個目錄(下的文件或子目錄),也是后續一些 IDE 工程文件中的一個 Group 或文件夾。
DefineGroup()?函數的參數描述:
| name | Group 的名字 |
| src | Group 中包含的文件,一般指的是 C/C++ 源文件。方便起見,也能夠通過 Glob 函數采用通配符的方式列出 SConscript 文件所在目錄中匹配的文件 |
| depend | Group 編譯時所依賴的選項(例如 FinSH 組件依賴于 RT_USING_FINSH 宏定義)。編譯選項一般指 rtconfig.h 中定義的 RT_USING_xxx 宏。當在 rtconfig.h 配置文件中定義了相應宏時,那么這個 Group 才會被加入到編譯環境中進行編譯。如果依賴的宏并沒在 rtconfig.h 中被定義,那么這個 Group 將不會被加入編譯。相類似的,在使用 scons 生成為 IDE 工程文件時,如果依賴的宏未被定義,相應的 Group 也不會在工程文件中出現 |
| parameters | 配置其他參數,可取值見下表,實際使用時不需要配置所有參數 |
parameters 可加入的參數:
| CCFLAGS | C 源文件編譯參數 |
| CPPPATH | 頭文件路徑 |
| CPPDEFINES | 鏈接時參數 |
| LIBRARY | 包含此參數,則會將組件生成的目標文件打包成庫文件 |
SConscript(dirs,variant_dir,duplicate)
讀取新的 SConscript 文件,SConscript() 函數的參數描述如下所示:
| dirs | SConscript 文件路徑 |
| variant_dir | 指定生成的目標文件的存放路徑 |
| duiplicate | 設定是否拷貝或鏈接源文件到 variant_dir |
SConscript 示例
下面我們將以幾個 SConscript 為例講解 scons 構建工具的使用方法。
SConscript 示例 1
我們先從 stm32f10x-HAL BSP 目錄下的 SConcript 文件開始講解,這個文件管理 BSP 下面的所有其他 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 工程所需要的源代碼就被加入了編譯列表中。
SConscript 示例 2
那么 stm32f10x-HAL BSP 其他的 SConcript 文件又是怎樣的呢?我們再看一下 drivers 目錄下 SConcript 文件,這個文件將管理 drivers 目錄下面的源代碼。drivers 目錄用于存放根據 RT-Thread 提供的驅動框架實現的底層驅動代碼。
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 目錄下的頭文件了。
SConscript 示例 3
我們再看一下 applications 目錄下的 SConcript 文件,這個文件將管理 applications 目錄下面的源代碼,用于存放用戶自己的應用代碼。
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 目錄里面的頭文件在源代碼的其他地方就可以引用了。
總結:這個源程序會將當前目錄下的所有 c 程序加入到組 Applications 中,因此如果在這個目錄下增加或者刪除文件,就可以將文件加入工程或者從工程中刪除。它適用于批量添加源碼文件。
SConscript 示例 4
下面是 RT-Thread 源代碼 component/finsh/SConscript 文件的內容,這個文件將管理 finsh 目錄下面的源代碼。
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] 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_srcgroup = DefineGroup('finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH, LINKFLAGS = LINKFLAGS)Return('group')我們來看一下文件中第一個 Python 條件判斷語句的內容,如果編譯工具是 keil,則變量?LINKFLAGS = '--keep *.o(FSymTab)'?否則置空。
DefinGroup 同樣將 finsh 目錄下的 src 指定的文件創建為 finsh 組。depend = ['RT_USING_FINSH']?表示這個組依賴 rtconfig.h 中的宏 RT_USING_FINSH。當 rtconfig.h 中打開宏 RT_USING_FINSH 時,finsh 組內的源碼才會被實際編譯,否則 SCons 不會編譯。
然后將 finsh 目錄加入到系統頭文件目錄中,這樣我們就可以在其他源碼中引用 finsh 目錄下的頭文件。
LINKFLAGS = LINKFLAGS?的含義與?CPPPATH = CPPPATH?類似。左邊的 LINKFLAGS 表示鏈接參數,右邊的 LINKFLAGS 則是前面 if else 語句所定義的值。也就是給工程指定鏈接參數。
使用 SCons 管理工程
前面小節對 RT-Thread 源代碼的相關 SConscript 做了詳細講解,大家也應該知道了 SConscript 文件的一些常見寫法,本小節將指導大家如何使用 SCons 管理自己的工程。
添加應用代碼
前文提到過 BSP 下的 Applications 文件夾用于存放用戶自己的應用代碼,目前只有一個 main.c 文件。如果用戶的應用代碼不是很多,建議相關源文件都放在這個文件夾下面。在 Applications 文件夾下新增了 2 個簡單的文件 hello.c 和 hello.h,內容如下所示。
/* file: hello.h */#ifndef _HELLO_H_ #define _HELLO_H_int hello_world(void);#endif /* _HELLO_H_ *//* file: hello.c */ #include <stdio.h> #include <finsh.h> #include <rtthread.h>int hello_world(void) {rt_kprintf("Hello, world!\n");return 0; }MSH_CMD_EXPORT(hello_world, Hello world!)applications 目錄下的 SConcript 文件會把當前目錄下的所有源文件都添加到工程中。需要使用?scons --target=xxx?命令才會把新增的 2 個文件添加到工程項目中。注意每次新增文件都要重新生成工程。
添加模塊
前文提到在自己源代碼文件不多的情況下,建議所有源代碼文件都放在 applications 文件夾里面。如果用戶源代碼很多了,并且想創建自己的工程模塊,或者需要使用自己獲取的其他模塊,怎么做會比較合適呢?
同樣以上文提到的 hello.c 和 hello.h 為例,這兩個文件將會放到一個單獨的文件夾里管理,并且在 MDK 工程文件里有自己的分組,且可以通過 menuconfig 選擇是否使用這個模塊。在 BSP 下新增 hello 文件夾。
大家注意到文件夾里多了一個 SConscript 文件,如果想要將自己的一些源代碼加入到 SCons 編譯環境中,一般可以創建或修改已有的 SConscript 文件。參考上文對 RT-Thread 源代碼的一些對 SConscript 文件的分析,這個新增的 hello 模塊 SConscript 文件內容如下所示:
from building import *cwd = GetCurrentDir() include_path = [cwd] src = []if GetDepend(['RT_USING_HELLO']):src += ['hello.c']group = DefineGroup('hello', src, depend = [''], CPPPATH = include_path)Return('group')通過上面幾行簡單的代碼,就創建了一個新組 hello,并且可以通過宏定義控制要加入到組里面的源文件,還將這個組所在的目錄添加到了系統頭文件路徑中。那么自定義宏 RT_USING_HELLO 又是通過怎樣的方式定義呢?這里要介紹一個新的文件 Kconfig。Kconfig 用來配置內核,使用 Env 配置系統時使用的 menuconfig 命令生成的配置界面就依賴 Kconfig 文件。menuconfig 命令通過讀取工程的各個 Kconfig 文件,生成配置界面供用戶配置內核,最后所有配置相關的宏定義都會自動保存到 BSP 目錄里的 rtconfig.h 文件中,每一個 BSP 都有一個 rtconfig.h 文件,也就是這個 BSP 的配置信息。
在 stm32f10x-HAL BSP 目錄下已經有了關于這個 BSP 的 Kconfig 文件,我們可以基于這個文件添加自己需要的配置選項。關于 hello 模塊添加了如下配置選項,# 號后面為注釋。
使用 Env 工具進入 stm32f10x-HAL BSP 目錄后,使用 menuconfig 命令在主頁面最下面就可以看到新增的 hello 模塊的配置菜單,進入菜單后如下圖所示。
還可以修改 hello value 的值。
保存配置后退出配置界面,打開 stm32f10x-HAL BSP 目錄下的 rtconfig.h 文件可以看到 hello 模塊的配置信息已經有了。
注意:每次 menuconfig 配置完成后都要使用 scons --target=XXX 命令生成新工程。
因為 rtconfig.h 中已經定義了 RT_USING_HELLO 宏,所以新生成工程時就會把 hello.c 的源文件添加到新工程中。
上面只是簡單列舉了在 Kconfig 文件中添加自己模塊的配置選項,用戶還可以參考《Env 用戶手冊》,里面也有對配置選項修改和添加的講解,也可以自己百度查看 Kconfig 的相關文檔,實現其他更復雜的配置選項。
添加庫
如果要往工程中添加一個額外的庫,需要注意不同的工具鏈對二進制庫的命名。
- ARMCC 工具鏈下的庫名稱應該是 xxx.lib,一個以 .lib 為后綴的文件。
- IAR 工具鏈下的庫名稱應該是 xxx.a,一個以 .a 為后綴的文件。
- GCC 工具鏈下的庫名稱應該是 libxxx.a,一個以 .a 為后綴的文件,并且有 lib 前綴。
ARMCC / IAR 工具鏈下,若添加庫名為 libabc.lib / libabc_iar.a 時,在指定庫時指定全名 libabc。
GCC 工具鏈比較特殊,它識別的是 libxxx.a 這樣的庫名稱,若添加庫名為 libabc.a 時,在指定庫時是指定 abc,而不是 libabc。
例如,/libs?下有以下庫文件需要添加:
libabc_keil.lib libabc_iar.a libabc_gcc.a則對應的 SConscript 如下:
Import('rtconfig') from building import *cwd = GetCurrentDir() src = Split(''' ''')LIBPATH = [cwd + '/libs'] # LIBPATH 指定庫的路徑,表示庫的搜索路徑是當前目錄下的'libs'目錄if rtconfig.CROSS_TOOL == 'gcc':LIBS = ['abc_gcc'] # GCC 下 LIBS 指定庫的名稱 elif rtconfig.CROSS_TOOL == 'keil':LIBS = ['libabc_keil'] # ARMCC 下 LIBS 指定庫的名稱 else:LIBS = ['libabc_iar'] # IAR 下 LIBS 指定庫的名稱group = DefineGroup('ABC', src, depend = [''], LIBS = LIBS, LIBPATH=LIBPATH)Return('group')編譯器選項
rtconfig.py 是一個 RT-Thread 標準的編譯器配置文件,控制了大部分編譯選項,是一個使用 python 語言編寫的腳本文件,主要用于完成以下工作:
-
指定編譯器(從支持的多個編譯器中選擇一個你現在使用的編譯器)。
-
指定編譯器參數,如編譯選項、鏈接選項等。
當我們使用 scons 命令編譯工程時,就會按照 rtconfig.py 的編譯器配置選項編譯工程。下面的代碼為 stm32f10x-HAL BSP 目錄下 rtconfig.py 的部分代碼。
import os# toolchains options ARCH='arm' CPU='cortex-m3' CROSS_TOOL='gcc'if os.getenv('RTT_CC'):CROSS_TOOL = os.getenv('RTT_CC')# cross_tool provides the cross compiler # EXEC_PATH is the compiler execute path, for example, CodeSourcery, Keil MDK, IARif CROSS_TOOL == 'gcc':PLATFORM = 'gcc'EXEC_PATH = '/usr/local/gcc-arm-none-eabi-5_4-2016q3/bin/' elif CROSS_TOOL == 'keil':PLATFORM = 'armcc'EXEC_PATH = 'C:/Keilv5' elif CROSS_TOOL == 'iar':PLATFORM = 'iar'EXEC_PATH = 'C:/Program Files/IAR Systems/Embedded Workbench 6.0 Evaluation'if os.getenv('RTT_EXEC_PATH'):EXEC_PATH = os.getenv('RTT_EXEC_PATH')BUILD = 'debug'if PLATFORM == 'gcc':# toolchainsPREFIX = 'arm-none-eabi-'CC = PREFIX + 'gcc'AS = PREFIX + 'gcc'AR = PREFIX + 'ar'LINK = PREFIX + 'gcc'TARGET_EXT = 'elf'SIZE = PREFIX + 'size'OBJDUMP = PREFIX + 'objdump'OBJCPY = PREFIX + 'objcopy'DEVICE = '-mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections'CFLAGS = DEVICEAFLAGS = '-c' + DEVICE + '-x assembler-with-cpp'LFLAGS = DEVICE + '-Wl,--gc-sections,-Map=rtthread-stm32.map,-cref,-u,Reset_Handler -T stm32_rom.ld'其中 CFLAGS 是 C 文件的編譯選項,AFLAGS 則是匯編文件的編譯選項,LFLAGS 是鏈接選項。BUILD 變量控制代碼優化的級別。默認 BUILD 變量取值為'debug',即使用 debug 方式編譯,優化級別 0。如果將這個變量修改為其他值,就會使用優化級別 2 編譯。下面幾種都是可行的寫法(總之只要不是'debug'就可以了)。
BUILD = '' BUILD = 'release' BUILD = 'hello, world'建議在開發階段都使用 debug 方式編譯,不開優化,等產品穩定之后再考慮優化。
關于這些選項的具體含義需要參考編譯器手冊,如上面使用的 armcc 是 MDK 的底層編譯器。其編譯選項的含義在 MDK help 中有詳細說明。
前文提到過如果用戶執行 scons 命令時希望使用其他編譯器編譯工程,可以在 Env 的命令行端使用相關命令指定編譯器和編譯器路徑。但是這樣修改只對當前的 Env 進程有效,再次打開時又需要重新使用命令設置,我們可以直接修改 rtconfig.py 文件達到永久配置編譯器的目的。一般來說,我們只需要修改 CROSS_TOOL 和下面的 EXEC_PATH 兩個選項。
-
CROSS_TOOL:指定編譯器。可選的值有 keil、gcc、iar,瀏覽 rtconfig.py 可以查看當前 BSP 所支持的編譯器。如果您的機器上安裝了 MDK,那么可以將 CROSS_TOOL 修改為 keil,則使用 MDK 編譯工程。
-
EXEC_PATH:編譯器的安裝路徑。這里有兩點需要注意:
安裝編譯器時(如 MDK、GNU GCC、IAR 等),不要安裝到帶有中文或者空格的路徑中。否則,某些解析路徑時會出現錯誤。有些程序默認會安裝到?C:\Program Files?目錄下,中間帶有空格。建議安裝時選擇其他路徑,養成良好的開發習慣。
修改 EXEC_PATH 時,需要注意路徑的格式。在 windows 平臺上,默認的路徑分割符號是反斜杠?“\”,而這個符號在 C 語言以及 Python 中都是用于轉義字符的。所以修改路徑時,可以將“\”?改為?“/”,或者在前面加 r(python 特有的語法,表示原始數據)。
假如某編譯器安裝位置為?D:\Dir1\Dir2?下。下面幾種是正確的寫法:
-
EXEC_PATH =?r'D:\Dir1\Dir2'?注意,字符串前帶有 r,則可正常使用?“\”。
-
EXEC_PATH =?'D:/Dir1/Dir2'?注意,改用?“/”,前面沒有 r。
-
EXEC_PATH =?'D:\\Dir1\\Dir2'?注意,這里使用?“\”?的轉義性來轉義?“\”?自己。
-
這是錯誤的寫法:EXEC_PATH =?'D:\Dir1\Dir2'。
如果 rtconfig.py 文件有以下代碼,在配置自己的編譯器時請將下列代碼注釋掉。
if os.getenv('RTT_CC'):CROSS_TOOL = os.getenv('RTT_CC') ... ... if os.getenv('RTT_EXEC_PATH'):EXEC_PATH = os.getenv('RTT_EXEC_PATH')上面 2 個 if 判斷會設置 CROSS_TOOL 和 EXEC_PATH 為 Env 的默認值。
編譯器配置完成之后,我們就可以使用 SCons 來編譯 RT-Thread 的 BSP 了。在 BSP 目錄打開命令行窗口,執行?scons?命令就會啟動編譯過程。
RT-Thread 輔助編譯腳本
在 RT-Thread 源代碼的 tools 目錄下存放有 RT-Thread 自己定義的一些輔助編譯的腳本,例如用于自動生成 RT-Thread 針對一些 IDE 集成開發環境的工程文件。其中最主要的是 building.py 腳本。
SCons 更多使用
對于復雜、大型的系統,顯然不僅僅是一個目錄下的幾個文件就可以搞定的,很可能是由數個文件夾一級一級組合而成。
在 SCons 中,可以編寫 SConscript 腳本文件來編譯這些相對獨立目錄中的文件,同時也可以使用 SCons 中的 Export 和 Import 函數在 SConstruct 與 SConscript 文件之間共享數據(也就是 Python 中的一個對象數據)。更多 SCons 的使用方法請參考?SCons 官方文檔。
總結
以上是生活随笔為你收集整理的SCons 构建工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: navacate连接不上mysql_解决
- 下一篇: 休闲阅读——《只是为了好玩:Linux之