Passing flags to custom commands with generator expressions

Given a command our_tool that we want to run as a custom target, and we want a flag --debug in debug builds only:

Some sources say you add a generator expression with quotes like this:

    ADD_CUSTOM_TARGET(our_tool_command 
                    COMMAND our_tool "$<$<config:Debug>:--debug>" 
                    VERBATIM)

This is wrong and results in an empty string "" argument to our command.

Some sources say you add a generator expression without quotes like this:

    ADD_CUSTOM_TARGET(our_tool_command 
                    COMMAND our_tool $<$<config:Debug>:--debug>
                    VERBATIM)

This is also wrong and still adds an empty string "" argument to our command.

The documentation suggests COMMAND_EXPAND_LISTS like so

    ADD_CUSTOM_TARGET(our_tool_command 
                    COMMAND our_tool $<$<config:Debug>:--debug>
                    VERBATIM COMMAND_EXPAND_LISTS)

This works, but it raises another question, what are you supposed to do if later an argument must contain a semicolon? “Passing lists or strings with semi-colon to add_custom_command” exists but that looks a lot like a hack that works in one specific generator only. Is there a proper supported way of doing this?

Some remarks:

  • The generator expression $<config:...> does not exist. The correct one is $<CONFIG:...>.
  • It is preferable to always specify the generator expressions inside quotes.

Now, your behavior is unexpected. The first example should work as expected (by specifying $<CONFIG:...>, of course).

What are your cmake version and generator used?

"$<$<CONFIG:Debug>:--debug>" will definitely always generates an argument. That seems consistent across versions and generators. (observed in at least 3.26.5 with Makefiles and in 4.1.2 with Ninja). The documentation mentions this under generator expressions. I don’t see a mention of what happens without the quotes but it seems to always generate an argument as well.

Just testing with a toy example:
args.sh:

for arg in "$@"; do
  printf '"%s"\n' "$arg"
done

CMakeLists.txt:

cmake_minimum_required(VERSION 3.24)
project(test)
add_custom_target(our_tool_command 
                COMMAND sh "${CMAKE_CURRENT_LIST_DIR}/args.sh" foo "$<$<CONFIG:Debug>:--debug>" bar $<$<CONFIG:Debug>:--debug>
                VERBATIM)

Building the our_tool_command target in Release config results in

"foo"
""
"bar"
""

for cmake 4.1.2 using either Ninja, Unix Makefiles or Xcode.

And when adding COMMAND_EXPAND_LISTS:

  • "foo\\;bar" becomes “foo;bar”
  • "foo$<SEMICOLON>bar" will be split in two arguments “foo” “bar”
  • "foo\\$<SEMICOLON>bar" becomes “foo;bar”

In fact, it seems I didn’t understand your problem: Are you complaining that an empty string is produced when the build is not a debug one?

If yes, the empty string is produced because you have specified the VERBATIM option.

Yes I thought passing VERBATIM is mandatory if you don’t want to be tied to one platform and generator.

So to summarize

  • Pass --debug for debug builds, and no argument for release build. This is solved by adding COMMAND_EXPAND_LISTS and "$<$<config:Debug>:--debug>"
  • But then we have no documented way of passing arguments containing semicolons.

So the solution to producing a literal ;:

  • \\; works and is consistent with cmake-language § Lists but that page cautions against relying on this when passing lists to other commands, so is this reliable behaviour across generators?