blob: 376b5df1cdd910421f1877126c9c185cde4cd012 [file] [log] [blame]
#-------------------------------------------------------------------------------
# Copyright (c) 2022, Arm Limited. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
#
#-------------------------------------------------------------------------------
import argparse
import logging
import os
import re
from kconfiglib import Kconfig
import menuconfig
import guiconfig
# NOTE: in_component_label is related with Kconfig menu prompt.
in_component_label = 'TF-M component configs'
def parse_args():
parser = argparse.ArgumentParser(description=\
'TF-M Kconfig tool generates CMake configurations and header file \
component configurations. Terminal UI and GUI can help quickly \
configurate TF-M build options.')
parser.add_argument(
'-k', '--kconfig-file',
dest = 'kconfig_file',
required = True,
help = 'The Top-level Kconfig file'
)
parser.add_argument(
'-o', '--output-path',
dest = 'output_path',
required = True,
help = 'The output file folder'
)
parser.add_argument(
'-u', '--ui',
dest = 'ui',
required = False,
default = None,
choices = ['gui', 'tui'],
help = 'Which config UI to display'
)
parser.add_argument(
'-p', '--platform-path',
dest = 'platform_path',
required = False,
help = 'The platform path which contains specific Kconfig and defconfig files'
)
args = parser.parse_args()
return args
def generate_file(dot_config):
'''
The .config file is the generated result from Kconfig files. It contains
the set and un-set configs and their values.
TF-M splits the configs to build options and component options. The former
will be written into CMake file. The latter are all under a menu which has
the prompt which contains in_component_label. These configs will be written
into header file.
'''
cmake_file, header_file = 'project_config.cmake', 'project_config.h'
in_component_options, menu_start = False, False
'''
The regular expression is used to parse the text like:
- CONFIG_FOO=val
- # CONFIG_FOO is not set
The 'FOO' will be saved into the name part of groupdict, and the 'val' will
be saved into the 'val' part of groupdict.
'''
pattern_set = re.compile('CONFIG_(?P<name>[A-Za-z|_|0-9]*)=(?P<val>\S+)')
pattern_not_set = re.compile('# CONFIG_(?P<name>[A-Za-z|_|0-9]*) is not set')
with open(cmake_file, 'w') as f_cmake, open(header_file, 'w') as f_header, \
open(dot_config, 'r') as f_config:
for line in f_config:
'''
Extract in_component_options flag from start line and end line
which has the in_component_label.
'''
if line.startswith('# ' + in_component_label):
in_component_options = True
continue
if line.startswith('end of ' + in_component_label):
in_component_options =False
continue
'''
Extract the menu prompt. It forms like:
...
#
# FOO Module
#
...
Here get the text 'FOO Module', and write it as comment in
output files.
'''
if line == '#\n' and not menu_start:
menu_start = True
continue
if line == '#\n' and menu_start:
menu_start = False
continue
# Write the menu prompt.
if menu_start and not in_component_options:
f_cmake.write('\n# {}\n'.format(line[2:-1]))
continue
if menu_start and in_component_options:
f_header.write('\n/* {} */\n'.format(line[2:-1]))
continue
'''
Parse dot_config text by regular expression and get the config's
name, value and type. Then write the result into CMake and
header files.
CONFIG_FOO=y
- CMake: set(FOO ON CACHE BOOL '')
- Header: #define FOO 1
CONFIG_FOO='foo'
- CMake: set(FOO 'foo' CACHE STRING '')
- Header: #define FOO 'foo'
# CONFIG_FOO is not set
- CMake: set(FOO OFF CACHE BOOL '')
- Header: #define FOO 0
'''
name, cmake_type, cmake_val, header_val = '', '', '', ''
# Search the configs set by Kconfig.
ret = pattern_set.match(line)
if ret:
name = ret.groupdict()['name']
val = ret.groupdict()['val']
if val == 'y':
cmake_val = 'ON'
cmake_type = 'BOOL'
header_val = '1'
else:
cmake_val = val
cmake_type = 'STRING'
header_val = val
# Search the not set configs.
ret = pattern_not_set.match(line)
if ret:
name = ret.groupdict()['name']
cmake_val = 'OFF'
cmake_type = 'BOOL'
header_val = '0'
# Write the result into cmake and header files.
if name and not in_component_options:
f_cmake.write('set({:<45} {:<15} CACHE {:<6} "")\n'.
format(name, cmake_val, cmake_type))
if name and in_component_options:
f_header.write('#define {:<45} {}\n'.format(name, header_val))
logging.info('TF-M build configs saved to \'{}\''.format(cmake_file))
logging.info('TF-M component configs saved to \'{}\''.format(header_file))
if __name__ == '__main__':
logging.basicConfig(format='[%(filename)s] %(levelname)s: %(message)s',
level = logging.INFO)
args = parse_args()
# dot_config has a fixed name. Do NOT rename it.
dot_config = '.config'
def_config = ''
mtime_prv = 0
if args.platform_path:
platform_abs_path = os.path.abspath(args.platform_path)
if not os.path.exists(platform_abs_path):
logging.error('Platform path {} doesn\'t exist!'.format(platform_abs_path))
exit(1)
def_config = os.path.join(platform_abs_path, 'defconfig')
# Pass environment variable to Kconfig to load extra Kconfig file.
os.environ['PLATFORM_PATH'] = platform_abs_path
# Load Kconfig file. kconfig_file is the root Kconfig file. The path is
# input by users from the command.
tfm_kconfig = Kconfig(args.kconfig_file)
if not os.path.exists(args.output_path):
os.mkdir(args.output_path)
# Change program execution path to the output folder path.
os.chdir(args.output_path)
if os.path.exists(dot_config):
# Load .config which contains the previous configurations.
mtime_prv = os.stat(dot_config).st_mtime
logging.info(tfm_kconfig.load_config(dot_config))
elif os.path.exists(def_config):
# Load platform specific defconfig if exists.
logging.info(tfm_kconfig.load_config(def_config))
# UI options
if args.ui == 'tui':
menuconfig.menuconfig(tfm_kconfig)
elif args.ui == 'gui':
guiconfig.menuconfig(tfm_kconfig)
else:
# Save .config if UI is not created.
# The previous .config will be saved as .config.old.
logging.info(tfm_kconfig.write_config(dot_config))
# Generate output files if .config has been changed.
if os.path.exists(dot_config) and os.stat(dot_config).st_mtime != mtime_prv:
generate_file(dot_config)