1#-------------------------------------------------------------------------------
2# Copyright (c) 2022-2023, Arm Limited. All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5#
6#-------------------------------------------------------------------------------
7
8# Load multiple config files and merge into one, generates CMake config file and config header file.
9# The first loaded configs would be overridden by later ones. That's how the "merge" works.
10# Check CONFIG_FILE_LIST for the loading order.
11# Configurations not set by any of the config files would be set to the default values in Kconfig
12# files with dependencies respected.
13# If a ".config" already exists in the output folder, then the CONFIG_FILE_LIST is ignored.
14# For more details, check the kconfig_system.rst.
15
16set(KCONFIG_OUTPUT_DIR  ${CMAKE_BINARY_DIR}/kconfig)
17
18set(DOTCONFIG_FILE      ${KCONFIG_OUTPUT_DIR}/.config)
19set(ROOT_KCONFIG        ${CMAKE_SOURCE_DIR}/Kconfig)
20set(PLATFORM_KCONFIG    ${TARGET_PLATFORM_PATH}/Kconfig)
21
22# This function parses the input ${cmake_file} to get normal CMake variables and their values in the
23# format of "set(_VAR_ _VALUE_)". The format could be split into multiple lines.
24# Note that CMake does not allow the "(" to be in a different line as "set" and no white spaces are
25# recommanded between "set" and "(". So the function only covers format of "set(".
26function(convert_normal_cmake_config_to_kconfig cmake_file out_var)
27    # Operate on a local var and write back to the "out_var" later.
28    set(local_var "")
29
30    # Read out the strings of the file. Binary data in the file are ignored
31    file(STRINGS ${cmake_file} CONTENTS)
32
33    # Exclude lines of comments (started with "#")
34    set(CONTENTS_WITHOUT_COMMENTS "")
35
36    foreach(LINE ${CONTENTS})
37        string(REGEX MATCH "^#.*" OUT_STRING ${LINE})
38        if(NOT OUT_STRING)
39            string(APPEND CONTENTS_WITHOUT_COMMENTS "${LINE}\n")
40        endif()
41
42        string(REGEX MATCH "^include\\((.*)\\)$" OUT_STRING ${LINE})
43        if(OUT_STRING AND CMAKE_MATCH_COUNT EQUAL 1)
44            message(FATAL_ERROR "Including another file in config file is not supported yet: ${LINE}")
45        endif()
46    endforeach()
47
48    # Search for strings match set(_VAR_ _VALUE_) with support of multi-line format
49    string(REGEX MATCHALL
50           "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*([^ \t\r\n]*)[ \t\r\n]*\\)"
51           OUT_STRINGS ${CONTENTS_WITHOUT_COMMENTS})
52
53    foreach(MATCHED_ITEM ${OUT_STRINGS})
54        # Try to convert CMake format to Kconfig one
55        # If the format does not match, the content will not be changed and fall down to the next
56
57        # Bool types
58        string(REGEX REPLACE
59               "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*(TRUE|ON)[ \t\r\n]*\\)"
60               "config \\1\n default y\n"
61               MATCHED_ITEM ${MATCHED_ITEM})
62        string(REGEX REPLACE
63               "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*(FALSE|OFF)[ \t\r\n]*\\)"
64               "config \\1\n default n\n"
65               MATCHED_ITEM ${MATCHED_ITEM})
66        # Hex int
67        string(REGEX REPLACE
68               "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*(0x[0-9a-fA-F]+[ \t\r\n]*)\\)"
69               "config \\1\n default \\2\n"
70               MATCHED_ITEM ${MATCHED_ITEM})
71        # Decimal int
72        string(REGEX REPLACE
73               "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*([0-9]+)[ \t\r\n]*\\)"
74               "config \\1\n default \\2\n"
75               MATCHED_ITEM ${MATCHED_ITEM})
76        # Quoted string
77        string(REGEX REPLACE
78               "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*(\".*\")[ \t\r\n]*\\)"
79               "config \\1\n default \\2\n"
80               MATCHED_ITEM ${MATCHED_ITEM})
81        # If none of the above matches, must be a non-quoted string
82        string(REGEX REPLACE
83               "set\\([ \t\r\n]*([A-Za-z0-9_]*)[ \t\r\n]*(.*)[ \t\r\n]*\\)"
84               "config \\1\n default \"\\2\"\n"
85               MATCHED_ITEM ${MATCHED_ITEM})
86
87        string(APPEND local_var ${MATCHED_ITEM})
88    endforeach()
89
90    set(${out_var} ${local_var} PARENT_SCOPE)
91endfunction()
92
93# This function goes through the CMake cache variables and convert them to .config format.
94# The function distinguishes command-line variables and other ones and it can only handle one of
95# them in the same time.
96function(convert_cache_config_to_dotconfig convert_cl_var out_var)
97    # Operate on a local var and write back to the out_var later
98    set(local_var "")
99
100    get_cmake_property(CACHE_VARS CACHE_VARIABLES)
101    foreach(CACHE_VAR ${CACHE_VARS})
102        get_property(HELP_STRING CACHE ${CACHE_VAR} PROPERTY HELPSTRING)
103
104        if("${HELP_STRING}" MATCHES "variable specified on the command line")
105            # Command-line variables have the help string above by default
106            set(IS_CL_VAR TRUE)
107        else()
108            set(IS_CL_VAR FALSE)
109        endif()
110
111        if((IS_CL_VAR AND NOT ${convert_cl_var}) OR (NOT IS_CL_VAR AND ${convert_cl_var}))
112            continue()
113        endif()
114
115        set(CACHE_VAR_VAL ${${CACHE_VAR}})
116        STRING(TOUPPER "${CACHE_VAR_VAL}" CACHE_VAR_VAL_UPPER)
117
118        set(CACHE_VAR "CONFIG_${CACHE_VAR}")
119
120        set(KCONFIG_OPTION_ITEM "")
121
122        # False CMAKE values
123        if(CACHE_VAR_VAL_UPPER STREQUAL "OFF" OR CACHE_VAR_VAL_UPPER STREQUAL "FALSE")
124            set(KCONFIG_OPTION_ITEM "${CACHE_VAR}=n\r\n")
125        # True CMAKE Values
126        elseif(CACHE_VAR_VAL_UPPER STREQUAL "ON" OR CACHE_VAR_VAL_UPPER STREQUAL "TRUE")
127            set(KCONFIG_OPTION_ITEM "${CACHE_VAR}=y\r\n")
128        # Non-quoted values (hex and decimal numbers)
129        elseif(CACHE_VAR_VAL MATCHES "^0x[a-fA-F0-9]+$" OR CACHE_VAR_VAL MATCHES "^[0-9]+$" )
130            set(KCONFIG_OPTION_ITEM "${CACHE_VAR}=${CACHE_VAR_VAL}\r\n")
131        # Everything else is a quoted string
132        else()
133            if(${CACHE_VAR} STREQUAL "CONFIG_TEST_PSA_API" AND CACHE_VAR_VAL_UPPER)
134                # Turn on the corresponding "choice" option for psa-arch-test
135                list(APPEND _LEGAL_PSA_API_TEST_LIST "IPC" "CRYPTO" "INITIAL_ATTESTATION" "INTERNAL_TRUSTED_STORAGE" "PROTECTED_STORAGE" "STORAGE")
136                list(FIND _LEGAL_PSA_API_TEST_LIST ${CACHE_VAR_VAL_UPPER} _RET_VAL)
137                if(NOT ${_RET_VAL} EQUAL -1)
138                    set(KCONFIG_OPTION_ITEM "CONFIG_PSA_API_TEST_${CACHE_VAR_VAL_UPPER}=y\r\n")
139
140                    if(${CACHE_VAR_VAL_UPPER} STREQUAL "IPC")
141                        # PSA API IPC test requires IPC model to be enabled while
142                        # the CONFIG_TFM_SPM_BACKEND_IPC cannot be selected or implied because it is a choice.
143                        # It can be only enabled in a Kconfig config file. So append it here.
144                        string(APPEND KCONFIG_OPTION_ITEM "CONFIG_CONFIG_TFM_SPM_BACKEND_IPC=y\r\n")
145                    endif()
146                endif()
147            elseif(${CACHE_VAR} STREQUAL "CONFIG_CONFIG_TFM_SPM_BACKEND")
148                # Turn on the corresponding "choice" option for SPM backend
149                set(KCONFIG_OPTION_ITEM "CONFIG_CONFIG_TFM_SPM_BACKEND_${CACHE_VAR_VAL_UPPER}=y\r\n")
150            else()
151                set(KCONFIG_OPTION_ITEM "${CACHE_VAR}=\"${CACHE_VAR_VAL}\"\r\n")
152            endif()
153        endif()
154
155        string(APPEND local_var ${KCONFIG_OPTION_ITEM})
156    endforeach()
157
158    set(${out_var} ${local_var} PARENT_SCOPE)
159endfunction()
160
161# Initialize the .cl_config
162set(COMMAND_LINE_CONFIG_TO_FILE ${KCONFIG_OUTPUT_DIR}/.cl_config)
163file(REMOVE ${COMMAND_LINE_CONFIG_TO_FILE})
164
165# Initialize the .cache_var_config
166set(CACHE_VAR_CONFIG_FILE ${KCONFIG_OUTPUT_DIR}/.cache_var_config)
167file(REMOVE ${CACHE_VAR_CONFIG_FILE})
168
169if(NOT EXISTS ${PLATFORM_KCONFIG} AND NOT EXISTS ${DOTCONFIG_FILE})
170    # Parse platform's preload.cmake and config.cmake to get config options.
171    set(PLATFORM_KCONFIG_OPTIONS "")
172    set(PLATFORM_KCONFIG ${KCONFIG_OUTPUT_DIR}/platform/Kconfig)
173
174    convert_normal_cmake_config_to_kconfig(${TARGET_PLATFORM_PATH}/preload.cmake PLATFORM_KCONFIG_OPTIONS)
175    file(WRITE ${PLATFORM_KCONFIG} ${PLATFORM_KCONFIG_OPTIONS})
176
177    if(EXISTS ${TARGET_PLATFORM_PATH}/config.cmake)
178        include(${TARGET_PLATFORM_PATH}/config.cmake)
179        convert_normal_cmake_config_to_kconfig(${TARGET_PLATFORM_PATH}/config.cmake PLATFORM_KCONFIG_OPTIONS)
180        file(APPEND ${PLATFORM_KCONFIG} ${PLATFORM_KCONFIG_OPTIONS})
181
182        set(PLATFORM_CMAKE_CONFIGS "")
183        set(CONVERT_CL_VAR FALSE)
184        convert_cache_config_to_dotconfig(CONVERT_CL_VAR PLATFORM_CMAKE_CONFIGS)
185        file(APPEND ${CACHE_VAR_CONFIG_FILE} ${PLATFORM_CMAKE_CONFIGS})
186    endif()
187endif()
188get_filename_component(PLATFORM_KCONFIG_PATH ${PLATFORM_KCONFIG} DIRECTORY)
189
190# Build type Kconfig file, for example 'Kconfig.minsizerel', the suffix passed
191# by Kconfig environment variables and it shall be lowercase.
192string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWERCASE)
193
194# TF-M profile config file
195if(TFM_PROFILE)
196    set(TFM_PROFILE_KCONFIG_FILE ${CMAKE_SOURCE_DIR}/config/profile/${TFM_PROFILE}.conf)
197    if(NOT EXISTS ${TFM_PROFILE_KCONFIG_FILE})
198        message(FATAL_ERROR "No such file: ${TFM_PROFILE_KCONFIG_FILE}, please check ${TFM_PROFILE} is right.")
199    endif()
200
201    set(TFM_PROFILE_TEST_KCONFIG_FILE ${CMAKE_SOURCE_DIR}/lib/ext/tf-m-tests/${TFM_PROFILE}_test.conf)
202    if(NOT EXISTS ${TFM_PROFILE_TEST_KCONFIG_FILE})
203        message(FATAL_ERROR "No such file: ${TFM_PROFILE_TEST_KCONFIG_FILE}, please check ${TFM_PROFILE} is right.")
204    endif()
205endif()
206
207# Parse command-line variables
208set(CL_CONFIGS "")
209set(CONVERT_CL_VAR TRUE)
210convert_cache_config_to_dotconfig(CONVERT_CL_VAR CL_CONFIGS)
211file(APPEND ${COMMAND_LINE_CONFIG_TO_FILE} ${CL_CONFIGS})
212
213if(NOT EXISTS ${CACHE_VAR_CONFIG_FILE})
214    set(CACHE_VAR_CONFIG_FILE "")
215endif()
216
217# User customized config file
218if(DEFINED KCONFIG_CONFIG_FILE AND NOT EXISTS ${KCONFIG_CONFIG_FILE})
219    message(FATAL_ERROR "No such file: ${KCONFIG_CONFIG_FILE}")
220endif()
221
222# Note the order of CONFIG_FILE_LIST, as the first loaded configs would be
223# overridden by later ones.
224list(APPEND CONFIG_FILE_LIST
225            ${TFM_PROFILE_KCONFIG_FILE}
226            ${TFM_PROFILE_TEST_KCONFIG_FILE}
227            ${CACHE_VAR_CONFIG_FILE}
228            ${KCONFIG_CONFIG_FILE}
229            ${COMMAND_LINE_CONFIG_TO_FILE})
230
231# Set up ENV variables for the tfm_kconfig.py which are then consumed by Kconfig files.
232set(KCONFIG_ENV_VARS "TFM_SOURCE_DIR=${CMAKE_SOURCE_DIR} \
233                      PLATFORM_PATH=${PLATFORM_KCONFIG_PATH} \
234                      CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE_LOWERCASE}")
235
236if(MENUCONFIG)
237    # Note: Currently, only GUI menuconfig can be supported with CMake integration
238    set(MENUCONFIG_ARG "-u=gui")
239else()
240    set(MENUCONFIG_ARG "")
241endif()
242
243if(DEFINED PROJECT_CONFIG_HEADER_FILE)
244    get_property(HELP_STRING CACHE PROJECT_CONFIG_HEADER_FILE PROPERTY HELPSTRING)
245
246    # It is not supported to set PROJECT_CONFIG_HEADER_FILE while using Kconfig, either from
247    # command line or CMake files. It should be set to the file generated the Kconfig system.
248    # As this file set it itself, if the user re-run the CMake config command, the
249    # PROJECT_CONFIG_HEADER_FILE will be already defined set.
250    # So the existence of the ${DOTCONFIG_FILE} is used to indicate if it is a re-configuration.
251    if("${HELP_STRING}" MATCHES "variable specified on the command line" OR NOT EXISTS ${DOTCONFIG_FILE})
252        message(FATAL_ERROR "It is NOT supported to manually set PROJECT_CONFIG_HEADER_FILE while using Kconfig.")
253    endif()
254endif()
255
256find_package(Python3)
257
258execute_process(
259    COMMAND
260    ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/kconfig/tfm_kconfig.py
261    -k ${ROOT_KCONFIG} -o ${KCONFIG_OUTPUT_DIR}
262    --envs ${KCONFIG_ENV_VARS}
263    --config-files ${CONFIG_FILE_LIST}
264    ${MENUCONFIG_ARG}
265    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
266    RESULT_VARIABLE ret
267)
268
269if(NOT ret EQUAL 0)
270    message(FATAL_ERROR "Kconfig tool failed!")
271endif()
272
273# Component configs generated by tfm_kconfig.py
274set(PROJECT_CONFIG_HEADER_FILE ${KCONFIG_OUTPUT_DIR}/project_config.h CACHE FILEPATH "User defined header file for TF-M config")
275
276# Load project cmake configs generated by tfm_kconfig.py
277include(${KCONFIG_OUTPUT_DIR}/project_config.cmake)
278