I'm writing a CMake file to support the IAR toolchain including ASM language but leave the option open for tools like GCC in the future.
I wrote up a generator expression like this:
target_compile_options(my_library PRIVATE
# ASM language options
$<$<COMPILE_LANG_AND_ID:ASM,IAR>:
-s+
-w+
--cpu Cortex-M7
--fpu VFPv5_sp
-M<> # This is a problem
>
)
The last option for the assembler requires the use of <> but this understandably causes CMake a problem because > indicates the end of a generator expression. I tried escaping the characters with backslashes such as -M\<\> or with quotes like -M'<>' or '"-M'<>'"` but it didn't work. I even tried defining a separate variable but the output commands are still wrong:
set(MY_IAR_ASM_FLAG "-M<>")
target_compile_options(my_library PRIVATE
# ASM language options
$<$<COMPILE_LANG_AND_ID:ASM,IAR>:
${MY_IAR_ASM_FLAG}
>
I end up with a stray > on the command line in all cases.
What's the right way to escape these characters so I get the right ASM switches? Or is there a good workaround?
You could use the CMake escaped characters (e.g., $<ANGLE-R>) like this
target_compile_options(my_library PRIVATE
# ASM language options
$<$<COMPILE_LANG_AND_ID:ASM,IAR>:
-s+
-w+
--cpu Cortex-M7
--fpu VFPv5_sp
-M<$<ANGLE-R> # This is a problem
>
)
Related
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.
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.
I have a file that contains a bunch of data. I want to turn it into a C++ string literal, because I need to compile this data into the binary - I cannot read it from disk.
One way of doing this is to just generate a C++ source file that declares a string literal with a known name. The CMake code to do this is straightforward, if somewhat awful:
function(make_literal_from_file dest_file source_file literal_name)
add_custom_command(
OUTPUT ${dest_file}
COMMAND printf \'char const* ${literal_name} = R\"\#\(\' > ${dest_file}
COMMAND cat ${source_file} >> ${dest_file}
COMMAND printf \'\)\#\"\;\' >> ${dest_file}
DEPENDS ${source_file})
endfunction()
This works and does what I want (printf is necessary to avoid a new line after the raw string introducer). However, the amount of escaping going on here makes it very difficult to see what's going on. Is there a way to write this function such that it's actually readable?
Note that I cannot use a file(READ ...)/configure_file(...) combo here because source_file could be something that is generated by CMake at build time and so may not be present at configuration time.
I would recommend writing a script to do this. You could write it in CMake, but I personally prefer a better language such as Python:
# Untested, just to show roughly how to do it
import sys
dest_file, source_file, literal_name = sys.argv[1:]
with open(dest_file) as dest, open(source_file) as source:
literal_contents = source.read()
dest.write(f'char const* {literal_name} = R"({literal_contents})";\n')
Corresponding CMake code:
# String interpolation came in Python 3.6, thus the requirement on 3.6.
# If using CMake < 3.12, use find_package(PythonInterp) instead.
find_package(Python3 3.6 COMPONENTS Interpreter)
# Make sure this resolves correctly. ${CMAKE_CURRENT_LIST_DIR} is helpful;
# it's the directory containing the current file (this cmake file)
set(make_literal_from_file_script "path/to/make_literal_from_file.py")
function(make_literal_from_file dest_file source_file literal_name)
add_custom_command(
OUTPUT "${dest_file}"
COMMAND
"${Python3_EXECUTABLE}" "${make_literal_from_file_script}"
"${dest_file}"
"${source_file}"
"${literal_name}"
DEPENDS "${source_file}")
endfunction()
If you don't want the dependency on Python, you could use C++ (only the CMake code shown):
add_executable(make_literal_from_file_exe
path/to/cpp/file.cpp
)
function(make_literal_from_file dest_file source_file literal_name)
add_custom_command(
OUTPUT "${dest_file}"
COMMAND
make_literal_from_file_exe
"${dest_file}"
"${source_file}"
"${literal_name}"
DEPENDS "${source_file}")
endfunction()
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
I wonder if there is a better solution to my problem. I am working on a platform independent software project and want to add python-based unittests to cmake. The issue that I encountered now is that when configuring the ctest tests and setting up the correct PYTHONPATH environment variable for running my test, I end up with a lot of boilerplate code for each test:
add_test(my_awesome_test ${PYTHON_EXECUTABLE} my_awesome_test.py)
if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=somepath\;anotherpath")
else() # e.g. Linux
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=somepath:anotherpath")
endif()
# more tests like this after this...
The problem here is the branching, that is required only because of the platform dependent list separators.
Is there some neater way to accomplish this?
Is there a constant which specifies the platform separator or a function that allows me to construct these lists?
If there is no "proper" answer, I also wanted to share my obvious, but not-so-nice solution:
if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
set(SEP "\\;")
else() # e.g. Linux
set(SEP ":")
endif()
add_test(my_awesome_test ${PYTHON_EXECUTABLE} my_awesome_test.py)
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=somepath${SEP}anotherpath")
# more tests like this after this...
On Windows the ";" must be double escaped because otherwise it it is substituted later in the add_test line as a single ";" again, which is then in turn interpreted as the cmake-list separator leading to wrong results. However, having cmake report which character should be used as list separator would still be nicer...
Unfortunately file(TO_NATIVE_PATH ... does not convert ; to : on Unix platforms. I think the easiest way is therefore something like this:
set(PATH "/usr/bin" "/bin")
if (UNIX)
string(REPLACE ";" ":" PATH "${PATH}")
endif()
This works because CMake lists are really ;-separated strings, so on Windows you can use them for path lists as-is. It won't work for paths containing ; or : but that will trip up 99% of Unix software anyway so I wouldn't worry about it.
You can use the file function as follow:
file(TO_CMAKE_PATH "<path>" <variable>)
file(TO_NATIVE_PATH "<path>" <variable>)
https://cmake.org/cmake/help/v3.9/command/file.html
You may define function/macro which transforms some choosen separator into platform-specific one. E.g., function below transforms CMake list into platform-specific path list:
function(to_path_list var path1)
if("${CMAKE_HOST_SYSTEM}" MATCHES ".*Windows.*")
set(sep "\\;")
else()
set(sep ":")
endif()
set(result "${path1}") # First element doesn't require separator at all...
foreach(path ${ARGN})
set(result "${result}${sep}${path}") # .. but other elements do.
endforeach()
set(${var} "${result}" PARENT_SCOPE)
endfunction()
Usage:
to_path_list(pythonpath "somepath" "anotherpath")
set_tests_properties(my_awesome_test PROPERTIES ENVIRONMENT "PYTHONPATH=${pythonpath}")