你有没有想过如何使用 CMake(强大的跨平台构建系统)创建可重用的库?如果是这样,你并不孤单。许多开发人员都面临着编写模块化和可维护代码的挑战,这些代码可以在不同的项目之间轻松共享和集成。在本指南中,我们将向您展示如何克服这一挑战并使用 CMake 创建可重用的 C++ 库。您将学习:
在本指南结束时,您将对如何使用 CMake 创建可重用的库以及如何将这些原则应用于您自己的项目有深入的了解。
编写可重用库是促进软件开发效率和协作的基本实践。使用 CMake 作为我们的构建系统,我们能够将功能封装到模块化组件中,从而更轻松地在各种项目之间共享、集成和维护代码。这种方法有很多好处,例如:
根据JetBrains的一项调查,CMake是C++开发人员中最受欢迎的构建系统,有45%的受访者使用它。CMake 也被许多开源项目广泛使用,例如 Boost、LLVM、OpenCV 和 TensorFlow。这些事实证明了 CMake 在创建可重用库方面的受欢迎程度和实用性。
该项目举例说明了一个实际用例,演示了如何使用 CMake 创建可重用的 C++ 库。该结构由多个组件组成,包括:
每个元素都经过精心组织,以展示代码组织、构建和集成方面的最佳实践。我们将在以下各节中详细解释每个组件。
该命令的作用不仅仅是命名项目并指定其版本和语言。它为变量建立了一个分层范围,为配置整个构建系统提供了一个清晰的结构。project()
project(my_libraries VERSION 1.0 LANGUAGES CXX)
CMake 项目可以涉及多个目录,每个目录都有自己的CMakeLists.txt文件。这种分层结构支持模块化开发,允许开发人员独立管理项目的特定方面。此外,该参数有助于在整个项目中生成一致的版本信息,这对于文档和兼容性管理至关重要。VERSION
该命令是通往外部库丰富生态系统的门户。它超越了简单的存在检查;它协调项目与外部库之间的复杂交互。find_package()
find_package(ExternalLibraries 1.0 REQUIRED EXACT)
在这里,我们超越了单纯的包容;我们主张这些选项的精确性。这不仅确保了所需的库存在,而且还确保了它与指定的版本精确匹配。像这样细致入微的方法可以防止因使用不兼容的版本而产生意外的副作用。此命令使用特定于 ExternalLibraries 的模块,该模块通常由库本身提供。REQUIRED EXACTFind<Package>.cmake
该命令将源代码转换为可重用的单元,即库。add_library()
add_library(MyLibraryA src/my_library_a.cpp)
虽然添加源文件是基础,但考虑库在更广泛项目中的作用同样重要。它是一个独立的实用程序、一个集成组件,还是一个与其他模块的接口?库设计中的明确意图有助于使代码库更易于维护和理解。
该命令决定了库与系统其余部分的交互方式。target_include_directories()
target_include_directories(MyLibraryA
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD\_INTERFACE:$(CMAKE_CURRENT_SOURCE_DIR)/include>
)
通过分离构建和安装接口,我们的库面向未来。用户在开发过程中和安装后都应无缝地包含我们的标头。这种做法简化了协作,并确保了一致的开发人员体验。
该表达式可能看起来很隐晦,但它是一个 CMake 生成器表达式。它根据是在构建过程中还是在安装后访问标头来动态调整包含目录路径。这确保了包含标头的一致性,无论上下文如何。$<INSTALL_INTERFACE:include>
该命令协调库和外部依赖项之间的关系。target_link_libraries()
target_link_libraries(MyLibraryA
PUBLIC
ExternalLibraries::ExternalLibraryA
PRIVATE
ExternalLibraries::ExternalLibraryB
)
了解和链接之间的区别至关重要。链接意味着您的库的用户也应链接到 。这一决定暴露了一个故意的界面。另一方面,指向的链接指示此依赖项是内部实现详细信息。这些决定决定了图书馆的感知和使用方式。PUBLICPRIVATEPUBLICExternalLibraryAPRIVATEExternalLibraryB
该命令可确定库的分布,使其为更广泛地采用做好准备。install()
install(TARGETS MyLibraryA
EXPORT MyLibraries-export
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
命令中的选项很重要。它会生成一个导出集,用于捕获有关已安装目标的信息,使其可用于其他项目。这封装了整个库及其配置,确保为外部开发人员提供无缝集成体验。EXPORTinstall()
CMakePackageConfigHelpers 模块通过生成配置和版本文件来提升库的专业性。
include(CMakePackageConfigHelpers)
write_basic_package_version_files(${CMAKE_CURRENT_SOURCE_DIR}/cmake/MyLibrariesConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/MyLibrariesConfigVersion.cmake
DESTINATION "lib/cmake/MyLibraries")
该选项传达了基本的兼容性保证。它断言具有相同主要版本的库的项目应该是兼容的。此信息对于依赖您的库做出有关更新的明智决策的其他开发人员至关重要。COMPATIBILITY SameMajorVersionwrite_basic_package_version_file
现在我们已经制作了可重用的库 MyLibraryA,让我们来探索一下如何将它无缝集成到我们的应用程序中,这个应用程序被亲切地命名为_应用程序_。此集成过程涉及配置应用程序的CMakeLists.txt文件以利用 MyLibraryA 提供的功能。
旅程从建立我们应用程序项目的基本细节开始。该命令用于定义项目名称、版本和编程语言。project()
project(application VERSION 1.0 LANGUAGES CXX)
在深入研究应用程序代码之前,我们必须确保我们的应用程序知道并正确配置为使用 MyLibraryA。该命令是实现此目的的门户。它查找并配置 MyLibraries 包,确保我们的应用程序可以依赖 MyLibraryA 的特定版本。find_package()
find_package(MyLibraries 1.0 REQUIRED EXACT)
此命令为我们的应用程序与 MyLibraryA 无缝集成奠定了基础,为应用程序与可重用库之间的有效协作铺平了道路。
设置项目并找到库后,下一步涉及使用命令为我们的应用程序创建可执行目标。此命令将源文件与“app”可执行文件相关联。add_executable()src/application.cpp
add_executable(app src/application.cpp)
通过执行此命令,我们将_应用程序_建立为应用程序的入口点,其中 MyLibraryA 将用于增强其功能。
现在是将我们的应用程序与 MyLibraryA 链接的关键时刻。该命令完成此链接,确保“应用程序”可执行文件受益于 MyLibraryA 中封装的功能。target_link_libraries()
target_link_libraries(app PRIVATE MyLibraries::MyLibraryA)
链接指定 MyLibraryA 是我们应用程序的内部详细信息。这种区别可确保应用程序可以利用库的功能,而无需将其暴露给外部实体。PRIVATE
使用 CMake 创建可重用的 C++ 库的旅程揭示了软件开发的强大范式,促进了效率、模块化和协作。通过利用 CMake 的功能,开发人员可以将功能封装到模块化组件中,从而生成不仅易于共享和集成的代码,而且还保持高度的一致性和可移植性。
本指南中强调的要点围绕着建立一个健壮的项目结构,利用外部依赖关系的_find_package_机制,以及在编写CMakeLists.txt文件时采用最佳实践。这种方法的好处是多方面的:它减少了代码重复和复杂性,确保了代码的质量和一致性,提高了不同项目的可移植性和兼容性,增强了性能和可伸缩性,并促进了测试和调试。
编写CMakeLists.txt文件的技巧包括清晰的组织、深思熟虑的构建和安装界面分离,以及了解目标链接的细微差别以实现有效封装。使用 CMakePackageConfigHelpers 生成配置和版本文件增加了一层专业性,使库更易于访问和集成到各种项目中。
尝试提供的原则,将它们应用到您自己的项目中,并使用 CMake 创建可重用库的强大功能!