​ Cmake Practice 总结 复杂例子 ​


本章我们将着重介绍系统预定义的 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 生成过程被中止。