Add PropertyCopy.cmake

PropertyCopy allows saving target interface properties to variables
and to translate the variables into different formats. The result
can be used to pass down compilation settings to external components.

Change-Id: I37159eef097cab3f9312a0ce6f766c9932520b7d
Signed-off-by: Gyorgy Szing <Gyorgy.Szing@arm.com>
diff --git a/tools/cmake/common/PropertyCopy.cmake b/tools/cmake/common/PropertyCopy.cmake
new file mode 100644
index 0000000..754c98c
--- /dev/null
+++ b/tools/cmake/common/PropertyCopy.cmake
@@ -0,0 +1,459 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+#[===[.rst:
+PropertyCopy.cmake
+------------------
+
+This module allows saving interface properties of a target to a set of variables and to translate
+the variables to cmake script fragment of compiler and linker flag lists.
+The main purpose is to allow transferring settings to sub-projects which need strong separation
+(i.e. ExternalProject is used) or use a non CMake build system.
+
+For CMake projects the data-flow is to save the settings to variables, translate these to cmake code
+fragment, and then inject these to the sub-projects using a generated initial cache file.
+Alternatively translate the saved values to list variables `<PREFIX>_CMAKE_C_FLAGS_INIT` and
+`<PREFIX>_CMAKE_EXE_LINKER_FLAGS_INIT`, and pass these to the sub-project using the -D command-line
+parameter.
+
+For non CMake projects the data-flow is to save the properties to variables and the translate to
+compiler and linker argument lists. Then use the generated `<PREFIX>_CMAKE_C_FLAGS_INIT` and
+`<PREFIX>_CMAKE_EXE_LINKER_FLAGS_INIT` variables in a build system specific way (e.g. setting
+`CFLAGS` and `LDFLAGS` environment variables) to configure the sub-project.
+
+#]===]
+
+#[===[.rst:
+.. cmake:variable:: PROPERTYCOPY_DEFAULT_PROPERTY_LIST
+
+Default list of properties to save and restore. It is used by functions in this file. Some of these
+allow using a custom list instead by passing appropriate parameters.
+#]===]
+set(PROPERTYCOPY_DEFAULT_PROPERTY_LIST INTERFACE_COMPILE_DEFINITIONS
+    INTERFACE_COMPILE_OPTIONS INTERFACE_INCLUDE_DIRECTORIES
+    INTERFACE_LINK_DIRECTORIES INTERFACE_LINK_LIBRARIES INTERFACE_LINK_OPTIONS
+    INTERFACE_POSITION_INDEPENDENT_CODE
+    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
+
+#[===[.rst:
+.. cmake:command:: save_interface_target_properties
+
+  .. code-block:: cmake
+
+    save_interface_target_properties(TGT stdlib:c PREFIX LIBC)
+    save_interface_target_properties(TGT stdlib:c PREFIX LIBC
+                PROPERTIES INTERFACE_LINK_DIRECTORIES INTERFACE_LINK_LIBRARIES)
+
+  Save interface properties of a target to a set of variables. Variables are named after the
+  properties prefixed with the parameter <PREFIX>_. (i.e. FOO_INTERFACE_COMPILE_OPTIONS if the
+  prefix was "FOO").
+  The list of properties to be saved can be set using the PROPERTIES parameter. If this is not
+  set, the :variable:`PROPERTYCOPY_DEFAULT_PROPERTY_LIST` is used.
+
+  Inputs:
+  ``TGT``
+    Target to copy properties from.
+  ``PROPERTIES``
+    Optional. List of properties to save. If not set, the default list is used. See:
+	:variable:`PROPERTYCOPY_DEFAULT_PROPERTY_LIST`.
+  ``PREFIX``
+    Prefix to use for output variable names.
+  Outputs:
+    A set of variables (see description).
+#]===]
+function(save_interface_target_properties)
+    set(_OPTIONS_ARGS)
+    set(_ONE_VALUE_ARGS TGT PREFIX)
+    set(_MULTI_VALUE_ARGS PROPERTIES)
+    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+    if (NOT DEFINED _MY_PARAMS_PREFIX)
+        message(FATAL_ERROR "Mandatory parameter PREFIX is not defined.")
+    endif()
+    if (NOT DEFINED _MY_PARAMS_TGT)
+        message(FATAL_ERROR "Mandatory parameter TGT is not defined.")
+    endif()
+    if(NOT TARGET ${_MY_PARAMS_TGT})
+        message(FATAL_ERROR "Target \"${_MY_PARAMS_TGT}\" does not exist.")
+    endif()
+    if (NOT DEFINED _MY_PARAMS_PROPERTIES)
+        set(_MY_PARAMS_PROPERTIES ${PROPERTYCOPY_DEFAULT_PROPERTY_LIST})
+    endif()
+
+    foreach(_prop IN LISTS _MY_PARAMS_PROPERTIES )
+        get_property(_set TARGET ${_MY_PARAMS_TGT} PROPERTY ${_prop} SET)
+        if (_set)
+            get_property(${_MY_PARAMS_PREFIX}_${_prop} TARGET ${_MY_PARAMS_TGT} PROPERTY ${_prop})
+            set(${_MY_PARAMS_PREFIX}_${_prop} ${${_MY_PARAMS_PREFIX}_${_prop}} PARENT_SCOPE)
+        endif()
+    endforeach()
+endfunction()
+
+#[===[.rst:
+.. cmake:command:: translate_interface_target_properties
+
+  .. code-block:: cmake
+
+    # To translate default set of properties saved to variables with ``LIBC_`` prefix
+    # using :command:`save_interface_target_properties`. Result string returned to
+    # ``_cmake_fragment``
+    translate_interface_target_properties(PREFIX LIBC RES _cmake_fragment)
+
+    # To translate default set of properties saved to variables with ``LIBC_`` prefix
+    # using :command:`save_interface_target_properties`. Results saved to lists prefixed
+    # with ``LIBC_``. List of generated lists is returned in ``_lists``
+    translate_interface_target_properties(PREFIX LIBC RES _lists)
+
+  Construct a string of cmake script fragment setting global cmake variables configuring
+  build properties to match saved target interface settings. The script fragment is returned
+  in ``RES``
+  Intended usage is to help transferring target specific settings to sub projects using
+  initial cache files.
+  Warning: quotation in property values is not handled. This can cause problems e.g. with
+           computed includes.
+
+  If ``TO_LIST`` is passed translation will be done to a lists. ``RES`` will hold a list of
+  list names where the settings are saved.
+  This mode allows further processing on the lists, e.g. to be converted to ``CFLAGS`` or
+  ``LDFLAGS`` environment variables.
+
+  Works in tandem with :command:`save_interface_target_properties`.
+
+  Inputs:
+  ``PREFIX``
+    Target to set properties on.
+  ``VARS``
+    Name of variables to copy from.
+  ``TO_LIST``
+    Translate to lists instead of cmake script fragment.
+  Outputs
+  ``RES``
+    Name of variable to store the results to.
+#]===]
+function(translate_interface_target_properties)
+    set(_OPTIONS_ARGS TO_LIST)
+    set(_ONE_VALUE_ARGS PREFIX RES)
+    set(_MULTI_VALUE_ARGS VARS)
+    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+    if (NOT DEFINED _MY_PARAMS_PREFIX)
+        message(FATAL_ERROR "Mandatory parameter PREFIX is not defined.")
+    endif()
+    string(LENGTH "${_MY_PARAMS_PREFIX}_" _PREFIX_LENGT)
+
+    if (NOT DEFINED _MY_PARAMS_RES)
+        message(FATAL_ERROR "Mandatory parameter RES is not defined.")
+    endif()
+
+    if (DEFINED _MY_PARAMS_VARS)
+        foreach(_VAR_NAME IN LISTS _MY_PARAMS_VARS)
+            if (NOT DEFINED ${_VAR_NAME})
+                message(FATAL_ERROR "Attempt to translate undefined variable \"${_VAR_NAME}\"")
+            endif()
+
+            string(SUBSTRING "${_VAR_NAME}" ${_PREFIX_LENGT} -1 _prop)
+            _prc_translate(PROP "${_prop}" VALUE ${${_VAR_NAME}} RES _res)
+
+            if(NOT "${_res}" STREQUAL "")
+                list(GET _res 0 _global_var_name)
+                list(GET _res 1 _global_var_value)
+                list(APPEND ${_MY_PARAMS_PREFIX}_${_global_var_name} ${_global_var_value})
+                if (NOT "${_MY_PARAMS_PREFIX}_${_global_var_name}" IN_LIST _RES)
+                    list(APPEND _RES "${_MY_PARAMS_PREFIX}_${_global_var_name}")
+                endif()
+            endif()
+        endforeach()
+    else()
+        foreach(_prop IN LISTS PROPERTYCOPY_DEFAULT_PROPERTY_LIST)
+            set(_VAR_NAME "${_MY_PARAMS_PREFIX}_${_prop}")
+            # Is the variable holding the value of the property available?
+            if (DEFINED ${_VAR_NAME})
+                _prc_translate(PROP "${_prop}" VALUE ${${_VAR_NAME}} RES _res)
+                if(NOT "${_res}" STREQUAL "")
+                    list(GET _res 0 _global_var_name)
+                    list(GET _res 1 _global_var_value)
+                    list(APPEND ${_MY_PARAMS_PREFIX}_${_global_var_name} ${_global_var_value})
+                    if (NOT "${_MY_PARAMS_PREFIX}_${_global_var_name}" IN_LIST _RES)
+                        list(APPEND _RES "${_MY_PARAMS_PREFIX}_${_global_var_name}")
+                    endif()
+                endif()
+            endif()
+        endforeach()
+    endif()
+
+    if (_MY_PARAMS_TO_LIST)
+        foreach(_list_name IN LISTS _RES)
+            set(${_list_name} ${${_list_name}} PARENT_SCOPE)
+        endforeach()
+        set(${_MY_PARAMS_RES} ${_RES} PARENT_SCOPE)
+    else()
+        foreach(_list_name IN LISTS _RES)
+            string(SUBSTRING "${_list_name}" ${_PREFIX_LENGT} -1 _short_name)
+            string(REPLACE ";" " " _list_value "${${_list_name}}")
+            string(APPEND _STRING_RES "set(${_short_name} \"\${${_short_name}} ${_list_value}\" CACHE STRING \"\" FORCE)\n")
+        endforeach()
+        set(${_MY_PARAMS_RES} ${_STRING_RES} PARENT_SCOPE)
+    endif()
+endfunction()
+
+#[===[.rst:
+.. cmake:command:: translate_value_as_property
+
+  .. code-block:: cmake
+
+    translate_value_as_property(VALUE "/foo/bar/include;/foo/bar/include1"
+                                PROPERTY INTERFACE_INCLUDE_DIRECTORIES
+                                RES _cmake_fragment)
+
+  Translate a value as the specified property would be. Can be used to translate variables not saved
+  with :command:`save_interface_target_properties`
+
+  Inputs:
+  ``VALUE``
+    Value to be converted.
+  ``PROPERTY``
+    The interface property to set conversion type.
+  Outputs:
+  ``RES``
+    Name of variable to write result string to.
+#]===]
+function(translate_value_as_property)
+    set(_OPTIONS_ARGS)
+    set(_ONE_VALUE_ARGS VALUE PROPERTY RES)
+    set(_MULTI_VALUE_ARGS)
+    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+    if (NOT DEFINED _MY_PARAMS_VALUE)
+        message(FATAL_ERROR "Mandatory parameter VALUE is not defined.")
+    endif()
+    if (NOT DEFINED _MY_PARAMS_RES)
+        message(FATAL_ERROR "Mandatory parameter RES is not defined.")
+    endif()
+    if (NOT DEFINED _MY_PARAMS_PROPERTY)
+        message(FATAL_ERROR "Mandatory parameter PROPERTY is not defined.")
+    endif()
+
+    set(A_${_MY_PARAMS_PROPERTY} ${_MY_PARAMS_VALUE})
+    translate_interface_target_properties(PREFIX A RES _cmake_fragment
+            VARS A_${_MY_PARAMS_PROPERTY})
+    set(${_MY_PARAMS_RES} ${_cmake_fragment} PARENT_SCOPE)
+endfunction()
+
+#[===[.rst:
+.. cmake:command:: unset_saved_properties
+
+  .. code-block:: cmake
+
+    unset_saved_properties("LIBC")
+
+  Unset saved properties. For cleaning up the variable name space.
+
+  Inputs:
+  ``PREFIX``
+    Prefix to use for output variable names.
+#]===]
+macro(unset_saved_properties PREFIX)
+    foreach(_prc_prop IN LISTS PROPERTYCOPY_DEFAULT_PROPERTY_LIST )
+        set(_PRC_VAR_NAME ${PREFIX}_${_prc_prop})
+        unset(${_PRC_VAR_NAME})
+    endforeach()
+    unset(_PRC_VAR_NAME)
+    unset(_prc_prop)
+endmacro()
+
+#[===[.rst:
+.. cmake:command:: unset_translated_lists
+
+  .. code-block:: cmake
+
+    unset_translated_lists(_lists)
+
+  Unset saved properties. Can be used for cleaning up the variable name space.
+
+  Inputs:
+  ``LISTVAR``
+    Prefix to use for output variable names.
+#]===]
+macro(unset_translated_lists LISTVAR)
+    foreach(_list_name IN LISTS ${LISTVAR} )
+        unset(${_list_name})
+    endforeach()
+    unset(_list_name)
+endmacro()
+#[===[.rst:
+.. cmake:command:: print_saved_properties
+
+  .. code-block:: cmake
+
+    print_saved_properties(PREFIX LIBC)
+
+  Print the value of all target interface properties saved with the specified prefix.
+  Can be used for debugging.
+
+  Inputs:
+  ``PREFIX``
+    Prefix to use for output variable names.
+#]===]
+function(print_saved_properties)
+    set(_OPTIONS_ARGS)
+    set(_ONE_VALUE_ARGS PREFIX)
+    set(_MULTI_VALUE_ARGS )
+    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+    if (NOT DEFINED _MY_PARAMS_PREFIX)
+        message(FATAL_ERROR "Mandatory parameter PREFIX is not defined.")
+    endif()
+    string(LENGTH "${_MY_PARAMS_PREFIX}_" _PREFIX_LENGT)
+
+    message(STATUS "Properties saved with prefix \"${_MY_PARAMS_PREFIX}\"")
+    foreach(_prop IN LISTS PROPERTYCOPY_DEFAULT_PROPERTY_LIST )
+        set(_VAR_NAME "${_MY_PARAMS_PREFIX}_${_prop}")
+        string(SUBSTRING "${_VAR_NAME}" ${_PREFIX_LENGT} -1 _prop)
+        if (NOT DEFINED ${_VAR_NAME})
+            set(_value "<Not set.>")
+        else()
+            set(_value ${${_VAR_NAME}})
+        endif()
+        message(STATUS "    ${_prop}:${_value}")
+    endforeach()
+endfunction()
+
+#[===[.rst:
+.. cmake:command:: print_translated_lists
+
+  .. code-block:: cmake
+
+    print_translated_lists(PREFIX LIBC)
+
+  Print the value of all lists translated from interface properties by calling
+  translate_interface_target_properties() with TO_LISTS set.
+  Can be used for debugging.
+
+  Inputs:
+  ``LIST``
+    Name of list of lists set by :command:`translate_interface_target_properties`
+#]===]
+function(print_translated_lists)
+    set(_OPTIONS_ARGS)
+    set(_ONE_VALUE_ARGS LIST)
+    set(_MULTI_VALUE_ARGS )
+    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+    if (NOT DEFINED _MY_PARAMS_LIST)
+        message(FATAL_ERROR "Mandatory parameter LIST is not defined.")
+    endif()
+
+    message(STATUS "Translated lists from \"${_MY_PARAMS_LIST}\"")
+    foreach(_list IN LISTS ${_MY_PARAMS_LIST})
+        message(STATUS "   ${_list}=${${_list}}")
+    endforeach()
+endfunction()
+
+# These properties are cmake specific and can not be translated.
+# INTERFACE_COMPILE_FEATURES, INTERFACE_LINK_DEPENDS, INTERFACE_SOURCES
+# LINK_INTERFACE_LIBRARIES
+
+# Translate target property to command line switch.
+function(_prc_translate)
+    set(_OPTIONS_ARGS)
+    set(_ONE_VALUE_ARGS PROP RES)
+    set(_MULTI_VALUE_ARGS VALUE)
+    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
+
+    if ("${_MY_PARAMS_VALUE}" STREQUAL "")
+        set(_res "")
+    else()
+        if (_MY_PARAMS_PROP STREQUAL INTERFACE_INCLUDE_DIRECTORIES)
+            _prc_translate_include_list("${_MY_PARAMS_VALUE}" _res)
+        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
+            _prc_translate_system_include_list("${_MY_PARAMS_VALUE}" _res)
+        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_COMPILE_DEFINITIONS)
+            _prc_translate_macro_list("${_MY_PARAMS_VALUE}" _res)
+        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_COMPILE_OPTIONS)
+            _prc_translate_compile_option_list("${_MY_PARAMS_VALUE}" _res)
+        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_LINK_OPTIONS)
+            _prc_translate_link_option_list("${_MY_PARAMS_VALUE}" _res)
+        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_LINK_DIRECTORIES)
+            _prc_translate_link_directory_list("${_MY_PARAMS_VALUE}" _res)
+        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_LINK_LIBRARIES)
+            _prc_translate_link_library_list("${_MY_PARAMS_VALUE}" _res)
+        else()
+            message(FATAL_ERROR "Can not translate target property \"${_MY_PARAMS_PROP}\" to global setting.")
+        endif()
+    endif()
+    set(${_MY_PARAMS_RES} "${_res}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of include directories to compiler flags.
+function(_prc_translate_include_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " ${CMAKE_INCLUDE_FLAG_C} " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_C_FLAGS_INIT;${CMAKE_INCLUDE_FLAG_C} ${_tmp}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of system include directories to compiler flags.
+function(_prc_translate_system_include_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " ${CMAKE_INCLUDE_SYSTEM_FLAG_C} " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_C_FLAGS_INIT;${CMAKE_INCLUDE_SYSTEM_FLAG_C} ${_tmp}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of C macro definitions to compiler flags.
+function(_prc_translate_macro_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " -D " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_C_FLAGS_INIT;-D ${_tmp}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of compilation options to compiler flags.
+function(_prc_translate_compile_option_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_C_FLAGS_INIT;${_tmp}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of link options to linker flags.
+function(_prc_translate_link_option_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_EXE_LINKER_FLAGS_INIT;${_tmp}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of linker search paths to linker flags.
+function(_prc_translate_link_directory_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " ${CMAKE_LIBRARY_PATH_FLAG} " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_EXE_LINKER_FLAGS_INIT;${CMAKE_LIBRARY_PATH_FLAG} ${_tmp}" PARENT_SCOPE)
+endfunction()
+
+# Translate list of libraries to linker flags.
+function(_prc_translate_link_library_list VALUE OUT)
+    if(NOT "${VALUE}" STREQUAL "")
+        string(REPLACE ";" " ${CMAKE_LINK_LIBRARY_FLAG} " _tmp "${VALUE}")
+    else()
+        set(_tmp "")
+    endif()
+    set(${OUT} "CMAKE_EXE_LINKER_FLAGS_INIT;${CMAKE_LINK_LIBRARY_FLAG} ${_tmp}" PARENT_SCOPE)
+endfunction()