21

What is the best way to join a list in CMake into a string?

By joining I mean convert SET(somelist "a" "b" "c\;c") to "a:b:c;c" where the glue string (":") is choosable. The following code works but it is REALLY long, is there a better way?

FUNCTION(JOIN LISTNAME GLUE OUTPUT)
SET(_TMP_STR "")
  FOREACH(VAL ${${LISTNAME}})
    SET(_TMP_STR "${_TMP_STR}${GLUE}${VAL}")
  ENDFOREACH(VAL ${${LISTNAME}})
  STRING(LENGTH "${GLUE}" GLUE_LEN)
  STRING(LENGTH "${_TMP_STR}" OUT_LEN)
  MATH(EXPR OUT_LEN ${OUT_LEN}-${GLUE_LEN})
  STRING(SUBSTRING "${_TMP_STR}" ${GLUE_LEN} ${OUT_LEN} _TMP_STR) 
  SET(${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
ENDFUNCTION()

#USAGE:
SET(somelist "a" "b" "c\;c")
JOIN(somelist ":" output)
MESSAGE("${output}") # will output "a:b:c;c"

Unfortunately using STRING(REPLACE ...) does not work:

function(JOINSTRREPLACE VALUES GLUE OUTPUT)
  string (REPLACE ";" "${GLUE}" _TMP_STR "${VALUES}")
  set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
endfunction()
JOINSTRREPLACE("${somelist}" ":" output)
MESSAGE(${output}) # will output "a:b:c\:c"
Geli
  • 503
  • 1
  • 4
  • 8

4 Answers4

14

Usually this task is solved with simple string REPLACE command. You can find a number of such replaces in scripts coming with cmake. But if you really need to care about semicolons inside you values, then you can use the following code:

function(JOIN VALUES GLUE OUTPUT)
  string (REGEX REPLACE "([^\\]|^);" "\\1${GLUE}" _TMP_STR "${VALUES}")
  string (REGEX REPLACE "[\\](.)" "\\1" _TMP_STR "${_TMP_STR}") #fixes escaping
  set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
endfunction()

SET( letters "" "\;a" b c "d\;d" )
JOIN("${letters}" ":" output)
MESSAGE("${output}") # :;a:b:c:d;d
Andrey Kamaev
  • 29,582
  • 6
  • 94
  • 88
9

You can use the string REPLACE function:

function(JOIN VALUES GLUE OUTPUT)
  string (REPLACE ";" "${GLUE}" _TMP_STR "${VALUES}")
  set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
endfunction()

#USAGE:
SET(somelist a b c)
JOIN("${somelist}" ":" output)
MESSAGE("${output}") # will output "a:b:c"
sakra
  • 62,199
  • 16
  • 168
  • 151
  • Oh yes thanks, this works. But does this mean that i can not have a list where an element contains a semicolon? – Geli Aug 24 '11 at 09:29
  • 1
    I updated the question to include a list with elements that contain semicolons. – Geli Aug 24 '11 at 10:38
  • Your original function JOIN does not cope with semicolons either. It outputs "a:b:cc" under Windows with cmake 2.8.5. – sakra Aug 24 '11 at 18:15
  • yes you are correct, but i think the one after the edit works. or does it not? It seems to work for me under linux, can you confirm it does not work under windows? – Geli Aug 29 '11 at 07:59
  • The newest version of JOIN outputs "a:b:cc" under Windows with cmake 2.8.5 for me. – sakra Aug 29 '11 at 18:21
  • Ah yes sorry my appologies! The last line should be MESSAGE("${output}") and not MESSAGE(${output}), because otherwiese the end of the string is a:b:c;c which is interpreted as a list "a:b:c" "c" and when such a list is converted to a string it is just joined directly. Wow cmake seems really broken to me now :/ – Geli Aug 29 '11 at 22:24
  • This wont't work when the list contains escaped semicolons. CMake's type "system" is so retarded. – Timmmm Mar 03 '17 at 15:02
7

The answers above are ok, when you do not use semicolons or when you can (and want to) escape them. I prefer not to escape semicolons, so I wrote the following function:

function(JOIN OUTPUT GLUE)
    set(_TMP_RESULT "")
    set(_GLUE "") # effective glue is empty at the beginning
    foreach(arg ${ARGN})
        set(_TMP_RESULT "${_TMP_RESULT}${_GLUE}${arg}")
        set(_GLUE "${GLUE}")
    endforeach()
    set(${OUTPUT} "${_TMP_RESULT}" PARENT_SCOPE)
endfunction()

The advantage is that the list can be empty and it doesn't have to be in a variable (but may be written in the place of invocation).

The usage is:

set(SOME_LIST a b c d)
join(RESULT "/" ${SOME_LIST})
message(STATUS "RESULT='${RESULT}'") # outputs RESULT='a/b/c/d'
# or
join(RESULT " " e f g h)
message(STATUS "RESULT='${RESULT}'") # outputs RESULT='e f g h'
Marek Kurdej
  • 1,459
  • 1
  • 17
  • 36