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