#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2024, Google Inc. # # Author: Stefan Klug # # This script looks for occurrences of the debug metadata controls in the source # tree and updates src/libcamera/control_ids_debug.yaml accordingly. It is meant # to be used during development to ease updating of the yaml file while # debugging. import argparse import logging import os import re import sys from dataclasses import dataclass from pathlib import Path logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') try: import ruamel.yaml as ruyaml except: logger.error( f'Failed to import ruamel.yaml. Please install the ruamel.yaml package.') sys.exit(1) @dataclass class FoundMatch: file: os.PathLike whole_match: str line: int type: str name: str size: str = None def get_control_name(control): k = list(control.keys()) if len(k) != 1: raise Exception(f"Can't handle control entry with {len(k)} keys") return k[0] def find_debug_controls(dir): extensions = ['.cpp', '.h'] files = [p for p in dir.rglob('*') if p.suffix in extensions] # The following regex was tested on # set>( controls::debug::something , static_cast(var) ) # set<>( controls::debug::something , static_cast(var) ) # set( controls::debug::something , static_cast (var) ) exp = re.compile(r'set' # set function r'(?:\<((?:[^)(])*)\>)?' # followed by a optional template param r'\(\s*controls::debug::(\w+)\s*,' # referencing a debug control ) matches = [] for p in files: with p.open('r') as f: for idx, line in enumerate(f): match = exp.search(line) if match: m = FoundMatch(file=p, line=idx, type=match.group(1), name=match.group(2), whole_match=match.group(0)) if m.type is not None and m.type.startswith('Span'): # Simple span type detection treating the last word # inside <> as type. r = re.match(r'Span<(?:.*\s+)(.*)>', m.type) m.type = r.group(1) m.size = '[n]' matches.append(m) return matches def main(argv): parser = argparse.ArgumentParser( description='Automatically updates control_ids_debug.yaml') parser.parse_args(argv[1:]) yaml = ruyaml.YAML() root_dir = Path(__file__).resolve().parent.parent ctrl_file = root_dir.joinpath('src/libcamera/control_ids_debug.yaml') matches = find_debug_controls(root_dir.joinpath('src')) doc = yaml.load(ctrl_file) controls = doc['controls'] # Create a map of names in the existing yaml for easier updating. controls_map = {} for control in controls: for k, v in control.items(): controls_map[k] = v obsolete_names = list(controls_map.keys()) for m in matches: if not m.type: p = m.file.relative_to(Path.cwd(), walk_up=True) logger.warning( f'{p}:{m.line + 1}: Failed to deduce type from {m.whole_match} ... skipping') continue p = m.file.relative_to(root_dir) desc = {'type': m.type, 'direction': 'out', 'description': f'Debug control {m.name} found in {p}:{m.line}'} if m.size is not None: desc['size'] = m.size if m.name in controls_map: # Can't use == for modified check because of the special yaml dicts. update_needed = False if list(controls_map[m.name].keys()) != list(desc.keys()): update_needed = True else: for k, v in controls_map[m.name].items(): if v != desc[k]: update_needed = True break if update_needed: logger.info(f"Update control '{m.name}'") controls_map[m.name].clear() controls_map[m.name].update(desc) obsolete_names.remove(m.name) else: logger.info(f"Add control '{m.name}'") insert_before = len(controls) for idx, control in enumerate(controls): if get_control_name(control).lower() > m.name.lower(): insert_before = idx break controls.insert(insert_before, {m.name: desc}) # Remove elements from controls without recreating the list (to keep # comments etc.). idx = 0 while idx < len(controls): name = get_control_name(controls[idx]) if name in obsolete_names: logger.info(f"Remove control '{name}'") controls.pop(idx) else: idx += 1 with ctrl_file.open('w') as f: # Ruyaml looses the header. f.write(("# SPDX-License-Identifier: LGPL-2.1-or-later\n" "#\n" "# This file was generated by utils/gen-debug-controls.py\n" "#\n")) yaml.dump(doc, f) return 0 if __name__ == '__main__': sys.exit(main(sys.argv))