|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
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命令:
- # 指定CMake最低版本要求
- cmake_minimum_required(VERSION 3.10)
- # 定义项目名称和版本
- project(MyProject VERSION 1.0.0 LANGUAGES CXX)
- # 添加可执行文件
- add_executable(my_app main.cpp)
- # 添加库
- add_library(my_lib STATIC lib_source.cpp)
- # 包含目录
- include_directories(include)
- # 链接库
- target_link_libraries(my_app my_lib)
- # 设置变量
- set(MY_VARIABLE "value")
- # 条件判断
- if(CMAKE_BUILD_TYPE STREQUAL "Debug")
- add_definitions(-DDEBUG)
- endif()
- # 打印消息
- message(STATUS "Building project: ${PROJECT_NAME}")
复制代码
项目结构
一个典型的CMake项目结构如下:
- my_project/
- ├── CMakeLists.txt # 主CMake配置文件
- ├── include/ # 头文件目录
- │ └── my_lib/
- │ └── my_lib.h
- ├── src/ # 源文件目录
- │ ├── my_lib.cpp
- │ └── main.cpp
- ├── build/ # 构建目录(通常不提交到版本控制)
- └── README.md # 项目说明文档
复制代码
这种结构将源代码、头文件和构建文件分开,使项目更加清晰和易于管理。
CMake项目配置
最简单的CMake项目
让我们从一个最简单的CMake项目开始。假设我们有一个包含单个源文件的简单程序:
- // main.cpp
- #include <iostream>
- int main() {
- std::cout << "Hello, CMake!" << std::endl;
- return 0;
- }
复制代码
对应的CMakeLists.txt文件非常简单:
- # 要求CMake最低版本
- cmake_minimum_required(VERSION 3.10)
- # 定义项目
- project(HelloCMake)
- # 添加可执行文件
- add_executable(hello_cmake main.cpp)
复制代码
要构建这个项目,可以执行以下命令:
- mkdir build
- cd build
- cmake ..
- make
复制代码
设置项目属性
CMake允许我们设置各种项目属性,如版本号、语言标准等:
- cmake_minimum_required(VERSION 3.10)
- # 设置项目名称、版本和语言
- project(MyProject
- VERSION 1.0.0
- DESCRIPTION "My awesome project"
- LANGUAGES CXX
- )
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- set(CMAKE_CXX_EXTENSIONS OFF)
- # 定义一些有用的变量
- set(PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
- set(PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
复制代码
添加源文件
当项目包含多个源文件时,我们可以使用以下方式添加它们:
- # 方式1:直接列出所有源文件
- add_executable(my_app
- src/main.cpp
- src/class1.cpp
- src/class2.cpp
- )
- # 方式2:使用变量存储源文件列表
- set(SOURCES
- src/main.cpp
- src/class1.cpp
- src/class2.cpp
- )
- add_executable(my_app ${SOURCES})
- # 方式3:使用file命令自动收集源文件
- file(GLOB_RECURSE SOURCES "src/*.cpp")
- add_executable(my_app ${SOURCES})
复制代码
注意:使用file(GLOB_RECURSE ...)自动收集源文件虽然方便,但也有缺点。CMake无法自动检测到新添加的文件,除非重新运行CMake配置。因此,对于大型项目,建议显式列出源文件。
设置编译选项
CMake允许我们为不同的构建类型设置不同的编译选项:
- # 设置构建类型(Release, Debug, RelWithDebInfo, MinSizeRel)
- if(NOT CMAKE_BUILD_TYPE)
- set(CMAKE_BUILD_TYPE "Release")
- 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")
- add_definitions(-DDEBUG=1)
- endif()
- # 添加编译器特定的警告选项
- if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- add_compile_options(-Wall -Wextra -Wpedantic)
- elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
- add_compile_options(/W4)
- endif()
复制代码
构建可执行文件和库
构建可执行文件
构建可执行文件是CMake的基本功能之一。我们已经在前面的例子中看到了add_executable命令的基本用法。现在让我们看一个更复杂的例子:
- cmake_minimum_required(VERSION 3.10)
- project(MyApp VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加源文件
- set(APP_SOURCES
- src/main.cpp
- src/app.cpp
- src/utils.cpp
- )
- # 添加头文件目录
- include_directories(${PROJECT_SOURCE_DIR}/include)
- # 添加可执行文件
- add_executable(${PROJECT_NAME} ${APP_SOURCES})
- # 设置输出目录
- set_target_properties(${PROJECT_NAME} PROPERTIES
- RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
- )
- # 链接系统库(如果需要)
- find_package(Threads REQUIRED)
- target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads)
复制代码
构建静态库
静态库是在编译时链接到可执行文件中的库文件。在CMake中,我们可以使用add_library命令创建静态库:
- cmake_minimum_required(VERSION 3.10)
- project(MyStaticLib VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加库源文件
- set(LIB_SOURCES
- src/my_lib.cpp
- src/utils.cpp
- )
- # 添加静态库
- add_library(${PROJECT_NAME} STATIC ${LIB_SOURCES})
- # 设置头文件目录
- target_include_directories(${PROJECT_NAME} PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 设置库属性
- set_target_properties(${PROJECT_NAME} PROPERTIES
- ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
- )
- # 编译定义
- target_compile_definitions(${PROJECT_NAME} PRIVATE MY_LIB_EXPORTS)
复制代码
构建动态库
动态库(在Windows上称为DLL,在Linux/Unix上称为共享库)是在运行时加载的库文件。创建动态库的CMake代码与静态库类似:
- cmake_minimum_required(VERSION 3.10)
- project(MySharedLib VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加库源文件
- set(LIB_SOURCES
- src/my_lib.cpp
- src/utils.cpp
- )
- # 添加动态库
- add_library(${PROJECT_NAME} SHARED ${LIB_SOURCES})
- # 设置头文件目录
- target_include_directories(${PROJECT_NAME} PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 设置库属性
- set_target_properties(${PROJECT_NAME} PROPERTIES
- LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
- RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin # Windows DLL需要
- )
- # 编译定义
- target_compile_definitions(${PROJECT_NAME} PRIVATE MY_SHARED_LIB_EXPORTS)
复制代码
对于动态库,我们通常需要处理符号导出问题。在头文件中,我们可以使用预处理器指令来控制符号的导出:
- // my_lib.h
- #pragma once
- #ifdef MY_SHARED_LIB_EXPORTS
- #define MY_API __declspec(dllexport) // Windows导出
- #else
- #define MY_API __declspec(dllimport) // Windows导入
- #endif
- // 对于非Windows平台
- #ifndef _WIN32
- #define MY_API
- #endif
- // 要导出的类或函数
- class MY_API MyClass {
- public:
- void doSomething();
- };
- MY_API void myFunction();
复制代码
CMake导出基础
什么是CMake导出
CMake导出是一种机制,允许我们创建包含构建目标信息的文件,这些文件可以被其他项目使用,而无需知道原始项目的构建细节。通过导出,其他项目可以轻松地找到并使用我们创建的库,包括链接到正确的库文件、包含正确的头文件目录,并传递必要的编译选项。
CMake导出的主要优势包括:
• 简化依赖管理
• 提供跨平台兼容性
• 支持不同构建类型(Debug/Release)
• 处理传递依赖关系
• 支持包管理器集成
基本导出命令
CMake提供了几个关键命令用于导出目标:
- # 导出目标到文件
- export(TARGETS my_target FILE my_target.cmake)
- # 安装并导出目标
- install(TARGETS my_target
- EXPORT my_export
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
- )
- # 安装导出文件
- install(EXPORT my_export
- FILE my_targets.cmake
- DESTINATION lib/cmake/my_project
- )
复制代码
导出目标
让我们看一个完整的例子,展示如何创建一个库并导出它:
- cmake_minimum_required(VERSION 3.10)
- project(MyLib VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加库源文件
- set(LIB_SOURCES
- src/my_lib.cpp
- src/utils.cpp
- )
- # 添加动态库
- add_library(${PROJECT_NAME} SHARED ${LIB_SOURCES})
- # 设置头文件目录
- target_include_directories(${PROJECT_NAME} PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 设置库属性
- set_target_properties(${PROJECT_NAME} PROPERTIES
- LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
- RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
- VERSION ${PROJECT_VERSION}
- SOVERSION 1
- )
- # 编译定义
- target_compile_definitions(${PROJECT_NAME} PRIVATE MY_LIB_EXPORTS)
- # 生成导出文件
- export(TARGETS ${PROJECT_NAME} FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake)
- # 安装规则
- install(TARGETS ${PROJECT_NAME}
- EXPORT ${PROJECT_NAME}Targets
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- RUNTIME DESTINATION bin
- INCLUDES DESTINATION include
- )
- # 安装头文件
- install(DIRECTORY include/ DESTINATION include)
- # 安装导出文件
- install(EXPORT ${PROJECT_NAME}Targets
- FILE ${PROJECT_NAME}Targets.cmake
- DESTINATION lib/cmake/${PROJECT_NAME}
- )
- # 生成配置文件
- include(CMakePackageConfigHelpers)
- write_basic_package_version_file(
- "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY AnyNewerVersion
- )
- # 安装配置文件
- install(FILES
- "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
- DESTINATION lib/cmake/${PROJECT_NAME}
- )
复制代码
高级导出技巧
导出配置
CMake允许我们为不同的构建类型创建不同的导出配置。这对于支持多配置生成器(如Visual Studio)特别有用:
- # 为每个配置创建单独的导出文件
- foreach(config_type DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)
- export(TARGETS my_target
- FILE "my_target_${config_type}.cmake"
- EXPORT_LINK_INTERFACE_LIBRARIES
- CXX_MODULES_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/modules"
- )
- endforeach()
- # 安装时处理多配置
- install(TARGETS my_target
- EXPORT my_export
- RUNTIME DESTINATION bin
- LIBRARY DESTINATION lib
- ARCHIVE DESTINATION lib
- COMPONENT runtime
- )
- # 针对不同配置设置不同的属性
- set_target_properties(my_target PROPERTIES
- DEBUG_POSTFIX "d"
- RELWITHDEBINFO_POSTFIX "rd"
- )
复制代码
命名空间
使用命名空间可以避免目标名称冲突,特别是在大型项目中:
- # 创建命名空间
- add_library(my_lib STATIC lib.cpp)
- add_library(my_ns::my_lib ALIAS my_lib)
- # 导出时使用命名空间
- export(TARGETS my_lib NAMESPACE my_ns:: FILE MyLibTargets.cmake)
- # 在其他项目中使用
- find_package(MyLib REQUIRED)
- target_link_libraries(my_app PRIVATE my_ns::my_lib)
复制代码
版本控制
版本控制对于库的维护和兼容性非常重要。CMake提供了强大的版本控制功能:
- # 设置项目版本
- project(MyLib VERSION 1.2.3)
- # 设置目标版本属性
- set_target_properties(MyLib PROPERTIES
- VERSION ${PROJECT_VERSION} # 完整版本号 (1.2.3)
- SOVERSION ${PROJECT_VERSION_MAJOR} # 主版本号 (1)
- )
- # 生成版本文件
- include(CMakePackageConfigHelpers)
- write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY AnyNewerVersion # 或 SameMajorVersion
- )
- # 安装版本文件
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
- DESTINATION lib/cmake/MyLib
- )
复制代码
依赖管理
在导出库时,正确处理依赖关系至关重要:
- # 假设我们的库依赖于其他库
- find_package(Boost REQUIRED COMPONENTS filesystem system)
- find_package(Threads REQUIRED)
- # 创建库
- add_library(MyLib SHARED my_lib.cpp)
- # 链接依赖库
- target_link_libraries(MyLib
- PUBLIC
- Boost::filesystem
- Boost::system
- PRIVATE
- Threads::Threads
- )
- # 导出时包含依赖关系
- export(TARGETS MyLib
- FILE MyLibTargets.cmake
- LINK_INTERFACE_LIBRARIES # 确保链接接口被正确导出
- )
- # 在配置文件中处理依赖
- configure_package_config_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/MyLibConfig.cmake.in"
- "${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
- INSTALL_DESTINATION lib/cmake/MyLib
- )
复制代码
对应的MyLibConfig.cmake.in文件可能如下:
- @PACKAGE_INIT@
- include(CMakeFindDependencyMacro)
- # 检查并加载依赖
- find_dependency(Boost REQUIRED COMPONENTS filesystem system)
- find_dependency(Threads REQUIRED)
- # 包含目标文件
- include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
- # 检查兼容性
- check_required_components(MyLib)
复制代码
实际项目案例
简单库的导出和使用
让我们创建一个完整的例子,展示如何创建一个简单的数学库,导出它,然后在另一个项目中使用它。
首先,创建数学库项目结构:
- MathLib/
- ├── CMakeLists.txt
- ├── include/
- │ └── mathlib.h
- └── src/
- └── mathlib.cpp
复制代码
mathlib.h文件:
- #pragma once
- #ifdef MATHLIB_EXPORTS
- #define MATHLIB_API __declspec(dllexport)
- #else
- #define MATHLIB_API __declspec(dllimport)
- #endif
- class MATHLIB_API MathOperations {
- public:
- static double add(double a, double b);
- static double subtract(double a, double b);
- static double multiply(double a, double b);
- static double divide(double a, double b);
- };
复制代码
mathlib.cpp文件:
- #include "mathlib.h"
- #include <stdexcept>
- double MathOperations::add(double a, double b) {
- return a + b;
- }
- double MathOperations::subtract(double a, double b) {
- return a - b;
- }
- double MathOperations::multiply(double a, double b) {
- return a * b;
- }
- double MathOperations::divide(double a, double b) {
- if (b == 0) {
- throw std::invalid_argument("Division by zero");
- }
- return a / b;
- }
复制代码
CMakeLists.txt文件:
- cmake_minimum_required(VERSION 3.10)
- project(MathLib VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加库源文件
- add_library(${PROJECT_NAME} SHARED
- src/mathlib.cpp
- )
- # 设置头文件目录
- target_include_directories(${PROJECT_NAME} PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 设置库属性
- set_target_properties(${PROJECT_NAME} PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION 1
- )
- # 编译定义
- target_compile_definitions(${PROJECT_NAME} PRIVATE MATHLIB_EXPORTS)
- # 生成导出文件
- export(TARGETS ${PROJECT_NAME} FILE ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake)
- # 安装规则
- include(GNUInstallDirs)
- install(TARGETS ${PROJECT_NAME}
- EXPORT ${PROJECT_NAME}Targets
- 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})
- # 安装导出文件
- install(EXPORT ${PROJECT_NAME}Targets
- FILE ${PROJECT_NAME}Targets.cmake
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
- )
- # 生成配置文件
- include(CMakePackageConfigHelpers)
- configure_package_config_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in"
- "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
- INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
- )
- # 生成版本文件
- write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY AnyNewerVersion
- )
- # 安装配置文件
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
- "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
- )
复制代码
MathLibConfig.cmake.in文件:
- @PACKAGE_INIT@
- include("${CMAKE_CURRENT_LIST_DIR}/MathLibTargets.cmake")
- check_required_components(MathLib)
复制代码
现在,让我们创建一个使用这个库的应用程序项目:
- MathApp/
- ├── CMakeLists.txt
- └── src/
- └── main.cpp
复制代码
main.cpp文件:
- #include <iostream>
- #include "mathlib.h"
- int main() {
- double a = 10.0;
- double b = 5.0;
-
- std::cout << a << " + " << b << " = " << MathOperations::add(a, b) << std::endl;
- std::cout << a << " - " << b << " = " << MathOperations::subtract(a, b) << std::endl;
- std::cout << a << " * " << b << " = " << MathOperations::multiply(a, b) << std::endl;
- std::cout << a << " / " << b << " = " << MathOperations::divide(a, b) << std::endl;
-
- return 0;
- }
复制代码
CMakeLists.txt文件:
- cmake_minimum_required(VERSION 3.10)
- project(MathApp VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 查找MathLib库
- find_package(MathLib REQUIRED)
- # 添加可执行文件
- add_executable(${PROJECT_NAME} src/main.cpp)
- # 链接MathLib库
- target_link_libraries(${PROJECT_NAME} PRIVATE MathLib::MathLib)
复制代码
要构建这个项目,首先构建并安装MathLib:
- cd MathLib
- mkdir build
- cd build
- cmake ..
- make
- sudo make install # 或者使用适当的权限安装
复制代码
然后构建MathApp:
- cd MathApp
- mkdir build
- cd build
- cmake ..
- make
复制代码
复杂项目的多级导出
在更复杂的项目中,我们可能需要导出多个相互依赖的库。让我们考虑一个项目,其中包含三个库:CoreLib、UtilsLib和AppLib。CoreLib是基础库,UtilsLib依赖于CoreLib,AppLib依赖于前两者。
项目结构:
- MyProject/
- ├── CMakeLists.txt
- ├── CoreLib/
- │ ├── CMakeLists.txt
- │ ├── include/
- │ │ └── corelib.h
- │ └── src/
- │ └── corelib.cpp
- ├── UtilsLib/
- │ ├── CMakeLists.txt
- │ ├── include/
- │ │ └── utilslib.h
- │ └── src/
- │ └── utilslib.cpp
- ├── AppLib/
- │ ├── CMakeLists.txt
- │ ├── include/
- │ │ └── applib.h
- │ └── src/
- │ └── applib.cpp
- └── examples/
- ├── CMakeLists.txt
- └── demo_app.cpp
复制代码
顶层CMakeLists.txt:
- cmake_minimum_required(VERSION 3.10)
- project(MyProject VERSION 1.0.0 LANGUAGES CXX)
- # 设置C++标准
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
- # 添加子目录
- add_subdirectory(CoreLib)
- add_subdirectory(UtilsLib)
- add_subdirectory(AppLib)
- add_subdirectory(examples)
- # 导出所有目标
- export(TARGETS CoreLib UtilsLib AppLib
- FILE ${PROJECT_BINARY_DIR}/MyProjectTargets.cmake
- NAMESPACE MyProject::
- )
- # 安装规则
- include(GNUInstallDirs)
- # 安装导出文件
- install(EXPORT MyProjectTargets
- FILE MyProjectTargets.cmake
- NAMESPACE MyProject::
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
- )
- # 生成配置文件
- include(CMakePackageConfigHelpers)
- configure_package_config_file(
- "${CMAKE_CURRENT_SOURCE_DIR}/MyProjectConfig.cmake.in"
- "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake"
- INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
- )
- # 生成版本文件
- write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY AnyNewerVersion
- )
- # 安装配置文件
- install(FILES
- "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfig.cmake"
- "${CMAKE_CURRENT_BINARY_DIR}/MyProjectConfigVersion.cmake"
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyProject
- )
复制代码
CoreLib/CMakeLists.txt:
- # 添加库
- add_library(CoreLib SHARED
- src/corelib.cpp
- )
- # 设置头文件目录
- target_include_directories(CoreLib PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 设置库属性
- set_target_properties(CoreLib PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION 1
- )
- # 编译定义
- target_compile_definitions(CoreLib PRIVATE CORELIB_EXPORTS)
- # 安装规则
- install(TARGETS CoreLib
- EXPORT MyProjectTargets
- 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})
复制代码
UtilsLib/CMakeLists.txt:
- # 添加库
- add_library(UtilsLib SHARED
- src/utilslib.cpp
- )
- # 设置头文件目录
- target_include_directories(UtilsLib PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 链接CoreLib
- target_link_libraries(UtilsLib PUBLIC CoreLib)
- # 设置库属性
- set_target_properties(UtilsLib PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION 1
- )
- # 编译定义
- target_compile_definitions(UtilsLib PRIVATE UTILSLIB_EXPORTS)
- # 安装规则
- install(TARGETS UtilsLib
- EXPORT MyProjectTargets
- 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})
复制代码
AppLib/CMakeLists.txt:
- # 添加库
- add_library(AppLib SHARED
- src/applib.cpp
- )
- # 设置头文件目录
- target_include_directories(AppLib PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 链接其他库
- target_link_libraries(AppLib PUBLIC CoreLib UtilsLib)
- # 设置库属性
- set_target_properties(AppLib PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION 1
- )
- # 编译定义
- target_compile_definitions(AppLib PRIVATE APPLIB_EXPORTS)
- # 安装规则
- install(TARGETS AppLib
- EXPORT MyProjectTargets
- 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})
复制代码
examples/CMakeLists.txt:
- # 添加可执行文件
- add_executable(demo_app demo_app.cpp)
- # 链接库
- target_link_libraries(demo_app PRIVATE AppLib)
复制代码
这个例子展示了如何在复杂项目中管理多个相互依赖的库,并将它们作为一个整体导出。通过这种方式,其他项目可以简单地使用find_package(MyProject REQUIRED)来访问所有这些库。
最佳实践和常见问题
目录结构建议
良好的目录结构可以使项目更易于维护和理解。以下是一个推荐的项目结构:
- MyProject/
- ├── CMakeLists.txt # 顶层CMake配置文件
- ├── README.md # 项目说明
- ├── LICENSE # 许可证文件
- ├── cmake/ # CMake辅助文件
- │ ├── FindSomeLib.cmake # 自定义查找模块
- │ └── MyProjectUtils.cmake # 项目特定的CMake函数
- ├── include/ # 公共头文件
- │ └── myproject/
- │ ├── config.h.in
- │ └── api.h
- ├── src/ # 源文件
- │ ├── core/
- │ │ ├── core.cpp
- │ │ └── CMakeLists.txt
- │ ├── utils/
- │ │ ├── utils.cpp
- │ │ └── CMakeLists.txt
- │ └── CMakeLists.txt
- ├── tests/ # 测试文件
- │ ├── CMakeLists.txt
- │ ├── test_core.cpp
- │ └── test_utils.cpp
- ├── examples/ # 示例程序
- │ ├── CMakeLists.txt
- │ └── demo.cpp
- ├── docs/ # 文档
- │ └── ...
- ├── third_party/ # 第三方库
- │ └── ...
- ├── scripts/ # 构建和部署脚本
- │ └── ...
- └── build/ # 构建目录(不提交到版本控制)
复制代码
常见错误和解决方案
1. 找不到依赖库
问题:find_package找不到所需的库。
解决方案:
- # 设置查找路径
- list(APPEND CMAKE_PREFIX_PATH /path/to/lib)
- # 或者设置特定的变量
- set(SomeLib_DIR /path/to/lib/cmake/somelib)
- # 提供更详细的错误信息
- find_package(SomeLib REQUIRED
- HINTS /path/to/lib
- PATH_SUFFIXES cmake/somelib
- )
复制代码
1. 链接错误
问题:链接阶段出现未定义的引用。
解决方案:
- # 确保所有必要的库都被链接
- target_link_libraries(my_target
- PUBLIC
- LibA
- LibB
- PRIVATE
- LibC
- )
- # 检查链接顺序(对于某些编译器很重要)
- target_link_libraries(my_target
- LibA
- LibB
- LibC
- )
复制代码
1. 头文件找不到
问题:编译时找不到头文件。
解决方案:
- # 确保正确设置了包含目录
- target_include_directories(my_target
- PUBLIC
- $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
- $<INSTALL_INTERFACE:include>
- )
- # 对于系统头文件
- target_include_directories(my_target SYSTEM PRIVATE /usr/include/somelib)
复制代码
1. 导出问题
问题:导出的目标在使用时出现问题。
解决方案:
- # 确保正确设置了目标属性
- set_target_properties(my_lib PROPERTIES
- INTERFACE_INCLUDE_DIRECTORIES "$<INSTALL_INTERFACE:include>"
- INTERFACE_LINK_LIBRARIES ""
- )
- # 检查导出文件
- export(TARGETS my_lib
- FILE my_libTargets.cmake
- NAMESPACE my_lib::
- )
复制代码
性能优化
1. 减少配置时间
- # 使用更快的查找方法
- find_package(Boost REQUIRED COMPONENTS filesystem system)
- # 而不是
- find_path(BOOST_INCLUDE_DIR boost/filesystem.hpp)
- find_library(BOOST_FILESYSTEM_LIB boost_filesystem)
- find_library(BOOST_SYSTEM_LIB boost_system)
- # 避免不必要的文件操作
- file(GLOB_RECURSE SOURCES "src/*.cpp") # 避免在大型项目中使用
复制代码
1. 并行构建
- # 启用并行构建
- set(CMAKE_BUILD_PARALLEL_LEVEL 8) # 使用8个并行任务
- # 或者使用命令行
- # cmake --build . --parallel 8
复制代码
1. 预编译头
- # 启用预编译头
- target_precompile_headers(my_target
- PRIVATE
- stdafx.h
- )
复制代码
1. 统一构建目录
- # 使用统一的构建目录可以避免重复构建
- set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/build)
复制代码
结论
CMake是一个强大而灵活的构建系统,通过正确使用CMake和CMakeExport功能,我们可以大大提高项目开发效率。本文从CMake的基础配置开始,逐步深入到高级的导出技巧,涵盖了从简单库到复杂多级依赖项目的各种情况。
通过掌握CMake的基础知识,我们可以轻松管理项目的构建过程;通过使用CMakeExport,我们可以创建易于分发和使用的库;通过采用最佳实践,我们可以确保项目的可维护性和可扩展性。
随着CMake的不断发展和完善,它已经成为现代C++项目的事实标准构建系统。无论是小型个人项目还是大型企业级应用,CMake都能提供强大而灵活的解决方案。希望本文能够帮助读者全面掌握CMake构建系统与库文件导出方法,从而在实际项目中更加高效地使用这些工具。 |
|