3、CMakeLists
约 4812 字大约 16 分钟
2025-03-08
编写者:bugcode
本文已完成并校对,部分内容引用自 计算机自学指南
CMakeLists.txt 编写完全指南
目录
1. CMakeLists.txt 基础
1.1 最简单的 CMakeLists.txt
# 指定 CMake 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 定义项目名称
project(HelloWorld)
# 添加可执行文件
add_executable(hello main.cpp)对应的 main.cpp:
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}1.2 项目版本和语言
cmake_minimum_required(VERSION 3.15)
# 指定项目名称、版本、描述、语言
project(MyApp
VERSION 1.2.3
DESCRIPTION "我的应用程序"
LANGUAGES CXX C # 指定使用的语言
)
# 访问项目信息
message("项目名称: ${PROJECT_NAME}")
message("项目版本: ${PROJECT_VERSION}")
message("主版本号: ${PROJECT_VERSION_MAJOR}")
message("次版本号: ${PROJECT_VERSION_MINOR}")
message("补丁版本: ${PROJECT_VERSION_PATCH}")
message("源码目录: ${PROJECT_SOURCE_DIR}")
message("构建目录: ${PROJECT_BINARY_DIR}")2. 基本语法和命令
2.1 注释
# 这是单行注释
#[[
这是多行注释
可以写多行内容
]]2.2 输出信息
# 普通信息
message("Hello, CMake!")
# 带状态的信息
message(STATUS "正在配置项目...")
# 警告信息
message(WARNING "这个变量已弃用")
# 错误信息(会停止配置)
message(FATAL_ERROR "致命错误:找不到必要的库")
# 调试信息
message(DEBUG "调试信息:变量值=${MY_VAR}")
# 跟踪信息
message(TRACE "详细信息:进入函数")2.3 文件操作
# 列出所有源文件
file(GLOB SOURCES "src/*.cpp")
file(GLOB_RECURSE ALL_SOURCES "src/**/*.cpp")
# 更精确的方式(推荐)
set(SOURCES
src/main.cpp
src/utils.cpp
src/network.cpp
)
# 读取文件内容
file(READ "version.txt" VERSION_CONTENT)
# 写入文件
file(WRITE "output.txt" "生成的内容\n")
file(APPEND "output.txt" "追加的内容\n")
# 复制文件
file(COPY file.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
# 下载文件
file(DOWNLOAD https://example.com/file.zip file.zip
STATUS status LOG log)
# 创建目录
file(MAKE_DIRECTORY output_dir)3. 变量和缓存
3.1 普通变量
# 设置变量
set(MY_VARIABLE "value")
set(SOURCES main.cpp utils.cpp network.cpp)
# 使用变量
message("MY_VARIABLE = ${MY_VARIABLE}")
# 列表操作
list(LENGTH SOURCES len)
list(GET SOURCES 0 first_source)
list(APPEND SOURCES new_file.cpp)
list(REMOVE_ITEM SOURCES old_file.cpp)
# 环境变量
message("PATH = $ENV{PATH}")
set(ENV{MY_ENV_VAR} "value")
# 缓存变量(可被命令行覆盖)
set(CACHE_VAR "default" CACHE STRING "这是一个缓存变量")3.2 缓存变量
# 定义缓存变量(用户可以修改)
set(MY_OPTION ON CACHE BOOL "启用某个功能")
set(MY_PATH "/usr/local" CACHE PATH "安装路径")
set(MY_STRING "default" CACHE STRING "字符串选项")
# 标记高级变量
mark_as_advanced(MY_STRING)
# 强制类型
set(FORCE_VAR "value" CACHE STRING "强制设置" FORCE)
# 选项(简化布尔缓存变量)
option(ENABLE_TESTS "启用测试" ON)
option(USE_OPENMP "使用 OpenMP" OFF)3.3 常用内置变量
# 系统信息
message("系统名称: ${CMAKE_SYSTEM_NAME}")
message("系统版本: ${CMAKE_SYSTEM_VERSION}")
message("处理器: ${CMAKE_SYSTEM_PROCESSOR}")
# 编译器信息
message("C++ 编译器: ${CMAKE_CXX_COMPILER}")
message("C++ 编译器ID: ${CMAKE_CXX_COMPILER_ID}")
message("C++ 编译器版本: ${CMAKE_CXX_COMPILER_VERSION}")
# 目录信息
message("当前源码目录: ${CMAKE_CURRENT_SOURCE_DIR}")
message("当前构建目录: ${CMAKE_CURRENT_BINARY_DIR}")
message("主源码目录: ${CMAKE_SOURCE_DIR}")
message("主构建目录: ${CMAKE_BINARY_DIR}")
# 构建类型
message("构建类型: ${CMAKE_BUILD_TYPE}") # Debug, Release 等
# 输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)4. 目标 (Targets)
4.1 可执行文件
# 简单可执行文件
add_executable(myapp main.cpp)
# 多个源文件
add_executable(myapp
main.cpp
utils.cpp
network.cpp
)
# 设置目标属性
set_target_properties(myapp PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
OUTPUT_NAME "myapp"
PREFIX ""
SUFFIX ".exe"
)
# 获取目标属性
get_target_property(OUTPUT_NAME myapp OUTPUT_NAME)4.2 库
# 静态库
add_library(mylib STATIC
lib1.cpp
lib2.cpp
)
# 动态库
add_library(myshared SHARED
shared1.cpp
shared2.cpp
)
# 对象库(不链接,只编译对象文件)
add_library(myobj OBJECT
obj1.cpp
obj2.cpp
)
# 接口库(只包含头文件)
add_library(myinterface INTERFACE)
target_include_directories(myinterface INTERFACE include)
# 别名目标
add_library(My::Lib ALIAS mylib)
# 导入库(已存在的库)
add_library(External::Lib UNKNOWN IMPORTED)
set_target_properties(External::Lib PROPERTIES
IMPORTED_LOCATION /path/to/lib.so
INTERFACE_INCLUDE_DIRECTORIES /path/to/include
)4.3 目标属性
# 包含目录
target_include_directories(myapp
PRIVATE
src
${CMAKE_CURRENT_BINARY_DIR}
PUBLIC
include
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/interface
)
# 链接库
target_link_libraries(myapp
PRIVATE
mylib
pthread
PUBLIC
fmt::fmt
INTERFACE
Eigen3::Eigen
)
# 编译定义
target_compile_definitions(myapp
PRIVATE
DEBUG_MODE
VERSION="1.0.0"
PUBLIC
USE_FEATURE_X
)
# 编译选项
target_compile_options(myapp
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4>
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
)
# 链接选项
target_link_options(myapp
PRIVATE
$<$<PLATFORM_ID:Linux>:-static-libstdc++>
$<$<CONFIG:Debug>:-g>
)
# 源文件属性
set_source_files_properties(main.cpp PROPERTIES
COMPILE_FLAGS "-O2"
SKIP_UNITY_BUILD_INCLUSION ON
)5. 项目结构组织
5.1 单文件项目
# 根目录 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(SimpleApp)
add_executable(simple_app main.cpp)5.2 多目录项目结构
MyProject/
├── CMakeLists.txt
├── src/
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── utils/
│ ├── CMakeLists.txt
│ ├── math.h
│ └── math.cpp
├── include/
│ └── myproject/
│ └── api.h
└── tests/
├── CMakeLists.txt
└── test_math.cpp根目录 CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
project(MyProject VERSION 1.0.0)
# 设置全局属性
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 添加子目录
add_subdirectory(src)
# 条件添加测试目录
option(BUILD_TESTS "构建测试" ON)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()src/CMakeLists.txt:
# 添加子目录
add_subdirectory(utils)
# 创建主库
add_library(mycore STATIC)
target_sources(mycore PRIVATE
${CMAKE_CURRENT_LIST_DIR}/core.cpp
)
target_include_directories(mycore
PUBLIC
${CMAKE_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(mycore
PUBLIC
mymath
)
# 创建可执行文件
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mycore)src/utils/CMakeLists.txt:
# 创建工具库
add_library(mymath STATIC
math.cpp
)
target_include_directories(mymath
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_compile_features(mymath PUBLIC cxx_std_17)tests/CMakeLists.txt:
# 查找 GTest
find_package(GTest REQUIRED)
# 添加测试
add_executable(test_math test_math.cpp)
target_link_libraries(test_math
PRIVATE
mymath
GTest::gtest_main
)
add_test(NAME MathTest COMMAND test_math)5.3 使用 target_sources 的最佳实践
# 基础库的 CMakeLists.txt
add_library(mylib)
# 添加源文件
target_sources(mylib PRIVATE
src/file1.cpp
src/file2.cpp
src/file3.cpp
)
# 条件添加源文件
if(WIN32)
target_sources(mylib PRIVATE src/windows_specific.cpp)
else()
target_sources(mylib PRIVATE src/unix_specific.cpp)
endif()
# 生成的文件
target_sources(mylib PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated.cpp)
# 头文件(仅用于 IDE 显示)
target_sources(mylib PRIVATE
FILE_SET HEADERS
BASE_DIRS include
FILES
include/mylib/api.h
include/mylib/internal.h
)6. 查找和链接库
6.1 使用 find_package
# 基本用法
find_package(OpenCV REQUIRED)
find_package(Boost COMPONENTS filesystem system REQUIRED)
find_package(fmt CONFIG REQUIRED)
# 检查是否找到
if(OpenCV_FOUND)
message("找到 OpenCV 版本: ${OpenCV_VERSION}")
else()
message(FATAL_ERROR "需要 OpenCV")
endif()
# 使用找到的库
target_link_libraries(myapp
PRIVATE
${OpenCV_LIBS}
Boost::filesystem
fmt::fmt
)
target_include_directories(myapp
PRIVATE
${OpenCV_INCLUDE_DIRS}
)6.2 编写 Find 模块
cmake/FindMyLib.cmake:
#[=======================================================================
.rst:
FindMyLib
---------
查找 MyLib 库的 CMake 模块。
导入的目标
^^^^^^^^^^
如果找到,这个模块会创建以下导入目标:
MyLib::MyLib - 主库目标
结果变量
^^^^^^^^^^
这个模块会设置以下变量:
MyLib_FOUND - 是否找到库
MyLib_INCLUDE_DIRS - 包含目录
MyLib_LIBRARIES - 库文件
MyLib_VERSION - 版本号
#]=======================================================================]
# 查找头文件
find_path(MyLib_INCLUDE_DIR
NAMES mylib/api.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
${CMAKE_INSTALL_PREFIX}/include
)
# 查找库文件
find_library(MyLib_LIBRARY
NAMES mylib libmylib
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
${CMAKE_INSTALL_PREFIX}/lib
)
# 获取版本
if(MyLib_INCLUDE_DIR)
file(READ "${MyLib_INCLUDE_DIR}/mylib/version.h" VERSION_H)
string(REGEX MATCH "#define MYLIB_VERSION_MAJOR ([0-9]+)" _ ${VERSION_H})
set(MyLib_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "#define MYLIB_VERSION_MINOR ([0-9]+)" _ ${VERSION_H})
set(MyLib_VERSION_MINOR ${CMAKE_MATCH_1})
set(MyLib_VERSION "${MyLib_VERSION_MAJOR}.${MyLib_VERSION_MINOR}")
endif()
# 处理 REQUIRED 和 QUIET 参数
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib
REQUIRED_VARS MyLib_LIBRARY MyLib_INCLUDE_DIR
VERSION_VAR MyLib_VERSION
)
# 设置输出变量
if(MyLib_FOUND)
set(MyLib_LIBRARIES ${MyLib_LIBRARY})
set(MyLib_INCLUDE_DIRS ${MyLib_INCLUDE_DIR})
# 创建导入目标
if(NOT TARGET MyLib::MyLib)
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MyLib_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MyLib_INCLUDE_DIR}"
)
endif()
endif()
# 标记为高级变量
mark_as_advanced(MyLib_INCLUDE_DIR MyLib_LIBRARY)6.3 使用 pkg-config
# 启用 pkg-config 支持
find_package(PkgConfig REQUIRED)
# 查找库
pkg_check_modules(GLIB REQUIRED glib-2.0>=2.40)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
# 使用找到的库
target_link_libraries(myapp
PRIVATE
${GLIB_LIBRARIES}
PkgConfig::GTK # 导入的目标
)
target_include_directories(myapp
PRIVATE
${GLIB_INCLUDE_DIRS}
)
target_compile_options(myapp
PRIVATE
${GLIB_CFLAGS_OTHER}
)6.4 使用 FetchContent(CMake 3.11+)
include(FetchContent)
# 下载和构建 fmt 库
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 8.1.1
GIT_SHALLOW TRUE
)
# 下载和构建 nlohmann_json
FetchContent_Declare(
nlohmann_json
URL https://github.com/nlohmann/json/releases/download/v3.10.5/json.hpp
DOWNLOAD_NO_EXTRACT TRUE
)
# 使内容可用
FetchContent_MakeAvailable(fmt nlohmann_json)
# 现在可以直接使用
target_link_libraries(myapp PRIVATE fmt::fmt)
target_include_directories(myapp PRIVATE ${nlohmann_json_SOURCE_DIR})7. 编译选项和特性
7.1 设置 C++ 标准
# 全局设置
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # 禁用编译器扩展
# 针对特定目标
target_compile_features(myapp
PUBLIC
cxx_std_17
PRIVATE
cxx_lambdas
cxx_constexpr
)
# 检查特性
target_compile_features(mylib INTERFACE cxx_std_14)7.2 编译器特定选项
# 根据编译器设置选项
if(MSVC)
# MSVC
target_compile_options(myapp PRIVATE
/W4
/WX # 警告视为错误
/EHsc # 启用 C++ 异常
/MP # 多进程编译
$<$<CONFIG:Release>:/O2 /GL>
$<$<CONFIG:Debug>:/Zi /Od /RTC1>
)
target_link_options(myapp PRIVATE
$<$<CONFIG:Release>:/LTCG>
$<$<CONFIG:Debug>:/DEBUG>
)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
# GCC/Clang
target_compile_options(myapp PRIVATE
-Wall
-Wextra
-Wpedantic
-Werror
-Wno-unused-parameter
$<$<CONFIG:Release>:-O3 -DNDEBUG>
$<$<CONFIG:Debug>:-g -O0 -fsanitize=address>
)
target_link_options(myapp PRIVATE
$<$<CONFIG:Debug>:-fsanitize=address>
)
endif()7.3 优化和调试
# 不同构建类型的标志
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0" CACHE STRING "Debug flags")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "Release flags")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG" CACHE STRING "RelWithDebInfo flags")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG" CACHE STRING "MinSizeRel flags")
# 链接时优化
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
# 按需启用
option(ENABLE_LTO "启用链接时优化" OFF)
if(ENABLE_LTO)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()8. 条件控制
8.1 if 语句
# 基本条件
if(WIN32)
message("Windows 系统")
elseif(APPLE)
message("macOS 系统")
elseif(UNIX)
message("Unix 系统")
else()
message("其他系统")
endif()
# 检查变量
if(DEFINED MY_VAR)
message("MY_VAR 已定义")
endif()
if(MY_VAR)
message("MY_VAR 为真")
endif()
# 字符串比较
if(MY_VAR STREQUAL "value")
message("相等")
endif()
if(MY_VAR MATCHES "^prefix.*")
message("匹配前缀")
endif()
# 版本比较
if(CMAKE_VERSION VERSION_LESS "3.12")
message("CMake 版本过低")
endif()
# 逻辑操作
if(WIN32 AND NOT MINGW)
message("Windows 但不是 MinGW")
endif()
if(MSVC OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message("MSVC 或 Clang")
endif()8.2 循环
# foreach 循环
set(LIBRARIES fmt boost eigen)
foreach(LIB ${LIBRARIES})
message("链接库: ${LIB}")
target_link_libraries(myapp PRIVATE ${LIB})
endforeach()
# 带索引的循环
foreach(INDEX RANGE 1 5)
message("Index: ${INDEX}")
endforeach()
# 列表循环
set(SOURCES main.cpp utils.cpp network.cpp)
foreach(FILE IN LISTS SOURCES)
get_filename_component(NAME ${FILE} NAME)
message("源文件: ${NAME}")
endforeach()
# while 循环(CMake 3.16+)
set(COUNTER 0)
while(COUNTER LESS 5)
message("Counter: ${COUNTER}")
math(EXPR COUNTER "${COUNTER} + 1")
endwhile()9. 函数和宏
9.1 函数定义
# 定义函数
function(add_my_library TARGET_NAME)
# 参数
set(options STATIC SHARED)
set(oneValueArgs VERSION)
set(multiValueArgs SOURCES DEPENDS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# 使用参数
if(ARG_STATIC)
add_library(${TARGET_NAME} STATIC ${ARG_SOURCES})
elseif(ARG_SHARED)
add_library(${TARGET_NAME} SHARED ${ARG_SOURCES})
else()
add_library(${TARGET_NAME} ${ARG_SOURCES})
endif()
if(ARG_VERSION)
set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${ARG_VERSION})
endif()
if(ARG_DEPENDS)
target_link_libraries(${TARGET_NAME} PRIVATE ${ARG_DEPENDS})
endif()
# 设置包含目录
target_include_directories(${TARGET_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
)
# 返回值
set(${TARGET_NAME}_CREATED TRUE PARENT_SCOPE)
endfunction()
# 使用函数
add_my_library(mylib
STATIC
VERSION 1.0.0
SOURCES
src/lib1.cpp
src/lib2.cpp
DEPENDS
fmt::fmt
)
if(mylib_CREATED)
message("库创建成功")
endif()9.2 宏定义
# 宏(不创建新作用域)
macro(print_variable VAR_NAME)
message("${VAR_NAME} = ${${VAR_NAME}}")
endmacro()
set(MY_VAR "hello")
print_variable(MY_VAR)
# 实用宏
macro(ensure_out_of_source_build)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "禁止源码内构建,请创建单独的构建目录")
endif()
endmacro()
ensure_out_of_source_build()9.3 模块化
cmake/Utils.cmake:
# 工具函数模块
function(add_platform_specific_sources TARGET)
if(WIN32)
target_sources(${TARGET} PRIVATE ${ARGN}_win.cpp)
elseif(APPLE)
target_sources(${TARGET} PRIVATE ${ARGN}_mac.cpp)
else()
target_sources(${TARGET} PRIVATE ${ARGN}_linux.cpp)
endif()
endfunction()
function(enable_warnings TARGET)
if(MSVC)
target_compile_options(${TARGET} PRIVATE /W4)
else()
target_compile_options(${TARGET} PRIVATE -Wall -Wextra)
endif()
endfunction()
# 导出函数
include(CMakeParseArguments)在主 CMakeLists.txt 中:
# 包含模块
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(Utils)
# 使用模块中的函数
add_executable(myapp main.cpp)
add_platform_specific_sources(myapp platform)
enable_warnings(myapp)10. 生成器表达式
10.1 基本用法
# 条件编译定义
target_compile_definitions(myapp PRIVATE
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<CONFIG:Release>:NDEBUG>
)
# 条件链接
target_link_libraries(myapp PRIVATE
$<$<PLATFORM_ID:Windows>:ws2_32>
$<$<PLATFORM_ID:Linux>:pthread>
$<$<BOOL:${USE_OPENMP}>:OpenMP::OpenMP>
)
# 条件编译选项
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4>
$<$<CXX_COMPILER_ID:GNU>:-Wall>
)
# 条件包含目录
target_include_directories(myapp PRIVATE
$<$<CONFIG:Debug>:${DEBUG_INCLUDE_DIRS}>
$<$<CONFIG:Release>:${RELEASE_INCLUDE_DIRS}>
)10.2 复杂条件
# 逻辑组合
target_compile_definitions(myapp PRIVATE
$<$<AND:$<CONFIG:Debug>,$<PLATFORM_ID:Linux>>:LINUX_DEBUG>
)
# 字符串操作
target_compile_definitions(myapp PRIVATE
VERSION="$<TARGET_PROPERTY:myapp,VERSION>"
)
# 列表操作
set(SUPPORTED_ARCH "x86_64;arm64")
target_compile_definitions(myapp PRIVATE
SUPPORTED_ARCHITECTURES="$<JOIN:${SUPPORTED_ARCH},,>"
)
# 条件输出
target_link_options(myapp PRIVATE
$<$<NOT:$<BOOL:${USE_SYSTEM_LIB}>>:/path/to/local/lib>
)10.3 目标相关
# 获取目标属性
target_compile_definitions(myapp PRIVATE
TARGET_FILE="$<TARGET_FILE:myapp>"
TARGET_DIR="$<TARGET_FILE_DIR:myapp>"
)
# 依赖目标信息
target_compile_definitions(myapp PRIVATE
DEPENDENCY_VERSION="$<TARGET_PROPERTY:fmt,VERSION>"
)
# 配置特定
target_sources(myapp PRIVATE
$<$<CONFIG:Debug>:${DEBUG_SOURCES}>
$<$<CONFIG:Release>:${RELEASE_SOURCES}>
)11. 测试和安装
11.1 添加测试
# 启用测试
enable_testing()
# 添加简单测试
add_test(NAME SimpleTest COMMAND myapp --test)
# 添加带参数的测试
add_test(NAME MathTest
COMMAND test_math --gtest_output=xml:test_results.xml
)
# 设置测试属性
set_tests_properties(MathTest PROPERTIES
TIMEOUT 10
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ENVIRONMENT "TEST_VAR=value"
)
# 添加多个测试
function(add_gtest TEST_NAME)
add_executable(${TEST_NAME} ${TEST_NAME}.cpp)
target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest_main)
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
endfunction()
add_gtest(test_math)
add_gtest(test_utils)
# 添加测试标签
set_property(TEST test_math test_utils PROPERTY LABELS "unit")
# 添加自定义测试
add_test(NAME IntegrationTest
COMMAND ${CMAKE_COMMAND}
-DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/data
-DOUTPUT=${CMAKE_CURRENT_BINARY_DIR}/output
-P ${CMAKE_CURRENT_SOURCE_DIR}/run_test.cmake
)11.2 安装配置
# 安装可执行文件
install(TARGETS myapp
RUNTIME DESTINATION bin
COMPONENT applications
)
# 安装库
install(TARGETS mylib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
# 安装头文件
install(DIRECTORY include/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN ".git" EXCLUDE
)
# 安装配置文件
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/myapp.conf
DESTINATION /etc
)
# 安装脚本
install(PROGRAMS
scripts/start.sh
scripts/stop.sh
DESTINATION bin
)
# 安装文档
install(FILES
README.md
LICENSE
DESTINATION share/doc/myapp
)
# 安装符号
if(MSVC)
install(FILES $<TARGET_PDB_FILE:myapp> DESTINATION bin OPTIONAL)
endif()11.3 导出配置
# 导出目标供其他项目使用
install(TARGETS mylib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
)
# 安装导出文件
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib
)
# 生成配置文件
include(CMakePackageConfigHelpers)
# 生成版本文件
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
# 生成配置文件
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
INSTALL_DESTINATION lib/cmake/MyLib
)
# 安装配置文件
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
DESTINATION lib/cmake/MyLib
)Config.cmake.in:
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
check_required_components(MyLib)12. 自定义命令和目标
12.1 生成文件
# 在构建时生成文件
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.h
COMMAND ${CMAKE_COMMAND}
-DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/version.h
-DVERSION=${PROJECT_VERSION}
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/version.txt
COMMENT "生成版本头文件"
VERBATIM
)
# 使用生成的文件
add_executable(myapp main.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.h)
# 多个输出
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/file1.cpp
${CMAKE_CURRENT_BINARY_DIR}/file1.h
COMMAND generator --output ${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/schema.proto
)12.2 自定义目标
# 添加自定义目标(不产生输出)
add_custom_target(format-code
COMMAND clang-format -i ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
COMMENT "格式化代码"
VERBATIM
)
# 添加依赖
add_custom_target(generate-files
DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CMAKE_CURRENT_BINARY_DIR}/config.h
)
# 使主目标依赖自定义目标
add_dependencies(myapp generate-files)
# 添加快捷命令
add_custom_target(run
COMMAND $<TARGET_FILE:myapp>
DEPENDS myapp
COMMENT "运行程序"
)12.3 使用外部工具
# 使用 protobuf
find_package(Protobuf REQUIRED)
function(protobuf_generate_cpp SRCS HDRS)
cmake_parse_arguments(PARSE_ARGV 2 ARG "" "PROTO_DIR" "")
set(PROTO_FILES ${ARGN})
set(PROTO_DIR ${ARG_PROTO_DIR})
foreach(PROTO_FILE ${PROTO_FILES})
get_filename_component(PROTO_WE ${PROTO_FILE} NAME_WE)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_WE}.pb.cc
${CMAKE_CURRENT_BINARY_DIR}/${PROTO_WE}.pb.h
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
--cpp_out ${CMAKE_CURRENT_BINARY_DIR}
-I ${PROTO_DIR}
${PROTO_DIR}/${PROTO_FILE}
DEPENDS ${PROTO_DIR}/${PROTO_FILE}
COMMENT "生成 protobuf 文件: ${PROTO_FILE}"
VERBATIM
)
list(APPEND ${SRCS} ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_WE}.pb.cc)
list(APPEND ${HDRS} ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_WE}.pb.h)
endforeach()
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
set(${HDRS} ${${HDRS}} PARENT_SCOPE)
endfunction()
# 使用函数
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS
PROTO_DIR ${CMAKE_CURRENT_SOURCE_DIR}/proto
message.proto
data.proto
)
add_library(proto ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(proto PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(proto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})13. 完整项目示例
13.1 项目结构
Calculator/
├── CMakeLists.txt
├── CMakePresets.json
├── README.md
├── LICENSE
├── .gitignore
├── src/
│ ├── CMakeLists.txt
│ ├── main.cpp
│ ├── calculator/
│ │ ├── CMakeLists.txt
│ │ ├── calculator.h
│ │ └── calculator.cpp
│ └── utils/
│ ├── CMakeLists.txt
│ ├── logger.h
│ └── logger.cpp
├── include/
│ └── calculator/
│ └── api.h
├── tests/
│ ├── CMakeLists.txt
│ ├── test_calculator.cpp
│ └── test_utils.cpp
├── cmake/
│ ├── CompilerOptions.cmake
│ ├── FindMyLib.cmake
│ └── Utils.cmake
├── docs/
│ └── Doxyfile.in
└── scripts/
├── build.sh
└── generate_docs.sh13.2 根目录 CMakeLists.txt
# 根目录 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(Calculator
VERSION 1.0.0
DESCRIPTION "一个简单的计算器应用"
LANGUAGES CXX
)
# 确保源码外构建
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "请使用源码外构建,例如:\n"
" mkdir build && cd build\n"
" cmake ..")
endif()
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 设置模块路径
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
# 包含自定义模块
include(CompilerOptions)
include(Utils)
# 选项
option(BUILD_TESTS "构建测试" ON)
option(BUILD_SHARED_LIBS "构建共享库" OFF)
option(ENABLE_DOCS "生成文档" OFF)
option(ENABLE_SANITIZERS "启用地址消毒器" OFF)
# 查找依赖
find_package(fmt CONFIG)
if(NOT fmt_FOUND)
message(STATUS "未找到 fmt,使用 FetchContent 下载")
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 8.1.1
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fmt)
endif()
# 添加子目录
add_subdirectory(src)
# 测试
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
# 文档
if(ENABLE_DOCS)
find_package(Doxygen REQUIRED)
add_subdirectory(docs)
endif()
# 打印配置总结
message("
========================================
${PROJECT_NAME} ${PROJECT_VERSION} 配置完成
========================================
安装目录: ${CMAKE_INSTALL_PREFIX}
构建类型: ${CMAKE_BUILD_TYPE}
共享库: ${BUILD_SHARED_LIBS}
测试: ${BUILD_TESTS}
文档: ${ENABLE_DOCS}
消毒器: ${ENABLE_SANITIZERS}
编译器: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}
C++ 标准: ${CMAKE_CXX_STANDARD}
========================================
")13.3 cmake/CompilerOptions.cmake
# cmake/CompilerOptions.cmake
function(set_compiler_options TARGET)
if(MSVC)
# MSVC
target_compile_options(${TARGET} PRIVATE
/W4
/WX
/EHsc
/MP
$<$<CONFIG:Debug>:/Zi /Od /RTC1>
$<$<CONFIG:Release>:/O2 /GL>
)
target_link_options(${TARGET} PRIVATE
$<$<CONFIG:Release>:/LTCG>
$<$<CONFIG:Debug>:/DEBUG>
)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
# GCC/Clang
target_compile_options(${TARGET} PRIVATE
-Wall
-Wextra
-Wpedantic
-Werror
-Wno-unused-parameter
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-O3 -DNDEBUG>
)
if(ENABLE_SANITIZERS AND CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(${TARGET} PRIVATE -fsanitize=address,undefined)
target_link_options(${TARGET} PRIVATE -fsanitize=address,undefined)
endif()
endif()
endfunction()
function(set_warnings_as_errors TARGET)
if(MSVC)
target_compile_options(${TARGET} PRIVATE /WX)
else()
target_compile_options(${TARGET} PRIVATE -Werror)
endif()
endfunction()13.4 cmake/Utils.cmake
# cmake/Utils.cmake
# 添加测试目标
function(add_test_target NAME)
add_executable(${NAME} ${NAME}.cpp)
target_link_libraries(${NAME} PRIVATE
CalculatorLib
GTest::gtest_main
)
add_test(NAME ${NAME} COMMAND ${NAME})
set_target_properties(${NAME} PROPERTIES
FOLDER "tests"
)
endfunction()
# 添加库
function(add_calculator_library NAME)
set(options STATIC SHARED)
set(oneValueArgs)
set(multiValueArgs SOURCES DEPENDS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(ARG_STATIC)
add_library(${NAME} STATIC)
elseif(ARG_SHARED)
add_library(${NAME} SHARED)
else()
add_library(${NAME})
endif()
if(ARG_SOURCES)
target_sources(${NAME} PRIVATE ${ARG_SOURCES})
endif()
if(ARG_DEPENDS)
target_link_libraries(${NAME} PUBLIC ${ARG_DEPENDS})
endif()
target_include_directories(${NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
set_compiler_options(${NAME})
endfunction()13.5 src/CMakeLists.txt
# src/CMakeLists.txt
# 添加子目录
add_subdirectory(calculator)
add_subdirectory(utils)
# 创建主库
add_library(CalculatorLib)
target_link_libraries(CalculatorLib
PUBLIC
calculator
PRIVATE
utils
fmt::fmt
)
# 创建可执行文件
add_executable(calculator_app main.cpp)
target_link_libraries(calculator_app PRIVATE CalculatorLib)
set_compiler_options(calculator_app)
# 安装
install(TARGETS calculator_app CalculatorLib calculator utils
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
)13.6 src/calculator/CMakeLists.txt
# src/calculator/CMakeLists.txt
add_calculator_library(calculator
STATIC
SOURCES
calculator.cpp
DEPENDS
utils
)
# 导出头文件
target_include_directories(calculator
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)13.7 tests/CMakeLists.txt
# tests/CMakeLists.txt
# 查找 GTest
find_package(GTest REQUIRED)
# 添加测试
add_test_target(test_calculator)
add_test_target(test_utils)
# 设置测试标签
set_tests_properties(test_calculator PROPERTIES LABELS "unit")
set_tests_properties(test_utils PROPERTIES LABELS "unit")
# 添加性能测试
add_executable(benchmark benchmark.cpp)
target_link_libraries(benchmark PRIVATE CalculatorLib benchmark::benchmark)
add_test(NAME benchmark COMMAND benchmark)
set_tests_properties(benchmark PROPERTIES LABELS "performance")13.8 docs/CMakeLists.txt
# docs/CMakeLists.txt
# 配置 Doxygen
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
@ONLY
)
# 添加文档目标
add_custom_target(docs
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "生成 API 文档"
VERBATIM
)
# 安装文档
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
DESTINATION share/doc/${PROJECT_NAME}
OPTIONAL
)13.9 CMakePresets.json
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "默认配置",
"description": "默认构建配置",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"BUILD_TESTS": "ON",
"ENABLE_SANITIZERS": "ON"
}
},
{
"name": "release",
"inherits": "default",
"displayName": "Release 配置",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ENABLE_SANITIZERS": "OFF"
}
},
{
"name": "windows",
"displayName": "Windows 配置",
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"binaryDir": "${sourceDir}/build/windows",
"cacheVariables": {
"BUILD_SHARED_LIBS": "ON"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default"
},
{
"name": "release",
"configurePreset": "release"
}
],
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"output": {
"outputOnFailure": true
}
}
]
}14. 最佳实践
14.1 现代 CMake 原则
# 1. 使用目标(Targets)而非变量
# ❌ 不好的做法
set(SOURCES main.cpp)
set(LIBS mylib)
add_executable(myapp ${SOURCES})
target_link_libraries(myapp ${LIBS})
# ✅ 好的做法
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# 2. 使用 target_include_directories 而非 include_directories
# ❌ 不好的做法
include_directories(include)
add_executable(myapp main.cpp)
# ✅ 好的做法
add_executable(myapp main.cpp)
target_include_directories(myapp PRIVATE include)
# 3. 明确指定可见性
target_link_libraries(myapp
PRIVATE
internal_lib # 仅供内部使用
PUBLIC
public_api_lib # 会传播给依赖者
INTERFACE
header_only_lib # 仅接口需要
)
# 4. 使用生成器表达式
target_compile_definitions(myapp PRIVATE
$<$<CONFIG:Debug>:DEBUG>
$<$<PLATFORM_ID:Windows>:WINDOWS>
)14.2 避免常见错误
# 1. 不要使用 file(GLOB)
# ❌ 不好的做法
file(GLOB SOURCES "src/*.cpp")
add_executable(myapp ${SOURCES})
# ✅ 好的做法
add_executable(myapp
src/main.cpp
src/utils.cpp
src/network.cpp
)
# 2. 不要修改 CMAKE_CXX_FLAGS 直接
# ❌ 不好的做法
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
# ✅ 好的做法
add_compile_options(-Wall)
# 或
target_compile_options(myapp PRIVATE -Wall)
# 3. 不要使用 link_directories
# ❌ 不好的做法
link_directories(/custom/lib)
add_executable(myapp main.cpp)
target_link_libraries(myapp mylib)
# ✅ 好的做法
find_library(MYLIB mylib PATHS /custom/lib)
target_link_libraries(myapp PRIVATE ${MYLIB})
# 4. 不要假设 Unix 环境
if(NOT WIN32)
# 这样会排除 Windows,但也可能排除其他平台
endif()
# 更好的做法
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Linux 特定代码
elseif(APPLE)
# macOS 特定代码
elseif(WIN32)
# Windows 特定代码
endif()14.3 性能优化
# 1. 使用 unity 构建(CMake 3.16+)
set_property(TARGET myapp PROPERTY UNITY_BUILD ON)
# 2. 使用预编译头
target_precompile_headers(myapp PRIVATE
<vector>
<string>
<iostream>
)
# 3. 设置输出目录避免重复链接
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 4. 使用 Ninja 生成器(更快)
# cmake -G Ninja ..14.4 可维护性
# 1. 使用函数和宏封装重复代码
function(add_my_library NAME)
add_library(${NAME} ${ARGN})
target_compile_features(${NAME} PUBLIC cxx_std_17)
set_compiler_options(${NAME})
endfunction()
# 2. 使用选项控制功能
option(ENABLE_FEATURE_X "启用特性 X" OFF)
if(ENABLE_FEATURE_X)
target_compile_definitions(myapp PRIVATE FEATURE_X)
endif()
# 3. 使用 cmake_format 格式化
# .cmake-format.py 配置文件
# 4. 添加注释
# 查找 OpenCV 库,用于图像处理功能
find_package(OpenCV COMPONENTS core imgproc REQUIRED)
# 5. 版本检查
if(CMAKE_VERSION VERSION_LESS "3.15")
message(WARNING "CMake 3.15+ 推荐使用,当前版本: ${CMAKE_VERSION}")
endif()14.5 调试辅助
# 添加调试信息
function(print_target_properties TARGET)
if(NOT TARGET ${TARGET})
message("目标 ${TARGET} 不存在")
return()
endif()
get_target_property(SOURCES ${TARGET} SOURCES)
get_target_property(INCLUDE_DIRS ${TARGET} INCLUDE_DIRECTORIES)
get_target_property(COMPILE_DEFS ${TARGET} COMPILE_DEFINITIONS)
get_target_property(LINK_LIBS ${TARGET} LINK_LIBRARIES)
message("目标 ${TARGET}:")
message(" 源文件: ${SOURCES}")
message(" 包含目录: ${INCLUDE_DIRS}")
message(" 编译定义: ${COMPILE_DEFS}")
message(" 链接库: ${LINK_LIBS}")
endfunction()
# 使用
print_target_properties(myapp)总结
编写高质量的 CMakeLists.txt 文件需要:
- 基础:理解基本语法、变量、目标
- 结构:合理组织项目结构,使用子目录
- 依赖:正确查找和链接库
- 可移植:考虑不同平台和编译器
- 现代:使用现代 CMake 特性(目标、生成器表达式)
- 可维护:添加注释,使用函数封装,保持清晰
记住现代 CMake 的核心原则:
- 以目标为中心:所有操作都围绕目标
- 明确可见性:PRIVATE/PUBLIC/INTERFACE
- 使用生成器表达式:实现条件配置
- 避免全局修改:尽量使用目标级别的设置
遵循这些原则,你可以写出清晰、可维护、跨平台的 CMakeLists.txt 文件。