1#-------------------------------------------------------------------------------
2# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6#-------------------------------------------------------------------------------
7
8#[===[.rst:
9PropertyCopy.cmake
10------------------
11
12This module allows saving interface properties of a target to a set of variables and to translate
13the variables to cmake script fragment of compiler and linker flag lists.
14The main purpose is to allow transferring settings to sub-projects which need strong separation
15(i.e. ExternalProject is used) or use a non CMake build system.
16
17For CMake projects the data-flow is to save the settings to variables, translate these to cmake code
18fragment, and then inject these to the sub-projects using a generated initial cache file.
19Alternatively translate the saved values to list variables `<PREFIX>_CMAKE_C_FLAGS_INIT` and
20`<PREFIX>_CMAKE_EXE_LINKER_FLAGS_INIT`, and pass these to the sub-project using the -D command-line
21parameter.
22
23For non CMake projects the data-flow is to save the properties to variables and the translate to
24compiler and linker argument lists. Then use the generated `<PREFIX>_CMAKE_C_FLAGS_INIT` and
25`<PREFIX>_CMAKE_EXE_LINKER_FLAGS_INIT` variables in a build system specific way (e.g. setting
26`CFLAGS` and `LDFLAGS` environment variables) to configure the sub-project.
27
28#]===]
29
30#[===[.rst:
31.. cmake:variable:: PROPERTYCOPY_DEFAULT_PROPERTY_LIST
32
33Default list of properties to save and restore. It is used by functions in this file. Some of these
34allow using a custom list instead by passing appropriate parameters.
35#]===]
36set(PROPERTYCOPY_DEFAULT_PROPERTY_LIST INTERFACE_COMPILE_DEFINITIONS
37    INTERFACE_COMPILE_OPTIONS INTERFACE_INCLUDE_DIRECTORIES
38    INTERFACE_LINK_DIRECTORIES INTERFACE_LINK_LIBRARIES INTERFACE_LINK_OPTIONS
39    INTERFACE_POSITION_INDEPENDENT_CODE
40    INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
41
42#[===[.rst:
43.. cmake:command:: save_interface_target_properties
44
45  .. code-block:: cmake
46
47    save_interface_target_properties(TGT stdlib:c PREFIX LIBC)
48    save_interface_target_properties(TGT stdlib:c PREFIX LIBC
49                PROPERTIES INTERFACE_LINK_DIRECTORIES INTERFACE_LINK_LIBRARIES)
50
51  Save interface properties of a target to a set of variables. Variables are named after the
52  properties prefixed with the parameter <PREFIX>_. (i.e. FOO_INTERFACE_COMPILE_OPTIONS if the
53  prefix was "FOO").
54  The list of properties to be saved can be set using the PROPERTIES parameter. If this is not
55  set, the :variable:`PROPERTYCOPY_DEFAULT_PROPERTY_LIST` is used.
56
57  Inputs:
58  ``TGT``
59    Target to copy properties from.
60  ``PROPERTIES``
61    Optional. List of properties to save. If not set, the default list is used. See:
62	:variable:`PROPERTYCOPY_DEFAULT_PROPERTY_LIST`.
63  ``PREFIX``
64    Prefix to use for output variable names.
65  Outputs:
66    A set of variables (see description).
67#]===]
68function(save_interface_target_properties)
69    set(_OPTIONS_ARGS)
70    set(_ONE_VALUE_ARGS TGT PREFIX)
71    set(_MULTI_VALUE_ARGS PROPERTIES)
72    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
73
74    if (NOT DEFINED _MY_PARAMS_PREFIX)
75        message(FATAL_ERROR "Mandatory parameter PREFIX is not defined.")
76    endif()
77    if (NOT DEFINED _MY_PARAMS_TGT)
78        message(FATAL_ERROR "Mandatory parameter TGT is not defined.")
79    endif()
80    if(NOT TARGET ${_MY_PARAMS_TGT})
81        message(FATAL_ERROR "Target \"${_MY_PARAMS_TGT}\" does not exist.")
82    endif()
83    if (NOT DEFINED _MY_PARAMS_PROPERTIES)
84        set(_MY_PARAMS_PROPERTIES ${PROPERTYCOPY_DEFAULT_PROPERTY_LIST})
85    endif()
86
87    foreach(_prop IN LISTS _MY_PARAMS_PROPERTIES )
88        get_property(_set TARGET ${_MY_PARAMS_TGT} PROPERTY ${_prop} SET)
89        if (_set)
90            get_property(${_MY_PARAMS_PREFIX}_${_prop} TARGET ${_MY_PARAMS_TGT} PROPERTY ${_prop})
91            set(${_MY_PARAMS_PREFIX}_${_prop} ${${_MY_PARAMS_PREFIX}_${_prop}} PARENT_SCOPE)
92        endif()
93    endforeach()
94endfunction()
95
96#[===[.rst:
97.. cmake:command:: translate_interface_target_properties
98
99  .. code-block:: cmake
100
101    # To translate default set of properties saved to variables with ``LIBC_`` prefix
102    # using :command:`save_interface_target_properties`. Result string returned to
103    # ``_cmake_fragment``
104    translate_interface_target_properties(PREFIX LIBC RES _cmake_fragment)
105
106    # To translate default set of properties saved to variables with ``LIBC_`` prefix
107    # using :command:`save_interface_target_properties`. Results saved to lists prefixed
108    # with ``LIBC_``. List of generated lists is returned in ``_lists``
109    translate_interface_target_properties(PREFIX LIBC RES _lists)
110
111  Construct a string of cmake script fragment setting global cmake variables configuring
112  build properties to match saved target interface settings. The script fragment is returned
113  in ``RES``
114  Intended usage is to help transferring target specific settings to sub projects using
115  initial cache files.
116  Warning: quotation in property values is not handled. This can cause problems e.g. with
117           computed includes.
118
119  If ``TO_LIST`` is passed translation will be done to a lists. ``RES`` will hold a list of
120  list names where the settings are saved.
121  This mode allows further processing on the lists, e.g. to be converted to ``CFLAGS`` or
122  ``LDFLAGS`` environment variables.
123
124  Works in tandem with :command:`save_interface_target_properties`.
125
126  Inputs:
127  ``PREFIX``
128    Target to set properties on.
129  ``VARS``
130    Name of variables to copy from.
131  ``TO_LIST``
132    Translate to lists instead of cmake script fragment.
133  Outputs
134  ``RES``
135    Name of variable to store the results to.
136#]===]
137function(translate_interface_target_properties)
138    set(_OPTIONS_ARGS TO_LIST)
139    set(_ONE_VALUE_ARGS PREFIX RES)
140    set(_MULTI_VALUE_ARGS VARS)
141    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
142
143    if (NOT DEFINED _MY_PARAMS_PREFIX)
144        message(FATAL_ERROR "Mandatory parameter PREFIX is not defined.")
145    endif()
146    string(LENGTH "${_MY_PARAMS_PREFIX}_" _PREFIX_LENGT)
147
148    if (NOT DEFINED _MY_PARAMS_RES)
149        message(FATAL_ERROR "Mandatory parameter RES is not defined.")
150    endif()
151
152    if (DEFINED _MY_PARAMS_VARS)
153        foreach(_VAR_NAME IN LISTS _MY_PARAMS_VARS)
154            if (NOT DEFINED ${_VAR_NAME})
155                message(FATAL_ERROR "Attempt to translate undefined variable \"${_VAR_NAME}\"")
156            endif()
157
158            string(SUBSTRING "${_VAR_NAME}" ${_PREFIX_LENGT} -1 _prop)
159            _prc_translate(PROP "${_prop}" VALUE ${${_VAR_NAME}} RES _res)
160
161            if(NOT "${_res}" STREQUAL "")
162                list(GET _res 0 _global_var_name)
163                list(GET _res 1 _global_var_value)
164                list(APPEND ${_MY_PARAMS_PREFIX}_${_global_var_name} ${_global_var_value})
165                if (NOT "${_MY_PARAMS_PREFIX}_${_global_var_name}" IN_LIST _RES)
166                    list(APPEND _RES "${_MY_PARAMS_PREFIX}_${_global_var_name}")
167                endif()
168            endif()
169        endforeach()
170    else()
171        foreach(_prop IN LISTS PROPERTYCOPY_DEFAULT_PROPERTY_LIST)
172            set(_VAR_NAME "${_MY_PARAMS_PREFIX}_${_prop}")
173            # Is the variable holding the value of the property available?
174            if (DEFINED ${_VAR_NAME})
175                _prc_translate(PROP "${_prop}" VALUE ${${_VAR_NAME}} RES _res)
176                if(NOT "${_res}" STREQUAL "")
177                    list(GET _res 0 _global_var_name)
178                    list(GET _res 1 _global_var_value)
179                    list(APPEND ${_MY_PARAMS_PREFIX}_${_global_var_name} ${_global_var_value})
180                    if (NOT "${_MY_PARAMS_PREFIX}_${_global_var_name}" IN_LIST _RES)
181                        list(APPEND _RES "${_MY_PARAMS_PREFIX}_${_global_var_name}")
182                    endif()
183                endif()
184            endif()
185        endforeach()
186    endif()
187
188    if (_MY_PARAMS_TO_LIST)
189        foreach(_list_name IN LISTS _RES)
190            set(${_list_name} ${${_list_name}} PARENT_SCOPE)
191        endforeach()
192        set(${_MY_PARAMS_RES} ${_RES} PARENT_SCOPE)
193    else()
194        foreach(_list_name IN LISTS _RES)
195            string(SUBSTRING "${_list_name}" ${_PREFIX_LENGT} -1 _short_name)
196            string(REPLACE ";" " " _list_value "${${_list_name}}")
197            string(APPEND _STRING_RES "set(${_short_name} \"\${${_short_name}} ${_list_value}\" CACHE STRING \"\" FORCE)\n")
198        endforeach()
199        set(${_MY_PARAMS_RES} ${_STRING_RES} PARENT_SCOPE)
200    endif()
201endfunction()
202
203#[===[.rst:
204.. cmake:command:: translate_value_as_property
205
206  .. code-block:: cmake
207
208    translate_value_as_property(VALUE "/foo/bar/include;/foo/bar/include1"
209                                PROPERTY INTERFACE_INCLUDE_DIRECTORIES
210                                RES _cmake_fragment)
211
212  Translate a value as the specified property would be. Can be used to translate variables not saved
213  with :command:`save_interface_target_properties`
214
215  Inputs:
216  ``VALUE``
217    Value to be converted.
218  ``PROPERTY``
219    The interface property to set conversion type.
220  Outputs:
221  ``RES``
222    Name of variable to write result string to.
223#]===]
224function(translate_value_as_property)
225    set(_OPTIONS_ARGS)
226    set(_ONE_VALUE_ARGS VALUE PROPERTY RES)
227    set(_MULTI_VALUE_ARGS)
228    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
229
230    if (NOT DEFINED _MY_PARAMS_VALUE)
231        message(FATAL_ERROR "Mandatory parameter VALUE is not defined.")
232    endif()
233    if (NOT DEFINED _MY_PARAMS_RES)
234        message(FATAL_ERROR "Mandatory parameter RES is not defined.")
235    endif()
236    if (NOT DEFINED _MY_PARAMS_PROPERTY)
237        message(FATAL_ERROR "Mandatory parameter PROPERTY is not defined.")
238    endif()
239
240    set(A_${_MY_PARAMS_PROPERTY} ${_MY_PARAMS_VALUE})
241    translate_interface_target_properties(PREFIX A RES _cmake_fragment
242            VARS A_${_MY_PARAMS_PROPERTY})
243    set(${_MY_PARAMS_RES} ${_cmake_fragment} PARENT_SCOPE)
244endfunction()
245
246#[===[.rst:
247.. cmake:command:: unset_saved_properties
248
249  .. code-block:: cmake
250
251    unset_saved_properties("LIBC")
252
253  Unset saved properties. For cleaning up the variable name space.
254
255  Inputs:
256  ``PREFIX``
257    Prefix to use for output variable names.
258#]===]
259macro(unset_saved_properties PREFIX)
260    foreach(_prc_prop IN LISTS PROPERTYCOPY_DEFAULT_PROPERTY_LIST )
261        set(_PRC_VAR_NAME ${PREFIX}_${_prc_prop})
262        unset(${_PRC_VAR_NAME})
263    endforeach()
264    unset(_PRC_VAR_NAME)
265    unset(_prc_prop)
266endmacro()
267
268#[===[.rst:
269.. cmake:command:: unset_translated_lists
270
271  .. code-block:: cmake
272
273    unset_translated_lists(_lists)
274
275  Unset saved properties. Can be used for cleaning up the variable name space.
276
277  Inputs:
278  ``LISTVAR``
279    Prefix to use for output variable names.
280#]===]
281macro(unset_translated_lists LISTVAR)
282    foreach(_list_name IN LISTS ${LISTVAR} )
283        unset(${_list_name})
284    endforeach()
285    unset(_list_name)
286endmacro()
287#[===[.rst:
288.. cmake:command:: print_saved_properties
289
290  .. code-block:: cmake
291
292    print_saved_properties(PREFIX LIBC)
293
294  Print the value of all target interface properties saved with the specified prefix.
295  Can be used for debugging.
296
297  Inputs:
298  ``PREFIX``
299    Prefix to use for output variable names.
300#]===]
301function(print_saved_properties)
302    set(_OPTIONS_ARGS)
303    set(_ONE_VALUE_ARGS PREFIX)
304    set(_MULTI_VALUE_ARGS )
305    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
306
307    if (NOT DEFINED _MY_PARAMS_PREFIX)
308        message(FATAL_ERROR "Mandatory parameter PREFIX is not defined.")
309    endif()
310    string(LENGTH "${_MY_PARAMS_PREFIX}_" _PREFIX_LENGT)
311
312    message(STATUS "Properties saved with prefix \"${_MY_PARAMS_PREFIX}\"")
313    foreach(_prop IN LISTS PROPERTYCOPY_DEFAULT_PROPERTY_LIST )
314        set(_VAR_NAME "${_MY_PARAMS_PREFIX}_${_prop}")
315        string(SUBSTRING "${_VAR_NAME}" ${_PREFIX_LENGT} -1 _prop)
316        if (NOT DEFINED ${_VAR_NAME})
317            set(_value "<Not set.>")
318        else()
319            set(_value ${${_VAR_NAME}})
320        endif()
321        message(STATUS "    ${_prop}:${_value}")
322    endforeach()
323endfunction()
324
325#[===[.rst:
326.. cmake:command:: print_translated_lists
327
328  .. code-block:: cmake
329
330    print_translated_lists(PREFIX LIBC)
331
332  Print the value of all lists translated from interface properties by calling
333  translate_interface_target_properties() with TO_LISTS set.
334  Can be used for debugging.
335
336  Inputs:
337  ``LIST``
338    Name of list of lists set by :command:`translate_interface_target_properties`
339#]===]
340function(print_translated_lists)
341    set(_OPTIONS_ARGS)
342    set(_ONE_VALUE_ARGS LIST)
343    set(_MULTI_VALUE_ARGS )
344    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
345
346    if (NOT DEFINED _MY_PARAMS_LIST)
347        message(FATAL_ERROR "Mandatory parameter LIST is not defined.")
348    endif()
349
350    message(STATUS "Translated lists from \"${_MY_PARAMS_LIST}\"")
351    foreach(_list IN LISTS ${_MY_PARAMS_LIST})
352        message(STATUS "   ${_list}=${${_list}}")
353    endforeach()
354endfunction()
355
356# These properties are cmake specific and can not be translated.
357# INTERFACE_COMPILE_FEATURES, INTERFACE_LINK_DEPENDS, INTERFACE_SOURCES
358# LINK_INTERFACE_LIBRARIES
359
360# Translate target property to command line switch.
361function(_prc_translate)
362    set(_OPTIONS_ARGS)
363    set(_ONE_VALUE_ARGS PROP RES)
364    set(_MULTI_VALUE_ARGS VALUE)
365    cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN})
366
367    if ("${_MY_PARAMS_VALUE}" STREQUAL "")
368        set(_res "")
369    else()
370        if (_MY_PARAMS_PROP STREQUAL INTERFACE_INCLUDE_DIRECTORIES)
371            _prc_translate_include_list("${_MY_PARAMS_VALUE}" _res)
372        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
373            _prc_translate_system_include_list("${_MY_PARAMS_VALUE}" _res)
374        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_COMPILE_DEFINITIONS)
375            _prc_translate_macro_list("${_MY_PARAMS_VALUE}" _res)
376        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_COMPILE_OPTIONS)
377            _prc_translate_compile_option_list("${_MY_PARAMS_VALUE}" _res)
378        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_LINK_OPTIONS)
379            _prc_translate_link_option_list("${_MY_PARAMS_VALUE}" _res)
380        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_LINK_DIRECTORIES)
381            _prc_translate_link_directory_list("${_MY_PARAMS_VALUE}" _res)
382        elseif(_MY_PARAMS_PROP STREQUAL INTERFACE_LINK_LIBRARIES)
383            _prc_translate_link_library_list("${_MY_PARAMS_VALUE}" _res)
384        else()
385            message(FATAL_ERROR "Can not translate target property \"${_MY_PARAMS_PROP}\" to global setting.")
386        endif()
387    endif()
388    set(${_MY_PARAMS_RES} "${_res}" PARENT_SCOPE)
389endfunction()
390
391# Translate list of include directories to compiler flags.
392function(_prc_translate_include_list VALUE OUT)
393    if(NOT "${VALUE}" STREQUAL "")
394        string(REPLACE ";" " ${CMAKE_INCLUDE_FLAG_C} " _tmp "${VALUE}")
395    else()
396        set(_tmp "")
397    endif()
398    set(${OUT} "CMAKE_C_FLAGS_INIT;${CMAKE_INCLUDE_FLAG_C} ${_tmp}" PARENT_SCOPE)
399endfunction()
400
401# Translate list of system include directories to compiler flags.
402function(_prc_translate_system_include_list VALUE OUT)
403    if(NOT "${VALUE}" STREQUAL "")
404        string(REPLACE ";" " ${CMAKE_INCLUDE_SYSTEM_FLAG_C} " _tmp "${VALUE}")
405    else()
406        set(_tmp "")
407    endif()
408    set(${OUT} "CMAKE_C_FLAGS_INIT;${CMAKE_INCLUDE_SYSTEM_FLAG_C} ${_tmp}" PARENT_SCOPE)
409endfunction()
410
411# Translate list of C macro definitions to compiler flags.
412function(_prc_translate_macro_list VALUE OUT)
413    if(NOT "${VALUE}" STREQUAL "")
414        string(REPLACE ";" " -D " _tmp "${VALUE}")
415    else()
416        set(_tmp "")
417    endif()
418    set(${OUT} "CMAKE_C_FLAGS_INIT;-D ${_tmp}" PARENT_SCOPE)
419endfunction()
420
421# Translate list of compilation options to compiler flags.
422function(_prc_translate_compile_option_list VALUE OUT)
423    if(NOT "${VALUE}" STREQUAL "")
424        string(REPLACE ";" " " _tmp "${VALUE}")
425    else()
426        set(_tmp "")
427    endif()
428    set(${OUT} "CMAKE_C_FLAGS_INIT;${_tmp}" PARENT_SCOPE)
429endfunction()
430
431# Translate list of link options to linker flags.
432function(_prc_translate_link_option_list VALUE OUT)
433    if(NOT "${VALUE}" STREQUAL "")
434        string(REPLACE ";" " " _tmp "${VALUE}")
435    else()
436        set(_tmp "")
437    endif()
438    set(${OUT} "CMAKE_EXE_LINKER_FLAGS_INIT;${_tmp}" PARENT_SCOPE)
439endfunction()
440
441# Translate list of linker search paths to linker flags.
442function(_prc_translate_link_directory_list VALUE OUT)
443    if(NOT "${VALUE}" STREQUAL "")
444        string(REPLACE ";" " ${CMAKE_LIBRARY_PATH_FLAG} " _tmp "${VALUE}")
445    else()
446        set(_tmp "")
447    endif()
448    set(${OUT} "CMAKE_EXE_LINKER_FLAGS_INIT;${CMAKE_LIBRARY_PATH_FLAG} ${_tmp}" PARENT_SCOPE)
449endfunction()
450
451# Translate list of libraries to linker flags.
452function(_prc_translate_link_library_list VALUE OUT)
453    if(NOT "${VALUE}" STREQUAL "")
454        string(REPLACE ";" " ${CMAKE_LINK_LIBRARY_FLAG} " _tmp "${VALUE}")
455    else()
456        set(_tmp "")
457    endif()
458    set(${OUT} "CMAKE_EXE_LINKER_FLAGS_INIT;${CMAKE_LINK_LIBRARY_FLAG} ${_tmp}" PARENT_SCOPE)
459endfunction()
460