Source files in libcamera start by a comment block header, which includes the file name and a one-line description of the file contents. While the latter is useful to get a quick overview of the file contents at a glance, the former is mostly a source of inconvenience. The name in the comments can easily get out of sync with the file name when files are renamed, and copy & paste during development have often lead to incorrect names being used to start with. Readers of the source code are expected to know which file they're looking it. Drop the file name from the header comment block. The change was generated with the following script: ---------------------------------------- dirs="include/libcamera src test utils" declare -rA patterns=( ['c']=' \* ' ['cpp']=' \* ' ['h']=' \* ' ['py']='# ' ['sh']='# ' ) for ext in ${!patterns[@]} ; do files=$(for dir in $dirs ; do find $dir -name "*.${ext}" ; done) pattern=${patterns[${ext}]} for file in $files ; do name=$(basename ${file}) sed -i "s/^\(${pattern}\)${name} - /\1/" "$file" done done ---------------------------------------- This misses several files that are out of sync with the comment block header. Those will be addressed separately and manually. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com>
389 lines
12 KiB
Python
Executable file
389 lines
12 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2019, Google Inc.
|
|
#
|
|
# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
|
#
|
|
# Generate control definitions from YAML
|
|
|
|
import argparse
|
|
from functools import reduce
|
|
import operator
|
|
import string
|
|
import sys
|
|
import yaml
|
|
import os
|
|
|
|
|
|
class ControlEnum(object):
|
|
def __init__(self, data):
|
|
self.__data = data
|
|
|
|
@property
|
|
def description(self):
|
|
"""The enum description"""
|
|
return self.__data.get('description')
|
|
|
|
@property
|
|
def name(self):
|
|
"""The enum name"""
|
|
return self.__data.get('name')
|
|
|
|
@property
|
|
def value(self):
|
|
"""The enum value"""
|
|
return self.__data.get('value')
|
|
|
|
|
|
class Control(object):
|
|
def __init__(self, name, data, vendor):
|
|
self.__name = name
|
|
self.__data = data
|
|
self.__enum_values = None
|
|
self.__size = None
|
|
self.__vendor = vendor
|
|
|
|
enum_values = data.get('enum')
|
|
if enum_values is not None:
|
|
self.__enum_values = [ControlEnum(enum) for enum in enum_values]
|
|
|
|
size = self.__data.get('size')
|
|
if size is not None:
|
|
if len(size) == 0:
|
|
raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension')
|
|
|
|
# Compute the total number of elements in the array. If any of the
|
|
# array dimension is a string, the array is variable-sized.
|
|
num_elems = 1
|
|
for dim in size:
|
|
if type(dim) is str:
|
|
num_elems = 0
|
|
break
|
|
|
|
dim = int(dim)
|
|
if dim <= 0:
|
|
raise RuntimeError(f'Control `{self.__name}` size must have positive values only')
|
|
|
|
num_elems *= dim
|
|
|
|
self.__size = num_elems
|
|
|
|
@property
|
|
def description(self):
|
|
"""The control description"""
|
|
return self.__data.get('description')
|
|
|
|
@property
|
|
def enum_values(self):
|
|
"""The enum values, if the control is an enumeration"""
|
|
if self.__enum_values is None:
|
|
return
|
|
for enum in self.__enum_values:
|
|
yield enum
|
|
|
|
@property
|
|
def is_enum(self):
|
|
"""Is the control an enumeration"""
|
|
return self.__enum_values is not None
|
|
|
|
@property
|
|
def vendor(self):
|
|
"""The vendor string, or None"""
|
|
return self.__vendor
|
|
|
|
@property
|
|
def name(self):
|
|
"""The control name (CamelCase)"""
|
|
return self.__name
|
|
|
|
@property
|
|
def type(self):
|
|
typ = self.__data.get('type')
|
|
size = self.__data.get('size')
|
|
|
|
if typ == 'string':
|
|
return 'std::string'
|
|
|
|
if self.__size is None:
|
|
return typ
|
|
|
|
if self.__size:
|
|
return f"Span<const {typ}, {self.__size}>"
|
|
else:
|
|
return f"Span<const {typ}>"
|
|
|
|
|
|
def snake_case(s):
|
|
return ''.join([c.isupper() and ('_' + c) or c for c in s]).strip('_')
|
|
|
|
|
|
def format_description(description):
|
|
description = description.strip('\n').split('\n')
|
|
description[0] = '\\brief ' + description[0]
|
|
return '\n'.join([(line and ' * ' or ' *') + line for line in description])
|
|
|
|
|
|
def generate_cpp(controls):
|
|
enum_doc_start_template = string.Template('''/**
|
|
* \\enum ${name}Enum
|
|
* \\brief Supported ${name} values''')
|
|
enum_doc_value_template = string.Template(''' * \\var ${value}
|
|
${description}''')
|
|
doc_template = string.Template('''/**
|
|
* \\var ${name}
|
|
${description}
|
|
*/''')
|
|
def_template = string.Template('extern const Control<${type}> ${name}(${id_name}, "${name}");')
|
|
enum_values_doc = string.Template('''/**
|
|
* \\var ${name}Values
|
|
* \\brief List of all $name supported values
|
|
*/''')
|
|
enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''')
|
|
enum_values_values = string.Template('''\tstatic_cast<int32_t>(${name}),''')
|
|
name_value_map_doc = string.Template('''/**
|
|
* \\var ${name}NameValueMap
|
|
* \\brief Map of all $name supported value names (in std::string format) to value
|
|
*/''')
|
|
name_value_map_start = string.Template('''extern const std::map<std::string, ${type}> ${name}NameValueMap = {''')
|
|
name_value_values = string.Template('''\t{ "${name}", ${name} },''')
|
|
|
|
ctrls_doc = {}
|
|
ctrls_def = {}
|
|
ctrls_map = []
|
|
|
|
for ctrl in controls:
|
|
id_name = snake_case(ctrl.name).upper()
|
|
|
|
vendor = ctrl.vendor
|
|
if vendor not in ctrls_doc:
|
|
ctrls_doc[vendor] = []
|
|
ctrls_def[vendor] = []
|
|
|
|
info = {
|
|
'name': ctrl.name,
|
|
'type': ctrl.type,
|
|
'description': format_description(ctrl.description),
|
|
'id_name': id_name,
|
|
}
|
|
|
|
target_doc = ctrls_doc[vendor]
|
|
target_def = ctrls_def[vendor]
|
|
|
|
if ctrl.is_enum:
|
|
enum_doc = []
|
|
enum_doc.append(enum_doc_start_template.substitute(info))
|
|
|
|
num_entries = 0
|
|
for enum in ctrl.enum_values:
|
|
value_info = {
|
|
'name': ctrl.name,
|
|
'value': enum.name,
|
|
'description': format_description(enum.description),
|
|
}
|
|
enum_doc.append(enum_doc_value_template.substitute(value_info))
|
|
num_entries += 1
|
|
|
|
enum_doc = '\n *\n'.join(enum_doc)
|
|
enum_doc += '\n */'
|
|
target_doc.append(enum_doc)
|
|
|
|
values_info = {
|
|
'name': info['name'],
|
|
'type': ctrl.type,
|
|
'size': num_entries,
|
|
}
|
|
target_doc.append(enum_values_doc.substitute(values_info))
|
|
target_def.append(enum_values_start.substitute(values_info))
|
|
for enum in ctrl.enum_values:
|
|
value_info = {
|
|
'name': enum.name
|
|
}
|
|
target_def.append(enum_values_values.substitute(value_info))
|
|
target_def.append("};")
|
|
|
|
target_doc.append(name_value_map_doc.substitute(values_info))
|
|
target_def.append(name_value_map_start.substitute(values_info))
|
|
for enum in ctrl.enum_values:
|
|
value_info = {
|
|
'name': enum.name
|
|
}
|
|
target_def.append(name_value_values.substitute(value_info))
|
|
target_def.append("};")
|
|
|
|
target_doc.append(doc_template.substitute(info))
|
|
target_def.append(def_template.substitute(info))
|
|
|
|
vendor_ns = vendor + '::' if vendor != "libcamera" else ''
|
|
ctrls_map.append('\t{ ' + vendor_ns + id_name + ', &' + vendor_ns + ctrl.name + ' },')
|
|
|
|
vendor_ctrl_doc_sub = []
|
|
vendor_ctrl_template = string.Template('''
|
|
/**
|
|
* \\brief Namespace for ${vendor} controls
|
|
*/
|
|
namespace ${vendor} {
|
|
|
|
${vendor_controls_str}
|
|
|
|
} /* namespace ${vendor} */''')
|
|
|
|
for vendor in [v for v in ctrls_doc.keys() if v not in ['libcamera']]:
|
|
vendor_ctrl_doc_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n\n'.join(ctrls_doc[vendor])}))
|
|
|
|
vendor_ctrl_def_sub = []
|
|
for vendor in [v for v in ctrls_def.keys() if v not in ['libcamera']]:
|
|
vendor_ctrl_def_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n'.join(ctrls_def[vendor])}))
|
|
|
|
return {
|
|
'controls_doc': '\n\n'.join(ctrls_doc['libcamera']),
|
|
'controls_def': '\n'.join(ctrls_def['libcamera']),
|
|
'controls_map': '\n'.join(ctrls_map),
|
|
'vendor_controls_doc': '\n'.join(vendor_ctrl_doc_sub),
|
|
'vendor_controls_def': '\n'.join(vendor_ctrl_def_sub),
|
|
}
|
|
|
|
|
|
def generate_h(controls, mode, ranges):
|
|
enum_template_start = string.Template('''enum ${name}Enum {''')
|
|
enum_value_template = string.Template('''\t${name} = ${value},''')
|
|
enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''')
|
|
name_value_map_template = string.Template('''extern const std::map<std::string, ${type}> ${name}NameValueMap;''')
|
|
template = string.Template('''extern const Control<${type}> ${name};''')
|
|
|
|
ctrls = {}
|
|
ids = {}
|
|
id_value = {}
|
|
|
|
for ctrl in controls:
|
|
id_name = snake_case(ctrl.name).upper()
|
|
|
|
vendor = ctrl.vendor
|
|
if vendor not in ctrls:
|
|
if vendor not in ranges.keys():
|
|
raise RuntimeError(f'Control id range is not defined for vendor {vendor}')
|
|
id_value[vendor] = ranges[vendor] + 1
|
|
ids[vendor] = []
|
|
ctrls[vendor] = []
|
|
|
|
target_ids = ids[vendor]
|
|
target_ids.append('\t' + id_name + ' = ' + str(id_value[vendor]) + ',')
|
|
|
|
info = {
|
|
'name': ctrl.name,
|
|
'type': ctrl.type,
|
|
}
|
|
|
|
target_ctrls = ctrls[vendor]
|
|
|
|
if ctrl.is_enum:
|
|
target_ctrls.append(enum_template_start.substitute(info))
|
|
|
|
num_entries = 0
|
|
for enum in ctrl.enum_values:
|
|
value_info = {
|
|
'name': enum.name,
|
|
'value': enum.value,
|
|
}
|
|
target_ctrls.append(enum_value_template.substitute(value_info))
|
|
num_entries += 1
|
|
target_ctrls.append("};")
|
|
|
|
values_info = {
|
|
'name': info['name'],
|
|
'type': ctrl.type,
|
|
'size': num_entries,
|
|
}
|
|
target_ctrls.append(enum_values_template.substitute(values_info))
|
|
target_ctrls.append(name_value_map_template.substitute(values_info))
|
|
|
|
target_ctrls.append(template.substitute(info))
|
|
id_value[vendor] += 1
|
|
|
|
vendor_template = string.Template('''
|
|
namespace ${vendor} {
|
|
|
|
#define LIBCAMERA_HAS_${vendor_def}_VENDOR_${mode}
|
|
|
|
enum {
|
|
${vendor_enums}
|
|
};
|
|
|
|
${vendor_controls}
|
|
|
|
} /* namespace ${vendor} */
|
|
''')
|
|
|
|
vendor_sub = []
|
|
for vendor in [v for v in ctrls.keys() if v != 'libcamera']:
|
|
vendor_sub.append(vendor_template.substitute({'mode': mode.upper(),
|
|
'vendor': vendor,
|
|
'vendor_def': vendor.upper(),
|
|
'vendor_enums': '\n'.join(ids[vendor]),
|
|
'vendor_controls': '\n'.join(ctrls[vendor])}))
|
|
|
|
return {
|
|
'ids': '\n'.join(ids['libcamera']),
|
|
'controls': '\n'.join(ctrls['libcamera']),
|
|
'vendor_controls': '\n'.join(vendor_sub)
|
|
}
|
|
|
|
|
|
def fill_template(template, data):
|
|
|
|
template = open(template, 'rb').read()
|
|
template = template.decode('utf-8')
|
|
template = string.Template(template)
|
|
return template.substitute(data)
|
|
|
|
|
|
def main(argv):
|
|
|
|
# Parse command line arguments
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],
|
|
help='Mode of operation')
|
|
parser.add_argument('--output', '-o', metavar='file', type=str,
|
|
help='Output file name. Defaults to standard output if not specified.')
|
|
parser.add_argument('--ranges', '-r', type=str, required=True,
|
|
help='Control id range reservation file.')
|
|
parser.add_argument('--template', '-t', dest='template', type=str, required=True,
|
|
help='Template file name.')
|
|
parser.add_argument('input', type=str, nargs='+',
|
|
help='Input file name.')
|
|
|
|
args = parser.parse_args(argv[1:])
|
|
|
|
ranges = {}
|
|
with open(args.ranges, 'rb') as f:
|
|
data = open(args.ranges, 'rb').read()
|
|
ranges = yaml.safe_load(data)['ranges']
|
|
|
|
controls = []
|
|
for input in args.input:
|
|
with open(input, 'rb') as f:
|
|
data = f.read()
|
|
vendor = yaml.safe_load(data)['vendor']
|
|
ctrls = yaml.safe_load(data)['controls']
|
|
controls = controls + [Control(*ctrl.popitem(), vendor) for ctrl in ctrls]
|
|
|
|
if args.template.endswith('.cpp.in'):
|
|
data = generate_cpp(controls)
|
|
elif args.template.endswith('.h.in'):
|
|
data = generate_h(controls, args.mode, ranges)
|
|
else:
|
|
raise RuntimeError('Unknown template type')
|
|
|
|
data = fill_template(args.template, data)
|
|
|
|
if args.output:
|
|
output = open(args.output, 'wb')
|
|
output.write(data.encode('utf-8'))
|
|
output.close()
|
|
else:
|
|
sys.stdout.write(data)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|