CMake进阶
CMake是什么
CMake是一种跨平台的自动化构建工具,可以生成跨平台的构建文件。比如Makefile、Visual Studio、XCode解决方案。使用CMake可以使得编译和构建过程更加简单。
CMake使用CMakeLists.txt文件描述项目的构建过程,通过一系列的命令来描述如何编译、链接和打包源代码。
CMake的优点
- 跨平台支持:CMake可以在windows、linux、macos等操作系统使用,并且可以支持多种编译器和构建工具。
- 灵活的构建过程:CMake的构建过程非常灵活,可以自定义编译选项、链接库等。
- 可拓展性:CMake可以使用自定义的模块拓展功能,可以方便的添加新的构建规则和命令。
- 高效的构建:支持多线程构建,可以加快构建速度。
语法
设置变量
可以使用set命令设置变量参数,变量可以包含字符串和列表,命令是不区分大小写的
1 | # 设置变量project为test |
引用变量
使用${}的方式引用定义的变量内容
1 | # 下面语句输出test |
条件语句
cmake中的条件语句可以使用if、elseif、endif
1 | # 下面代码根据CMAKE_SYSTEM_NAME判断不同的平台环境 |
循环语句
cmake中循环语句包含while,foreach/endforeach
函数和宏定义
cmake中使用function来定义函数,使用macro来定义宏。
函数和宏都可以用来封装一些操作或者功能,并且在需要的时候调用他们。存在以下区别:
- 参数传递方式不同:函数的参数是以变量的形式传递的,而宏的参数则是以文本替换的方式传递的。
- 变量作用域不同:函数中定义的变量只能在函数内部使用,而宏中定义的变量则可以在宏的调用位置和宏内部使用。
- 函数返回值类型只能为字符串:在CMake中,函数的返回值只能是字符串类型,而宏没有返回值。
- 函数可以使用return命令返回值:函数中可以使用return命令来返回一个字符串值,而宏没有此功能。
- 宏可以在其调用位置使用命令:宏可以在其调用位置执行一些命令,而函数则不能。
1 | function(func) |
注释
在cmake中行注释以 # 字符开始,直到行结束 。
块注释以 #[[ 开始 , 以 #]] 结束。
常用内置变量
CMAKE_SOURCE_DIR
项目源码根目录的绝对路径。
CMAKE_BINARY_DIR
CMake生成的Makefile和可执行文件等二进制文件所在目录的绝对路径。
CMAKE_CURRENT_SOURCE_DIR
当前处理的CMakeLists.txt所在的目录的绝对路径。
CMAKE_CURRENT_BINARY_DIR
当前处理的CMakeLists.txt生成的目标文件所在的目录的绝对路径。
CMAKE_INSTALL_PREFIX
安装目录的根目录。
CMAKE_CXX_COMPILER
C++编译器的完整路径。
CMAKE_C_COMPILER
C编译器的完整路径。
CMAKE_BUILD_TYPE
构建类型,通常为Debug或Release。
CMAKE_LIBRARY_OUTPUT_DIRECTORY
生成的动态库库文件输出目录。
CMAKE_RUNTIME_OUTPUT_DIRECTORY
生成的可执行文件输出目录。
CMAKE_ARCHIVE_OUTPUT_DIRECTORY
生成的静态链接库文件的输出目录
CMAKE_MODULE_PATH
指定一组目录列表,用于cmake在构建过程中去查找模块或者脚本,这些模块或者脚本可以提供一些常用的功能,用于拓展cmake的功能。
常用命令
add_executable
add_executable用来生成一个可执行文件的目标
1 | add_executable(target_name source_file1 [source_file2 ...]) |
其中,target_name是要生成的可执行文件的名称,source_file1、source_file2等是用于生成该可执行文件的源文件。
例如,如果我们有两个源文件main.cpp和helper.cpp,并且希望生成一个名为my_program的可执行文件,则可以在CMakeLists.txt文件中使用如下命令:
1 | add_executable(my_program main.cpp helper.cpp) |
add_library
add_library命令用来创建一个链接库目标。语法格式如下:
1 | add_library(target_name [STATIC | SHARED | MODULE] source1 [source2 ...]) |
target_name为要创建的目标的名称。STATIC表示生成静态链接库,SHARED参数表示生成动态链接库,MODULE表示动态加载模块
在C++开发中,我们一般需要告诉编辑器头文件和库文件的搜索路径(1. C/C++->附加包含目录 2. 链接器->常规->附加库目录和输入->附加依赖项)。对应cmake中就是下面这两个命令:[target_]include_directories,[target_]link_directories,注意带target_前缀的添加搜索路径只是针对当前目标库添加,而不带的是全局添加,后面所有库都会附加。
include_directories
include_directories用于向编译器添加一个或多个头文件搜索路径。该命令将指定的路径添加到编译器的搜索路径中,以便编译器可以找到指定路径中的头文件。命令语法如下:
1 | include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...]) |
link_directories
link_directories用于向链接器添加一个或多个库文件搜索路径。该命令将指定的路径添加到链接器的搜索路径中,以便链接器可以找到指定路径中的库文件
add_definitions
add_definitions用于向编译器添加定义,它可以将一个或者多个定义添加到所有源文件的编译器命令中,影响编译器的行为。
1 | add_definitions(-DFOO -DBAR ...) |
需要注意的是上面三个命令会影响所有的构建项目,包括子项目。如果想要只影响指定的项目,使用target前缀相关的命令。
target_include_directories
target_include_directories用于为一个目标添加头文件的搜索路径,可以添加一个或者多个目录,从而使编译器能找到头文件。
1 | target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] |
target_link_libraries
target_link_libraries用于为一个目标文件添加链接库。它可以向一个目标文件的链接器命令添加一个或者多个链接库。从而使编译器能够正确的链接到指定库。语法如下:
1 | target_link_libraries(<target> |
target_compile_definitions
target_compile_definitions用于为一个指定目标文件添加编译器定义。
1 | target_compile_definitions(<target> |
以上三个target相关的命令都包含了PRIVATE、PUBLIC、INTERFACE三个不同的访问控制级别。相关含义如下:
- PRIVATE 表示为当前target设置的头文件路径、链接库和定义只对当前target可见(不具有传染性)。
- PUBLIC 表示为当前target设置的相关属性可以被其他引用了该target的其他目标文件可见(具有传染性)。
include
用于在CMAKE_MODULE_PATH中加载指定的脚本或者模块文件,语法如下
1 | include(filename) |
cmake文件引入成功之后就可以使用文件中定义的函数、变量、宏等
find_package
用于在系统中查找已安装的库,并将其导入到CMake项目中。它通常用于在项目中引入第三方库。
find_package的语法如下:
1 | find_package(<package_name> [version] [EXACT] [QUIET] [MODULE] [REQUIRED] [COMPONENTS ...]) |
其中,
当find_package找到库时,会在CMAKE变量中设置一些有用的变量。比如
_FOUND:表示库是否已经找到。 _INCLUDE_DIRS:表示库头文件所在目录的列表。 _LIBRARIES:表示包含的链接库列表 find_package有两种搜索模式:1
2
3
4
5
6
7find_package(Boost 1.72.0 REQUIRED COMPONENTS filesystem system)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
add_executable(myapp main.cpp)
target_link_libraries(myapp ${Boost_LIBRARIES})
endif()- 模块模式(Module mode)
在该模式下,cmake会搜索一个名为Find.cmake的文件。搜索路径的顺序依次是CMAKE_MODULE_PATH指定的目录、cmake安装目录中查找。如果找到了相应的文件,那么就会读取并处理该文件。 - 配置模式(Config mode)
该模式下,CMake会搜索-config.cmake文件或 Config.cmake文件,我很少用。
帮助文档与示例
上面只是介绍了一些cmake常用、基本的操作。还有很多特性、命令我们都可以通过cmd下cmake --help去查询了解。
最后是一个正式项目的示例:这是一个windows平台的QT项目,通过vscode使用cmake + ninja + msvc构建、编译。
注:ninja与msvc(window平台编译器)关系类似make与gcc,前者是项目构建工具,后者是源码编译器。可以简单这么理解这个流程,cmake通过解析CMakeLists.txt构建工程,而解析操作则是由ninja完成的,最终编译代码则是用msvc完成。
ninja是谷歌的一个高效的构建工具,一般比vsstudio的快不少。
vscode环境搭建步骤:
安装QT4.8.7、vs2010、cmake(3.24.1)、vscode的cmake插件。
配置示例工程根目录的ninja到环境变量。
Ctrl+Shift+p依次执行命令:a. CMake:Configure,只需要执行一次,第一次会提示选择编译器(VisualStudio.10.0-x86)。
b. Cmake:Build,如果修改了代码重新Build不生效,可以先CMake:Clean,再CMake:Build。
c. 编译完就可以F5调试运行了。