E.g:
set (txt "Hello" "There" "World")
# TODO
message (txt) # Prints "Hello\nThere\nWorld" (i.e. each list item on a new line
What do I put in place of TODO?
CMake's lists are semicolon-delimited. So "Hello" "There" "World"
is internally represented as Hello;There;World
. So a simple solution is to replace semicolons with newlines:
string (REPLACE ";" "\n" txt "${txt}")
This works in this example, however lets try a more complicated example:
set (txt "" [[\;One]] "Two" [[Thre\;eee]] [[Four\\;rrr]])
The [[
]]
is a raw string so the \
's are passed through into CMake's internal representation of the list unchanged. The internal representation is: ;\;One;Two;Thre\;eee;Four\\;rrr
. We'd expect it to print:
<blank line>
;One
Two
Thre;eee
Four\\;rrr
I'm not actually 100% sure about the Four\\;rrr
one but I think it is right. Anyway with our naive implementation we actually get this:
<blank line>
\
One
Two
Thre\
eee
Four\\
rrr
It's because it doesn't know to not convert actual semicolons that are escaped. The solution is to use a regex:
string (REGEX REPLACE "[^\\\\];" "\\1\n" txt "${txt}")
I.e. only replace ;
if it is preceded by a non-\
character (and put that character in the replacement). The almost works, but it doesn't handle the first empty element because the semicolon isn't preceded by anything. The final answer is to allow the start of string too:
string (REGEX REPLACE "(^|[^\\\\]);" "\\1\n" txt "${txt}")
Oh and the \\\\
is because one level of escaping is removed by CMake processing the string literal, and another by the regex engine. You could also do this:
string (REGEX REPLACE [[(^|[^\\]);]] "\\1\n" txt "${txt}")
But I don't think that is clearer.
Maybe there is a simpler method than this but I couldn't find it. Anyway, that, Ladies and Gentlemen, is why you should never use strings as your only type, or do in-band string delimiting. Still, could have been worse - at least they didn't use spaces as a separator like Bash!
I just wanted to add some alternatives I'm seeing just using the fact that message()
does place a newline at the end by itself:
Just using for_each()
to iterate over the list:
set (txt "Hello" "There" "World")
foreach(line IN LISTS txt)
message("${line}")
endforeach()
An function()
based alternative I came up with looks more complicated:
function(message_cr line)
message("${line}")
if (ARGN)
message_cr(${ARGN})
endif()
endfunction()
set(txt "Hello" "There" "World")
message_cr(${txt})
The more generalized version of those approaches would look like:
for_each()
with strings
set(txt "Hello" "There" "World")
foreach(line IN LISTS txt)
string(APPEND multiline "${line}\n")
endforeach()
message("${multiline}")
function()
with strings
function(stringify_cr var line)
if (ARGN)
stringify_cr(${var} ${ARGN})
endif()
set(${var} "${line}\n${${var}}" PARENT_SCOPE)
endfunction()
set(txt "Hello" "There" "World")
stringify_cr(multiline ${txt})
message(${multiline})
If you don't like the additional newline at the end add string(STRIP "${multiline}" multiline)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With