>

CMake 通过用户编写的 CMakeLists.txt 来生成 Makefile,可以减轻为复杂工程编写 Makefile 的工作量。
CMakeLists.txt 的编写遵循 Cmake 定义的一些规则,本文介绍该文件的编写以及如何使用它生成 Makefile。

我们需要在源码所在的顶层目录来编写 CMakeLists.txt 文件,编写完成之后就可以通过 cmake 命令来生成 Makefile 等规则文件了。为了防止编译后生成的文件与已有的源文件在同一个目录下导致混乱,我们通常会在项目源码之外的其他位置新建一个 build 子目录来存放编译后生成的文件。新建 build 目录之后,进入该目录,在其中执行 cmake src_dir 生成 Makefile,生成的 Makefile 会在当前的 build 目录中,如果生成过程顺利没有出错,那么直接执行 make 命令就可以自动编译整个项目了。

上述流程大致如下:

$ cd <build_dir_different_from_source_dir>
$ cmake <source_dir>

CMakeLists.txt 的一般格式如下:

# This is test
CMAKE_MINIMUM_REQUIRED(VERSION 3.2)
PROJECT(MAIN) 
AUX_SOURCE_DIRECTORY(. DIR_SRCS) 
ADD_EXECUTABLE(main ${DIR_SRCS})

CMakeLists.txt 的语法比较简单,由命令、注释和空格组成。
符号 # 后面的内容是注释。
命令由命令名称、小括号和参数组成,命令不区分大小写,参数之间使用空格进行间隔。

cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
project:表示项目的名称是 MAIN 。
aux_source_directory: 定义需要编译的所有源码文件,这里用 . 表示当前目录中的所有文件,保存在变量 DIR_SRCS 中。
add_executable:将名为 ${DIR_SRCS} 指定的所有源文件编译成一个目标名称为 main 的可执行文件。

CMake 中有许多环境变量,可以通过 SET() 命令去设置,然后通过 ${Var} 的方式引用。

比如:

SET(SRC_LIST test.cpp main.cpp)
ADD_EXECUTABLE(hello ${SRC_LIST})

1. 指定编译选项 (Compile args)

在使用 gcc 或者 g++ 编译源码文件时,我们通常会指定一些编译参数,比如常见的 gcc -g -Wall -O2 等等。

如何在 CMakeLists.txt 中指定这些选项呢?可以参考如下方式:

SET(CMAKE_BUILD_TYPE DEBUG)
SET(CMAKE_CXX_FLAGS "-Wall")
SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
SET(CMAKE_CXX_FLAGS_DEBUG  "-O0 -g -DDEBUG_ -DUSE_BOOST")
SET(CMAKE_CXX_FLAGS_RELEASE "-O2")
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
INCLUDE_DIRECTORIES(../otherheaders)

2. 添加头文件搜索路径

如果我们有除 gcc 默认头文件搜索路径之外的头文件需要引入,通常我们会通过 export C_INCLUDE_PATH=/path/to/our_include 或者 gcc -I /path/to/our_include 的方式引入。

那么在 CMakeLists.txt 中如何指定呢?

使用 CMake 中的 include_directories 命令来添加:

include_directories(
    /usr/local/other_include
    ../others
    include
)

如果要为指定的目标文件(库文件或可执行文件,比如目标为 foobar)添加头文件路径,可以使用 target_include_directories 命令来添加:

target_include_directories(
    foobar PUBLIC
    /usr/local/flac
    include
    src
)

3. 添加动态链接库

在目标文件的链接阶段,我们可能会需要链接外部库,比如 -lpthread, -lcurl, -lcrypto 等等。

如何在 CMakeLists.txt 来指定这些外部库呢?

CMake 提供了一个 FIND_PACKAGE(LibName) 命令帮助我们来查找指定的外部链接库,这个是通过 cmake 提供的 .cmake 后缀的文件来定位的。
然后通过 TARGET_LINK_LIBRARIES 来为目标文件添加指定的库链接。

CMake 提供的 .cmake 文件通常位于 /usr/share/cmake/Modules 或者 /usr/local/share/cmake-3.6/Modules/ 目录中,
文件名是由 Find 加上库名字(即上述 FIND_PACKAGE 中的 LibName)拼接而成,比如 pthread 库,使用了 FindThreads.cmake 作为文件名,而 curl 库,使用了 FindCURL.cmake 作为文件名。
遇到默认没有提供的库的 .cmake 文件,用户还可以自定义 .cmake 文件。如果不知道如何编写 .cmake 文件,可以参考系统提供的 .cmake 来编写,语法非常简单。

下面简单列举下 -lpthread, -lcurl, -lcrypto 这几个库在 CMakeLists.txt 中的指定方式,其他的外部库指定方式是完全类似的。当然,需要先确保本地已经安装了这几个库。


(1) -lpthread
FIND_PACKAGE(Threads REQUIRED)
TARGET_LINK_LIBRARIES(testprog ${CMAKE_THREAD_LIBS_INIT})

提示:添加 REQUIRED 的目的是强制要求链接该库,没找到的话会报错。如果不添加 REQUIRED 的话,就相当于是 OPTIONAL 可选的。


(2) -lcurl
FIND_PACKAGE(CURL REQUIRED)
IF(CURL_FOUND)
    INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
    SET(Libs ${Libs} ${CURL_LIBRARIES})
ELSE()
    MESSAGE(FATAL_ERROR "Could not find the CURL library.")
ENDIF()

TARGET_LINK_LIBRARIES(testcurl ${Libs})

(3) -lcrypto

默认没有提供 .cmake 文件,只能自己手写。可以参考 FindCURL.cmake 来编写。

下面介绍如何编写 .cmake 文件:

(i)首先定位 header 头文件

FIND_PATH(CRYPTO_INCLUDE_DIR openssl/crypto.h)    参考 find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)

(ii)然后声明 library 名字,crypto 是由 openssl 提供的库的名字,链接时也是使用 -lcrypto,因此 库名字就叫 crypto。
实际的库文件名是 libcrypto.so,位于 /usr/lib64 或者 /usr/lib/x86_64-linux-gnu/ 目录下

FIND_LIBRARY(CRYPTO_LIBRARIES NAMES crypto)

(iii)声明为高级

MARK_AS_ADVANCED(CRYPTO_INCLUDE_DIR CRYPTO_LIBRARIES)

(iv)如果找到了头文件和相应的库,就设置相关的标志,比如 CRYPTO_FOUND 设为 true

IF(CRYPTO_INCLUDE_DIR AND CRYPTO_LIBRARIES)
   SET(CRYPTO_FOUND TRUE)
ELSE()
   SET(CRYPTO_FOUND FALSE)
ENDIF ()

(v)然后根据 CRYPTO_FOUND 来设置一些变量。

if(CRYPTO_FOUND)
    set(CRYPTO_LIBRARIES ${CRYPTO_LIBRARY})
    set(CRYPTO_INCLUDE_DIRS ${CRYPTO_INCLUDE_DIR})
endif()

(iv)和 (v) 还可以直接用统一的方式来处理
引入 cmake 默认提供的 FindPackageHandleStandardArgs.cmake ,然后调用 FIND_PACKAGE_HANDLE_STANDARD_ARGS()

include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS( CRYPTO
                                   REQUIRED_VARS CRYPTO_LIBRARY CRYPTO_INCLUDE_DIR
                                   VERSION_VAR CRYPTO_VERSION_STRING)

4 cmake 提示信息

有时候我们需要在执行 cmake 命令时向终端输出一些信息,以便更清晰的了解编译过程。cmake 提供了 MESSAGE 命令来达到目的,

MESSAGE 指令的语法:

MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)

MESSAGE 指令包含三种类型:

  • SEND_ERROR 产生错误,生成过程被跳过。
  • SATUS 输出前缀为 - 的信息。
  • FATAL_ERROR 立即终止所有cmake过程。

例子:

MESSAGE(STATUS "Found xxx in ${XXX_LIBRARIES}")
MESSAGE(FATAL_ERROR "Could not find the xxx library.")
MESSAGE(SEND_ERROR "Couldn't find xxx include files and/or library")

附:CMake 命令列表
https://cmake.org/cmake/help/v3.2/manual/cmake-commands.7.html




参考
https://www.johnlamp.net/cmake-tutorial.html
https://cmake.org/cmake-tutorial/
https://wiki.openssl.org/index.php/Libcrypto_API
https://cmake.org/cmake/help/v2.8.8/cmake.html#command%3afind_package