>

注:本文中的大部分内容均参考至 CMake Practice 一书。


CMake 中默认的模块文件通常会位于 /usr/share/cmake-3.7.1/Module 目录下,如果我们需要为工程项目自定义 CMake 的模块文件,通常在工程根目录下新建一个名称为 cmake 的目录来存放,然后在 CMakeLists.txt 中通过 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 的方式引入该目录。

模块的使用和自定义模块


纯粹依靠 CMake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以 CMake 设计成了可扩展的架构,使得我们可以通过编写一些自定义的模块来扩展 CMake。

这里将着重介绍系统预定义的 Find 模块的使用以及如何编写用户自定义的 Find 模块,系统中提供了其他各种模块,一般情况需要使用 INCLUDE 指令显式的调用,FIND_PACKAGE 指令是一个特例,可以直接调用预定义的模块。

下面,我就通过一个具体的例子来介绍如何编写用户自定义的 Find 模块。


####(一)使用 CMake 默认提供的 FindCURL 模块

我们在 /proj/cmake/test/src 编写 main.c,这段代码通过 curl 命令获取 www.linux-ren.org 的首页并写入 /tmp/curl-test 文件中,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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);
CURL coderes;
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 库的函数,在执行 gcc 编译链接时,不仅需要能找到对应的头文件信息,还需要添加动态库选项 -lcurl。 因此,编写 CMakeLiksts.txt 时也需要能实现这一点,下面就讲述如何实现:

建立主工程文件 CmakeLists.txt,内容如下:

PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)

新建 src/CmakeLists.txt,内容如下

ADD_EXECUTABLE(curltest main.c)

现在自然是没办法编译的,我们需要添加 curl 的头文件路径和库文件。

方法1:
直接通过 INCLUDE_DIRECTORIESTARGET_LINK_LIBRARIES 指令添加:

我们可以直接在 src/CMakeLists.txt 中添加:

INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltestcurl)

然后建立 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 中。

我们再来看一个复杂的例子,通过 _FOUND 来控制工程特性:

SET(mySourcesviewer.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 模块

接下来在 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_FOUNDTRUE)
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指令中使用。

下面建立 src/main.c,内容为:

1
2
3
4
5
#include <hello.h>
int main() {
HelloFunc();
return 0;
}

建立 src/CMakeLists.txt 文件,内容如下:

FIND_PACKAGE(HELLO)
IF(HELLO_FOUND)
    ADD_EXECUTABLE(hellomain.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 ..

我们可以从输出中看到:

FoundHello: /usr/lib/libhello.so

如果我们把上面的FIND_PACKAGE(HELLO)修改为FIND_PACKAGE(HELLO QUIET),

不会看到上面的输出。接下来就可以使用make命令构建工程,运行:

# ./src/hello

可以得到输出:

HelloWorld

说明工程成功构建。


#####(四)如果没有找到hellolibrary呢? 我们可以尝试将/usr/lib/libhello.x移动到/tmp目录,这样按照FindHELLO模块的定义,找不到hellolibrary了,我们再来看一下构建结果:
$ cmake ..

仍然可以成功进行构建,但是这时候是没有办法编译的。

修改FIND_PACKAGE(HELLO)为FIND_PACKAGE(HELLO REQUIRED),将hellolibrary定义为工程必须的共享库。
这时候再次运行

$ cmake ..

我们得到如下输出:

CMakeError: Could not find hello library.

因为找不到libhello.x,所以,整个Makefile生成过程被出错中止。

一些问题

1.怎样区分debug、release版本
建立debug/release两目录,分别在其中执行cmake -D CMAKE_BUILD_TYPE=Debug(或Release),需要编译不同版本时进入不同目录执行make即可:

Debug版会使用参数-g;
Release版使用-O3–DNDEBUG

另一种设置方法——例如DEBUG版设置编译参数DDEBUG

IF(DEBUG_mode)
    add_definitions(-DDEBUG)
ENDIF()

在执行cmake时增加参数即可,例如cmake -D DEBUG_mode=ON

2.怎样设置条件编译
例如debug版设置编译选项DEBUG,并且更改不应改变CMakelist.txt
使用option command,eg:

option(DEBUG_mode”ON for debug or OFF for release” ON)
IF(DEBUG_mode)
add_definitions(-DDEBUG)
ENDIF()

使其生效的方法:首先cmake生成makefile,然后make edit_cache编辑编译选项;Linux下会打开一个文本框,可以更改,改完后再make生成目标文件——emacs不支持make edit_cache;

局限:这种方法不能直接设置生成的makefile,而是必须使用命令在make前设置参数;对于debug、release版本,相当于需要两个目录,分别先cmake一次,然后分别makeedit_cache一次;

期望的效果:在执行cmake时直接通过参数指定一个开关项,生成相应的makefile。

参考:

https://www.kancloud.cn/itfanr/cmake-practice/82982
http://stackoverflow.com/questions/10765885/how-to-install-your-custom-cmake-find-module
http://blog.csdn.net/z_h_s/article/details/50699905