Cmake string to get key value pairs from a string list containing key values separated by colon

1.7k views Asked by At

I have a stringlist (input):

set(MY_LIST "A:1;B:2;C:3")

I want to fetch the key values using foreach and set them as cmake constants. Something like:

foreach(ITEM ${MY_LIST})
   SET(<ITEM_A> <value_ofA>)
endforeach()

So basically i want the end result as but this should be achieved using a forloop:

SET(A "1")
SET(B "2")
SET(C "3")

How can i achieve this by using foreach to navigate each string in my list and set the key value pairs as cmake constants?

3

There are 3 answers

2
Alex Reinking On BEST ANSWER

This is not so bad:

cmake_minimum_required(VERSION 3.21)

set(MY_LIST "A:1;B:2;C:3;D:foo:bar")
foreach (pair IN LISTS MY_LIST)
  string(FIND "${pair}" ":" pos)
  if (pos LESS 1)
    message(WARNING "Skipping malformed pair (no var name): ${pair}")
  else ()
    string(SUBSTRING "${pair}" 0 "${pos}" var)
    math(EXPR pos "${pos} + 1")  # Skip the separator
    string(SUBSTRING "${pair}" "${pos}" -1 val)
    set("${var}" "${val}")
  endif ()
endforeach ()

message(STATUS "${A}")
message(STATUS "${B}")
message(STATUS "${C}")
message(STATUS "${D}")

The output is:

$ cmake -P test.cmake
-- 1
-- 2
-- 3
-- foo:bar

Might work on earlier versions, too. I keep up to date.

0
KamilCuk On

I was multiple times frustrated with iterating over multiple array elements in CMake. So I created a function. The following rather long file available from here:

function(foreach_count_items fci_foreach_count_items_loopvar_name
         fci_foreach_count_items_statevar_name)
  if("${ARGC}" LESS 1)
    message(FATAL_ERROR "foreach_count_items: wrong number of arguments")
  endif()

  set(fci_foreach_count_items_statevar
      "${${fci_foreach_count_items_statevar_name}}")
  set(fci_foreach_count_items_loopvar
      "${${fci_foreach_count_items_loopvar_name}}")
  # message(STATUS "fci_foreach_count_items_loopvar=${fci_foreach_count_items_loopvar}")
  # message(STATUS "fci_foreach_count_items_statevar=${fci_foreach_count_items_statevar}")

  if("${fci_foreach_count_items_statevar}" STREQUAL "")
    set(fci_foreach_count_items_statevar 1)
  endif()

  math(EXPR count "${ARGC} - 2")
  math(EXPR argc_less_one "${ARGC} - 1")
  math(EXPR argc_less_two "${ARGC} - 2")

  math(EXPR lastidx "${ARGC} - 1")
  # message(STATUS "set(${ARGV${lastidx}} ${fci_foreach_count_items_loopvar}
  # PARENT_SCOPE)")
  set("${ARGV${lastidx}}" "${fci_foreach_count_items_loopvar}")
  set("${ARGV${lastidx}}"
      "${fci_foreach_count_items_loopvar}"
      PARENT_SCOPE)
  # message(STATUS "lastidx=${lastidx}") message(STATUS
  # "ARGV${lastidx}=${ARGV${lastidx}}=${${ARGV${lastidx}}}")

  if(fci_foreach_count_items_statevar LESS count)
    foreach(i RANGE 2 ${argc_less_two} 1)
      math(EXPR j "${i} + 1")
      # message(STATUS "${i} ${j} | ${ARGV${i}}=${${ARGV${i}}}
      # ${ARGV${j}}=${${ARGV${j}}}")
      set("${ARGV${i}}" "${${ARGV${j}}}")
      set("${ARGV${i}}" "${${ARGV${j}}}" PARENT_SCOPE)
    endforeach()

    # foreach(i RANGE ${argc_less_one}) message(STATUS "ARGV${i} = ${ARGV${i}} =
    # ${${ARGV${i}}}") ndforeach()

    math(EXPR fci_foreach_count_items_statevar
         "${fci_foreach_count_items_statevar} + 1")
    set(fci_foreach_count_items_loopvar YES)
  else()
    set(fci_foreach_count_items_statevar 1)
    set(fci_foreach_count_items_loopvar NO)
  endif()

  set("${fci_foreach_count_items_statevar_name}" "${fci_foreach_count_items_statevar}" PARENT_SCOPE)
  set("${fci_foreach_count_items_loopvar_name}" "${fci_foreach_count_items_loopvar}" PARENT_SCOPE)
endfunction()

Allows to use it like:

set(mylist "A:1;B:2;C:3")
foreach(ii IN LISTS mylist)
   foreach_count_items(tmp item value)
   #                       ^^^^^^^^^^  - variable list
   #                   ^^^             - temporary iterator
   if(tmp)  # when iterator is set, ignore that loop, wait for the next one
     continue()
   enidf()

   # use item and value here
   set(${item} ${value})
endforeach()
1
fabian On

Simply use a regex to split the values. The code below wraps the functionality in a function. It also provides the functionality to add a prefix to avoid issues with reserved variable names in addition to creating a list of those variables. (For a shortened version that leaves out the extra logic, see the last code snippet of my answer.)

function(my_parse_values OUT_PREFIX)
    set(VAR_LIST)
    foreach(_PAIR IN LISTS ARGN)
        if (_PAIR MATCHES "^([^:]+):(.*)$")
            set("${OUT_PREFIX}${CMAKE_MATCH_1}" ${CMAKE_MATCH_2} PARENT_SCOPE)
            list(APPEND VAR_LIST ${CMAKE_MATCH_1})
        else()
            message(FATAL_ERROR "Invalid pair: ${_PAIR}")
        endif()
    endforeach()
    list(REMOVE_DUPLICATES VAR_LIST)
    set(${OUT_PREFIX} ${VAR_LIST} PARENT_SCOPE)
endfunction()

Example use:

set(MY_LIST "A:1;B:2;C:3")
my_parse_values(VARS ${MY_LIST})

# VARS_A with value 1,
# VARS_B with value 2,
# VARS_C with value 3 and
# list VARS with elements A, B and C
# are defined now...

foreach(_VAR IN LISTS VARS)
    message("${_VAR} = ${VARS_${_VAR}}")
endforeach()

If you don't need a list of variables, and want to set the variables as is, the code could be shortened to this:

function(my_parse_values)
    foreach(_PAIR IN LISTS ARGN)
        if (_PAIR MATCHES "^([^:]+):(.*)$")
            set(${CMAKE_MATCH_1} ${CMAKE_MATCH_2} PARENT_SCOPE)
        else()
            message(FATAL_ERROR "Invalid pair: ${_PAIR}")
        endif()
    endforeach()
endfunction()

set(MY_LIST "A:1;B:2;C:3")
my_parse_values(${MY_LIST})

message("A = ${A}")
message("B = ${B}")
message("C = ${C}")