CMake自定义命令获取依赖库递归包含目录失败,如何编译CUDA Fatbin?
问题:提取INTERFACE库递归包含目录失败,无法正确编译CUDA Fatbin
尝试编写CMake函数,结合所有INTERFACE类型依赖库的包含路径编译CUDA Fatbin文件,但提取依赖的递归包含目录时出错,输出如下:
-- print_all_include_directories lib DummyCompile_cuda_INPUT_fatbin_offline.cu -- print_all_include_directories_internal Include directories for target DummyCompile_cuda_INPUT_fatbin_offline.cu: INCLUDE_DIRS INCLUDE_DIRS-NOTFOUND -- print_all_include_directories_internal Include directories for target ParamL: INCLUDE_DIRS INCLUDE_DIRS-NOTFOUND
预期应获取依赖库路径(如/.../AnotherFolder),需定位问题原因并找到替代方案,以结合所有包含目录编译Fatbin。
相关代码
主CMake脚本
cmake_minimum_required(VERSION 3.29) project(TestJitLto CUDA) set(CMAKE_CUDA_ARCHITECTURES 86 75 70 ) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CUDA_STANDARD 17) find_package(CUDAToolkit REQUIRED cusparse cublas cudadevrt cudart nvJitLink) add_executable(TestJitLto online.cu) set_target_properties(TestJitLto PROPERTIES CUDA_SEPARABLE_COMPILATION ON) target_link_libraries(TestJitLto PUBLIC CUDA::nvJitLink CUDA::cudart CUDA::nvrtc) ADD_SUBDIRECTORY(AnotherFolder) ADD_SUBDIRECTORY(JitLtoLib)
Fatbin生成CMake脚本
##Function to recursively get and print include directories of all linked libraries function(print_all_include_directories_internal target) message(STATUS "print_all_include_directories_internal Include directories for target ${target}:") # Get the include directories of the target itself get_target_property(INCLUDE_DIRS ${target} INTERFACE_INCLUDE_DIRECTORIES) get_target_property(INCLUDE_DIRS ${target} INCLUDE_DIRECTORIES) message("INCLUDE_DIRS ${INCLUDE_DIRS}") if (INCLUDE_DIRS) foreach (dir ${INCLUDE_DIRS}) list(APPEND INCLUDE_LIST_TMP ${dir}) endforeach () list(APPEND INCLUDE_LIST_TMP ${INCLUDE_LIST}) list(REMOVE_DUPLICATES INCLUDE_LIST_TMP) message("INCLUDE_LIST_TMP ${INCLUDE_LIST_TMP}") set(INCLUDE_LIST2 ${INCLUDE_LIST_TMP}) else () set(INCLUDE_LIST2 "") endif () # Now get the libraries linked to this target get_target_property(LINK_LIBS ${target} LINK_LIBRARIES) #get_target_property(LINK_LIBS2 ${target} INTERFACE_LINK_LIBRARIES) set(LINK_LIBS ${LINK_LIBS} ${LINK_LIBS2}) list(REMOVE_DUPLICATES LINK_LIBS) #message("LINK_LIBS ${LINK_LIBS}") if (LINK_LIBS) foreach (linked_lib ${LINK_LIBS}) # Check if the linked library is a target, skip if it's a system library or non-target if (TARGET ${linked_lib}) #message(STATUS "Checking include directories for linked library: ${linked_lib} ${INCLUDE_LIST}") print_all_include_directories_internal(${linked_lib}) # Recursive call list(APPEND INCLUDE_LIST2 ${INCLUDE_LIST}) list(REMOVE_DUPLICATES INCLUDE_LIST2) else () list(APPEND INCLUDE_LIST2 ${INCLUDE_LIST}) list(REMOVE_DUPLICATES INCLUDE_LIST2) # message(STATUS "INCLUDE_LIST1:" ${INCLUDE_LIST2}) endif () endforeach () else () list(APPEND INCLUDE_LIST2 ${INCLUDE_LIST}) list(REMOVE_DUPLICATES INCLUDE_LIST2) #message(STATUS "INCLUDE_LIST2:" ${INCLUDE_LIST2}) endif () set(INCLUDE_LIST ${INCLUDE_LIST2} PARENT_SCOPE) endfunction() function(print_all_include_directories lib) message(STATUS " print_all_include_directories lib ${lib}") print_all_include_directories_internal(${lib}) set(INCLUDE_LIST ${INCLUDE_LIST} PARENT_SCOPE) message(STATUS " print_all_include_directories1 ${INCLUDE_LIST}") endfunction() # Function to compile a single CUDA file into a fatbin function(compile_cuda_fatbin_function INPUT_FILE OUTPUT_DIR DEPENDS_LIBS) ADD_LIBRARY(DummyCompile_cuda_INPUT_fatbin_${INPUT_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_FILE}) target_link_libraries(DummyCompile_cuda_INPUT_fatbin_${INPUT_FILE} PUBLIC ${DEPENDS_LIBS}) print_all_include_directories(DummyCompile_cuda_INPUT_fatbin_${INPUT_FILE}) get_filename_component(OUT_FILE ${INPUT_FILE} NAME_WLE) message("CMAKE_CUDA_ARCHITECTURES ${CMAKE_CUDA_ARCHITECTURES}") # Initialize an empty list for the flags set(GENCODE_FLAGS "") # Split the architecture string by semicolons and iterate over each foreach (ARCH IN LISTS CMAKE_CUDA_ARCHITECTURES) # Add `-gencode` flags to the list list(APPEND GENCODE_FLAGS "-gencode arch=compute_${ARCH},code=lto_${ARCH}") endforeach () string(REPLACE ";" " " GENCODE_FLAGS "${GENCODE_FLAGS}") # Print the generated flags for verification message(STATUS "Generated -gencode flags1: ${GENCODE_FLAGS}") message("ARCH_VECTOR is ${GENCODE_FLAGS}") separate_arguments(GENCODE_FLAGSA UNIX_COMMAND "${GENCODE_FLAGS}") # Custom command to compile the CUDA file into a fatbin add_custom_command( OUTPUT "${OUTPUT_DIR}/${INPUT_FILE}.fatbin" VERBATIM COMMAND /usr/local/cuda/bin/nvcc ARGS ${GENCODE_FLAGSA} -rdc=true -fatbin ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_FILE} -o ${CMAKE_CURRENT_BINARY_DIR}/${OUT_FILE}.fatbin MAIN_DEPENDENCY "${INPUT_FILE}" COMMENT "Compiling CUDA binary for ${INPUT_FILE} to fatbin with LTO archive" ) # Custom target that depends on the fatbin output add_custom_target(compile_cuda_fatbin_${INPUT_FILE} DEPENDS "${OUTPUT_DIR}/${INPUT_FILE}.fatbin" ${DEPENDS_LIBS} ) ADD_LIBRARY(Dummycompile_cuda_fatbin_${INPUT_FILE} INTERFACE) target_link_libraries(Dummycompile_cuda_fatbin_${INPUT_FILE} INTERFACE ${DEPENDS_LIBS}) target_include_directories(Dummycompile_cuda_fatbin_${INPUT_FILE} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) add_dependencies(Dummycompile_cuda_fatbin_${INPUT_FILE} compile_cuda_fatbin_${INPUT_FILE}) endfunction() # Function to compile a list of CUDA files into fatbins function(compile_cuda_fatbin_files_function FILES) foreach (FILE ${FILES}) compile_cuda_fatbin_function("${FILE}" "${OUTPUT_DIR}" "${LTO_ARC}") endforeach () endfunction() compile_cuda_fatbin_function(offline.cu ${CMAKE_CURRENT_SOURCE_DIR} ParamL)
依赖库CMake脚本(AnotherFolder)
add_library(ParamL INTERFACE) target_include_directories(ParamL INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
问题原因分析
- 变量覆盖错误:在
print_all_include_directories_internal函数中,先获取INTERFACE_INCLUDE_DIRECTORIES,随后立即获取INCLUDE_DIRECTORIES并覆盖INCLUDE_DIRS变量。INTERFACE类型目标仅INTERFACE_INCLUDE_DIRECTORIES有有效路径,INCLUDE_DIRECTORIES会返回NOTFOUND,导致正确路径被覆盖丢失。 - 目标属性匹配错误:创建的
DummyCompile_cuda_INPUT_fatbin_${INPUT_FILE}是普通库目标,无法自动继承依赖INTERFACE库的包含路径,直接获取INCLUDE_DIRECTORIES无法得到INTERFACE库的路径。 - 递归变量传递逻辑混乱:递归调用时,
INCLUDE_LIST的父作用域传递逻辑存在问题,子函数的结果无法正确合并到父函数中,导致最终无法收集到完整路径。
解决方案
方法1:修复递归提取函数
修改print_all_include_directories_internal函数,区分目标类型并修正变量传递逻辑:
function(print_all_include_directories_internal target) message(STATUS "print_all_include_directories_internal Include directories for target ${target}:") set(INCLUDE_LIST_TMP "") # 根据目标类型选择对应属性 get_target_property(TARGET_TYPE ${target} TYPE) if(TARGET_TYPE STREQUAL "INTERFACE_LIBRARY") get_target_property(INCLUDE_DIRS ${target} INTERFACE_INCLUDE_DIRECTORIES) else() get_target_property(INCLUDE_DIRS ${target} INCLUDE_DIRECTORIES) # 同时获取目标自身的INTERFACE包含路径 get_target_property(INTERFACE_INCLUDES ${target} INTERFACE_INCLUDE_DIRECTORIES) if(INTERFACE_INCLUDES) list(APPEND INCLUDE_DIRS ${INTERFACE_INCLUDES}) endif() endif() if(INCLUDE_DIRS) list(APPEND INCLUDE_LIST_TMP ${INCLUDE_DIRS}) list(REMOVE_DUPLICATES INCLUDE_LIST_TMP) message("INCLUDE_DIRS ${INCLUDE_DIRS}") endif() # 获取所有链接库,包括INTERFACE_LINK_LIBRARIES get_target_property(LINK_LIBS ${target} LINK_LIBRARIES) get_target_property(INTERFACE_LINK_LIBS ${target} INTERFACE_LINK_LIBRARIES) set(ALL_LINK_LIBS ${LINK_LIBS} ${INTERFACE_LINK_LIBS}) list(REMOVE_DUPLICATES ALL_LINK_LIBS) if(ALL_LINK_LIBS) foreach(linked_lib ${ALL_LINK_LIBS}) if(TARGET ${linked_lib}) print_all_include_directories_internal(${linked_lib}) # 合并子函数返回的包含目录 list(APPEND INCLUDE_LIST_TMP ${INCLUDE_LIST}) list(REMOVE_DUPLICATES INCLUDE_LIST_TMP) endif() endforeach() endif() set(INCLUDE_LIST ${INCLUDE_LIST_TMP} PARENT_SCOPE) endfunction()
方法2:利用CMake生成器表达式(更推荐)
无需手动递归提取,通过CMake原生生成器表达式自动获取所有依赖的包含目录,简化实现:
function(compile_cuda_fatbin_function INPUT_FILE OUTPUT_DIR DEPENDS_LIBS) # 创建INTERFACE目标聚合所有依赖 add_library(FatbinDeps_${INPUT_FILE} INTERFACE) target_link_libraries(FatbinDeps_${INPUT_FILE} INTERFACE ${DEPENDS_LIBS}) # 生成包含目录的-I参数 set(INCLUDE_FLAGS "") # 使用生成器表达式递归获取所有INTERFACE包含目录 string(APPEND INCLUDE_FLAGS "$<JOIN:$<TARGET_PROPERTY:FatbinDeps_${INPUT_FILE},INTERFACE_INCLUDE_DIRECTORIES>, -I>") if(INCLUDE_FLAGS) set(INCLUDE_FLAGS "-I${INCLUDE_FLAGS}") endif() get_filename_component(OUT_FILE ${INPUT_FILE} NAME_WLE) set(GENCODE_FLAGS "") foreach(ARCH IN LISTS CMAKE_CUDA_ARCHITECTURES) list(APPEND GENCODE_FLAGS "-gencode arch=compute_${ARCH},code=lto_${ARCH}") endforeach() add_custom_command( OUTPUT "${OUTPUT_DIR}/${INPUT_FILE}.fatbin" VERBATIM COMMAND $<TARGET_FILE:CUDA::nvcc> ARGS ${GENCODE_FLAGS} -rdc=true -fatbin ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_FILE} ${INCLUDE_FLAGS} -o ${CMAKE_CURRENT_BINARY_DIR}/${OUT_FILE}.fatbin MAIN_DEPENDENCY "${INPUT_FILE}" COMMENT "Compiling CUDA binary for ${INPUT_FILE} to fatbin with LTO archive" ) add_custom_target(compile_cuda_fatbin_${INPUT_FILE} DEPENDS "${OUTPUT_DIR}/${INPUT_FILE}.fatbin" ) add_library(Dummycompile_cuda_fatbin_${INPUT_FILE} INTERFACE) target_link_libraries(Dummycompile_cuda_fatbin_${INPUT_FILE} INTERFACE ${DEPENDS_LIBS}) target_include_directories(Dummycompile_cuda_fatbin_${INPUT_FILE} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) add_dependencies(Dummycompile_cuda_fatbin_${INPUT_FILE} compile_cuda_fatbin_${INPUT_FILE}) endfunction()
关键改进说明
- 方法2通过
$<TARGET_PROPERTY:...,INTERFACE_INCLUDE_DIRECTORIES>生成器表达式,自动递归获取所有依赖INTERFACE库的包含路径,避免手动遍历的错误。 - 使用
$<TARGET_FILE:CUDA::nvcc>替代硬编码路径,提升跨平台兼容性。 - 聚合INTERFACE目标统一管理依赖,确保包含路径正确传递。
内容的提问来源于stack exchange,提问作者Yehonatan
相关产品推荐
相关产品推荐

