CMake 基本使用
Mastering CMake 这个也是官网文档,比官方教程内容更好理解。
CMakeLists.txt是cmake的工程配置文件,一般把CMakeLists.txt文件放在工程根目录,同时新建一个Build目录,所有生成的工程文件都放在Build目录中,清除工程文件时,直接删除Build目录中的内容。
基本步骤
- 给工程定义一个或多个CMakeLists.txt文件
- 使用cmake命令生成目标工程文件vcproject/makefile
- 使用工程文件编译工程
CMakeLists.txt
CMakeLists.txt
是cmake的主文件,其中定义兼容的最小版本,工程的基本信息.这个文件一般在工程根目录。
1 | # always first line |
生成目标工程
在工程的目录新建build目录,到build目录中执行cmake ..
生成工程文件。前两步也可以使用cmake自带的gui工具,linux平台依赖Curses进程名为ccmake。生成的工程文件会在build目录中,如果要清理工程,只需要把build目录清空即可。
1 | PS E:\code\rust\cargo_demo\src\build> cmake .. |
编译工程
在build目录中执行cmake --build .
编译当前生成的工程。生成的目标程序默认在Debug目录
1 | PS E:\code\rust\cargo_demo\src\build> cmake --build . |
CMake配置
编译器配置
编译器配置有三种方式,优先推荐Generator的方式
- 使用Generator
- 使用环境变量
- 使用cache entry
Generator
使用cmake -G
可以查看当前cmake支持的Generator。cmake会根据不同的Generator遵循对应的编译惯例
环境变量
CMAKE_C_COMPILER
指定C的编译器
CMAKE_CXX_COMPILER
指定C++的编译器
配置文件
使用配置文件可以让cmake根据配置生成一些配置头文件供工程的源程序代码使用,例如版本号信息
在工程根目录新建一个TestConfig.h.in
的配置文件,cmake会把工程配置文件中的变量替换配置文件中的变量
1 | // the configured options and settings for Test, |
cmake会在build目录生成TestCongfig.h
,所以如果代码中要使用这里定义的变量,需要把build目录添加到include的目录中。这三行是有顺序要求的。
1 | # configure a header file to pass some of the CMake settings to the source code |
自动生成的TestCongfig.h
头文件,
1 | // the configured options and settings for Test, |
可以在代码中使用这些宏或变量声明
1 |
|
使用依赖库
在库的源代码目录中新增库的CMakeLists.txt文件,其中INTERFACE说明库的使用者都要include库的源代码目录,有了这个INTERFACE的声明后,就可以不用在主程序的cmake中include库的源代码目录了
1 | # Add a library called FunLibs |
在应用的CMakeLists.txt文件中配置库的编译和引用,因为库声明了INTERFACE要求,所以这里不需要include库的目录了,只是说明要链接库FunLibs。
1 | if(USE_MYMATH) |
CMAKE生成宏
可以根据条件来指定工程使用系统库还是自定义的库,或者一些特殊的配置,类似条件编译
- 在cmake文件中使用
option
声明宏并定义宏的默认值 - 在配置文件
TestConfig.h.in
中增加一句#cmakedefine USE_MYMATH
,用来在配置头文件中生成宏,以便在代码中使用这个宏 - cmake的配置文件中,可以使用这个宏来决定是否使用一些配置
下面的例子声明了USE_MYMATH
宏,这个宏的默认是开,可以在cmakelists文件中使用,当这个宏开时,使用自己实现的库,而不用系统库。
同时配置文件中也会根据这里定义宏的值在TestConfig.h
来定义宏 #define USE_MYMATH
,
当不想配置这个宏时,可以在执行cmake .. -DUSE_MYMATH=OFF
关闭这个宏,这样生成的头文件中,USE_MYMATH
就是未定义状态/* #undef USE_MYMATH */
。
需要注意的是宏的值会CMakeCache.txt
被缓存,所以需要删除这个文件重新生成工程。
1 | # always first line |
c++程序
1 |
|
自定义命令
可以在编译完成后执行一些自定义的命令,例如在编译完成后,把生成的可执行文件拷贝到某个目录。这里的目录都需要使用绝对路径。
1 | add_custom_command( |
交叉编译
cmake默认都是编译native的工程,交叉编译其他平台的程序时,需要额外信息告诉cmake编译器和运行库等。
交叉编译中,执行编译系统称为Host,运行程序的系统称为Target
工具链配置
交叉编译需要指定交叉编译工具链,一般可以通过单独的一个toolchain文件说明目标程序的编译器,依赖库目录等。
例如创建一个toolchain.cmake
文件用来编译运行在RaspberryPi的程序。
1 | # the name of the target operating system |
指定编译器时最好用引号括起来,windows的目录需要使用/
不能使用\
会被解析为转义字符,这样这个工具链配置文件就固定生成给RaspberryPi使用的程序。工具链文件可以放在一个公共目录下,这样所有的工程都可以复用这个工具链配置
生成工程文件
1 | cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake -DCMAKE_BUILD_TYPE=Debug .. |
其中使用-DCMAKE_TOOLCHAIN_FILE
指定工具链文件,-G"Unix Makefiles"
说明生成makefile类型的工程
1 | E:\code\rust\cargo_demo\src\build_linux>cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE= |
生成makefile文件之后,可以在build_linux目录中执行cmake --build .
来生成最终的目标程序
1 | E:\code\rust\cargo_demo\src\build_linux>cmake --build . |
把生成的Test程序传到之前的RaspberryPi的虚拟机中可以正常执行。
1 | pi@raspberrypi:~ $ chmod +x Test |
单元测试
在CMakeLists.txt中可以配置单元测试,编译程序后执行ctest -C Debug -VV,对于MSVC需要指定测试的类型是Debug还是Release。对于GNU的,执行
ctest -N或
ctest -VV`,N选项简化输出,VV选项详细输出
add_test(NAME 用例名称 COMMAND 执行的命令和参数)
添加一个测试用例
还可以定义一个函数把测试的代码封装起来,下例中的do_test
函数,其中使用了正则表达式进行匹配结果
在CMakeLists.txt最后添加
1 | # enable testing |
输出如下
1 | PS E:\code\rust\cargo_demo\src\build> ctest -C Debug |