How to idiomatically add include path flags inside add_custom_command?

This is pattern I’ve had to use multiple times and I keep thinking that the “correct” way to do it should be something like:

add_custom_command(
  # ...
  COMMAND ${CMAKE_COMMAND} -E env my_compiler some_file "-I$<JOIN:${MY_INCLUDE_DIRS}, -I>"
  # ...
)

But this does not work because CMake escapes whitespace between flags. The obvious alternative would be getting rid of the quotes around the generator expression but that causes it to not be expanded at all in build.ninja (or similar)!

Is there really no way to achieve this without “manual” string operations?

There is an example showing precisely this in the genex documentation. The example builds it up in several steps, but here are the two final forms it offers:

Using $<JOIN>:

# The $<BOOL:...> check prevents adding anything if the property is empty,
# assuming the property value cannot be one of CMake's false constants.
set(prop "$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>")
add_custom_target(run_some_tool
  COMMAND some_tool "$<$<BOOL:${prop}>:-I$<JOIN:${prop},;-I>>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)

Using $<LIST>:

add_custom_target(run_some_tool
  COMMAND some_tool "$<LIST:TRANSFORM,$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,PREPEND,-I>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)

Thanks @Petr! With the linked section I think I’ve now finally understood this, I should probably read the whole page…

I have a question about this. If tgt’s INCLUDE_DIRECTORIES has $<BUILD_INTERFACE:...> and $<INSTALL_INTERFACE:...> generator expressions as suggested here, then

"$<LIST:TRANSFORM,$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>,PREPEND,-I>"

expands to something like

-Idir1 -Idir2 -I -Idir3

with one empty -I flag for each $<INSTALL_INTERFACE:...>. The empty -I flags cause gcc to complain it can’t find headers! Is there a way to filter these out?

Edit: the form with $<JOIN>: doesn’t produce the empty -I flags and works as expected! It seems these two solutions are not equivalent. It would be a good idea to update the documentation accordingly (or fix the bug, if it’s a bug).

set(prop "$<TARGET_PROPERTY:tgt,INCLUDE_DIRECTORIES>")
add_custom_target(run_some_tool
  COMMAND some_tool "$<$<BOOL:${prop}>:-I$<JOIN:${prop},;-I>>"
  COMMAND_EXPAND_LISTS
  VERBATIM
)