mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
build-system: include soc_caps defines into kconfig
Adds gen_soc_caps_kconfig.py which parses the soc caps (soc_caps.h) into a format that can be included in kconfig.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Generate kconfig variables from SoC caps headers
|
||||
|
||||
`gen_soc_caps_config.py` is used to turn defines from `soc_caps.h` into kconfig variables that can be included and used both in menuconfig and during the build process
|
||||
|
||||
The script will automatically run as part of the pre-commit hook if any of the `soc_caps` related files have been modified.
|
||||
|
||||
To run it manually from IDF_PATH run:
|
||||
|
||||
`python tools/gen_soc_caps_kconfig/gen_soc_caps_kconfig.py 'components/soc/*/include/soc/'`
|
||||
|
||||
The results are `kconfig.soc_caps.in`-files containing the bool/int/hex/string values defined in the `soc_caps.h` file.
|
||||
|
||||
This target specific kconfig file is then imported in `$IDF_PATH/Kconfig`.
|
||||
|
||||
# Tips
|
||||
Defines that are 0 or 1 will automatically be interpreted as Booleans. If it's necessary to have this variable available as an integer instead then this can be achieved by adding an `U` suffix when defining it. E.g:
|
||||
|
||||
`#define SOC_I2C_NUM (1U)`
|
||||
|
||||
|
||||
The script is not able to evaluate expressions, e.g X*Y. If you need a variable to be available in kconfig it needs to be a simple assignment expression.
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""
|
||||
Generate Kconfig.soc_caps.in with defines from soc_caps.h
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import io
|
||||
import logging
|
||||
import sys
|
||||
from difflib import unified_diff
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
from string import Template
|
||||
|
||||
import pyparsing
|
||||
from pyparsing import (CaselessLiteral, Char, Combine, Group, Literal, OneOrMore, # pylint: disable=unused-import
|
||||
Optional, ParserElement, QuotedString, Word, alphas, hexnums, nums)
|
||||
|
||||
pyparsing.usePackrat = True
|
||||
|
||||
try:
|
||||
import typing # noqa: F401 # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class KconfigWriter():
|
||||
PREAMBLE = inspect.cleandoc('''
|
||||
#####################################################
|
||||
# This file is auto-generated from SoC caps
|
||||
# using gen_soc_caps_kconfig.py, do not edit manually
|
||||
#####################################################
|
||||
''')
|
||||
|
||||
KCONFIG_ENTRY_TEMPLATE = Template(
|
||||
inspect.cleandoc('''
|
||||
config $name
|
||||
$entry_type
|
||||
default $value
|
||||
'''))
|
||||
|
||||
def __init__(self): # type: () -> None
|
||||
self.entries = set('')
|
||||
|
||||
self.kconfig_text = io.StringIO('')
|
||||
self.kconfig_text.write(self.PREAMBLE)
|
||||
|
||||
def add_entry(self, name, entry_type, value): # type: (str, str, typing.Any) -> None
|
||||
|
||||
if name in self.entries:
|
||||
logging.info('Duplicate entry: {}'.format(name))
|
||||
return
|
||||
|
||||
self.entries.add(name)
|
||||
self.kconfig_text.write('\n\n')
|
||||
|
||||
# Format values for kconfig
|
||||
if entry_type == 'bool':
|
||||
value = 'y' if value else 'n'
|
||||
elif entry_type == 'string':
|
||||
value = '"' + value + '"'
|
||||
|
||||
entry = self.KCONFIG_ENTRY_TEMPLATE.substitute(name=name, entry_type=entry_type, value=value)
|
||||
self.kconfig_text.write(entry)
|
||||
|
||||
def update_file(self, kconfig_path, always_write): # type: (Path, bool) -> bool
|
||||
|
||||
try:
|
||||
with open(kconfig_path, 'r') as f:
|
||||
old_content = f.readlines()
|
||||
except FileNotFoundError:
|
||||
old_content = ['']
|
||||
|
||||
self.kconfig_text.seek(0)
|
||||
new_content = self.kconfig_text.readlines()
|
||||
new_content[-1] += '\n' # Add final newline to end of file
|
||||
|
||||
file_needs_update = always_write
|
||||
|
||||
# Check if file was updated and print diff for users
|
||||
diff = unified_diff(old_content, new_content, fromfile=str(kconfig_path), n=2)
|
||||
for line in diff:
|
||||
print(line, end='')
|
||||
file_needs_update = True
|
||||
|
||||
if file_needs_update:
|
||||
print('\n' + 'Updating file: {}'.format(kconfig_path))
|
||||
with open(kconfig_path, 'w') as f:
|
||||
f.writelines(new_content)
|
||||
|
||||
return file_needs_update
|
||||
|
||||
|
||||
def parse_define(define_line): # type: (str) -> typing.Any[typing.Type[ParserElement]]
|
||||
|
||||
# Group for parsing literal suffix of a numbers, e.g. 100UL
|
||||
literal_symbol = Group(CaselessLiteral('L') | CaselessLiteral('U'))
|
||||
literal_suffix = OneOrMore(literal_symbol)
|
||||
|
||||
# Define name
|
||||
name = Word(alphas, alphas + nums + '_')
|
||||
|
||||
# Define value, either a hex, int or a string
|
||||
hex_value = Combine(Literal('0x') + Word(hexnums) + Optional(literal_suffix).suppress())('hex_value')
|
||||
int_value = Word(nums)('int_value') + ~Char('.') + Optional(literal_suffix)('literal_suffix')
|
||||
str_value = QuotedString('"')('str_value')
|
||||
|
||||
# Remove optional parenthesis around values
|
||||
value = Optional('(').suppress() + (hex_value ^ int_value ^ str_value)('value') + Optional(')').suppress()
|
||||
|
||||
expr = '#define' + Optional(name)('name') + Optional(value)
|
||||
res = expr.parseString(define_line)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def generate_defines(soc_caps_dir, filename, always_write): # type: (Path, str, bool) -> bool
|
||||
|
||||
soc_headers = list(soc_caps_dir.glob(filename))
|
||||
if soc_headers == []:
|
||||
return False
|
||||
|
||||
# Sort header files to make the generated files deterministic
|
||||
soc_headers.sort(key=lambda file: file.name)
|
||||
|
||||
defines = []
|
||||
for soc_header in soc_headers:
|
||||
defines.extend(get_defines(soc_header))
|
||||
|
||||
writer = KconfigWriter()
|
||||
|
||||
for line in defines:
|
||||
|
||||
try:
|
||||
res = parse_define(line)
|
||||
except pyparsing.ParseException:
|
||||
logging.debug('Failed to parse: {}'.format(line))
|
||||
continue
|
||||
|
||||
# Add the kconfig entry corresponding to the type we parsed
|
||||
if 'str_value' in res:
|
||||
writer.add_entry(res.name, 'string', res.str_value)
|
||||
elif 'int_value' in res:
|
||||
# defines with an integer value of 0 or 1 are
|
||||
# added as bool entries as long they have no literal suffix
|
||||
if 'literal_suffix' not in res and res.int_value == '0':
|
||||
writer.add_entry(res.name, 'bool', False)
|
||||
elif 'literal_suffix' not in res and res.int_value == '1':
|
||||
writer.add_entry(res.name, 'bool', True)
|
||||
else:
|
||||
writer.add_entry(res.name, 'int', res.int_value)
|
||||
elif 'hex_value' in res:
|
||||
writer.add_entry(res.name, 'hex', res.hex_value)
|
||||
|
||||
# Updates output if necessary
|
||||
updated = writer.update_file(Path(soc_caps_dir) / 'Kconfig.soc_caps.in', always_write)
|
||||
|
||||
return updated
|
||||
|
||||
|
||||
def get_defines(header_path): # type: (Path) -> list[str]
|
||||
defines = []
|
||||
logging.info('Reading macros from {}...'.format(header_path))
|
||||
with open(header_path, 'r') as f:
|
||||
output = f.read()
|
||||
|
||||
for line in output.split('\n'):
|
||||
line = line.strip()
|
||||
if len(line):
|
||||
defines.append(line)
|
||||
|
||||
return defines
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('-d', '--dir', help='SoC caps folder paths, support wildcards', nargs='+', default=[])
|
||||
parser.add_argument('-n', '--filename', nargs='?', default='*caps.h',
|
||||
help='SoC caps filename, support wildcards')
|
||||
parser.add_argument('-v', '--verbose', action='count', help='Increase the logging level of the script. Can be specified multiple times.')
|
||||
parser.add_argument('--always-write', help='Always generate new output files', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.verbose:
|
||||
log_level = logging.WARNING
|
||||
elif args.verbose == 1:
|
||||
log_level = logging.INFO
|
||||
else:
|
||||
log_level = logging.DEBUG
|
||||
|
||||
logging.basicConfig(level=log_level)
|
||||
|
||||
files_updated = []
|
||||
for caps_dir in args.dir:
|
||||
soc_caps_dirs = Path().glob(caps_dir)
|
||||
files_updated += [generate_defines(d, args.filename, args.always_write) for d in soc_caps_dirs if path.isdir(d)]
|
||||
|
||||
print('Updated {} files'.format(sum(files_updated)))
|
||||
|
||||
sys.exit(all(files_updated))
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import gen_soc_caps_kconfig
|
||||
except ImportError:
|
||||
sys.path.append('..')
|
||||
import gen_soc_caps_kconfig
|
||||
|
||||
|
||||
class ParserTests(unittest.TestCase):
|
||||
|
||||
def test_parse_name(self):
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_NAME (2)')
|
||||
self.assertEqual(res.name, 'TEST_NAME')
|
||||
|
||||
def test_parse_int(self):
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_INT (2)')
|
||||
self.assertEqual(res.int_value, '2')
|
||||
|
||||
def test_parse_string(self):
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_STRING "this is a test string"')
|
||||
self.assertEqual(res.str_value, 'this is a test string')
|
||||
|
||||
def test_parse_hex(self):
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_HEX 0xDEADBEEF')
|
||||
self.assertEqual(res.hex_value, '0xDEADBEEF')
|
||||
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_HEX 0x0000000')
|
||||
self.assertEqual(res.hex_value, '0x0000000')
|
||||
|
||||
def test_parse_literal_suffix(self):
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST (0x200UL)')
|
||||
self.assertEqual(res.hex_value, '0x200')
|
||||
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_U 20000u')
|
||||
self.assertEqual(res.int_value, '20000')
|
||||
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_U 329000LL')
|
||||
self.assertEqual(res.int_value, '329000')
|
||||
|
||||
def test_parse_float(self):
|
||||
# Kconfig doesnt support floats, should not be parsed as anything
|
||||
res = gen_soc_caps_kconfig.parse_define('#define TEST_FLOAT (3.14)')
|
||||
self.assertEqual(res.value, '')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Running gen_soc_caps_config tests...')
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user