本章我们将着重介绍系统预定义的 find 模块的使用以及自己编写 find 模块,系统中提供了其他各种模块,一般情况需要使用 include 指令显式的调用,find_package 指令是一个特例,可以直接调用预定义的模块。
其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以,cmake 设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake.
在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前面的 libhello 共享库,编写一个 FindHello.cmake 模块。
使用 FindCURL 模块
在 /home/xiao/cmake_practice 目录建立 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.google.com");
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.google.com 的首页并写入/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 目录进行外部构建即可。
方法 2: 使用 FindCURL 模块。
向 src/CMakeLists.txt 中添加:
find_package(CURL)
if(CURL_FOUND)
include_directories(${CURL_INCLUDE_DIR})
target_link_libraries(curltest ${CURL_LIBRARY})
else()
message(FATAL_ERROR ”CURL library not found”)
endif()
对于系统预定义的 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)
find_package(PNG)
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 或 PNG 库 来决定程序是否支持 JPEG 或 PNG 功能。
编写属于自己的 FindHello 模块。
我们在此前的 t3 实例中,演示了构建动态库、静态库的过程并进行了安装。
接下来,我们在 t6 示例中演示如何自定义 FindHELLO 模块并使用这个模块构建工程:
在/home/xiao/cmake_practice/中建立 t6 目录,并在其中建立 cmake 目录用于存放我们自己定义的 FindHELLO.cmake 模块,同时建立 src 目录,用于存放我们的源文件。
定义 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 ()
if (HELLO_FOUND)
if (NOT HELLO_FIND_QUIETLY)
message(STATUS "Found Hello: ${HELLO_LIBRARY}")
endif ()
ELSE ()
if (HELLO_FIND_REQUIRED)
message(FATAL_ERROR "Could not find hello library")
endif ()
ENDIF ()
针对上面的模块让我们再来回顾一下 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()
为了能够让工程找到 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),将 hello library 定义为工程必须的共享库。
这时候再次运行 cmake ..
我们得到如下输出:
CMake Error: Could not find hello library.
因为找不到 libhello.x,所以,整个 Makefile 生成过程被中止。