mirror of
https://github.com/espressif/esp-idf.git
synced 2026-07-15 23:43:02 +03:00
147 lines
5.3 KiB
Python
Executable File
147 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
"""
|
|
Pre-commit check: ensure every header defining ESP_ERR_* codes is registered
|
|
via idf_define_esp_err_codes() in its component's CMakeLists.txt.
|
|
|
|
Compares the set of headers found by scanning all components against those
|
|
explicitly listed in idf_define_esp_err_codes(HEADERS ...) calls.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
# Add tools/ to path so we can import err_codes_extract
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
from err_codes_extract import EXCLUDE_SEARCH_DIRS
|
|
from err_codes_extract import extract_from_file
|
|
|
|
# Additional directories to exclude beyond those in err_codes_extract
|
|
EXTRA_EXCLUDE_DIRS = {'remote'}
|
|
|
|
# Pattern matching idf_define_esp_err_codes(HEADERS ...) in CMakeLists.txt
|
|
ERR_CODES_CALL = re.compile(r'idf_define_esp_err_codes\s*\(')
|
|
|
|
|
|
def find_all_err_headers(components_dir: str) -> dict[str, set[str]]:
|
|
"""
|
|
Scan all component headers for ESP_ERR_* definitions using
|
|
err_codes_extract.extract_from_file().
|
|
|
|
Returns:
|
|
dict mapping component name -> set of header paths (relative to component dir)
|
|
"""
|
|
exclude = EXCLUDE_SEARCH_DIRS | EXTRA_EXCLUDE_DIRS
|
|
result: dict[str, set[str]] = {}
|
|
|
|
for component_name in sorted(os.listdir(components_dir)):
|
|
component_dir = os.path.join(components_dir, component_name)
|
|
if not os.path.isdir(component_dir):
|
|
continue
|
|
|
|
for root, dirnames, filenames in os.walk(component_dir, topdown=True):
|
|
dirnames[:] = [d for d in dirnames if d not in exclude]
|
|
for filename in filenames:
|
|
if not filename.endswith('.h'):
|
|
continue
|
|
filepath = os.path.join(root, filename)
|
|
rel_path = os.path.relpath(filepath, component_dir)
|
|
if extract_from_file(filepath, rel_path):
|
|
result.setdefault(component_name, set()).add(rel_path)
|
|
|
|
return result
|
|
|
|
|
|
def parse_registered_headers(components_dir: str) -> dict[str, set[str]]:
|
|
"""
|
|
Parse all CMakeLists.txt files for idf_define_esp_err_codes(HEADERS ...) calls.
|
|
|
|
Returns:
|
|
dict mapping component name -> set of header paths (relative to component dir)
|
|
"""
|
|
result: dict[str, set[str]] = {}
|
|
|
|
for component_name in sorted(os.listdir(components_dir)):
|
|
component_dir = os.path.join(components_dir, component_name)
|
|
cmake_file = os.path.join(component_dir, 'CMakeLists.txt')
|
|
if not os.path.isfile(cmake_file):
|
|
continue
|
|
|
|
try:
|
|
with open(cmake_file, encoding='utf-8') as f:
|
|
content = f.read()
|
|
except (UnicodeDecodeError, OSError):
|
|
continue
|
|
|
|
# Find all idf_define_esp_err_codes() calls and extract HEADERS args
|
|
for match in ERR_CODES_CALL.finditer(content):
|
|
start = match.end()
|
|
# Find the matching closing paren, handling nesting
|
|
depth = 1
|
|
pos = start
|
|
while pos < len(content) and depth > 0:
|
|
if content[pos] == '(':
|
|
depth += 1
|
|
elif content[pos] == ')':
|
|
depth -= 1
|
|
pos += 1
|
|
|
|
args_str = content[start : pos - 1]
|
|
# Remove comments
|
|
args_str = re.sub(r'#.*$', '', args_str, flags=re.MULTILINE)
|
|
# Extract tokens after HEADERS keyword
|
|
headers_match = re.search(r'\bHEADERS\b\s+(.*)', args_str, re.DOTALL)
|
|
if headers_match:
|
|
tokens = headers_match.group(1).split()
|
|
# Stop at next keyword (uppercase word) or end
|
|
for token in tokens:
|
|
if re.match(r'^[A-Z_]+$', token):
|
|
break
|
|
# Normalize path separators
|
|
header_path = token.replace('\\', '/')
|
|
result.setdefault(component_name, set()).add(header_path)
|
|
|
|
return result
|
|
|
|
|
|
def main() -> int:
|
|
idf_path = os.environ.get('IDF_PATH', os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
components_dir = os.path.join(idf_path, 'components')
|
|
|
|
if not os.path.isdir(components_dir):
|
|
print(f'Error: components directory not found: {components_dir}', file=sys.stderr)
|
|
return 1
|
|
|
|
found_headers = find_all_err_headers(components_dir)
|
|
registered_headers = parse_registered_headers(components_dir)
|
|
|
|
errors: list[str] = []
|
|
|
|
for component, headers in sorted(found_headers.items()):
|
|
registered = registered_headers.get(component, set())
|
|
missing = headers - registered
|
|
if missing:
|
|
for header in sorted(missing):
|
|
errors.append(
|
|
f'{component}: {header} defines ESP_ERR_* codes but is not registered in idf_define_esp_err_codes()'
|
|
)
|
|
|
|
if errors:
|
|
print('Error code registration check failed:', file=sys.stderr)
|
|
for error in errors:
|
|
print(f' {error}', file=sys.stderr)
|
|
print(
|
|
f'\n{len(errors)} header(s) define ESP_ERR_* codes but are not registered.\n'
|
|
f"Add idf_define_esp_err_codes(HEADERS ...) to the component's CMakeLists.txt.",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|