CMake

Table of Contents

1 CMake简介

CMake 是一个跨平台的工程构建工具。CMake并不直接构建出最终的软件,而是在Linux/Unix中生成Makefile,在Windows中生成Windows Visual Studio工程文件,然后用户可以按照各个系统中原生的方式进行编译。

CMake配置文件的名称为CMakeLists.txt,放在每个源代码目录中。

参考:
Mastering CMake(本文主要摘自该书)
CMake实践: http://sewm.pku.edu.cn/src/paradise/reference/CMake Practice.pdf
cmake - Cross-Platform Makefile Generator: https://cmake.org/cmake/help/cmake-2.6.html
CMake tutorial - and its friends CPack, CTest and CDash: http://www.bogotobogo.com/cplusplus/files/cmake/CMake-tutorial-pdf.pdf

1.1 第一个CMake例子

假设工作目录为“/Users/cig01/test/”,其中有一个简单的测试程序(计算平方根),内容如下:

// File tutorial.cxx
// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
  }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

用CMake编译它的过程为:首先,在同一目录中手动创建CMakeLists.txt;然后,利用cmake命令生成对应的Makefile,再用make命令即可。

准备CMakeLists.txt如下:

$ cat CMakeLists.txt
project (Tutorial)
add_executable(MyApp tutorial.cxx)

cmake命令的基本语法是 cmake <path-to-source> 。这里,源码在当前目录中,执行命令 cmake . 可以生成Makefile文件,如:

$ ls
CMakeLists.txt      tutorial.cxx
$ cmake .
-- The C compiler identification is AppleClang 6.1.0.6020053
-- The CXX compiler identification is AppleClang 6.1.0.6020053
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc
-- Check for working C compiler: /Library/Developer/CommandLineTools/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++
-- Check for working CXX compiler: /Library/Developer/CommandLineTools/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/cig01/test
$ ls -F
CMakeCache.txt      CMakeLists.txt      cmake_install.cmake
CMakeFiles/         Makefile            tutorial.cxx

可以发现命令 cmake . 除了生成Makefile外,还生成了其它一些辅助文件(如CMakeCache.txt等,不用理睬它们)。

最后,我们可以调用make进行编译,如:

$ make
Scanning dependencies of target MyApp
[ 50%] Building CXX object CMakeFiles/MyApp.dir/tutorial.cxx.o
[100%] Linking CXX executable MyApp
[100%] Built target MyApp
$ ./MyApp 5
The square root of 5 is 2.23607

1.1.1 add_executable命令

add_executable命令的作用是添加一个可执行程序到目标列表中。
前面例子中,add_executable(MyApp tutorial.cxx)的作用是用tutorial.cxx生成可执行程序MyApp。如果一个可执行程序依赖于多个源文件,可以在add_executable中直接指定多个源文件。如add_executable(MyApp2 main.cxx func1.cxx func2.cxx)。

1.1.2 in-source build VS. out-of-source build

前面所述的编译方式,在执行完 cmake . 后,Makefile和一些辅助文件都生成在源代码相同的目录中(这称为in-source build),但这比较混乱,没有把源码和cmake生成文件分开。

$ ls /Users/cig01/test
CMakeCache.txt      CMakeLists.txt      cmake_install.cmake
CMakeFiles          Makefile            tutorial.cxx

有另外一种推荐的编译方式:out-of-source build,即在源代码目录结构外的其它目录中运行cmake,这时cmake生成的文件都会保存在启动cmake时的目录中,而不会影响到源代码目录。 比如,新建一个build子目录,进入到build子目录中运行命令 cmake .. ,cmake生成的文件都会放在build子目录中。

1.2 推荐的工程目录结构

一般地,程序源码放在工程目录的src子目录中,工程目录中还可能有doc子目录用来存在相关文档。往往还会在工程目录中添加文件COPYRIGHT和README。

如果把前面介绍的例子(计算平方根)整理为这种结构,则整个工程的目录结构为:

├── CMakeLists.txt
├── COPYRIGHT
├── doc/
├── README
└── src/
    ├── CMakeLists.txt
    └── tutorial.cxx

工程根目录下的CMakeLists.txt内容如下:

project (Tutorial)
add_subdirectory(src bin)

src子目录中的CMakeLists.txt内容如下:

add_executable(MyApp tutorial.cxx)

out-of-source编译过程:在工程根目录中建立build目录,进入build目录中运行 cmake .. 和make。
具体过程如下:

$ ls -F
CMakeLists.txt  COPYRIGHT       README          doc/            src/
$ mkdir build
$ cd build
$ cmake ..
$ make

编译完成后,cmake和make产生的文件或目录都在build子目录中,目标程序MyApp会生成在build/bin目录中。

1.2.1 add_subdirectory命令

前面介绍的实例中,工程根目录的CMakeLists.txt中有add_subdirectory(src bin)命令,它的作用是将src子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin目录。如果指定编译输出目录bin,那么编译结果(包括中间结果)都将存放在build/src目录(这个目录跟原有的src目录对应),指定bin目录后,相当于在编译时将src重命名为bin,子目录src的所有中间结果和目标二进制(如MyApp)都将存放在build/bin目录。

总结: add_subdirectory命令的作用是向当前工程添加存放源文件的子目录,并可以指定生成二进制文件的存放位置。

add_subdirectory命令的语法为: add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) ,其说明文档可参考: make --help-command add_subdirectory

1.3 在线查看帮助文档

用命令 cmake --help 可以查看cmake在线帮助文档。

$ cmake --help
Usage

  cmake [options] <path-to-source>
  cmake [options] <path-to-existing-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Options
  -C <initial-cache>           = Pre-load a script to populate the cache.
  -D <var>[:<type>]=<value>    = Create a cmake cache entry.
  -U <globbing_expr>           = Remove matching entries from CMake cache.
  -G <generator-name>          = Specify a build system generator.
  -T <toolset-name>            = Specify toolset name if supported by
                                 generator.
  -A <platform-name>           = Specify platform name if supported by
                                 generator.
  -Wno-dev                     = Suppress developer warnings.
  -Wdev                        = Enable developer warnings.
  -E                           = CMake command mode.
  -L[A][H]                     = List non-advanced cached variables.
  --build <dir>                = Build a CMake-generated project binary tree.
  -N                           = View mode only.
  -P <file>                    = Process script mode.
  --find-package               = Run in pkg-config like mode.
  --graphviz=[file]            = Generate graphviz of dependencies, see
                                 CMakeGraphVizOptions.cmake for more.
  --system-information [file]  = Dump information about this system.
  --debug-trycompile           = Do not delete the try_compile build tree.
                                 Only useful on one try_compile at a time.
  --debug-output               = Put cmake in a debug mode.
  --trace                      = Put cmake in trace mode.
  --trace-expand               = Put cmake in trace mode with variable
                                 expansion.
  --warn-uninitialized         = Warn about uninitialized values.
  --warn-unused-vars           = Warn about unused variables.
  --no-warn-unused-cli         = Don't warn about command line options.
  --check-system-vars          = Find problems with variable usage in system
                                 files.
  --help,-help,-usage,-h,-H,/? = Print usage information and exit.
  --version,-version,/V [<f>]  = Print version number and exit.
  --help-full [<f>]            = Print all help manuals and exit.
  --help-manual <man> [<f>]    = Print one help manual and exit.
  --help-manual-list [<f>]     = List help manuals available and exit.
  --help-command <cmd> [<f>]   = Print help for one command and exit.
  --help-command-list [<f>]    = List commands with help available and exit.
  --help-commands [<f>]        = Print cmake-commands manual and exit.
  --help-module <mod> [<f>]    = Print help for one module and exit.
  --help-module-list [<f>]     = List modules with help available and exit.
  --help-modules [<f>]         = Print cmake-modules manual and exit.
  --help-policy <cmp> [<f>]    = Print help for one policy and exit.
  --help-policy-list [<f>]     = List policies with help available and exit.
  --help-policies [<f>]        = Print cmake-policies manual and exit.
  --help-property <prop> [<f>] = Print help for one property and exit.
  --help-property-list [<f>]   = List properties with help available and
                                 exit.
  --help-properties [<f>]      = Print cmake-properties manual and exit.
  --help-variable var [<f>]    = Print help for one variable and exit.
  --help-variable-list [<f>]   = List variables with help available and exit.
  --help-variables [<f>]       = Print cmake-variables manual and exit.

Generators

The following generators are available on this platform:
  Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Xcode                        = Generate Xcode project files.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
  KDevelop3                    = Generates KDevelop 3 project files.
  KDevelop3 - Unix Makefiles   = Generates KDevelop 3 project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.

1.4 CMake实例:编译和使用库(add_library, target_link_libraries)

还是用前面“计算平方根”的例子来说明如何编译和使用库。

这次,我们要自己实现求平方根的函数(mysqrt),并把它封装到一个动态库中。主程序使用动态库中的函数mysqrt。

整个工程的结构如下:

├── CMakeLists.txt
└── src/
    ├── CMakeLists.txt
    ├── MathFunctions/
    │   ├── CMakeLists.txt
    │   ├── MathFunctions.h
    │   └── mysqrt.cxx
    └── tutorial.cxx

编译测试过程如下:

$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./bin/Tutorial 5
The square root of 5 is 2.23607
$ ldd bin/Tutorial
	linux-vdso.so.1 =>  (0x00007ffc483dd000)
	libMathFunctions.so => /home/cig01/test/build/bin/MathFunctions/libMathFunctions.so (0x00007f02583d4000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f025800f000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f0257d09000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f02585d6000)

工程中,src/tutorial.cxx内容如下:

#include <stdio.h>
#include <stdlib.h>
#include <MathFunctions.h>

int main (int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
  }
  double inputValue = atof(argv[1]);
  double outputValue = mysqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

工程中,src/MathFunctions/mysqrt.cxx内容如下:

#include <math.h>

double mysqrt(double arg) {
  return sqrt(arg);
}

工程中,src/MathFunctions/MathFunctions.h内容如下:

double mysqrt(double arg);

这个工程中有3个CMakeLists.txt
工程根目录下的CMakeLists.txt内容为:

cmake_minimum_required(VERSION 2.8)

project (Tutorial)
add_subdirectory(src bin)

工程src/MathFunctions子目录中的CMakeLists.txt内容为:

add_library(MathFunctions SHARED mysqrt.cxx)       # add_library 作用:把 mysqrt.cxx 编译为动态库 libMathFunctions.so

工程src子目录中的CMakeLists.txt内容为:

add_subdirectory(MathFunctions)

include_directories("${PROJECT_SOURCE_DIR}/src/MathFunctions")

# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions)     # target_link_libraries 作用:把可执行程序 Tutorial 链接到库 MathFunctions

2 编写CMakeLists.txt

CMakeLists.txt是CMake最重要的配置文件,它出现在每个源代码目录中。

2.1 CMakeLists.txt基本语法

CMakeLists.txt由命令、注释和空白组成。

A command consists of the command name, opening parenthesis, white space separated arguments and a closing parenthesis.
A comment is indicated using the # character and runs from that character until the end of the line.

CMakeLists.txt由命令组成,且命令都具有下面格式:

command(args...)

命令名字不区分大小写(cmake 2.2之前,只接受大写形式的命令名字)。命令的多个参数之间用“空格”分开。

2.1.1 定义(set命令)和使用变量

set 命令可以定义变量,用语法 ${VAR} 可以引用变量的值(不能省写为 $VAR )。
如: set(foo a b c) 定义变量foo为列表a b c,从而 command(${foo})command(a b c) (传递了3个参数)是等价的; command("${foo}")command("a b c") (传递了1个参数)是等价的。

unset 命令可以取消一个变量的定义。

2.1.1.1 环境变量

$EVN{VAR} 可以访问环境变量。
如,下面命令可打印出PATH环境变量:

message($ENV{PATH})

可以用 set(ENV{var} value) 增加或修改环境变量。
如,新增加环境变量MY_PATH,设置为/home/mybin:

set(ENV{MY_PATH} /home/mybin)

2.2 CMake基本命令

执行命令 cmake --help-command-list 可以列出所有cmake命令。执行命令 cmake --help-command <cmd> 可以查看命令cmd的帮助文档。如:

$ cmake --help-command while
while
-----

Evaluate a group of commands while a condition is true

::

 while(condition)
   COMMAND1(ARGS ...)
   COMMAND2(ARGS ...)
   ...
 endwhile(condition)

All commands between while and the matching ``endwhile()`` are recorded
without being invoked.  Once the ``endwhile()`` is evaluated, the
recorded list of commands is invoked as long as the condition is true.  The
condition is evaluated using the same logic as the ``if()`` command.

2.2.1 project命令

命令 project 用来指定工程的名称。

project(<PROJECT-NAME> [C] [C++])

project命令隐式地定义两个cmake变量:“<PROJECT-NAME>_BINARY_DIR”以及“<PROJECT-NAME>_SOURCE_DIR”。

例如:“project(HELLO)”将定义变量“HELLO_BINARY_DIR”和“HELLO_SOURCE_DIR”。不过,同时cmake系统也为我们预定义了变量“PROJECT_BINARY_DIR”和“PROJECT_SOURCE_DIR”,他们的值分别跟“HELLO_BINARY_DIR”与“HELLO_SOURCE_DIR”一致。所以可直接使用“PROJECT_BINARY_DIR”和“PROJECT_SOURCE_DIR”。

2.3 Flow Control

流程控制语句也属于cmake命令,这里单独介绍它们。

参考:Mastering CMake, 4.3 Flow Control

2.3.1 Conditional statements (if)

if 命令基本格式如下:

if(expression)
  # then section.
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
elseif(expression2)
  # elseif section.
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
else(expression)         # 可省写为 else() ;如果不省写,则else中的 expression 和if中指定的 expression 要一致(尽管看起来奇怪)。
  # else section.
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endif(expression)        # 可省写为 endif() ;如果不省写,则endif中 expression 和if中指定的 expression 要一致。

Evaluates the given expression. If the result is true, the commands in the THEN section are invoked. Otherwise, the commands in the else section are invoked. The elseif and else sections are optional. You may have multiple elseif clauses. Note that the expression in the else and endif clause is optional.

if命令中支持的expression形式很多,完整说明可参考: cmake --help-command if ,这里介绍一些常用的expression形式。

if语法形式 说明
if (expression) expression不为:“空,0,N,NO,OFF,FALSE,NOTFOUND或以_NOTFOUND结尾的字符串”中的一个时为真
if (NOT expression) 和上面相反
if (expr1 AND expr2) 当expr1,expr2都为真时为真
if (expr1 OR expr2) 当expr1,expr2有一个为真时为真
if (DEFINED variable) 当变量variable已定义时(不管所定义的值是什么)为真
if (COMMAND cmd) 当cmd是命令,宏或函数时为真
if (EXISTS path-to-file-or-directory) 当文件或目录path-to-file-or-directory存在时为真
if (file1 IS_NEWER_THAN file2) 当file1比file2新,或者file1/file2中有一个不存在时为真(测试时请使用全路径)
if (IS_DIRECTORY path-to-directory) 当path-to-directory是目录时为真(测试时请使用全路径)

if命令用来测试数字和字符串的用法如下:

if语法形式(var可以用var名,也可以用${var}) 说明
if (<var or string> MATCHES regex) <var or string>匹配上正则表达式regex时为真
if (<var or string> LESS <var or string>) 小于(数字比较)
if (<var or string> GREATER <var or string>) 大于(数字比较)
if (<var or string> EQUAL <var or string>) 等于(数字比较)
if (<var or string> STRLESS <var or string>) 小于(字母表顺序比较)
if (<var or string> STRGREATER <var or string>) 大于(字母表顺序比较)
if (<var or string> STREQUAL <var or string>) 等于(字母表顺序比较)

2.3.2 Looping constructs (foreach, while)

2.3.2.1 foreach

foreach的语法有三种形式。
形式一:

foreach(loop_var arg1 arg2 ...)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endforeach(loop_var)

foreach形式一的实例:

set(mylist a b c d)

foreach(_var ${mylist})
  message("current var:${_var}")
endforeach()

形式二(又包含两种子形式):

# 从start开始到stop结束,以step为步进
foreach(loop_var RANGE start stop [step])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endforeach(loop_var)

# 从0到total以1为步进时,可简写为:
foreach(loop_var RANGE total)     # 相当于 foreach(loop_var RANGE 0 total 1)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endforeach(loop_var)

foreach形式二的实例(从1到100求和):

set(result 0)

foreach(_var RANGE 1 100)
  math(EXPR result "${result}+${_var}")
endforeach()

message("from 1 plus to 100 is: ${result}")  # 会输出 from 1 plus to 100 is: 5050

形式三:

foreach(loop_var IN [LISTS [list1 [...]]]
                    [ITEMS [item1 [...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endforeach(loop_var)
2.3.2.2 while
while(condition)
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endwhile(condition)

while的真假判断条件和if语句相同。

2.3.2.3 break, continue

命令break(), continue()可用在foreach或while中。

参考:
cmake --help-command break
cmake --help-command continue

2.3.3 Procedure definitions (function, macro)

2.3.3.1 function

CMake中函数定义语法如下所示:

function(<name> [arg1 [arg2 [arg3 ...]]])
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endfunction(<name>)

在函数内部,可以使用 ${ARGC} 访问参数的个数,用 ${ARGV0}, ${ARGV1} 可以访问第1,2个参数等等。

如,下面定义一个名为fun1的函数:

function (fun1 str1 str2)
  message(${str1})
  message(${ARGV0})         # 和上一行作用相同
endfunction()

fun1(a b)        # 调用函数fun1
fun1(a b c)      # 调用函数fun1,传递参数多于声明参数时,也可以调用
fun1(a)          # 报错。传递参数少于声明参数。

参考: cmake --help-command function

2.3.3.2 macro(及和function的区别)

macro的语法如下所示:

macro(<name> [arg1 [arg2 [arg3 ...]]])
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
endmacro(<name>)

CMake中的function与macro的区别就在于: function开启一个新的作用域,函数中的变量默认是局部的(但可以通过set的选项PARENT_SCOPE使变量在函数外面可访问到);而macro在当前环境中展开(和C的宏展开有点类似),宏不会开启新作用域,macro中的变量在外面可以被访问到。 测试如下:

macro(macro_test)
    set(test1 "aaa")                 #变量test1在宏外面是可见的
endmacro()

function(fun_test1)
    set(test2 "bbb")                 #变量test2在函数外面不可见
  endfunction()

function(fun_test2)
    set(test3 "ccc" PARENT_SCOPE)    #由于使用了set的选项PARENT_SCOPE,所有变量test3在函数外面可见
endfunction()

macro_test()
message("${test1}")     # 会输出 aaa

fun_test1()
message("${test2}")     # test2 在函数外不可见,相当于输出一个未定义变量(用message输出未定义变量时仅是输出空行)

fun_test2()
message("${test3}")     # 会输出 ccc

2.4 CMake内置变量

CMake定义了很多内置变量,可在CMakeLists.txt中直接使用。

执行命令 cmake --help-variable-list 可以列出所有内置变量。执行命令 cmake --help-variable <var> 可以查看变量var的帮助文档。如:

$ cmake --help-variable UNIX
UNIX
----

``True`` for UNIX and UNIX like operating systems.

Set to ``true`` when the target system is UNIX or UNIX like (i.e.
``APPLE`` and ``CYGWIN``).

3 System Inspection

3.1 Finding Packages (find_package)

如果软件使用了外部库(如TIFF,ZLIB,Threads等),但一般事先不知道它的头文件和链接库的位置,需要在编译命令中加上包含它们的查找路径。这个工作可以使用CMake中的 find_package 命令来完成。

find_packge 命令的功能是“Load settings for an external project.”,其基本格式为:

# find_package 命令用法一
find_package(<package> [version] [EXACT] [QUIET] [MODULE]
             [REQUIRED] [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

# find_package 命令用法二
find_package(<package> [version] [EXACT] [QUIET]
             [REQUIRED] [[COMPONENTS] [components...]]
             [CONFIG|NO_MODULE]
             [NO_POLICY_SCOPE]
             [NAMES name1 [name2 ...]]
             [CONFIGS config1 [config2 ...]]
             [HINTS path1 [path2 ... ]]
             [PATHS path1 [path2 ... ]]
             [PATH_SUFFIXES suffix1 [suffix2 ...]]
             [NO_DEFAULT_PATH]
             [NO_CMAKE_ENVIRONMENT_PATH]
             [NO_CMAKE_PATH]
             [NO_SYSTEM_ENVIRONMENT_PATH]
             [NO_CMAKE_PACKAGE_REGISTRY]
             [NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
             [NO_CMAKE_SYSTEM_PATH]
             [NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
             [CMAKE_FIND_ROOT_PATH_BOTH |
              ONLY_CMAKE_FIND_ROOT_PATH |
              NO_CMAKE_FIND_ROOT_PATH])

运行find_package时,如果对应的外部库被找到,一般地,变量“<package>_FOUND”会被置为“TRUE”,变量“<package>_INCLUDE_DIR”会被设置为找到库对应头文件的目录,变量“<package>_LIBRARIES”会被设置为对应库文件的全路径。 例如:

find_package (ZLIB REQUIRED)      # 查找加载 ZLIB 库相关配置。选项 REQUIRED 的含义是找不到对应库,就不往下执行了,直接报错
message(${ZLIB_FOUND})            # 若找到 ZLIB 库,变量 ZLIB_FOUND 会被置为 TRUE
message(${ZLIB_INCLUDE_DIRS})     # 若找到 ZLIB 库,变量 ZLIB_INCLUDE_DIRS 会被设置为找到库对应头文件的目录,比如 /usr/include
message(${ZLIB_LIBRARIES})        # 若找到 ZLIB 库,变量 ZLIB_LIBRARIES 会被设置为对应库文件的全路径,比如 /usr/lib/x86_64-linux-gnu/libz.so

参考:
Mastering CMake, 5.3 Finding Packages
cmake --help-command find_package

3.1.1 find_package工作过程

find_package 有两种模式:“Module mode”和“Config mode”。

“Module mode”是指 find_package 查找名为“Find<package>.cmake”的文件,并加载它。 文件“Find<package>.cmake”的查找位置是 CMAKE_MODULE_PATH 指定的位置和CMake安装位置。
“Config mode”是指 find_package 查找名为“<package>Config.cmake”或者“<package>-config.cmake”的文件,并加载它。

默认, find_package 会先工作在“Module mode”,找不到时再工作在“Config mode”。但如果调用find_package时指定了选项“MODULE”,则只会工作在“Module mode”,如果调用find_package时指定了选项“CONFIG|NO_MODULE”,则只会工作在“Config mode”。

3.1.2 CMake内置的Find<package>.cmake文件

CMake内置了一些常用库对应的“Find<package>.cmake”文件。比如:

$ ls /usr/share/cmake-2.8/Modules/Find*
/usr/share/cmake-2.8/Modules/FindALSA.cmake
/usr/share/cmake-2.8/Modules/FindArmadillo.cmake
/usr/share/cmake-2.8/Modules/FindASPELL.cmake
/usr/share/cmake-2.8/Modules/FindAVIFile.cmake
/usr/share/cmake-2.8/Modules/FindBISON.cmake
/usr/share/cmake-2.8/Modules/FindBLAS.cmake
/usr/share/cmake-2.8/Modules/FindBoost.cmake
/usr/share/cmake-2.8/Modules/FindBullet.cmake
/usr/share/cmake-2.8/Modules/FindBZip2.cmake
/usr/share/cmake-2.8/Modules/FindCABLE.cmake
/usr/share/cmake-2.8/Modules/FindCoin3D.cmake
/usr/share/cmake-2.8/Modules/FindCUDA.cmake
/usr/share/cmake-2.8/Modules/FindCups.cmake
/usr/share/cmake-2.8/Modules/FindCURL.cmake
/usr/share/cmake-2.8/Modules/FindCurses.cmake
/usr/share/cmake-2.8/Modules/FindCVS.cmake
/usr/share/cmake-2.8/Modules/FindCxxTest.cmake
/usr/share/cmake-2.8/Modules/FindCygwin.cmake
/usr/share/cmake-2.8/Modules/FindDart.cmake
/usr/share/cmake-2.8/Modules/FindDCMTK.cmake
/usr/share/cmake-2.8/Modules/FindDevIL.cmake
/usr/share/cmake-2.8/Modules/FindDoxygen.cmake
/usr/share/cmake-2.8/Modules/FindEigen3.cmake
/usr/share/cmake-2.8/Modules/FindEXPAT.cmake
/usr/share/cmake-2.8/Modules/FindFLEX.cmake
/usr/share/cmake-2.8/Modules/FindFLTK2.cmake
/usr/share/cmake-2.8/Modules/FindFLTK.cmake
/usr/share/cmake-2.8/Modules/FindFreetype.cmake
/usr/share/cmake-2.8/Modules/FindGCCXML.cmake
/usr/share/cmake-2.8/Modules/FindGDAL.cmake
/usr/share/cmake-2.8/Modules/FindGettext.cmake
/usr/share/cmake-2.8/Modules/FindGIF.cmake
/usr/share/cmake-2.8/Modules/FindGit.cmake
/usr/share/cmake-2.8/Modules/FindGLEW.cmake
/usr/share/cmake-2.8/Modules/FindGLU.cmake
/usr/share/cmake-2.8/Modules/FindGLUT.cmake
/usr/share/cmake-2.8/Modules/FindGnuplot.cmake
/usr/share/cmake-2.8/Modules/FindGnuTLS.cmake
/usr/share/cmake-2.8/Modules/FindGTest.cmake
/usr/share/cmake-2.8/Modules/FindGTK2.cmake
/usr/share/cmake-2.8/Modules/FindGTK.cmake
/usr/share/cmake-2.8/Modules/FindHDF5.cmake
/usr/share/cmake-2.8/Modules/FindHg.cmake
/usr/share/cmake-2.8/Modules/FindHSPELL.cmake
/usr/share/cmake-2.8/Modules/FindHTMLHelp.cmake
/usr/share/cmake-2.8/Modules/FindIcotool.cmake
/usr/share/cmake-2.8/Modules/FindImageMagick.cmake
/usr/share/cmake-2.8/Modules/FindITK.cmake
/usr/share/cmake-2.8/Modules/FindJasper.cmake
/usr/share/cmake-2.8/Modules/FindJava.cmake
/usr/share/cmake-2.8/Modules/FindJNI.cmake
/usr/share/cmake-2.8/Modules/FindJPEG.cmake
/usr/share/cmake-2.8/Modules/FindKDE3.cmake
/usr/share/cmake-2.8/Modules/FindKDE4.cmake
/usr/share/cmake-2.8/Modules/FindLAPACK.cmake
/usr/share/cmake-2.8/Modules/FindLATEX.cmake
/usr/share/cmake-2.8/Modules/FindLibArchive.cmake
/usr/share/cmake-2.8/Modules/FindLibLZMA.cmake
/usr/share/cmake-2.8/Modules/FindLibXml2.cmake
/usr/share/cmake-2.8/Modules/FindLibXslt.cmake
/usr/share/cmake-2.8/Modules/FindLua50.cmake
/usr/share/cmake-2.8/Modules/FindLua51.cmake
/usr/share/cmake-2.8/Modules/FindMatlab.cmake
/usr/share/cmake-2.8/Modules/FindMFC.cmake
/usr/share/cmake-2.8/Modules/FindMotif.cmake
/usr/share/cmake-2.8/Modules/FindMPEG2.cmake
/usr/share/cmake-2.8/Modules/FindMPEG.cmake
/usr/share/cmake-2.8/Modules/FindMPI.cmake
/usr/share/cmake-2.8/Modules/FindOpenAL.cmake
/usr/share/cmake-2.8/Modules/FindOpenGL.cmake
/usr/share/cmake-2.8/Modules/FindOpenMP.cmake
/usr/share/cmake-2.8/Modules/FindOpenSceneGraph.cmake
/usr/share/cmake-2.8/Modules/FindOpenSSL.cmake
/usr/share/cmake-2.8/Modules/FindOpenThreads.cmake
/usr/share/cmake-2.8/Modules/FindosgAnimation.cmake
/usr/share/cmake-2.8/Modules/Findosg.cmake
/usr/share/cmake-2.8/Modules/FindosgDB.cmake
/usr/share/cmake-2.8/Modules/Findosg_functions.cmake
/usr/share/cmake-2.8/Modules/FindosgFX.cmake
/usr/share/cmake-2.8/Modules/FindosgGA.cmake
/usr/share/cmake-2.8/Modules/FindosgIntrospection.cmake
/usr/share/cmake-2.8/Modules/FindosgManipulator.cmake
/usr/share/cmake-2.8/Modules/FindosgParticle.cmake
/usr/share/cmake-2.8/Modules/FindosgPresentation.cmake
/usr/share/cmake-2.8/Modules/FindosgProducer.cmake
/usr/share/cmake-2.8/Modules/FindosgQt.cmake
/usr/share/cmake-2.8/Modules/FindosgShadow.cmake
/usr/share/cmake-2.8/Modules/FindosgSim.cmake
/usr/share/cmake-2.8/Modules/FindosgTerrain.cmake
/usr/share/cmake-2.8/Modules/FindosgText.cmake
/usr/share/cmake-2.8/Modules/FindosgUtil.cmake
/usr/share/cmake-2.8/Modules/FindosgViewer.cmake
/usr/share/cmake-2.8/Modules/FindosgVolume.cmake
/usr/share/cmake-2.8/Modules/FindosgWidget.cmake
/usr/share/cmake-2.8/Modules/FindPackageHandleStandardArgs.cmake
/usr/share/cmake-2.8/Modules/FindPackageMessage.cmake
/usr/share/cmake-2.8/Modules/FindPerl.cmake
/usr/share/cmake-2.8/Modules/FindPerlLibs.cmake
/usr/share/cmake-2.8/Modules/FindPHP4.cmake
/usr/share/cmake-2.8/Modules/FindPhysFS.cmake
/usr/share/cmake-2.8/Modules/FindPike.cmake
/usr/share/cmake-2.8/Modules/FindPkgConfig.cmake
/usr/share/cmake-2.8/Modules/FindPNG.cmake
/usr/share/cmake-2.8/Modules/FindPostgreSQL.cmake
/usr/share/cmake-2.8/Modules/FindProducer.cmake
/usr/share/cmake-2.8/Modules/FindProtobuf.cmake
/usr/share/cmake-2.8/Modules/FindPythonInterp.cmake
/usr/share/cmake-2.8/Modules/FindPythonLibs.cmake
/usr/share/cmake-2.8/Modules/FindQt3.cmake
/usr/share/cmake-2.8/Modules/FindQt4.cmake
/usr/share/cmake-2.8/Modules/FindQt.cmake
/usr/share/cmake-2.8/Modules/FindQuickTime.cmake
/usr/share/cmake-2.8/Modules/FindRTI.cmake
/usr/share/cmake-2.8/Modules/FindRuby.cmake
/usr/share/cmake-2.8/Modules/FindSDL.cmake
/usr/share/cmake-2.8/Modules/FindSDL_image.cmake
/usr/share/cmake-2.8/Modules/FindSDL_mixer.cmake
/usr/share/cmake-2.8/Modules/FindSDL_net.cmake
/usr/share/cmake-2.8/Modules/FindSDL_sound.cmake
/usr/share/cmake-2.8/Modules/FindSDL_ttf.cmake
/usr/share/cmake-2.8/Modules/FindSelfPackers.cmake
/usr/share/cmake-2.8/Modules/FindSquish.cmake
/usr/share/cmake-2.8/Modules/FindSubversion.cmake
/usr/share/cmake-2.8/Modules/FindSWIG.cmake
/usr/share/cmake-2.8/Modules/FindTCL.cmake
/usr/share/cmake-2.8/Modules/FindTclsh.cmake
/usr/share/cmake-2.8/Modules/FindTclStub.cmake
/usr/share/cmake-2.8/Modules/FindThreads.cmake
/usr/share/cmake-2.8/Modules/FindTIFF.cmake
/usr/share/cmake-2.8/Modules/FindUnixCommands.cmake
/usr/share/cmake-2.8/Modules/FindVTK.cmake
/usr/share/cmake-2.8/Modules/FindWget.cmake
/usr/share/cmake-2.8/Modules/FindWish.cmake
/usr/share/cmake-2.8/Modules/FindwxWidgets.cmake
/usr/share/cmake-2.8/Modules/FindwxWindows.cmake
/usr/share/cmake-2.8/Modules/FindX11.cmake
/usr/share/cmake-2.8/Modules/FindXMLRPC.cmake
/usr/share/cmake-2.8/Modules/FindZLIB.cmake

上面这些模块文件可以被 find_package 命令或 include 命令加入到当前工程中。
比如,下面三个命令功能相同:

find_package(OpenGL)
include(${CMAKE_ROOT}/Modules/FindOpenGL.cmake)    # 这个命令和上面命令作用相同
include(FindOpenGL)                                # 这个命令和上面命令作用相同
3.1.2.1 Find<package>.cmake中的常见变量(“<package>_INCLUDE_DIR”等)

一般每个“Find<package>.cmake”文件中会定义了一些变量,这些变量在执行完find_package或include后可以直接使用(如果找到了对应的库)。如FindOpenGL.cmake文件中定义了(如果找到对应的库)“OPENGL_FOUND”,“OPENGL_INCLUDE_DIR”,“OPENGL_LIBRARIES”等等变量。一般地,这些变量在文件FindOpenGL.cmake前几行的注释中会有说明。比如,FindOpenGL.cmake前几行内容如下:

#.rst:
# FindOpenGL
# ----------
#
# FindModule for OpenGL and GLU.
#
# Result Variables
# ^^^^^^^^^^^^^^^^
#
# This module sets the following variables:
#
# ``OPENGL_FOUND``
#  True, if the system has OpenGL.
# ``OPENGL_XMESA_FOUND``
#  True, if the system has XMESA.
# ``OPENGL_GLU_FOUND``
#  True, if the system has GLU.
# ``OPENGL_INCLUDE_DIR``
#  Path to the OpenGL include directory.
# ``OPENGL_LIBRARIES``
#  Paths to the OpenGL and GLU libraries.
Table 1: Find<package>.cmake文件中定义的常见变量(不一样Find<package>.cmake都会定义它们)
变量名 说明
<package>_FOUND Set to false, or undefined, if we haven't found <package>.
<package>_INCLUDE_DIRS Where to find the package's header files, typically <package>.h, etc.
<package>_LIBRARIES The libraries to link against to use <package>. These include full paths.
<package>_DEFINITIONS Preprocessor definitions to use when compiling code that uses <package>.
<package>_EXECUTABLE Where to find the <package> tool that is part of the package.
<package>_<tool>_EXECUTABLE Where to find the <tool> tool that comes with <package>.

说明:上面变量中的<package>一般全为大写,比如FindOpenGL.cmake中定义变量OPENGL_FOUND,而不是变量OpenGL_FOUND。

3.2 向C/C++编译器添加“definition flags” (add_definitions)

Command add_definitions will add your definitions to your compiler command line, e.g. -D with gcc, or /D with MSVC.

3.2.1 更精细地添加“definition flags” (COMPILE_DEFINITIONS属性)

如果想更精细地(比如只对某个源文件)添加“definition flags”,则可以使用COMPILE_DEFINITIONS属性。

For example, the code:

add_library (mylib src1.c src2.c)
add_executable (myexe main1.c)
set_property (
  DIRECTORY
  PROPERTY COMPILE_DEFINITIONS A AV=1
  )

set_property (
  TARGET mylib
  PROPERTY COMPILE_DEFINITIONS B BV=2
  )

set_property (
  SOURCE src1.c
  PROPERTY COMPILE_DEFINITIONS C CV=3
  )

will build the source files with these definitions:

src1.c:     -DA -DAV=1 -DB -DBV=2 -DC -DCV=3
src2.c:     -DA -DAV=1 -DB -DBV=2
main2.c:    -DA -DAV=1

3.3 Configure Header File (configure_file)

使用命令 configure_file 可以把输入文件修改(变量展开或替换)后复制为另外一个文件(称为输出文件)。这可以用来配置头文件。
configure_file 命令的基本语法为:

configure_file(<input> <output>
               [COPYONLY] [ESCAPE_QUOTES] [@ONLY]
               [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

输入文件中有“三种方式”可以来指定一个想要替换的变量。
替换方式一:输入文件中的下面内容:

#cmakedefine VARIABLE

当变量VARIABLE是true时,会被替换为:

#define VARIABLE

否则,会被替换为:

/* #undef VARIABLE */

替换方式二:输入文件中的下面内容会被替换为变量VARIABLE的值。

@VARIABLE@

替换方式三:输入文件中的下面内容会被替换为变量VARIABLE的值(由于下面形式可能出现在其它程序语言中,用configure_file的选项 @ONLY 可以禁止这个替换,以避免错误的替换)。

${VARIABLE}

参考: cmake --help-command configure_file

3.3.1 configure_file使用实例

假设输入文件PrjConf.h.in为:

#define VERSION @MY_VERSION@

#cmakedefine HAVE_USLEEP 1
#cmakedefine HAVE_UTIME 1

假设CMakeLists.txt为:

include (CheckFunctionExists)              # 引入函数 check_function_exists (它可以检测C函数是否在系统中存在)
check_function_exists(usleep HAVE_USLEEP)  # 如果 usleep 存在,会
check_function_exists(utime HAVE_UTIME)

set(MY_VERSION 1.3)
configure_file (
  ${PROJECT_SOURCE_DIR}/PrjConf.h.in
  ${PROJECT_BINARY_DIR}/PrjConf.h
  )

则执行完cmake后,输出文件PrjConf.h会为(如果系统中存在usleep, utime函数):

#define VERSION 1.3

#define HAVE_USLEEP 1
#define HAVE_UTIME 1

4 Custom Commands and Targets

有时,我们想要在编译完成前后进行一些其它任务,如调用lex/yacc进行处理、调用第三方工具进行文档生成等。CMake提供了机制来完成这些任务。

4.1 可移植的命令(cmake -E)

When the cmake executable is passed the -E option it acts as a general purpose cross-platform utility command.

这时,cmake的调用语法为:

cmake -E <command> [<options>...]

可用的<command>及<options>可以通过命令 cmake -E 列出,如下:

$ cmake -E
CMake Error: cmake version 3.4.1
Usage: cmake -E [command] [arguments ...]
Available commands:
  chdir dir cmd [args]...   - run command in a given directory
  compare_files file1 file2 - check if file1 is same as file2
  copy file destination     - copy file to destination (either file or directory)
  copy_directory source destination   - copy directory 'source' content to directory 'destination'
  copy_if_different in-file out-file  - copy file if input has changed
  echo [string]...          - displays arguments as text
  echo_append [string]...   - displays arguments as text but no new line
  env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...
                            - run command in a modified environment
  environment               - display the current environment
  make_directory dir        - create a directory
  md5sum file1 [...]        - compute md5sum of files
  remove [-f] file1 file2 ... - remove the file(s), use -f to force it
  remove_directory dir      - remove a directory and its contents
  rename oldname newname    - rename a file or directory (on one volume)
  tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
                            - create or extract a tar or zip archive
  sleep <number>...         - sleep for given number of seconds
  time command [args] ...   - run command and return elapsed time
  touch file                - touch a file.
  touch_nocreate file       - touch a file but do not create it.
Available on UNIX only:
  create_symlink old new    - create a symbolic link new -> old

运行实例:

$ cmake -E echo hello cmake
hello cmake

在CMakeLists文件中,可以通过 ${CMAKE_COMMAND} 来引用cmake可执行程序。

4.2 用命令add_custom_command按编译事件执行指定任务

命令 add_custom_command 会在生成的Makefile中增加一个规则,它两种主要用法,一种是用来生成文件(这里不介绍,后文将介绍);另一种是根据编译事件执行指定任务。

用命令 add_custom_command 根据编译事件执行指定任务的格式为:

add_custom_command(TARGET target
                   PRE_BUILD | PRE_LINK | POST_BUILD
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [BYPRODUCTS [files...]]
                   [WORKING_DIRECTORY dir]
                   [COMMENT comment]
                   [VERBATIM] [USES_TERMINAL])

实例:

add_executable(Foo bar.c)

# get where the executable will be located
get_target_property(EXEC_LOC Foo LOCATION)

# then add the costom command to copy it
add_custom_command (
  TARGET Foo
  POST_BUILD
  COMMAND ${CMAKE_COMMAND}
  ARGS -E copy ${EXEC_LOC} /testing_department/files
  )

在上面实例中,用bar.c生成可执行程序Foo,当它编译完成后,通过调用 “cmake -E copy”命令把它复制到目录/testing_department/files中。

4.3 用命令add_custom_command生成文件

用命令 add_custom_command 生成文件时,用法如下:

add_custom_command(OUTPUT output1 [output2 ...]
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [MAIN_DEPENDENCY depend]
                   [DEPENDS [depends...]]
                   [BYPRODUCTS [files...]]
                   [IMPLICIT_DEPENDS <lang1> depend1
                                    [<lang2> depend2] ...]
                   [WORKING_DIRECTORY dir]
                   [COMMENT comment]
                   [VERBATIM] [APPEND] [USES_TERMINAL])

add_custom_command 生成文件的实例:

add_executable(creator creator.cxx)

get_target_property(creator EXE_LOC LOCATION)

# add the custom command to produce created.c
add_custom_command (
  OUTPUT ${PROJECT_BINARY_DIR}/created.c
  DEPENDS creator
  COMMAND ${EXE_LOC}
  ARGS ${PROJECT_BINARY_DIR}/created.c
  )

add_executable(Foo ${PROJECT_BINARY_DIR}/created.c)

在上面实例中,首先用creator.cxx生成可执行程序creator,然后调用creator生成created.c,最后created.c会编译为可执行程序Foo。

4.4 用命令add_custom_target增加定制目标

命令 add_custom_target 可以增加定制目标,常常用于编译文档、运行测试用例、发布网站等工作。它的语法为:

add_custom_target(Name [ALL] [command1 [args1...]]
                  [COMMAND command2 [args2...] ...]
                  [DEPENDS depend depend depend ... ]
                  [BYPRODUCTS [files...]]
                  [WORKING_DIRECTORY dir]
                  [COMMENT comment]
                  [VERBATIM] [USES_TERMINAL]
                  [SOURCES src1 [src2...]])

Adds a target with the given name that executes the given commands. The target has no output file and is always considered out of date

这个增加的目标总是“out of date(过时的)”,总会被执行。

add_custom_target实例:

# set the list of documents to process
set (DOCS doc1 doc2 doc3)

# add the custom commands for each document
foreach (DOC ${DOCS})

  # add the rule to build .pdf from the .tex file
  # This relies on LATEX and DVIPDF being set correctly
  add_custom_command (
    OUTPUT  ${PROJECT_BINARY_DIR}/${DOC}.pdf
    DEPENDS ${PROJECT_SOURCE_DIR}/${DOC}.tex
    COMMAND ${LATEX}
    ARGS    ${PROJECT_SOURCE_DIR}/${DOC}.tex    # 先由 .tex 生成 .dvi
    COMMAND ${DVIPDF}
    ARGS    ${PROJECT_SOURCE_DIR}/${DOC}.dvi    # 再由 .dvi 生成 .pdf

  # build a list of all the results
  set (DOC_RESULTS ${DOC_RESULTS} ${PROJECT_BINARY_DIR}/${DOC}.pdf)

endforeach()

# finally add the custom target that when invoked will
# cause the generaton of the pdf file
add_custom_target (TDocument ALL
  DEPENDS ${DOC_RESULTS}
  )

这个例子,增加了一个名为TDocument的目标,目录的依赖是所有的pdf文件,规则是由add_custom_command命令定义的。

5 Tips

5.1 make VERBOSE=1

生成Makefile后,运行make时,如果想得到更详细的输出(往往用于调试),可以指定“VERBOSE=1”,即这样运行make:

make VERBOSE=1

Author: cig01

Created: <2015-07-12 Sun 00:00>

Last updated: <2017-12-10 Sun 21:34>

Creator: Emacs 25.3.1 (Org mode 9.1.4)