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%20Practice.pdf
CMake Documentation: https://cmake.org/cmake/help/latest/
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 作为编译目录:

  1. 进入到 build 子目录中运行命令 cd build; cmake ..
  2. 通过 -B 指定编译目录,如在当前目录中执行 cmake -Bbuild

这样,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(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

参数 EXCLUDE_FROM_ALL 表示 subdirectory 中的所有 target 排除在上级目录的 all target 列表之外,这样当执行默认的 make 时,这个 subdirectory 中的所有 target 不会被编译。

总结: add_subdirectory 命令的作用是向当前工程添加存放源文件的子目录,并可以指定生成二进制文件的存放位置。 其说明文档可参考: cmake --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 中指定了 SHARED,作用是把 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. Installing Files

CMake 提供了 install 命令,用于指定如何安装项目的文件。如何生成目标是 Makefile,则执行 make install 时对应执行的就是 CMakeLists.txt 中 install 命令。

install 命令有下面几种形式:

install(TARGETS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])

它们的用途如下:

  • install(TARGETS ...): Install the binary files corresponding to targets built inside the project.
  • install(FILES ...): General-purpose file installation. It is typically used for installation of header files, documentation, and data files required by your software.
  • install(PROGRAMS ...): Installs executable files not built by the project, such as shell scripts. It is identical to install (FILES) except that the default permissions of the installed file include the executable bit.
  • install(DIRECTORY ...): Install an entire directory tree. This may be used for installing directories with resources such as icons and images.
  • install(SCRIPT ...): Specify a user-provided Cmake script file to be executed during installation. Typically this is used to define pre-install or post-install actions for other rules.
  • install(CODE ...): Specify user-provided Cmake code to be executed during the installation. This is similar to install (SCRIPT) but the code is provided inline in the call as a string.
  • install(EXPORT ...): Generates and installs a CMake file containing code to import targets from the installation tree into another project.

2.5. CMake 命令列表

CMakeLists.txt 文件包含一系列命令来描述需要执行的任务。执行 cmake --help-command-list 可以查看 cmake 所支持的命令列表:

$ cmake --help-command-list
add_compile_definitions
add_compile_options
add_custom_command
add_custom_target
add_definitions
add_dependencies
add_executable
add_library
add_link_options
add_subdirectory
add_test
aux_source_directory
break
build_command
build_name
cmake_host_system_information
cmake_minimum_required
cmake_parse_arguments
cmake_policy
configure_file
continue
create_test_sourcelist
ctest_build
ctest_configure
ctest_coverage
ctest_empty_binary_directory
ctest_memcheck
ctest_read_custom_files
ctest_run_script
ctest_sleep
ctest_start
ctest_submit
ctest_test
ctest_update
ctest_upload
define_property
else
elseif
enable_language
enable_testing
endforeach
endfunction
endif
endmacro
endwhile
exec_program
execute_process
export
export_library_dependencies
file
find_file
find_library
find_package
find_path
find_program
fltk_wrap_ui
foreach
function
get_cmake_property
get_directory_property
get_filename_component
get_property
get_source_file_property
get_target_property
get_test_property
if
include
include_directories
include_external_msproject
include_guard
include_regular_expression
install
install_files
install_programs
install_targets
link_directories
link_libraries
list
load_cache
load_command
macro
make_directory
mark_as_advanced
math
message
option
output_required_files
project
qt_wrap_cpp
qt_wrap_ui
remove
remove_definitions
return
separate_arguments
set
set_directory_properties
set_property
set_source_files_properties
set_target_properties
set_tests_properties
site_name
source_group
string
subdir_depends
subdirs
target_compile_definitions
target_compile_features
target_compile_options
target_include_directories
target_link_directories
target_link_libraries
target_link_options
target_precompile_headers
target_sources
try_compile
try_run
unset
use_mangled_mesa
utility_source
variable_requires
variable_watch
while
write_file

完整的命令列表可以参考:https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html

2.6. 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``).

完整的内置变量列表可以参考:https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html

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_DIRS”(或者“<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”:

  1. “Module mode”是指 find_package 查找名为“Find<package>.cmake”的文件,并加载它。 文件“Find<package>.cmake”的查找位置是 CMAKE_MODULE_PATH 指定的位置和 CMake 安装位置。
  2. “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” (set_property)

如果想更精细地(比如只对某个源文件)添加“definition flags”,则可以使用 set_property 设置 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 可以把 input 文件中的变量进行展开或替换后复制为另外一个 output 文件。这往往用来配置头文件。 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>

Last updated: <2020-01-04 Sat>

Creator: Emacs 27.1 (Org mode 9.4)