23

I need to execute a command with add_custom_command a capture its stdout. The shell redirection to file > does not work and add_custom_command does not have any related options. How can I do it?

tamas.kenez
  • 7,301
  • 4
  • 24
  • 34

3 Answers3

10

(Please note that I changed my answer after investigating the issue because of Florian's comment. More about the edit below)

The documentation of the execute_process clearly states that it does not use intermediate shell, so redirection operators do not work there.

But it's not true for the add_custom_command. Redirection should work there as expected. [EDIT] The reason sometimes it does not seem to work is some unfortunate combination of the CMake generator, the VERBATIM specifier and the (missing) space between > and the file name.

It turned out if you make sure there's a space between > and the file name it works with in most cases, even with VERBATIM:

add_custom_command(OUTPUT ${some-file}
    COMMAND cmake --version > ${some-file}
    VERBATIM # optional
)

A note on an alternative solution: previously I thought add_custom_command, just like execute_process doesn't use an intermediate shell so I suggested calling a CMake script which contains an execute_process command which runs the actual command, redirecting its output with the OUTPUT_FILE option.

If for some reason the solution above still fails for you, try the alternative solution using an ExecuteProcessWrapper.cmake

tamas.kenez
  • 7,301
  • 4
  • 24
  • 34
  • 4
    I think CMake's implementation of custom command may be platform dependent. Can you add an example where custom command is not using an intermediate shell? I've successfully used stdout/stderr redirection with Ninja and Visual Studio generators. Those generators do prefix `cmd.exe /c` for custom commands on Windows. And the pipe commands are tested in CMake's [custom command unit test](https://github.com/Kitware/CMake/blob/master/Tests/CustomCommand/CMakeLists.txt#L231). – Florian Jul 23 '15 at 11:36
  • @Florian, you're right, I was wrong. I'm fixing my answer. – tamas.kenez Jul 23 '15 at 12:49
  • 3
    I found that thread on CMake developers' forum: http://cmake.3232098.n2.nabble.com/Redirect-output-from-add-custom-target-td7583901.html. They write, that redirection in `add_custom_target()` may not work in some cases. From the other side, both `add_custom_command()` and `add_custom_target()` accept `VERBATIM` option, which has no sence until shell is involved. Contrary, `execute_process()` doesn't accept that option, and has explicit remark about redirection. So, where it the truth? – Tsyvarev Jul 23 '15 at 13:04
  • @Tsyvarev, thanks, I restored the link to my previous solution if for some reason it still doesn't work for somebody – tamas.kenez Jul 23 '15 at 13:11
  • 1
    Hmm, For me `VERBATIM` doesn't prevent redirection. See, e.g., that answer: http://stackoverflow.com/a/31558761/3440745. – Tsyvarev Jul 23 '15 at 13:13
  • 1
    @Tsyvarev it seems that this redirection issue is very variable across platforms and generators. For me: mingw or window-cmd prompt + msvc generator the VERBATIM does break redirection. – tamas.kenez Jul 23 '15 at 13:21
  • CMake's unit test I linked in my first comment does use `VERBATIM` and for me it worked with `VERBATIM`. I think the problem with pipes actually is that everything of a custom command is put into one single shell line call (without any bracketing). It goes into the binary directory with `cd`, adds all `COMMAND`s you have listed, etc. So if there is one pipe command in the middle everything depends on how the shell prioritize all concatenated commands given to it. If you copy the not working command line from your make/msvc environment and execute it directly on the prompt/shell what happens? – Florian Jul 23 '15 at 14:16
  • Would it help if you use `VERBATIM` and place the `(` `)` brackets directly in `add_custom_command()` around your command incl. the pipe to file? I think those brackets are universal for the major shells. References [Pipe | Redirection < > Precedence](http://stackoverflow.com/questions/12942042/pipe-redirection-precedence) and [Shell - Multiple commands in one line](http://stackoverflow.com/questions/5130847/shell-multiple-commands-in-one-line). – Florian Jul 23 '15 at 14:39
  • @Florian, brackets didn't help. I checked the command lines generated into the `vcxproj`. The end of the command line without `VERBATIM` is `>C:/ki/git/cmaketry/foo.cpp` (this one works). With `VERBATIM`: `">C:/ki/git/cmaketry/foo.cpp"` – tamas.kenez Jul 23 '15 at 16:10
  • 1
    The question is really where those quotes come from. Please try to add a space after `>` because I think the pipe command is handled in a special way. See [BUG: Do not escape shell operators when generating command lines](https://github.com/Kitware/CMake/commit/c7d84b21c636a559b1f1a87735ce12d21f4a9dcd) and [CMAKE add_custom_command: How to use my raw commands](http://stackoverflow.com/questions/16052444/cmake-add-custom-command-how-to-use-my-raw-commands) – Florian Jul 23 '15 at 19:07
  • 1
    @Florian, thanks, with space after `>` it does work with `VERBATIM`! – tamas.kenez Jul 23 '15 at 19:17
3
find_program(BASH bash HINTS /bin)
...
add_custom_command(
  OUTPUT
    ${some-file}
  COMMAND
    ${CMAKE_COMMAND} -E env ${BASH} -c "cmake --version > ${some-file} 2>&1"
  VERBATIM
)
Lance E.T. Compte
  • 932
  • 1
  • 11
  • 33
0

As a fallback, one solution is to place the script you want to run in a separate .sh file and run that file. That way you do not have to think too hard about how to write such a script within cmake. It also makes it a lot easier to maintain and for other programmers to help with the script. This may be less important for a one liner, but it often starts with a single line and ends up being a 2,000 line script one day...

add_custom_command(
  OUTPUT
    ${some-file}
  COMMAND
    ${CMAKE_SOURCE_DIR}/my-script.sh ${some-file}
  WORKING_DIRECTORY
    ${CMAKE_BINARY_DIR}
)

I also like to use the WORKING_DIRECTORY to be clear on where we start when executing the script.

P.S. the script needs to be executable:

chmod 755 my-script.sh

However, in your specific situation, you could also have considered executing the command and saving the results in a file like so:

execute_process(
  COMMAND
    cmake --version
  OUTPUT_FILE
    ${some-file}
)

which is probably much cleaner.

The one thing to keep in mind in this case: the order of execution will be different. The execute_process() happens as the statement is parsed. The add_custom_command() is executed when its output is required, usually through an add_custom_target().

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156