CMake入门

前言

或许你已经见过 GUN make,
还有很多诸如 qmake, pmake, MS nmake, Makepp 等等,
但这些都有不同的标准和规范,导致需要在每个平台上都需要写一种makefile,很麻烦.
但是有个工具能够自动生成这些makefile文件,这就是 CMake

  • 需要开发者写的只是一个CMakeList.txt文件,该文件与平台无关
  • 该工具可以生成 makefile 文件,也可以生成Visual Studio工程文件
  • 然后做拥有 makefile 后该做的事–make
  • 一般后面还有安装

单个源文件

文件结构

  • main.c
  • CMakeLists.txt

文件内容

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
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>
/**
* power - Calculate the power of number.
* @param base: Base value.
* @param exponent: Exponent value.
*
* @return base raised to the power exponent.
*/
double power(double base, int exponent)
{
int result = base;
int i;
if (exponent == 0) {
return 1;
}
for(i = 1; i < exponent; ++i){
result = result * base;
}
return result;
}
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
1
2
3
4
5
6
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo1)
# 指定生成目标
add_executable(Demo main.c)

CMake使用#号作为注释符号

使用方法(下略)

  • cmake .
  • make
  • ./Demo1 2 3

多个源文件

文件目录

  • main.c
  • MathFunctions.c
  • MathFunctions.h
  • CMakeLists.txt

文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include "MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* power - Calculate the power of number.
* @param base: Base value.
* @param exponent: Exponent value.
*
* @return base raised to the power exponent.
*/
double power(double base, int exponent)
{
int result = base;
int i;
if (exponent == 0) {
return 1;
}
for(i = 1; i < exponent; ++i){
result = result * base;
}
return result;
}
1
2
3
4
#ifndef POWER_H
#define POWER_H
extern double power(double base, int exponent);
#endif

注意要定义头文件以使程序能够找到外部的函数

1
2
3
4
5
6
7
8
9
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})

注意新增的后两个用法,CMake有自动查找源文件的功能
引用变量的方法也是linux系统常用方法

新函数

  • aux_source_directory(<dir> <variable>)
    • 用于查找指定路径下的源文件列表并保存至指定变量

包含子目录

文件结构

  • main.c
  • CMakeLists.txt
  • math/
    • MathFunctions.c
    • MathFunctions.h
    • CMakeLists.c

文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include "math/MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
double result = power(base, exponent);
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}

注意这里自定义头文件的使用,是需要带路径的,不然找不到

1
2
3
4
5
6
7
8
9
10
11
12
13
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo3)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 添加 math 子目录
add_subdirectory(math)
# 指定生成目标
add_executable(Demo main.cc)
# 添加链接库
target_link_libraries(Demo MathFunctions)

添加math
子目录之后,math/Mathfunctions.txt文件与源文件也会被处理,并且应该会在父文件夹下生成库文件
生成目标与添加链接库是两回事,所以使用了两个函数
MathFunctions.c 与上一个例子相同
MathFunctions.h 与上一个例子也相同

1
2
3
4
5
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})

新函数

  • add_subdirectory(<dir>)
    • 增加子目录
  • target_link_libraries(<dst> <lib>)
    • 添加链接库
  • add_library (<lib> <src>)
    • 生成链接库
    • 作为对比, add_executable(<exe> <src>) 是生成最终的程序

增加编译选项

文件结构

  • main.c
  • config.h.in
  • CMakeLists.txt
  • math/
    • MathFunctions.c
    • MathFunctions.h
    • CMakeLists.txt

在使用编译选项时文件本身必须是有该功能的

文件内容

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 <stdio.h>
#include <stdlib.h>
#include <config.h>
#ifdef USE_MYMATH
#include <MathFunctions.h>
#else
#include <math.h>
#endif
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#ifdef USE_MYMATH
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#else
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
  • 其中 config.h 是由cmake根据 config.h.in 文件自动生成的,

主要功能是将 编译选项 转化为程序中使用的 变量

  • 这里也有了根据预编译参数而采取不同动作的代码
1
#cmakedefine USE_MYMATH
  • 顺便贴上生成的头文件 config.h
    • 选项打开时

      1
      #define USE_MYMATH
    • 选项关闭时

      1
      /* #undef USE_MYMATH */
  • 算是选项传递给程序的关键了吧
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
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo4)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# 加入一个配置头文件,用于处理 CMake 对源码的设置
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的 MathFunctions 库
option (USE_MYMATH
"Use provided math implementation" ON)
# 是否加入 MathFunctions 库
if (USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable (Demo ${DIR_SRCS})
target_link_libraries (Demo ${EXTRA_LIBS})
  • ${PROJECT_SOURCE_DIR} 不知道是什么时候定义的

MathFunctions.c 与上一个例子相同
MathFunctions.h 与上一个例子也相同
math/CMakeLists.txt 也与上一个例子相同

使用方法

  • ccmake .
  • 一开始什么都没有,使用=c=读取设置
  • 使用方向键移动,使用回车更改选项内容
  • 确定后再次按 c
  • 没有错误就显示 g
  • 按下=g=后开始生成=makefile=文件
  • make

新函数

  • configure_file(<configfile> <headfile>)
    • 设置配置用头文件是从哪里生成的
  • option(<OPTION> <msg> <ON/OFF>)
    • 为cmake添加可以设置的选项
  • if(<OPTION>)...endif(<OPTION>)
    • 定义选项相应的操作
  • include_directories ("<path>") 用于检测路径是否存在,不存在时能够在cmake界面显示错误信息
    • 添加路径的功能 add_subdirectory 已经有了,
      但是好像不负责报错,前面直接使用是因为作者本人确定吧
  • set()
    • 使用方法暂时不清楚,只知道是设置变量

定义安装规则

安装规则指库文件的放置位置,头文件放置位置等

文件结构

同上个例子

文件内容

仅不同的
CMakeLists.txt 中添加

1
2
3
4
# 指定安装路径
install (TARGETS Demo DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
DESTINATION include)

math/CMakeLists.txt 中添加

1
2
3
# 指定 MathFunctions 库的安装路径
install (TARGETS MathFunctions DESTINATION lib)
install (FILES MathFunctions.h DESTINATION include)

生成的 Demo 文件将会被复制到 /usr/local/bin
libMathFunctions.o 文件将会被复制到 /usr/local/lib
MathFunctions.h =和生成的 =config.h =文件会被复制到 =/usr/local/include

新函数

  • install(TARGETS/FILES <obj> DESTINATION <subdir>)
    • 设置安装对象的位置,其中 subdir 对应的父文件夹路径在使用ccmake时就能看到设置项
      CMAKE_INSTALL_PREFIX,默认使用的是 /usr/local

使用方法

  • 生成了 makefile 文件
  • 使用 sudo make install
    • 输出

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      > sudo make install
      [sudo] xxx 的密码:
      Scanning dependencies of target MathFunctions
      [ 25%] Building CXX object math/CMakeFiles/MathFunctions.dir/MathFunctions.cc.o
      [ 50%] Linking CXX static library libMathFunctions.a
      [ 50%] Built target MathFunctions
      Scanning dependencies of target Demo
      [ 75%] Building CXX object CMakeFiles/Demo.dir/main.cc.o
      [100%] Linking CXX executable Demo
      [100%] Built target Demo
      Install the project...
      -- Install configuration: ""
      -- Installing: /usr/local/bin/Demo
      -- Installing: /usr/local/include/config.h
      -- Installing: /usr/local/lib/libMathFunctions.a
      -- Installing: /usr/local/include/MathFunctions.h
    • 在安装时候动态库(.o)被改成了静态库(.a)

  • 然后在其他路径下就能使用=Demo=了

测试

CMake 提供了一个称为 CTest 的测试工具.
在根目录 CMakeLists.txt 文件中使用 add_test 命令即可

基础测试

  1. 文件目录

    同上个例子

  2. 文件内容

    在根目录 CMakeLists.txt 文件中添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 启用测试
    enable_testing()
    # 测试程序是否成功运行
    add_test (test_run Demo 5 2)
    # 测试帮助信息是否可以正常提示
    add_test (test_usage Demo)
    set_tests_properties (test_usage
    PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")
    # 测试 5 的平方
    add_test (test_5_2 Demo 5 2)
    set_tests_properties (test_5_2
    PROPERTIES PASS_REGULAR_EXPRESSION "is 25")
    # 测试 10 的 5 次方
    add_test (test_10_5 Demo 10 5)
    set_tests_properties (test_10_5
    PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")
    # 测试 2 的 10 次方
    add_test (test_2_10 Demo 2 10)
    set_tests_properties (test_2_10
    PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")
  3. 新命令

    • enable_testing()
      • 启用测试
    • add_test (<testName> <Function> [<args>])
      • 下面什么都写,有输出就是通过?
    • set_tests_properties (<testName> PROPERTIES PASS_REGULAR_EXPRESSION "<expression>")
      • 添加测试通过条件
  4. 使用方法

    • 生成 makefile 文件
    • 先生成程序本身,使用 make
    • 再使用测试命令 make test
      • 测试输出

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        > make test
        Running tests...
        Test project /path/to/Demo5
        Start 1: test_run
        1/5 Test #1: test_run ......................... Passed 0.00 sec
        Start 2: test_usage
        2/5 Test #2: test_usage ....................... Passed 0.00 sec
        Start 3: test_5_2
        3/5 Test #3: test_5_2 ......................... Passed 0.00 sec
        Start 4: test_10_5
        4/5 Test #4: test_10_5 ........................ Passed 0.00 sec
        Start 5: test_2_10
        5/5 Test #5: test_2_10 ........................ Passed 0.00 sec
        100% tests passed, 0 tests failed out of 5
        Total Test time (real) = 0.01 sec
      • 自动计数的

    • 关于 CTest 的更详细的用法可以通过 man 1 ctest 参考 CTest 的文档

使用宏测试

在要测试的数量多的情况下,可以使用宏来简化 CMakeLists.txt 文件的编写
在上述 CMakeLists.txt 文件中使用

1
2
3
4
5
6
7
8
9
10
# 定义一个宏,用来简化测试工作
macro (do_test arg1 arg2 result)
add_test (test_${arg1}_${arg2} Demo ${arg1} ${arg2})
set_tests_properties (test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# 使用该宏进行一系列的数据测试
do_test (5 2 "is 25")
do_test (10 5 "is 100000")
do_test (2 10 "is 1024")
  • 别看现在是复制粘贴,在没有鼠标,不能复制窗口内容的时代宏是非常有用的
  • 现在使用宏也能保持非常好的易读性
  1. 新命令

    • macro(<apiName <args>)...endmacro(<apiName>)
      • 定义宏,并且宏可以使用参数,在宏定义中使用=${}=调用参数

支持gdb

gdb是 GNU Debugger 的简称,可以使用命令行接口(CLI)进行众多错误检测等
让 CMake 支持 gdb
的设置也很容易,只需要指定 Debug 模式下开启 -g 选项:

1
2
3
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

之后可以直接对生成的程序使用 gdb 来调试。

添加环境检查

跨平台时最常见的应用场景了

文件结构

同上

文件内容

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 <stdio.h>
#include <stdlib.h>
#include <config.h>
#ifdef HAVE_POW
#include <math.h>
#else
#include <MathFunctions.h>
#endif
int main(int argc, char *argv[])
{
if (argc < 3){
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#ifdef HAVE_POW
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}

文件本身需要支持根据环境检测结果改变代码.
顶层的 CMakeLists.txt 文件中增加

1
2
3
# 检查系统是否支持 pow 函数
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (pow HAVE_POW)
  • 注意这部分应该写在 configure_file 命令前

配置文件 config.h.in 改成设置环境检查的结果

1
2
// does the platform provide pow function?
#cmakedefine HAVE_POW

新命令

  • include() 这里导入了后面的函数
  • check_function_exists (<funcName> <var>)
    • 将检测函数 <funcName> 存在与否并将结果存入 <var>

添加版本号

文件结构

同上

文件内容

只需要在顶层=CMakeLists.txt=文件中添加

1
2
set (Demo_VERSION_MAJOR 1)
set (Demo_VERSION_MINOR 0)

分别添加主版本号与副版本号

在程序中获取版本号信息

是通过 config.h.in 文件传递给 config.h 再在 main.c 文件中include实现的
config.h.in

1
2
3
// the configured options and settings for Tutorial
#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@

注意cmake变量在设置文件中的引用方法
main.c

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
27
28
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#include "math/MathFunctions.h"
int main(int argc, char *argv[])
{
if (argc < 3){
// print version info
printf("%s Version %d.%d\n",
argv[0],
Demo_VERSION_MAJOR,
Demo_VERSION_MINOR);
printf("Usage: %s base exponent \n", argv[0]);
return 1;
}
double base = atof(argv[1]);
int exponent = atoi(argv[2]);
#if defined (HAVE_POW)
printf("Now we use the standard library. \n");
double result = pow(base, exponent);
#else
printf("Now we use our own Math library. \n");
double result = power(base, exponent);
#endif
printf("%g ^ %d is %g\n", base, exponent, result);
return 0;
}
  1. 新命令

生成安装包

软件做出来给自己用还不够爽,给别人用才爽
这里使用了=CPack=,同样是CMake的一个工具
安装包有*二进制安装包*与*源码安装包*
发布软件需要附带证书,不然别人也不敢乱用

文件结构

  • main.c
  • config.h.in
  • CMakeLists.txt
  • License.txt
  • math/
    • MathFunctions.c
    • MathFunctions.h
    • CMakeLists.txt

文件内容

在顶层的=CMakeLists.txt=文件中增加

1
2
3
4
5
6
7
# 构建一个 CPack 安装包
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE
"${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)

说明

  • 先导入 InstallRequiredSystemLibraries 模块,以便之后导入 CPack
    模块;
  • 然后设置许可证信息
  • 再设置版本信息
  • 导入 CPack 模块

使用方法

  • 生成 makefile 文件
  • 生成安装文件
    • 二进制安装包

      1
      cpack -C CPackConfig.cmake
    • 生成源码安装包

      1
      cpack -C CPackSourceConfig.cmake
    • 生成的文件

      • name-v-v-linux.sh
      • name-v-v-linux.tar.gz
      • name-v-v-linux.gar.Z
    • 生成的文件内容相同

  • 安装(./xxx.sh)
    • 出现了CPack自动生成的安装页面
      • 显示证书
      • 提问与应答
  • 在路径下使用

关于 CPack 的更详细的用法可以通过 man 1 cpack 参考 CPack 的文档。

其他平台项目迁移到CMake

CMake
可以很轻松地构建出在适合各个平台执行的工程环境。而如果当前的工程环境不是
CMake ,而是基于某个特定的平台,是否可以迁移到 CMake
呢?答案是可能的。下面针对几个常用的平台,列出了它们对应的迁移方案。

autotools

qmake

Visual Studio

  • vcproj2cmake.rb 可以根据 Visual
    Studio 的工程文件(后缀名是 .vcproj 或 =.vcxproj=)生成
    CMakeLists.txt 文件。
  • vcproj2cmake.ps1 vcproj2cmake
    的 PowerShell 版本。
  • folders4cmake 根据
    Visual Studio 项目文件生成相应的 “source~group~”
    信息,这些信息可以很方便的在 CMake 脚本中使用。支持 Visual Studio 9/10
    工程文件。

CMakeLists.txt 自动推导

  • gencmake 根据现有文件推导
    CMakeLists.txt 文件。
  • CMakeListGenerator 应用一套文件和目录分析创建出完整的
    CMakeLists.txt 文件。仅支持 Win32 平台。

参考&引用

CMake入门实战