cmake parse arguments incorrectly in add_custom_target command - cmake

I wrote a cmake command like this:
add_custom_target(testar
COMMAND clearmake -C gnu ${CMD_ARGS})
the CMD_ARGS is defined on the command line like:
cmake -DCMD_ARGS="-d -w"
But in the generated makefile, the -d -w is changed into -d\ -w; it added a slash before all spaces, resulting in:
clearmake -C gnu -d\ -w
If I use VERBATIM option in add_custom_target, cmake doesn't add a slash, but it quotes the argument like
clearmake -C gnu "-d -w"
which is incorrect, I would like:
clearmake -C gnu -d -w
What is the syntax needed to generate the above target?

The arguments are expected to be a list, which "-d -w" is not (it's just a string). You can do two things:
Pass in the arguments as -DCMD_ARGS="-d;-w" (the space is a semicolon)
Use the separate_arguments command on CMD_ARGS before you pass it into add_custom_target (which makes spaces semi-colons to generate a proper list).
Nothing in the add_custom_target command needs to change, the input to CMake is incorrect which can be fixed with 1 or handled by 2.

Related

CMake add_custom_command: How to write a "quoted string" to a file?

I have spent way too much time trying to add a custom command that writes a "quoted string" to a file:
add_custom_command(
OUTPUT file
COMMAND ${CMAKE_COMMAND} -E echo "\"quoted string\"" > file
...
DEPENDS something
VERBATIM (?)
)
I have tried various ways to escape the quotes \", \\", \\\", quoting the whole command, putting the command in variable, but none of them worked. How can this be achieved?
The following works on both Windows(Ninja) and WSL(unix makefiles):
set(my_output what_it_is.txt)
add_custom_command(
OUTPUT ${my_output}
COMMAND ${CMAKE_COMMAND} -E echo \"quoted string\" > ${my_output}
VERBATIM
)
And the output:
$ cat some_bin/what_it_is.txt
"quoted string"
In my limited experience, VERBATIM is usually the key if you're fighting escaping things in custom commands.
Note: I believe that the redirect is platform specific, so you might want to consider doing something like the file command in a CMake script and invoking that script in the custom command, COMMAND ${CMAKE_COMMAND} -P some_script.cmake.

Create multi-COMMAND execute_process

According to documentation execute_process can accept more than one command and AFAIR they gonna be executed in parallel, so running multiple execute_process is less than desirable. Each command should be created in runtime by iterating over list. The problem is execute_process could not accept string which contains the COMMAND token and the command with arguments like below and execute_process does not support chaining command by using &&:
set(commands)
foreach (Node ${NodeList})
string(REGEX MATCHALL "[A-Za-z0-9_\\.]+" NodeDefinition "${Node}")
list(GET NodeDefinition 1 IP)
list(APPEND commands "COMMAND ssh -tt user#${IP} \"${command}\"")
endforeach ()
list(JOIN commands "\n" exec_commands)
message(STATUS "Commands: ${exec_commands}")
execute_process(${commands})
It gives
execute_process given unknown argument "COMMAND ssh -tt user#127.0.0.1 "ls /bin""
Is there a way to workaround the problem?
EDIT001: Looks like the execute_process would work if I do it this way:
execute_process(COMMAND ${command1}
COMMAND ${command2}
...
COMMAND ${commandn}
)
Looks like macro will solve this problem
EDIT002: Well, it didnt
EDIT003: According to #Tsyvarev comment, I dont have to create stringy command, just append all to list, as well, skip double quoting the command. The working code looks like
foreach (Node ${NodeList})
string(REGEX MATCHALL "[A-Za-z0-9_\\.]+" NodeDefinition "${Node}")
list(GET NodeDefinition 0 Name)
list(GET NodeDefinition 1 IP)
string(SUBSTRING ${Name} 0 8 starts_with)
if (starts_with STREQUAL "ch_node_")
list(APPEND commands COMMAND ssh -tt user#${IP} ${command})
endif ()
endforeach ()
execute_process(${commands})
When passing multiple commands to execute_process, these commands are separated solely by the COMMAND keyword, there is no need to "group" commands with double quotes.
Following execute_process will run 3 commands:
execute_process(COMMAND echo abc
COMMAND mkdir foo
COMMAND ssh -tt user#127.0.0.1 ls /bin)
So, when forming commands in a variable, double quotes are not needed either:
# Incorrect
list(APPEND commands "COMMAND ssh -tt user#${IP} \"${command}\"")
# Correct
list(APPEND commands COMMAND ssh -tt user#${IP} ${command})
(Quotes around the command passed to ssh for execute on the target machine are not needed either, ssh automatically treats every argument, followed the executable, as arguments to that executable.)
Note that commands in execute_process are not executed strictly in order, but piped: output of the first COMMAND is piped into the second one, output of the second one is piped into the third one, and so on.

cmake and portable nul / /dev/null device

In my Cmake script I need to redirect the standard output to the NUL / /dev/null device. I searched the CMake documentation for a portable solution but didn't find something.
I could do something like
if (WIN32)
set(NULDEV NUL)
else()
set(NULDEV /dev/null)
endif()
and use in the code ${NULDEV}, but I'd prefer a portable solution coming with CMake.
Edit usage form:
add_custom_target(docs
COMMENT "Generating documentation."
COMMAND ${CMAKE_COMMAND} -E chdir ${PROJECT_BINARY_DIR} "${THE_PROGRAM}" arguments > nul
)
Is this possible?
If you are running a shell command using execute_process() and want to quite the output. you can use the OUTPUT_QUIET and/or ERROR_QUIET options.
From the execute_process documentation:
OUTPUT_QUIET, ERROR_QUIET
The standard output or standard error results will be quietly ignored.
Example 1:
execute_process(COMMAND "${THE_PROGRAM}" argument OUTPUT_QUIET)
If you are using add_custom_target(), then it is unfortunately not that straight forward. What you could do is:
Example 2:
Create a wrapper cmake script for executing your program:
# generate_docs.cmake
execute_process(COMMAND "${THE_PROGRAM}" argument OUTPUT_QUIET)
Let CMake execute the wrapper script instead of running the program directly:
add_custom_target(docs
COMMENT "Generating documentation."
COMMAND ${CMAKE_COMMAND} -P generate_docs.cmake
)

How do I append a command to compiler/linker/archive commands

I want to pipe stdout from my compiler commands to a utility which colors warnings, errors, etc. in the command console
SET(CMAKE_C_COMPILE_OBJECT "<CMAKE_C_COMPILER> -c -MD -MF=<OBJECT>.d <DEFINES> <INCLUDES> <FLAGS> <SOURCE> -o <OBJECT> | color.exe")
However, ninja doesn't seem to recognise the pipe (|) notation. Maybe I could prepend cmd /c to the compiler call command using RULE_LAUNCH_COMPILE, but I'd rather a platform independent solution.
Is there a rule for piping command outputs? (such as RULE_LAUNCH_COMPILE, but which appends to the compiler commands rather than prepending?)
I originally thought about simply piping the output of the .bat script which launches CMake, but the color utility path is defined in the CMake toolchain file, as it included as part of one compiler vendor's package.
[update]:
The following CMake commands solves my initial problem by allowing me to pipe the output of a compile command to another program:
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "cmd /c")
SET(CMAKE_C_COMPILE_OBJECT "${CMAKE_C_COMPILE_OBJECT} 2>&1 | ${GCOLOR} -f")

CMake: unescape whitespace when echoing to a file

As part of our build process we automatically run unit tests through valgrind during the actual build (ie: it's not a separate target such as make test)
We create a sentinel file when the tests pass, so that subsequent build won't rerun the tests if not necessary.
We also save the command line and test output to a file.
Here I have built the valgrind command line:
set(VALGRIND_BIN "valgrind")
set(VALGRIND_OPTS "--leak-check=full --track-origins=yes")
set(VALGRIND_CMD "${VALGRIND_BIN} ${VALGRIND_OPTS}")
separate_arguments(VALGRIND_CMD)
These are the "passed" sentinal file, and the test output file.
set(OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}.output)
set(PASSED_FILE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_NAME}.passed)
Here I add a custom_command which works in the following way:
It echos the command line and saves it to the output file
It runs the test through valgrind, saving all output to the output file
If the test doesn't pass it will cat the output file and the command fails
If the test passes it will touch the passed sentinel file.
Here is the cmake source:
add_custom_command(
OUTPUT
${PASSED_FILE}
COMMAND
echo "\"${VALGRIND_BIN} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>\"" > ${OUTPUT_FILE}
COMMAND
${VALGRIND_CMD} $<TARGET_FILE:${TEST_NAME}> >> ${OUTPUT_FILE} 2>&1 || (cat ${OUTPUT_FILE} && false)
COMMAND
${CMAKE_COMMAND} -E touch ${PASSED_FILE}
COMMENT
"Running ${ARG_NAME} tests"
DEPENDS
${TEST_NAME}
USES_TERMINAL
)
Unfortunately cmake is escaping all the whitespace in my echo of the test command line, so that the first line in the output file looks like this:
valgrind\ --leak-check=full\ --track-origins=yes\ /home/steve/src/test\
I have proven to myself the escapes aren't in the variables, as if I output a message they aren't in there.
message(STATUS "\"${VALGRIND_BIN} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>\"")
The resulting output:
-- "valgrind --leak-check=full --track-origins=yes $<TARGET_FILE:test>"
Question:
How can I unescape the whitespace when echoing to a file?
That is, how can I have the line not be this:
valgrind\ --leak-check=full\ --track-origins=yes\ /home/steve/src/test\
but instead be this:
valgrind --leak-check=full --track-origins=yes /home/steve/src/test
You can put everything into a list, which will be expanded and the spaces will not be escaped.
Because CMake will be escaping spaces if it believes the string to be a single argument. Giving it as a list will take every element as a separate argument:
list(APPEND VALGRIND_CMD "$<TARGET_FILE:${TEST_NAME}>")
add_custom_command(
OUTPUT
${PASSED_FILE}
COMMAND
${CMAKE_COMMAND} -E echo \"${VALGRIND_CMD}\" > ${OUTPUT_FILE}
COMMAND
${VALGRIND_CMD} >> ${OUTPUT_FILE} 2>&1 || (cat ${OUTPUT_FILE} && false)
COMMAND
${CMAKE_COMMAND} -E touch ${PASSED_FILE}
COMMENT
"Running ${ARG_NAME} tests"
USES_TERMINAL
)
References
cmake: How to include literal double-quote in custom command?
cmake: when to quote variables?
As pointed by #MuertoExcobito, option VERBATIM cares about properly escaping parameters, no needs in additional double quotes escaped manually:
COMMAND
echo "${VALGRIND_BIN} ${VALGRIND_OPTS} $<TARGET_FILE:${TEST_NAME}>" > ${OUTPUT_FILE}
VERBATIM
(Outer double quotes are needed for CMake do not separate echo parameters).