This question already has answers here:
CMake's execute_process and arbitrary shell scripts
(3 answers)
Closed 5 years ago.
I wanted to extract a string from a header in an execute_process.
But there is a bug with the command and I try a lot of things, always the same error.
execute_process(
COMMAND cat $(version_h) | grep -a "define myVersion " | cut -d " " -f3 | cut -d '"' -f2`
OUTPUT_VARIABLE _Version)
If I write the command in the console line, there is no problem.
The error says: "Parse error. Function missing ending ")". Instead found unterminated string with text "
"
execute_process() only deals with processes and their arguments, i.e. there is no shell involved.
So, you have two main options:
execute_process(COMMAND bash -c "..." OUTPUT_VARIABLE _Version)
execute_process(COMMAND cat ... COMMAND grep ... COMMAND cut ... COMMAND cut ... OUTPUT_VARIABLE _Version)
In the second version, the standard output and standard inputs of the commands chain together.
If you want to do anything more complex, you'll have to create a separate script and invoke it in a process-, not shell-, oriented way, i.e. option 1.
The problem was that I need to remove a quote character and I guess there is a confusion with Cmake and the bash command.
execute_process(COMMAND cat ... COMMAND grep ... COMMAND cut ... COMMAND cut -c2- COMMAND rev COMMAND cut -c2- COMMAND rev OUTPUT_VARIABLE _Version)
Related
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.
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.
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).
I just don't seem to be able to wrap my head around CMake's escape rules. Given:
set(X A B C)
add_custom_target( works COMMAND DUMMY=0 X="${X}" env | grep ^X= COMMENT "This works")
add_custom_target( fails COMMAND X="${X}" env | grep ^X= COMMENT "This fails")
The intention is to execute command X="A B C" env. The custom target works correctly constructs the command, where as fails incorrectly executes:
X=\"A B C\" env ...
But why?
Actually you ran into two problems:
Don't quote CMake variables in custom commands. CMake will do the necessary escape sequences for you.
The first literal after COMMAND is assumed to be a command name or file. So CMake tries to handle it as a single "string".
So I changed the quoting and the env call and the following did work for me:
cmake_minimum_required(VERSION 2.8)
project(QuotedStrings)
set(X "A B C")
add_custom_target( works COMMAND env DUMMY=0 X=${X} | grep ^X= COMMENT "This works")
add_custom_target( fails_no_more COMMAND env X=${X} | grep ^X= COMMENT "This works too")
For more details and possibilities see:
cmake: How to include literal double-quote in custom command?
cmake: when to quote variables?
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.