diff --git a/cmake/modules/boards.cmake b/cmake/modules/boards.cmake index 2b78845482a2..3f086dca9480 100644 --- a/cmake/modules/boards.cmake +++ b/cmake/modules/boards.cmake @@ -357,3 +357,4 @@ build_info(board name VALUE ${BOARD}) string(REGEX REPLACE "^/" "" qualifiers "${BOARD_QUALIFIERS}") build_info(board qualifiers VALUE ${qualifiers}) build_info(board revision VALUE ${BOARD_REVISION}) +build_info(board path PATH ${BOARD_DIRECTORIES}) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 6ad4a169567c..a7c41e72be0d 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -3060,6 +3060,16 @@ endfunction() # This function extends the CMake string function by providing additional # manipulation arguments to CMake string. # +# ESCAPE: Ensure that any single '\', except '\"', in the input string is +# escaped with the escape char '\'. For example the string 'foo\bar' +# will be escaped so that it becomes 'foo\\bar'. +# Backslashes which are already escaped will not be escaped further, +# for example 'foo\\bar' will not be modified. +# This is useful for handling of windows path separator in strings or +# when strings contains newline escapes such as '\n' and this can +# cause issues when writing to a file where a '\n' is desired in the +# string instead of a newline. +# # SANITIZE: Ensure that the output string does not contain any special # characters. Special characters, such as -, +, =, $, etc. are # converted to underscores '_'. @@ -3071,9 +3081,11 @@ endfunction() # # returns the updated string function(zephyr_string) - set(options SANITIZE TOUPPER) + set(options SANITIZE TOUPPER ESCAPE) cmake_parse_arguments(ZEPHYR_STRING "${options}" "" "" ${ARGN}) + zephyr_check_flags_exclusive(${CMAKE_CURRENT_FUNCTION} ZEPHYR_STRING SANITIZE ESCAPE) + if (NOT ZEPHYR_STRING_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Function zephyr_string() called without a return variable") endif() @@ -3091,6 +3103,13 @@ function(zephyr_string) string(TOUPPER ${work_string} work_string) endif() + if(ZEPHYR_STRING_ESCAPE) + # If a single '\' is discovered, such as 'foo\bar', then it must be escaped like: 'foo\\bar' + # \\1 and \\2 are keeping the match patterns, the \\\\ --> \\ meaning an escaped '\', + # which then becomes a single '\' in the final string. + string(REGEX REPLACE "([^\\][\\])([^\\\"])" "\\1\\\\\\2" work_string "${ZEPHYR_STRING_UNPARSED_ARGUMENTS}") + endif() + set(${return_arg} ${work_string} PARENT_SCOPE) endfunction() diff --git a/cmake/modules/yaml.cmake b/cmake/modules/yaml.cmake index 9ab9b333fa46..a509567bc5db 100644 --- a/cmake/modules/yaml.cmake +++ b/cmake/modules/yaml.cmake @@ -19,6 +19,13 @@ # - foo2 # - foo3 # +# Support for list of maps, like: +# foo: +# - bar: val1 +# baz: val1 +# - bar: val2 +# baz: val2 +# # All of above can be combined, for example like: # foo: # bar: baz @@ -28,14 +35,6 @@ # - beta # - gamma # fred: thud -# -# Support for list of objects are currently experimental and not guranteed to work. -# For example: -# foo: -# - bar: val1 -# baz: val1 -# - bar: val2 -# baz: val2 include_guard(GLOBAL) @@ -72,6 +71,98 @@ function(internal_yaml_context_free) endif() endfunction() +# Internal helper function to provide the correct initializer for a list in the +# JSON content. +function(internal_yaml_list_initializer var genex) + if(genex) + set(${var} "\"@YAML-LIST@\"" PARENT_SCOPE) + else() + set(${var} "[]" PARENT_SCOPE) + endif() +endfunction() + +# Internal helper function to append items to a list in the JSON content. +# Unassigned arguments are the values to be appended. +function(internal_yaml_list_append var genex key) + set(json_content "${${var}}") + string(JSON subjson GET "${json_content}" ${key}) + if(genex) + # new lists are stored in CMake string format, but those imported via + # yaml_load() are proper JSON arrays. When an append is requested, those + # must be converted back to a CMake list. + string(JSON type TYPE "${json_content}" ${key}) + if(type STREQUAL ARRAY) + string(JSON arraylength LENGTH "${subjson}") + internal_yaml_list_initializer(subjson TRUE) + if(${arraylength} GREATER 0) + math(EXPR arraystop "${arraylength} - 1") + list(GET ARG_YAML_LIST 0 entry_0) + if(entry_0 STREQUAL MAP) + message(FATAL_ERROR "${function}(GENEX ${argument} ) is not valid at this position.\n" + "Syntax is 'LIST MAP \"key1: value1.1, ...\" MAP \"key1: value1.2, ...\"" + ) + endif() + + foreach(i RANGE 0 ${arraystop}) + string(JSON item GET "${json_content}" ${key} ${i}) + list(APPEND subjson ${item}) + endforeach() + endif() + endif() + list(APPEND subjson ${ARGN}) + string(JSON json_content SET "${json_content}" ${key} "\"${subjson}\"") + else() + # lists are stored as JSON arrays + string(JSON index LENGTH "${subjson}") + list(LENGTH ARGN length) + if(NOT length EQUAL 0) + list(GET ARG_YAML_LIST 0 entry_0) + if(entry_0 STREQUAL MAP) + math(EXPR length "${length} / 2") + math(EXPR stop "${index} + ${length} - 1") + foreach(i RANGE ${index} ${stop}) + list(POP_FRONT ARG_YAML_LIST argument) + if(NOT argument STREQUAL MAP) + message(FATAL_ERROR "yaml_set(${argument} ) is not valid at this position.\n" + "Syntax is 'LIST MAP \"key1: value1.1, ...\" MAP \"key1: value1.2, ...\"" + ) + endif() + list(POP_FRONT ARG_YAML_LIST map_value) + string(REGEX REPLACE "([^\\])," "\\1;" pair_list "${map_value}") + set(qouted_map_value) + foreach(pair ${pair_list}) + if(NOT pair MATCHES "[^ ]*:[^ ]*") + message(FATAL_ERROR "yaml_set(MAP ${map_value} ) is malformed.\n" + "Syntax is 'LIST MAP \"key1: value1.1, ...\" MAP \"key1: value1.2, ...\"\n" + "If value contains comma ',' then ensure the value field is properly qouted " + "and escaped" + ) + endif() + string(REGEX MATCH "^[^:]*" map_key "${pair}") + string(REGEX REPLACE "^${map_key}:[ ]*" "" value "${pair}") + string(STRIP "${map_key}" map_key) + if(value MATCHES "," AND NOT (value MATCHES "\\\\," AND value MATCHES "'.*'")) + message(FATAL_ERROR "value: ${value} is not properly quoted") + endif() + string(REGEX REPLACE "\\\\," "," value "${value}") + list(APPEND qouted_map_value "\"${map_key}\": \"${value}\"") + endforeach() + list(JOIN qouted_map_value "," qouted_map_value) + string(JSON json_content SET "${json_content}" ${key} ${i} "{${qouted_map_value}}") + endforeach() + else() + math(EXPR stop "${index} + ${length} - 1") + list(GET ARG_YAML_LIST 0 entry_0) + foreach(i RANGE ${index} ${stop}) + list(POP_FRONT ARGN value) + string(JSON json_content SET "${json_content}" ${key} ${i} "\"${value}\"") + endforeach() + endif() + endif() + endif() + set(${var} "${json_content}" PARENT_SCOPE) +endfunction() + # Usage # yaml_context(EXISTS NAME ) # @@ -125,6 +216,7 @@ function(yaml_create) if(DEFINED ARG_YAML_FILE) zephyr_set(FILE ${ARG_YAML_FILE} SCOPE ${ARG_YAML_NAME}) endif() + zephyr_set(GENEX FALSE SCOPE ${ARG_YAML_NAME}) zephyr_set(JSON "{}" SCOPE ${ARG_YAML_NAME}) endfunction() @@ -149,7 +241,7 @@ function(yaml_load) zephyr_set(FILE ${ARG_YAML_FILE} SCOPE ${ARG_YAML_NAME}) execute_process(COMMAND ${PYTHON_EXECUTABLE} -c - "import json; import yaml; print(json.dumps(yaml.safe_load(open('${ARG_YAML_FILE}'))))" + "import json; import yaml; print(json.dumps(yaml.safe_load(open('${ARG_YAML_FILE}')) or {}))" OUTPUT_VARIABLE json_load_out ERROR_VARIABLE json_load_error RESULT_VARIABLE json_load_result @@ -161,6 +253,7 @@ function(yaml_load) ) endif() + zephyr_set(GENEX FALSE SCOPE ${ARG_YAML_NAME}) zephyr_set(JSON "${json_load_out}" SCOPE ${ARG_YAML_NAME}) endfunction() @@ -241,8 +334,9 @@ function(yaml_length out_var) endfunction() # Usage: -# yaml_set(NAME KEY ... VALUE ) -# yaml_set(NAME KEY ... [APPEND] LIST ...) +# yaml_set(NAME KEY ... [GENEX] VALUE ) +# yaml_set(NAME KEY ... [APPEND] [GENEX] LIST ...) +# yaml_set(NAME KEY ... [APPEND] LIST MAP MAP MAP ...) # # Set a value or a list of values to given key. # @@ -252,18 +346,46 @@ endfunction() # NAME : Name of the YAML context. # KEY ... : Name of key. # VALUE : New value for the key. -# List : New list of values for the key. +# LIST : New list of values for the key. # APPEND : Append the list of values to the list of values for the key. +# GENEX : The value(s) contain generator expressions. When using this +# option, also see the notes in the yaml_save() function. +# MAP : Map, with key-value pairs where key-value is separated by ':', +# and pairs separated by ','. +# Format example: ": , : , ..." +# MAP can be given multiple times to separate maps when adding them to a list. +# LIST MAP cannot be used with GENEX. +# +# Note: if a map value contains commas, ',', then the value string must be quoted in +# single quotes and commas must be double escaped, like this: 'A \\,string' # function(yaml_set) - cmake_parse_arguments(ARG_YAML "APPEND" "NAME;VALUE" "KEY;LIST" ${ARGN}) + cmake_parse_arguments(ARG_YAML "APPEND;GENEX" "NAME;VALUE" "KEY;LIST" ${ARGN}) zephyr_check_arguments_required_all(${CMAKE_CURRENT_FUNCTION} ARG_YAML NAME KEY) zephyr_check_arguments_required_allow_empty(${CMAKE_CURRENT_FUNCTION} ARG_YAML VALUE LIST) zephyr_check_arguments_exclusive(${CMAKE_CURRENT_FUNCTION} ARG_YAML VALUE LIST) internal_yaml_context_required(NAME ${ARG_YAML_NAME}) + if(ARG_YAML_GENEX) + zephyr_set(GENEX TRUE SCOPE ${ARG_YAML_NAME}) + endif() + + if(DEFINED ARG_YAML_LIST + OR LIST IN_LIST ARG_YAML_KEYWORDS_MISSING_VALUES) + set(key_is_list TRUE) + endif() + + if(ARG_YAML_APPEND AND NOT key_is_list) + message(FATAL_ERROR "${CMAKE_CURRENT_FUNCTION}(APPEND ...) can only be used with argument: LIST") + endif() + + if(ARG_YAML_GENEX AND MAP IN_LIST ARG_YAML_LIST) + message(FATAL_ERROR "${function}(GENEX ...) cannot be used with argument: LIST MAP") + endif() + zephyr_get_scoped(json_content ${ARG_YAML_NAME} JSON) + zephyr_get_scoped(genex ${ARG_YAML_NAME} GENEX) set(yaml_key_undefined ${ARG_YAML_KEY}) foreach(k ${yaml_key_undefined}) @@ -281,8 +403,8 @@ function(yaml_set) list(REVERSE yaml_key_undefined) if(NOT "${yaml_key_undefined}" STREQUAL "") - if(ARG_YAML_APPEND) - set(json_string "[]") + if(key_is_list) + internal_yaml_list_initializer(json_string ${genex}) else() set(json_string "\"\"") endif() @@ -295,23 +417,16 @@ function(yaml_set) ) endif() - if(DEFINED ARG_YAML_LIST OR LIST IN_LIST ARG_YAML_KEYWORDS_MISSING_VALUES) + if(key_is_list) if(NOT ARG_YAML_APPEND) - string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} "[]") - endif() - - string(JSON subjson GET "${json_content}" ${ARG_YAML_KEY}) - string(JSON index LENGTH "${subjson}") - list(LENGTH ARG_YAML_LIST length) - math(EXPR stop "${index} + ${length} - 1") - if(NOT length EQUAL 0) - foreach(i RANGE ${index} ${stop}) - list(POP_FRONT ARG_YAML_LIST value) - string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} ${i} "\"${value}\"") - endforeach() + internal_yaml_list_initializer(json_string ${genex}) + string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} "${json_string}") endif() + zephyr_string(ESCAPE escape_list "${ARG_YAML_LIST}") + internal_yaml_list_append(json_content ${genex} "${ARG_YAML_KEY}" ${escape_list}) else() - string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} "\"${ARG_YAML_VALUE}\"") + zephyr_string(ESCAPE escape_value "${ARG_YAML_VALUE}") + string(JSON json_content SET "${json_content}" ${ARG_YAML_KEY} "\"${escape_value}\"") endif() zephyr_set(JSON "${json_content}" SCOPE ${ARG_YAML_NAME}) @@ -345,8 +460,12 @@ endfunction() # Usage: # yaml_save(NAME [FILE ]) # -# Write the YAML context to the file which were given with the earlier -# 'yaml_load()' or 'yaml_create()' call. +# Write the YAML context to , or the one given with the earlier +# 'yaml_load()' or 'yaml_create()' call. This will be performed immediately if +# the context does not use generator expressions; otherwise, keys that include +# a generator expression will initially be written as comments, and the full +# contents will be available at build time. Build steps that depend on the file +# being complete must depend on the '_yaml_saved' target. # # NAME : Name of the YAML context # FILE : Path to file to write the context. @@ -364,22 +483,66 @@ function(yaml_save) if(NOT yaml_file) zephyr_check_arguments_required(${CMAKE_CURRENT_FUNCTION} ARG_YAML FILE) endif() - - zephyr_get_scoped(json_content ${ARG_YAML_NAME} JSON) - to_yaml("${json_content}" 0 yaml_out) - if(DEFINED ARG_YAML_FILE) set(yaml_file ${ARG_YAML_FILE}) else() zephyr_get_scoped(yaml_file ${ARG_YAML_NAME} FILE) endif() + + zephyr_get_scoped(genex ${ARG_YAML_NAME} GENEX) + zephyr_get_scoped(json_content ${ARG_YAML_NAME} JSON) + to_yaml("${json_content}" 0 yaml_out ${genex}) + if(EXISTS ${yaml_file}) FILE(RENAME ${yaml_file} ${yaml_file}.bak) endif() FILE(WRITE ${yaml_file} "${yaml_out}") + + set(save_target ${ARG_YAML_NAME}_yaml_saved) + if (NOT TARGET ${save_target}) + # Create a target for the completion of the YAML save operation. + # This will be a dummy unless genexes are used. + add_custom_target(${save_target} ALL DEPENDS ${yaml_file}) + set_target_properties(${save_target} PROPERTIES + genex_save_count 0 + temp_files "" + ) + endif() + + if (genex) + get_property(genex_save_count TARGET ${save_target} PROPERTY genex_save_count) + if (${genex_save_count} EQUAL 0) + # First yaml_save() for this context with genexes enabled + add_custom_command( + OUTPUT ${yaml_file} + DEPENDS $ + COMMAND ${CMAKE_COMMAND} + -DJSON_FILE="$" + -DYAML_FILE="${yaml_file}" + -DTEMP_FILES="$" + -P ${ZEPHYR_BASE}/cmake/yaml-filter.cmake + ) + endif() + + math(EXPR genex_save_count "${genex_save_count} + 1") + set_property(TARGET ${save_target} PROPERTY genex_save_count ${genex_save_count}) + + cmake_path(SET yaml_path "${yaml_file}") + cmake_path(GET yaml_path STEM yaml_file_no_ext) + set(json_file ${yaml_file_no_ext}_${genex_save_count}.json) + set_property(TARGET ${save_target} PROPERTY json_file ${json_file}) + + # comment this to keep the temporary JSON files + set_property(TARGET ${save_target} APPEND PROPERTY temp_files ${json_file}) + + FILE(GENERATE OUTPUT ${json_file} + CONTENT "${json_content}" + ) + endif() endfunction() -function(to_yaml json level yaml) +function(to_yaml in_json level yaml genex) + zephyr_string(ESCAPE json "${in_json}") if(level GREATER 0) math(EXPR level_dec "${level} - 1") set(indent_${level} "${indent_${level_dec}} ") @@ -398,10 +561,12 @@ function(to_yaml json level yaml) string(JSON type TYPE "${json}" ${member}) string(JSON subjson GET "${json}" ${member}) if(type STREQUAL OBJECT) + # JSON object -> YAML dictionary set(${yaml} "${${yaml}}${indent_${level}}${member}:\n") math(EXPR sublevel "${level} + 1") - to_yaml("${subjson}" ${sublevel} ${yaml}) + to_yaml("${subjson}" ${sublevel} ${yaml} ${genex}) elseif(type STREQUAL ARRAY) + # JSON array -> YAML list set(${yaml} "${${yaml}}${indent_${level}}${member}:") string(JSON arraylength LENGTH "${subjson}") if(${arraylength} LESS 1) @@ -411,10 +576,47 @@ function(to_yaml json level yaml) math(EXPR arraystop "${arraylength} - 1") foreach(i RANGE 0 ${arraystop}) string(JSON item GET "${json}" ${member} ${i}) - set(${yaml} "${${yaml}}${indent_${level}} - ${item}\n") + # Check the length of item. Only OBJECT and ARRAY may have length, so a length at this + # level means `to_yaml()` should be called recursively. + string(JSON length ERROR_VARIABLE ignore LENGTH "${item}") + if(length) + set(non_indent_yaml) + to_yaml("${item}" 0 non_indent_yaml FALSE) + string(REGEX REPLACE "\n$" "" non_indent_yaml "${non_indent_yaml}") + string(REPLACE "\n" "\n${indent_${level}} " indent_yaml "${non_indent_yaml}") + set(${yaml} "${${yaml}}${indent_${level}} - ${indent_yaml}\n") + else() + set(${yaml} "${${yaml}}${indent_${level}} - ${item}\n") + endif() endforeach() endif() + elseif(type STREQUAL STRING) + # JSON string maps to multiple YAML types: + # - with unexpanded generator expressions: save as YAML comment + # - if it matches the special prefix: convert to YAML list + # - otherwise: save as YAML scalar + if (subjson MATCHES "\\$<.*>" AND ${genex}) + # Yet unexpanded generator expression: save as comment + string(SUBSTRING ${indent_${level}} 1 -1 short_indent) + set(${yaml} "${${yaml}}#${short_indent}${member}: ${subjson}\n") + elseif(subjson MATCHES "^@YAML-LIST@") + # List-as-string: convert to list + set(${yaml} "${${yaml}}${indent_${level}}${member}:") + list(POP_FRONT subjson) + if(subjson STREQUAL "") + set(${yaml} "${${yaml}} []\n") + else() + set(${yaml} "${${yaml}}\n") + foreach(item ${subjson}) + set(${yaml} "${${yaml}}${indent_${level}} - ${item}\n") + endforeach() + endif() + else() + # Raw strings: save as is + set(${yaml} "${${yaml}}${indent_${level}}${member}: ${subjson}\n") + endif() else() + # Other JSON data type -> YAML scalar, as-is set(${yaml} "${${yaml}}${indent_${level}}${member}: ${subjson}\n") endif() endforeach() diff --git a/cmake/yaml-filter.cmake b/cmake/yaml-filter.cmake new file mode 100644 index 000000000000..c7733be6485f --- /dev/null +++ b/cmake/yaml-filter.cmake @@ -0,0 +1,35 @@ +# Copyright (c) 2024 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +# Simple second stage filter for YAML generation, used when generator +# expressions have been used for some of the data and the conversion to +# YAML needs to happen after cmake has completed processing. +# +# This scripts expects as input: +# - JSON_FILE: the name of the input file, in JSON format, that contains +# the expanded generator expressions. +# - YAML_FILE: the name of the final output YAML file. +# - TEMP_FILES: a list of temporary files that need to be removed after +# the conversion is done. +# +# This script loads the Zephyr yaml module and reuses its `to_yaml()` +# function to convert the fully expanded JSON content to YAML, taking +# into account the special format that was used to store lists. +# Temporary files are then removed. + +cmake_minimum_required(VERSION 3.20.0) + +set(ZEPHYR_BASE ${CMAKE_CURRENT_LIST_DIR}/../) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules") +include(yaml) + +file(READ ${JSON_FILE} json_content) +to_yaml("${json_content}" 0 yaml_out TRUE) +file(WRITE ${YAML_FILE} "${yaml_out}") + +# Remove unused temporary files. JSON_FILE needs to be kept, or the +# build system will complain there is no rule to rebuild it +list(REMOVE_ITEM TEMP_FILES ${JSON_FILE}) +foreach(file ${TEMP_FILES}) + file(REMOVE ${file}) +endforeach() diff --git a/scripts/schemas/build-schema.yml b/scripts/schemas/build-schema.yml index 5086afbb25b1..93e78d5bc6b2 100644 --- a/scripts/schemas/build-schema.yml +++ b/scripts/schemas/build-schema.yml @@ -56,6 +56,17 @@ mapping: type: seq sequence: - type: str + images: + type: seq + sequence: + - type: map + mapping: + name: + type: str + source-dir: + type: str + type: + type: str kconfig: type: map mapping: diff --git a/share/sysbuild/cmake/modules/sysbuild_images.cmake b/share/sysbuild/cmake/modules/sysbuild_images.cmake index eb73ec994483..7115567b8d95 100644 --- a/share/sysbuild/cmake/modules/sysbuild_images.cmake +++ b/share/sysbuild/cmake/modules/sysbuild_images.cmake @@ -5,6 +5,26 @@ # This module is responsible for including images into sysbuild and to call # pre and post hooks. +# Internal function to update build info with list of images abnd write the file. +# Main reason for using an internal function is to properly scope variable usage. +# Function takes a list of images. +function(sysbuild_info_image images) + set(info_image_list) + foreach(image ${images}) + ExternalProject_Get_Property(${image} SOURCE_DIR) + get_target_property(type ${image} APP_TYPE) + if(type) + list(APPEND info_image_list MAP "name: ${image}, source-dir: ${SOURCE_DIR}, type: ${type}") + else() + list(APPEND info_image_list MAP "name: ${image}, source-dir: ${source_dir}") + endif() + endforeach() + build_info(images VALUE ${info_image_list}) + # Save current state of build info. This allow external tools to fetch sysbuild controlled images + # even in the event that one or more image fails configuration stage. + yaml_save(NAME build_info) +endfunction() + get_filename_component(APP_DIR ${APP_DIR} ABSOLUTE) get_filename_component(app_name ${APP_DIR} NAME) set(DEFAULT_IMAGE "${app_name}") @@ -15,6 +35,9 @@ sysbuild_add_subdirectory(${sysbuild_toplevel_SOURCE_DIR}/images sysbuild/images get_property(IMAGES GLOBAL PROPERTY sysbuild_images) sysbuild_module_call(PRE_CMAKE MODULES ${SYSBUILD_MODULE_NAMES} IMAGES ${IMAGES}) sysbuild_images_order(IMAGES_CONFIGURATION_ORDER CONFIGURE IMAGES ${IMAGES}) + +sysbuild_info_image("${IMAGES}") + foreach(image ${IMAGES_CONFIGURATION_ORDER}) sysbuild_module_call(PRE_IMAGE_CMAKE MODULES ${SYSBUILD_MODULE_NAMES} IMAGES ${IMAGES} IMAGE ${image}) ExternalZephyrProject_Cmake(APPLICATION ${image}) diff --git a/tests/cmake/yaml/CMakeLists.txt b/tests/cmake/yaml/CMakeLists.txt index 04873efa9a9e..97cf10ecefc5 100644 --- a/tests/cmake/yaml/CMakeLists.txt +++ b/tests/cmake/yaml/CMakeLists.txt @@ -99,6 +99,25 @@ function(test_reading_int) ) endfunction() +function(test_reading_map_list_entry) + set(expected_length 2) + set(expected_name "MapEntry1") + set(expected_int 5) + yaml_length(actual_length NAME yaml-test KEY cmake test map-list) + yaml_get(actual_name NAME yaml-test KEY cmake test map-list 0 map-entry-name) + yaml_get(actual_int NAME yaml-test KEY cmake test map-list 0 map-entry-int) + + test_assert(TEST ${expected_length} EQUAL ${actual_length} + COMMENT "yaml key value does not match expectation." + ) + test_assert(TEST ${expected_name} STREQUAL ${actual_name} + COMMENT "yaml key value does not match expectation." + ) + test_assert(TEST ${expected_int} EQUAL ${actual_int} + COMMENT "yaml key value does not match expectation." + ) +endfunction() + function(test_reading_not_found) set(expected cmake-missing-NOTFOUND) yaml_get(actual NAME yaml-test KEY cmake missing test key) @@ -126,6 +145,15 @@ function(test_reading_not_array) ) endfunction() +function(test_reading_not_found_map_list_entry) + set(expected cmake-test-map-list-3-NOTFOUND) + yaml_get(actual NAME yaml-test KEY cmake test map-list 3 map-entry-name) + + test_assert(TEST ${expected} STREQUAL ${actual} + COMMENT "Expected -NOTFOUND, but something was found." + ) +endfunction() + function(test_save_new_file) yaml_save(NAME yaml-test FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_save.yaml) @@ -260,6 +288,137 @@ function(test_setting_list_int) endforeach() endfunction() +function(test_setting_map_list_entry) + yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml + NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create + ) + + set(new_entry_name_0 MapEntryNew1) + set(new_entry_int_0 42) + set(new_entry_name_1 MapEntryNew2) + set(new_entry_int_1 24) + set(new_entry_name_2 MapEntryNew3) + set(new_entry_int_2 4224) + yaml_set(actual NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create + KEY cmake test set map-list LIST + MAP "map-entry-name: ${new_entry_name_0}, map-entry-int: ${new_entry_int_0}" + MAP "map-entry-name: ${new_entry_name_1}, map-entry-int: ${new_entry_int_1}" + MAP "map-entry-name: ${new_entry_name_2}, map-entry-int: ${new_entry_int_2}" + ) + + yaml_save(NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create) + + # Read-back the yaml and verify the values. + yaml_load(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml + NAME ${CMAKE_CURRENT_FUNCTION}_readback + ) + + yaml_length(readback NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list) + + test_assert(TEST 3 EQUAL ${readback} + COMMENT "readback yaml list length does not match original." + ) + + foreach(index 0 1 2) + yaml_get(readback_name NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-name) + yaml_get(readback_int NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-int) + + test_assert(TEST "${readback_name}" STREQUAL "${new_entry_name_${index}}" + COMMENT "list values mismatch." + ) + test_assert(TEST "${readback_int}" EQUAL "${new_entry_int_${index}}" + COMMENT "list values mismatch." + ) + endforeach() +endfunction() + +function(test_setting_map_list_entry_windows) + yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml + NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create + ) + + set(new_entry_name_0 MapEntryWindowsPath1) + set(new_entry_path_0 "c:/tmp/zephyr") + set(new_entry_name_1 MapEntryWindowsPath2) + set(new_entry_path_1 "c:/program files/space") + set(new_entry_name_2 MapEntryWindowsPath3) + set(new_entry_path_2 "D:/alternative/drive") + yaml_set(actual NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create + KEY cmake test set map-list LIST + MAP "map-entry-name: ${new_entry_name_0}, map-entry-path: ${new_entry_path_0}" + MAP "map-entry-name: ${new_entry_name_1}, map-entry-path: ${new_entry_path_1}" + MAP "map-entry-name: ${new_entry_name_2}, map-entry-path: ${new_entry_path_2}" + ) + + yaml_save(NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create) + + # Read-back the yaml and verify the values. + yaml_load(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml + NAME ${CMAKE_CURRENT_FUNCTION}_readback + ) + + yaml_length(readback NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list) + + test_assert(TEST 3 EQUAL ${readback} + COMMENT "readback yaml list length does not match original." + ) + + foreach(index 0 1 2) + yaml_get(readback_name NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-name) + yaml_get(readback_path NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-path) + + test_assert(TEST "${readback_name}" STREQUAL "${new_entry_name_${index}}" + COMMENT "list values mismatch." + ) + test_assert(TEST "${readback_path}" STREQUAL "${new_entry_path_${index}}" + COMMENT "list values mismatch." + ) + endforeach() +endfunction() + +function(test_setting_map_list_entry_commas) + yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml + NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create + ) + + set(new_entry_name_0 TestString1) + set(new_entry_str_0 "'A\\,string'") + set(new_entry_name_1 TestString2) + set(new_entry_str_1 "'\\, is first'") + set(new_entry_name_2 TestString3) + set(new_entry_str_2 "'\\, and : is\\,everywhere\\,'") + yaml_set(actual NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create + KEY cmake test set map-list LIST + MAP "map-entry-name: ${new_entry_name_0}, map-entry-str: ${new_entry_str_0}" + MAP "map-entry-name: ${new_entry_name_1}, map-entry-str: ${new_entry_str_1}" + MAP "map-entry-name: ${new_entry_name_2}, map-entry-str: ${new_entry_str_2}" + ) + + yaml_save(NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create) + + # Read-back the yaml and verify the values. + yaml_load(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml + NAME ${CMAKE_CURRENT_FUNCTION}_readback + ) + + yaml_length(readback NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list) + + test_assert(TEST 3 EQUAL ${readback} + COMMENT "readback yaml list length does not match original." + ) + + foreach(index 0 1 2) + yaml_get(readback_name NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-name) + yaml_get(readback_str NAME ${CMAKE_CURRENT_FUNCTION}_readback KEY cmake test set map-list ${index} map-entry-str) + + test_assert(TEST "${readback_name}" STREQUAL "${new_entry_name_${index}}" + COMMENT "list values mismatch." + ) + test_assert(TEST "'${readback_str}'" STREQUAL "${new_entry_str_${index}}" + COMMENT "list values mismatch." + ) + endforeach() +endfunction() function(test_setting_empty_value) yaml_create(FILE ${CMAKE_BINARY_DIR}/${CMAKE_CURRENT_FUNCTION}_test_create.yaml NAME ${CMAKE_CURRENT_FUNCTION}_yaml-create @@ -384,9 +543,11 @@ test_reading_string() test_reading_int() test_reading_list_strings() test_reading_list_int() +test_reading_map_list_entry() test_reading_not_found() test_reading_not_found_array() test_reading_not_array() +test_reading_not_found_map_list_entry() test_save_new_file() @@ -394,6 +555,9 @@ test_setting_int() test_setting_string() test_setting_list_strings() test_setting_list_int() +test_setting_map_list_entry() +test_setting_map_list_entry_windows() +test_setting_map_list_entry_commas() test_setting_empty_value() test_setting_empty_list() diff --git a/tests/cmake/yaml/test.yaml b/tests/cmake/yaml/test.yaml index 4f21cff2c2a2..f106a360ed88 100644 --- a/tests/cmake/yaml/test.yaml +++ b/tests/cmake/yaml/test.yaml @@ -11,3 +11,8 @@ cmake: - "list" - "of" - "strings" + map-list: + - map-entry-name: "MapEntry1" + map-entry-int: 5 + - map-entry-name: "MapEntry2" + map-entry-int: 4