Cmake之深入理解find_package()的用法

使用find_package引入外部依賴包

       本章節通過示例演示Cmake中find_package的用法。

1.通過Cmake內置模塊引入依賴包

       爲了方便我們在項目中引入外部依賴包,cmake官方爲我們預定義了許多尋找依賴包的Module,他們存儲在path_to_your_cmake/share/cmake-<version>/Modules目錄下。每個以Find<LibaryName>.cmake命名的文件都可以幫我們找到一個包。我們也可以在官方文檔中查看到哪些庫官方已經爲我們定義好了,我們可以直接使用find_package函數進行引用官方文檔:Find Modules

我們以curl庫爲例,假設我們項目需要引入這個庫,從網站中請求網頁到本地,我們看到官方已經定義好了FindCURL.cmake。所以我們在CMakeLists.txt中可以直接用find_pakcage進行引用。

find_package(CURL)
add_executable(curltest curltest.cc)
if(CURL_FOUND)
    target_include_directories(clib PRIVATE ${CURL_INCLUDE_DIR})
    target_link_libraries(curltest ${CURL_LIBRARY})
else(CURL_FOUND)
    message(FATAL_ERROR ”CURL library not found”)
endif(CURL_FOUND)

對於系統預定義的 Find<LibaryName>.cmake 模塊,使用方法一般如上例所示。

每一個模塊都會定義以下幾個變量 -

<LibaryName>_FOUND

<LibaryName>_INCLUDE_DIR or <LibaryName>_INCLUDES <LibaryName>_LIBRARY or <LibaryName>_LIBRARIES

你可以通過<LibaryName>_FOUND 來判斷模塊是否被找到,如果沒有找到,按照工程的需要關閉 某些特性、給出提醒或者中止編譯,上面的例子就是報出致命錯誤並終止構建。 如果<LibaryName>_FOUND 爲真,則將<LibaryName>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES.

2.通過find_package引入非官方的庫(該方式只對支持cmake編譯安裝的庫有效)

     假設此時我們需要引入glog庫來進行日誌的記錄,我們在Module目錄下並沒有找到 FindGlog.cmake。所以我們需要自行安裝glog庫,再進行引用。

安裝

# clone該項目
git clone https://github.com/google/glog.git 
# 切換到需要的版本 
cd glog
git checkout v0.40  

# 根據官網的指南進行安裝
cmake -H. -Bbuild -G "Unix Makefiles"
cmake --build build
cmake --build build --target install

此時我們便可以通過與引入curl庫一樣的方式引入glog庫了

find_package(GLOG)
add_executable(glogtest glogtest.cc)
if(GLOG_FOUND)
    # 由於glog在連接時將頭文件直接鏈接到了庫裏面,所以這裏不用顯示調用target_include_directories
    target_link_libraries(glogtest glog::glog)
else(GLOG_FOUND)
    message(FATAL_ERROR ”GLOG library not found”)
endif(GLOG_FOUND)

Module模式與Config模式

       通過上文我們瞭解了通過Cmake引入依賴庫的基本用法。知其然也要知其所以然,find_package對我們來說是一個黑盒子,那麼它是具體通過什麼方式來查找到我們依賴的庫文件的路徑的呢。到這裏我們就不得不聊到find_package的兩種模式,一種是Module模式,也就是我們引入curl庫的方式。另一種叫做Config模式,也就是引入glog庫的模式。下面我們來詳細介紹着兩種方式的運行機制。

在Module模式中,cmake需要找到一個叫做Find<LibraryName>.cmake的文件。這個文件負責找到庫所在的路徑,爲我們的項目引入頭文件路徑和庫文件路徑。cmake搜索這個文件的路徑有兩個,一個是上文提到的cmake安裝目錄下的share/cmake-<version>/Modules目錄,另一個使我們指定的CMAKE_MODULE_PATH的所在目錄。

如果Module模式搜索失敗,沒有找到對應的Find<LibraryName>.cmake文件,則轉入Config模式進行搜索。它主要通過<LibraryName>Config.cmake or <lower-case-package-name>-config.cmake這兩個文件來引入我們需要的庫。以我們剛剛安裝的glog庫爲例,在我們安裝之後,它在/usr/local/lib/cmake/glog/目錄下生成了glog-config.cmake文件,而/usr/local/lib/cmake/<LibraryName>/正是find_package函數的搜索路徑之一。(find_package的搜索路徑是一系列的集合,而且在linux,windows,mac上都會有所區別,需要的可以參考官方文檔find_package

由以上的例子可以看到,對於原生支持Cmake編譯和安裝的庫通常會安裝Config模式的配置文件到對應目錄,這個配置文件直接配置了頭文件庫文件的路徑以及各種cmake變量供find_package使用。而對於非由cmake編譯的項目,我們通常會編寫一個Find<LibraryName>.cmake,通過腳本來獲取頭文件、庫文件等信息。通常,原生支持cmake的項目庫安裝時會拷貝一份XXXConfig.cmake到系統目錄中,因此在沒有顯式指定搜索路徑時也可以順利找到。

編寫自己的Find<LibraryName>.cmake模塊

假設我們編寫了一個新的函數庫,我們希望別的項目可以通過find_package對它進行引用我們應該怎麼辦呢。

我們在當前目錄下新建一個ModuleMode的文件夾,在裏面我們編寫一個計算兩個整數之和的一個簡單的函數庫。庫函數以手工編寫Makefile的方式進行安裝,庫文件安裝在/usr/lib目錄下,頭文件放在/usr/include目錄下。其中的Makefile文件如下:

# 1、準備工作,編譯方式、目標文件名、依賴庫路徑的定義。
CC = g++
CFLAGS  := -Wall -O3 -std=c++11 

OBJS = libadd.o #.o文件與.cpp文件同名
LIB = libadd.so # 目標文件名
INCLUDE = ./ # 頭文件目錄
HEADER = libadd.h # 頭文件

all : $(LIB)

# 2. 生成.o文件 
$(OBJS) : libadd.cc
    $(CC) $(CFLAGS) -I ./ -fpic -c $< -o $@

# 3. 生成動態庫文件
$(LIB) : $(OBJS)
    rm -f $@
    g++ $(OBJS) -shared -o $@ 
    rm -f $(OBJS)


# 4. 刪除中間過程生成的文件 
clean:
    rm -f $(OBJS) $(TARGET) $(LIB)

# 5.安裝文件
install:
    cp $(LIB) /usr/lib
    cp $(HEADER) /usr/include

編譯安裝

make
sudo make install

接下來我們回到我們的Cmake項目中來,在cmake文件夾下新建一個FindAdd.cmake的文件。我們的目標是找到庫的頭文件所在目錄和共享庫文件的所在位置。

# 在指定目錄下尋找頭文件和動態庫文件的位置,可以指定多個目標路徑
find_path(ADD_INCLUDE_DIR libadd.h /usr/include/ /usr/local/include ${CMAKE_SOURCE_DIR}/ModuleMode)
find_library(ADD_LIBRARY NAMES add PATHS /usr/lib/add /usr/local/lib/add ${CMAKE_SOURCE_DIR}/ModuleMode)

if (ADD_INCLUDE_DIR AND ADD_LIBRARY)
    set(ADD_FOUND TRUE)
endif (ADD_INCLUDE_DIR AND ADD_LIBRARY)

這時我們便可以像引用curl一樣引入我們自定義的庫了。

在CMakeLists.txt中添加

# 將項目目錄下的cmake文件夾加入到CMAKE_MODULE_PATH中,讓find_pakcage能夠找到我們自定義的函數庫
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
add_executable(addtest addtest.cc)
find_package(ADD)
if(ADD_FOUND)
    target_include_directories(addtest PRIVATE ${ADD_INCLUDE_DIR})
    target_link_libraries(addtest ${ADD_LIBRARY})
else(ADD_FOUND)
    message(FATAL_ERROR "ADD library not found")
endif(ADD_FOUND)

 

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