活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

CMake与CMakeExport详解从基础配置到高级导出技巧全面掌握CMake构建系统与库文件导出方法提升项目开发效率

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-3 00:50:01 | 显示全部楼层 |阅读模式

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

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

x
引言

CMake是一个跨平台的构建系统生成器,它使用简单的配置文件来控制软件编译过程,并生成标准的构建文件(如Makefile或Visual Studio项目)。CMakeExport则是CMake中用于导出库文件和目标的重要功能,它使得不同项目之间可以轻松共享和使用已编译的库。本文将从CMake的基础配置开始,逐步深入到高级的CMakeExport技巧,帮助读者全面掌握CMake构建系统与库文件导出方法,从而提升项目开发效率。

CMake基础

CMake简介

CMake是一个开源、跨平台的构建自动化工具,它使用名为CMakeLists.txt的配置文件来生成标准的构建文件(如Unix的Makefile或Windows的Visual Studio项目)。CMake的设计目标是提供一个简单而强大的方式来管理软件构建过程,支持多种编译器和开发环境。

CMake的主要特点包括:

• 跨平台支持:可以在Windows、Linux、macOS等多种操作系统上运行
• 多编译器支持:支持GCC、Clang、MSVC等多种编译器
• 简单语法:使用简单的命令和语法来描述构建过程
• 可扩展性:支持自定义命令和函数
• 大型项目支持:能够管理复杂的项目结构和依赖关系

基本语法和命令

CMake使用一系列命令来配置项目,这些命令在CMakeLists.txt文件中编写。以下是一些基本的CMake命令:
  1. # 指定CMake最低版本要求
  2. cmake_minimum_required(VERSION 3.10)
  3. # 定义项目名称和版本
  4. project(MyProject VERSION 1.0.0 LANGUAGES CXX)
  5. # 添加可执行文件
  6. add_executable(my_app main.cpp)
  7. # 添加库
  8. add_library(my_lib STATIC lib_source.cpp)
  9. # 包含目录
  10. include_directories(include)
  11. # 链接库
  12. target_link_libraries(my_app my_lib)
  13. # 设置变量
  14. set(MY_VARIABLE "value")
  15. # 条件判断
  16. if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  17.     add_definitions(-DDEBUG)
  18. endif()
  19. # 打印消息
  20. message(STATUS "Building project: ${PROJECT_NAME}")
复制代码

项目结构

一个典型的CMake项目结构如下:
  1. my_project/
  2. ├── CMakeLists.txt          # 主CMake配置文件
  3. ├── include/                # 头文件目录
  4. │   └── my_lib/
  5. │       └── my_lib.h
  6. ├── src/                    # 源文件目录
  7. │   ├── my_lib.cpp
  8. │   └── main.cpp
  9. ├── build/                  # 构建目录(通常不提交到版本控制)
  10. └── README.md               # 项目说明文档
复制代码

这种结构将源代码、头文件和构建文件分开,使项目更加清晰和易于管理。

CMake项目配置

最简单的CMake项目

让我们从一个最简单的CMake项目开始。假设我们有一个包含单个源文件的简单程序:
  1. // main.cpp
  2. #include <iostream>
  3. int main() {
  4.     std::cout << "Hello, CMake!" << std::endl;
  5.     return 0;
  6. }
复制代码

对应的CMakeLists.txt文件非常简单:
  1. # 要求CMake最低版本
  2. cmake_minimum_required(VERSION 3.10)
  3. # 定义项目
  4. project(HelloCMake)
  5. # 添加可执行文件
  6. add_executable(hello_cmake main.cpp)
复制代码

要构建这个项目,可以执行以下命令:
  1. mkdir build
  2. cd build
  3. cmake ..
  4. make
复制代码

设置项目属性

CMake允许我们设置各种项目属性,如版本号、语言标准等:
  1. cmake_minimum_required(VERSION 3.10)
  2. # 设置项目名称、版本和语言
  3. project(MyProject
  4.     VERSION 1.0.0
  5.     DESCRIPTION "My awesome project"
  6.     LANGUAGES CXX
  7. )
  8. # 设置C++标准
  9. set(CMAKE_CXX_STANDARD 17)
  10. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  11. set(CMAKE_CXX_EXTENSIONS OFF)
  12. # 定义一些有用的变量
  13. set(PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
  14. set(PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
复制代码

添加源文件

当项目包含多个源文件时,我们可以使用以下方式添加它们:
  1. # 方式1:直接列出所有源文件
  2. add_executable(my_app
  3.     src/main.cpp
  4.     src/class1.cpp
  5.     src/class2.cpp
  6. )
  7. # 方式2:使用变量存储源文件列表
  8. set(SOURCES
  9.     src/main.cpp
  10.     src/class1.cpp
  11.     src/class2.cpp
  12. )
  13. add_executable(my_app ${SOURCES})
  14. # 方式3:使用file命令自动收集源文件
  15. file(GLOB_RECURSE SOURCES "src/*.cpp")
  16. add_executable(my_app ${SOURCES})
复制代码

注意:使用file(GLOB_RECURSE ...)自动收集源文件虽然方便,但也有缺点。CMake无法自动检测到新添加的文件,除非重新运行CMake配置。因此,对于大型项目,建议显式列出源文件。

设置编译选项

CMake允许我们为不同的构建类型设置不同的编译选项:
  1. # 设置构建类型(Release, Debug, RelWithDebInfo, MinSizeRel)
  2. if(NOT CMAKE_BUILD_TYPE)
  3.     set(CMAKE_BUILD_TYPE "Release")
  4. endif()
  5. # 针对不同构建类型的编译选项
  6. set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
  7. set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
  8. set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DNDEBUG")
  9. set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
  10. # 添加编译定义
  11. if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  12.     add_definitions(-DDEBUG=1)
  13. endif()
  14. # 添加编译器特定的警告选项
  15. if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  16.     add_compile_options(-Wall -Wextra -Wpedantic)
  17. elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  18.     add_compile_options(/W4)
  19. endif()
复制代码

构建可执行文件和库

构建可执行文件

构建可执行文件是CMake的基本功能之一。我们已经在前面的例子中看到了add_executable命令的基本用法。现在让我们看一个更复杂的例子:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyApp VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加源文件
  7. set(APP_SOURCES
  8.     src/main.cpp
  9.     src/app.cpp
  10.     src/utils.cpp
  11. )
  12. # 添加头文件目录
  13. include_directories(${PROJECT_SOURCE_DIR}/include)
  14. # 添加可执行文件
  15. add_executable(${PROJECT_NAME} ${APP_SOURCES})
  16. # 设置输出目录
  17. set_target_properties(${PROJECT_NAME} PROPERTIES
  18.     RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
  19. )
  20. # 链接系统库(如果需要)
  21. find_package(Threads REQUIRED)
  22. target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads)
复制代码

构建静态库

静态库是在编译时链接到可执行文件中的库文件。在CMake中,我们可以使用add_library命令创建静态库:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyStaticLib VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加库源文件
  7. set(LIB_SOURCES
  8.     src/my_lib.cpp
  9.     src/utils.cpp
  10. )
  11. # 添加静态库
  12. add_library(${PROJECT_NAME} STATIC ${LIB_SOURCES})
  13. # 设置头文件目录
  14. target_include_directories(${PROJECT_NAME} PUBLIC
  15.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  16.     $<INSTALL_INTERFACE:include>
  17. )
  18. # 设置库属性
  19. set_target_properties(${PROJECT_NAME} PROPERTIES
  20.     ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
  21. )
  22. # 编译定义
  23. target_compile_definitions(${PROJECT_NAME} PRIVATE MY_LIB_EXPORTS)
复制代码

构建动态库

动态库(在Windows上称为DLL,在Linux/Unix上称为共享库)是在运行时加载的库文件。创建动态库的CMake代码与静态库类似:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MySharedLib VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加库源文件
  7. set(LIB_SOURCES
  8.     src/my_lib.cpp
  9.     src/utils.cpp
  10. )
  11. # 添加动态库
  12. add_library(${PROJECT_NAME} SHARED ${LIB_SOURCES})
  13. # 设置头文件目录
  14. target_include_directories(${PROJECT_NAME} PUBLIC
  15.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  16.     $<INSTALL_INTERFACE:include>
  17. )
  18. # 设置库属性
  19. set_target_properties(${PROJECT_NAME} PROPERTIES
  20.     LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
  21.     RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin  # Windows DLL需要
  22. )
  23. # 编译定义
  24. target_compile_definitions(${PROJECT_NAME} PRIVATE MY_SHARED_LIB_EXPORTS)
复制代码

对于动态库,我们通常需要处理符号导出问题。在头文件中,我们可以使用预处理器指令来控制符号的导出:
  1. // my_lib.h
  2. #pragma once
  3. #ifdef MY_SHARED_LIB_EXPORTS
  4. #define MY_API __declspec(dllexport)  // Windows导出
  5. #else
  6. #define MY_API __declspec(dllimport)  // Windows导入
  7. #endif
  8. // 对于非Windows平台
  9. #ifndef _WIN32
  10. #define MY_API
  11. #endif
  12. // 要导出的类或函数
  13. class MY_API MyClass {
  14. public:
  15.     void doSomething();
  16. };
  17. MY_API void myFunction();
复制代码

CMake导出基础

什么是CMake导出

CMake导出是一种机制,允许我们创建包含构建目标信息的文件,这些文件可以被其他项目使用,而无需知道原始项目的构建细节。通过导出,其他项目可以轻松地找到并使用我们创建的库,包括链接到正确的库文件、包含正确的头文件目录,并传递必要的编译选项。

CMake导出的主要优势包括:

• 简化依赖管理
• 提供跨平台兼容性
• 支持不同构建类型(Debug/Release)
• 处理传递依赖关系
• 支持包管理器集成

基本导出命令

CMake提供了几个关键命令用于导出目标:
  1. # 导出目标到文件
  2. export(TARGETS my_target FILE my_target.cmake)
  3. # 安装并导出目标
  4. install(TARGETS my_target
  5.     EXPORT my_export
  6.     LIBRARY DESTINATION lib
  7.     ARCHIVE DESTINATION lib
  8.     RUNTIME DESTINATION bin
  9.     INCLUDES DESTINATION include
  10. )
  11. # 安装导出文件
  12. install(EXPORT my_export
  13.     FILE my_targets.cmake
  14.     DESTINATION lib/cmake/my_project
  15. )
复制代码

导出目标

让我们看一个完整的例子,展示如何创建一个库并导出它:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MyLib VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加库源文件
  7. set(LIB_SOURCES
  8.     src/my_lib.cpp
  9.     src/utils.cpp
  10. )
  11. # 添加动态库
  12. add_library(${PROJECT_NAME} SHARED ${LIB_SOURCES})
  13. # 设置头文件目录
  14. target_include_directories(${PROJECT_NAME} PUBLIC
  15.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  16.     $<INSTALL_INTERFACE:include>
  17. )
  18. # 设置库属性
  19. set_target_properties(${PROJECT_NAME} PROPERTIES
  20.     LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
  21.     RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
  22.     VERSION ${PROJECT_VERSION}
  23.     SOVERSION 1
  24. )
  25. # 编译定义
  26. target_compile_definitions(${PROJECT_NAME} PRIVATE MY_LIB_EXPORTS)
  27. # 生成导出文件
  28. export(TARGETS ${PROJECT_NAME} FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake)
  29. # 安装规则
  30. install(TARGETS ${PROJECT_NAME}
  31.     EXPORT ${PROJECT_NAME}Targets
  32.     LIBRARY DESTINATION lib
  33.     ARCHIVE DESTINATION lib
  34.     RUNTIME DESTINATION bin
  35.     INCLUDES DESTINATION include
  36. )
  37. # 安装头文件
  38. install(DIRECTORY include/ DESTINATION include)
  39. # 安装导出文件
  40. install(EXPORT ${PROJECT_NAME}Targets
  41.     FILE ${PROJECT_NAME}Targets.cmake
  42.     DESTINATION lib/cmake/${PROJECT_NAME}
  43. )
  44. # 生成配置文件
  45. include(CMakePackageConfigHelpers)
  46. write_basic_package_version_file(
  47.     "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  48.     VERSION ${PROJECT_VERSION}
  49.     COMPATIBILITY AnyNewerVersion
  50. )
  51. # 安装配置文件
  52. install(FILES
  53.     "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  54.     DESTINATION lib/cmake/${PROJECT_NAME}
  55. )
复制代码

高级导出技巧

导出配置

CMake允许我们为不同的构建类型创建不同的导出配置。这对于支持多配置生成器(如Visual Studio)特别有用:
  1. # 为每个配置创建单独的导出文件
  2. foreach(config_type DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)
  3.     export(TARGETS my_target
  4.         FILE "my_target_${config_type}.cmake"
  5.         EXPORT_LINK_INTERFACE_LIBRARIES
  6.         CXX_MODULES_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/modules"
  7.     )
  8. endforeach()
  9. # 安装时处理多配置
  10. install(TARGETS my_target
  11.     EXPORT my_export
  12.     RUNTIME DESTINATION bin
  13.     LIBRARY DESTINATION lib
  14.     ARCHIVE DESTINATION lib
  15.     COMPONENT runtime
  16. )
  17. # 针对不同配置设置不同的属性
  18. set_target_properties(my_target PROPERTIES
  19.     DEBUG_POSTFIX "d"
  20.     RELWITHDEBINFO_POSTFIX "rd"
  21. )
复制代码

命名空间

使用命名空间可以避免目标名称冲突,特别是在大型项目中:
  1. # 创建命名空间
  2. add_library(my_lib STATIC lib.cpp)
  3. add_library(my_ns::my_lib ALIAS my_lib)
  4. # 导出时使用命名空间
  5. export(TARGETS my_lib NAMESPACE my_ns:: FILE MyLibTargets.cmake)
  6. # 在其他项目中使用
  7. find_package(MyLib REQUIRED)
  8. target_link_libraries(my_app PRIVATE my_ns::my_lib)
复制代码

版本控制

版本控制对于库的维护和兼容性非常重要。CMake提供了强大的版本控制功能:
  1. # 设置项目版本
  2. project(MyLib VERSION 1.2.3)
  3. # 设置目标版本属性
  4. set_target_properties(MyLib PROPERTIES
  5.     VERSION ${PROJECT_VERSION}        # 完整版本号 (1.2.3)
  6.     SOVERSION ${PROJECT_VERSION_MAJOR}  # 主版本号 (1)
  7. )
  8. # 生成版本文件
  9. include(CMakePackageConfigHelpers)
  10. write_basic_package_version_file(
  11.     "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
  12.     VERSION ${PROJECT_VERSION}
  13.     COMPATIBILITY AnyNewerVersion  # 或 SameMajorVersion
  14. )
  15. # 安装版本文件
  16. install(FILES
  17.     "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
  18.     DESTINATION lib/cmake/MyLib
  19. )
复制代码

依赖管理

在导出库时,正确处理依赖关系至关重要:
  1. # 假设我们的库依赖于其他库
  2. find_package(Boost REQUIRED COMPONENTS filesystem system)
  3. find_package(Threads REQUIRED)
  4. # 创建库
  5. add_library(MyLib SHARED my_lib.cpp)
  6. # 链接依赖库
  7. target_link_libraries(MyLib
  8.     PUBLIC
  9.         Boost::filesystem
  10.         Boost::system
  11.     PRIVATE
  12.         Threads::Threads
  13. )
  14. # 导出时包含依赖关系
  15. export(TARGETS MyLib
  16.     FILE MyLibTargets.cmake
  17.     LINK_INTERFACE_LIBRARIES  # 确保链接接口被正确导出
  18. )
  19. # 在配置文件中处理依赖
  20. configure_package_config_file(
  21.     "${CMAKE_CURRENT_SOURCE_DIR}/MyLibConfig.cmake.in"
  22.     "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
  23.     INSTALL_DESTINATION lib/cmake/MyLib
  24. )
复制代码

对应的MyLibConfig.cmake.in文件可能如下:
  1. @PACKAGE_INIT@
  2. include(CMakeFindDependencyMacro)
  3. # 检查并加载依赖
  4. find_dependency(Boost REQUIRED COMPONENTS filesystem system)
  5. find_dependency(Threads REQUIRED)
  6. # 包含目标文件
  7. include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
  8. # 检查兼容性
  9. check_required_components(MyLib)
复制代码

实际项目案例

简单库的导出和使用

让我们创建一个完整的例子,展示如何创建一个简单的数学库,导出它,然后在另一个项目中使用它。

首先,创建数学库项目结构:
  1. MathLib/
  2. ├── CMakeLists.txt
  3. ├── include/
  4. │   └── mathlib.h
  5. └── src/
  6.     └── mathlib.cpp
复制代码

mathlib.h文件:
  1. #pragma once
  2. #ifdef MATHLIB_EXPORTS
  3. #define MATHLIB_API __declspec(dllexport)
  4. #else
  5. #define MATHLIB_API __declspec(dllimport)
  6. #endif
  7. class MATHLIB_API MathOperations {
  8. public:
  9.     static double add(double a, double b);
  10.     static double subtract(double a, double b);
  11.     static double multiply(double a, double b);
  12.     static double divide(double a, double b);
  13. };
复制代码

mathlib.cpp文件:
  1. #include "mathlib.h"
  2. #include <stdexcept>
  3. double MathOperations::add(double a, double b) {
  4.     return a + b;
  5. }
  6. double MathOperations::subtract(double a, double b) {
  7.     return a - b;
  8. }
  9. double MathOperations::multiply(double a, double b) {
  10.     return a * b;
  11. }
  12. double MathOperations::divide(double a, double b) {
  13.     if (b == 0) {
  14.         throw std::invalid_argument("Division by zero");
  15.     }
  16.     return a / b;
  17. }
复制代码

CMakeLists.txt文件:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MathLib VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加库源文件
  7. add_library(${PROJECT_NAME} SHARED
  8.     src/mathlib.cpp
  9. )
  10. # 设置头文件目录
  11. target_include_directories(${PROJECT_NAME} PUBLIC
  12.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  13.     $<INSTALL_INTERFACE:include>
  14. )
  15. # 设置库属性
  16. set_target_properties(${PROJECT_NAME} PROPERTIES
  17.     VERSION ${PROJECT_VERSION}
  18.     SOVERSION 1
  19. )
  20. # 编译定义
  21. target_compile_definitions(${PROJECT_NAME} PRIVATE MATHLIB_EXPORTS)
  22. # 生成导出文件
  23. export(TARGETS ${PROJECT_NAME} FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake)
  24. # 安装规则
  25. include(GNUInstallDirs)
  26. install(TARGETS ${PROJECT_NAME}
  27.     EXPORT ${PROJECT_NAME}Targets
  28.     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  29.     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  30.     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  31.     INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  32. )
  33. # 安装头文件
  34. install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
  35. # 安装导出文件
  36. install(EXPORT ${PROJECT_NAME}Targets
  37.     FILE ${PROJECT_NAME}Targets.cmake
  38.     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
  39. )
  40. # 生成配置文件
  41. include(CMakePackageConfigHelpers)
  42. configure_package_config_file(
  43.     "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in"
  44.     "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
  45.     INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
  46. )
  47. # 生成版本文件
  48. write_basic_package_version_file(
  49.     "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  50.     VERSION ${PROJECT_VERSION}
  51.     COMPATIBILITY AnyNewerVersion
  52. )
  53. # 安装配置文件
  54. install(FILES
  55.     "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
  56.     "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
  57.     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
  58. )
复制代码

MathLibConfig.cmake.in文件:
  1. @PACKAGE_INIT@
  2. include("${CMAKE_CURRENT_LIST_DIR}/MathLibTargets.cmake")
  3. check_required_components(MathLib)
复制代码

现在,让我们创建一个使用这个库的应用程序项目:
  1. MathApp/
  2. ├── CMakeLists.txt
  3. └── src/
  4.     └── main.cpp
复制代码

main.cpp文件:
  1. #include <iostream>
  2. #include "mathlib.h"
  3. int main() {
  4.     double a = 10.0;
  5.     double b = 5.0;
  6.    
  7.     std::cout << a << " + " << b << " = " << MathOperations::add(a, b) << std::endl;
  8.     std::cout << a << " - " << b << " = " << MathOperations::subtract(a, b) << std::endl;
  9.     std::cout << a << " * " << b << " = " << MathOperations::multiply(a, b) << std::endl;
  10.     std::cout << a << " / " << b << " = " << MathOperations::divide(a, b) << std::endl;
  11.    
  12.     return 0;
  13. }
复制代码

CMakeLists.txt文件:
  1. cmake_minimum_required(VERSION 3.10)
  2. project(MathApp VERSION 1.0.0 LANGUAGES CXX)
  3. # 设置C++标准
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 查找MathLib库
  7. find_package(MathLib REQUIRED)
  8. # 添加可执行文件
  9. add_executable(${PROJECT_NAME} src/main.cpp)
  10. # 链接MathLib库
  11. target_link_libraries(${PROJECT_NAME} PRIVATE MathLib::MathLib)
复制代码

要构建这个项目,首先构建并安装MathLib:
  1. cd MathLib
  2. mkdir build
  3. cd build
  4. cmake ..
  5. make
  6. sudo make install  # 或者使用适当的权限安装
复制代码

然后构建MathApp:
  1. cd MathApp
  2. mkdir build
  3. cd build
  4. cmake ..
  5. make
复制代码

复杂项目的多级导出

在更复杂的项目中,我们可能需要导出多个相互依赖的库。让我们考虑一个项目,其中包含三个库:CoreLib、UtilsLib和AppLib。CoreLib是基础库,UtilsLib依赖于CoreLib,AppLib依赖于前两者。

项目结构:
  1. MyProject/
  2. ├── CMakeLists.txt
  3. ├── CoreLib/
  4. │   ├── CMakeLists.txt
  5. │   ├── include/
  6. │   │   └── corelib.h
  7. │   └── src/
  8. │       └── corelib.cpp
  9. ├── UtilsLib/
  10. │   ├── CMakeLists.txt
  11. │   ├── include/
  12. │   │   └── utilslib.h
  13. │   └── src/
  14. │       └── utilslib.cpp
  15. ├── AppLib/
  16. │   ├── CMakeLists.txt
  17. │   ├── include/
  18. │   │   └── applib.h
  19. │   └── src/
  20. │       └── applib.cpp
  21. └── examples/
  22.     ├── CMakeLists.txt
  23.     └── demo_app.cpp
复制代码

顶层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 17)
  5. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  6. # 添加子目录
  7. add_subdirectory(CoreLib)
  8. add_subdirectory(UtilsLib)
  9. add_subdirectory(AppLib)
  10. add_subdirectory(examples)
  11. # 导出所有目标
  12. export(TARGETS CoreLib UtilsLib AppLib
  13.     FILE ${PROJECT_BINARY_DIR}/MyProjectTargets.cmake
  14.     NAMESPACE MyProject::
  15. )
  16. # 安装规则
  17. include(GNUInstallDirs)
  18. # 安装导出文件
  19. install(EXPORT MyProjectTargets
  20.     FILE MyProjectTargets.cmake
  21.     NAMESPACE MyProject::
  22.     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
  23. )
  24. # 生成配置文件
  25. include(CMakePackageConfigHelpers)
  26. configure_package_config_file(
  27.     "${CMAKE_CURRENT_SOURCE_DIR}/MyProjectConfig.cmake.in"
  28.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake"
  29.     INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
  30. )
  31. # 生成版本文件
  32. write_basic_package_version_file(
  33.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
  34.     VERSION ${PROJECT_VERSION}
  35.     COMPATIBILITY AnyNewerVersion
  36. )
  37. # 安装配置文件
  38. install(FILES
  39.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake"
  40.     "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
  41.     DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
  42. )
复制代码

CoreLib/CMakeLists.txt:
  1. # 添加库
  2. add_library(CoreLib SHARED
  3.     src/corelib.cpp
  4. )
  5. # 设置头文件目录
  6. target_include_directories(CoreLib PUBLIC
  7.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  8.     $<INSTALL_INTERFACE:include>
  9. )
  10. # 设置库属性
  11. set_target_properties(CoreLib PROPERTIES
  12.     VERSION ${PROJECT_VERSION}
  13.     SOVERSION 1
  14. )
  15. # 编译定义
  16. target_compile_definitions(CoreLib PRIVATE CORELIB_EXPORTS)
  17. # 安装规则
  18. install(TARGETS CoreLib
  19.     EXPORT MyProjectTargets
  20.     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  21.     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  22.     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  23.     INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  24. )
  25. # 安装头文件
  26. install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
复制代码

UtilsLib/CMakeLists.txt:
  1. # 添加库
  2. add_library(UtilsLib SHARED
  3.     src/utilslib.cpp
  4. )
  5. # 设置头文件目录
  6. target_include_directories(UtilsLib PUBLIC
  7.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  8.     $<INSTALL_INTERFACE:include>
  9. )
  10. # 链接CoreLib
  11. target_link_libraries(UtilsLib PUBLIC CoreLib)
  12. # 设置库属性
  13. set_target_properties(UtilsLib PROPERTIES
  14.     VERSION ${PROJECT_VERSION}
  15.     SOVERSION 1
  16. )
  17. # 编译定义
  18. target_compile_definitions(UtilsLib PRIVATE UTILSLIB_EXPORTS)
  19. # 安装规则
  20. install(TARGETS UtilsLib
  21.     EXPORT MyProjectTargets
  22.     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  23.     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  24.     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  25.     INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  26. )
  27. # 安装头文件
  28. install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
复制代码

AppLib/CMakeLists.txt:
  1. # 添加库
  2. add_library(AppLib SHARED
  3.     src/applib.cpp
  4. )
  5. # 设置头文件目录
  6. target_include_directories(AppLib PUBLIC
  7.     $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  8.     $<INSTALL_INTERFACE:include>
  9. )
  10. # 链接其他库
  11. target_link_libraries(AppLib PUBLIC CoreLib UtilsLib)
  12. # 设置库属性
  13. set_target_properties(AppLib PROPERTIES
  14.     VERSION ${PROJECT_VERSION}
  15.     SOVERSION 1
  16. )
  17. # 编译定义
  18. target_compile_definitions(AppLib PRIVATE APPLIB_EXPORTS)
  19. # 安装规则
  20. install(TARGETS AppLib
  21.     EXPORT MyProjectTargets
  22.     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  23.     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  24.     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
  25.     INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
  26. )
  27. # 安装头文件
  28. install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
复制代码

examples/CMakeLists.txt:
  1. # 添加可执行文件
  2. add_executable(demo_app demo_app.cpp)
  3. # 链接库
  4. target_link_libraries(demo_app PRIVATE AppLib)
复制代码

这个例子展示了如何在复杂项目中管理多个相互依赖的库,并将它们作为一个整体导出。通过这种方式,其他项目可以简单地使用find_package(MyProject REQUIRED)来访问所有这些库。

最佳实践和常见问题

目录结构建议

良好的目录结构可以使项目更易于维护和理解。以下是一个推荐的项目结构:
  1. MyProject/
  2. ├── CMakeLists.txt              # 顶层CMake配置文件
  3. ├── README.md                   # 项目说明
  4. ├── LICENSE                     # 许可证文件
  5. ├── cmake/                      # CMake辅助文件
  6. │   ├── FindSomeLib.cmake       # 自定义查找模块
  7. │   └── MyProjectUtils.cmake    # 项目特定的CMake函数
  8. ├── include/                    # 公共头文件
  9. │   └── myproject/
  10. │       ├── config.h.in
  11. │       └── api.h
  12. ├── src/                        # 源文件
  13. │   ├── core/
  14. │   │   ├── core.cpp
  15. │   │   └── CMakeLists.txt
  16. │   ├── utils/
  17. │   │   ├── utils.cpp
  18. │   │   └── CMakeLists.txt
  19. │   └── CMakeLists.txt
  20. ├── tests/                      # 测试文件
  21. │   ├── CMakeLists.txt
  22. │   ├── test_core.cpp
  23. │   └── test_utils.cpp
  24. ├── examples/                   # 示例程序
  25. │   ├── CMakeLists.txt
  26. │   └── demo.cpp
  27. ├── docs/                       # 文档
  28. │   └── ...
  29. ├── third_party/                # 第三方库
  30. │   └── ...
  31. ├── scripts/                    # 构建和部署脚本
  32. │   └── ...
  33. └── build/                      # 构建目录(不提交到版本控制)
复制代码

常见错误和解决方案

1. 找不到依赖库

问题:find_package找不到所需的库。

解决方案:
  1. # 设置查找路径
  2.    list(APPEND CMAKE_PREFIX_PATH /path/to/lib)
  3.    # 或者设置特定的变量
  4.    set(SomeLib_DIR /path/to/lib/cmake/somelib)
  5.    # 提供更详细的错误信息
  6.    find_package(SomeLib REQUIRED
  7.        HINTS /path/to/lib
  8.        PATH_SUFFIXES cmake/somelib
  9.    )
复制代码

1. 链接错误

问题:链接阶段出现未定义的引用。

解决方案:
  1. # 确保所有必要的库都被链接
  2.    target_link_libraries(my_target
  3.        PUBLIC
  4.            LibA
  5.            LibB
  6.        PRIVATE
  7.            LibC
  8.    )
  9.    # 检查链接顺序(对于某些编译器很重要)
  10.    target_link_libraries(my_target
  11.        LibA
  12.        LibB
  13.        LibC
  14.    )
复制代码

1. 头文件找不到

问题:编译时找不到头文件。

解决方案:
  1. # 确保正确设置了包含目录
  2.    target_include_directories(my_target
  3.        PUBLIC
  4.            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  5.            $<INSTALL_INTERFACE:include>
  6.    )
  7.    # 对于系统头文件
  8.    target_include_directories(my_target SYSTEM PRIVATE /usr/include/somelib)
复制代码

1. 导出问题

问题:导出的目标在使用时出现问题。

解决方案:
  1. # 确保正确设置了目标属性
  2.    set_target_properties(my_lib PROPERTIES
  3.        INTERFACE_INCLUDE_DIRECTORIES "$<INSTALL_INTERFACE:include>"
  4.        INTERFACE_LINK_LIBRARIES ""
  5.    )
  6.    # 检查导出文件
  7.    export(TARGETS my_lib
  8.        FILE my_libTargets.cmake
  9.        NAMESPACE my_lib::
  10.    )
复制代码

性能优化

1. 减少配置时间
  1. # 使用更快的查找方法
  2.    find_package(Boost REQUIRED COMPONENTS filesystem system)
  3.    # 而不是
  4.    find_path(BOOST_INCLUDE_DIR boost/filesystem.hpp)
  5.    find_library(BOOST_FILESYSTEM_LIB boost_filesystem)
  6.    find_library(BOOST_SYSTEM_LIB boost_system)
  7.    # 避免不必要的文件操作
  8.    file(GLOB_RECURSE SOURCES "src/*.cpp")  # 避免在大型项目中使用
复制代码

1. 并行构建
  1. # 启用并行构建
  2.    set(CMAKE_BUILD_PARALLEL_LEVEL 8)  # 使用8个并行任务
  3.    # 或者使用命令行
  4.    # cmake --build . --parallel 8
复制代码

1. 预编译头
  1. # 启用预编译头
  2.    target_precompile_headers(my_target
  3.        PRIVATE
  4.            stdafx.h
  5.    )
复制代码

1. 统一构建目录
  1. # 使用统一的构建目录可以避免重复构建
  2.    set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build)
复制代码

结论

CMake是一个强大而灵活的构建系统,通过正确使用CMake和CMakeExport功能,我们可以大大提高项目开发效率。本文从CMake的基础配置开始,逐步深入到高级的导出技巧,涵盖了从简单库到复杂多级依赖项目的各种情况。

通过掌握CMake的基础知识,我们可以轻松管理项目的构建过程;通过使用CMakeExport,我们可以创建易于分发和使用的库;通过采用最佳实践,我们可以确保项目的可维护性和可扩展性。

随着CMake的不断发展和完善,它已经成为现代C++项目的事实标准构建系统。无论是小型个人项目还是大型企业级应用,CMake都能提供强大而灵活的解决方案。希望本文能够帮助读者全面掌握CMake构建系统与库文件导出方法,从而在实际项目中更加高效地使用这些工具。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则