1

I want to automatically build GLSL shaders to SPIR-V and then copy them to the location of an executable target using CMake. I created the following function for this purpose:

function(target_shaders target_name paths_in)
    set(paths_out "")
    foreach(path_in_raw ${paths_in})
        set(path_in "${CMAKE_CURRENT_SOURCE_DIR}/${path_in_raw}")
        set(path_out "$<TARGET_FILE:${target_name}>/../${path_in_raw}.spv")
        add_custom_command(OUTPUT ${path_out}
            COMMAND glslc ${path_in} -o ${path_out}
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
            DEPENDS ${path_in}
            COMMENT "Compiling GLSL shader" ${path_in})
        list(APPEND paths_out ${path_out})
    endforeach()

    add_custom_target("${target_name}_shaders" DEPENDS ${paths_out})
    add_dependencies(${target_name} "${target_name}_shaders")
endfunction()

It takes a target name and a list of GLSL files as arguments. I invoke it like this:

add_executable(mini-vk main.cpp)
target_shaders(mini-vk "basic.vert;basic.frag")

I expected this to compile basic.vert and basic.frag to respective .spv files in the same directory that mini-vk.exe is located. Unfortunately, I get the following error instead:

[cmake] CMake Error at CMakeLists.txt:9 (add_custom_command):
[cmake]   Error evaluating generator expression:
[cmake] 
[cmake]     $<TARGET_FILE:mini-vk>
[cmake] 
[cmake]   No target "mini-vk"
[cmake] Call Stack (most recent call first):
[cmake]   CMakeLists.txt:26 (target_shaders)

I am not sure why there is "no target mini-vk" as it is created on the line just above target_shaders.

For clarity, the code does work if I type the output path manually by changing the definition of path_out:

set(path_out "${CMAKE_CURRENT_SOURCE_DIR}/build/Debug/${path_in_raw}.spv")

I don't want to have to use this hack though.

janekb04
  • 4,304
  • 2
  • 20
  • 51
  • The description for OUTPUT parameter of add_custom_command has following note: "New in version 3.20: Arguments to OUTPUT may use a restricted set of generator expressions. Target-dependent expressions are not permitted.". This is probably the reason why `$` doesn't work for you: it IS [target-dependent expression](https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#target-dependent-queries). – Tsyvarev Aug 28 '21 at 13:57
  • @Tsyvarev I missed that. This is probably the reason why it doesn't work. I will have to look for a different solution, then. – janekb04 Aug 28 '21 at 14:19
  • Could you post a simple `main.cpp`, `basic.vert` and `basic.frag` so I can test a solution? – Alex Reinking Aug 28 '21 at 22:05
  • related: [Copy File to Executable Directory Recopy on Modified](https://stackoverflow.com/q/53548358/11107541) – starball Aug 30 '23 at 21:31

1 Answers1

1

Here's a solution involving stamp files. Rather than using the "true" path as the OUTPUT, you use an empty file just for its timestamp and then go ahead and run the command you actually want with the TARGET_FILE_DIR generator expression.

cmake_minimum_required(VERSION 3.21)
project(test)

find_package(Vulkan REQUIRED)

function(target_shaders name)
  set(shaders "")

  foreach (input IN LISTS ARGN)
    # Might want to add code to handle clashing names 
    # in different directories (e.g. ./basic.vert and
    # ./subdir/basic.vert)
    cmake_path(GET input FILENAME stem)
    cmake_path(ABSOLUTE_PATH input
               BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
               NORMALIZE)
    add_custom_command(
      OUTPUT "${stem}.stamp"
      COMMAND Vulkan::glslc "${input}" -o "$<TARGET_FILE_DIR:${name}>/${stem}.spv"
      COMMAND "${CMAKE_COMMAND}" -E touch "${stem}.stamp"
      DEPENDS "${input}"
    )
    list(APPEND shaders "${stem}.stamp")
  endforeach ()

  add_custom_target("${name}.shaders" DEPENDS ${shaders})
  add_dependencies("${name}" "${name}.shaders")
endfunction()

# Just to test changing the output directory
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin)

add_executable(mini-vk main.cpp)
target_shaders(mini-vk basic.vert "${CMAKE_CURRENT_SOURCE_DIR}/basic.frag")
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • Unfortunately, your solution didn't work for me. This could be since I am using MSVC with Visual Studio generators, but I'm not sure. I was able to use `CMAKE_RUNTIME_OUTPUT_DIRECTORY` instead, though. – janekb04 Aug 29 '21 at 19:31
  • @janekb04 - _In what way_ did it not work? I'm happy to help fix it if you can provide repro steps. – Alex Reinking Aug 29 '21 at 19:38