0

Consider the following custom command (latest CMake + ninja):

add_custom_command(
        OUTPUT
            ${OUTPUT}
        COMMAND
            ${Python3_EXECUTABLE} script.py ${INPUT} > ${OUTPUT}
        DEPENDS
            ${INPUT}
        VERBATIM
        COMMAND_EXPAND_LISTS
    )

When script.py runs without errors, it works fine.

However, when script.py fails with an error, ${OUTPUT} is still created.
So current build fails as expected but next build sees ${OUTPUT} newer than ${INPUT} and does not try to run the custom command again as it should.

I would expect the build system to automatically delete ${OUTPUT} when the command fails, to prevent such case, but apparently this does not happen.

  • Is there a way to perform an action "on failure" of a custom command?
    If there was, I could delete ${OUTPUT} there.
  • Alternatively, what is the simplest way to prevent output creation unless command succeeds?

I've tried naively to do something like:

${Python3_EXECUTABLE} script.py ${INPUT} > ${OUTPUT} || rm -f ${OUTPUT}

But that doesn't work because the command result code is in fact the rm result code instead of Python's result code, therefore the custom command doesn't fail as it should on a subsequent build.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
Amir Gonnen
  • 3,525
  • 4
  • 32
  • 61

1 Answers1

2

You almost there, just add after rm a command, which would return an error code. E.g. that way:

${Python3_EXECUTABLE} script.py ${INPUT} > ${OUTPUT} || (rm -f ${OUTPUT} && /bin/false)

With such command VERBATIM option for add_custom_command should NOT be used: With that option CMake quotes the brackets (( and )), which prevents a shell to interpret them for grouping purposes.


I would expect the build system to automatically delete ${OUTPUT} when the command fails

Note, that CMake by itself is NOT a build system, it just generates a code for the build system.

As example, when CMake generates a Makefile, it adds .DELETE_ON_ERROR target, so make utility actually deletes output files on failure.

It seems, that Ninja doesn't have such functionality as Make does. Or CMake doesn't use this functionality when generate a code for Ninja.

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
  • Adding `(rm -f ${OUTPUT} && /bin/false)` doesn't work because cmake doesn't process the `(` correctly: `/bin/sh: 1: (: not found`. Also tried to escape it `\(` or quote it but still not working. – Amir Gonnen Mar 27 '21 at 21:49
  • Yes, CMake is "oversmart" with escaping/quoting things... Probably, removing `VERBATIM` from the `add_custom_command` call should help. – Tsyvarev Mar 27 '21 at 21:54
  • Removing `VERBATIM` helped. The command line becomes very long, is there a way to line break the command before `||`? Tried escaping line ending `\\` and tried putting the whole command in a multi-line string, both didn't work. – Amir Gonnen Mar 27 '21 at 22:39
  • 1
    You could create a helper script (in bash, in python, etc.) which will be called like `redirect_output_smart.sh ${OUTPUT} ${Python3_EXECUTABLE} script.py ${INPUT}`. The script would call the wrapped command with and redirect its output into given file. If the command fails, the script would remove that file. – Tsyvarev Mar 27 '21 at 23:01