CMake进阶: 实现自定义模块的 find_package
目录1.背景2.整体架构设计3.通用、可复用的 CMake 组件查找函数4.通用组件配置模板5.各模块代码和CMakeLists.txt编写5.1.Memory模块5.2.Allocate模块5.3.File模块5.4.Transform模块5.5.顶层构建脚本CMakeLists.txt6.构建与安装7.支持的 find_package 语法8.外部调用示例验证 find_package9.关键注意事项1.背景CMake指令find_package我自己写了个库名字为 MyProject, 里面有 File, Transform, Memory, Allocate 几个大模块各个模块有自己的 cmake使用方在 CMake 中 find_package (MyProject REQUIRED COMPONENTS File Transform Allocate) 查找需要的一个或多个模块并引入怎么来实现 MyProject 和各个子模块的 cmake 脚本呢其实这就是变相的来实现 find_package。2.整体架构设计核心原则每个子模块独立构建导出为MyProject::模块名命名空间导入目标现代 CMake 最佳实践顶层包统一入口处理组件筛选、依赖检查、结果聚合完全遵循 CMake 包配置规范无需自定义宏兼容性最优目录结构MyProject/ ├── CMakeLists.txt # 顶层构建入口 ├── cmake/ # 公共CMake脚本与模板 │ ├── PkgFindComponents.cmake # 通用组件查找核心函数 │ ├── MyProjectConfig.cmake.in # 顶层包配置模板 │ └── ComponentConfig.cmake.in # 子组件配置通用模板 ├── File/ # 模块1基础文件操作静态库 │ ├── CMakeLists.txt │ ├── include/MyProject/File/File.h │ └── src/File.cpp ├── Transform/ # 模块2文本转换依赖File模块 │ ├── CMakeLists.txt │ ├── include/MyProject/Transform/Transform.h │ └── src/Transform.cpp ├── Memory/ # 模块3内存管理独立静态库 │ ├── CMakeLists.txt │ ├── include/MyProject/Memory/Memory.h │ └── src/Memory.cpp ├── Allocate/ # 模块4对象分配纯头文件库依赖Memory │ ├── CMakeLists.txt │ └── include/MyProject/Allocate/Allocate.h └── example/ # 外部调用示例验证find_package ├── CMakeLists.txt └── main.cpp安装后结构find_package 可识别install/ ├── bin/ │ ├── MyProjectFile.dll # Release 动态库 │ ├── MyProjectFiled.dll # Debug 动态库 │ ├── MyProjectTransform.dll │ └── MyProjectTransformd.dll ├── lib/ │ ├── MyProjectFile.lib # Release 导入库 │ ├── MyProjectFiled.lib # Debug 导入库 │ └── cmake/MyProject/ │ ├── MyProjectConfig.cmake │ ├── MyProjectFileTargets.cmake # 包含双配置导入信息 │ └── ... └── include/MyProject/ ├── File/ │ ├── File.h │ └── Export.h └── ...3.通用、可复用的 CMake 组件查找函数完全兼容 CMake 原生find_package语义支持组件依赖递归加载、重复加载保护、ALL关键字、QUIET/REQUIRED最终配合标准FindPackageHandleStandardArgs完成结果校验。设计思路用function替代macro自带局部作用域避免变量污染遵循 CMake 命名约定Package_Component_FOUND支持组件间依赖自动递归加载重复加载标记防止循环依赖死循环最终交由标准find_package_handle_standard_args处理 REQUIRED / 版本校验行为和官方包完全一致函数实现新建文件cmake/PkgFindComponents.cmake存放通用函数# # 通用组件查找函数 pkg_find_components # 支持ALL关键字、组件依赖递归加载、重复加载保护、QUIET模式 # function(pkg_find_components) set(options QUIET) set(oneValueArgs PACKAGE CONFIG_DIR) set(multiValueArgs SUPPORTED_COMPONENTS COMPONENTS) cmake_parse_arguments(PFC ${options} ${oneValueArgs} ${multiValueArgs} ${ARGN}) # 参数校验 if(NOT PFC_PACKAGE) message(FATAL_ERROR pkg_find_components: 必须指定 PACKAGE 参数) endif() if(NOT PFC_SUPPORTED_COMPONENTS) message(FATAL_ERROR pkg_find_components: 必须指定 SUPPORTED_COMPONENTS) endif() if(NOT PFC_CONFIG_DIR) set(PFC_CONFIG_DIR ${CMAKE_CURRENT_LIST_DIR}) endif() # 处理 ALL 关键字 if(ALL IN_LIST PFC_COMPONENTS) set(PFC_COMPONENTS ${PFC_SUPPORTED_COMPONENTS}) endif() # 未指定组件则默认加载全部 if(NOT PFC_COMPONENTS) set(PFC_COMPONENTS ${PFC_SUPPORTED_COMPONENTS}) endif() # 初始化状态变量 set(${PFC_PACKAGE}_FAILED_COMPONENTS ) set(_PFC_LOADED ) # 内部递归加载函数 function(_pfc_load_component comp) # 已加载则跳过防重复、防循环依赖 if(comp IN_LIST _PFC_LOADED) return() endif() list(APPEND _PFC_LOADED ${comp}) set(_PFC_LOADED ${_PFC_LOADED} PARENT_SCOPE) set(_comp_config ${PFC_CONFIG_DIR}/${comp}Config.cmake) # 配置文件不存在 → 标记失败 if(NOT EXISTS ${_comp_config}) set(${PFC_PACKAGE}_${comp}_FOUND FALSE PARENT_SCOPE) list(APPEND ${PFC_PACKAGE}_FAILED_COMPONENTS ${comp}) set(${PFC_PACKAGE}_FAILED_COMPONENTS ${${PFC_PACKAGE}_FAILED_COMPONENTS} PARENT_SCOPE) if(NOT PFC_QUIET) message(STATUS ${PFC_PACKAGE}: 组件 [${comp}] 配置文件不存在) endif() return() endif() # 加载组件配置 include(${_comp_config}) # 同步FOUND状态到上层 if(DEFINED ${PFC_PACKAGE}_${comp}_FOUND) set(${PFC_PACKAGE}_${comp}_FOUND ${${PFC_PACKAGE}_${comp}_FOUND} PARENT_SCOPE) else() set(${PFC_PACKAGE}_${comp}_FOUND TRUE PARENT_SCOPE) endif() # 加载失败 → 加入失败列表 if(NOT ${PFC_PACKAGE}_${comp}_FOUND) list(APPEND ${PFC_PACKAGE}_FAILED_COMPONENTS ${comp}) set(${PFC_PACKAGE}_FAILED_COMPONENTS ${${PFC_PACKAGE}_FAILED_COMPONENTS} PARENT_SCOPE) return() endif() # 递归加载组件依赖 if(DEFINED ${PFC_PACKAGE}_${comp}_DEPENDS) foreach(_dep ${${PFC_PACKAGE}_${comp}_DEPENDS}) _pfc_load_component(${_dep}) endforeach() endif() endfunction() # 遍历所有请求组件 foreach(_comp ${PFC_COMPONENTS}) if(NOT _comp IN_LIST PFC_SUPPORTED_COMPONENTS) if(NOT PFC_QUIET) message(STATUS ${PFC_PACKAGE}: 忽略未知组件 [${_comp}]支持列表${PFC_SUPPORTED_COMPONENTS}) endif() continue() endif() _pfc_load_component(${_comp}) endforeach() # 结果变量提升到父作用域 set(${PFC_PACKAGE}_FAILED_COMPONENTS ${${PFC_PACKAGE}_FAILED_COMPONENTS} PARENT_SCOPE) foreach(_comp ${PFC_COMPONENTS}) if(DEFINED ${PFC_PACKAGE}_${_comp}_FOUND) set(${PFC_PACKAGE}_${_comp}_FOUND ${${PFC_PACKAGE}_${_comp}_FOUND} PARENT_SCOPE) endif() endforeach() endfunction()4.通用组件配置模板所有子模块复用同一个模板放在cmake/ComponentConfig.cmake.in用于生成每个组件的配置文件# 加载本组件的导出目标 include(${CMAKE_CURRENT_LIST_DIR}/MODULE_NAMETargets.cmake) # 声明组件依赖自动递归加载 set(MyProject_MODULE_NAME_DEPENDS MODULE_DEPENDS) # 标记组件是否找到 set(MyProject_MODULE_NAME_FOUND TRUE) if(NOT TARGET MyProject::MODULE_NAME) set(MyProject_MODULE_NAME_FOUND FALSE) endif()模块查找入口组件放在cmake/MyProjectConfig.cmake.inPACKAGE_INIT # 引入通用组件查找函数 include(${CMAKE_CURRENT_LIST_DIR}/PkgFindComponents.cmake) # 声明本包支持的所有组件 set(MyProject_SUPPORTED_COMPONENTS File Transform Memory Allocate) # 执行组件查找 pkg_find_components( PACKAGE MyProject SUPPORTED_COMPONENTS ${MyProject_SUPPORTED_COMPONENTS} COMPONENTS ${MyProject_FIND_COMPONENTS} CONFIG_DIR ${CMAKE_CURRENT_LIST_DIR} ${MyProject_FIND_QUIETLY} ) # 用CMake标准宏统一处理结果版本校验、REQUIRED组件校验 include(FindPackageHandleStandardArgs) find_package_handle_standard_args(MyProject VERSION_VAR MyProject_VERSION HANDLE_COMPONENTS REQUIRED_VARS PACKAGE_PREFIX_DIR )5.各模块代码和CMakeLists.txt编写5.1.Memory模块它被编译成动态库目录结构如下Memory.h#pragma once #include cstddef #include MyProject/Memory/Export.h // 自动生成的导出宏 namespace MyProject::Memory { MYPROJECT_Memory_EXPORT void* safe_malloc(size_t size); MYPROJECT_Memory_EXPORT void safe_free(void* ptr); MYPROJECT_Memory_EXPORT size_t memory_usage(); }Memory.cpp#include MyProject/Memory/Memory.h #include cstdlib #include atomic namespace MyProject::Memory { static std::atomicsize_t g_usage{0}; void* safe_malloc(size_t size) { if (size 0) return nullptr; void* ptr std::malloc(size); if (ptr) g_usage size; return ptr; } void safe_free(void* ptr) { std::free(ptr); } size_t memory_usage() { return g_usage.load(); } } // namespaceCMakeLists.txtcmake_minimum_required(VERSION 3.16) include(GenerateExportHeader) # 标准导出宏生成工具 set(MODULE_NAME Memory) set(TARGET_NAME ${MODULE_NAME}) set(MODULE_DEPENDS ) # 无依赖 # 构建静态库 add_library(${TARGET_NAME} SHARED src/Memory.cpp) # 2. 生成 Windows 导出宏头文件 # 自动生成 dll 导出/导入宏跨平台自动兼容 generate_export_header(${TARGET_NAME} BASE_NAME ${TARGET_NAME} EXPORT_MACRO_NAME MYPROJECT_${MODULE_NAME}_EXPORT EXPORT_FILE_NAME MyProject/${MODULE_NAME}/Export.h ) # 3. 头文件目录 # 额外加入构建目录用于找到生成的 Export.h target_include_directories(${TARGET_NAME} PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR} $INSTALL_INTERFACE:include ) # 4. 安装头文件包含生成的导出头 install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN *.h ) # 安装自动生成的导出头文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyProject/${MODULE_NAME}/Export.h DESTINATION include/MyProject/${MODULE_NAME} ) # 5. 安装动态库目标 # Windows: dll → bin导入库.lib → libLinux/macOS: so/dylib → lib install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME}Targets RUNTIME DESTINATION bin # 运行时文件Windows .dll、Linux 可执行 LIBRARY DESTINATION lib # 动态库Linux .so、macOS .dylib ARCHIVE DESTINATION lib # 静态导入库Windows .lib INCLUDES DESTINATION include ) # 6. 导出目标文件完全兼容多配置 install(EXPORT ${TARGET_NAME}Targets FILE ${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: DESTINATION lib/cmake/MyProject ) export(EXPORT ${TARGET_NAME}Targets FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: ) # 7. 生成组件配置文件和静态库完全一致 configure_file( ${CMAKE_SOURCE_DIR}/cmake/ComponentConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake ONLY ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake DESTINATION lib/cmake/MyProject )cmake安装后的MemoryConfig.cmake在/lib/cmake/MyProject下# 加载本组件的导出目标 include(${CMAKE_CURRENT_LIST_DIR}/MemoryTargets.cmake) # 声明组件依赖自动递归加载 set(MyProject_Memory_DEPENDS ) # 标记组件是否找到 set(MyProject_Memory_FOUND TRUE) if(NOT TARGET MyProject::Memory) set(MyProject_Memory_FOUND FALSE) endif()MemoryTargets.cmake# Generated by CMake if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} LESS 2.8) message(FATAL_ERROR CMake 2.8.3 required) endif() if(CMAKE_VERSION VERSION_LESS 2.8.3) message(FATAL_ERROR CMake 2.8.3 required) endif() cmake_policy(PUSH) cmake_policy(VERSION 2.8.3...3.30) #---------------------------------------------------------------- # Generated CMake target import file. #---------------------------------------------------------------- # Commands may need to know the format version. set(CMAKE_IMPORT_FILE_VERSION 1) # Protect against multiple inclusion, which would fail when already imported targets are added once more. set(_cmake_targets_defined ) set(_cmake_targets_not_defined ) set(_cmake_expected_targets ) foreach(_cmake_expected_target IN ITEMS MyProject::Memory) list(APPEND _cmake_expected_targets ${_cmake_expected_target}) if(TARGET ${_cmake_expected_target}) list(APPEND _cmake_targets_defined ${_cmake_expected_target}) else() list(APPEND _cmake_targets_not_defined ${_cmake_expected_target}) endif() endforeach() unset(_cmake_expected_target) if(_cmake_targets_defined STREQUAL _cmake_expected_targets) unset(_cmake_targets_defined) unset(_cmake_targets_not_defined) unset(_cmake_expected_targets) unset(CMAKE_IMPORT_FILE_VERSION) cmake_policy(POP) return() endif() if(NOT _cmake_targets_defined STREQUAL ) string(REPLACE ; , _cmake_targets_defined_text ${_cmake_targets_defined}) string(REPLACE ; , _cmake_targets_not_defined_text ${_cmake_targets_not_defined}) message(FATAL_ERROR Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n) endif() unset(_cmake_targets_defined) unset(_cmake_targets_not_defined) unset(_cmake_expected_targets) # Compute the installation prefix relative to this file. get_filename_component(_IMPORT_PREFIX ${CMAKE_CURRENT_LIST_FILE} PATH) get_filename_component(_IMPORT_PREFIX ${_IMPORT_PREFIX} PATH) get_filename_component(_IMPORT_PREFIX ${_IMPORT_PREFIX} PATH) get_filename_component(_IMPORT_PREFIX ${_IMPORT_PREFIX} PATH) if(_IMPORT_PREFIX STREQUAL /) set(_IMPORT_PREFIX ) endif() # Create imported target MyProject::Memory add_library(MyProject::Memory SHARED IMPORTED) set_target_properties(MyProject::Memory PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include ) # Load information for each installed configuration. file(GLOB _cmake_config_files ${CMAKE_CURRENT_LIST_DIR}/MemoryTargets-*.cmake) foreach(_cmake_config_file IN LISTS _cmake_config_files) include(${_cmake_config_file}) endforeach() unset(_cmake_config_file) unset(_cmake_config_files) # Cleanup temporary variables. set(_IMPORT_PREFIX) # Loop over all imported files and verify that they actually exist foreach(_cmake_target IN LISTS _cmake_import_check_targets) if(CMAKE_VERSION VERSION_LESS 3.28 OR NOT DEFINED _cmake_import_check_xcframework_for_${_cmake_target} OR NOT IS_DIRECTORY ${_cmake_import_check_xcframework_for_${_cmake_target}}) foreach(_cmake_file IN LISTS _cmake_import_check_files_for_${_cmake_target}) if(NOT EXISTS ${_cmake_file}) message(FATAL_ERROR The imported target \${_cmake_target}\ references the file \${_cmake_file}\ but this file does not exist. Possible reasons include: * The file was deleted, renamed, or moved to another location. * An install or uninstall procedure did not complete successfully. * The installation package was faulty and contained \${CMAKE_CURRENT_LIST_FILE}\ but not all the files it references. ) endif() endforeach() endif() unset(_cmake_file) unset(_cmake_import_check_files_for_${_cmake_target}) endforeach() unset(_cmake_target) unset(_cmake_import_check_targets) # This file does not depend on other imported targets which have # been exported from the same project but in a separate export set. # Commands beyond this point should not need to know the version. set(CMAKE_IMPORT_FILE_VERSION) cmake_policy(POP)MemoryTargets.cmake又include了MemoryTargets-debug.cmake和MemoryTargets-release.cmake脚本MemoryTargets-debug.cmake为#---------------------------------------------------------------- # Generated CMake target import file for configuration Debug. #---------------------------------------------------------------- # Commands may need to know the format version. set(CMAKE_IMPORT_FILE_VERSION 1) # Import target MyProject::Memory for configuration Debug set_property(TARGET MyProject::Memory APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(MyProject::Memory PROPERTIES IMPORTED_IMPLIB_DEBUG ${_IMPORT_PREFIX}/lib/Memoryd.lib IMPORTED_LOCATION_DEBUG ${_IMPORT_PREFIX}/bin/Memoryd.dll ) list(APPEND _cmake_import_check_targets MyProject::Memory ) list(APPEND _cmake_import_check_files_for_MyProject::Memory ${_IMPORT_PREFIX}/lib/Memoryd.lib ${_IMPORT_PREFIX}/bin/Memoryd.dll ) # Commands beyond this point should not need to know the version. set(CMAKE_IMPORT_FILE_VERSION)MemoryTargets-release.cmake为#---------------------------------------------------------------- # Generated CMake target import file for configuration Release. #---------------------------------------------------------------- # Commands may need to know the format version. set(CMAKE_IMPORT_FILE_VERSION 1) # Import target MyProject::Memory for configuration Release set_property(TARGET MyProject::Memory APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(MyProject::Memory PROPERTIES IMPORTED_IMPLIB_RELEASE ${_IMPORT_PREFIX}/lib/Memory.lib IMPORTED_LOCATION_RELEASE ${_IMPORT_PREFIX}/bin/Memory.dll ) list(APPEND _cmake_import_check_targets MyProject::Memory ) list(APPEND _cmake_import_check_files_for_MyProject::Memory ${_IMPORT_PREFIX}/lib/Memory.lib ${_IMPORT_PREFIX}/bin/Memory.dll ) # Commands beyond this point should not need to know the version. set(CMAKE_IMPORT_FILE_VERSION)下面几个模块cmake安装后的脚本类似就不用多讲了。5.2.Allocate模块Allocate依赖于Memory它被编译成静态库目录结构如下Allocate.h#pragma once #include MyProject/Memory/Memory.h #include utility namespace MyProject::Allocate { templatetypename T, typename... Args T* make_new(Args... args) { void* ptr Memory::safe_malloc(sizeof(T)); if (!ptr) return nullptr; return new (ptr) T(std::forwardArgs(args)...); } templatetypename T void delete_obj(T* ptr) { if (!ptr) return; ptr-~T(); Memory::safe_free(ptr); } } // namespaceCMakeLists.txtcmake_minimum_required(VERSION 3.16) set(MODULE_NAME Allocate) set(TARGET_NAME ${MODULE_NAME}) set(MODULE_DEPENDS Memory) # 依赖Memory组件 # 纯头文件库INTERFACE类型 add_library(${TARGET_NAME} INTERFACE) target_include_directories(${TARGET_NAME} INTERFACE $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $INSTALL_INTERFACE:include ) # 依赖传递 target_link_libraries(${TARGET_NAME} INTERFACE Memory) # 安装头文件 install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN *.h ) # 安装目标与导出 install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME}Targets INCLUDES DESTINATION include ) install(EXPORT ${TARGET_NAME}Targets FILE ${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: DESTINATION lib/cmake/MyProject ) export(EXPORT ${TARGET_NAME}Targets FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: ) configure_file( ${CMAKE_SOURCE_DIR}/cmake/ComponentConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake ONLY ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake DESTINATION lib/cmake/MyProject )5.3.File模块它被编译成动态库目录结构如下File.h#pragma once #include string #include MyProject/File/Export.h // 自动生成的导出宏 namespace MyProject::File { MYPROJECT_File_EXPORT std::string read_file(const std::string path); MYPROJECT_File_EXPORT bool write_file(const std::string path, const std::string content); }File.cpp#include MyProject/File/File.h #include fstream #include sstream namespace MyProject::File { std::string read_file(const std::string path) { std::ifstream file(path); if (!file) return {}; std::stringstream ss; ss file.rdbuf(); return ss.str(); } bool write_file(const std::string path, const std::string content) { std::ofstream file(path); if (!file) return false; file content; return true; } } // namespaceCMakeLists.txtcmake_minimum_required(VERSION 3.28) include(GenerateExportHeader) # 标准导出宏生成工具 set(MODULE_NAME File) set(TARGET_NAME ${MODULE_NAME}) set(MODULE_DEPENDS ) # 无依赖 # 构建静态库 add_library(${TARGET_NAME} SHARED src/File.cpp) # 2. 生成 Windows 导出宏头文件 # 自动生成 dll 导出/导入宏跨平台自动兼容 generate_export_header(${TARGET_NAME} BASE_NAME ${TARGET_NAME} EXPORT_MACRO_NAME MYPROJECT_${MODULE_NAME}_EXPORT EXPORT_FILE_NAME MyProject/${MODULE_NAME}/Export.h ) # 3. 头文件目录 # 额外加入构建目录用于找到生成的 Export.h target_include_directories(${TARGET_NAME} PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR} $INSTALL_INTERFACE:include ) # 4. 安装头文件包含生成的导出头 install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN *.h ) # 安装自动生成的导出头文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyProject/${MODULE_NAME}/Export.h DESTINATION include/MyProject/${MODULE_NAME} ) # 5. 安装动态库目标 # Windows: dll → bin导入库.lib → libLinux/macOS: so/dylib → lib install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME}Targets RUNTIME DESTINATION bin # 运行时文件Windows .dll、Linux 可执行 LIBRARY DESTINATION lib # 动态库Linux .so、macOS .dylib ARCHIVE DESTINATION lib # 静态导入库Windows .lib INCLUDES DESTINATION include ) # 6. 导出目标文件完全兼容多配置 install(EXPORT ${TARGET_NAME}Targets FILE ${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: DESTINATION lib/cmake/MyProject ) export(EXPORT ${TARGET_NAME}Targets FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: ) # 7. 生成组件配置文件和静态库完全一致 configure_file( ${CMAKE_SOURCE_DIR}/cmake/ComponentConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake ONLY ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake DESTINATION lib/cmake/MyProject )5.4.Transform模块它被编译成动态库目录结构如下Transform.h#pragma once #include string #include MyProject/Transform/Export.h // 自动生成的导出宏 namespace MyProject::Transform { MYPROJECT_Transform_EXPORT std::string to_upper(const std::string input); MYPROJECT_Transform_EXPORT std::string transform_file(const std::string input_path, const std::string output_path); }Transform.cpp#include MyProject/Transform/Transform.h #include MyProject/File/File.h #include algorithm #include cctype namespace MyProject::Transform { std::string to_upper(const std::string input) { std::string res input; std::transform(res.begin(), res.end(), res.begin(), ::toupper); return res; } std::string transform_file(const std::string input_path, const std::string output_path) { std::string content File::read_file(input_path); std::string upper to_upper(content); File::write_file(output_path, upper); return upper; } } // namespaceCMakeLists.txtcmake_minimum_required(VERSION 3.16) include(GenerateExportHeader) set(MODULE_NAME Transform) set(TARGET_NAME ${MODULE_NAME}) set(MODULE_DEPENDS File) # 声明依赖File组件 # 构建静态库 add_library(${TARGET_NAME} SHARED src/Transform.cpp) generate_export_header(${TARGET_NAME} BASE_NAME ${TARGET_NAME} EXPORT_MACRO_NAME MYPROJECT_${MODULE_NAME}_EXPORT EXPORT_FILE_NAME MyProject/${MODULE_NAME}/Export.h ) target_include_directories(${TARGET_NAME} PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR} $INSTALL_INTERFACE:include ) # 模块间构建依赖 #target_link_libraries(${TARGET_NAME} PUBLIC MyProjectFile) target_link_libraries(${TARGET_NAME} PUBLIC File) # 安装头文件 install(DIRECTORY include/ DESTINATION include FILES_MATCHING PATTERN *.h ) # 安装自动生成的导出头文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyProject/${MODULE_NAME}/Export.h DESTINATION include/MyProject/${MODULE_NAME} ) # 5. 安装动态库目标 # Windows: dll → bin导入库.lib → libLinux/macOS: so/dylib → lib install(TARGETS ${TARGET_NAME} EXPORT ${TARGET_NAME}Targets RUNTIME DESTINATION bin # 运行时文件Windows .dll、Linux 可执行 LIBRARY DESTINATION lib # 动态库Linux .so、macOS .dylib ARCHIVE DESTINATION lib # 静态导入库Windows .lib INCLUDES DESTINATION include ) install(EXPORT ${TARGET_NAME}Targets FILE ${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: DESTINATION lib/cmake/MyProject ) export(EXPORT ${TARGET_NAME}Targets FILE ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Targets.cmake NAMESPACE MyProject:: ) configure_file( ${CMAKE_SOURCE_DIR}/cmake/ComponentConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake ONLY ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}Config.cmake DESTINATION lib/cmake/MyProject )5.5.顶层构建脚本CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(MyProject VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 新增Windows 多配置适配 # Debug 版本库加 d 后缀自动区分 Release/Debug set(CMAKE_DEBUG_POSTFIX d) if(WIN32) # 统一输出根目录多配置生成器VS会自动创建 Release/Debug 子目录 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # dll / exe set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # .lib 导入库 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # Linux/macOS 动态库 endif() # 可选统一 MSVC 运行时库避免和外部项目运行时冲突 # set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$$CONFIG:Debug:DebugDLL) # # 引入CMake包配置工具 include(CMakePackageConfigHelpers) # 所有模块列表 set(MYPROJECT_MODULES File Transform Memory Allocate) # 批量添加子模块 foreach(module ${MYPROJECT_MODULES}) add_subdirectory(${module}) endforeach() # 生成版本兼容文件 write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) # 生成顶层包配置文件 configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/MyProjectConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake INSTALL_DESTINATION lib/cmake/MyProject ) # 安装顶层配置文件 公共组件查找脚本 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake ${CMAKE_CURRENT_SOURCE_DIR}/cmake/PkgFindComponents.cmake DESTINATION lib/cmake/MyProject )6.构建与安装# 在MyProject根目录执行 mkdir build cd build cmake .. -DCMAKE_INSTALL_PREFIX../install cmake --build . --config Debug cmake --install . --config Debug cmake --build . --config Release cmake --install . --config Release7.支持的 find_package 语法# 1. 指定必填组件 find_package(MyProject REQUIRED COMPONENTS File Transform) # 2. 加载全部组件 find_package(MyProject REQUIRED COMPONENTS ALL) # 3. 安静模式查找 find_package(MyProject QUIET COMPONENTS Memory) # 4. 版本检查 find_package(MyProject 1.0 COMPONENTS Allocate) # 5. 检查单个组件是否找到 if(MyProject_File_FOUND) message(STATUS File组件可用) endif()整个调用链条过程find_package(MyProject COMPONENTS File) ↓ 加载 MyProjectConfig.cmake ↓ 调用 pkg_find_components 遍历组件 ↓ 加载 MyProjectFileConfig.cmake 你写的组件配置 ↓ include(MyProjectFileTargets.cmake) ← 就是这个文件定义导入目标 ↓ 得到 MyProject::File 目标用户可直接 target_link_libraries所以你只需要保证install(EXPORT)/export()命令写对CMake 会自动生成并维护这个文件永远不要手动去写它。8.外部调用示例验证 find_packageexample/main.cpp#include iostream #include MyProject/Transform/Transform.h #include MyProject/Allocate/Allocate.h #include MyProject/Memory/Memory.h int main() { // 测试Transform组件自动依赖File std::string text hello cmake components; std::cout Original: text std::endl; std::cout To upper: MyProject::Transform::to_upper(text) std::endl; // 测试Allocate组件自动依赖Memory int* num MyProject::Allocate::make_newint(2024); std::cout Allocated int: *num std::endl; std::cout Memory usage: MyProject::Memory::memory_usage() bytes std::endl; MyProject::Allocate::delete_obj(num); std::cout \nAll components work successfully! std::endl; return 0; }example/CMakeLists.txtcmake_minimum_required(VERSION 3.16) project(MyProjectExample LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) # 查找指定组件Transform会自动加载依赖的FileAllocate自动加载Memory find_package(MyProject CONFIG REQUIRED COMPONENTS Transform Allocate) add_executable(example main.cpp) # 链接命名空间目标现代CMake最佳实践 target_link_libraries(example PRIVATE MyProject::Transform MyProject::Allocate )运行时 dll 处理Windows 特有dll 必须和 exe 在同一目录或在 PATH 中才能运行。推荐在示例项目中加一条自动复制命令# 构建后自动把依赖的 dll 复制到 exe 目录 add_custom_command(TARGET example POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $TARGET_FILE:MyProject::File $TARGET_FILE:MyProject::Transform $TARGET_FILE_DIR:example )9.关键注意事项导出宏不可省略Windows 下动态库默认不导出符号不加generate_export_header会导致没有.lib导入库外部链接失败。多配置生成器忽略CMAKE_BUILD_TYPEVisual Studio 等多配置生成器必须用--config Release/Debug指定构建配置。头文件公共库文件分配置安装多个配置时头文件只安装一次库文件因后缀不同互不覆盖。组件依赖自动传递动态库之间的依赖关系会通过IMPORTED_LINK_INTERFACE_LIBRARIES自动传递用户无需手动链接依赖项。跨平台兼容同一份 CMake 代码在 Linux/macOS 下会自动构建.so/.dylib无需修改。