logo头像
Snippet 博客主题

CMake入门

概叙

你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。

CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeLists.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。


入门示例

这是一个对输入的数求平方根的示例,用编译配置开关控制是调用系统数学函数(sqrt)还是自定义函数(mysqrt)。主要包含设置版本号、配置头文件(可选项开关)、添加静态库的使用。

1. 目录结构

  • build目录自己创建,内容执行cmake命令自动生成
  • MathFunctions目录是自定义的数学函数库(会编译成静态库)
  • tuorial.cpp主入口文件

2. cmake命令使用

  • 配置环境

    测试环境:windows11

    需要安装CMake 和 MinGW,MinGW 就是 GCC 的 Windows 移植版本。

  • 构建命令,只需要第一次执行一次

    cmake -G”MinGW Makefiles” ..

    构建系统是需要指定 CMakeLists.txt 所在路径,此时在 build 目录下,所以用 .. 表示 CMakeLists.txt 在上一级目录。

    Windows 下,CMake 默认使用微软的 MSVC 作为编译器,我想使用 MinGW 编译器,可以通过 -G 参数来进行指定,只有第一次构建项目时需要指定。

    执行完会在 build 目录下会生成 Makefile 文件。

  • 编译、链接可执行程序

    cmake –build .

    –build 指定编译生成的文件存放目录,其中就包括可执行文件,. 表示存放到当前目录

    执行完会在 build 目录下生成一个 Tutorial.exe 可执行文件

  • 设置配置的可选项开关

    cmake -DUSE_MYMATH=OFF ..

    把USE_MYMATH设置为OFF,此设置将存储在缓存中,以便用户不需要在每次构建项目时设置该值。

3. 源码

所有细节看详细注释

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
34
35
36
37
38
39
40
41
42
// tutorial.cpp

#include <cmath>
#include <iostream>
#include <string>
#include "TutorialConfig.h"

// USE_MYMATH 编译开关
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

/*
argv[0] Tutorial.exe
argv[1] 外部输入第一个参数
*/
int main(int argc, char* argv[])
{
if (argc < 2) {
// 版本号使用示例
// Tutorial_VERSION_MAJOR 主版本号 Tutorial_VERSION_MINOR 次版本号
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 2;
}

// 将输入字符串转换为double
const double inputValue = std::stod(argv[1]);

// 编译开关控制调用系统sqrt还是自己的mysqrt
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif

std::cout << "The square root of " << inputValue
<< " is " << outputValue
<< std::endl;
return 0;
}
1
2
3
// MathFunctions.h

double mysqrt(double value);
1
2
3
4
5
6
7
8
// mysqrt.cpp

#include "MathFunctions.h"

double mysqrt(double value)
{
return 1.0;
}
1
2
3
4
5
6
7
8
// TutorialConfig.h.in

// 编译配置文件, 执行cmake --build . 会自定生成对应.h头文件

#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@ // 主版本号
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@ // 次版本号

#cmakedefine 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 外层主CMakeLists.txt

# 指定cmake最低版本
cmake_minimum_required(VERSION 3.15)

# 设置项目名称和版本号
# 这里项目名称会自定映射到 PROJECT_NAME 宏上
# 这里的版本号会自定映射到 Tutorial_VERSION_MAJOR=1 Tutorial_VERSION_MINOR=0 这两个宏上(TutorialConfig.h.in中配置)
project(Tutorial VERSION 1.0)

# 配置头文件将版本号传递给源代码, TutorialConfig.h.in编译时自定生成TutorialConfig.h需要这么配置下生效
configure_file(TutorialConfig.h.in TutorialConfig.h)

# 指定c++标准(现在新版本的cmake默认是c++14标准, 如果没有使用更高特性这里可以不用指定)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 可选项配置, USE_MYMATH定义在TutorialConfig.h.in配置中, 默认缺省值是 ON
# 修改:build目录下 cmake -DUSE_MYMATH=OFF ..
option(USE_MYMATH "Use tutorial provided math implementation" ON)

# 如果开了则使用自定义库,否则用系统的
if(USE_MYMATH)
add_subdirectory(MathFunctions) # 指定库所在目录, 此目录应包含库的CMakeLists.txt和代码文件
list(APPEND EXTRA_LIBS MathFunctions) # APPEND表示将元素MathFunctions追加到列表EXTRA_LIBS中
endif()

# 设置变量SRC_LIST的值tutorial.cpp(值可以多个空格隔开)
SET(SRC_LIST tutorial.cpp)

# 用来生成可执行文件,需要指定生成可执行文件的名称和相关源文件
add_executable(${PROJECT_NAME} ${SRC_LIST})

# 将库文件(静态库)添加到可执行文件中
target_link_libraries(${PROJECT_NAME} PUBLIC ${EXTRA_LIBS})

# 指定目标头文件目录
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
)
1
2
3
4
5
6
7
8
9
# 自定义库CMakeLists.txt

# 用来生成库文件(静态库),需要指定生成库文件的名称和相关源文件
add_library(MathFunctions mysqrt.cpp)

# 指定目标头文件目录
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)

4. 执行结果


进阶

1. 安装程序

程序写完,我们可以通过命令把可执行文件和头文件安装到指定目录。

接着上面工程,在两个CMakeLists.txt文件最后添加安装设置。

1
2
3
# 指定安装路径
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)
1
2
3
# 指定 MathFunctions 库的安装路径
install (TARGETS MathFunctions DESTINATION bin) # 目标文件
install (FILES MathFunctions.h DESTINATION include) # 头文件

执行命令:

cmake --install . --prefix "C:\Users\KL179\Desktop\cmake_test\install"

2. 测试程序

在根目录的CMakeLists.txt(主)最后添加测试用例。

测试用例主要就是两个接口:

add_test 添加用例

set_tests_properties 验证结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 启用测试
enable_testing()

# 测试程序是否成功运行
add_test (test_run Tutorial 8)

# 测试帮助信息是否可以正常提示
add_test (test_usage Tutorial)

# PASS_REGULAR_EXPRESSION 用来测试上面输出是否包含后面跟着的字符串(即测试结果验证)
set_tests_properties (test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")


# 定义一个宏,用来简化测试工作
macro (do_test arg1 result)
add_test (test_${arg1} Tutorial ${arg1})
set_tests_properties (test_${arg1} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)

# 使用该宏进行一系列的数据测试
do_test (1 "is 1")
do_test (4 "is 2")
do_test (16 "is 4")

运行ctest执行(或者make test)

3. 支持gdb配置

让 CMake 支持 gdb 的设置也很容易,只需要指定 Debug 模式下开启 -g 选项:

1
2
3
4
5
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")

4. 检测接口是否存在

一般是使用平台相关特性时,下面我们以检测系统是否有sqrt函数为例。

首先在主CMakeLists.txt文件里configure_file之前添加下面设置:

1
2
3
# 检查系统是否支持 sqrt 函数
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
check_function_exists (sqrt HAVE_SQRT)

接下来修改 TutorialConfig.h.in 文件,预定义HAVE_SQRT宏变量。

1
#cmakedefine HAVE_SQRT

最后就可以使用HAVE_SQRT来判断了。

1
2
3
4
5
6
7
// tutorial.cpp里添加测试示例

#ifdef HAVE_SQRT
std::cout << "check_function_exists sqrt is exist" << std::endl;
#else
std::cout << "check_function_exists sqrt is noexist" << std::endl;
#endif

5. 生成安装包

生成各种平台上的安装包,包括二进制安装包和源码安装包。为了完成这个任务,我们需要用到 CPack ,它同样也是由 CMake 提供的一个工具,专门用于打包。

注意:CPack打包依赖 NSIS 需要安装。

只需要在顶层的 CMakeLists.txt 文件尾部添加下面几行:

1
2
3
4
5
6
7
8
9
10
# 导入 InstallRequiredSystemLibraries 模块,以便之后导入 CPack 模块
include (InstallRequiredSystemLibraries)

# 设置一些 CPack 相关变量,包括版权信息和版本信息
set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")

# 导入 CPack 模块
include (CPack)

然后命令行执行cmake打包命令就可以打包了。

  • 生成二进制安装包:
1
cpack -C CPackConfig.cmake
  • 生成源码安装包
1
cpack -C CPackSourceConfig.cmake

生成的安装包,双击安装执行截图如下: