# 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()