ci(check): update check_soc_struct_headers script

This commit is contained in:
laokaiyao
2026-08-17 14:13:16 +02:00
committed by Fu Hanxi
parent 3c88b7176d
commit 27b4ffd2f4
+82 -48
View File
@@ -1,12 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# A check script that just works at the time of writing... # A check script that just works at the time of writing...
# #
# also builds a structure tree for further reference # also builds a structure tree for further reference
# #
# Input file format must be similiar to those headers generated by regtool, or this script makes no sense at all # Input file format must be similar to those headers generated by regtool, or this script makes no sense at all
# #
# Known limitation: # Known limitation:
# 1. won't accept /* ... */ /* ... */': badly behavior with multiline comment # 1. won't accept /* ... */ /* ... */': badly behavior with multiline comment
@@ -21,18 +20,18 @@
# 5. typedef volatile struct xxx{}: xxx must exists # 5. typedef volatile struct xxx{}: xxx must exists
# #
# Otherwise won't fail but warning # Otherwise won't fail but warning
import os import os
import re import re
import sys import sys
from typing import Any from typing import Any
from typing import Optional
class MemberField: class MemberField:
member_type = '' member_type = ''
bitfield = None bitfield = None
def __init__(self, m_type: str, m_bits: int=None) -> None: def __init__(self, m_type: str, m_bits: Optional[int] = None) -> None:
self.member_type = m_type self.member_type = m_type
self.bitfield = m_bits self.bitfield = m_bits
@@ -44,8 +43,8 @@ class MemberField:
def __str__(self) -> str: def __str__(self) -> str:
if self.bitfield is None: if self.bitfield is None:
return '"Field type={}"'.format(self.member_type) return f'"Field type={self.member_type}"'
return '"Field type={} bit={}"'.format(self.member_type, self.bitfield) return f'"Field type={self.member_type} bit={self.bitfield}"'
class SoCStructureHeaderChecker: class SoCStructureHeaderChecker:
@@ -56,8 +55,10 @@ class SoCStructureHeaderChecker:
# capture: type_var_name # capture: type_var_name
__REGEXP_MATCH_STRUCT_UNION_END_NAME = r'^[\s]*}[\s]*([\w\[\]\*]*)[\s]*;[\s\S]*$' __REGEXP_MATCH_STRUCT_UNION_END_NAME = r'^[\s]*}[\s]*([\w\[\]\*]*)[\s]*;[\s\S]*$'
# capture: type, name, bitfield # capture: type, name, bitfield
__REGEXP_MATCH_BITFIELD_MEMBER = (r'^[\s]*(?:(?:volatile[\s]+)|(?:))([\w\*]+)[\s]+([\w\*]+(?:(?:\[[\s\S]*\])|(?:)))' __REGEXP_MATCH_BITFIELD_MEMBER = (
r'[\s]*(?:(?:[\s]*;)|(?::[\s]*([\d]+)[\s]*;))[\s\S]*$') r'^[\s]*(?:(?:volatile[\s]+)|(?:))([\w\*]+)[\s]+([\w\*]+(?:(?:\[[\s\S]*\])|(?:)))'
r'[\s]*(?:(?:[\s]*;)|(?::[\s]*([\d]+)[\s]*;))[\s\S]*$'
)
# should be useless and can be safely deleted # should be useless and can be safely deleted
__REGEXP_MATCH_MULTILINE_COMMENT = r'^[\s]*[\/]{0,2}\*[\/]?[\s\S]*$' __REGEXP_MATCH_MULTILINE_COMMENT = r'^[\s]*[\/]{0,2}\*[\/]?[\s\S]*$'
__REGEX_MATCH_SIMPLE_VAL_FIELD = r'^[\s]*(?:(?:volatile[\s]+)|(?:))([\w]+)[\s]+([\w\[\]\*]+)[\s]*;[\s]*$' __REGEX_MATCH_SIMPLE_VAL_FIELD = r'^[\s]*(?:(?:volatile[\s]+)|(?:))([\w]+)[\s]+([\w\[\]\*]+)[\s]*;[\s]*$'
@@ -65,16 +66,16 @@ class SoCStructureHeaderChecker:
__REGEX_MATCH_ROOT_EXTERNAL = r'^[\s]*extern[\s]+([\w]+)[\s]+([\w]+)[\s]*;[\s]*$' __REGEX_MATCH_ROOT_EXTERNAL = r'^[\s]*extern[\s]+([\w]+)[\s]+([\w]+)[\s]*;[\s]*$'
__linecount = 0 __linecount = 0
__fd = None # type: Any __fd = None # type: Any
__is_eof = False __is_eof = False
# generated reference tree # generated reference tree
__ref_tree = dict() # type: dict __ref_tree = dict() # type: dict
# middle result of generated tree, shared # middle result of generated tree, shared
# named typedef, or named struct/union. referd but will not delete # named typedef, or named struct/union. referd but will not delete
__temp_ref_types = dict() # type: dict __temp_ref_types = dict() # type: dict
def __expand_type(self, member_type: str, bitfield: int=None) -> Any: def __expand_type(self, member_type: str, bitfield: Optional[int] = None) -> Any:
if member_type == 'uint32_t': if member_type == 'uint32_t':
return MemberField(member_type, bitfield) return MemberField(member_type, bitfield)
if bitfield is not None: if bitfield is not None:
@@ -84,7 +85,7 @@ class SoCStructureHeaderChecker:
return self.__temp_ref_types[member_type] return self.__temp_ref_types[member_type]
return None return None
def __getline(self, incomment:bool=False) -> Any: def __getline(self, incomment: bool = False) -> Any:
rawline = self.__fd.readline() rawline = self.__fd.readline()
if not rawline: if not rawline:
self.__is_eof = True self.__is_eof = True
@@ -95,7 +96,7 @@ class SoCStructureHeaderChecker:
pos = rawline.find('*/') pos = rawline.find('*/')
if pos != -1: if pos != -1:
# set string that is behind comment # set string that is behind comment
rawline = rawline[pos + 2:] rawline = rawline[pos + 2 :]
else: else:
# continue multiple line # continue multiple line
return self.__getline(True) return self.__getline(True)
@@ -112,7 +113,7 @@ class SoCStructureHeaderChecker:
pos = match_obj.groups()[1].find('*/') pos = match_obj.groups()[1].find('*/')
if pos != -1: if pos != -1:
# apply string that is behind comment # apply string that is behind comment
rawline = rawline + match_obj.groups()[1][pos + 2:] rawline = rawline + match_obj.groups()[1][pos + 2 :]
else: else:
# multiple line # multiple line
return self.__getline(True) return self.__getline(True)
@@ -121,7 +122,7 @@ class SoCStructureHeaderChecker:
# skip empty line # skip empty line
return self.__getline() return self.__getline()
if rawline.count(';') > 1: if rawline.count(';') > 1:
print('\033[0;34mINFO\033[0m: line: {}: possibily multiple expression within same line'.format(self.__linecount)) print(f'\033[0;34mINFO\033[0m: line: {self.__linecount}: possibly multiple expression within same line')
print(rawline) print(rawline)
return rawline return rawline
@@ -129,7 +130,7 @@ class SoCStructureHeaderChecker:
ret_val = 0 ret_val = 0
# first check for anonymous register structs # first check for anonymous register structs
if is_typedef and is_volatile and name is None: if is_typedef and is_volatile and name is None:
print('\033[0;31mERROR\033[0m: line {}: annoymous struct'.format(self.__linecount)) print(f'\033[0;31mERROR\033[0m: line {self.__linecount}: anonymous struct')
ret_val = -1 ret_val = -1
node_tree = dict() node_tree = dict()
bitcount = 0 bitcount = 0
@@ -145,7 +146,8 @@ class SoCStructureHeaderChecker:
if match_obj is not None: if match_obj is not None:
has_nested_struct_union = True has_nested_struct_union = True
ret, inherited_node_tree = self.__process_structure( ret, inherited_node_tree = self.__process_structure(
match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile') match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile'
)
if ret != 0: if ret != 0:
ret_val = -2 ret_val = -2
if inherited_node_tree is not None: if inherited_node_tree is not None:
@@ -155,7 +157,9 @@ class SoCStructureHeaderChecker:
match_obj = re.match(self.__REGEXP_MATCH_UNION_BEGIN, rawline) match_obj = re.match(self.__REGEXP_MATCH_UNION_BEGIN, rawline)
if match_obj is not None: if match_obj is not None:
has_nested_struct_union = True has_nested_struct_union = True
ret, inherited_node_tree = self.__process_union(match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile') ret, inherited_node_tree = self.__process_union(
match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile'
)
if ret != 0: if ret != 0:
ret_val = -2 ret_val = -2
if inherited_node_tree is not None: if inherited_node_tree is not None:
@@ -169,16 +173,21 @@ class SoCStructureHeaderChecker:
if bitcount not in (0, 32): if bitcount not in (0, 32):
ret_val = -2 ret_val = -2
if is_typedef: if is_typedef:
print('\033[0;31mERROR\033[0m: line {}: bitfield count is {}, type {}'.format(self.__linecount, bitcount, match_obj.groups()[0])) print(
f'\033[0;31mERROR\033[0m: line {self.__linecount}: bitfield count is {bitcount}, '
f'type {match_obj.groups()[0]}'
)
else: else:
print('\033[0;31mERROR\033[0m: line {}: bitfield count is {}, type {}, varname "{}"' print(
.format(self.__linecount, bitcount, name, match_obj.groups()[0])) f'\033[0;31mERROR\033[0m: line {self.__linecount}: bitfield count is {bitcount}, '
f'type {name}, varname "{match_obj.groups()[0]}"'
)
parsed_varname = match_obj.groups()[0] parsed_varname = match_obj.groups()[0]
if is_typedef: if is_typedef:
# is a typedef # is a typedef
if match_obj.groups()[0] == '' or match_obj.groups()[0].find('[') != -1: if match_obj.groups()[0] == '' or match_obj.groups()[0].find('[') != -1:
# should be c error # should be c error
print('\033[0;31mERROR\033[0m: line {}: C error'.format(self.__linecount)) print(f'\033[0;31mERROR\033[0m: line {self.__linecount}: C error')
ret_val = -3 ret_val = -3
if match_obj.groups()[0] in self.__temp_ref_types: if match_obj.groups()[0] in self.__temp_ref_types:
# duplication, script bug: we are putting all types into same namespace # duplication, script bug: we are putting all types into same namespace
@@ -212,7 +221,10 @@ class SoCStructureHeaderChecker:
bitcount += field_bit bitcount += field_bit
# bitfield should be u32 # bitfield should be u32
if match_obj.groups()[0] != 'uint32_t': if match_obj.groups()[0] != 'uint32_t':
print('\033[0;33mWARN\033[0m: line: {}: {} has type {}'.format(self.__linecount, match_obj.groups()[1], match_obj.groups()[0])) print(
f'\033[0;33mWARN\033[0m: line: {self.__linecount}: {match_obj.groups()[1]} '
f'has type {match_obj.groups()[0]}'
)
else: else:
has_non_bitfield_member = True has_non_bitfield_member = True
# append to node tree # append to node tree
@@ -225,9 +237,9 @@ class SoCStructureHeaderChecker:
node_tree[match_obj.groups()[1]] = member_node node_tree[match_obj.groups()[1]] = member_node
else: else:
if '*' not in match_obj.groups()[0]: if '*' not in match_obj.groups()[0]:
print('\033[0;33mWARN\033[0m: line {}: unknown type {}'.format(self.__linecount, match_obj.groups()[0])) print(f'\033[0;33mWARN\033[0m: line {self.__linecount}: unknown type {match_obj.groups()[0]}')
else: else:
print('\033[0;33mWARN\033[0m: line {}: pointer type {}'.format(self.__linecount, match_obj.groups()[0])) print(f'\033[0;33mWARN\033[0m: line {self.__linecount}: pointer type {match_obj.groups()[0]}')
continue continue
# check comments # check comments
match_obj = re.match(self.__REGEXP_MATCH_MULTILINE_COMMENT, rawline) match_obj = re.match(self.__REGEXP_MATCH_MULTILINE_COMMENT, rawline)
@@ -235,24 +247,29 @@ class SoCStructureHeaderChecker:
# code comments # code comments
continue continue
# dump out unmatched condition # dump out unmatched condition
print(('\033[0;33mWARN\033[0m: line: {}: unexpected expression: {}'.format(self.__linecount, rawline)).replace('\n', '')) print(
(f'\033[0;33mWARN\033[0m: line: {self.__linecount}: unexpected expression: {rawline}').replace('\n', '')
)
if bitcount != 0 and has_nested_struct_union: if bitcount != 0 and has_nested_struct_union:
print('\033[0;33mWARN\033[0m: line: {}: mixed bitfield member and nested structure/union'.format(self.__linecount)) print(f'\033[0;33mWARN\033[0m: line: {self.__linecount}: mixed bitfield member and nested structure/union')
if bitcount != 0 and has_non_bitfield_member: if bitcount != 0 and has_non_bitfield_member:
print('\033[0;33mWARN\033[0m: line: {}: mixed bitfield member and non-bitfield member'.format(self.__linecount)) print(f'\033[0;33mWARN\033[0m: line: {self.__linecount}: mixed bitfield member and non-bitfield member')
if is_typedef and is_volatile and name is None: if is_typedef and is_volatile and name is None:
if parsed_varname != '': if parsed_varname != '':
print('SUGGEST: {}'.format(parsed_varname.rstrip('t') + 's')) print('SUGGEST: {}'.format(parsed_varname.rstrip('t') + 's'))
if name is not None and is_typedef and is_volatile and parsed_varname.rstrip('t') != name.rstrip('s'): if name is not None and is_typedef and is_volatile and parsed_varname.rstrip('t') != name.rstrip('s'):
print('\033[0;33mWARN\033[0m: line: {}: different type and typedef name: {} {}'.format(self.__linecount, name, parsed_varname)) print(
f'\033[0;33mWARN\033[0m: line: {self.__linecount}: different type and typedef name: '
f'{name} {parsed_varname}'
)
return ret_val, node_tree return ret_val, node_tree
def __process_union(self, name: str, is_typedef: bool, is_volatile: bool) -> Any: def __process_union(self, name: str, is_typedef: bool, is_volatile: bool) -> Any:
ret_val = 0 ret_val = 0
# first check for anonymous register structs # first check for anonymous register structs
if is_typedef and is_volatile and name is None: if is_typedef and is_volatile and name is None:
print('\033[0;31mERROR\033[0m: line {}: annoymous union'.format(self.__linecount)) print(f'\033[0;31mERROR\033[0m: line {self.__linecount}: anonymous union')
ret_val = -1 ret_val = -1
node_tree = dict() # type: Any node_tree = dict() # type: Any
has_struct_count = 0 has_struct_count = 0
@@ -266,7 +283,8 @@ class SoCStructureHeaderChecker:
if match_obj is not None: if match_obj is not None:
has_struct_count += 1 has_struct_count += 1
ret, inherited_node_tree = self.__process_structure( ret, inherited_node_tree = self.__process_structure(
match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile') match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile'
)
if ret != 0: if ret != 0:
ret_val = -2 ret_val = -2
if inherited_node_tree is not None: if inherited_node_tree is not None:
@@ -276,7 +294,9 @@ class SoCStructureHeaderChecker:
match_obj = re.match(self.__REGEXP_MATCH_UNION_BEGIN, rawline) match_obj = re.match(self.__REGEXP_MATCH_UNION_BEGIN, rawline)
if match_obj is not None: if match_obj is not None:
has_struct_count += 1 has_struct_count += 1
ret, inherited_node_tree = self.__process_union(match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile') ret, inherited_node_tree = self.__process_union(
match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile'
)
if ret != 0: if ret != 0:
ret_val = -2 ret_val = -2
if inherited_node_tree is not None: if inherited_node_tree is not None:
@@ -291,7 +311,7 @@ class SoCStructureHeaderChecker:
# is a typedef # is a typedef
if match_obj.groups()[0] == '': if match_obj.groups()[0] == '':
# should be c error # should be c error
print('\033[0;31mERROR\033[0m: line {}: C error'.format(self.__linecount)) print(f'\033[0;31mERROR\033[0m: line {self.__linecount}: C error')
ret_val = -3 ret_val = -3
if match_obj.groups()[0] in self.__temp_ref_types: if match_obj.groups()[0] in self.__temp_ref_types:
# duplication, script bug: we are putting all types into same namespace # duplication, script bug: we are putting all types into same namespace
@@ -325,7 +345,11 @@ class SoCStructureHeaderChecker:
if match_obj is not None: if match_obj is not None:
# expecting to see 'uint32_t val;' # expecting to see 'uint32_t val;'
if match_obj.groups()[0] != 'uint32_t' or match_obj.groups()[1] != 'val': if match_obj.groups()[0] != 'uint32_t' or match_obj.groups()[1] != 'val':
print(('\033[0;33mWARN\033[0m: unexpected union member at {}: {}'.format(self.__linecount, rawline)).replace('\n', '')) print(
(f'\033[0;33mWARN\033[0m: unexpected union member at {self.__linecount}: {rawline}').replace(
'\n', ''
)
)
else: else:
has_val_field_count += 1 has_val_field_count += 1
# append to node tree # append to node tree
@@ -334,21 +358,28 @@ class SoCStructureHeaderChecker:
node_tree[match_obj.groups()[1]] = member_node node_tree[match_obj.groups()[1]] = member_node
else: else:
if '*' not in match_obj.groups()[0]: if '*' not in match_obj.groups()[0]:
print('\033[0;31mERROR\033[0m: line {}: unknown type {}'.format(self.__linecount, match_obj.groups()[0])) print(f'\033[0;31mWARN\033[0m: line {self.__linecount}: unknown type {match_obj.groups()[0]}')
else: else:
print('\033[0;33mWARN\033[0m: line {}: pointer type {}'.format(self.__linecount, match_obj.groups()[0])) print(f'\033[0;33mWARN\033[0m: line {self.__linecount}: pointer type {match_obj.groups()[0]}')
continue continue
# dump out unmatched condition # dump out unmatched condition
print(('\033[0;33mWARN\033[0m: line: {}: unexpected expression: {}'.format(self.__linecount, rawline)).replace('\n', '')) print(
(f'\033[0;33mWARN\033[0m: line: {self.__linecount}: unexpected expression: {rawline}').replace('\n', '')
)
if not (has_struct_count == 1 and has_val_field_count == 1): if not (has_struct_count == 1 and has_val_field_count == 1):
print('\033[0;34mINFO\033[0m: line: {}: not a typical union: {} nested structures, {} u32 val member' print(
.format(self.__linecount, has_struct_count, has_val_field_count)) f'\033[0;34mINFO\033[0m: line: {self.__linecount}: not a typical union: {has_struct_count} '
f'nested structures, {has_val_field_count} u32 val member'
)
if is_typedef and is_volatile and name is None: if is_typedef and is_volatile and name is None:
if parsed_varname != '': if parsed_varname != '':
print('SUGGEST: {}'.format(parsed_varname.rstrip('t') + 's')) print('SUGGEST: {}'.format(parsed_varname.rstrip('t') + 's'))
if name is not None and is_typedef and is_volatile and parsed_varname.rstrip('t') != name.rstrip('s'): if name is not None and is_typedef and is_volatile and parsed_varname.rstrip('t') != name.rstrip('s'):
print('\033[0;33mWARN\033[0m: line: {}: different type and typedef name: {} {}'.format(self.__linecount, name, parsed_varname)) print(
f'\033[0;33mWARN\033[0m: line: {self.__linecount}: different type and typedef name: '
f'{name} {parsed_varname}'
)
return ret_val, node_tree return ret_val, node_tree
def __process_root(self) -> int: def __process_root(self) -> int:
@@ -363,7 +394,8 @@ class SoCStructureHeaderChecker:
match_obj = re.match(self.__REGEXP_MATCH_STRUCTURE_BEGIN, rawline) match_obj = re.match(self.__REGEXP_MATCH_STRUCTURE_BEGIN, rawline)
if match_obj is not None: if match_obj is not None:
ret, inherited_node_tree = self.__process_structure( ret, inherited_node_tree = self.__process_structure(
match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile') match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile'
)
if ret != 0: if ret != 0:
ret_val = -2 ret_val = -2
if inherited_node_tree is not None: if inherited_node_tree is not None:
@@ -372,7 +404,9 @@ class SoCStructureHeaderChecker:
continue continue
match_obj = re.match(self.__REGEXP_MATCH_UNION_BEGIN, rawline) match_obj = re.match(self.__REGEXP_MATCH_UNION_BEGIN, rawline)
if match_obj is not None: if match_obj is not None:
ret, inherited_node_tree = self.__process_union(match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile') ret, inherited_node_tree = self.__process_union(
match_obj.groups()[2], match_obj.groups()[0] == 'typedef', match_obj.groups()[1] == 'volatile'
)
if ret != 0: if ret != 0:
ret_val = -2 ret_val = -2
if inherited_node_tree is not None: if inherited_node_tree is not None:
@@ -387,7 +421,7 @@ class SoCStructureHeaderChecker:
return ret_val return ret_val
def check(self, file: str) -> int: def check(self, file: str) -> int:
self.__fd = open(file, 'r', encoding='utf8') self.__fd = open(file, encoding='utf8')
self.__linecount = 0 self.__linecount = 0
self.__is_eof = False self.__is_eof = False
@@ -395,9 +429,9 @@ class SoCStructureHeaderChecker:
self.__fd.close() self.__fd.close()
if ret_val != 0: if ret_val != 0:
print('\033[0;31mCHECK FAILED\033[0m:\t{}'.format(file)) print(f'\033[0;31mCHECK FAILED\033[0m:\t{file}')
else: else:
print('\033[0;32mCHECK PASSED\033[0m:\t{}'.format(file)) print(f'\033[0;32mCHECK PASSED\033[0m:\t{file}')
return ret_val return ret_val
def get_ref_tree(self) -> Any: def get_ref_tree(self) -> Any:
@@ -410,7 +444,7 @@ def main() -> None:
print('file not exist') print('file not exist')
exit(-1) exit(-1)
checker = SoCStructureHeaderChecker() checker = SoCStructureHeaderChecker()
print('CHECKING:\t{}'.format(sys.argv[1])) print(f'CHECKING:\t{sys.argv[1]}')
ret = checker.check(sys.argv[1]) ret = checker.check(sys.argv[1])
if len(sys.argv) == 3 and sys.argv[2] == 'print': if len(sys.argv) == 3 and sys.argv[2] == 'print':
print(checker.get_ref_tree()) print(checker.get_ref_tree())