#九、復雜的例子
[TOC]
模塊的使用和自定義模塊你現在還會覺得cmake 簡單嗎?
本章我們將著重介紹系統預定義的Find模塊的使用以及自己編寫Find 模塊,系統中提供了其他各種模塊,一般情況需要使用INCLUDE指令顯式的調用,FIND_PACKAGE指令是一個特例,可以直接調用預定義的模塊。
其實使用純粹依靠cmake 本身提供的基本指令來管理工程是一件非常復雜的事情,所以,cmake 設計成了可擴展的架構,可以通過編寫一些通用的模塊來擴展cmake.
在本章,我們準備首先介紹一下cmake 提供的FindCURL 模塊的使用。然后,基于我們前面的libhello 共享庫,編寫一個FindHello.cmake 模塊。
##一,使用FindCURL 模塊
在/backup/cmake 目錄建立t5目錄,用于存放我們的CURL 的例子。
建立src 目錄,并建立src/main.c,內容如下:
```
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
int written = fwrite(ptr, size, nmemb, (FILE *)fp);
return written;
}
int main()
{
const char * path = “/tmp/curl-test”;
const char * mode = “w”;
fp = fopen(path,mode);
curl_global_init(CURL_GLOBAL_ALL);
CURLcode res;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
```
這段代碼的作用是通過curl 取回www.linux-ren.org 的首頁并寫入/tmp/curl-test 文件中。
建立主工程文件CMakeLists.txt
```
PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)
```
建立src/CMakeLists.txt
```
ADD_EXECUTABLE(curltest main.c)
```
現在自然是沒辦法編譯的,我們需要添加curl 的頭文件路徑和庫文件。
方法1:直接通過`INCLUDE_DIRECTORIES 和TARGET_LINK_LIBRARIES` 指令添加:
我們可以直接在src/CMakeLists.txt 中添加:`INCLUDE_DIRECTORIES(/usr/include)TARGET_LINK_LIBRARIES(curltest curl)`,然后建立build 目錄進行外部構建即可。
現在我們要探討的是使用cmake 提供的FindCURL 模塊。
方法2,使用FindCURL 模塊。
向src/CMakeLists.txt 中添加:
```
FIND_PACKAGE(CURL)
IF(CURL_FOUND)INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
MESSAGE(FATAL_ERROR ”CURL library not found”) ENDIF(CURL_FOUND)
```
對于系統預定義的Find<name>.cmake 模塊,使用方法一般如上例所示:每一個模塊都會定義以下幾個變量
* <name>_FOUND
* <name>_INCLUDE_DIR or <name>_INCLUDES
* <name>_LIBRARY or <name>_LIBRARIES
你可以通過<name>_FOUND 來判斷模塊是否被找到,如果沒有找到,按照工程的需要關閉某些特性、給出提醒或者中止編譯,上面的例子就是報出致命錯誤并終止構建。
如果<name>_FOUND 為真,則將<name>_INCLUDE_DIR 加入INCLUDE_DIRECTORIES,將<name>_LIBRARY 加入TARGET_LINK_LIBRARIES 中。
我們再來看一個復雜的例子,通過<name>_FOUND 來控制工程特性:
```
SET(mySources viewer.c)SET(optionalSources)
SET(optionalLibs)FIND_PACKAGE(JPEG)IF(JPEG_FOUND)
SET(optionalSources ${optionalSources} jpegview.c)
INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)
IF(PNG_FOUND)
SET(optionalSources ${optionalSources} pngview.c)
INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)
ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )
TARGET_LINK_LIBRARIES(viewer ${optionalLibs}
```
通過判斷系統是否提供了JPEG 庫來決定程序是否支持JPEG 功能。
##二,編寫屬于自己的FindHello
模塊。我們在此前的t3實例中,演示了構建動態庫、靜態庫的過程并進行了安裝。接下來,我們在t6示例中演示如何自定義FindHELLO模塊并使用這個模塊構建工程:請在建立/backup/cmake/ 中建立t6目錄,并在其中建立cmake 目錄用于存放我們自己
定義的FindHELLO.cmake 模塊,同時建立src 目錄,用于存放我們的源文件。
1,定義cmake/FindHELLO.cmake模塊
```
FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello/usr/local/include/hello)
FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib/usr/local/lib)
IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
IF (HELLO_FOUND)
IF (NOT HELLO_FIND_QUIETLY)
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
IF (HELLO_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find hello library")
ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)
```
針對上面的模塊讓我們再來回顧一下FIND_PACKAGE 指令:`FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]`
`[[REQUIRED|COMPONENTS] [componets...]])` 前面的CURL 例子中我們使用了最簡單的FIND_PACKAGE 指令,其實他可以使用多種參數,QUIET 參數,對應與我們編寫的FindHELLO 中的HELLO_FIND_QUIETLY ,如果不指定
這個參數,就會執行:
```
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
```
REQUIRED參數,其含義是指這個共享庫是否是工程必須的,如果使用了這個參數,說明這
個鏈接庫是必備庫,如果找不到這個鏈接庫,則工程不能編譯。對應于FindHELLO.cmake 模塊中的HELLO_FIND_REQUIRED 變量。同樣,我們在上面的模塊中定義了`HELLO_FOUND`,
`HELLO_INCLUDE_DIR`,`HELLO_LIBRARY` 變量供開發者在FIND_PACKAGE 指令中使用。OK,下面建立src/main.c,內容為:
```
#include <hello.h>
int main(){
HelloFunc();
return 0;}
```
建立src/CMakeLists.txt 文件,內容如下:
```
FIND_PACKAGE(HELLO)IF(HELLO_FOUND)
ADD_EXECUTABLE(hello main.c)
INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
ENDIF(HELLO_FOUND)
```
為了能夠讓工程找到FindHELLO.cmake模塊(存放在工程中的cmake目錄)我們在主工程文件CMakeLists.txt 中加入:
```
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
```
##三,使用自定義的FindHELLO
模塊構建工程仍然采用外部編譯的方式,建立build 目錄,進入目錄運行:cmake ..
我們可以從輸出中看到:
>Found Hello: /usr/lib/libhello.so
如果我們把上面的`FIND_PACKAGE(HELLO)` 修改為`FIND_PACKAGE(HELLO QUIET)`,則不會看到上面的輸出。接下來就可以使用make 命令構建工程,運行:./src/hello 可以得到輸出Hello World 。說明工程成功構建。
##四,如果沒有找到hello library 呢?
我們可以嘗試將/usr/lib/libhello.x 移動到/tmp 目錄,這樣,按照FindHELLO 模塊的定義,就找不到hello library 了,我們再來看一下構建結果:cmake ..
仍然可以成功進行構建,但是這時候是沒有辦法編譯的。
修改FIND_PACKAGE(HELLO) 為FIND_PACKAGE(HELLO REQUIRED) ,將hellolibrary 定義為工程必須的共享庫。這時候再次運行cmake ..我們得到如下輸出:
>CMake Error: Could not find hello library
因為找不到libhello.x,所以,整個Makefile 生成過程被出錯中止。
##五、小結
在本節中,我們學習了如何使用系統提供的Find<NAME> 模塊并學習了自己編寫Find<NAME> 模塊以及如何在工程中使用這些模塊。
后面的章節,我們會逐漸學習更多的cmake 模塊使用方法以及用cmake 來管理GTK 和QT4 工程。