CMake为静态库添加可选依赖,无需强制消费者依赖其依赖库
我有一个用CMake构建的静态库thelib,正在集成到vcpkg中。库包含可选功能:基于OpenSSL的SSL包装、基于SQLite的数据库包装,核心功能不依赖这些库。源码结构如下:
include: core.h ssl.h sql.h src: core.cpp ssl.cpp sql.cpp
其中ssl.cpp和sql.cpp分别依赖OpenSSL和SQLite的头文件实现功能,core.cpp无外部依赖。我通过vcpkg清单的特性按需启用功能,CMake配置片段如下:
if (OPENSSL_FEATURE) find_package(OpenSSL REQUIRED) target_compile_definitions(thelib PUBLIC HAVE_OPENSSL) target_link_libraries(thelib PRIVATE OpenSSL::SSL PRIVATE OpenSSL::Crypto) endif()
现在有另一个库otherlib仅依赖thelib的核心功能,用CMake和vcpkg构建,配置如下:
find_package(thelib REQUIRED) target_link_libraries(otherlib PRIVATE thelib)
但CMake报错,提示找不到thelib依赖的OpenSSL::SSL等库。手动添加对应的find_package(不执行target_link_libraries)后能构建通过,但otherlib的消费者会被要求查找所有未使用的依赖库。
我原本以为target_link_libraries的PRIVATE关键字能向消费者隐藏依赖,但静态库的依赖仍会被添加到链接目标中。考虑过拆分子库,但小型库工作量太大,想知道如何让CMake仅链接实际使用的依赖?
补充:安装生成的thelib-target.cmake包含以下代码,导致链接thelib时必须让这些依赖可见,即便用不到:
set_target_properties(thelib::thelib PROPERTIES INTERFACE_COMPILE_DEFINITIONS "HAVE_OPENSSL;HAVE_SQLITE" INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" INTERFACE_LINK_LIBRARIES "\$<LINK_ONLY:OpenSSL::SSL>;\$<LINK_ONLY:OpenSSL::Crypto>;\$<LINK_ONLY:SQLite::SQLite3>" )
不想拆分库,能否用CMake组件解决这个问题?
完全可以用CMake的**组件(Components)**特性解决,无需拆分子库。核心思路是把可选功能封装成组件,让消费者只声明依赖自己需要的组件,CMake只会暴露对应组件的依赖。
步骤1:改造thelib的CMake配置
修改thelib的CMakeLists.txt,将可选功能定义为组件,根据组件启用状态配置依赖和接口属性:
# 定义核心库目标 add_library(thelib STATIC src/core.cpp) target_include_directories(thelib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> ) # 声明组件:Core(默认必选)、SSL、SQL set(THELIB_COMPONENTS Core SSL SQL) set(THELIB_DEFAULT_COMPONENTS Core) # 处理SSL组件 option(BUILD_SSL_COMPONENT "Build SSL support component" OFF) if(BUILD_SSL_COMPONENT) find_package(OpenSSL REQUIRED) target_sources(thelib PRIVATE src/ssl.cpp) target_compile_definitions(thelib PUBLIC HAVE_OPENSSL) # 仅当消费者依赖SSL组件时,才暴露OpenSSL依赖 set_property(TARGET thelib APPEND PROPERTY INTERFACE_LINK_LIBRARIES $<IF:$<IN_LIST:SSL,${thelib_FIND_COMPONENTS}>,OpenSSL::SSL;OpenSSL::Crypto,> ) endif() # 处理SQL组件 option(BUILD_SQL_COMPONENT "Build SQLite support component" OFF) if(BUILD_SQL_COMPONENT) find_package(SQLite3 REQUIRED) target_sources(thelib PRIVATE src/sql.cpp) target_compile_definitions(thelib PUBLIC HAVE_SQLITE) # 仅当消费者依赖SQL组件时,才暴露SQLite依赖 set_property(TARGET thelib APPEND PROPERTY INTERFACE_LINK_LIBRARIES $<IF:$<IN_LIST:SQL,${thelib_FIND_COMPONENTS}>,SQLite::SQLite3,> ) endif() # 生成组件相关的安装配置 include(CMakePackageConfigHelpers) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/thelibConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion ) # 创建Config模板的逻辑文件 configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/thelibConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/thelibConfig.cmake" INSTALL_DESTINATION lib/cmake/thelib NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) # 安装目标与cmake配置文件 install(TARGETS thelib EXPORT thelibTargets ARCHIVE DESTINATION lib INCLUDES DESTINATION include ) install(EXPORT thelibTargets FILE thelibTargets.cmake NAMESPACE thelib:: DESTINATION lib/cmake/thelib ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/thelibConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/thelibConfigVersion.cmake" DESTINATION lib/cmake/thelib )
同时创建thelibConfig.cmake.in模板,处理组件的依赖查找逻辑:
@PACKAGE_INIT@ include(CMakeFindDependencyMacro) # 仅查找消费者声明依赖的组件对应的外部库 foreach(component IN LISTS thelib_FIND_COMPONENTS) if(component STREQUAL "SSL") find_dependency(OpenSSL REQUIRED) elseif(component STREQUAL "SQL") find_dependency(SQLite3 REQUIRED) endif() endforeach() include("${CMAKE_CURRENT_LIST_DIR}/thelibTargets.cmake") check_required_components(thelib)
步骤2:关联vcpkg特性与CMake组件
修改thelib的vcpkg.json,将vcpkg特性与CMake组件绑定:
{ "name": "thelib", "version": "1.0.0", "features": { "ssl": { "description": "启用SSL功能", "dependencies": ["openssl"] }, "sql": { "description": "启用SQLite功能", "dependencies": ["sqlite3"] } }, "builtin-baseline": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
修改portfile.cmake,根据vcpkg特性传递CMake选项:
vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS FEATURES ssl BUILD_SSL_COMPONENT sql BUILD_SQL_COMPONENT ) vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS ${FEATURE_OPTIONS} ) vcpkg_cmake_install() vcpkg_cmake_config_fixup(PACKAGE_NAME thelib)
步骤3:消费者(otherlib)的配置修改
让otherlib仅依赖thelib的Core组件,避免加载冗余依赖:
# 仅依赖核心组件 find_package(thelib REQUIRED COMPONENTS Core) target_link_libraries(otherlib PRIVATE thelib::thelib)
如果需要SSL功能,消费者只需修改为:
find_package(thelib REQUIRED COMPONENTS Core SSL) target_link_libraries(consumer PRIVATE thelib::thelib)
原理说明
- CMake组件系统将可选功能与依赖绑定,只有当消费者声明依赖对应组件时,才会触发外部依赖的查找。
- 改造后的安装配置不会硬编码所有依赖,而是通过条件表达式动态暴露依赖,避免强制消费者查找未使用的库。
- vcpkg特性与CMake组件的绑定,让用户可以通过
vcpkg install thelib[ssl]的方式按需安装对应功能与依赖。
内容的提问来源于stack exchange,提问作者dev65

