如何用CMake基于生成的配置文件构建两个差异化共享库?
我想要构建两个仅代码存在细微差异的共享库,计划通过CMake生成名为ENGINE_TYPE的头文件变量,分别设为0和1来构建foo和foo1库。但发现两个库最终都使用了foo1对应的代码逻辑,怀疑是CMake先生成ENGINE_TYPE为0的config.h后,又被ENGINE_TYPE为1的版本覆盖,导致编译时两个库都使用了后者的配置。请问该方案是否可行?若可行,该如何解决?
给出的CMake代码如下:
# Global Definitions set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) set(${PROJECT_NAME}_VERSION_PATCH 0) # Function to configure and build a library function(configure_and_build_library TARGET_NAME ENGINE_TYPE) set(${PROJECT_NAME}_SOURCES src/lib/src/my.cpp) set(engineType ${ENGINE_TYPE}) configure_file(include/config.h.in ${CMAKE_BINARY_DIR}/config.h @ONLY) add_library(${TARGET_NAME} SHARED ${${PROJECT_NAME}_SOURCES}) target_compile_definitions(${TARGET_NAME} PRIVATE engineType=${engineType}) set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH} SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}) target_link_libraries(${TARGET_NAME} ${OPENSSL_CRYPTO_LIBRARY}) endfunction() # Build valeoengine with engineType 0 set(engineType 0) configure_and_build_library(foo ${engineType}) # Build valeodecryptengine with engineType 1 set(engineType 1) configure_and_build_library(foo1 ${engineType}) # Install project if(NOT SKIP_INSTALL_ALL) install(TARGETS foo foo1 LIBRARY DESTINATION lib) endif()
这个方案完全可行,核心问题出在configure_file的输出路径:两次调用函数时,都将生成的config.h输出到同一个全局路径${CMAKE_BINARY_DIR}/config.h,第二次生成的文件会直接覆盖第一次的。最终两个库编译时都会读取最后生成的、对应ENGINE_TYPE=1的配置文件,导致逻辑一致。
另外你在函数里同时用了configure_file生成头文件,又通过target_compile_definitions定义了engineType变量——如果代码里同时引用这两个地方的变量,也可能导致逻辑混乱,需要统一配置方式。
有两种可靠的解决方式,可根据需求选择:
方式一:为每个库生成独立的config.h(保留头文件生成方式)
修改configure_file的输出路径,为每个目标库创建单独的子目录存放对应的config.h,避免文件覆盖。同时让每个库只包含自己对应的头文件目录:
# Global Definitions set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) set(${PROJECT_NAME}_VERSION_PATCH 0) # Function to configure and build a library function(configure_and_build_library TARGET_NAME ENGINE_TYPE) set(${PROJECT_NAME}_SOURCES src/lib/src/my.cpp) # 为每个目标创建单独的配置目录 set(CONFIG_DIR ${CMAKE_BINARY_DIR}/config/${TARGET_NAME}) file(MAKE_DIRECTORY ${CONFIG_DIR}) # 生成对应目标的config.h到专属目录 set(ENGINE_TYPE ${ENGINE_TYPE}) configure_file(include/config.h.in ${CONFIG_DIR}/config.h @ONLY) add_library(${TARGET_NAME} SHARED ${${PROJECT_NAME}_SOURCES}) # 让目标库只包含自己的配置目录 target_include_directories(${TARGET_NAME} PRIVATE ${CONFIG_DIR}) set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH} SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}) target_link_libraries(${TARGET_NAME} ${OPENSSL_CRYPTO_LIBRARY}) endfunction() # Build foo with ENGINE_TYPE 0 configure_and_build_library(foo 0) # Build foo1 with ENGINE_TYPE 1 configure_and_build_library(foo1 1) # Install project if(NOT SKIP_INSTALL_ALL) install(TARGETS foo foo1 LIBRARY DESTINATION lib) endif()
注:这里将函数内的变量名统一为ENGINE_TYPE,和头文件中的变量名对应,避免混淆。
方式二:直接用编译定义替代头文件生成(更简洁)
既然已经尝试用target_compile_definitions,可以直接去掉configure_file步骤,在代码中通过预编译宏区分逻辑,完全避免文件覆盖问题:
第一步:修改代码逻辑
在my.cpp中直接使用预编译宏ENGINE_TYPE,示例:
#ifdef ENGINE_TYPE if (ENGINE_TYPE == 0) { // foo库的逻辑 } else if (ENGINE_TYPE == 1) { // foo1库的逻辑 } #endif
第二步:修改CMake代码
# Global Definitions set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) set(${PROJECT_NAME}_VERSION_PATCH 0) # Function to configure and build a library function(configure_and_build_library TARGET_NAME ENGINE_TYPE) set(${PROJECT_NAME}_SOURCES src/lib/src/my.cpp) add_library(${TARGET_NAME} SHARED ${${PROJECT_NAME}_SOURCES}) # 直接为目标定义专属的预编译宏 target_compile_definitions(${TARGET_NAME} PRIVATE ENGINE_TYPE=${ENGINE_TYPE}) set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH} SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}) target_link_libraries(${TARGET_NAME} ${OPENSSL_CRYPTO_LIBRARY}) endfunction() # Build foo with ENGINE_TYPE 0 configure_and_build_library(foo 0) # Build foo1 with ENGINE_TYPE 1 configure_and_build_library(foo1 1) # Install project if(NOT SKIP_INSTALL_ALL) install(TARGETS foo foo1 LIBRARY DESTINATION lib) endif()
这种方式不需要生成额外头文件,更简洁高效,也不会有文件覆盖风险。
内容的提问来源于stack exchange,提问作者mohamed Elmodather

