在實際項目中,往往需要將一些基礎庫或者算法庫發佈出去,但是不同項目可能需要用到不同的子模塊,此時爲了保持簡潔,可能需要合併多個靜態庫爲一個。
在筆者的實際工作中,合併靜態庫的需求還是有的,而且大多數時候都是基於CMake的項目,所以希望能夠基於不同配置,自動合併多個模塊的靜態庫爲一個,方便發佈版本和管理。本文介紹的就是如何在CMake工程中,優雅地完成多個靜態庫目標的合併。
本文仍以本系列的開源項目https://gitee.com/RealCoolEngineer/cmake-template爲例(當前commit id: 9015193
)。
一 合併靜態庫的方法
靜態庫其實就是一些源文件被編譯成對應機器代碼文件(.o
文件)的集合。
在Linux系統中,通過ar
命令可以對靜態庫進行各種操作,在MacOS
下可以使用libtool
工具。有以下幾種不同的合併靜態庫的方法。
1 方法1
先使用ar
把靜態庫拆解爲多個.o
文件:
ar x liba.a
ar x libb.a
再把所有的.o
文件打包爲一個靜態庫:
ar crs libmerge.a *.o
參數解釋:
-
x
:拆解靜態庫文件爲其包含的內容 -
c
:封裝.o
文件爲靜態庫文件 -
r
:覆蓋同名庫文件或者新創建目標庫文件 -
s
:相當於對結果執行一次ranlib
,爲靜態庫的內容添加索引,提高訪問效率
2 方法2
當然,還有更加簡潔的命令:
ar crsT libmerge.a liba.a libb.a
參數T
表示將後續所有靜態庫中的.o
文件打包到第一個參數指定的靜態庫文件中,如果不加該參數,得到的將會是後面幾個.a
文件的集合。可以使用命令ar -t
查看打包的內容,諸君手動一試便知。
MacOS下的
ar
命令和Linux的有所不同,此法不適用於MacOS。
3 方法3
使用MRI
腳本。
首先編寫一個MRI
腳本,比如merge.mri
:
create libmerge.a
addlib liba.a
addlib libb.a
save
end
然後使用命令:
ar -M < merge.mri
MacOS下面的的
ar
命令並沒有-M
參數,所以此法也不適用於MacOS系統。
4 方法4
此方法針對MacOS系統,在MacOS系統下可以使用libtool
命令:
libtool -static -o libmerge.a liba.a libb.a
在Linux下也有
libtool
工具,但是用法和MacOS也是不一致的,所以此法不適用於Linux。
二 基於CMake合併靜態庫
在示例項目cmake-template
中,源碼目錄下有兩個子目錄:src/math
和src/nn
,分別編譯得到兩個靜態庫目標libmath.a
和libnn.a
。
現在在項目根目錄下的CMakeLists.txt
中:通過add_custom_command
命令配合add_custom_target
命令,將libmath.a
和libnn.a
合併爲libmerge.a
;並將合併的靜態庫文件導入使用。
1 合併靜態庫
這裏使用了CMake的內置變量APPLE
,如果是MacOS
系統,APPLE
會被設置爲true
,以此來確定要使用libtool
還是ar
。
合併靜態庫實現代碼如下:
# Merge library
if (APPLE)
add_custom_command(OUTPUT libmerge.a
COMMAND libtool -static -o libmerge.a $<TARGET_FILE:math> $<TARGET_FILE:nn>
DEPENDS math nn)
else()
add_custom_command(OUTPUT libmerge.a
COMMAND ar crsT libmerge.a $<TARGET_FILE:math> $<TARGET_FILE:nn>
DEPENDS math nn)
endif()
add_custom_target(_merge ALL DEPENDS libmerge.a)
代碼解釋:
-
OUTPUT
:指定輸出文件名稱(會被標記爲GENERATED) -
COMMAND
:後面跟的就是要執行的命令,這裏就和在shell中執行命令差不多 -
DEPENDS
:表明依賴的目標 -
add_custom_target
指明的目標依賴於合併操作的輸出(libmerge.a
),而合併操作需要依賴目標math
和nn
,所以在這兩個目標文件生成以後,就會去執行合併操作
需要注意的是,合併靜態庫的時候需要知道每個靜態庫的路徑,在CMake中,目標靜態庫math
的路徑可以使用生成器表達式$<TARGET_FILE:math>
獲取。
但是還有另外一種情況,如果是使用find_library
查找到的靜態庫,比如:
find_library(LIB_C c HINTS ${SEARCH_PATH})
這時DEPENDS
中就不用加上c
,而${LIB_C}
就是該庫的路徑了。
2 導入合併的靜態庫並使用
現在把合併的靜態庫導入,並在鏈接demo
可執行程序時使用。
代碼實現如下:
add_library(merge STATIC IMPORTED GLOBAL)
set_target_properties(merge PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/libmerge.a
)
# Build demo executable
add_executable(demo src/c/main.c)
target_link_libraries(demo PRIVATE merge)
因爲libmerge.a
是命令add_custom_command
指定的輸出,所以它會標記爲是自動生成的文件(GENERATED)。
鏈接demo
的時候依賴導入的靜態庫merge
,而merge
的IMPORTED_LOCATION
指定的這個文件是自動生成的,所以CMake就知道需要等libmerge.a
生成之後才能開始鏈接demo
。
如果沒有add_custom_target
那一行,執行編譯構建會報錯,由此也可見一斑:
make[2]: *** No rule to make target `libmerge.a', needed by `demo'
以上便是CMake下合併靜態庫的實踐,歡迎交流指正。