CMake使用教程
Posted crazyang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CMake使用教程相关的知识,希望对你有一定的参考价值。
为什么需要CMake
如果你一直在windows平台上开发,使用最多的可能就是VS的开发环境,它已经集成了全套的开发环境包括构建编译等。你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到"Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。
Linux 下安装CMake
1、安装gcc等必备程序包(已安装则略过此步)
yum install -y gcc gcc-c++ make automake
2、安装wget (已安装则略过此步)
yum install -y wget
3、安装OpenSSL
yum install openssl
yum install openssl-devel
4、获取CMake源码包
wget https://cmake.org/files/v3.19/cmake-3.19.0-rc1.tar.gz
5、解压CMake源码包
tar -zxvf cmake-3.19.0-rc1.tar.gz
6、进入cmark的源码目录
cd cmake-3.19.0-rc1
7、运行当前目录下的一个文件
./bootstrap
8、运行gmake
命令(这步时间有点长),如果安装了make却提示找不到gmake,只需要使用sudo ln -s /usr/bin/make /usr/bin/gmake
,然后
gmake
9、进行安装
sudo gmake install
10、 安装完成,查看cmake版本号,如果输出版本号,则安装成功。
cmake --version
入门:单个源文件
简单的项目,只需要单个源文件就可以。如果现在有一个main.cpp
,内容如下:
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
cout << "Hello CMake!" << endl;
return 0;
}
我们在同级目录下新建一个CMakelist.txt
,其内容如下:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.19)
# 项目名称
project(demo1)
# 指定生成目标,将main.cpp生成一个名为demo1的可以执行文件
add_executable(demo1 main.cpp)
需要注意的是,CMake是不区分大小写的;#
开头该行会被视为注释;命令由命令名称、小括号、参数组成;参数之间用空格间隔。
CMake编译运行
现在我们在当前目录中使用cmake .
执行,可以观察到多了几个和CMake相关的文件,如下:
CMakeCache.txt CMakeFiles CMakelists.txt Makefile cmake_install.cmake demo1 main.cpp
其中有一个文件是Makefile,再用make
命令编译就得到demo1可执行文件。
- 直接生成二进制文件
还可以直接调用CMake构建系统以实际编译/链接项目
cmake --build .
可以看到生成了可执行二进制文件。
同目录多个源文件
实际项目中在一个目录中会有很多个源文件,现在开始编写一个目录中,多个源文件情况:
├── CMakelists.txt
├── MyMath.cpp
├── MyMath.h
└── main.cpp
MyMath.h
文件内容如下:
#pragma once
int my_add(int a, int b);
MyMath.cpp
文件内容如下:
#include "MyMath.h"
int my_add(int a, int b)
{
return a + b;
}
main.cpp文件内容如下:
#include <iostream>
#include "MyMath.h"
using namespace std;
int main(int argc, char **argv)
{
cout << my_add(1, 2) << endl;
return 0;
}
CMakelists.txt文件内容如下:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.19)
# 项目名称
project(demo2)
# 指定生成目标,将main.cpp,MyMath.cpp生成一个名为demo2的可以执行文件
add_executable(demo2 main.cpp MyMath.cpp)
和单个文件类似,仅仅只在add_executable
中添加了一个MyMath.cpp
源文件,虽然这样写没有问题,但是当源文件很多时,把所有的源文件加入是很繁琐的工作。
我们可以使用aux_source_directory命令,该命令会查找指定目录下所有的源文件,并将结果存在指定的变量名中,如下:
aux_source_directory(<dir> <variable>)
因此,修改CMakeList为如下:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.19)
# 项目名称
project(demo2)
# 将当前目录所有源文件保存在变量DIR_SRCS中
aux_source_directory(. DIR_SRCS)
# 指定生成目标,将DIR_SRCS变量的所有源文件生成一个名为demo2的可以执行文件
add_executable(demo2 ${DIR_SRCS})
多目录&多源文件
现在我们的项目中不仅有多个源文件,还有一些其他的库目录,比如我们要使用一个自己的math目录,其文件结构如下:
├── CMakelists.txt
├── MyPrint.cpp
├── MyPrint.h
├── main.cpp
└── math
├── CMakelists.txt
├── MyMath.cpp
└── MyMath.h
- 子目录math下文件内容
MyPrint.h
的文件内容:
#pragma once
int my_add(int a, int b);
MyPrint.cpp
的文件内容:
#include "MyMath.h"
int my_add(int a, int b)
{
return a + b;
}
CMakelists.txt
的文件内容:
# 将当前目录所有源文件保存在变量DIR_LIB_SRCS中
aux_source_directory(. DIR_LIB_SRCS)
# 将DIR_LIB_SRCS变量中的所有源文件编译成静态链接库,链接库名为MyMath
add_library (MyMath ${DIR_LIB_SRCS})
- 主目录下文件内容
MyPrint.h
的文件内容:
#pragma once
void my_print(int a);
MyPrint.cpp
的文件内容:
#include "MyPrint.h"
#include <stdio.h>
void my_print(int a)
{
printf("%d", a);
}
main.cpp
的文件内容:
#include "math/MyMath.h"
#include "MyPrint.h"
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
my_print(my_add(1, 2));
return 0;
}
CMakelists.txt
的文件内容:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.19)
# 项目名称
project(demo3)
# 将当前目录所有源文件保存在变量DIR_SRCS中
aux_source_directory(. DIR_SRCS)
# 添加子目录
add_subdirectory(math)
# 指定生成目标,将DIR_SRCS变量的所有源文件生成一个名为demo2的可以执行文件
add_executable(demo3 ${DIR_SRCS})
# 添加链接库,MyMath是子目录中的链接库名
target_link_libraries(demo3 MyMath)
add_subdirectory
指明本项目包含一个子目录math,这样math下的CMakelists.txt文件和源代码也会被处理。
target_link_libraries
指明可执行文件 demo3需要链接一个名为MyMath的链接库。
add_library
将某个目录中的源文件编译为静态链接库。
自定义编译选项
CMake可以为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。
例如,可以将MyMath库设计为一个可选库,如果该选项为ON,就是用该库中定义的数学函数来进行宏运算。否则就调用标准库中的数学函数库。
现在用abs函数来举例,在MyMath库中重新定义一个my_abs函数,当前文件目录如下:
├── CMakelists.txt
├── config.h.in
├── main.cpp
└── math
├── CMakelists.txt
├── MyMath.cpp
└── MyMath.h
- 子目录math下文件内容
MyMath.h
的文件内容:
#pragma once
int my_abs(int a);
MyMath.cpp
的文件内容:
#include "MyMath.h"
int my_abs(int a)
{
return a >= 0 ? a : -a;
}
CMakelists.txt
的文件内容:
# 将当前目录所有源文件保存在变量DIR_LIB_SRCS中
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库名为MyMath
add_library (MyMath ${DIR_LIB_SRCS})
- 主目录下文件内容
config.h.in
的文件内容:
#cmakedefine USE_MYMATH
main.cpp
的文件内容:
#include "math/MyMath.h"
#include "config.h"
#include <stdio.h>
#ifdef USE_MYMATH
#include "math/MyMath.h"
#else
#include "math.h"
#endif
int main(int argc, char **argv)
{
#ifdef USE_MYMATH
printf("my abs: %d", my_abs(-1));
#else
printf("std abs: %d", abs(-1));
#endif
return 0;
}
CMakelists.txt
的文件内容:
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.19)
# 项目名称
project(demo4)
# 加入一个配置头文件,用于处理CMake对源码的设置
configure_file(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
# 是否使用自己的MyMath库
option(USE_MYMATH "Use MyMath" ON)
# 是否加入MyMath库
if(USE_MYMATH)
include_directories ("${PROJECT_SOURCE_DIR}/math")
add_subdirectory (math)
list(APPEND EXTRA_LIBS MyMath)
list(APPEND EXTRA_INCLUDE "${PROJECT_SOURCE_DIR}/math")
endif(USE_MYMATH)
# 将当前目录所有源文件保存在变量DIR_SRCS中
aux_source_directory(. DIR_SRCS)
# 指定生成目标,将DIR_SRCS变量的所有源文件生成一个名为demo2的可以执行文件
add_executable(demo4 ${DIR_SRCS})
# 添加链接库,MyMath是子目录中的库的名字
target_link_libraries(demo4 PUBLIC ${EXTRA_LIBS})
# 添加二进制树到头文件搜索路径,才能发现config.h
target_include_directories(demo4 PUBLIC "${PROJECT_BINARY_DIR}" ${EXTRA_INCLUDE})
其中configure_file
命令将config.h.in生成config.h文件,通过这种机制,可以通过预定义一些参数和变量来控制代码的生成。
option
命令添加了一个USE_MYMATH
选项,并且设置为ON
。
根据 USE_MYMATH
变量的值来决定是否使用我们自己编写的MyMath库。
main.cpp中,主函数根据USE_MYMATH
的预定义值决定使用标准库还是MyMath库。
主函数中引入头文件#include "config.h"
,我们并不直接编写这个文件,而是从CMakeList.txt中配置的config.h.in文件来生成它,我们只需要编写config.h.in文件,其内容如上面代码所示,如果option的USE_MYMATH为ON
,生成的config.h中定义的内容是#define USE_MYMATH
,如果为OFF
,生成的config.h中定义的内容是/* #undef USE_MYMATH */
。
还可以交互式的选择该变量的值,使用ccmake
命令,也可以使用 cmake -i
命令,该命令会提供一个会话式的交互式配置界面,从中可以找到定义的 USE_MYMATH
选项,按键盘的方向键可以在不同的选项窗口间跳转,按下 enter
键可以修改该选项。修改完成后可以按下 c
选项完成配置,之后再按 g
键确认生成 Makefile 。ccmake 的其他操作可以参考其窗口下方给出的指令提示。
定制安装规则&测试
CMake可以指定安装规则,以及添加测试。这两个功能分别可以通过在产生Makefile后使用make install
来执行。在以前的GNU Makefile中,你可能需要为此编写install
和test
两个伪目标和相应的规则,但是在CMake里,这样的工作同样只需要几条命令。
为工程定制安装规则
还是使用上面demo4例子,首先在math/CMakelists.txt文件末尾添加下面几行:
# 指定MyMath库的安装路径
install(TARGETS MyMath DESTINATION bin)
install(FILES MyMath.h DESTINATION include)
用来指明MyMath库的安装路径。然后再修改根目录的CMakelists.txt文件,在末尾添加下面几行:
# 指定安装路径
install(TARGETS demo4 DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/config.h" DESTINATION include)
通过上卖弄的定制,生成的Demo文件和MyMath函数库libMyMath.a文件会被复制到/usr/local/bin
中,而MyMath.h和生成的config.h文件则会被复制到/usr/local/include
中。我们可以验证一下(注意
:这里的/usr/local/
是默认安装到的根目录,可以通过修改CMAKE_INSTALL_PREFIX
变量的值来指定这些文件应该拷贝到哪个根目录)
[crazyang@zt demo4]$ sudo make install
[ 50%] Built target MyMath
[100%] Built target demo4
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/libMyMath.a
-- Installing: /usr/local/include/MyMath.h
-- Installing: /usr/local/bin/demo4
-- Installing: /usr/local/include/config.h
[crazyang@zt demo4]$ ls /usr/local/bin/
demo4 libMyMath.a
[crazyang@zt demo4]$ ls /usr/local/include/
MyMath.h config.h
为工程添加测试
添加测试也比较简单。CMake提供了一个称为CTest的测试工具。我们要做的只是在项目根目录的CMakelists文件中调用一系列的add_test
命令。
还是使用demo4举例子,但是为了方便演示,将demo4的主函数修改一下,使用传入的参数:
#include "math/MyMath.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef USE_MYMATH
#include "math/MyMath.h"
#else
#include "math.h"
#endif
int main(int argc, char **argv)
{
if (argc < 2)
{
printf(Usage:);
return 1;
}
#ifdef USE_MYMATH
printf("my abs: %d", my_abs(atoi(argv[1])));
#else
printf("std abs: %d", abs(atoi(argv[1])));
#endif
return 0;
}
在根目录的CMakelists.txt文件,在末尾添加下面几行:
# 启用测试
enable_testing()
# 测试程序是否成功运行
add_test(test_run demo4 -1)
# 测试帮助信息是否可以正常提示
add_test (test_usage demo4)
set_tests_properties (test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*")
# 测试-2的绝对值
add_test(test_-2 demo4 -2)
set_tests_properties (test_-2 PROPERTIES PASS_REGULAR_EXPRESSION "2")
# 测试5的绝对值
add_test(test_5 demo4 5)
set_tests_properties (test_5 PROPERTIES PASS_REGULAR_EXPRESSION "5")
上面的CMake包含了3个测试,test_run
用来测试程序是否成功并返回0值,括号中分别是测试名,二进制运行文件名,主函数传入的参数。剩下的两个测试分别用来测试-2的绝对值、5的绝对值是否能得到正确的结果。其中PASS_REGULAR_EXPRESSION
用来测试输出是否包含后面跟着的字符串。
测试的结果如下:
[crazyang@zt-2013412:/mnt/d/CODE/demo4]$ make test
Running tests...
Test project /mnt/d/CODE/demo4
Start 1: test_run
1/4 Test #1: test_run ......................... Passed 0.04 sec
Start 2: test_usage
2/4 Test #2: test_usage ....................... Passed 0.05 sec
Start 3: test_1
3/4 Test #3: test_1 ........................... Passed 0.04 sec
Start 4: test_2
4/4 Test #4: test_2 ........................... Passed 0.04 sec
100% tests passed, 0 tests failed out of 4
Total Test time (real) = 0.17 sec
如果测试太多数据,按上面那么写会非常繁琐。这是可以通过编写宏来实现:
# 定义一个宏,用来简化测试工作
macro(do_test arg1 result)
add_test(test_${arg1} demo4 ${arg1})
set_tests_properties(test_${arg1} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro(do_test arg1 result)
# 使用该宏进行一系列的数据测试
do_test(-1 "1")
do_test(-5 "5")
do_test(10 "10")
关于CTest的更详细文档可以用过命令man 1 ctest
参考CTest的文档。
支持gdb调试
使CMake支持gdb设置也很容易,只需要指定Debug模式下开启-g
选项:
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 来调试。
以上是关于CMake使用教程的主要内容,如果未能解决你的问题,请参考以下文章
B站视频教程笔记基于VSCode和CMake实现C/C++开发 | Linux篇(gcc/g++)(安装配置使用详细教程)(VSCode教程)(CMake教程)(精!)