Ninja generator, add_custom_command and conditional statements

I am trying to create a command that will copy a data directory from my source directory to my build directory so that it will be grabbed when I package up a build into a zip file. For Windows, my custom command looks like

add_custom_command(TARGET "${target_name}" PRE_BUILD
  COMMAND IF NOT EXIST "$<SHELL_PATH:$<TARGET_FILE_DIR:${target_name}>/${dest_relpath}>" mkdir "$<SHELL_PATH:$<TARGET_FILE_DIR:${target_name}>/${dest_relpath}>"
  COMMAND cp -auv "${source_dir}/*" "$<TARGET_FILE_DIR:${target_name}>/${dest_relpath}")

And this works well in the MSVC generator, but in the Ninja generator, I get the following in my build.ninja file (edited for length):

PRE_LINK = cmd.exe /C “IF NOT EXIST c:\build-dir\Debug\data mkdir c:\build-dir\Debug\data && cp -auv C:/source-dir/data/* C:/build-dir/Debug/data”

But “&&” means “execute the second command if the first command succeeded”, which is not how I thought add_custom_command handled multiple commands. And both IF NOT EXIST and mkdir return errors if the directory does exist, which causes the copy to not happen.

Is this a bug in the Ninja generator for Windows? If not, how should I call add_custom_command in order to get the behavior I want?

Yeah, I would definitely not recommend trying to shoehorn chained commands into an IF statement inside the Ninja file like that. My recommended approach would be to put your logic inside a cmake -P script. For example:

add_custom_command(TARGET "${target_name}" PRE_BUILD
  COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/copy_dir.cmake")

I wrote something that the CMake documentation strongly implies will work one way, and that does in fact work that way in both the “Unix Makefiles” and “Visual Studio 15 2017 Win64” generators.

If the Ninja generator generates something that works another way that you would “definitely not recommend”, isn’t that a problem?

Interesting. Can you please point out where in the documentation this is implied? FWIW, the add_custom_command() documentation says:

If more than one COMMAND is specified they will be executed in order, but not necessarily composed into a stateful shell or batch script.

So, if the documentation implied somewhere else that your case would work, that sounds like a documentation error, and I would like to fix it.

OK, I know what the actual problem is now. On Windows, && binds more tightly than IF does. So the PRE_LINK command in the first post is interpreted as

IF NOT EXIST c:\build-dir\Debug\data
( mkdir c:\build-dir\Debug\data && cp -auv C:/source-dir/data/* C:/build-dir/Debug/data )

which I would have said as one COMMAND if that is what I intended. By using two separate COMMANDs, I was stating my intention to be

( IF NOT EXIST c:\build-dir\Debug\data mkdir c:\build-dir\Debug\data )
&& ( cp -auv C:/source-dir/data/* C:/build-dir/Debug/data )

Would it be possible to get the Ninja generator for Windows to add parentheses to group things so such ambiguity doesn’t happen? Now that I know that the lack of parentheses is the problem, I can work around this myself, but I would like to help other people avoid pitfalls.

I will defer to @brad.king on this one.

I vaguely recall someone trying to modify the generator to add ( and ) but encountering problems. Unfortunately I could not find anything in a quick search just now. If anyone can get that working in a compatible way I’d be open to integrating it into the generator.

add_custom_command's abstraction isn’t really meant to allow arbitrary shell-specific code since it’s supposed to be cross-platform. You could use ${CMAKE_COMMAND} -E make_directory ... to make the directory instead. It doesn’t complain about already-existing directories.