How to correctly quote CMAKE Join Generator expressions - cmake

Consider a target with compile options and include files:
target_compile_options(Tutorial PRIVATE /DC=1)
target_compile_options(Tutorial PRIVATE /DD=2)
target_include_directories(Tutorial PRIVATE "include files/a")
target_include_directories(Tutorial PRIVATE "include files/b")
And a custom command that wants to do something special:
add_custom_command(
OUTPUT Tutorial.i
COMMAND "${CMAKE_C_COMPILER}" -I"$<JOIN:$<TARGET_PROPERTY:Tutorial,INCLUDE_DIRECTORIES>,\" -I\">" "$<JOIN:$<TARGET_PROPERTY:Tutorial,COMPILE_OPTIONS>,\" \">" /P ${CMAKE_CURRENT_SOURCE_DIR}/Tutorial.cxx
MAIN_DEPENDENCY "Tutorial.cxx"
COMMENT "Preprocessing Tutorial.cxx"
)
This correctly quotes the include folders:
-I"include files/a" -I"include files/b"
Note the " marks immediately before and after the $<JOIN...> expression show up in the output.
However, the compile options come out without the leading and trailing " like this:
/DC=1" "/DD=2
On the other hand, if the generator expression for the compile options is this:
"$<JOIN:$<TARGET_PROPERTY:Tutorial,COMPILE_OPTIONS>, >"
Then, the result is this where the outer quotes are retained.
"/DC=1 /DD=2"
Neither are what I need - which is each option quoted separately. This can be fixed by expressly including first and last quotes:
"\"$<JOIN:$<TARGET_PROPERTY:Tutorial,COMPILE_OPTIONS>, >\""
What I don't understand is why the two JOIN expressions behave differently about retaining or dropping the surrounding double quotes?
P.S> And if the $<JOIN isn't surrounded in quotes at all, then it doesn't seem to be processed $<JOIN...> actually shows up in the command line.
This can be reproduced starting with the CMake Tutorial step 4 and adding the option and custom command lines above.

Related

CMake remove escape character from generator expresion

I have the generator expression GEN_EXPR_INCLUDE_PATHS that provides a list of the directories to include.
The goal is to pass this list directly as compiler option via add_custom_command:
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/${HEADER_FILE_NAME}.gch
DEPENDS ${HEADER_FILE} IMPLICIT_DEPENDS CXX ${HEADER_FILE}
COMMAND ${CMAKE_CXX_COMPILER}
${FLAGS}
${HEADER_INCLUDE_PATHS}
${gccGarbageCollectorOpts}
-c ${HEADER_FILE} -o
${CMAKE_BINARY_DIR}/${HEADER_FILE_NAME}.gch
)
Where HEADER_INCLUDE_PATHS is following generator expression:
$<1:-I$<JOIN:$<TARGET_PROPERTY:Some_Target,INTERFACE_INCLUDE_DIRECTORIES>, -I>>
Just because the INTERFACE_INCLUDE_DIRECTORIES of Some_Target is received as generator expression.
But cmake adds escape chars before spaces, and gcc fails due to these extra chars.
Is it possible to do something with it?
I'll be appriciate for any support.
add_custom_command accepts COMMAND parameters as a list. In CMake a list is a string which elements are separated by semicolon, so it is semicolon which should be used for joining, not a space.
Also, for correctly split semicolon-containing strings, add_custom_command needs COMMAND_EXPAND_LISTS keyword.
set(HEADER_INCLUDE_PATHS -I$<JOIN:$<TARGET_PROPERTY:mylib,INTERFACE_INCLUDE_DIRECTORIES>,;-I>)
add_custom_command(
...
COMMAND ${CMAKE_CXX_COMPILER}
...
"${HEADER_INCLUDE_PATHS}" # Double quotes are important!
...
COMMAND_EXPAND_LISTS # This option is important too
)
Actually, an example with a space-separated join of include directories
-I$<JOIN:$<TARGET_PROPERTY:INCLUDE_DIRECTORIES>, -I>
is provided in CMake documentation itself. Not sure about their intention for this example.

How do I include a literal double quote in a custom CMake command?

I'm trying to create a custom command that runs with some environment variables, such as LDFLAGS, whose value needs to be quoted if it contains spaces:
LDFLAGS="-Lmydir -Lmyotherdir"
I cannot find a way to include this argument in a CMake custom command, due to CMake's escaping rules. Here's what I've tried so far:
COMMAND LDFLAGS="-Ldir -Ldir2" echo blah VERBATIM)
yields "LDFLAGS=\"-Ldir -Ldir2\"" echo blah
COMMAND LDFLAGS=\"-Ldir -Ldir2\" echo blah VERBATIM)
yields LDFLAGS=\"-Ldir -Ldir2\" echo blah
It seems I either get the whole string quoted, or the escaped quotes don't resolve when used as part of the command.
I would appreciate either a way to include the literal double-quote or as an alternative a better way to set environment variables for a command. Please note that I'm still on CMake 2.8, so I don't have the new "env" command available in 3.2.
Note that this is not a duplicate of When to quote variables? as none of those quoting methods work for this particular case.
The obvious choice - often recommended when hitting the boundaries of COMMAND especially with older versions of CMake - is to use an external script.
I just wanted to add some simple COMMAND only variations that do work and won't need a shell, but are - I have to admit - still partly platform dependent.
One example would be to put only the quoted part into a variable:
set(vars_as_string "-Ldir -Ldir2")
add_custom_target(
QuotedEnvVar
COMMAND env LD_FLAGS=${vars_as_string} | grep LD_FLAGS
)
Which actually does escape the space and not the quotes.
Another example would be to add it with escaped quotes as a "launcher" rule:
add_custom_target(
LauncherEnvVar
COMMAND env | grep LD_FLAGS
)
set_target_properties(
LauncherEnvVar
PROPERTIES RULE_LAUNCH_CUSTOM "env LD_FLAGS=\"-Ldir -Ldir2\""
)
Edit: Added examples for multiple quoted arguments without the need of escaping quotes
Another example would be to "hide some of the complexity" in a function and - if you want to add this to all your custom command calls - use the global/directory RULE_LAUNCH_CUSTOM property:
function(set_env)
get_property(_env GLOBAL PROPERTY RULE_LAUNCH_CUSTOM)
if (NOT _env)
set_property(GLOBAL PROPERTY RULE_LAUNCH_CUSTOM "env")
endif()
foreach(_arg IN LISTS ARGN)
set_property(GLOBAL APPEND_STRING PROPERTY RULE_LAUNCH_CUSTOM " ${_arg}")
endforeach()
endfunction(set_env)
set_env(LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb")
add_custom_target(
MultipleEnvVar
COMMAND env | grep -E 'LDFLAGS|CFLAGS'
)
Alternative (for CMake >= 3.0)
I think what we actually are looking for here (besides the cmake -E env ...) is named Bracket Argument and does allow any character without the need of adding backslashes:
set_property(
GLOBAL PROPERTY
RULE_LAUNCH_CUSTOM [=[env LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb"]=]
)
add_custom_target(
MultipleEnvVarNew
COMMAND env | grep -E 'LDFLAGS|CFLAGS'
)
References
0005145: Set environment variables for ADD_CUSTOM_COMMAND/ADD_CUSTOM_TARGET
How to modify environment variables passed to custom CMake target?
[CMake] How to set environment variable for custom command
cmake: when to quote variables?
You need three backslashes. I needed this recently to get a preprocessor define from PkgConfig and apply it to my C++ flags:
pkg_get_variable(SHADERDIR movit shaderdir)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSHADERDIR=\\\"${SHADERDIR}\\\"")
Florian's answer is wrong on several counts:
Putting the quoted part in a variable makes no difference.
You should definitely use VERBATIM. It fixes platform-specific quoting bugs.
You definitely shouldn't use RULE_LAUNCH_CUSTOM for this. It isn't intended for this and only works with some generators.
You shouldn't use env as the command. It isn't available on Windows.
It turns out the real reason OPs code doesn't work is that CMake always fully quotes the first word after COMMAND because it's supposed to be the name of an executable. You simply shouldn't put environment variables first.
For example:
add_custom_command(
OUTPUT q1.txt
COMMAND ENV_VAR="a b" echo "hello" > q1.txt
VERBATIM
)
add_custom_target(q1 ALL DEPENDS q1.txt)
$ VERBOSE=1 make
...
"ENV_VAR=\"a b\"" echo hello > q1.txt
/bin/sh: ENV_VAR="a b": command not found
So how do you pass an environment variable with spaces? Simple.
add_custom_command(
OUTPUT q1.txt
COMMAND ${CMAKE_COMMAND} -E env ENV_VAR="a b" echo "hello" > q1.txt
VERBATIM
)
Ok, I removed my original answer as the one proposed by #Florian is better. There is one additional tweak needed for multiple quoted args. Consider a list of environment variables as such:
set(my_env_vars LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb")
In order to produce the desired expansion, convert to string and then replace ; with a space.
set(my_env_string "${my_env_vars}") #produces LDFLAGS="...";CFLAGS="..."
string(REPLACE ";" " " my_env_string "${my_env_string}")
Then you can proceed with #Florian's brilliant answer and add the custom launch rule. If you need semicolons in your string then you'll need to convert them to something else first.
Note that in this case I didn't need to launch with env:
set_target_properties(mytarget PROPERTIES RULE_LAUNCH_CUSTOM "${my_env_string}")
This of course depends on your shell.
On second thought, my original answer is below as I also have a case where I don't have access to the target name.
set(my_env LDFLAGS=\"-Ldir -Ldir2" CFLAGS=\"-Idira -Idirb\")
add_custom_command(COMMAND sh -c "${my_env} grep LDFLAGS" VERBATIM)
This technique still requires that the semicolons from the list->string conversion be replaced.
Some folks suggest to use ${CMAKE_COMMAND} and pass your executable as an argument, e.g:
COMMAND ${CMAKE_COMMAND} -E env "$(WindowsSdkDir)/bin/x64/makecert.exe" ...
That worked for me.

How to preserve single quotes in a CMake cached variable?

I have a variable
SET(CODE_COVERAGE_EXCLUSION_LIST
""
CACHE STRING "List of resources to exclude from code coverage analysis")
It must contain a list of expressions such as : 'tests/*' '/usr/*'
When trying to set the default value to the above expressions, the single quotes are removed.
How to preserve them ?
Moreover, when I try to pass the exclusion list like this
cmake -DCODE_COVERAGE_EXCLUSION_LIST="'tests/*' '/usr/*'" ..
The initial and final single quotes are lost. How to preserve them as well ?
Finally, the same question applies when using cmake-gui.
EDIT : I tried to use backslash to escape the quotes :
SET(CODE_COVERAGE_EXCLUSION_LIST
" \'tests/*\' \'/usr/*\'"
CACHE STRING "List of resources to exclude from code coverage analysis : ")
It gave me the following error :
Syntax error in cmake code at
xxx.cmake:106
when parsing string
\'tests/*\' \'/usr/*\'
Invalid escape sequence \'
EDIT2 : code of the add_custom_target (and not add_custom_command, my bad)
ADD_CUSTOM_TARGET(${_targetname}
# Cleanup lcov
${LCOV_PATH} --directory . --zerocounters
# Run tests
COMMAND ${_testrunner} ${ARGV3}
# Capturing lcov counters and generating report
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info
COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' ${CODE_COVERAGE_EXCLUSION_LIST} --output-file ${_outputname}.info.cleaned
COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned
COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
)
Turning my comments into an answer
First - taking the question why you need those quotes aside - I could reproduce your problem and found several possible solutions:
Adding spaces at the begin and end of your cached variable
cmake -DCODE_COVERAGE_EXCLUSION_LIST=" 'tests/*' '/usr/*' " ..
Using "escaped" double quotes instead of single quotes
cmake -DCODE_COVERAGE_EXCLUSION_LIST:STRING="\"tests/*\" \"/usr/\"" ..
Using your set(... CACHE ...) example by setting policy CMP0053 switch introduced with CMake 3.1:
cmake_policy(SET CMP0053 NEW)
set(CODE_COVERAGE_EXCLUSION_LIST
"\'tests/*\' \'/usr/*\'"
CACHE STRING "List of resources to exclude from code coverage analysis : ")
But when setting this in the code I could also just do
set(CODE_COVERAGE_EXCLUSION_LIST
"'tests/*' '/usr/*'"
CACHE STRING "List of resources to exclude from code coverage analysis : ")
The quoting issue seems only to be a problem when called from command line
Then - if I do assume you may not need the quotes - you could pass the paths as a list:
A semicolon separated CMake list is expanded to parameters again (with spaces as delimiter) when used in a COMMAND
cmake -DCODE_COVERAGE_EXCLUSION_LIST:STRING="tests/*;/usr/*" ..
with
add_custom_target(
...
COMMAND ${LCOV_PATH} --remove ${_outputname}.info ${CODE_COVERAGE_EXCLUSION_LIST} --output-file ${_outputname}.info.cleaned
)
would give something like
.../lcov --remove output.info tests/* /usr/* --output-file output.info.cleaned
I also tried to add the VERBATIM option, because "all arguments to the commands will be escaped properly for the build tool so that the invoked command receives each argument unchanged". But in this case, it didn't change anything.
References
add_custom_target()
CMake Language: Escape Sequences
0015200: Odd quoting issue when mixing single, double and escaped quotes to COMMAND

CMake error with string sub-command STRIP "requires two arguments"

I am trying to compile a library with CMake. This library uses CMake with the pods build system.
During configuring I get the following error:
CMake Error at cmake/pods.cmake:257 (string):
string sub-command STRIP requires two arguments.
In the specific file pods.cmake the command looks like this:
execute_process(COMMAND
${PKG_CONFIG_EXECUTABLE} --cflags-only-I ${ARGN}
OUTPUT_VARIABLE _pods_pkg_include_flags)
string(STRIP ${_pods_pkg_include_flags} _pods_pkg_include_flags)
which looks fine to me. Any ideas why this error occurs? I don't understand why cmake complains that it needs two arguments for the STRIP command when it clearly has two.
Note: I use cmake 2.8.12.2, but according to the documentation this should be valid.
While your CMake file does syntactically contain two arguments, ${_pods_pkg_include_flags} can be empty. If so, it is not an argument semantically and never reaches string(), which then sees just one. If it's possible for a string to be empty (and you want to treat it as an empty string in such case instead of skipping it), quote it:
string(STRIP "${_pods_pkg_include_flags}" _pods_pkg_include_flags)

CMake variable expansion using "#" vs. "${}"

Consider the following:
SET(TEST_DIR, "test")
INSTALL(PROGRAMS scripts/foo.py DESTINATION ${TEST_DIR})
INSTALL(PROGRAMS scripts/foo.py DESTINATION #TEST_DIR#)
The first INSTALL command does not work. The second does. Why is that? What is the difference between those two? I have not found any reference to ## expansion except in the context of creation of configuration files. Everything else only uses ${} expansion.
UPDATE: OK, obvious bug in the above. My SET() command has an extraneous comma. Removing it, such that it looks like:
SET(TEST_DIR "test")
results in both ## and ${} expansions working. Still wondering (a) what is the meaning of ## as opposed to ${}, and why only the former worked with my incorrect SET() statement.
According to the documentation for the configure_file() command when configuring a file both the ${VAR} form and #VAR# form will be replaced VAR's value. Based on your experience above and some testing I did both forms are replaced when CMake evaluates your CMakeLists.txt, too. Since this is not documented I would recommend against using the #VAR# from in your CMakeLists.txt
Note that when using configure_file() you can restrict replacement to only the #VAR# form by using the #ONLY argument.
As far as I know, the #VAR# syntax is only used when replacing variables with the configure_file command.
Note that the configure_file command allows for an extra option #ONLY. Using it you can specify that only the #VAR#'s are replaced, but that the ${VAR}'s are kept.
As an example, this can be useful when generating e.g. a cmake-file which is later to be used with CMake again. E.g. when building your project, the #VAR# will be replaced when using configure_file. After you distributed your project and someone else uses the generated UseProject.cmake file, the ${VAR}$ entries will be replaced.