The yaml definitions for controls now require a direction attribute which is not auto generated by gen-debug-controls.py. Fix that. Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
163 lines
5.3 KiB
Python
Executable file
163 lines
5.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2024, Google Inc.
|
|
#
|
|
# Author: Stefan Klug <stefan.klug@ideasonboard.com>
|
|
#
|
|
# 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<Span<type>>( controls::debug::something , static_cast<type>(var) )
|
|
# set<>( controls::debug::something , static_cast<type>(var) )
|
|
# set( controls::debug::something , static_cast<type> (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))
|