CMake應用:合併靜態庫的最佳實踐

在實際項目中,往往需要將一些基礎庫或者算法庫發佈出去,但是不同項目可能需要用到不同的子模塊,此時爲了保持簡潔,可能需要合併多個靜態庫爲一個。

在筆者的實際工作中,合併靜態庫的需求還是有的,而且大多數時候都是基於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

參數解釋:

  1. x:拆解靜態庫文件爲其包含的內容
  2. c:封裝.o文件爲靜態庫文件
  3. r:覆蓋同名庫文件或者新創建目標庫文件
  4. 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/mathsrc/nn,分別編譯得到兩個靜態庫目標libmath.alibnn.a

現在在項目根目錄下的CMakeLists.txt中:通過add_custom_command命令配合add_custom_target命令,將libmath.alibnn.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)

代碼解釋:

  1. OUTPUT:指定輸出文件名稱(會被標記爲GENERATED
  2. COMMAND:後面跟的就是要執行的命令,這裏就和在shell中執行命令差不多
  3. DEPENDS:表明依賴的目標
  4. add_custom_target指明的目標依賴於合併操作的輸出(libmerge.a),而合併操作需要依賴目標mathnn,所以在這兩個目標文件生成以後,就會去執行合併操作

需要注意的是,合併靜態庫的時候需要知道每個靜態庫的路徑,在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,而mergeIMPORTED_LOCATION指定的這個文件是自動生成的,所以CMake就知道需要等libmerge.a生成之後才能開始鏈接demo

如果沒有add_custom_target那一行,執行編譯構建會報錯,由此也可見一斑:

make[2]: *** No rule to make target `libmerge.a', needed by `demo'

以上便是CMake下合併靜態庫的實踐,歡迎交流指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章