为何须要CMake
假如你仍然在windows平台上开发,使用最多的可能就是VS的开发环境,它早已集成了全套的开发环境包括建立编译等。你似乎听过好几种Make工具,比如GNUMake,QT的qmake,谷歌的MSnmake,BSDMake(pmake),Makepp,等等。这种Make工具遵守着不同的规范和标准,所执行的Makefile格式也千差万别。这样就带来了一个严峻的问题:假若软件想跨平台,必需要保证就能在不同平台编译。而假如使用前面的Make工具,就得为每一种标准写一次Makefile,这将是一件让人费解的工作。
CMake就是针对里面问题所设计的工具:它首先容许开发者编撰一种平台无关的CMakeList.txt文件来订制整个编译流程,之后再依据目标用户的平台进一步生成所需的本地化Makefile和工程文件,如Unix的Makefile或Windows的VisualStudio工程。进而做到"Writeonce,runeverywhere”。其实,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,只须要使用sudoln-s/usr/bin/make/usr/bin/gmake,之后
gmake
9、进行安装
sudo gmake install
10、安装完成,查看cmake版本号,假如输出版本号,则安装成功。
cmake --version
入门:单个源文件
简单的项目,只须要单个源文件就可以。假如现今有一个main.cpp,内容如下:
#include
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
#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源文件linux cmake安装教程,即使这样写没有问题,然而当源文件好多时,把所有的源文件加入是很繁杂的工作。
我们可以使用aux_source_directory命令,该命令会查找指定目录下所有的源文件,并将结果存在指定的变量名中,如下:
aux_source_directory( )
为此,更改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
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
void my_print(int a)
{
printf("%d", a);
}
main.cpp的文件内容:
#include "math/MyMath.h"
#include "MyPrint.h"
#include
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
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
#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文件linux cmake安装教程,其内容如前面代码所示,假如option的USE_MYMATH为ON,生成的config.h中定义的内容是#defineUSE_MYMATH,倘若为OFF,生成的config.h中定义的内容是/*#undefUSE_MYMATH*/。
还可以交互式的选择该变量的值,使用ccmake命令,也可以使用cmake-i命令,该命令会提供一个会话式的交互式配置界面,从中可以找到定义的USE_MYMATH选项,按按键的方向键可以在不同的选项窗口间跳转,按下enter键可以更改该选项。更改完成后可以按下c选项完成配置linux服务器搭建,然后再按g键确认生成Makefile。ccmake的其他操作可以参考其窗口下方给出的指令提示。
订制安装规则&测试
CMake可以指定安装规则,以及添加测试。这两个功能分别可以通过在形成Makefile后使用makeinstall来执行。在先前的GNUMakefile中,你可能须要因此编撰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的主函数更改一下linux命令行,使用传入的参数:
#include "math/MyMath.h"
#include "config.h"
#include
#include
#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的更详尽文档可以用过命令man1ctest参考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使用教程