简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français

站内搜索

搜索

活动公告

11-02 12:46
10-23 09:32
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31
10-23 09:28
通知:签到时间调整为每日4:00(东八区)
10-23 09:26

CMake语法实用手册详解常用命令与配置助你轻松驾驭跨平台项目构建

3万

主题

423

科技点

3万

积分

大区版主

木柜子打湿

积分
31916

三倍冰淇淋无人之境【一阶】财Doro小樱(小丑装)立华奏以外的星空【二阶】⑨的冰沙

发表于 2025-10-1 10:20:00 | 显示全部楼层 |阅读模式 [标记阅至此楼]

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

CMake是一个开源、跨平台的构建自动化工具,它使用平台无关的配置文件(CMakeLists.txt)来生成标准的构建文件(如Unix的Makefile或Windows的Visual Studio项目)。CMake的设计初衷是解决跨平台构建的复杂性,使开发者能够用一套配置文件管理多个平台的项目构建。

在软件开发中,不同平台(Windows、Linux、macOS等)和不同编译器(GCC、Clang、MSVC等)有不同的构建系统和约定。CMake通过提供一个抽象层,让开发者可以专注于项目本身,而不必为每个平台维护不同的构建文件。

CMake广泛应用于开源项目和商业软件中,包括知名的软件如KDE、VTK、ITK、Blender等。掌握CMake不仅能够简化构建过程,还能提高项目的可维护性和可移植性。

本手册将详细介绍CMake的语法、常用命令和配置技巧,帮助你轻松驾驭跨平台项目构建。

CMake基础

CMake语法基础

CMake脚本语言具有简单直观的语法,主要由命令(命令名不区分大小写,但参数区分大小写)、变量和注释组成。

CMake中的注释以#开头,一直到行尾:
  1. # 这是一个单行注释
复制代码

CMake命令的基本语法是:
  1. command(参数1 参数2 参数3 ...)
复制代码

命令名不区分大小写,但按照惯例,通常使用小写。参数可以是变量、字符串或其他值。例如:
  1. message(STATUS "Hello, CMake!")
复制代码

在CMake中,变量使用set()命令设置,使用${VAR_NAME}语法引用:
  1. # 设置变量
  2. set(MY_VARIABLE "Hello, World!")
  3. # 引用变量
  4. message(STATUS ${MY_VARIABLE})
复制代码

CMake有一些内置变量,如CMAKE_SOURCE_DIR(指向CMakeLists.txt所在的顶级目录)、CMAKE_BINARY_DIR(指向构建目录)等。

CMake中的变量可以存储列表,列表是以分号分隔的字符串:
  1. # 创建列表
  2. set(MY_LIST "item1" "item2" "item3")
  3. # 或者使用分号分隔
  4. set(ANOTHER_LIST "item1;item2;item3")
  5. # 访问列表元素
  6. message(STATUS ${MY_LIST})  # 输出: item1;item2;item3
  7. message(STATUS ${MY_LIST[0]})  # 输出: item1
复制代码

CMake脚本执行流程

CMake按照以下顺序处理CMakeLists.txt文件:

1. 读取CMakeLists.txt文件
2. 解析并执行命令
3. 生成构建系统(如Makefile或Visual Studio项目)

CMake使用变量CMAKE_CURRENT_SOURCE_DIR和CMAKE_CURRENT_BINARY_DIR来跟踪当前处理的源目录和二进制目录。

常用CMake命令详解

项目设置命令

指定运行此CMakeLists.txt所需的最低CMake版本:
  1. cmake_minimum_required(VERSION 3.10)
复制代码

这个命令应该在CMakeLists.txt的开头使用,以确保使用兼容的CMake版本。如果使用的CMake版本低于指定版本,CMake会报错。

设置项目名称和版本:
  1. project(MyProject VERSION 1.0.0 LANGUAGES CXX)
复制代码

project()命令会设置一些变量,如PROJECT_NAME(项目名称)、PROJECT_VERSION(项目版本)等。LANGUAGES参数指定项目使用的编程语言,默认是C和CXX。

目标定义命令

添加一个可执行文件目标:
  1. # 简单形式
  2. add_executable(my_app main.cpp)
  3. # 指定源文件列表
  4. set(SOURCES main.cpp utils.cpp)
  5. add_executable(my_app ${SOURCES})
  6. # 指定WIN32_EXECUTABLE属性(Windows GUI应用)
  7. add_executable(my_app WIN32 main.cpp)
复制代码

添加一个库目标:
  1. # 静态库
  2. add_library(my_lib STATIC source1.cpp source2.cpp)
  3. # 共享库
  4. add_library(my_lib SHARED source1.cpp source2.cpp)
  5. # 模块库(某些平台不支持)
  6. add_library(my_lib MODULE source1.cpp source2.cpp)
  7. # 接口库(仅包含头文件,不编译源文件)
  8. add_library(my_lib INTERFACE)
复制代码

为目标指定包含目录:
  1. # 为my_lib目标添加包含目录
  2. target_include_directories(my_lib PUBLIC
  3.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  4.     $<INSTALL_INTERFACE:include>
  5. )
  6. # 为my_app目标添加包含目录
  7. target_include_directories(my_app PRIVATE
  8.     ${CMAKE_CURRENT_SOURCE_DIR}/include
  9. )
复制代码

这里的PUBLIC、PRIVATE和INTERFACE指定了包含目录的可见性:

• PUBLIC:对当前目标和依赖此目标的目标都可见
• PRIVATE:仅对当前目标可见
• INTERFACE:仅对依赖此目标的目标可见

为目标指定链接的库:
  1. # 链接库
  2. target_link_libraries(my_app PRIVATE my_lib)
  3. # 链接外部库
  4. target_link_libraries(my_app PRIVATE pthread)
  5. # 链接系统库
  6. target_link_libraries(my_app PRIVATE ${CMAKE_DL_LIBS})
复制代码

为目标指定编译定义:
  1. # 为my_app添加编译定义
  2. target_compile_definitions(my_app PRIVATE MY_DEFINE=1)
  3. # 为my_lib添加公共编译定义
  4. target_compile_definitions(my_lib PUBLIC USE_MY_LIB)
复制代码

为目标指定编译选项:
  1. # 为my_app添加编译选项
  2. target_compile_options(my_app PRIVATE -Wall -Wextra)
  3. # 使用生成器表达式
  4. target_compile_options(my_lib PRIVATE
  5.     $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
  6.     $<$<CXX_COMPILER_ID:MSVC>:/W4>
  7. )
复制代码

查找依赖命令

查找并加载外部项目设置:
  1. # 查找Boost库
  2. find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
  3. # 如果找到,链接到目标
  4. if(Boost_FOUND)
  5.     target_link_libraries(my_app PRIVATE Boost::filesystem Boost::system)
  6. endif()
复制代码

find_package有两种模式:Module模式和Config模式。Module模式查找Find.cmake文件,Config模式查找Config.cmake文件。

查找库文件:
  1. # 查找math库
  2. find_library(MATH_LIB m)
  3. if(MATH_LIB)
  4.     target_link_libraries(my_app PRIVATE ${MATH_LIB})
  5. endif()
复制代码

查找包含文件的路径:
  1. # 查找包含header.h的路径
  2. find_path(HEADER_INCLUDE_DIR header.h
  3.     PATHS /usr/include /usr/local/include
  4. )
  5. if(HEADER_INCLUDE_DIR)
  6.     target_include_directories(my_app PRIVATE ${HEADER_INCLUDE_DIR})
  7. endif()
复制代码

查找文件的完整路径:
  1. # 查找config.txt文件
  2. find_file(CONFIG_FILE config.txt
  3.     PATHS /etc /usr/local/etc
  4. )
  5. if(CONFIG_FILE)
  6.     message(STATUS "Found config file: ${CONFIG_FILE}")
  7. endif()
复制代码

安装规则命令

定义安装规则:
  1. # 安装目标
  2. install(TARGETS my_app my_lib
  3.     RUNTIME DESTINATION bin
  4.     LIBRARY DESTINATION lib
  5.     ARCHIVE DESTINATION lib
  6. )
  7. # 安装文件
  8. install(FILES ${HEADER_FILES} DESTINATION include)
  9. # 安装目录
  10. install(DIRECTORY include/ DESTINATION include)
  11. # 安装脚本
  12. install(SCRIPT cmake_install.cmake)
复制代码

DESTINATION指定安装路径,是相对于CMAKE_INSTALL_PREFIX的相对路径。

测试相关命令

启用测试功能:
  1. enable_testing()
复制代码

这个命令应该在CMakeLists.txt的顶层调用,以启用CTest测试功能。

添加测试:
  1. # 添加简单测试
  2. add_test(NAME my_test COMMAND my_app)
  3. # 添加带参数的测试
  4. add_test(NAME my_test_with_args COMMAND my_app arg1 arg2)
  5. # 设置测试属性
  6. set_tests_properties(my_test PROPERTIES
  7.     WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
  8. )
复制代码

CMake变量与属性

CMake变量

CMake变量用于存储值,可以在整个构建过程中使用。变量的作用域取决于它们被定义的位置。

使用set()命令设置变量:
  1. # 设置简单变量
  2. set(MY_VAR "value")
  3. # 设置列表变量
  4. set(MY_LIST "item1" "item2" "item3")
  5. # 设置缓存变量(在CMake运行之间保持)
  6. set(MY_CACHE_VAR "value" CACHE STRING "Description")
复制代码

缓存变量存储在CMakeCache.txt文件中,可以在CMake运行之间保持。

使用${VAR_NAME}语法引用变量:
  1. message(STATUS "MY_VAR = ${MY_VAR}")
复制代码

可以使用$ENV{VAR_NAME}语法访问环境变量:
  1. message(STATUS "HOME directory: $ENV{HOME}")
复制代码

使用set(ENV{VAR_NAME} value)设置环境变量:
  1. set(ENV{PATH} "$ENV{PATH};/new/path")
复制代码

CMake属性

属性是与目录、目标、源文件、测试等关联的命名值。属性可以控制构建过程的行为。

使用set_property()命令设置属性:
  1. # 设置目标属性
  2. set_property(TARGET my_app PROPERTY
  3.     CXX_STANDARD 11
  4.     CXX_STANDARD_REQUIRED ON
  5. )
  6. # 设置源文件属性
  7. set_property(SOURCE main.cpp PROPERTY
  8.     COMPILE_DEFINITIONS DEBUG=1
  9. )
  10. # 设置目录属性
  11. set_property(DIRECTORY . PROPERTY
  12.     INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include
  13. )
复制代码

使用get_property()命令获取属性:
  1. # 获取目标属性
  2. get_property(MY_APP_CXX_STANDARD TARGET my_app PROPERTY CXX_STANDARD)
  3. message(STATUS "my_app CXX standard: ${MY_APP_CXX_STANDARD}")
复制代码

使用define_property()命令定义自定义属性:
  1. define_property(TARGET PROPERTY MY_CUSTOM_PROPERTY
  2.     BRIEF_DOCS "My custom property"
  3.     FULL_DOCS "More detailed description of my custom property"
  4. )
复制代码

条件判断与流程控制

条件判断

CMake提供了if()、elseif()和else()命令进行条件判断:
  1. # 简单条件
  2. if(MY_VAR)
  3.     message(STATUS "MY_VAR is true")
  4. endif()
  5. # 比较条件
  6. if(MY_VAR STREQUAL "value")
  7.     message(STATUS "MY_VAR is 'value'")
  8. endif()
  9. # 数值比较
  10. if(MY_NUMBER EQUAL 42)
  11.     message(STATUS "MY_NUMBER is 42")
  12. endif()
  13. # 逻辑操作
  14. if(MY_VAR AND (MY_NUMBER GREATER 10 OR MY_NUMBER LESS 5))
  15.     message(STATUS "Complex condition")
  16. endif()
  17. # 检查变量是否定义
  18. if(DEFINED MY_VAR)
  19.     message(STATUS "MY_VAR is defined")
  20. endif()
  21. # 检查命令是否存在
  22. if(COMMAND some_command)
  23.     message(STATUS "some_command exists")
  24. endif()
复制代码

循环

CMake提供了foreach()和while()命令进行循环:
  1. # foreach循环
  2. foreach(item IN LISTS MY_LIST)
  3.     message(STATUS "Item: ${item}")
  4. endforeach()
  5. # 范围循环
  6. foreach(i RANGE 1 5)
  7.     message(STATUS "i = ${i}")
  8. endforeach()
  9. # while循环
  10. set(i 0)
  11. while(i LESS 5)
  12.     message(STATUS "i = ${i}")
  13.     math(EXPR i "${i} + 1")
  14. endwhile()
复制代码

流程控制

CMake提供了一些流程控制命令:

从函数或文件返回:
  1. function(my_function)
  2.     if(CONDITION)
  3.         return()
  4.     endif()
  5.     # 其他代码
  6. endfunction()
复制代码

跳出循环:
  1. foreach(item IN LISTS MY_LIST)
  2.     if(item STREQUAL "stop")
  3.         break()
  4.     endif()
  5.     message(STATUS "Item: ${item}")
  6. endforeach()
复制代码

继续下一次循环:
  1. foreach(item IN LISTS MY_LIST)
  2.     if(item STREQUAL "skip")
  3.         continue()
  4.     endif()
  5.     message(STATUS "Item: ${item}")
  6. endforeach()
复制代码

函数与宏的定义与使用

函数

使用function()命令定义函数:
  1. # 定义函数
  2. function(print_message message)
  3.     message(STATUS "Message: ${message}")
  4. endfunction()
  5. # 调用函数
  6. print_message("Hello, CMake!")
复制代码

函数有自己的变量作用域,参数通过${ARGN}、${ARGV}、${ARGC}等变量访问:
  1. # 定义可变参数函数
  2. function(print_all)
  3.     message(STATUS "Number of arguments: ${ARGC}")
  4.     foreach(i RANGE 1 ${ARGC})
  5.         message(STATUS "Argument ${i}: ${ARGV${i}}")
  6.     endforeach()
  7. endfunction()
  8. # 调用函数
  9. print_all("arg1" "arg2" "arg3")
复制代码



使用macro()命令定义宏:
  1. # 定义宏
  2. macro(print_message message)
  3.     message(STATUS "Message: ${message}")
  4. endmacro()
  5. # 调用宏
  6. print_message("Hello, CMake!")
复制代码

宏与函数的主要区别在于:

• 宏没有自己的变量作用域,它们在调用者的作用域中执行
• 宏的参数是文本替换,而不是值传递

返回值

CMake函数和宏不能直接返回值,但可以通过以下方式模拟返回值:
  1. # 通过参数返回值
  2. function(get_result out_var)
  3.     set(${out_var} "result" PARENT_SCOPE)
  4. endfunction()
  5. # 调用函数
  6. get_result(result)
  7. message(STATUS "Result: ${result}")
  8. # 使用return()命令
  9. function(get_result)
  10.     return("result")
  11. endfunction()
  12. # 调用函数
  13. get_result(result)
  14. message(STATUS "Result: ${result}")
复制代码

CMake项目结构最佳实践

基本项目结构

一个典型的CMake项目结构如下:
  1. my_project/
  2. ├── CMakeLists.txt          # 顶层CMakeLists.txt
  3. ├── src/                    # 源代码目录
  4. │   ├── CMakeLists.txt      # src目录的CMakeLists.txt
  5. │   ├── lib/
  6. │   │   ├── CMakeLists.txt
  7. │   │   ├── my_lib.cpp
  8. │   │   └── my_lib.h
  9. │   └── app/
  10. │       ├── CMakeLists.txt
  11. │       └── main.cpp
  12. ├── include/                # 头文件目录
  13. │   └── my_lib/
  14. │       └── my_lib.h
  15. ├── tests/                  # 测试目录
  16. │   ├── CMakeLists.txt
  17. │   └── test_my_lib.cpp
  18. ├── cmake/                  # CMake模块目录
  19. │   └── FindSomeLib.cmake
  20. ├── examples/               # 示例目录
  21. │   ├── CMakeLists.txt
  22. │   └── example.cpp
  23. └── README.md
复制代码

顶层CMakeLists.txt示例
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyProject VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 11)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加子目录
  7. add_subdirectory(src)
  8. add_subdirectory(tests)
  9. add_subdirectory(examples)
  10. # 设置安装路径
  11. include(GNUInstallDirs)
  12. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
  13. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
  14. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
  15. # 配置文件
  16. configure_file(
  17.     "${PROJECT_SOURCE_DIR}/cmake/MyProjectConfig.cmake.in"
  18.     "${PROJECT_BINARY_DIR}/MyProjectConfig.cmake"
  19.     @ONLY
  20. )
  21. # 安装配置文件
  22. install(FILES "${PROJECT_BINARY_DIR}/MyProjectConfig.cmake"
  23.     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
  24. )
复制代码

库CMakeLists.txt示例
  1. # 设置源文件
  2. set(LIB_SOURCES
  3.     my_lib.cpp
  4. )
  5. # 创建库
  6. add_library(my_lib ${LIB_SOURCES})
  7. # 设置包含目录
  8. target_include_directories(my_lib
  9.     PUBLIC
  10.         $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../include>
  11.         $<INSTALL_INTERFACE:include>
  12. )
  13. # 设置编译定义
  14. target_compile_definitions(my_lib
  15.     PRIVATE MY_LIB_EXPORTS
  16. )
  17. # 设置链接库
  18. target_link_libraries(my_lib
  19.     PUBLIC
  20.         # 公共依赖
  21.     PRIVATE
  22.         # 私有依赖
  23. )
  24. # 设置属性
  25. set_target_properties(my_lib PROPERTIES
  26.     VERSION ${PROJECT_VERSION}
  27.     SOVERSION ${PROJECT_VERSION_MAJOR}
  28. )
  29. # 安装规则
  30. install(TARGETS my_lib
  31.     EXPORT MyProjectTargets
  32.     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  33.     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  34.     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  35. )
  36. # 安装头文件
  37. install(DIRECTORY ../../include/my_lib
  38.     DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  39. )
复制代码

应用程序CMakeLists.txt示例
  1. # 设置源文件
  2. set(APP_SOURCES
  3.     main.cpp
  4. )
  5. # 创建可执行文件
  6. add_executable(my_app ${APP_SOURCES})
  7. # 设置包含目录
  8. target_include_directories(my_app
  9.     PRIVATE
  10.         ${CMAKE_CURRENT_SOURCE_DIR}/../../include
  11. )
  12. # 链接库
  13. target_link_libraries(my_app
  14.     PRIVATE
  15.         my_lib
  16. )
  17. # 安装规则
  18. install(TARGETS my_app
  19.     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  20. )
复制代码

跨平台构建技巧

平台检测

CMake提供了一些变量来检测当前平台:
  1. # 操作系统检测
  2. if(WIN32)
  3.     message(STATUS "Windows")
  4. elseif(UNIX AND NOT APPLE)
  5.     message(STATUS "Linux")
  6. elseif(APPLE)
  7.     message(STATUS "macOS")
  8. endif()
  9. # 编译器检测
  10. if(MSVC)
  11.     message(STATUS "MSVC compiler")
  12.     add_compile_options(/W4)
  13. elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
  14.     message(STATUS "GCC or Clang compiler")
  15.     add_compile_options(-Wall -Wextra)
  16. endif()
  17. # 架构检测
  18. if(CMAKE_SIZEOF_VOID_P EQUAL 8)
  19.     message(STATUS "64-bit system")
  20. else()
  21.     message(STATUS "32-bit system")
  22. endif()
复制代码

平台特定的代码和库

处理平台特定的代码和库:
  1. # 平台特定的源文件
  2. if(WIN32)
  3.     list(APPEND SOURCES win_specific.cpp)
  4. elseif(UNIX)
  5.     list(APPEND SOURCES unix_specific.cpp)
  6. endif()
  7. # 平台特定的库
  8. find_package(Threads REQUIRED)
  9. if(WIN32)
  10.     target_link_libraries(my_app PRIVATE wsock32)
  11. else()
  12.     target_link_libraries(my_app PRIVATE pthread)
  13. endif()
  14. # 平台特定的编译选项
  15. if(MSVC)
  16.     target_compile_options(my_app PRIVATE /W4)
  17. else()
  18.     target_compile_options(my_app PRIVATE -Wall -Wextra)
  19. endif()
复制代码

路径处理

处理跨平台路径问题:
  1. # 使用file命令处理路径
  2. file(TO_CMAKE_PATH "/path/to/file" PATH_VAR)
  3. file(TO_NATIVE_PATH "${PATH_VAR}" NATIVE_PATH)
  4. # 使用生成器表达式处理路径
  5. target_include_directories(my_app PRIVATE
  6.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
  7.     $<INSTALL_INTERFACE:include>
  8. )
  9. # 使用路径组件
  10. get_filename_component(DIR ${PATH} DIRECTORY)
  11. get_filename_component(NAME ${PATH} NAME_WE)
  12. get_filename_component(EXT ${PATH} EXT)
复制代码

生成器表达式

生成器表达式是CMake的强大功能,可以在生成构建系统时进行条件评估:
  1. # 基于配置的生成器表达式
  2. target_compile_definitions(my_app PRIVATE
  3.     $<$<CONFIG:Debug>:DEBUG_BUILD>
  4.     $<$<CONFIG:Release>:NDEBUG>
  5. )
  6. # 基于平台的生成器表达式
  7. target_link_libraries(my_app PRIVATE
  8.     $<$<PLATFORM_ID:Windows>:ws2_32>
  9.     $<$<PLATFORM_ID:Linux>:pthread>
  10. )
  11. # 基于编译器的生成器表达式
  12. target_compile_options(my_app PRIVATE
  13.     $<$<CXX_COMPILER_ID:MSVC>:/W4>
  14.     $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
  15. )
  16. # 基于语言特性的生成器表达式
  17. target_compile_features(my_app PRIVATE
  18.     $<$<CXX_COMPILER_ID:GNU,Clang>:cxx_std_11>
  19.     $<$<CXX_COMPILER_ID:MSVC>:cxx_std_14>
  20. )
复制代码

高级主题

CMake模块

CMake模块是可重用的CMake代码片段,通常以.cmake为扩展名。可以使用include()命令包含模块:
  1. # 包含模块
  2. include(FindPackageHandleStandardArgs)
  3. # 使用模块中的函数
  4. find_package_handle_standard_args(MyLib
  5.     DEFAULT_MSG
  6.     MYLIB_INCLUDE_DIR
  7.     MYLIB_LIBRARY
  8. )
复制代码

自定义Find模块

创建自定义的Find模块来查找第三方库:
  1. # FindMyLib.cmake
  2. find_path(MYLIB_INCLUDE_DIR
  3.     NAMES mylib.h
  4.     PATHS /usr/include /usr/local/include
  5. )
  6. find_library(MYLIB_LIBRARY
  7.     NAMES mylib
  8.     PATHS /usr/lib /usr/local/lib
  9. )
  10. include(FindPackageHandleStandardArgs)
  11. find_package_handle_standard_args(MyLib
  12.     DEFAULT_MSG
  13.     MYLIB_INCLUDE_DIR
  14.     MYLIB_LIBRARY
  15. )
  16. if(MYLIB_FOUND)
  17.     if(NOT TARGET MyLib::MyLib)
  18.         add_library(MyLib::MyLib UNKNOWN IMPORTED)
  19.         set_target_properties(MyLib::MyLib PROPERTIES
  20.             IMPORTED_LOCATION "${MYLIB_LIBRARY}"
  21.             INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
  22.         )
  23.     endif()
  24. endif()
  25. mark_as_advanced(MYLIB_INCLUDE_DIR MYLIB_LIBRARY)
复制代码

配置文件模板

使用配置文件模板生成配置文件:
  1. # MyConfig.h.in
  2. #pragma once
  3. #define MY_APP_VERSION "@PROJECT_VERSION@"
  4. #define MY_APP_NAME "@PROJECT_NAME@"
  5. #cmakedefine USE_FEATURE_A
  6. #cmakedefine USE_FEATURE_B
  7. // 路径示例
  8. #define INSTALL_PATH "@CMAKE_INSTALL_PREFIX@"
复制代码
  1. # CMakeLists.txt
  2. # 配置文件
  3. configure_file(
  4.     "${PROJECT_SOURCE_DIR}/MyConfig.h.in"
  5.     "${PROJECT_BINARY_DIR}/MyConfig.h"
  6.     @ONLY
  7. )
  8. # 添加包含目录
  9. target_include_directories(my_app PRIVATE
  10.     ${PROJECT_BINARY_DIR}
  11. )
复制代码

自定义命令和目标

使用add_custom_command()和add_custom_target()创建自定义构建规则:
  1. # 自定义命令(生成源文件)
  2. add_custom_command(
  3.     OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.cpp
  4.     COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate.py
  5.         -o ${CMAKE_CURRENT_BINARY_DIR}/generated.cpp
  6.     DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate.py
  7.     WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  8.     COMMENT "Generating generated.cpp"
  9. )
  10. # 自定义目标(运行测试)
  11. add_custom_target(run_tests
  12.     COMMAND ${CMAKE_CTEST_COMMAND} --verbose
  13.     DEPENDS my_app
  14.     WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
  15. )
复制代码

打包和分发

使用CPack创建安装包:
  1. # 包含CPack
  2. include(InstallRequiredSystemLibraries)
  3. include(CPack)
  4. # 设置CPack变量
  5. set(CPACK_PACKAGE_NAME "MyProject")
  6. set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
  7. set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Project Description")
  8. set(CPACK_PACKAGE_VENDOR "My Company")
  9. # 设置包类型
  10. if(WIN32)
  11.     set(CPACK_GENERATOR ZIP NSIS)
  12. elseif(UNIX)
  13.     set(CPACK_GENERATOR TGZ DEB RPM)
  14. endif()
复制代码

实际案例:构建一个跨平台项目

让我们通过一个完整的例子来展示如何使用CMake构建一个跨平台项目。这个项目包含一个库和一个使用该库的应用程序。

项目结构
  1. my_project/
  2. ├── CMakeLists.txt
  3. ├── include/
  4. │   └── mylib/
  5. │       └── mylib.h
  6. ├── src/
  7. │   ├── CMakeLists.txt
  8. │   ├── mylib.cpp
  9. │   └── app.cpp
  10. └── tests/
  11.     ├── CMakeLists.txt
  12.     └── test_mylib.cpp
复制代码

头文件 (include/mylib/mylib.h)
  1. #pragma once
  2. #include <string>
  3. namespace mylib {
  4. class MyLib {
  5. public:
  6.     MyLib(const std::string& name);
  7.     std::string greet() const;
  8.    
  9. private:
  10.     std::string name_;
  11. };
  12. }  // namespace mylib
复制代码

库实现 (src/mylib.cpp)
  1. #include "mylib/mylib.h"
  2. #include <sstream>
  3. namespace mylib {
  4. MyLib::MyLib(const std::string& name) : name_(name) {}
  5. std::string MyLib::greet() const {
  6.     std::ostringstream oss;
  7.     oss << "Hello, " << name_ << "!";
  8.     return oss.str();
  9. }
  10. }  // namespace mylib
复制代码

应用程序 (src/app.cpp)
  1. #include "mylib/mylib.h"
  2. #include <iostream>
  3. int main() {
  4.     mylib::MyLib lib("World");
  5.     std::cout << lib.greet() << std::endl;
  6.     return 0;
  7. }
复制代码

测试文件 (tests/test_mylib.cpp)
  1. #include "mylib/mylib.h"
  2. #include <cassert>
  3. #include <string>
  4. int main() {
  5.     mylib::MyLib lib("Test");
  6.     std::string result = lib.greet();
  7.     assert(result == "Hello, Test!");
  8.     return 0;
  9. }
复制代码

顶层CMakeLists.txt
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyProject VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 11)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加子目录
  7. add_subdirectory(src)
  8. add_subdirectory(tests)
  9. # 设置输出目录
  10. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
  11. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
  12. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
  13. # 打包支持
  14. include(InstallRequiredSystemLibraries)
  15. include(CPack)
  16. set(CPACK_PACKAGE_NAME "MyProject")
  17. set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
  18. set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Project")
  19. set(CPACK_PACKAGE_VENDOR "My Company")
复制代码

src/CMakeLists.txt
  1. # 库
  2. add_library(mylib SHARED mylib.cpp)
  3. target_include_directories(mylib
  4.     PUBLIC
  5.         $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
  6.         $<INSTALL_INTERFACE:include>
  7. )
  8. # 设置属性
  9. set_target_properties(mylib PROPERTIES
  10.     VERSION ${PROJECT_VERSION}
  11.     SOVERSION ${PROJECT_VERSION_MAJOR}
  12. )
  13. # 应用程序
  14. add_executable(myapp app.cpp)
  15. target_link_libraries(myapp PRIVATE mylib)
  16. # 安装规则
  17. install(TARGETS mylib myapp
  18.     EXPORT MyProjectTargets
  19.     LIBRARY DESTINATION lib
  20.     ARCHIVE DESTINATION lib
  21.     RUNTIME DESTINATION bin
  22. )
  23. install(DIRECTORY ../include/mylib
  24.     DESTINATION include
  25. )
  26. # 导出配置
  27. install(EXPORT MyProjectTargets
  28.     FILE MyProjectTargets.cmake
  29.     DESTINATION lib/cmake/MyProject
  30. )
  31. # 配置文件
  32. include(CMakePackageConfigHelpers)
  33. configure_package_config_file(
  34.     "${CMAKE_CURRENT_SOURCE_DIR}/MyProjectConfig.cmake.in"
  35.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake"
  36.     INSTALL_DESTINATION lib/cmake/MyProject
  37. )
  38. write_basic_package_version_file(
  39.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
  40.     VERSION ${PROJECT_VERSION}
  41.     COMPATIBILITY AnyNewerVersion
  42. )
  43. install(FILES
  44.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake"
  45.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
  46.     DESTINATION lib/cmake/MyProject
  47. )
复制代码

tests/CMakeLists.txt
  1. # 启用测试
  2. enable_testing()
  3. # 测试可执行文件
  4. add_executable(test_mylib test_mylib.cpp)
  5. target_link_libraries(test_mylib PRIVATE mylib)
  6. # 添加测试
  7. add_test(NAME test_mylib COMMAND test_mylib)
复制代码

MyProjectConfig.cmake.in (src/MyProjectConfig.cmake.in)
  1. @PACKAGE_INIT@
  2. include("${CMAKE_CURRENT_LIST_DIR}/MyProjectTargets.cmake")
  3. check_required_components(MyProject)
复制代码

构建和测试
  1. # 创建构建目录
  2. mkdir build && cd build
  3. # 配置项目
  4. cmake ..
  5. # 构建
  6. cmake --build .
  7. # 运行测试
  8. ctest --verbose
  9. # 安装
  10. cmake --install . --prefix /path/to/install
  11. # 创建包
  12. cpack -G TGZ
复制代码

常见问题与解决方案

问题1:如何处理第三方依赖?

解决方案:使用ExternalProject或FetchContent模块。
  1. # 使用FetchContent (CMake 3.11+)
  2. include(FetchContent)
  3. FetchContent_Declare(
  4.     googletest
  5.     GIT_REPOSITORY https://github.com/google/googletest.git
  6.     GIT_TAG main
  7. )
  8. FetchContent_MakeAvailable(googletest)
  9. # 使用FetchContent获取的库
  10. target_link_libraries(myapp PRIVATE gtest_main)
复制代码

问题2:如何处理不同构建类型(Debug、Release等)?

解决方案:使用CMAKE_BUILD_TYPE变量和生成器表达式。
  1. # 设置构建类型
  2. if(NOT CMAKE_BUILD_TYPE)
  3.     set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
  4. endif()
  5. # 基于构建类型的设置
  6. target_compile_definitions(myapp PRIVATE
  7.     $<$<CONFIG:Debug>:DEBUG>
  8.     $<$<CONFIG:Release>:NDEBUG>
  9. )
  10. # 设置输出目录基于构建类型
  11. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE})
  12. set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE})
  13. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE})
复制代码

问题3:如何处理不同平台的路径分隔符?

解决方案:使用CMake的路径处理函数和生成器表达式。
  1. # 使用file命令处理路径
  2. file(TO_CMAKE_PATH "C:\\Path\\To\\File" CMAKE_PATH)
  3. file(TO_NATIVE_PATH "/unix/path/to/file" NATIVE_PATH)
  4. # 使用路径组件
  5. get_filename_component(DIR ${PATH} DIRECTORY)
  6. get_filename_component(NAME ${PATH} NAME_WE)
  7. # 使用生成器表达式处理路径
  8. target_include_directories(myapp PRIVATE
  9.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
  10.     $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
  11. )
复制代码

问题4:如何处理编译器特定的警告和标志?

解决方案:使用生成器表达式和编译器ID检测。
  1. # 检测编译器
  2. if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
  3.     add_compile_options(-Wall -Wextra -pedantic)
  4. elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  5.     add_compile_options(/W4)
  6. endif()
  7. # 使用生成器表达式
  8. target_compile_options(myapp PRIVATE
  9.     $<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
  10.     $<$<CXX_COMPILER_ID:MSVC>:/W4>
  11. )
复制代码

问题5:如何创建可重用的CMake函数和宏?

解决方案:定义函数和宏,并将它们放在单独的.cmake文件中。
  1. # CMakeFunctions.cmake
  2. function(add_my_library name)
  3.     cmake_parse_arguments(ARG
  4.         ""
  5.         "VERSION"
  6.         "SOURCES;HEADERS;DEPENDENCIES"
  7.         ${ARGN}
  8.     )
  9.    
  10.     add_library(${name} ${ARG_SOURCES})
  11.    
  12.     target_include_directories(${name}
  13.         PUBLIC
  14.             $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
  15.             $<INSTALL_INTERFACE:include>
  16.     )
  17.    
  18.     if(ARG_VERSION)
  19.         set_target_properties(${name} PROPERTIES VERSION ${ARG_VERSION})
  20.     endif()
  21.    
  22.     if(ARG_DEPENDENCIES)
  23.         target_link_libraries(${name} PUBLIC ${ARG_DEPENDENCIES})
  24.     endif()
  25.    
  26.     # 安装规则
  27.     install(TARGETS ${name}
  28.         EXPORT ${name}Targets
  29.         LIBRARY DESTINATION lib
  30.         ARCHIVE DESTINATION lib
  31.         RUNTIME DESTINATION bin
  32.     )
  33.    
  34.     if(ARG_HEADERS)
  35.         install(FILES ${ARG_HEADERS} DESTINATION include)
  36.     endif()
  37. endfunction()
复制代码

然后在CMakeLists.txt中包含并使用:
  1. include(CMakeFunctions)
  2. add_my_library(mylib
  3.     VERSION 1.0.0
  4.     SOURCES mylib.cpp
  5.     HEADERS mylib.h
  6.     DEPENDENCIES some_other_lib
  7. )
复制代码

总结与资源推荐

CMake是一个强大的跨平台构建工具,掌握它能够大大简化项目构建和管理的复杂性。本手册介绍了CMake的基本语法、常用命令、变量和属性、条件判断与流程控制、函数与宏、项目结构最佳实践、跨平台构建技巧以及高级主题。

通过学习这些内容,你应该能够:

• 编写基本的CMakeLists.txt文件
• 管理项目依赖和链接库
• 创建可安装的库和应用程序
• 处理跨平台构建问题
• 使用高级CMake特性解决复杂问题

推荐资源

1. 官方文档:CMake官方文档CMake教程
2. CMake官方文档
3. CMake教程
4. 书籍:“Professional CMake: A Practical Guide” by Craig Scott“Mastering CMake” by Ken Martin and Bill Hoffman
5. “Professional CMake: A Practical Guide” by Craig Scott
6. “Mastering CMake” by Ken Martin and Bill Hoffman
7. 在线资源:CMake Discourse论坛CMake WikiModern CMake
8. CMake Discourse论坛
9. CMake Wiki
10. Modern CMake
11. 示例项目:CMake示例集合Effective Modern CMake
12. CMake示例集合
13. Effective Modern CMake

官方文档:

• CMake官方文档
• CMake教程

书籍:

• “Professional CMake: A Practical Guide” by Craig Scott
• “Mastering CMake” by Ken Martin and Bill Hoffman

在线资源:

• CMake Discourse论坛
• CMake Wiki
• Modern CMake

示例项目:

• CMake示例集合
• Effective Modern CMake

通过不断实践和学习,你将能够熟练使用CMake来管理复杂的跨平台项目构建。记住,CMake是一个不断发展的工具,保持对新版本特性的关注将帮助你更好地利用它的强大功能。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

频道订阅

频道订阅

加入社群

加入社群

联系我们|TG频道|RSS

Powered by Pixtech

© 2025 Pixtech Team.