Gyorgy Szing | 5e429cb | 2019-12-03 20:39:55 +0100 | [diff] [blame] | 1 | #------------------------------------------------------------------------------- |
| 2 | # Copyright (c) 2019-2020, Arm Limited and Contributors. All rights reserved. |
| 3 | # |
| 4 | # SPDX-License-Identifier: BSD-3-Clause |
| 5 | # |
| 6 | #------------------------------------------------------------------------------- |
| 7 | |
| 8 | #[===[.rst: |
| 9 | Map utility file |
| 10 | ---------------- |
| 11 | Map is a key-value pair storage (also known as associative array) |
| 12 | implementation. It can hold multiple key-value pairs, the keys have to be unique |
| 13 | in a map. Trying to add a new pair whose key already exists in the map will |
| 14 | cause an error. Keys and values have a one-to-one relation, i.e. a key has |
| 15 | exactly one value associated to it, which cannot be empty. If an empty/null |
| 16 | value is needed, use a single space character instead. The value associated to a |
| 17 | key cannot be overwritten, however it is possible to remove a key-value pair |
| 18 | from the map then add the same key again with a new value. |
| 19 | |
| 20 | The main purpose of this utility is to store configuration data for the project, |
| 21 | i.e. build options, compiler flags, etc. |
| 22 | |
| 23 | Internally the utility uses global properties to store the data. The global |
| 24 | property ``MAPS.<name of map>`` is created as an indicator that the map is |
| 25 | defined, while global properties ``MAPS.<name of map>.KEYS`` and ``MAPS.<name of |
| 26 | map>.VALS`` hold the corresponding data as lists. A value for a key is |
| 27 | identified by having the same index in the VALS lists, as the key has in the |
| 28 | KEYS list. |
| 29 | |
| 30 | .. todo:: Investigate alternatives to global properties (name collision possible). |
| 31 | |
| 32 | #]===] |
| 33 | |
| 34 | include_guard(DIRECTORY) |
| 35 | include(Common/Utils) |
| 36 | |
| 37 | #[===[.rst: |
| 38 | .. cmake:command:: map_new |
| 39 | |
| 40 | .. code-block:: cmake |
| 41 | |
| 42 | map_new(NAME foo) |
| 43 | |
| 44 | Create a new named set of key-value pairs. |
| 45 | |
| 46 | Inputs: |
| 47 | |
| 48 | ``NAME`` |
| 49 | Name of the new map, use |C identifier like string|. The name must be unique |
| 50 | within the global namespace, otherwise an error is generated. |
| 51 | |
| 52 | #]===] |
| 53 | function(map_new) |
| 54 | set(_OPTIONS_ARGS) |
| 55 | set(_ONE_VALUE_ARGS NAME) |
| 56 | set(_MULTI_VALUE_ARGS) |
| 57 | cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) |
| 58 | |
| 59 | check_args(map_new NAME) |
| 60 | |
| 61 | get_property(is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED) |
| 62 | if(is_defined) |
| 63 | message(FATAL_ERROR "map_new(): '${_MY_PARAMS_NAME}' is already defined.") |
| 64 | endif() |
| 65 | |
| 66 | set(_null " ") |
| 67 | define_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} BRIEF_DOCS ${_null} FULL_DOCS ${_null}) |
| 68 | define_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS BRIEF_DOCS ${_null} FULL_DOCS ${_null}) |
| 69 | define_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS BRIEF_DOCS ${_null} FULL_DOCS ${_null}) |
| 70 | endfunction() |
| 71 | |
| 72 | #[===[.rst: |
| 73 | .. cmake:command:: map_add |
| 74 | |
| 75 | .. code-block:: cmake |
| 76 | |
| 77 | map_add(NAME foo KEY one VAL 1) |
| 78 | |
| 79 | Add new key-value pair to a map. |
| 80 | |
| 81 | ``KEY`` and ``VAL`` are stored as CMake properties (string list) which permit |
| 82 | a broad, but not clearly defined set of characters. Semicolons must be escaped |
| 83 | as ``\;`` in all cases. To minimize the amount of possible bugs, both ``KEY`` |
| 84 | and ``VAL`` should use |C identifier like string|. Exceptions are e.g. when a |
| 85 | path is stored as the value. |
| 86 | |
| 87 | Inputs: |
| 88 | |
| 89 | ``NAME`` |
| 90 | Name of the map. Trying to add to a non-existing map generates an error. |
| 91 | |
| 92 | ``KEY`` |
| 93 | New key to add. Key must be unique, trying to add a new pair whose key |
| 94 | already exists in the map will cause an error. |
| 95 | |
| 96 | ``VAL`` |
| 97 | Value for new key. |
| 98 | |
| 99 | #]===] |
| 100 | function(map_add) |
| 101 | set(_OPTIONS_ARGS) |
| 102 | set(_ONE_VALUE_ARGS NAME KEY VAL) |
| 103 | set(_MULTI_VALUE_ARGS) |
| 104 | cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) |
| 105 | |
| 106 | check_args(map_add NAME KEY VAL) |
| 107 | |
| 108 | get_property(_is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED) |
| 109 | if(NOT _is_defined) |
| 110 | message(FATAL_ERROR "map_add(): '${_MY_PARAMS_NAME}' is not defined.") |
| 111 | endif() |
| 112 | |
| 113 | get_property(_keys GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS) |
| 114 | get_property(_vals GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS) |
| 115 | |
| 116 | if(${_MY_PARAMS_KEY} IN_LIST _keys) |
| 117 | message(FATAL_ERROR "map_add(): key '${_MY_PARAMS_KEY}' is already defined.") |
| 118 | else() |
| 119 | list(APPEND _keys ${_MY_PARAMS_KEY}) |
| 120 | list(APPEND _vals ${_MY_PARAMS_VAL}) |
| 121 | |
| 122 | set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS ${_keys}) |
| 123 | set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS ${_vals}) |
| 124 | endif() |
| 125 | endfunction() |
| 126 | |
| 127 | #[===[.rst: |
| 128 | .. cmake:command:: map_remove |
| 129 | |
| 130 | .. code-block:: cmake |
| 131 | |
| 132 | map_remove(NAME foo KEY one) |
| 133 | |
| 134 | Remove existing key-value pair from a map. |
| 135 | |
| 136 | Inputs: |
| 137 | |
| 138 | ``NAME`` |
| 139 | Name of the map. Trying to remove from a non-existing map generates an |
| 140 | error. |
| 141 | |
| 142 | ``KEY`` |
| 143 | Key to remove. Trying to remove a non-existing key generates an error. |
| 144 | |
| 145 | #]===] |
| 146 | function(map_remove) |
| 147 | set(_OPTIONS_ARGS) |
| 148 | set(_ONE_VALUE_ARGS NAME KEY) |
| 149 | set(_MULTI_VALUE_ARGS) |
| 150 | cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) |
| 151 | |
| 152 | check_args(map_remove NAME KEY) |
| 153 | |
| 154 | get_property(_is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED) |
| 155 | if(NOT _is_defined) |
| 156 | message(FATAL_ERROR "map_remove(): '${_MY_PARAMS_NAME}' is not defined.") |
| 157 | endif() |
| 158 | |
| 159 | get_property(_keys GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS) |
| 160 | get_property(_vals GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS) |
| 161 | |
| 162 | list(FIND _keys ${_MY_PARAMS_KEY} _index) |
| 163 | if(_index EQUAL -1) |
| 164 | message(FATAL_ERROR "map_remove(): key '${_MY_PARAMS_KEY}' does not exist.") |
| 165 | endif() |
| 166 | |
| 167 | list(REMOVE_AT _keys ${_index}) |
| 168 | list(REMOVE_AT _vals ${_index}) |
| 169 | |
| 170 | set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS ${_keys}) |
| 171 | set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS ${_vals}) |
| 172 | endfunction() |
| 173 | |
| 174 | #[===[.rst: |
| 175 | .. cmake:command:: map_read |
| 176 | |
| 177 | .. code-block:: cmake |
| 178 | |
| 179 | map_read(NAME foo KEYS _keys VALS _vals) |
| 180 | |
| 181 | Read the keys and the values of the map into two separate lists in the parent |
| 182 | scope. |
| 183 | |
| 184 | Inputs: |
| 185 | |
| 186 | ``NAME`` |
| 187 | Name of the map. Trying to read a non-existing map generates an error. |
| 188 | |
| 189 | Outputs: |
| 190 | |
| 191 | ``KEYS`` |
| 192 | Read the keys list of the map into this variable of the parent scope. |
| 193 | |
| 194 | ``VALS`` |
| 195 | Read the values list of the map into this variable of the parent scope. |
| 196 | |
| 197 | #]===] |
| 198 | function(map_read) |
| 199 | set(_OPTIONS_ARGS) |
| 200 | set(_ONE_VALUE_ARGS NAME KEYS VALS) |
| 201 | set(_MULTI_VALUE_ARGS) |
| 202 | cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) |
| 203 | |
| 204 | check_args(map_read NAME KEYS VALS) |
| 205 | |
| 206 | get_property(_is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED) |
| 207 | if(NOT _is_defined) |
| 208 | message(FATAL_ERROR "map_read(): '${_MY_PARAMS_NAME}' is not defined.") |
| 209 | endif() |
| 210 | |
| 211 | get_property(_keys GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS) |
| 212 | get_property(_vals GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS) |
| 213 | |
| 214 | set(${_MY_PARAMS_KEYS} ${_keys} PARENT_SCOPE) |
| 215 | set(${_MY_PARAMS_VALS} ${_vals} PARENT_SCOPE) |
| 216 | endfunction() |
| 217 | |
| 218 | #[===[.rst: |
| 219 | .. cmake:command:: map_to_list |
| 220 | |
| 221 | .. code-block:: cmake |
| 222 | |
| 223 | map_to_list(KEYS ${_keys} VALS ${_vals} OUT _combined) |
| 224 | |
| 225 | Combine the keys and values list of a map (provided by |
| 226 | :cmake:command:`map_read` function) into a single list in the parent scope. |
| 227 | |
| 228 | * If a key 'FOO' has the value 'BAR', in the combined list it will be |
| 229 | 'FOO=BAR'. |
| 230 | * If a key 'FOO' has a single space character value, in the combined list it |
| 231 | will be 'FOO'. |
| 232 | |
| 233 | Inputs: |
| 234 | |
| 235 | ``KEYS`` |
| 236 | Keys list of a map. |
| 237 | |
| 238 | ``VALS`` |
| 239 | Values list of a map. |
| 240 | |
| 241 | Outputs: |
| 242 | |
| 243 | ``OUT`` |
| 244 | Write the combined list of key-value pairs into this variable of the parent |
| 245 | scope. |
| 246 | |
| 247 | #]===] |
| 248 | function(map_to_list) |
| 249 | set(_OPTIONS_ARGS) |
| 250 | set(_ONE_VALUE_ARGS OUT) |
| 251 | set(_MULTI_VALUE_ARGS KEYS VALS) |
| 252 | cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) |
| 253 | |
| 254 | check_args(map_to_list KEYS VALS OUT) |
| 255 | |
| 256 | list(LENGTH _MY_PARAMS_KEYS _count) |
| 257 | math(EXPR _count "${_count}-1") |
| 258 | foreach(i RANGE ${_count}) |
| 259 | list(GET _MY_PARAMS_KEYS ${i} _key) |
| 260 | list(GET _MY_PARAMS_VALS ${i} _val) |
| 261 | |
| 262 | if(${_val} STREQUAL " ") |
| 263 | list(APPEND _out "${_key}") |
| 264 | else() |
| 265 | list(APPEND _out "${_key}=${_val}") |
| 266 | endif() |
| 267 | endforeach() |
| 268 | |
| 269 | set(${_MY_PARAMS_OUT} ${_out} PARENT_SCOPE) |
| 270 | endfunction() |
| 271 | |
| 272 | #[===[.rst: |
| 273 | .. cmake:command:: map_print |
| 274 | |
| 275 | .. code-block:: cmake |
| 276 | |
| 277 | map_print(NAME foo) |
| 278 | |
| 279 | Print each key-value pair in a map, for debug purposes. |
| 280 | |
| 281 | Inputs: |
| 282 | |
| 283 | ``NAME`` |
| 284 | Name of the map to print. |
| 285 | |
| 286 | #]===] |
| 287 | function(map_print) |
| 288 | set(_OPTIONS_ARGS) |
| 289 | set(_ONE_VALUE_ARGS NAME) |
| 290 | set(_MULTI_VALUE_ARGS) |
| 291 | cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) |
| 292 | |
| 293 | check_args(map_print NAME) |
| 294 | |
| 295 | map_read(NAME ${_MY_PARAMS_NAME} KEYS _keys VALS _vals) |
| 296 | |
| 297 | message("====Map '${_MY_PARAMS_NAME}'====") |
| 298 | list(LENGTH _keys _count) |
| 299 | math(EXPR _count "${_count}-1") |
| 300 | foreach(i RANGE ${_count}) |
| 301 | list(GET _keys ${i} _key) |
| 302 | list(GET _vals ${i} _val) |
| 303 | message("[${_key}]: ${_val}") |
| 304 | endforeach() |
| 305 | message("====Map '${_MY_PARAMS_NAME}' end====\n") |
| 306 | endfunction() |