CMake初探篇2-语法与准则
CMake初探篇一
千年暗室,一灯即明.

1. CMake 行为准则(Do's and Don'ts)
接下来的两个列表很大程度上基于优秀的 gist Effective Modern CMake. 那个列表更长且更详细,也非常欢迎你去仔细阅读它。
-
不要使用具有全局作用域的函数:这包含
link_directories、include_libraries等相似的函数。使用target_link_directoriess、target_include_libraries等具有target的限定。 -
不要添加非必要的 PUBLIC 要求:你应该避免把一些不必要的东西强加给用户(-Wall)。相比于 PUBLIC,更应该把他们声明为 PRIVATE。
-
将库直接链接到需要构建的目标上:如果可以的话,总是显式地将库链接到目标上。
-
当链接库文件时,不要省略 PUBLIC 或 PRIVATE 关键字:这将会导致后续所有的链接都是缺省的。
-
把 CMake 程序视作代码:它是代码。它应该和其他的代码一样,是整洁并且可读的。
-
建立目标的观念:你的目标应该代表一系列的概念。为任何需要保持一致的东西指定一个 (导入型)INTERFACE 目标,然后每次都链接到该目标。
-
导出你的接口:你的 CMake 项目应该可以直接构建或者安装。
-
为库书写一个 Config.cmake 文件:这是库作者为支持客户的体验而应该做的。
-
声明一个 ALIAS 目标以保持使用的一致性:使用
add_subdirectory和find_package应该提供相同的目标和命名空间。 -
将常见的功能合并到有详细文档的函数或宏中:函数往往是更好的选择。
-
使用小写的函数名: CMake 的函数和宏的名字可以定义为大写或小写,但是一般都使用小写,变量名用大写。
-
使用
cmake_policy和/或 限定版本号范围: 每次改变版本特性 (policy) 都要有据可依。应该只有不得不使用旧特性时才降低特性 (policy) 版本。
2. 简单案例与CMake语法分析
下面是最简单生成一个可执行程序的CMakeLists.txt的构成,包含指定CMake指定运行最低版本,项目名称和可执行程序的生成。
cmake_minimum_required(VERSION 3.12)
project(HakuApp)
add_executable(HakuApp maim.cpp)
这是每个 CMakeLists.txt 都必须包含的第一行
cmake_minimum_required(VERSION 3.12)
顺便提一下关于 CMake 的语法。命令 cmake_minimum_required 是不区分大小写的,所以常用的做法是使用小写和下划线的连接来表示函数,使用大写表示变量。
project(xxx) 语法

从CMake给出project的定义可知,该函数作用是给项目设置个名字。项目的名称被保存到PROJECT_NAME
变量中,如果当前构建的顶层CMakeLists.txt,名字也会保存到CMAKE_PROJECT_NAME中。
语法为:<>表示必须要填入的参数,[]表示可以选填的参数,[<>]表示,如果进行了选填,那就必须完成<>中指定的内容。
换句话说,必须指定<PROJECT_NAME>字段,其他均可以不用填写。如果选填了<major>。如果使用了次版本.,那就必须填写<minor>。
project(HakuApp
VERSION 1.0.0
LANGUAGES CXX
DESCRIPTION "a simple test dll"
HOMEPAGE_URL ""
)
# 输出所有项目相关变量, 上面填的与下面的一一对应可以输出。
message("=== 项目信息 === ")
message("项目名称: ${PROJECT_NAME}")
message("完整版本: ${PROJECT_VERSION}")
message("主版本: ${PROJECT_VERSION_MAJOR}")
message("次版本: ${PROJECT_VERSION_MINOR}")
message("补丁版本: ${PROJECT_VERSION_PATCH}")
message("微调版本: ${PROJECT_VERSION_TWEAK}")
message("描述: ${PROJECT_DESCRIPTION}")
message("主页: ${PROJECT_HOMEPAGE_URL}")
生成一个可执行文件
add_executable(HakuApp maim.cpp)
HakuApp 既是生成的可执行文件的名称,也是创建的 CMake 目标(target)的名称(我保证,你很快会听到更多关于目标的内容)。紧接着的是源文件的列表,你想列多少个都可以。CMake 很聪明 ,它根据拓展名只编译源文件。在大多数情况下,头文件将会被忽略;列出他们的唯一原因是为了让他们在 IDE 中被展示出来,目标文件在许多 IDE 中被显示为文件夹。
有了上面的三行语法,我们可以生成一个简单的可执行程序了。
3. 变量与缓存
使用下面命令可以查看具体的函数用法
cmake --help-command set
3.1 本地变量
我们首先讨论变量。你可以这样声明一个本地 ( local ) 变量:
set(
... [PARENT_SCOPE])
set(MY_VARIABLE "value")
set(MY_LIST "one" "two")
变量名通常全部用大写,变量值跟在其后。你可以通过 ${} 来解析一个变量,例如 ${MY_VARIABLE}。CMake 有作用域的概念,在声明一个变量后,你只可以在它的作用域内访问这个变量。如果你将一个函数或一个文件放到一个子目录中,这个变量将不再被定义。你可以通过在变量声明末尾添加 PARENT_SCOPE 来将它的作用域置定为当前的上一级作用域。这个一般用于函数定义中的返回值。
3.2 缓存变量
缓存变量(Cache Variable)存储在 CMakeCache.txt 文件中,在多次配置之间保持持久性。在子目录中仍可以访问缓存变量。
set(
... CACHE [FORCE])
: BOOL, FILEPATH, PATH, STRING, INTERNAL
# 方式1:set() 命令带 CACHE 选项
set(VARIABLE_NAME value CACHE STRING "Description")
# 方式2:option() 命令(布尔型缓存变量)
option(<variable> "<help_text>" [initial_value])
option(OPTION_NAME "Description" defaultValue)
我们可以通过下面命令,查看当前工程中所有的或者特定的缓存变量,也可以在CMakeCache.txt找到。
# 查看所有缓存变量
cmake -B build -L
# 查看 CMAKE_ 前缀的变量, window下
cmake -B build -L | findstr CMAKE_
# 查看特定前缀的变量, Linux下
cmake -B build -L | grep CMAKE_
3.3 环境变量
你也可以通过 set(ENV{variable_name} value) 和 $ENV{variable_name} 来设置和获取环境变量,不过一般来说,我们最好避免这么用。
4. 实际案例(库 + 可执行程序)
aux_source_directory命令可以用来获取指定目录下的所有源文件列表
aux_source_directory(<dir> <variable>)
# 常见用法
# 将当前目录下的所有源文件(如 .cpp, .c)添加到变量 SRC_LIST 中
aux_source_directory(. SRC_LIST)
# 或者,指定一个子目录,如 `src`
aux_source_directory(src SRC_LIST)
# 然后使用这个文件列表来创建可执行文件或库
add_executable(my_app ${SRC_LIST})
aux_source_directory 命令主要用于收集 源文件,如 .cpp 和 .c 文件。它不会自动包含头文件(如 .h, .hpp)。
file(GLOB),如果项目结构包含子目录或者需要更灵活地指定文件类型(例如同时收集头文件),可以使用file命令配合GLOB或GLOB_RECURSE选项
# 收集当前目录下所有的 .cpp 文件
file(GLOB SRC_LIST "*.cpp")
# 递归收集当前目录及所有子目录下的 .cpp 和 .h 文件
file(GLOB_RECURSE ALL_FILES "*.cpp" "*.h")
| 特性 | aux_source_directory |
file(GLOB/GLOB_RECURSE) |
|---|---|---|
| 主要用途 | 收集指定目录下的源文件 | 通过通配符模式匹配文件 |
| 文件类型 | 主要针对.c, .cpp等源文件 |
可指定任意扩展名(如*.cpp, *.h) |
| 递归搜索 | 不支持,仅搜索指定目录本身 | GLOB_RECURSE 选项支持递归搜索子目录 |
| 灵活性 | 较低,自动识别源文件 | 高,可自定义匹配模式 |
| 潜在问题 | 可能遗漏在头文件中实现的代码 | 可能包含编译生成的临时文件(需注意构建目录分离) |
实际场景,现在我们有一个库和一个可执行程序,目录结构如下:
window下, tree /F
D:.
│ CMakeLists.txt
│ main.cpp
└───TestLib1
CMakeLists.txt
testClassA.cpp
testClassA.h
4.1 简单可执行程序调用
CMakeLists.txt部分
# CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 3.5)
# 项目信息
project (DemoHakuon)
# 添加 math 子目录
add_executable(lesson1 main.cpp)
# 添加子模块, TestLib1库
add_subdirectory(TestLib1)
# 可执行程序包含库部分.
target_link_libraries(lesson1 PRIVATE TestLib1)
## ------------ 库部分 TestLib1/CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 3.5)
add_library(TestLib1 testClassA.cpp)
具体实现部分可任意编写,这个是简单的库调用,不包含库的导出和导入操作。
4.2 库的安装和导出
下方为一个通用的安装库的函数,许需要传入目标的名称即可完成库的安装操作。
# 安装库
function(install_project_library TARGET_NAME)
if(NOT TARGET ${TARGET_NAME})
message(FATAL_ERROR "Target '${TARGET_NAME}' not found. Check your target name.")
endif()
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
# 安装目标文件
install(TARGETS ${TARGET_NAME}
EXPORT ${TARGET_NAME}Targets
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${TARGET_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
# 导出目标信息
install(EXPORT ${TARGET_NAME}Targets
FILE ${TARGET_NAME}Targets.cmake
NAMESPACE ${TARGET_NAME}::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${TARGET_NAME}
)
# 生成配置文件
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/${TARGET_NAME}Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${TARGET_NAME}
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
# 安装 config 文件
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}ConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${TARGET_NAME}
)
endfunction()
4.3 打包
简单的打包指令如下面:
# 最小安装配置
install(TARGETS lesson1 RUNTIME DESTINATION bin)
# 最小打包配置
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_GENERATOR "ZIP") # 只生成ZIP包
include(CPack)
参考链接
本文来自博客园,作者:Hakuon,转载请注明原文链接:https://chuna2.787528.xyz/Hakuon/p/19315251
浙公网安备 33010602011771号