Skip to content

Commit 7b8c9a2

Browse files
committed
[nrf fromtree] cmake: support array of maps in yaml module
This commit introduce support for maps in a yaml list. The yaml_set() function has been extended with the following signature: > yaml_set(NAME <name> KEY <key>... > [APPEND] LIST MAP <map1> MAP <map2> MAP ... > ) where a `MAP <map>` has the form: `MAP "<key1>: <value1>, <key2>: <value2>, ...` Signed-off-by: Torsten Rasmussen <[email protected]> (cherry picked from commit 0828d0b)
1 parent 687c5cc commit 7b8c9a2

File tree

3 files changed

+250
-15
lines changed

3 files changed

+250
-15
lines changed

cmake/modules/yaml.cmake

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
# - foo2
2020
# - foo3
2121
#
22+
# Support for list of maps, like:
23+
# foo:
24+
# - bar: val1
25+
# baz: val1
26+
# - bar: val2
27+
# baz: val2
28+
#
2229
# All of above can be combined, for example like:
2330
# foo:
2431
# bar: baz
@@ -28,14 +35,6 @@
2835
# - beta
2936
# - gamma
3037
# fred: thud
31-
#
32-
# Support for list of objects are currently experimental and not guranteed to work.
33-
# For example:
34-
# foo:
35-
# - bar: val1
36-
# baz: val1
37-
# - bar: val2
38-
# baz: val2
3938

4039
include_guard(GLOBAL)
4140

@@ -97,6 +96,13 @@ function(internal_yaml_list_append var genex key)
9796
internal_yaml_list_initializer(subjson TRUE)
9897
if(${arraylength} GREATER 0)
9998
math(EXPR arraystop "${arraylength} - 1")
99+
list(GET ARG_YAML_LIST 0 entry_0)
100+
if(entry_0 STREQUAL MAP)
101+
message(FATAL_ERROR "${function}(GENEX ${argument} ) is not valid at this position.\n"
102+
"Syntax is 'LIST MAP \"key1: value1.1, ...\" MAP \"key1: value1.2, ...\""
103+
)
104+
endif()
105+
100106
foreach(i RANGE 0 ${arraystop})
101107
string(JSON item GET "${json_content}" ${key} ${i})
102108
list(APPEND subjson ${item})
@@ -109,12 +115,49 @@ function(internal_yaml_list_append var genex key)
109115
# lists are stored as JSON arrays
110116
string(JSON index LENGTH "${subjson}")
111117
list(LENGTH ARGN length)
112-
math(EXPR stop "${index} + ${length} - 1")
113118
if(NOT length EQUAL 0)
114-
foreach(i RANGE ${index} ${stop})
115-
list(POP_FRONT ARGN value)
116-
string(JSON json_content SET "${json_content}" ${key} ${i} "\"${value}\"")
117-
endforeach()
119+
list(GET ARG_YAML_LIST 0 entry_0)
120+
if(entry_0 STREQUAL MAP)
121+
math(EXPR length "${length} / 2")
122+
math(EXPR stop "${index} + ${length} - 1")
123+
foreach(i RANGE ${index} ${stop})
124+
list(POP_FRONT ARG_YAML_LIST argument)
125+
if(NOT argument STREQUAL MAP)
126+
message(FATAL_ERROR "yaml_set(${argument} ) is not valid at this position.\n"
127+
"Syntax is 'LIST MAP \"key1: value1.1, ...\" MAP \"key1: value1.2, ...\""
128+
)
129+
endif()
130+
list(POP_FRONT ARG_YAML_LIST map_value)
131+
string(REGEX REPLACE "([^\\])," "\\1;" pair_list "${map_value}")
132+
set(qouted_map_value)
133+
foreach(pair ${pair_list})
134+
if(NOT pair MATCHES "[^ ]*:[^ ]*")
135+
message(FATAL_ERROR "yaml_set(MAP ${map_value} ) is malformed.\n"
136+
"Syntax is 'LIST MAP \"key1: value1.1, ...\" MAP \"key1: value1.2, ...\"\n"
137+
"If value contains comma ',' then ensure the value field is properly qouted "
138+
"and escaped"
139+
)
140+
endif()
141+
string(REGEX MATCH "^[^:]*" map_key "${pair}")
142+
string(REGEX REPLACE "^${map_key}:[ ]*" "" value "${pair}")
143+
string(STRIP "${map_key}" map_key)
144+
if(value MATCHES "," AND NOT (value MATCHES "\\\\," AND value MATCHES "'.*'"))
145+
message(FATAL_ERROR "value: ${value} is not properly quoted")
146+
endif()
147+
string(REGEX REPLACE "\\\\," "," value "${value}")
148+
list(APPEND qouted_map_value "\"${map_key}\": \"${value}\"")
149+
endforeach()
150+
list(JOIN qouted_map_value "," qouted_map_value)
151+
string(JSON json_content SET "${json_content}" ${key} ${i} "{${qouted_map_value}}")
152+
endforeach()
153+
else()
154+
math(EXPR stop "${index} + ${length} - 1")
155+
list(GET ARG_YAML_LIST 0 entry_0)
156+
foreach(i RANGE ${index} ${stop})
157+
list(POP_FRONT ARGN value)
158+
string(JSON json_content SET "${json_content}" ${key} ${i} "\"${value}\"")
159+
endforeach()
160+
endif()
118161
endif()
119162
endif()
120163
set(${var} "${json_content}" PARENT_SCOPE)
@@ -293,6 +336,7 @@ endfunction()
293336
# Usage:
294337
# yaml_set(NAME <name> KEY <key>... [GENEX] VALUE <value>)
295338
# yaml_set(NAME <name> KEY <key>... [APPEND] [GENEX] LIST <value>...)
339+
# yaml_set(NAME <name> KEY <key>... [APPEND] LIST MAP <map1> MAP <map2> MAP ...)
296340
#
297341
# Set a value or a list of values to given key.
298342
#
@@ -306,6 +350,14 @@ endfunction()
306350
# APPEND : Append the list of values to the list of values for the key.
307351
# GENEX : The value(s) contain generator expressions. When using this
308352
# option, also see the notes in the yaml_save() function.
353+
# MAP <map> : Map, with key-value pairs where key-value is separated by ':',
354+
# and pairs separated by ','.
355+
# Format example: "<key1>: <value1>, <key2>: <value2>, ..."
356+
# MAP can be given multiple times to separate maps when adding them to a list.
357+
# LIST MAP cannot be used with GENEX.
358+
#
359+
# Note: if a map value contains commas, ',', then the value string must be quoted in
360+
# single quotes and commas must be double escaped, like this: 'A \\,string'
309361
#
310362
function(yaml_set)
311363
cmake_parse_arguments(ARG_YAML "APPEND;GENEX" "NAME;VALUE" "KEY;LIST" ${ARGN})
@@ -328,6 +380,10 @@ function(yaml_set)
328380
message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}(APPEND ...) can only be used with argument: LIST")
329381
endif()
330382

383+
if(ARG_YAML_GENEX AND MAP IN_LIST ARG_YAML_LIST)
384+
message(FATAL_ERROR "${function}(GENEX ...) cannot be used with argument: LIST MAP")
385+
endif()
386+
331387
zephyr_get_scoped(json_content ${ARG_YAML_NAME} JSON)
332388
zephyr_get_scoped(genex ${ARG_YAML_NAME} GENEX)
333389

@@ -366,7 +422,6 @@ function(yaml_set)
366422
internal_yaml_list_initializer(json_string ${genex})
367423
string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} "${json_string}")
368424
endif()
369-
370425
internal_yaml_list_append(json_content ${genex} "${ARG_YAML_KEY}" ${ARG_YAML_LIST})
371426
else()
372427
string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} "\"${ARG_YAML_VALUE}\"")
@@ -518,7 +573,18 @@ function(to_yaml json level yaml genex)
518573
math(EXPR arraystop "${arraylength} - 1")
519574
foreach(i RANGE 0 ${arraystop})
520575
string(JSON item GET "${json}" ${member} ${i})
521-
set(${yaml} "${${yaml}}${indent_${level}} - ${item}\n")
576+
# Check the length of item. Only OBJECT and ARRAY may have length, so a length at this
577+
# level means `to_yaml()` should be called recursively.
578+
string(JSON length ERROR_VARIABLE ignore LENGTH "${item}")
579+
if(length)
580+
set(non_indent_yaml)
581+
to_yaml("${item}" 0 non_indent_yaml FALSE)
582+
string(REGEX REPLACE "\n$" "" non_indent_yaml "${non_indent_yaml}")
583+
string(REPLACE "\n" "\n${indent_${level}} " indent_yaml "${non_indent_yaml}")
584+
set(${yaml} "${${yaml}}${indent_${level}} - ${indent_yaml}\n")
585+
else()
586+
set(${yaml} "${${yaml}}${indent_${level}} - ${item}\n")
587+
endif()
522588
endforeach()
523589
endif()
524590
elseif(type STREQUAL STRING)

tests/cmake/yaml/CMakeLists.txt

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ function(test_reading_int)
9999
)
100100
endfunction()
101101

102+
function(test_reading_map_list_entry)
103+
set(expected_length 2)
104+
set(expected_name "MapEntry1")
105+
set(expected_int 5)
106+
yaml_length(actual_length NAME yaml-test KEY cmake test map-list)
107+
yaml_get(actual_name NAME yaml-test KEY cmake test map-list 0 map-entry-name)
108+
yaml_get(actual_int NAME yaml-test KEY cmake test map-list 0 map-entry-int)
109+
110+
test_assert(TEST ${expected_length} EQUAL ${actual_length}
111+
COMMENT "yaml key value does not match expectation."
112+
)
113+
test_assert(TEST ${expected_name} STREQUAL ${actual_name}
114+
COMMENT "yaml key value does not match expectation."
115+
)
116+
test_assert(TEST ${expected_int} EQUAL ${actual_int}
117+
COMMENT "yaml key value does not match expectation."
118+
)
119+
endfunction()
120+
102121
function(test_reading_not_found)
103122
set(expected cmake-missing-NOTFOUND)
104123
yaml_get(actual NAME yaml-test KEY cmake missing test key)
@@ -126,6 +145,15 @@ function(test_reading_not_array)
126145
)
127146
endfunction()
128147

148+
function(test_reading_not_found_map_list_entry)
149+
set(expected cmake-test-map-list-3-NOTFOUND)
150+
yaml_get(actual NAME yaml-test KEY cmake test map-list 3 map-entry-name)
151+
152+
test_assert(TEST ${expected} STREQUAL ${actual}
153+
COMMENT "Expected -NOTFOUND, but something was found."
154+
)
155+
endfunction()
156+
129157
function(test_save_new_file)
130158
yaml_save(NAME yaml-test FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_save.yaml)
131159

@@ -260,6 +288,137 @@ function(test_setting_list_int)
260288
endforeach()
261289
endfunction()
262290

291+
function(test_setting_map_list_entry)
292+
yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
293+
NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
294+
)
295+
296+
set(new_entry_name_0 MapEntryNew1)
297+
set(new_entry_int_0 42)
298+
set(new_entry_name_1 MapEntryNew2)
299+
set(new_entry_int_1 24)
300+
set(new_entry_name_2 MapEntryNew3)
301+
set(new_entry_int_2 4224)
302+
yaml_set(actual NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
303+
KEY cmake test set map-list LIST
304+
MAP "map-entry-name: ${new_entry_name_0}, map-entry-int: ${new_entry_int_0}"
305+
MAP "map-entry-name: ${new_entry_name_1}, map-entry-int: ${new_entry_int_1}"
306+
MAP "map-entry-name: ${new_entry_name_2}, map-entry-int: ${new_entry_int_2}"
307+
)
308+
309+
yaml_save(NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create)
310+
311+
# Read-back the yaml and verify the values.
312+
yaml_load(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
313+
NAME ${CMAKE_CURRENT_FUNCTION}_readback
314+
)
315+
316+
yaml_length(readback NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list)
317+
318+
test_assert(TEST 3 EQUAL ${readback}
319+
COMMENT "readback yaml list length does not match original."
320+
)
321+
322+
foreach(index 0 1 2)
323+
yaml_get(readback_name NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-name)
324+
yaml_get(readback_int NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-int)
325+
326+
test_assert(TEST "${readback_name}" STREQUAL "${new_entry_name_${index}}"
327+
COMMENT "list values mismatch."
328+
)
329+
test_assert(TEST "${readback_int}" EQUAL "${new_entry_int_${index}}"
330+
COMMENT "list values mismatch."
331+
)
332+
endforeach()
333+
endfunction()
334+
335+
function(test_setting_map_list_entry_windows)
336+
yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
337+
NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
338+
)
339+
340+
set(new_entry_name_0 MapEntryWindowsPath1)
341+
set(new_entry_path_0 "c:/tmp/zephyr")
342+
set(new_entry_name_1 MapEntryWindowsPath2)
343+
set(new_entry_path_1 "c:/program files/space")
344+
set(new_entry_name_2 MapEntryWindowsPath3)
345+
set(new_entry_path_2 "D:/alternative/drive")
346+
yaml_set(actual NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
347+
KEY cmake test set map-list LIST
348+
MAP "map-entry-name: ${new_entry_name_0}, map-entry-path: ${new_entry_path_0}"
349+
MAP "map-entry-name: ${new_entry_name_1}, map-entry-path: ${new_entry_path_1}"
350+
MAP "map-entry-name: ${new_entry_name_2}, map-entry-path: ${new_entry_path_2}"
351+
)
352+
353+
yaml_save(NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create)
354+
355+
# Read-back the yaml and verify the values.
356+
yaml_load(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
357+
NAME ${CMAKE_CURRENT_FUNCTION}_readback
358+
)
359+
360+
yaml_length(readback NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list)
361+
362+
test_assert(TEST 3 EQUAL ${readback}
363+
COMMENT "readback yaml list length does not match original."
364+
)
365+
366+
foreach(index 0 1 2)
367+
yaml_get(readback_name NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-name)
368+
yaml_get(readback_path NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-path)
369+
370+
test_assert(TEST "${readback_name}" STREQUAL "${new_entry_name_${index}}"
371+
COMMENT "list values mismatch."
372+
)
373+
test_assert(TEST "${readback_path}" STREQUAL "${new_entry_path_${index}}"
374+
COMMENT "list values mismatch."
375+
)
376+
endforeach()
377+
endfunction()
378+
379+
function(test_setting_map_list_entry_commas)
380+
yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
381+
NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
382+
)
383+
384+
set(new_entry_name_0 TestString1)
385+
set(new_entry_str_0 "'A\\,string'")
386+
set(new_entry_name_1 TestString2)
387+
set(new_entry_str_1 "'\\, is first'")
388+
set(new_entry_name_2 TestString3)
389+
set(new_entry_str_2 "'\\, and : is\\,everywhere\\,'")
390+
yaml_set(actual NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
391+
KEY cmake test set map-list LIST
392+
MAP "map-entry-name: ${new_entry_name_0}, map-entry-str: ${new_entry_str_0}"
393+
MAP "map-entry-name: ${new_entry_name_1}, map-entry-str: ${new_entry_str_1}"
394+
MAP "map-entry-name: ${new_entry_name_2}, map-entry-str: ${new_entry_str_2}"
395+
)
396+
397+
yaml_save(NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create)
398+
399+
# Read-back the yaml and verify the values.
400+
yaml_load(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
401+
NAME ${CMAKE_CURRENT_FUNCTION}_readback
402+
)
403+
404+
yaml_length(readback NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list)
405+
406+
test_assert(TEST 3 EQUAL ${readback}
407+
COMMENT "readback yaml list length does not match original."
408+
)
409+
410+
foreach(index 0 1 2)
411+
yaml_get(readback_name NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-name)
412+
yaml_get(readback_str NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-str)
413+
414+
test_assert(TEST "${readback_name}" STREQUAL "${new_entry_name_${index}}"
415+
COMMENT "list values mismatch."
416+
)
417+
test_assert(TEST "'${readback_str}'" STREQUAL "${new_entry_str_${index}}"
418+
COMMENT "list values mismatch."
419+
)
420+
endforeach()
421+
endfunction()
263422
function(test_setting_empty_value)
264423
yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml
265424
NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create
@@ -384,16 +543,21 @@ test_reading_string()
384543
test_reading_int()
385544
test_reading_list_strings()
386545
test_reading_list_int()
546+
test_reading_map_list_entry()
387547
test_reading_not_found()
388548
test_reading_not_found_array()
389549
test_reading_not_array()
550+
test_reading_not_found_map_list_entry()
390551

391552
test_save_new_file()
392553

393554
test_setting_int()
394555
test_setting_string()
395556
test_setting_list_strings()
396557
test_setting_list_int()
558+
test_setting_map_list_entry()
559+
test_setting_map_list_entry_windows()
560+
test_setting_map_list_entry_commas()
397561

398562
test_setting_empty_value()
399563
test_setting_empty_list()

tests/cmake/yaml/test.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ cmake:
1111
- "list"
1212
- "of"
1313
- "strings"
14+
map-list:
15+
- map-entry-name: "MapEntry1"
16+
map-entry-int: 5
17+
- map-entry-name: "MapEntry2"
18+
map-entry-int: 4

0 commit comments

Comments
 (0)