cmake adds redundant quotation marks for external projects - cmake

As part of a larger cmake build I have an external project. A custom build command is created to build this part of the software.
I want to pass flags in quotes. However, cmake keeps wrapping my code with quotes where I do not want them. Take the following example case:
include(ExternalProject)
set(bar "echo;cxxflags=\"flag1 flag2\"")
ExternalProject_Add(test
PREFIX ""
DOWNLOAD_COMMAND ""
COMMAND "${bar}"
TEST ""
)
When I run
export VERBOSE=1
cmake ..
make
I the cxxflags argument is wrapped in quotes which is not usable for my purpose.
...
cd /some/path && echo "cxxflags=\"flag1 flag2\""
...
If I use the following CMakeLists.txt, the entire command is wrapped in quotes and won't execute.
include(ExternalProject)
set(bar "echo;cxxflags=\"flag1 flag2\"")
string(REPLACE ";" " " barcmd "${bar}")
ExternalProject_Add(test
PREFIX ""
DOWNLOAD_COMMAND ""
COMMAND ${barcmd}
TEST ""
)
The entire command is wrapped quotes:
...
cd /some/path && "echo cxxflags=\"flag1 flag2\""
...
What I need is something like
cd /some/path && echo cxxflags=\"flag1 flag2\"
When my arguments do not contain any quotation marks, the first approach works fine, i.e.
include(ExternalProject)
set(bar "echo;cxxflags=flag1)
ExternalProject_Add(test
PREFIX ""
DOWNLOAD_COMMAND ""
COMMAND "${bar}"
TEST ""
)
works as expected and outputs
cd /some/path && echo cxxflags=flag1

The question occurred building the boost library with cmake. There is no proper solution. Kitware devs suggest wrapping the entire argument with quotes and have no quotes within the argument itself. The call is written to a script file, which is invoked by cmake.
This solved my problem.

Related

Streaming output from make called by cmake

I am calling make in a cmake custom command (I have to bridge between a CMake build and a legacy make build). Everything works fine, except one thing: Instead of seeing the output of the make build live on stdout, it only gets flushed after the build finishes. I have searched online but couldn't find a solution so far. What could the cause for such a behavior be?
I tested this modified simple example which recursively calls itself. This does behave exactly how I expect. I see the echos from the recursive calls as they happen. I can't figure out what the difference to my actual code is:
include(ExternalProject)
ExternalProject_Add(Wrapper
PREFIX "Wrapper"
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMAKE_COMMAND} -E echo hello
COMMAND ${CMAKE_COMMAND} -E sleep 5
COMMAND ${CMAKE_COMMAND} -E env FOO=BAR ${CMAKE_MAKE_PROGRAM} -C "${CMAKE_CURRENT_SOURCE_DIR}"
BUILD_ALWAYS ON
INSTALL_COMMAND ""
)
My actual code, which only shows the output after the external make build is completely finished:
ExternalProject_Add(Wrapper
PREFIX "Wrapper"
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMAKE_COMMAND} -E copy ${target_files} ${CMAKE_INSTALL_PREFIX}
COMMAND ${CMAKE_COMMAND} -E env ASM_OPT="${CMAKE_ASM_FLAGS}" CC_OPT="${CMAKE_C_FLAGS}" CPP_OPT="${CMAKE_CXX_FLAGS}" ${CMAKE_MAKE_PROGRAM} -C "${CMAKE_CURRENT_SOURCE_DIR}/.." -f Makefile.mak -O -j 4
BUILD_ALWAYS ON
INSTALL_COMMAND ""
DEPENDS ${ALL_LIBS}
)
Bad news: In the case of other than Unix Makefiles generator (e.g., Ninja, the CMAKE_MAKE_PROGRAM variable gonna have a path to underlaid build system tool (e.g., /usr/bin/ninja for Ninja generator :)
A better way is to use ExternalProject to build your non-CMake (third party) project.
Or, since CMake 3.18 execute_process got the ECHO_OUTPUT_VARIABLE named keyword (option) which is equal to *nix tee command...

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).

CMake quote escape conumdrum

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?

Add quotation mark in CMake string

I am using CMake to create and build my project solution. i am using the following command to add a post build event to copy a .tlb from the local bin to the program bin.
ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME} POST_BUILD COMMAND xcopy /D /Y "${CMAKE_SOURCE_DIR}LocalBin\\example.tlb" "${CMAKE_SOURCE_DIR}ProgramBin\\$<CONFIGURATION>\\")
When this adds the command to the project properties , it is added as
xcopy /D /Y LocalBin\example.tlb ProgramBin\Debug\
However this gives me an error. Exited with Code 4.
If i go into the project properties and hack the command line and change it to add " "
xcopy /D /Y "LocalBin\example.tlb " "ProgramBin\Debug\"
It works.
Is there a way i can change the CMake add custom command to include the " " in the actual command line so it will work and there is no need to manually change the project properties.
You have to escape the quotation mark inside the string. Escaping is done in CMake with a back slash. So adding a quotation mark to your string, add \".
Quoting the CMake documentation https://cmake.org/cmake/help/v3.4/manual/cmake-language.7.html#escape-sequences
A \ followed by a non-alphanumeric character simply encodes the literal character without interpreting it as syntax
In your case you'll end up with
"\"${CMAKE_SOURCE_DIR}LocalBin\\example.tlb\""
instead of
"${CMAKE_SOURCE_DIR}LocalBin\\example.tlb"
After struggling with this for some time, I found a workable solution using generator expressions:
ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME} POST_BUILD COMMAND xcopy /D /Y $<1:"${CMAKE_SOURCE_DIR}LocalBin\\example.tlb"> $<1:"${CMAKE_SOURCE_DIR}ProgramBin\\$<CONFIGURATION>\\">)
This is an open bug in CMake.
Sadly this works only for the command arguments, but not for the command itself. This is really no fun when you have to call for example shader compiler that is in the Program Files (path with space in it) and prebuild rule fails. To work around this problem I created batch file on the fly with cmake file(WRITE ...) command and then call this batch file with correctly escaped path to the actual shader compiler I want to execute. In the batch I have a line with %1
Solution for my problem
file(WRITE Shaders/compile_shaders.bat "\
%1 /ESolveVMF /Tcs_5_0 /DTGSize_=16 /Fh \"${CMAKE_CURRENT_LIST_DIR}/Shaders/GenerateRoughnessMaps.inc\" \"${CMAKE_CURRENT_LIST_DIR}/Shaders/GenerateRoughnessMaps.hlsl\"\n\
%1 /ESolveVMFReconstructNormal /Tcs_5_0 /DTGSize_=16 /Fh \"${CMAKE_CURRENT_LIST_DIR}/Shaders/GenerateReconstructNormalRoughnessMaps.inc\" \"${CMAKE_CURRENT_LIST_DIR}/Shaders/GenerateRoughnessMaps.hlsl\"")
add_custom_command(TARGET ${CURRENT_MODULE_NAME} PRE_BUILD
COMMAND "${CMAKE_CURRENT_LIST_DIR}\\Shaders\\compile_shaders.bat" "\"$(WindowsSdkDir)bin\\x86\\fxc.exe\""
COMMENT "Compiling shaders")

custom_command ECHO with special character

I am trying to add a custom_command with CMake and call COMMAND echo "$" > file.txt
as long as I put $ in it, the config file will generate but failed to build.
I have also tried echo "\$" and doesn't seems to work.
add_custom_command( TARGET ${TARGET_NAME}
POST_BUILD
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/out
COMMAND echo "-keep class com.android.**\$* { ; }" >> ./proguard.txt
)
The cmake command works but as long as I call ninja, I got the following error:
error: 'src', needed by 'all', missing and no known rule to make it
Seems like cmake is unable to generate the build step. My intention is to print that **$ into a file.
Both
COMMAND echo "$$" > file.txt
and
COMMAND echo "$" > file.txt VERBATIM
output $ sign into given file.
EDIT: This works on makefile generators, and only when make is run from the terminal. Generally redirection sign ">" is not worked as expected in COMMAND expression.