Files
2026-07-13 13:04:25 +08:00

143 lines
4.9 KiB
Python

# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# Generate a C source file that registers error codes into a designated linker section.
# The generated file creates an array of esp_err_msg_t entries in the ".esp_err_msg_tbl"
# section, which is collected at link time by the linker script.
#
# Usage:
# python err_codes_to_c.py --csv errors.csv --output esp_err_codes_mycomp.c --component mycomp
#
import argparse
import csv
import datetime
import os
def read_csv(csv_path: str) -> list[tuple[str, int, str]]:
"""
Read error codes from CSV file.
Returns list of (name, value, comment) tuples.
"""
entries: list[tuple[str, int, str]] = []
with open(csv_path, encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
name = row['name']
value = int(row['value'])
comment = row.get('comment', '')
entries.append((name, value, comment))
return entries
def generate_c_source(entries: list[tuple[str, int, str]], component: str, headers: list[str]) -> str:
"""
Generate C source code that places error code entries into the
.esp_err_msg_tbl linker section.
"""
lines: list[str] = []
year = datetime.datetime.now().year
lines.append('/*')
lines.append(f' * SPDX-FileCopyrightText: {year} Espressif Systems (Shanghai) CO LTD')
lines.append(' *')
lines.append(' * SPDX-License-Identifier: Apache-2.0')
lines.append(' */')
lines.append('')
lines.append('// Auto-generated by err_codes_to_c.py — do not edit')
lines.append('')
lines.append('#include "esp_err.h"')
lines.append('#include "esp_err_msg.h"')
lines.append('#include "esp_attr.h"')
lines.append('')
# Include the headers that define the error codes
# Skip headers already included above
already_included = {'esp_err.h', 'esp_err_msg.h', 'esp_attr.h'}
for header in headers:
if header in already_included:
continue
lines.append(f'#if __has_include("{header}")')
lines.append(f'#include "{header}"')
lines.append('#endif')
lines.append('')
lines.append('#ifdef CONFIG_ESP_ERR_TO_NAME_LOOKUP')
lines.append('')
# Generate array entries with #ifdef guards
safe_component = component.replace('-', '_').replace('.', '_')
symbol_name = f'esp_err_msg_{safe_component}'
lines.append(f'// Force-link symbol for {component} error codes')
lines.append(f'const int {symbol_name}_include = 0;')
lines.append('')
if entries:
lines.append(f'static const esp_err_msg_t {symbol_name}[]')
lines.append(' PLACE_IN_SECTION("esp_err_msg_tbl") = {')
for name, value, comment in entries:
comment_str = f' /* {comment} */' if comment else ''
lines.append(f'#ifdef {name}')
lines.append(f' {{ {name}, "{name}" }},{comment_str}')
lines.append('#endif')
lines.append('};')
lines.append('')
lines.append('#endif // CONFIG_ESP_ERR_TO_NAME_LOOKUP')
lines.append('')
return '\n'.join(lines)
def _infer_include_path(file_path: str) -> str:
"""
Convert a file path like 'components/foo/include/bar/baz.h'
into an include path like 'bar/baz.h'.
"""
parts = file_path.replace('\\', '/').split('/')
try:
idx = parts.index('include')
return '/'.join(parts[idx + 1 :])
except ValueError:
return os.path.basename(file_path)
def main() -> None:
parser = argparse.ArgumentParser(
description='Generate C source file with error code registration via linker section'
)
parser.add_argument('--csv', required=True, help='Input CSV file with error codes (from err_codes_extract.py)')
parser.add_argument('--output', required=True, help='Output C source file path')
parser.add_argument('--component', required=True, help='Component name (used for symbol naming)')
parser.add_argument('--headers', nargs='*', default=[], help='Header files to #include (include-path form)')
args = parser.parse_args()
entries = read_csv(args.csv)
# Deduce headers from the CSV file paths if none specified
headers = list(args.headers)
if not headers:
seen_files = set()
with open(args.csv, encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
file_path = row.get('file', '')
if file_path and file_path not in seen_files:
seen_files.add(file_path)
inc = _infer_include_path(file_path)
if inc not in headers:
headers.append(inc)
source = generate_c_source(entries, args.component, headers)
os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write(source)
if __name__ == '__main__':
main()