CMake 是一种跨平台的构建系统生成工具,它使用一种特定的领域语言(DSL)来描述构建过程。CMake 语言主要用于编写 CMakeLists.txt 文件和 .cmake 脚本文件。下面我将按照您提出的方面详细介绍 CMake 语言的基础知识。
1. 基础数据类型
CMake 语言中的基础数据类型包括字符串(String)、数字(Number)和布尔值(Boolean)。
- 字符串:在 CMake 中,字符串可以用双引号括起来,但也可以不用引号。如果字符串中包含空格或特殊字符,则需要使用双引号。字符串可以通过
${}进行变量替换。
例如:
set(MY_STRING "Hello World") message(${MY_STRING})- 数字:数字在 CMake 中通常以字符串的形式表示,但在某些上下文中(如数学运算)会被解释为数字。
例如:
set(MY_NUMBER 42)- 布尔值:CMake 中的布尔值有
ON、OFF、YES、NO、TRUE、FALSE等。在条件判断中,这些值会被相应地解释为真或假。
例如:
set(MY_BOOL ON) if(MY_BOOL) message("It's true!") endif()2. 复杂数据结构类型
CMake 本身不支持像数组或字典这样的复杂数据结构,但可以通过列表(List)来模拟数组,以及通过将变量名组合成字符串来模拟字典。
- 列表:列表是由分号分隔的字符串组成的,可以用于存储多个值。CMake 提供了一些命令来操作列表,如
list。
例如:
set(MY_LIST "a;b;c") # 创建一个列表 list(APPEND MY_LIST "d") # 向列表追加元素 注意,列表在内部存储为字符串,其中元素用分号分隔。因此,如果一个元素本身包含分号,则需要转义。- 字典:CMake 没有内置的字典类型,但可以通过约定来模拟。例如,使用前缀相同的变量名来模拟键值对,或者使用两个列表来分别存储键和值。
3. 关键字和保留字
CMake 语言中有一些关键字和保留字,用于定义命令和控制流。例如:
if,else,elseif,endifforeach,endforeach,while,endwhilefunction,endfunction,macro,endmacroset,unset,option,find_package,add_executable,target_link_libraries等命令也是保留的。
这些关键字和保留字不能用作变量名或函数名。
4. 程序控制结构类型
CMake 提供了常见的控制结构,包括条件判断和循环。
条件判断:使用
if、elseif、else和endif。
例如:复制 下载 if(CMAKE_SYSTEM_NAME STREQUAL "Linux") message("Building on Linux") elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") message("Building on Windows") else() message("Building on unknown system") endif() 条件判断中可以使用各种逻辑操作符和比较操作符。
- **循环**:CMake 支持 `foreach` 循环和 `while` 循环。 `foreach` 循环示例: ```cmake set(MY_LIST "a;b;c") foreach(item IN LISTS MY_LIST) message(${item}) endforeach()`while` 循环示例:set(i 0) while(i LESS 5) message("i = ${i}") math(EXPR i "${i} + 1") endwhile()5. 函数
在 CMake 中,可以定义函数(function)和宏(macro)。函数和宏的主要区别在于作用域:函数有自己独立的作用域,而宏则类似于文本替换,没有独立作用域。
- 函数定义:
function(my_function arg1 arg2) message("arg1 = ${arg1}, arg2 = ${arg2}") set(${ARGV2} "new_value" PARENT_SCOPE) # 修改父作用域的变量 endfunction()调用函数: ```cmake my_function("Hello" "World") 函数参数可以通过 `ARGV`、`ARGV0`、`ARGV1` 等访问,也可以通过命名的参数访问。 ```- 宏定义:
复制 下载 macro(my_macro arg1 arg2) message("arg1 = ${arg1}, arg2 = ${arg2}") endmacro() 宏的调用方式与函数相同,但宏内部没有独立的作用域,因此会直接影响调用者的变量。6. 输入输出
CMake 中的输入输出主要通过命令实现。
- 输出:使用
message命令输出信息,可以指定输出级别(如STATUS、WARNING、FATAL_ERROR等)。
例如:
message(STATUS "This is a status message")- 输入:CMake 没有直接的输入命令,但可以通过变量设置来传递输入。例如,在命令行中使用
-D选项定义变量,或在 CMake GUI 中设置变量。
例如,在命令行中:
复制 下载 cmake -DMY_VARIABLE=value .. 然后在 CMakeLists.txt 中可以使用 `${MY_VARIABLE}`。7. 基础库,第三方库
CMake 提供了丰富的内置命令和模块,可以视为基础库。此外,CMake 还可以通过find_package命令来查找和使用第三方库。
- 内置命令和模块:CMake 自带了很多模块,如
FindOpenGL、FindThreads等,可以通过find_package或include来使用。
例如:
复制 下载 find_package(OpenGL REQUIRED) include(CheckCXXCompilerFlag)- 第三方库:对于第三方库,如果提供了 CMake 配置文件(如
FooConfig.cmake或foo-config.cmake),则可以使用find_package来查找。如果没有,可能需要自己编写查找脚本,或者使用add_subdirectory直接包含库的源代码。
8. 指针,引用等
CMake 语言中没有指针和引用的概念。变量都是字符串或字符串列表,通过变量名直接引用。在函数中,如果需要修改父作用域的变量,可以使用PARENT_SCOPE选项。
例如:
function(set_parent_var) set(my_var "new_value" PARENT_SCOPE) endfunction() set(my_var "old_value") set_parent_var() message(${my_var}) # 输出 new_value其他方面补充说明
变量作用域:CMake 有全局作用域、目录作用域和函数作用域。变量可以在父作用域中定义,然后在子作用域中修改(使用
PARENT_SCOPE)。缓存变量:CMake 允许将变量存储在缓存中(使用
-D选项或set命令的CACHE选项),这样可以在多次运行中持久化。属性:CMake 有属性的概念,可以设置目标(target)、目录(directory)、源文件(source file)等对象的属性。例如,设置目标的属性:
set_target_properties(my_target PROPERTIES CXX_STANDARD 11)- 生成器表达式:CMake 支持生成器表达式(Generator Expressions),这是一种在生成构建系统时进行求值的表达式,常用于设置依赖于配置(如Debug/Release)的属性。例如:
target_compile_definitions(my_target PRIVATE $<$<CONFIG:Debug>:DEBUG_MODE>) 表示在Debug配置下定义DEBUG_MODE宏。- 跨平台支持:CMake 的一个主要优势是跨平台,它可以根据不同平台生成相应的构建系统(如 Makefile、Visual Studio 项目等)。
以上是 CMake 语言的基础知识介绍。CMake 语言虽然简单,但通过其丰富的命令和模块,可以管理复杂的构建过程。建议在实践中逐步深入学习。
CMake 语言全面介绍
1. 基础数据类型
CMake 语言本质上是一种字符串处理语言,所有数据都以字符串形式表示,但具有特定的语义分类:
1.1 字符串(String)
# 基础字符串定义 set(MY_STRING "Hello World") set(ANOTHER_STRING WithoutQuotes) # 如果没有特殊字符,引号可选 # 包含特殊字符的字符串 set(PATH_STRING "C:/Program Files/MyApp") set(ESCAPED_STRING "Line1\nLine2\tTabbed") # 支持转义字符 # 多行字符串(CMake 3.0+) set(MULTILINE_STRING [[ First line Second line Third line with "quotes" ]])1.2 数字(Numeric)
虽然所有值都是字符串,但某些上下文会解释为数字:
set(MY_NUMBER 42) set(FLOATING_NUMBER 3.14159) set(HEX_NUMBER 0xFF) # 十六进制1.3 布尔值(Boolean)
CMake 支持多种布尔表示形式:
set(TRUE_VALUES ON YES TRUE Y 1) set(FALSE_VALUES OFF NO FALSE N 0 IGNORE NOTFOUND)1.4 空值/未定义值
set(EMPTY_STRING "") # 空字符串 unset(UNDEFINED_VAR) # 未定义变量2. 复杂数据结构类型
2.1 列表(Lists)
CMake 使用分号分隔的字符串表示列表:
# 创建列表 set(MY_LIST item1 item2 item3) # 自动用分号分隔 set(EXPLICIT_LIST "item1;item2;item3") set(LIST_WITH_SPACES "item 1;item 2;item 3") # 列表操作 list(APPEND MY_LIST item4) # 添加元素 list(LENGTH MY_LIST LIST_LEN) # 获取长度 list(GET MY_LIST 0 FIRST_ITEM) # 获取元素 list(REMOVE_ITEM MY_LIST item2) # 删除元素 list(FIND MY_LIST item3 INDEX) # 查找元素2.2 缓存变量(Cache Variables)
具有持久性的特殊变量:
# 定义缓存变量 set(MY_CACHE_VAR "default_value" CACHE STRING "Description") # 类型可以是:STRING, FILEPATH, PATH, BOOL, INTERNAL option(ENABLE_FEATURE "Enable feature" ON) # BOOL类型缓存变量3. 关键字和保留字
3.1 命令关键字
CMake 有大量内置命令,常用的包括:
- 项目定义:
project,cmake_minimum_required - 目标定义:
add_executable,add_library,add_custom_target - 目录操作:
add_subdirectory,include,include_directories - 属性设置:
set_target_properties,set_property,set_directory_properties - 条件控制:
if,else,elseif,endif,while,endforeach - 函数/宏:
function,endfunction,macro,endmacro
3.2 保留的变量名
以CMAKE_、_CMAKE_、_开头的变量名:
# 内置变量示例 CMAKE_VERSION # CMake版本 CMAKE_SOURCE_DIR # 源代码目录 CMAKE_BINARY_DIR # 构建目录 CMAKE_CURRENT_LIST_DIR # 当前CMakeLists.txt所在目录 # 项目相关变量 PROJECT_NAME # 项目名称 PROJECT_VERSION # 项目版本3.3 生成器表达式(Generator Expressions)
特殊的表达式语法,在生成构建系统时求值:
# 条件表达式 $<CONFIG:Debug> # Debug配置时为1 $<PLATFORM_ID:Windows> # Windows平台时为1 # 目标相关表达式 $<TARGET_FILE:my_target> # 目标文件路径 $<TARGET_PROPERTY:target,prop> # 目标属性 # 布尔表达式 $<AND:cond1,cond2> # 逻辑与 $<OR:cond1,cond2> # 逻辑或4. 程序控制结构
4.1 条件语句
# 基础if语句 if(condition) # ... elseif(another_condition) # ... else() # ... endif() # 条件操作符 if(variable) # 变量存在且不为false值 if(NOT condition) # 逻辑非 if(cond1 AND cond2) # 逻辑与 if(cond1 OR cond2) # 逻辑或 # 比较操作符 if(variable STREQUAL "value") # 字符串相等 if(variable VERSION_EQUAL 2) # 版本相等 if(NOT EXISTS path) # 文件/目录是否存在4.2 循环语句
# foreach循环 foreach(item IN LISTS my_list) message("Processing: ${item}") endforeach() foreach(i RANGE 1 10 2) # 从1到10,步长为2 message("Number: ${i}") endforeach() # while循环 set(counter 0) while(counter LESS 5) message("Counter: ${counter}") math(EXPR counter "${counter} + 1") endwhile()5. 函数和宏
5.1 函数(Function)
函数有独立的作用域:
function(my_function arg1 arg2) # 参数处理 message("arg1=${arg1}, arg2=${arg2}") set(${ARGV2} "modified" PARENT_SCOPE) # 修改父作用域变量 endfunction() # 调用函数 my_function("value1" "value2")5.2 宏(Macro)
宏是文本替换,没有独立作用域:
macro(my_macro arg1) set(${arg1}_modified "new_value") endmacro() # 调用宏 my_macro(MY_VAR)5.3 参数处理
function(process_args) # ARGC - 参数个数 # ARGV - 所有参数的列表 # ARGN - 命名参数之后的所有参数 foreach(arg IN LISTS ARGV) message("Argument: ${arg}") endforeach() endfunction()6. 输入输出
6.1 消息输出
# 消息级别 message(STATUS "Informational message") # 状态信息 message(WARNING "Warning message") # 警告信息 message(AUTHOR_WARNING "Author warning") # 开发者警告 message(SEND_ERROR "Error but continue") # 错误但继续 message(FATAL_ERROR "Fatal error") # 致命错误,停止处理6.2 文件操作
# 读写文件 file(WRITE filename.txt "content") file(APPEND filename.txt "more content") file(READ filename.txt CONTENT_VAR) # 文件系统操作 file(GLOB SOURCE_FILES "src/*.cpp") file(MAKE_DIRECTORY ${output_dir}) file(REMOVE ${file_to_delete}) # 配置时文件操作 configure_file(input.txt output.txt @ONLY)7. 基础库和第三方库
7.1 CMake 内置模块
# 查找包 find_package(Threads REQUIRED) # 查找线程库 find_package(OpenGL REQUIRED) # 查找OpenGL # 使用模块 include(CMakePrintHelpers) # 打印辅助 include(CheckCXXCompilerFlag) # 检查编译器标志 include(FeatureSummary) # 功能摘要 # 测试框架 enable_testing() # 启用测试 add_test(NAME test1 COMMAND mytest) # 添加测试7.2 查找和使用库
# 查找系统库 find_library(MATH_LIB m) find_path(INCLUDE_DIR someheader.h) # 现代CMake目标方式 add_library(mylib STATIC src.cpp) target_include_directories(mylib PUBLIC include) target_compile_definitions(mylib PRIVATE MY_DEFINE) target_link_libraries(mylib PUBLIC Threads::Threads) # 使用第三方库(包配置文件) find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system) target_link_libraries(myapp PUBLIC Boost::filesystem Boost::system)7.3 包管理器集成
# 使用FetchContent(CMake 3.11+) include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.11.0 ) FetchContent_MakeAvailable(googletest) # 使用ExternalProject include(ExternalProject) ExternalProject_Add( external_lib URL "http://example.com/library.zip" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" )8. CMake中的"指针"和"引用"
8.1 变量引用
CMake 使用${}进行变量展开:
set(VAR_NAME "value") set(REFERENCE "${VAR_NAME}") # 引用变量值8.2 目标别名(类似引用)
add_library(mylib_real STATIC src.cpp) add_library(mylib::mylib ALIAS mylib_real) # 创建别名 # 使用别名(类似智能指针) target_link_libraries(myapp PRIVATE mylib::mylib)8.3 属性作为间接访问
# 设置属性 set_property(TARGET mytarget PROPERTY INCLUDE_DIRECTORIES "/path/include") # 获取属性 get_property(inc_dirs TARGET mytarget PROPERTY INCLUDE_DIRECTORIES)9. 其他重要特性
9.1 作用域
# 目录作用域 set(LOCAL_VAR "value") # 当前目录作用域 # 函数作用域 function(my_func) set(LOCAL_VAR "new" PARENT_SCOPE) # 修改父作用域 endfunction() # 缓存作用域 set(CACHE_VAR "default" CACHE STRING "Description")9.2 策略(Policies)
# 设置策略 cmake_policy(SET CMP0077 NEW) # 启用新行为 # 检查版本 if(POLICY CMP0077) cmake_policy(SET CMP0077 NEW) endif()9.3 交叉编译
# 工具链文件 set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)9.4 安装规则
# 安装目标 install(TARGETS myapp mylib RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static ) # 安装文件 install(FILES headers/myheader.h DESTINATION include) install(DIRECTORY doc/ DESTINATION share/doc)10. 最佳实践和技巧
10.1 现代CMake实践
# 使用目标为中心的现代方法 add_library(mylib) target_sources(mylib PRIVATE src.cpp) target_include_directories(mylib PUBLIC include) target_compile_features(mylib PUBLIC cxx_std_17) # 正确使用PUBLIC/PRIVATE/INTERFACE # PUBLIC - 接口和实现都需要 # PRIVATE - 仅实现需要 # INTERFACE - 仅接口需要10.2 调试技巧
# 打印变量 message("Variable value: ${MY_VAR}") # 打印所有变量 get_cmake_property(vars VARIABLES) foreach(var ${vars}) message("${var}=${${var}}") endforeach() # 调试模式 set(CMAKE_VERBOSE_MAKEFILE ON)10.3 模块化设计
# 创建可重用模块 # mymodule.cmake function(add_mylibrary target) add_library(${target} STATIC ${ARGN}) target_compile_definitions(${target} PRIVATE MYLIB_EXPORT) endfunction() # 使用模块 include(mymodule) add_mylibrary(mylib src1.cpp src2.cpp)总结
CMake 是一门专门用于构建系统的领域特定语言(DSL),其核心特点包括:
- 字符串基础:所有数据都以字符串形式处理
- 声明式语法:主要描述构建依赖和规则
- 跨平台设计:支持多种编译器和操作系统
- 模块化:支持包含和重用CMake代码
- 生成器表达式:提供条件化和目标相关的配置
现代CMake强调:
- 使用目标(target)为中心的配置
- 最小化全局变量使用
- 正确传播依赖关系(PUBLIC/PRIVATE/INTERFACE)
- 支持包管理和查找
CMake的学习曲线相对陡峭,但掌握后能极大简化跨平台项目的构建管理。建议从简单项目开始,逐步学习现代CMake的最佳实践。