|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. CMake基础介绍
CMake是一个开源、跨平台的构建自动化工具,它使用平台无关的配置文件来控制软件编译过程,并生成标准的构建文件(如Unix的Makefile或Windows Visual C++的projects/workspaces)。CMake的设计初衷是解决跨平台构建的复杂性,使开发者能够用一套配置文件管理多个平台的构建过程。
1.1 CMake的工作原理
CMake的工作流程主要包括以下几个步骤:
1. 配置阶段:CMake读取CMakeLists.txt文件,处理其中的命令,生成构建系统所需的中间文件。
2. 生成阶段:根据中间文件,生成特定平台和构建工具的原生构建文件(如Makefile、Visual Studio项目等)。
3. 构建阶段:使用生成的原生构建文件进行实际的编译和链接操作。
1.2 基本语法与结构
CMake使用一种简单的脚本语言,命令不区分大小写,但参数和变量是区分大小写的。以下是一个基本的CMakeLists.txt文件示例:
- # 指定CMake最低版本要求
- cmake_minimum_required(VERSION 3.10)
- # 定义项目名称
- project(MyProject VERSION 1.0)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 11)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加可执行文件
- add_executable(my_app main.cpp)
- # 链接库
- target_link_libraries(my_app some_library)
复制代码
1.3 变量与缓存
CMake中的变量分为普通变量和缓存变量。普通变量只在当前作用域有效,而缓存变量会存储在CMakeCache.txt中,在多次CMake运行之间保持不变。
- # 设置普通变量
- set(MY_VARIABLE "value")
- # 设置缓存变量
- set(MY_CACHE_VARIABLE "value" CACHE STRING "Description")
- # 获取变量值
- message(STATUS "Variable value: ${MY_VARIABLE}")
复制代码
2. CMake模块原理与使用
CMake模块是CMake功能扩展的重要方式,它们是一组包含CMake命令的文件,可以被其他CMake脚本包含以复用功能。
2.1 内置模块
CMake提供了许多内置模块,位于CMake安装目录的Modules文件夹中。这些模块提供了查找依赖库、工具链配置、测试等功能。
Find模块用于在系统中查找特定的库或程序。例如,FindBoost.cmake用于查找Boost库:
- # 查找Boost库
- find_package(Boost 1.66 REQUIRED COMPONENTS filesystem system)
- # 如果找到,链接到目标
- if(Boost_FOUND)
- target_link_libraries(my_app Boost::filesystem Boost::system)
- endif()
复制代码
使用内置模块通常通过include命令:
- # 使用CheckFunctionExists模块检查函数是否存在
- include(CheckFunctionExists)
- # 检查函数是否存在
- check_function_exists(malloc HAVE_MALLOC)
- # 根据结果定义宏
- if(HAVE_MALLOC)
- add_definitions(-DHAVE_MALLOC)
- endif()
复制代码
2.2 自定义模块
除了使用内置模块,开发者也可以创建自己的模块以复用代码。
创建一个自定义模块非常简单,只需创建一个.cmake文件,并在其中定义所需的函数或宏。例如,创建一个名为MyUtils.cmake的模块:
- # MyUtils.cmake
- # 定义一个函数,用于打印带颜色的消息
- function(print_color MESSAGE COLOR)
- if(COLOR STREQUAL "RED")
- message(FATAL_ERROR ${MESSAGE})
- elseif(COLOR STREQUAL "GREEN")
- message(STATUS ${MESSAGE})
- elseif(COLOR STREQUAL "YELLOW")
- message(WARNING ${MESSAGE})
- else()
- message(${MESSAGE})
- endif()
- endfunction()
- # 定义一个宏,用于添加测试
- macro(add_test_case NAME)
- add_executable(${NAME} ${NAME}.cpp)
- add_test(NAME ${NAME} COMMAND ${NAME})
- endmacro()
复制代码
要使用自定义模块,需要将其路径添加到CMAKE_MODULE_PATH变量中:
- # 添加自定义模块路径
- list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
- # 包含自定义模块
- include(MyUtils)
- # 使用模块中定义的函数
- print_color("This is a success message" "GREEN")
- # 使用模块中定义的宏
- add_test_case(my_test)
复制代码
2.3 模块的最佳实践
1. 模块命名:使用描述性的名称,并遵循CMake的命名约定。
2. 文档注释:为模块中的函数和宏添加文档注释,说明其用途和参数。
3. 错误处理:在模块中添加适当的错误检查和处理。
4. 命名空间:为避免命名冲突,考虑在函数和宏名称前加前缀。
- # 带有文档注释和错误处理的模块函数
- #[[
- Function to compile a source file with specific flags.
-
- Parameters:
- SOURCE_FILE - The source file to compile
- OUTPUT_NAME - The name of the output file
- COMPILE_FLAGS - Compilation flags to use
- ]]
- function(compile_with_flags SOURCE_FILE OUTPUT_NAME COMPILE_FLAGS)
- # 检查源文件是否存在
- if(NOT EXISTS ${SOURCE_FILE})
- message(FATAL_ERROR "Source file ${SOURCE_FILE} does not exist")
- endif()
-
- # 创建临时可执行文件
- add_executable(${OUTPUT_NAME} ${SOURCE_FILE})
-
- # 设置编译属性
- set_target_properties(${OUTPUT_NAME} PROPERTIES
- COMPILE_FLAGS ${COMPILE_FLAGS}
- )
- endfunction()
复制代码
3. CMake包管理原理
包管理是现代软件开发中的重要组成部分,CMake提供了多种机制来处理软件包的依赖关系。
3.1 find_package机制
find_package是CMake中最常用的包查找机制,它有两种模式:Module模式和Config模式。
在Module模式下,CMake会查找名为Find.cmake的文件。这些文件通常位于CMAKE_MODULE_PATH指定的路径中。
- # 使用Module模式查找包
- find_package(ZLIB REQUIRED)
- # 如果找到,链接到目标
- if(ZLIB_FOUND)
- target_include_directories(my_app PRIVATE ${ZLIB_INCLUDE_DIRS})
- target_link_libraries(my_app ${ZLIB_LIBRARIES})
- endif()
复制代码
在Config模式下,CMake会查找名为Config.cmake或-config.cmake的文件。这些文件通常由包的安装过程生成,位于系统的标准路径中。
- # 使用Config模式查找包
- find_package(OpenCV REQUIRED)
- # 如果找到,链接到目标
- if(OpenCV_FOUND)
- target_link_libraries(my_app ${OpenCV_LIBS})
- endif()
复制代码
3.2 自定义包配置
为了让其他项目能够通过find_package找到你的项目,你需要提供适当的包配置文件。
首先,在项目的CMakeLists.txt中添加以下内容:
- cmake_minimum_required(VERSION 3.10)
- project(MyLib VERSION 1.0.0 LANGUAGES CXX)
- # 添加库
- add_library(mylib src/mylib.cpp)
- # 生成导出头文件
- include(GenerateExportHeader)
- generate_export_header(mylib)
- # 安装规则
- install(TARGETS mylib
- EXPORT MyLibTargets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
- )
- install(FILES
- include/mylib.h
- "${CMAKE_CURRENT_BINARY_DIR}/mylib_export.h"
- DESTINATION include
- )
- # 生成并安装包配置文件
- include(CMakePackageConfigHelpers)
- write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY AnyNewerVersion
- )
- install(EXPORT MyLibTargets
- FILE MyLibTargets.cmake
- DESTINATION lib/cmake/MyLib
- )
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
- DESTINATION lib/cmake/MyLib
- )
- # 生成包配置文件
- configure_package_config_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/MyLibConfig.cmake.in"
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
- INSTALL_DESTINATION lib/cmake/MyLib
- )
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
- DESTINATION lib/cmake/MyLib
- )
复制代码
然后,创建一个MyLibConfig.cmake.in文件:
- @PACKAGE_INIT@
- include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
- check_required_components(MyLib)
复制代码
其他项目现在可以通过find_package找到并使用你的库:
- cmake_minimum_required(VERSION 3.10)
- project(MyApp)
- # 查找自定义包
- find_package(MyLib REQUIRED)
- # 创建可执行文件
- add_executable(myapp main.cpp)
- # 链接库
- target_link_libraries(myapp MyLib::mylib)
复制代码
3.3 现代CMake包管理
现代CMake提供了更多高级的包管理功能,如FetchContent和CPack。
FetchContent是CMake 3.11引入的功能,允许在配置时下载和构建依赖项。
- cmake_minimum_required(VERSION 3.11)
- project(MyApp)
- # 包含FetchContent模块
- include(FetchContent)
- # 声明要获取的内容
- FetchContent_Declare(
- googletest
- GIT_REPOSITORY https://github.com/google/googletest.git
- GIT_TAG main
- )
- # 获取内容
- FetchContent_MakeAvailable(googletest)
- # 创建可执行文件
- add_executable(myapp main.cpp)
- # 链接gtest
- target_link_libraries(myapp gtest_main)
复制代码
CPack是CMake的打包系统,可以生成各种格式的安装包。
- # 在CMakeLists.txt末尾添加
- include(InstallRequiredSystemLibraries)
- set(CPACK_PACKAGE_NAME "MyApp")
- set(CPACK_PACKAGE_VERSION "1.0.0")
- set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Application")
- set(CPACK_PACKAGE_VENDOR "My Company")
- include(CPack)
复制代码
然后,构建项目后运行cpack命令即可生成安装包。
4. 实战技巧与最佳实践
4.1 项目结构组织
良好的项目结构是可维护性的基础。以下是一个推荐的C++项目结构:
- myproject/
- ├── CMakeLists.txt # 主CMake文件
- ├── src/ # 源代码
- │ ├── CMakeLists.txt # src目录的CMake文件
- │ ├── library/
- │ │ ├── CMakeLists.txt
- │ │ ├── lib1.cpp
- │ │ └── lib1.h
- │ └── app/
- │ ├── CMakeLists.txt
- │ └── main.cpp
- ├── include/ # 公共头文件
- │ └── myproject/
- │ └── config.h.in
- ├── tests/ # 测试代码
- │ ├── CMakeLists.txt
- │ └── test1.cpp
- ├── cmake/ # 自定义CMake模块
- │ └── FindSomeLib.cmake
- ├── third_party/ # 第三方库
- │ └── CMakeLists.txt
- ├── docs/ # 文档
- └── README.md
复制代码
主CMakeLists.txt文件可能如下:
- cmake_minimum_required(VERSION 3.10)
- project(MyProject VERSION 1.0.0 LANGUAGES CXX C)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加自定义模块路径
- list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
- # 选项
- option(BUILD_TESTS "Build tests" ON)
- # 配置头文件
- configure_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/include/myproject/config.h.in"
- "${CMAKE_CURRENT_BINARY_DIR}/include/myproject/config.h"
- )
- # 包含子目录
- add_subdirectory(src)
- add_subdirectory(third_party)
- if(BUILD_TESTS)
- enable_testing()
- add_subdirectory(tests)
- endif()
复制代码
4.2 目标导向的CMake
现代CMake推荐使用目标导向的方式,而不是全局设置。这样可以避免全局变量的污染,并提高依赖管理的精确性。
- # 创建库
- add_library(mylib STATIC src/mylib.cpp)
- # 设置库的属性
- target_include_directories(mylib
- PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- PRIVATE
- ${CMAKE_CURRENT_SOURCE_DIR}/src
- )
- target_compile_features(mylib PUBLIC cxx_std_17)
- # 创建可执行文件
- add_executable(myapp src/main.cpp)
- # 链接库
- target_link_libraries(myapp PRIVATE mylib)
复制代码
4.3 条件编译和平台检测
CMake提供了多种方式来处理不同平台和编译器的差异:
- # 检测操作系统
- if(WIN32)
- # Windows特定代码
- add_definitions(-DWIN32_LEAN_AND_MEAN)
- elseif(UNIX AND NOT APPLE)
- # Linux特定代码
- find_package(Threads REQUIRED)
- elseif(APPLE)
- # macOS特定代码
- set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12")
- endif()
- # 检测编译器
- if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- # GCC特定设置
- add_compile_options(-Wall -Wextra)
- elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- # Clang特定设置
- add_compile_options(-Wall -Wextra)
- elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
- # MSVC特定设置
- add_compile_options(/W4)
- endif()
- # 检测架构
- if(CMAKE_SIZEOF_VOID_P EQUAL 8)
- # 64位系统
- set(ARCH_64 TRUE)
- else()
- # 32位系统
- set(ARCH_32 TRUE)
- endif()
复制代码
4.4 构建类型和配置
CMake支持多种构建类型,如Debug、Release、RelWithDebInfo和MinSizeRel:
- # 设置默认构建类型
- if(NOT CMAKE_BUILD_TYPE)
- set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
- endif()
- # 根据构建类型设置不同的编译选项
- set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
- set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
- set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG")
- set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
- # 设置特定于目标的编译选项
- if(CMAKE_BUILD_TYPE STREQUAL "Debug")
- target_compile_definitions(myapp PRIVATE DEBUG_MODE)
- endif()
复制代码
5. 提升项目构建效率与可维护性
5.1 依赖管理
良好的依赖管理是提高项目构建效率的关键。
- # 使用INTERFACE库管理头文件依赖
- add_library(myproject INTERFACE)
- target_include_directories(myproject
- INTERFACE
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 使用ALIAS简化链接
- add_library(myproject::myproject ALIAS myproject)
- # 在其他目标中使用
- target_link_libraries(myapp PRIVATE myproject::myproject)
复制代码
Conan和vcpkg是流行的C++包管理器,可以与CMake集成使用。
Conan示例:
首先,创建conanfile.txt:
- [requires]
- boost/1.78.0
- [generators]
- cmake
复制代码
然后在CMakeLists.txt中:
- # 包含Conan生成的文件
- include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
- conan_basic_setup()
- # 使用Conan管理的库
- target_link_libraries(myapp ${CONAN_LIBS})
复制代码
vcpkg示例:
- # 设置vcpkg工具链文件
- set(CMAKE_TOOLCHAIN_FILE
- "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
- CACHE STRING "Vcpkg toolchain file")
- # 查找依赖
- find_package(fmt CONFIG REQUIRED)
- # 使用依赖
- target_link_libraries(myapp fmt::fmt)
复制代码
5.2 构建优化
优化构建过程可以显著提高开发效率。
- # 启用UNITY构建
- set_target_properties(mylib PROPERTIES
- UNITY_BUILD ON
- UNITY_BUILD_BATCH_SIZE 10
- )
复制代码- # 启用预编译头
- target_precompile_headers(mylib PRIVATE
- <vector>
- <string>
- "common.h"
- )
复制代码- # 设置并行编译数
- if(NOT CMAKE_BUILD_PARALLEL_LEVEL)
- set(CMAKE_BUILD_PARALLEL_LEVEL 4)
- endif()
复制代码
5.3 代码生成
CMake不仅用于构建,还可以用于代码生成。
- # 配置头文件模板 - config.h.in
- #pragma once
- #define PROJECT_NAME "@PROJECT_NAME@"
- #define PROJECT_VERSION "@PROJECT_VERSION@"
- #cmakedefine USE_FEATURE_A
- #cmakedefine USE_FEATURE_B
- # 在CMakeLists.txt中
- configure_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
- "${CMAKE_CURRENT_BINARY_DIR}/config.h"
- )
- target_include_directories(myapp PRIVATE
- ${CMAKE_CURRENT_BINARY_DIR}
- )
复制代码- # 添加自定义命令生成代码
- add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated_code.cpp
- COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate_code.py
- --input ${CMAKE_CURRENT_SOURCE_DIR}/data.json
- --output ${CMAKE_CURRENT_BINARY_DIR}/generated_code.cpp
- DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generate_code.py
- ${CMAKE_CURRENT_SOURCE_DIR}/data.json
- COMMENT "Generating code from data.json"
- )
- # 创建库包含生成的代码
- add_library(mylib
- ${CMAKE_CURRENT_BINARY_DIR}/generated_code.cpp
- other_source.cpp
- )
复制代码
6. 解决复杂依赖问题
6.1 循环依赖处理
循环依赖是项目构建中的常见问题,需要特别处理。
使用接口库可以打破循环依赖:
- # 在项目A中
- add_library(project_a INTERFACE)
- target_include_directories(project_a INTERFACE include)
- # 在项目B中
- add_library(project_b src/b.cpp)
- target_link_libraries(project_b PRIVATE project_a)
- # 在项目A中(需要依赖B的部分)
- add_library(project_a_implementation src/a.cpp)
- target_link_libraries(project_a_implementation PUBLIC project_b)
复制代码
在C++代码中,使用前向声明和PIMPL(Pointer to Implementation)模式可以减少头文件依赖:
- // a.h
- #pragma once
- #include <memory>
- class B; // 前向声明
- class A {
- public:
- A();
- ~A();
- void doSomething();
- private:
- std::unique_ptr<B> impl; // PIMPL模式
- };
- // a.cpp
- #include "a.h"
- #include "b.h" // 在这里包含完整的B定义
- A::A() : impl(std::make_unique<B>()) {}
- A::~A() = default;
- void A::doSomething() {
- impl->doSomethingElse();
- }
复制代码
6.2 条件依赖处理
有时依赖项是可选的,需要条件处理:
- # 检测可选依赖
- find_package(OpenGL QUIET)
- # 创建接口库管理可选依赖
- add_library(optional_dependencies INTERFACE)
- if(OpenGL_FOUND)
- target_link_libraries(optional_dependencies INTERFACE OpenGL::GL)
- add_compile_definitions(USE_OPENGL)
- endif()
- # 在目标中使用
- target_link_libraries(myapp PRIVATE optional_dependencies)
复制代码
在C++代码中:
- // main.cpp
- #include <iostream>
- #ifdef USE_OPENGL
- #include <GL/gl.h>
- void renderWithOpenGL() {
- std::cout << "Rendering with OpenGL" << std::endl;
- // OpenGL渲染代码
- }
- #else
- void renderWithOpenGL() {
- std::cout << "OpenGL not available" << std::endl;
- }
- #endif
- int main() {
- renderWithOpenGL();
- return 0;
- }
复制代码
6.3 多平台依赖处理
不同平台可能需要不同的依赖处理方式:
- # 多平台依赖处理
- if(WIN32)
- find_package(DirectX REQUIRED)
- set(PLATFORM_DEPENDENCIES DirectX::DXGI)
- elseif(APPLE)
- find_library(COCOA_LIBRARY Cocoa)
- find_library(COREVIDEO_LIBRARY CoreVideo)
- set(PLATFORM_DEPENDENCIES ${COCOA_LIBRARY} ${COREVIDEO_LIBRARY})
- elseif(UNIX)
- find_package(X11 REQUIRED)
- set(PLATFORM_DEPENDENCIES X11::X11)
- endif()
- # 创建平台特定库
- add_library(platform_specific INTERFACE)
- target_link_libraries(platform_specific INTERFACE ${PLATFORM_DEPENDENCIES})
- # 在目标中使用
- target_link_libraries(myapp PRIVATE platform_specific)
复制代码
6.4 版本冲突处理
当不同依赖项需要不同版本的同一个库时,可能会发生版本冲突:
- # 检查版本兼容性
- find_package(Boost 1.66 QUIET)
- if(NOT Boost_FOUND)
- find_package(Boost 1.65 QUIET)
- if(Boost_FOUND)
- message(WARNING "Boost 1.66 not found, using 1.65 instead. Some features may not be available.")
- else()
- message(FATAL_ERROR "Boost 1.66 or 1.65 not found")
- endif()
- endif()
- # 创建适配器库以处理API差异
- add_library(boost_adapter INTERFACE)
- if(Boost_VERSION VERSION_LESS 1.66)
- target_compile_definitions(boost_adapter INTERFACE BOOST_PRE_1_66)
- endif()
- # 在目标中使用
- target_link_libraries(myapp PRIVATE boost_adapter Boost::boost)
复制代码
7. 从入门到精通的学习路径
7.1 入门级
对于CMake初学者,建议从以下基础概念开始:
1. 基本命令:cmake_minimum_required,project,add_executable,add_library
2. 变量设置:set,option
3. 简单项目结构:单一CMakeLists.txt文件
4. 基本构建过程:创建构建目录,运行cmake和make
示例入门项目:
- # CMakeLists.txt
- cmake_minimum_required(VERSION 3.10)
- project(HelloWorld)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 11)
- # 添加可执行文件
- add_executable(hello_world main.cpp)
复制代码- // main.cpp
- #include <iostream>
- int main() {
- std::cout << "Hello, World!" << std::endl;
- return 0;
- }
复制代码
构建命令:
- mkdir build
- cd build
- cmake ..
- make
- ./hello_world
复制代码
7.2 中级
掌握了基础后,可以学习以下中级概念:
1. 多项目结构:使用add_subdirectory
2. 库的创建和使用:add_library,target_link_libraries
3. 包含目录管理:target_include_directories
4. 条件编译:if,elseif,else,endif
5. 查找依赖:find_package
示例中级项目结构:
- myproject/
- ├── CMakeLists.txt
- ├── include/
- │ └── mylib/
- │ └── mylib.h
- └── src/
- ├── CMakeLists.txt
- ├── mylib.cpp
- └── main.cpp
复制代码
主CMakeLists.txt:
- cmake_minimum_required(VERSION 3.10)
- project(MyProject)
- set(CMAKE_CXX_STANDARD 14)
- # 添加子目录
- add_subdirectory(src)
复制代码
src/CMakeLists.txt:
- # 添加库
- add_library(mylib mylib.cpp)
- # 设置包含目录
- target_include_directories(mylib PUBLIC
- ${CMAKE_CURRENT_SOURCE_DIR}/../include
- )
- # 添加可执行文件
- add_executable(myapp main.cpp)
- # 链接库
- target_link_libraries(myapp PRIVATE mylib)
复制代码
7.3 高级
高级CMake用户应该掌握以下概念:
1. 现代CMake目标导向方法:使用target_*命令而不是全局变量
2. 生成器表达式:$<...>语法
3. 自定义模块和函数:function,macro,include
4. 包配置和安装:install,export,configure_package_config_file
5. 测试支持:enable_testing,add_test
6. 高级依赖管理:FetchContent,ExternalProject
示例高级项目:
- cmake_minimum_required(VERSION 3.14)
- project(MyAdvancedProject VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- # 选项
- option(BUILD_SHARED_LIBS "Build shared libraries" ON)
- option(BUILD_TESTS "Build tests" ON)
- option(ENABLE_PROFILING "Enable profiling" OFF)
- # 自定义函数
- function(add_example NAME)
- add_executable(${NAME} examples/${NAME}.cpp)
- target_link_libraries(${NAME} PRIVATE mylib)
- if(ENABLE_PROFILING)
- target_compile_definitions(${NAME} PRIVATE ENABLE_PROFILING)
- endif()
- endfunction()
- # 包含子目录
- add_subdirectory(src)
- add_subdirectory(include)
- # 查找依赖
- find_package(Boost 1.66 REQUIRED COMPONENTS filesystem system)
- find_package(fmt REQUIRED)
- # 添加示例
- if(BUILD_EXAMPLES)
- add_example(example1)
- add_example(example2)
- endif()
- # 添加测试
- if(BUILD_TESTS)
- enable_testing()
- add_subdirectory(tests)
- endif()
- # 配置和安装
- include(GNUInstallDirs)
- include(CMakePackageConfigHelpers)
- # 安装目标
- install(TARGETS mylib
- EXPORT MyLibTargets
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
- )
- install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
- # 生成配置文件
- write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY AnyNewerVersion
- )
- install(EXPORT MyLibTargets
- FILE MyLibTargets.cmake
- NAMESPACE MyLib::
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
- )
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
- )
- configure_package_config_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/MyLibConfig.cmake.in"
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
- INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
- )
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
- )
复制代码
7.4 专家级
专家级CMake用户应该能够:
1. 设计复杂的构建系统:处理大型项目、多语言项目
2. 创建和维护CMake工具链:交叉编译、自定义工具链
3. 优化构建性能:减少构建时间、提高并行度
4. 解决复杂的依赖问题:循环依赖、版本冲突
5. 扩展CMake功能:编写复杂的模块和脚本
示例专家级项目可能包括:
- # 工具链文件示例 - my_toolchain.cmake
- set(CMAKE_SYSTEM_NAME Linux)
- set(CMAKE_SYSTEM_PROCESSOR arm)
- set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
- set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
- set(CMAKE_FIND_ROOT_PATH /usr/arm-linux-gnueabihf)
- set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
- set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
- set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
- set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
- # 使用工具链文件
- # cmake -DCMAKE_TOOLCHAIN_FILE=my_toolchain.cmake ..
复制代码- # 复杂的依赖管理示例
- include(FetchContent)
- # 声明依赖
- FetchContent_Declare(
- Catch2
- GIT_REPOSITORY https://github.com/catchorg/Catch2.git
- GIT_TAG v2.13.7
- )
- # 检查是否已经获取
- if(NOT Catch2_POPULATED)
- FetchContent_Populate(Catch2)
- add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR} EXCLUDE_FROM_ALL)
- endif()
- # 创建测试
- function(add_catch_test NAME)
- add_executable(${NAME} ${NAME}.cpp)
- target_link_libraries(${NAME} PRIVATE Catch2::Catch2)
- add_test(NAME ${NAME} COMMAND ${NAME})
- endfunction()
- # 使用函数添加测试
- add_catch_test(test_mylib)
复制代码
结论
CMake是一个功能强大且灵活的构建系统,通过深入理解其模块和包管理原理,掌握应用实战技巧,可以显著提升项目的构建效率与可维护性,有效解决复杂的依赖问题。从入门到精通,CMake的学习曲线虽然有些陡峭,但一旦掌握,将成为C++开发者不可或缺的工具。
通过本文的介绍,我们了解了CMake的基础知识、模块原理、包管理机制、实战技巧以及解决复杂依赖问题的方法。希望这些内容能够帮助读者更好地使用CMake,构建更加高效、可维护的C++项目。
在实际应用中,建议读者结合自己的项目需求,逐步应用本文介绍的技术和最佳实践,不断积累经验,最终成为CMake的专家用户。 |
|