blob: 710f9b3c561e40c8128c5005d2665a959883b8c9 [file] [log] [blame]
Gyorgy Szing5e429cb2019-12-03 20:39:55 +01001#-------------------------------------------------------------------------------
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:
9Map utility file
10----------------
11Map is a key-value pair storage (also known as associative array)
12implementation. It can hold multiple key-value pairs, the keys have to be unique
13in a map. Trying to add a new pair whose key already exists in the map will
14cause an error. Keys and values have a one-to-one relation, i.e. a key has
15exactly one value associated to it, which cannot be empty. If an empty/null
16value is needed, use a single space character instead. The value associated to a
17key cannot be overwritten, however it is possible to remove a key-value pair
18from the map then add the same key again with a new value.
19
20The main purpose of this utility is to store configuration data for the project,
21i.e. build options, compiler flags, etc.
22
23Internally the utility uses global properties to store the data. The global
24property ``MAPS.<name of map>`` is created as an indicator that the map is
25defined, while global properties ``MAPS.<name of map>.KEYS`` and ``MAPS.<name of
26map>.VALS`` hold the corresponding data as lists. A value for a key is
27identified by having the same index in the VALS lists, as the key has in the
28KEYS list.
29
30.. todo:: Investigate alternatives to global properties (name collision possible).
31
32#]===]
33
34include_guard(DIRECTORY)
35include(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#]===]
53function(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})
70endfunction()
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#]===]
100function(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()
125endfunction()
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#]===]
146function(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})
172endfunction()
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#]===]
198function(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)
216endfunction()
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#]===]
248function(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)
270endfunction()
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#]===]
287function(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")
306endfunction()