1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
#!/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
fmt = '%(levelname)s: %(message)s'
try:
import coloredlogs
coloredlogs.install(level=logging.INFO, fmt=fmt)
except ImportError:
logging.basicConfig(level=logging.INFO, format=fmt)
try:
import ruamel.yaml as ruyaml
except:
logger.error(
f'Failed to import ruamel.yaml. Please install the ruamel.yaml package.')
sys.exit(1)
logger = logging.getLogger(__name__)
@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())
found_by_name = {}
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)
logger.info(f"Found control {m.name} in {p}")
desc = {'type': m.type,
'direction': 'out',
'description': f'Debug control {m.name} found in {p}'}
if m.size is not None:
desc['size'] = m.size
c = found_by_name.setdefault(m.name, m)
if c.type != m.type or c.size != m.size:
logger.error(
f"Found multiple entries for control '{m.name}' with differing type or size")
return 1
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)
# Don't try to remove more than once in case control was found multiple files.
if m.name in obsolete_names:
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)
p = ctrl_file.relative_to(Path.cwd(), walk_up=True)
logger.info(f"Sucessfully updated {p}")
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
|