blob: c18acc3ca970e20d286551359737caa315f322c8 [file] [log] [blame]
Jianliang Shen002edf92022-11-01 17:46:16 +08001#-------------------------------------------------------------------------------
2# Copyright (c) 2022, Arm Limited. All rights reserved.
3# SPDX-License-Identifier: BSD-3-Clause
4#
5#-------------------------------------------------------------------------------
6
7import argparse
8import logging
9import os
10import re
Jianliang Shen002edf92022-11-01 17:46:16 +080011
12from kconfiglib import Kconfig
13import menuconfig
14import guiconfig
15
16# NOTE: in_component_label is related with Kconfig menu prompt.
17in_component_label = 'TF-M component configs'
18
19def parse_args():
20 parser = argparse.ArgumentParser(description=\
21 'TF-M Kconfig tool generates CMake configurations and header file \
22 component configurations. Terminal UI and GUI can help quickly \
23 configurate TF-M build options.')
24
25 parser.add_argument(
26 '-k', '--kconfig-file',
27 dest = 'kconfig_file',
28 required = True,
29 help = 'The Top-level Kconfig file'
30 )
31
32 parser.add_argument(
33 '-o', '--output-path',
34 dest = 'output_path',
35 required = True,
36 help = 'The output file folder'
37 )
38
39 parser.add_argument(
40 '-u', '--ui',
41 dest = 'ui',
42 required = False,
43 default = None,
44 choices = ['gui', 'tui'],
45 help = 'Which config UI to display'
46 )
47
48 parser.add_argument(
49 '-p', '--platform-path',
50 dest = 'platform_path',
51 required = False,
Jianliang Shen2b24f252022-11-25 11:08:54 +080052 help = 'The platform path which contains specific Kconfig and defconfig files'
Jianliang Shen002edf92022-11-01 17:46:16 +080053 )
54
55 args = parser.parse_args()
56
57 return args
58
59def generate_file(dot_config):
60 '''
61 The .config file is the generated result from Kconfig files. It contains
62 the set and un-set configs and their values.
63
64 TF-M splits the configs to build options and component options. The former
65 will be written into CMake file. The latter are all under a menu which has
66 the prompt which contains in_component_label. These configs will be written
67 into header file.
68 '''
69 cmake_file, header_file = 'project_config.cmake', 'project_config.h'
70 in_component_options, menu_start = False, False
71
72 '''
73 The regular expression is used to parse the text like:
74 - CONFIG_FOO=val
75 - # CONFIG_FOO is not set
Jianliang Shen2b24f252022-11-25 11:08:54 +080076 The 'FOO' will be saved into the name part of groupdict, and the 'val' will
Jianliang Shen002edf92022-11-01 17:46:16 +080077 be saved into the 'val' part of groupdict.
78 '''
79 pattern_set = re.compile('CONFIG_(?P<name>[A-Za-z|_|0-9]*)=(?P<val>\S+)')
80 pattern_not_set = re.compile('# CONFIG_(?P<name>[A-Za-z|_|0-9]*) is not set')
81
82 with open(cmake_file, 'w') as f_cmake, open(header_file, 'w') as f_header, \
83 open(dot_config, 'r') as f_config:
84
85 for line in f_config:
86 '''
87 Extract in_component_options flag from start line and end line
88 which has the in_component_label.
89 '''
90 if line.startswith('# ' + in_component_label):
91 in_component_options = True
92 continue
93 if line.startswith('end of ' + in_component_label):
94 in_component_options =False
95 continue
96
97 '''
98 Extract the menu prompt. It forms like:
99 ...
100 #
101 # FOO Module
102 #
103 ...
104 Here get the text 'FOO Module', and write it as comment in
105 output files.
106 '''
107 if line == '#\n' and not menu_start:
108 menu_start = True
109 continue
110 if line == '#\n' and menu_start:
111 menu_start = False
112 continue
113
114 # Write the menu prompt.
115 if menu_start and not in_component_options:
116 f_cmake.write('\n# {}\n'.format(line[2:-1]))
117 continue
118 if menu_start and in_component_options:
119 f_header.write('\n/* {} */\n'.format(line[2:-1]))
120 continue
121
122 '''
123 Parse dot_config text by regular expression and get the config's
124 name, value and type. Then write the result into CMake and
125 header files.
126
127 CONFIG_FOO=y
128 - CMake: set(FOO ON CACHE BOOL '')
129 - Header: #define FOO 1
130 CONFIG_FOO='foo'
131 - CMake: set(FOO 'foo' CACHE STRING '')
132 - Header: #define FOO 'foo'
133 # CONFIG_FOO is not set
134 - CMake: set(FOO OFF CACHE BOOL '')
135 - Header: #define FOO 0
136 '''
137 name, cmake_type, cmake_val, header_val = '', '', '', ''
138
139 # Search the configs set by Kconfig.
140 ret = pattern_set.match(line)
141 if ret:
142 name = ret.groupdict()['name']
143 val = ret.groupdict()['val']
144 if val == 'y':
145 cmake_val = 'ON'
146 cmake_type = 'BOOL'
147 header_val = '1'
148 else:
149 cmake_val = val
150 cmake_type = 'STRING'
151 header_val = val
152
153 # Search the not set configs.
154 ret = pattern_not_set.match(line)
155 if ret:
156 name = ret.groupdict()['name']
157 cmake_val = 'OFF'
158 cmake_type = 'BOOL'
159 header_val = '0'
160
161 # Write the result into cmake and header files.
162 if name and not in_component_options:
163 f_cmake.write('set({:<45} {:<15} CACHE {:<6} "")\n'.
164 format(name, cmake_val, cmake_type))
165 if name and in_component_options:
166 f_header.write('#define {:<45} {}\n'.format(name, header_val))
167
168 logging.info('TF-M build configs saved to \'{}\''.format(cmake_file))
169 logging.info('TF-M component configs saved to \'{}\''.format(header_file))
170
171if __name__ == '__main__':
172 logging.basicConfig(format='[%(filename)s] %(levelname)s: %(message)s',
173 level = logging.INFO)
174
175 args = parse_args()
176
177 # dot_config has a fixed name. Do NOT rename it.
178 dot_config = '.config'
179 def_config = ''
180 mtime_prv = 0
181
Jianliang Shen2b24f252022-11-25 11:08:54 +0800182 if args.platform_path and os.path.exists(args.platform_path):
Jianliang Shen002edf92022-11-01 17:46:16 +0800183 platform_abs_path = os.path.abspath(args.platform_path)
Jianliang Shen2b24f252022-11-25 11:08:54 +0800184 def_config = os.path.join(platform_abs_path, 'defconfig')
Jianliang Shen002edf92022-11-01 17:46:16 +0800185
186 # Pass environment variable to Kconfig to load extra Kconfig file.
187 os.environ['PLATFORM_PATH'] = platform_abs_path
188
Jianliang Shen002edf92022-11-01 17:46:16 +0800189 # Load Kconfig file. kconfig_file is the root Kconfig file. The path is
190 # input by users from the command.
191 tfm_kconfig = Kconfig(args.kconfig_file)
192
Jianliang Shen2b24f252022-11-25 11:08:54 +0800193 if not os.path.exists(args.output_path):
194 os.mkdir(args.output_path)
195
196 # Change program execution path to the output folder path.
197 os.chdir(args.output_path)
198
Jianliang Shen002edf92022-11-01 17:46:16 +0800199 if os.path.exists(dot_config):
200 # Load .config which contains the previous configurations.
201 mtime_prv = os.stat(dot_config).st_mtime
202 tfm_kconfig.load_config(dot_config)
203 logging.info('Load configs from \'{}\''.format(dot_config))
204 elif os.path.exists(def_config):
205 # Load platform specific defconfig if exists.
206 tfm_kconfig.load_config(def_config)
207 logging.info('Load configs from \'{}\''.format(def_config))
208
Jianliang Shen002edf92022-11-01 17:46:16 +0800209 # UI options
210 if args.ui == 'tui':
211 menuconfig.menuconfig(tfm_kconfig)
212 elif args.ui == 'gui':
213 guiconfig.menuconfig(tfm_kconfig)
214 else:
215 # Save .config if UI is not created.
216 # The previous .config will be saved as .config.old.
217 tfm_kconfig.write_config(dot_config)
218
219 # Generate output files if .config has been changed.
220 if os.path.exists(dot_config) and os.stat(dot_config).st_mtime != mtime_prv:
221 generate_file(dot_config)