How to generate portable rules (add_custom_command)?

Hi was using the mailinglist before, but was told that is kinda dead and I should be asking here.

I’m writing a patch for cmake to address https://gitlab.kitware.com/cmake/cmake/issues/19703

For this I need to generate a rule (aka, a Makefile rule) that will create a given file if
it doesn’t exist yet or if the user supplied COMMAND returns some exit code.

I’m not familiar with all the platforms that cmake supports (in fact, I’m only knowledgeable on linux/UNIX,
but assuming that this will include MacOS when it comes to shell scripting?), so I’d like to know the following:

Will an exit code (failure or success being enough, but a specific exit value would be nice) always be available for a given user COMMAND?

Am I correct that the COMMAND of add_custom_command is already executed in some shell? If so, what are the common traits of this shell?

Assuming there is no common trait that will allow me to simply generate general shell code, aka POSIX shell code:

if ${custom_command} || ! test -e ${stamp_file}; then
    ${CMAKE_COMMAND} -E touch ${stamp_file}
fi

then what is the correct way to generate a rule that has this functionality with cmake?

On the mailinglist Hendrik Sattler pointed out that I could use ‘&&’ (and hence I presume ‘||’) at
the very least based on the fact that currently cmake generates rules containing:

cd /path/to/workingdir && ${CMAKE_COMMAND} -E touch ${stamp_file}

However, if I look at cmake’s code then the working directory is passed deep into cmake
functions down to operating system dependent implementations, so I am not convinced
anymore that even ‘&&’ is guaranteed to work on every platform.

How can I do this?

Thanks for any help,
Carlo Wood

I recommend you put the logic you want in a small CMake script and invoke that instead. This will work on all platforms and is fully supported. You can split things across multiple COMMAND lines and they will be executed in sequence, but if one fails, that stops the sequence and the remaining commands won’t be run. For example:

add_custom_command(OUTPUT someFile.txt
    COMMAND ... # Whatever actions you want to take go here
    COMMAND ${CMAKE_COMMAND} -P myScript.cmake   # Only executed if previous command succeeds
    DEPENDS ... # See below
    COMMENT "Checking if blah blah"   # Also see below
)

Then in myScript.cmake you can put the logic to only touch the file if it doesn’t already exist:

if(NOT EXISTS someFile.txt)
    file(TOUCH someFile.txt)
endif()

You can also just put the whole set of commands in the one script, if that meets your requirements (it would probably be simpler and therefore recommended if it does meet them).

My understanding is that this is all stemming from the discussions taking place in issue #19703 and MR !4533 where you want to provide different behavior for the update step of an ExternalProject. Within the internals of that module’s use of add_custom_command(), you probably want to use the DEPENDS line to depend on a file that will never exist, forcing the custom command to always execute. The comment should still be there in my opinion, saying something like “Checking if update is needed” because there is still some action being taken. If that action results in some kind of error, it’s good to have the comment before it to give context, otherwise the user may think the error is coming from whatever build step took place just before it.

That’s all a bit hand-wavy and unchecked, but hopefully it points you in the right direction.

Hi Craig, thanks for your answer :). Unfortunately, after trying to do this for a month or so I am convinced that what I want to do is not possible without changing cmake itself.

Apart from rewriting all of ExternalProject myself. But say I want to keep using that because of the rather complex configure and build rules etc. Then the only way to stop the configure step from running is by NOT touching the update steps stamp file and that is currently impossible.

So your solution might work, but then it will have to become a part of cmake’s ExternalProject itself. I’m not sure if that is what you intended?