Search code examples
c++cmakecudathrust

CMAKE_CXX_SOURCE_FILE_EXTENSIONS not working with thrust/cuda


Thrust allows for one to specify different backends at cmake configure time via the THRUST_DEVICE_SYSTEM flag. My problem is that I have a bunch of .cu files that I want to be compiled as regular c++ files when a user runs cmake with -DTHRUST_DEVICE_SYSTEM=OMP (for example). If I change the extension of the .cu files to .cpp they compile fine (indicating that I just need tell cmake to use the c++ compiler on the .cu files). But if I add .cu to CMAKE_CXX_SOURCE_FILE_EXTENSIONS then I get a CMake Error: Cannot determine link language for target "cuda_kernels". Here's a minimal cmake example:

cmake_minimum_required(VERSION 3.19)

project(kernels LANGUAGES C CXX Fortran)

set(KERNELS_USE_OMP OFF)
if ("${THRUST_DEVICE_SYSTEM}" STREQUAL "OMP")
  set(KERNELS_USE_OMP ON)
endif()

# verify CUDA support
include(CheckLanguage)
check_language(CUDA)
if (CMAKE_CUDA_COMPILER AND NOT KERNELS_USE_OMP)
  enable_language(CUDA)
else()
  list(PREPEND CMAKE_CXX_SOURCE_FILE_EXTENSIONS "cu;CU")
endif()

message(STATUS "${CMAKE_CXX_SOURCE_FILE_EXTENSIONS}")

find_package(Thrust REQUIRED CONFIG)
thrust_create_target(Thrust FROM_OPTIONS)


add_library(cuda_kernels my_kernels.cu)
target_link_libraries(cuda_kernels Thrust)

The output of the message command on my system is: -- cu;CU;C;M;c++;cc;cpp;cxx;mm;mpp;CPP;ixx;cppm

Why is cmake not respecting my CMAKE_CXX_SOURCE_FILE_EXTENSIONS changes?


Solution

  • Why is cmake not respecting my CMAKE_CXX_SOURCE_FILE_EXTENSIONS changes?

    The extension-to-language for <LANG> is set as soon as <LANG> is enabled by inspecting the value of the CMAKE_<LANG>_SOURCE_FILE_EXTENSIONS variable when the language detection module exits.

    Unfortunately, there is no blessed way to override this list for CXX as it is hard-coded in Modules/CMakeCXXCompiler.cmake.in.

    Perhaps the best way of working around the actual error would be to use the LANGUAGE source file property to tell CMake how to compile the individual CUDA files, like so:

    cmake_minimum_required(VERSION 3.19)
    project(kernels LANGUAGES CXX)
    
    find_package(Thrust REQUIRED)
    thrust_create_target(Thrust FROM_OPTIONS)
    
    thrust_is_cuda_system_found(USE_CUDA)
    if (USE_CUDA)
      enable_language(CUDA)
    endif()
    
    set(cuda_kernel_sources my_kernels.cu)
    
    add_library(cuda_kernels ${cuda_kernel_sources})
    target_link_libraries(cuda_kernels PRIVATE Thrust)
    
    if (NOT USE_CUDA)
      set_source_files_properties(
        ${cuda_kernel_sources}
        PROPERTIES
        LANGUAGE CXX
      )
    endif ()
    

    This will certainly be friendlier to other projects that might try to add_subdirectory yours.


    However, if we want to be very naughty, we can do this:

    cmake_minimum_required(VERSION 3.19)
    project(kernels LANGUAGES NONE)
    
    ###
    # Hacky language extension override
    
    function(add_cuda_extensions variable access value current_list_file stack)
      if (NOT cu IN_LIST value)
        list(PREPEND "${variable}" "cu" "CU")
        set("${variable}" "${${variable}}" PARENT_SCOPE)
      endif ()
    endfunction()
    
    # verify CUDA support
    include(CheckLanguage)
    check_language(CUDA)
    if (CMAKE_CUDA_COMPILER AND NOT THRUST_DEVICE_SYSTEM STREQUAL "OMP")
      enable_language(CUDA)
      enable_language(CXX)
    else()
      variable_watch(CMAKE_CXX_SOURCE_FILE_EXTENSIONS add_cuda_extensions)
      enable_language(CXX)
    endif()
    
    ###
    # Normal project code starts here
    
    message(STATUS "${CMAKE_CXX_SOURCE_FILE_EXTENSIONS}")
    
    find_package(Thrust REQUIRED)
    thrust_create_target(Thrust FROM_OPTIONS)
    
    add_library(cuda_kernels my_kernels.cu)
    target_link_libraries(cuda_kernels PRIVATE Thrust)
    

    This waits for the platform module to try to write CMAKE_CXX_SOURCE_FILE_EXTENSIONS and then any time it's accessed, quickly inserts the cu and CU extensions to the list.