一、CMake变量的常见类型
按照CMake官方文档的说法,其预定义的变量可分为很多种,比如路径变量、项目变量、编译器变量等等(有兴趣可以查看一下其官方文档)。但实际情况中,大多数的开发不可能对所有的相关细节进行处理,那个不用学了也就忘记了。所以大家只要记住几种主要的类型即可。可以CMake中的变量分成几种常见的类型:普通变量和缓存变量、参数变量、环境变量和命令行变量。下面针对其进行分别的说明
1. 普通变量和缓存变量
普通变量只存在于当前 CMake 配置过程中,受作用域影响,不会自动写入 CMakeCache.txt。
cmake
set(MY_NAME "ivus")
message("MY_NAME=${MY_NAME}")
应用:
cmake
set(SRC_FILES main.cpp app.cpp)
add_executable(app ${SRC_FILES})
它一般用在于当前CMake配置过程,受到目录作用域、函数作用域影响,不会自动持久化。
缓存变量会写入 CMakeCache.txt,用户可以通过命令行、CMake GUI、ccmake 修改。
cmake
set(ENABLE_TESTS ON CACHE BOOL "Enable tests")
命令行设置:
bash
cmake -S . -B build -DENABLE_TESTS=ON
应用:
cmake
option(BUILD_TESTING "Build tests" ON)
读取缓存变量:
cmake
message("${ENABLE_TESTS}")
message("$CACHE{ENABLE_TESTS}")
一般来说,${VAR}会先查普通变量,再查缓存变量而$CACHE{VAR}会直接读取 cache entry
2. 参数变量
参数变量一般是指宏和函数的参数控制相关变量:
cmake
macro(demo_macro arg1 arg2)
message("ARG1: ${arg1}")
message("ARG2: ${arg2}")
message("ARGN: ${ARGN}")
endmacro()
应用:
cmake
demo_macro(v1 v2 ext1 ext2 ext3)
它们有点类似于c/c++中main函数的传参的处理。主要有:
text
ARGC:实参数量
ARGV:所有实参
ARGV0:第 1 个实参
ARGV1:第 2 个实参
ARGV2:第 3 个实参
ARGN:未被命名形参接收的剩余实参
3. 环境变量
CMake 中访问环境变量:
cmake
message("$ENV{PATH}")
判断环境变量是否存在:
cmake
if(DEFINED ENV{CC})
message("CC=$ENV{CC}")
endif()
设置当前CMake进程内环境变量:
cmake
set(ENV{MY_ENV} "abc")
它不会永久修改系统环境变量,也不会自动影响外部 shell,切记。
下面列出几个常见环境变量:
text
CC
CXX
CFLAGS
CXXFLAGS
LDFLAGS
CMAKE_GENERATOR
CMAKE_BUILD_PARALLEL_LEVEL
CMAKE_PREFIX_PATH
CMAKE_TOOLCHAIN_FILE
4. 命令行变量
通过 -D(这个有点宏变量的味道)传入的变量通常进入 cache:
bash
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/opt/myapp \
-DBUILD_SHARED_LIBS=ON
在掌握了大致的分类和初步的用法后,下面将对其中的一些常见的变量进行使用说明。
二、路径相关变量
1. CMAKE_SOURCE_DIR
顶层源码目录。
cmake
message("Top source dir: ${CMAKE_SOURCE_DIR}")
无论在顶层还是子目录中,CMAKE_SOURCE_DIR 都指向最顶层源码目录。
2. CMAKE_BINARY_DIR
顶层构建目录。
cmake
message("Top build dir: ${CMAKE_BINARY_DIR}")
例如:
bash
cmake -S . -B build
则:
text
CMAKE_BINARY_DIR = /path/to/project/build
3. CMAKE_CURRENT_SOURCE_DIR
当前正在处理的 CMakeLists.txt 所在源码目录。
cmake
message("Current source dir: ${CMAKE_CURRENT_SOURCE_DIR}")
4. CMAKE_CURRENT_BINARY_DIR
当前目录对应的构建目录。
cmake
message("Current binary dir: ${CMAKE_CURRENT_BINARY_DIR}")
5. PROJECT_SOURCE_DIR
当前 project对应的源码根目录。
cmake
project(MyApp)
message("${PROJECT_SOURCE_DIR}")
6. PROJECT_BINARY_DIR
当前project对应的构建目录。
三、项目信息变量
1. PROJECT_NAME
当前项目名称。
cmake
project(IvusSystem)
message("${PROJECT_NAME}")
2. CMAKE_PROJECT_NAME
顶层项目名称。如果子项目里也调用了project,PROJECT_NAME会变化,但 CMAKE_PROJECT_NAME仍是顶层项目名。
3. PROJECT_VERSION
项目版本。
cmake
project(MyApp VERSION 1.2.3)
message("${PROJECT_VERSION}")
相关变量:
text
PROJECT_VERSION_MAJOR
PROJECT_VERSION_MINOR
PROJECT_VERSION_PATCH
PROJECT_VERSION_TWEAK
应用:
cmake
project(MyApp VERSION 1.2.3)
message("${PROJECT_VERSION_MAJOR}") # 1
message("${PROJECT_VERSION_MINOR}") # 2
message("${PROJECT_VERSION_PATCH}") # 3
四、构建类型变量
1. CMAKE_BUILD_TYPE
单配置生成器使用,例如 Ninja、Unix Makefiles。
bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
常见值:
text
Debug
Release
RelWithDebInfo
MinSizeRel
需要说明的是,在跨平台如Visual Studio、Xcode、Ninja Multi-Config 这类多配置生成器通常不用它。
2. CMAKE_CONFIGURATION_TYPES
多配置生成器使用:
cmake
message("${CMAKE_CONFIGURATION_TYPES}")
常见值:
text
Debug;Release;RelWithDebInfo;MinSizeRel
3. CMAKE_DEBUG_POSTFIX
给 Debug 版本库名加后缀:
cmake
set(CMAKE_DEBUG_POSTFIX "d")
可能生成:
text
mylibd.dll
mylibd.lib
五、编译器相关变量
1. CMAKE_C_COMPILER
C编译器路径:
cmake
message("C compiler: ${CMAKE_C_COMPILER}")
2. CMAKE_CXX_COMPILER
C++ 编译器路径:
cmake
message("CXX compiler: ${CMAKE_CXX_COMPILER}")
3. CMAKE__COMPILER
通用的编译形式:
text
CMAKE_C_COMPILER
CMAKE_CXX_COMPILER
CMAKE_CUDA_COMPILER
CMAKE_Fortran_COMPILER
CMAKE_OBJC_COMPILER
CMAKE_OBJCXX_COMPILER
4. CMAKE__COMPILER_VERSION
编译器版本:
cmake
message("${CMAKE_CXX_COMPILER_VERSION}")
5. CMAKE__COMPILER_TARGET
交叉编译目标 :
cmake
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
六、编译选项变量
1. CMAKE_C_FLAGS
全局 C编译选项:
cmake
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
2. CMAKE_CXX_FLAGS
全局 C++ 编译选项:
cmake
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
3. 配置相关编译选项
推荐使用:
cmake
target_compile_options(myapp PRIVATE -Wall -Wextra)
而不是全局修改 CMAKE_CXX_FLAGS。
七、C++ 标准相关变量
1. CMAKE_CXX_STANDARD
设置默认 C++ 标准。
cmake
set(CMAKE_CXX_STANDARD 17)
2. CMAKE_CXX_STANDARD_REQUIRED
要求必须满足该标准。
cmake
set(CMAKE_CXX_STANDARD_REQUIRED ON)
3. CMAKE_CXX_EXTENSIONS
是否启用编译器扩展,推荐target级别写法:
cmake
target_compile_features(myapp PRIVATE cxx_std_17)
八、链接相关变量
1. CMAKE_EXE_LINKER_FLAGS
可执行文件链接选项。
cmake
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
2. 其他链接变量
text
CMAKE_SHARED_LINKER_FLAGS
CMAKE_MODULE_LINKER_FLAGS
CMAKE_STATIC_LINKER_FLAGS
推荐使用:
cmake
target_link_options(myapp PRIVATE -Wl,--as-needed)
target_link_libraries(myapp PRIVATE pthread)
九、输出目录变量
1. CMAKE_RUNTIME_OUTPUT_DIRECTORY
可执行文件输出目录:
cmake
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
2. CMAKE_LIBRARY_OUTPUT_DIRECTORY
动态库输出目录:
cmake
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
3. CMAKE_ARCHIVE_OUTPUT_DIRECTORY
静态库输出目录。
cmake
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
也可以按配置区分:
cmake
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/debug)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/release)
推荐使用:
cmake
set_target_properties(myapp PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
十、查找包和查找路径变量
1. CMAKE_PREFIX_PATH
find_package()、find_library()、find_path()查找依赖的路径:
bash
cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/Qt/6.5.3/gcc_64
也可以使用环境变量:
bash
export CMAKE_PREFIX_PATH=/opt/Qt/6.5.3/gcc_64
2. CMAKE_MODULE_PATH
CMake自定义 FindXXX.cmake的路径:
cmake
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
find_package(MyLib REQUIRED)
3. CMAKE_FIND_ROOT_PATH
交叉编译时的根路径:
cmake
set(CMAKE_FIND_ROOT_PATH /opt/sysroot)
十一、目标构建行为变量
1. BUILD_SHARED_LIBS
add_library()默认生成静态库或动态库。
cmake
set(BUILD_SHARED_LIBS ON)
add_library(mylib a.cpp)
如果 BUILD_SHARED_LIBS=ON,默认生成动态库。
如果显式写了 STATIC或SHARED,它不生效:
cmake
add_library(mylib STATIC a.cpp)
2. CMAKE_VERBOSE_MAKEFILE
Makefile构建时显示详细命令:
cmake
set(CMAKE_VERBOSE_MAKEFILE ON)
或者:
bash
cmake --build build --verbose
十二、函数和宏参数变量
在前面介绍了function或 macro中,有一组特殊参数变量,这些变量只在 function/ macro调用上下文中有意义,常用于实现可变参数函数。
1. ${ARGV} 和 ARGV0、{ARGV0}、ARGV0、{ARGV1}
${ARGV} 表示调用时传入的所有实参。${ARGV0}、${ARGV1}、${ARGV2} 等表示按下标访问单个实参。
示例:
cmake
function(demo a b)
message("a = ${a}")
message("b = ${b}")
message("ARGC = ${ARGC}")
message("ARGV = ${ARGV}")
message("ARGV0 = ${ARGV0}")
message("ARGV1 = ${ARGV1}")
message("ARGV2 = ${ARGV2}")
message("ARGV3 = ${ARGV3}")
message("ARGN = ${ARGN}")
endfunction()
demo(one two three four)
输出含义:
text
a = one
b = two
ARGC = 4
ARGV = one;two;three;four
ARGV0 = one
ARGV1 = two
ARGV2 = three
ARGV3 = four
ARGN = three;four
对应关系:
text
demo(one two three four)
│ │ │ │
│ │ │ └── ARGV3 = four
│ │ └──────── ARGV2 = three
│ └──────────── ARGV1 = two
└──────────────── ARGV0 = one
其中 a、b 是命名形参:
cmake
function(demo a b)
所以:
text
a = ARGV0
b = ARGV1
ARGN = 从 ARGV2 开始剩下的参数
2. ${ARGN}
${ARGN} 表示:
text
调用 function / macro 时,没有被显式命名形参接收的剩余参数列表
示例:
cmake
function(print_args first second)
message("first = ${first}")
message("second = ${second}")
message("ARGN = ${ARGN}")
endfunction()
print_args(A B C D E)
输出含义:
text
first = A
second = B
ARGN = C;D;E
也就是说:
text
first 接收 A
second 接收 B
ARGN 接收剩下的 C D E
3. ${ARGC}
${ARGC} 表示实参数量:
cmake
function(count_args)
message("argc = ${ARGC}")
endfunction()
count_args(a b c)
输出:
text
argc = 3
4. 封装可变参数命令
例如封装 add_executable():
cmake
function(my_add_executable target)
message("target = ${ARGV0}")
message("sources = ${ARGN}")
add_executable(${target} ${ARGN})
endfunction()
my_add_executable(app main.cpp util.cpp net.cpp)
含义:
text
ARGV0 = app
target = app
ARGN = main.cpp;util.cpp;net.cpp
实际等价于:
cmake
add_executable(app main.cpp util.cpp net.cpp)
5. 列表特性
${ARGV} 和 ${ARGN} 都是 CMake 列表,多个参数之间用分号 ; 分隔:
cmake
function(show_all)
foreach(item IN LISTS ARGV)
message("item = ${item}")
endforeach()
endfunction()
show_all(a b c)
输出:
text
item = a
item = b
item = c
总结:
text
ARGV 是所有参数
ARGV0 / ARGV1 / ARGV2 是按下标访问单个参数
ARGC 是参数个数
ARGN 是未被命名形参接收的剩余参数
十三、推荐用法
1. 项目选项用 option()
cmake
option(ENABLE_LOG "Enable log" ON)
不要写成普通变量,否则用户不方便通过 -D 配置。
2. 建议尽可能不使用全局 CMAKE_CXX_FLAGS
不建议:
cmake
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
建议:
cmake
target_compile_options(myapp PRIVATE -Wall -Wextra)
十四、总结
对于常见的CMake变量还是要准确把握,用到细节一些不太常见的如一些策略变量或具体到某个平台的变量再查看相关的文档即可。开发者是要便利的使用CMake作为工具,而不要将其作为一种应用上负担,抓住重点,切记。