utils: codegen: gen-controls.py: Convert to jinja2 templates

Jinja2 templates help separate the logic related to the template from
the generation of the data. The python code becomes much clearer as a
result.

As an added bonus, we can use a single template file for both controls
and properties.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
This commit is contained in:
Laurent Pinchart 2024-08-08 03:41:32 +03:00
parent 48f9660acd
commit dc067c4bce
7 changed files with 175 additions and 340 deletions

View file

@ -2,7 +2,7 @@
/* /*
* Copyright (C) 2019, Google Inc. * Copyright (C) 2019, Google Inc.
* *
* Control ID list * {{mode|capitalize}} ID list
* *
* This file is auto-generated. Do not edit. * This file is auto-generated. Do not edit.
*/ */
@ -18,18 +18,42 @@
namespace libcamera { namespace libcamera {
namespace controls { namespace {{mode}} {
extern const ControlIdMap {{mode}};
{%- for vendor, ctrls in controls -%}
{% if vendor != 'libcamera' %}
namespace {{vendor}} {
#define LIBCAMERA_HAS_{{vendor|upper}}_VENDOR_{{mode|upper}}
{%- endif %}
enum { enum {
${ids} {%- for ctrl in ctrls %}
{{ctrl.name|snake_case|upper}} = {{ctrl.id}},
{%- endfor %}
}; };
${controls} {% for ctrl in ctrls -%}
{% if ctrl.is_enum -%}
enum {{ctrl.name}}Enum {
{%- for enum in ctrl.enum_values %}
{{enum.name}} = {{enum.value}},
{%- endfor %}
};
extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values;
extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap;
{% endif -%}
extern const Control<{{ctrl.type}}> {{ctrl.name}};
{% endfor -%}
extern const ControlIdMap controls; {% if vendor != 'libcamera' %}
} /* namespace {{vendor}} */
{% endif -%}
${vendor_controls} {% endfor %}
} /* namespace {{mode}} */
} /* namespace controls */
} /* namespace libcamera */ } /* namespace libcamera */

View file

@ -80,7 +80,7 @@ foreach mode, entry : controls_map
properties_files_names += files_list properties_files_names += files_list
endif endif
template_file = files(outfile + '.in') template_file = files('control_ids.h.in')
ranges_file = files('../../src/libcamera/control_ranges.yaml') ranges_file = files('../../src/libcamera/control_ranges.yaml')
control_headers += custom_target(header + '_h', control_headers += custom_target(header + '_h',
input : input_files, input : input_files,

View file

@ -1,34 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Property ID list
*
* This file is auto-generated. Do not edit.
*/
#pragma once
#include <map>
#include <stdint.h>
#include <string>
#include <libcamera/controls.h>
namespace libcamera {
namespace properties {
enum {
${ids}
};
${controls}
extern const ControlIdMap properties;
${vendor_controls}
} /* namespace properties */
} /* namespace libcamera */

View file

@ -2,51 +2,120 @@
/* /*
* Copyright (C) 2019, Google Inc. * Copyright (C) 2019, Google Inc.
* *
* Control ID list * {{mode}} ID list
* *
* This file is auto-generated. Do not edit. * This file is auto-generated. Do not edit.
*/ */
#include <libcamera/control_ids.h> #include <libcamera/{{filename}}.h>
#include <libcamera/controls.h> #include <libcamera/controls.h>
/** /**
* \file control_ids.h * \file {{filename}}.h
* \brief Camera control identifiers * \brief Camera {{mode}} identifiers
*/ */
namespace libcamera { namespace libcamera {
/** /**
* \brief Namespace for libcamera controls * \brief Namespace for libcamera {{mode}}
*/ */
namespace controls { namespace {{mode}} {
${controls_doc} {%- for vendor, ctrls in controls -%}
${vendor_controls_doc} {%- if vendor != 'libcamera' %}
/**
* \brief Namespace for {{vendor}} {{mode}}
*/
namespace {{vendor}} {
{%- endif -%}
{% for ctrl in ctrls %}
{% if ctrl.is_enum -%}
/**
* \enum {{ctrl.name}}Enum
* \brief Supported {{ctrl.name}} values
{%- for enum in ctrl.enum_values %}
*
* \var {{enum.name}}
* \brief {{enum.description|format_description}}
{%- endfor %}
*/
/**
* \var {{ctrl.name}}Values
* \brief List of all {{ctrl.name}} supported values
*/
/**
* \var {{ctrl.name}}NameValueMap
* \brief Map of all {{ctrl.name}} supported value names (in std::string format) to value
*/
{% endif -%}
/**
* \var {{ctrl.name}}
* \brief {{ctrl.description|format_description}}
*/
{%- endfor %}
{% if vendor != 'libcamera' %}
} /* namespace {{vendor}} */
{% endif -%}
{%- endfor %}
#ifndef __DOXYGEN__ #ifndef __DOXYGEN__
/* /*
* Keep the controls definitions hidden from doxygen as it incorrectly parses * Keep the {{mode}} definitions hidden from doxygen as it incorrectly parses
* them as functions. * them as functions.
*/ */
${controls_def} {% for vendor, ctrls in controls -%}
${vendor_controls_def} {% if vendor != 'libcamera' %}
namespace {{vendor}} {
{% endif %}
#endif {%- for ctrl in ctrls %}
{% if ctrl.is_enum -%}
extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values = {
{%- for enum in ctrl.enum_values %}
static_cast<{{ctrl.type}}>({{enum.name}}),
{%- endfor %}
};
extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap = {
{%- for enum in ctrl.enum_values %}
{ "{{enum.name}}", {{enum.name}} },
{%- endfor %}
};
{% endif -%}
extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}");
{%- endfor %}
{% if vendor != 'libcamera' %}
} /* namespace {{vendor}} */
{% endif -%}
{%- endfor %}
#endif /* __DOXYGEN__ */
/** /**
* \brief List of all supported libcamera controls * \brief List of all supported libcamera {{mode}}
{%- if mode == 'controls' %}
* *
* Unless otherwise stated, all controls are bi-directional, i.e. they can be * Unless otherwise stated, all controls are bi-directional, i.e. they can be
* set through Request::controls() and returned out through Request::metadata(). * set through Request::controls() and returned out through Request::metadata().
{%- endif %}
*/ */
extern const ControlIdMap controls { extern const ControlIdMap {{mode}} {
${controls_map} {%- for vendor, ctrls in controls -%}
{%- for ctrl in ctrls %}
{ {{ctrl.namespace}}{{ctrl.name|snake_case|upper}}, &{{ctrl.namespace}}{{ctrl.name}} },
{%- endfor -%}
{%- endfor %}
}; };
} /* namespace controls */ } /* namespace {{mode}} */
} /* namespace libcamera */ } /* namespace libcamera */

View file

@ -143,9 +143,10 @@ foreach mode, inout_files : controls_mode_files
input_files = inout_files[0] input_files = inout_files[0]
output_file = inout_files[1] output_file = inout_files[1]
template_file = files(output_file + '.in') template_file = files('control_ids.cpp.in')
ranges_file = files('control_ranges.yaml') ranges_file = files('control_ranges.yaml')
control_sources += custom_target(mode + '_cpp',
control_sources += custom_target(mode + '_ids_cpp',
input : input_files, input : input_files,
output : output_file, output : output_file,
command : [gen_controls, '-o', '@OUTPUT@', command : [gen_controls, '-o', '@OUTPUT@',

View file

@ -1,48 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* Property ID list
*
* This file is auto-generated. Do not edit.
*/
#include <libcamera/property_ids.h>
/**
* \file property_ids.h
* \brief Camera property identifiers
*/
namespace libcamera {
/**
* \brief Namespace for libcamera properties
*/
namespace properties {
${controls_doc}
${vendor_controls_doc}
#ifndef __DOXYGEN__
/*
* Keep the properties definitions hidden from doxygen as it incorrectly parses
* them as functions.
*/
${controls_def}
${vendor_controls_def}
#endif
/**
* \brief List of all supported libcamera properties
*/
extern const ControlIdMap properties {
${controls_map}
};
} /* namespace properties */
} /* namespace libcamera */

View file

@ -7,12 +7,10 @@
# Generate control definitions from YAML # Generate control definitions from YAML
import argparse import argparse
from functools import reduce import jinja2
import operator import os
import string
import sys import sys
import yaml import yaml
import os
class ControlEnum(object): class ControlEnum(object):
@ -81,6 +79,13 @@ class Control(object):
for enum in self.__enum_values: for enum in self.__enum_values:
yield enum yield enum
@property
def enum_values_count(self):
"""The number of enum values, if the control is an enumeration"""
if self.__enum_values is None:
return 0
return len(self.__enum_values)
@property @property
def is_enum(self): def is_enum(self):
"""Is the control an enumeration""" """Is the control an enumeration"""
@ -119,221 +124,21 @@ def snake_case(s):
def format_description(description): def format_description(description):
description = description.strip('\n').split('\n') description = description.strip('\n').split('\n')
description[0] = '\\brief ' + description[0] for i in range(1, len(description)):
return '\n'.join([(line and ' * ' or ' *') + line for line in description]) line = description[i]
description[i] = (line and ' * ' or ' *') + line
return '\n'.join(description)
def generate_cpp(controls): def extend_control(ctrl, id, ranges):
enum_doc_start_template = string.Template('''/** ctrl.id = ranges[ctrl.vendor] + id + 1
* \\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 = {} if ctrl.vendor != 'libcamera':
ctrls_def = {} ctrl.namespace = f'{ctrl.vendor}::'
ctrls_map = [] else:
ctrl.namespace = ''
for ctrl in controls: return ctrl
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): def main(argv):
@ -358,29 +163,47 @@ def main(argv):
data = open(args.ranges, 'rb').read() data = open(args.ranges, 'rb').read()
ranges = yaml.safe_load(data)['ranges'] ranges = yaml.safe_load(data)['ranges']
controls = [] controls = {}
for input in args.input: for input in args.input:
with open(input, 'rb') as f: data = yaml.safe_load(open(input, 'rb').read())
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'): vendor = data['vendor']
data = generate_cpp(controls) if vendor not in ranges.keys():
elif args.template.endswith('.h.in'): raise RuntimeError(f'Control id range is not defined for vendor {vendor}')
data = generate_h(controls, args.mode, ranges)
else:
raise RuntimeError('Unknown template type')
data = fill_template(args.template, data) ctrls = controls.setdefault(vendor, [])
for i, ctrl in enumerate(data['controls']):
ctrl = Control(*ctrl.popitem(), vendor)
ctrls.append(extend_control(ctrl, i, ranges))
# Sort the vendors by range numerical value
controls = [[vendor, ctrls] for vendor, ctrls in controls.items()]
controls.sort(key=lambda item: ranges[item[0]])
filename = {
'controls': 'control_ids',
'properties': 'property_ids',
}[args.mode]
data = {
'filename': filename,
'mode': args.mode,
'controls': controls,
}
env = jinja2.Environment()
env.filters['format_description'] = format_description
env.filters['snake_case'] = snake_case
template = env.from_string(open(args.template, 'r', encoding='utf-8').read())
string = template.render(data)
if args.output: if args.output:
output = open(args.output, 'wb') output = open(args.output, 'w', encoding='utf-8')
output.write(data.encode('utf-8')) output.write(string)
output.close() output.close()
else: else:
sys.stdout.write(data) sys.stdout.write(string)
return 0 return 0