blob: 710f9b3c561e40c8128c5005d2665a959883b8c9 [file] [log] [blame]
#-------------------------------------------------------------------------------
# Copyright (c) 2019-2020, Arm Limited and Contributors. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
#-------------------------------------------------------------------------------
#[===[.rst:
Map utility file
----------------
Map is a key-value pair storage (also known as associative array)
implementation. It can hold multiple key-value pairs, the keys have to be unique
in a map. Trying to add a new pair whose key already exists in the map will
cause an error. Keys and values have a one-to-one relation, i.e. a key has
exactly one value associated to it, which cannot be empty. If an empty/null
value is needed, use a single space character instead. The value associated to a
key cannot be overwritten, however it is possible to remove a key-value pair
from the map then add the same key again with a new value.
The main purpose of this utility is to store configuration data for the project,
i.e. build options, compiler flags, etc.
Internally the utility uses global properties to store the data. The global
property ``MAPS.<name of map>`` is created as an indicator that the map is
defined, while global properties ``MAPS.<name of map>.KEYS`` and ``MAPS.<name of
map>.VALS`` hold the corresponding data as lists. A value for a key is
identified by having the same index in the VALS lists, as the key has in the
KEYS list.
.. todo:: Investigate alternatives to global properties (name collision possible).
#]===]
include_guard(DIRECTORY)
include(Common/Utils)
#[===[.rst:
.. cmake:command:: map_new
.. code-block:: cmake
map_new(NAME foo)
Create a new named set of key-value pairs.
Inputs:
``NAME``
Name of the new map, use |C identifier like string|. The name must be unique
within the global namespace, otherwise an error is generated.
#]===]
function(map_new)
set(_OPTIONS_ARGS)
set(_ONE_VALUE_ARGS NAME)
set(_MULTI_VALUE_ARGS)
cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
check_args(map_new NAME)
get_property(is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED)
if(is_defined)
message(FATAL_ERROR "map_new(): '${_MY_PARAMS_NAME}' is already defined.")
endif()
set(_null " ")
define_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} BRIEF_DOCS ${_null} FULL_DOCS ${_null})
define_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS BRIEF_DOCS ${_null} FULL_DOCS ${_null})
define_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS BRIEF_DOCS ${_null} FULL_DOCS ${_null})
endfunction()
#[===[.rst:
.. cmake:command:: map_add
.. code-block:: cmake
map_add(NAME foo KEY one VAL 1)
Add new key-value pair to a map.
``KEY`` and ``VAL`` are stored as CMake properties (string list) which permit
a broad, but not clearly defined set of characters. Semicolons must be escaped
as ``\;`` in all cases. To minimize the amount of possible bugs, both ``KEY``
and ``VAL`` should use |C identifier like string|. Exceptions are e.g. when a
path is stored as the value.
Inputs:
``NAME``
Name of the map. Trying to add to a non-existing map generates an error.
``KEY``
New key to add. Key must be unique, trying to add a new pair whose key
already exists in the map will cause an error.
``VAL``
Value for new key.
#]===]
function(map_add)
set(_OPTIONS_ARGS)
set(_ONE_VALUE_ARGS NAME KEY VAL)
set(_MULTI_VALUE_ARGS)
cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
check_args(map_add NAME KEY VAL)
get_property(_is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED)
if(NOT _is_defined)
message(FATAL_ERROR "map_add(): '${_MY_PARAMS_NAME}' is not defined.")
endif()
get_property(_keys GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS)
get_property(_vals GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS)
if(${_MY_PARAMS_KEY} IN_LIST _keys)
message(FATAL_ERROR "map_add(): key '${_MY_PARAMS_KEY}' is already defined.")
else()
list(APPEND _keys ${_MY_PARAMS_KEY})
list(APPEND _vals ${_MY_PARAMS_VAL})
set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS ${_keys})
set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS ${_vals})
endif()
endfunction()
#[===[.rst:
.. cmake:command:: map_remove
.. code-block:: cmake
map_remove(NAME foo KEY one)
Remove existing key-value pair from a map.
Inputs:
``NAME``
Name of the map. Trying to remove from a non-existing map generates an
error.
``KEY``
Key to remove. Trying to remove a non-existing key generates an error.
#]===]
function(map_remove)
set(_OPTIONS_ARGS)
set(_ONE_VALUE_ARGS NAME KEY)
set(_MULTI_VALUE_ARGS)
cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
check_args(map_remove NAME KEY)
get_property(_is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED)
if(NOT _is_defined)
message(FATAL_ERROR "map_remove(): '${_MY_PARAMS_NAME}' is not defined.")
endif()
get_property(_keys GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS)
get_property(_vals GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS)
list(FIND _keys ${_MY_PARAMS_KEY} _index)
if(_index EQUAL -1)
message(FATAL_ERROR "map_remove(): key '${_MY_PARAMS_KEY}' does not exist.")
endif()
list(REMOVE_AT _keys ${_index})
list(REMOVE_AT _vals ${_index})
set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS ${_keys})
set_property(GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS ${_vals})
endfunction()
#[===[.rst:
.. cmake:command:: map_read
.. code-block:: cmake
map_read(NAME foo KEYS _keys VALS _vals)
Read the keys and the values of the map into two separate lists in the parent
scope.
Inputs:
``NAME``
Name of the map. Trying to read a non-existing map generates an error.
Outputs:
``KEYS``
Read the keys list of the map into this variable of the parent scope.
``VALS``
Read the values list of the map into this variable of the parent scope.
#]===]
function(map_read)
set(_OPTIONS_ARGS)
set(_ONE_VALUE_ARGS NAME KEYS VALS)
set(_MULTI_VALUE_ARGS)
cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
check_args(map_read NAME KEYS VALS)
get_property(_is_defined GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME} DEFINED)
if(NOT _is_defined)
message(FATAL_ERROR "map_read(): '${_MY_PARAMS_NAME}' is not defined.")
endif()
get_property(_keys GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.KEYS)
get_property(_vals GLOBAL PROPERTY MAPS.${_MY_PARAMS_NAME}.VALS)
set(${_MY_PARAMS_KEYS} ${_keys} PARENT_SCOPE)
set(${_MY_PARAMS_VALS} ${_vals} PARENT_SCOPE)
endfunction()
#[===[.rst:
.. cmake:command:: map_to_list
.. code-block:: cmake
map_to_list(KEYS ${_keys} VALS ${_vals} OUT _combined)
Combine the keys and values list of a map (provided by
:cmake:command:`map_read` function) into a single list in the parent scope.
* If a key 'FOO' has the value 'BAR', in the combined list it will be
'FOO=BAR'.
* If a key 'FOO' has a single space character value, in the combined list it
will be 'FOO'.
Inputs:
``KEYS``
Keys list of a map.
``VALS``
Values list of a map.
Outputs:
``OUT``
Write the combined list of key-value pairs into this variable of the parent
scope.
#]===]
function(map_to_list)
set(_OPTIONS_ARGS)
set(_ONE_VALUE_ARGS OUT)
set(_MULTI_VALUE_ARGS KEYS VALS)
cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
check_args(map_to_list KEYS VALS OUT)
list(LENGTH _MY_PARAMS_KEYS _count)
math(EXPR _count "${_count}-1")
foreach(i RANGE ${_count})
list(GET _MY_PARAMS_KEYS ${i} _key)
list(GET _MY_PARAMS_VALS ${i} _val)
if(${_val} STREQUAL " ")
list(APPEND _out "${_key}")
else()
list(APPEND _out "${_key}=${_val}")
endif()
endforeach()
set(${_MY_PARAMS_OUT} ${_out} PARENT_SCOPE)
endfunction()
#[===[.rst:
.. cmake:command:: map_print
.. code-block:: cmake
map_print(NAME foo)
Print each key-value pair in a map, for debug purposes.
Inputs:
``NAME``
Name of the map to print.
#]===]
function(map_print)
set(_OPTIONS_ARGS)
set(_ONE_VALUE_ARGS NAME)
set(_MULTI_VALUE_ARGS)
cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
check_args(map_print NAME)
map_read(NAME ${_MY_PARAMS_NAME} KEYS _keys VALS _vals)
message("====Map '${_MY_PARAMS_NAME}'====")
list(LENGTH _keys _count)
math(EXPR _count "${_count}-1")
foreach(i RANGE ${_count})
list(GET _keys ${i} _key)
list(GET _vals ${i} _val)
message("[${_key}]: ${_val}")
endforeach()
message("====Map '${_MY_PARAMS_NAME}' end====\n")
endfunction()