[PATCH 07/10] utils: codegen: gen-controls.py: Convert to jinja2 templates
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Fri Aug 9 02:59:11 CEST 2024
Jinja2 templates help separating the logic related to the template from
the generation of the data. The python code gets 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 at ideasonboard.com>
---
include/libcamera/control_ids.h.in | 40 +++-
include/libcamera/meson.build | 2 +-
include/libcamera/property_ids.h.in | 34 ----
src/libcamera/control_ids.cpp.in | 101 ++++++++--
src/libcamera/meson.build | 5 +-
src/libcamera/property_ids.cpp.in | 48 -----
utils/codegen/gen-controls.py | 285 ++++++----------------------
7 files changed, 176 insertions(+), 339 deletions(-)
delete mode 100644 include/libcamera/property_ids.h.in
delete mode 100644 src/libcamera/property_ids.cpp.in
diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
index 293ba966fbc4..858ef872e9ee 100644
--- a/include/libcamera/control_ids.h.in
+++ b/include/libcamera/control_ids.h.in
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * Control ID list
+ * {{mode|capitalize}} ID list
*
* This file is auto-generated. Do not edit.
*/
@@ -18,18 +18,42 @@
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 {
-${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}
-
-} /* namespace controls */
+{% endfor %}
+} /* namespace {{mode}} */
} /* namespace libcamera */
diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
index 87b9a9412fe7..d90a8615e52d 100644
--- a/include/libcamera/meson.build
+++ b/include/libcamera/meson.build
@@ -80,7 +80,7 @@ foreach mode, entry : controls_map
properties_files_names += files_list
endif
- template_file = files(outfile + '.in')
+ template_file = files('control_ids.h.in')
ranges_file = files('../../src/libcamera/control_ranges.yaml')
control_headers += custom_target(header + '_h',
input : input_files,
diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in
deleted file mode 100644
index e6edabca771f..000000000000
--- a/include/libcamera/property_ids.h.in
+++ /dev/null
@@ -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 */
diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in
index 0b028c92d852..05c8fb385d20 100644
--- a/src/libcamera/control_ids.cpp.in
+++ b/src/libcamera/control_ids.cpp.in
@@ -2,51 +2,120 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * Control ID list
+ * {{mode}} ID list
*
* This file is auto-generated. Do not edit.
*/
-#include <libcamera/control_ids.h>
+#include <libcamera/{{filename}}.h>
#include <libcamera/controls.h>
/**
- * \file control_ids.h
- * \brief Camera control identifiers
+ * \file {{filename}}.h
+ * \brief Camera {{mode}} identifiers
*/
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__
/*
- * 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.
*/
-${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
* set through Request::controls() and returned out through Request::metadata().
+{%- endif %}
*/
-extern const ControlIdMap controls {
-${controls_map}
+extern const ControlIdMap {{mode}} {
+{%- 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 */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index e5e959d9c7bd..3fd3a87e9f95 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -143,9 +143,10 @@ foreach mode, inout_files : controls_mode_files
input_files = inout_files[0]
output_file = inout_files[1]
- template_file = files(output_file + '.in')
+ template_file = files('control_ids.cpp.in')
ranges_file = files('control_ranges.yaml')
- control_sources += custom_target(mode + '_cpp',
+
+ control_sources += custom_target(mode + '_ids_cpp',
input : input_files,
output : output_file,
command : [gen_controls, '-o', '@OUTPUT@',
diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in
deleted file mode 100644
index 2d3f192eb6ef..000000000000
--- a/src/libcamera/property_ids.cpp.in
+++ /dev/null
@@ -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 */
diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py
index 56315f5089b4..685ef7a00d5f 100755
--- a/utils/codegen/gen-controls.py
+++ b/utils/codegen/gen-controls.py
@@ -7,12 +7,10 @@
# Generate control definitions from YAML
import argparse
-from functools import reduce
-import operator
-import string
+import jinja2
+import os
import sys
import yaml
-import os
class ControlEnum(object):
@@ -81,6 +79,13 @@ class Control(object):
for enum in self.__enum_values:
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
def is_enum(self):
"""Is the control an enumeration"""
@@ -119,221 +124,23 @@ def snake_case(s):
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])
+ for i in range(1, len(description)):
+ line = description[i]
+ description[i] = (line and ' * ' or ' *') + line
+ return '\n'.join(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} },''')
+def extend_control(ctrl, id, ranges):
+ ctrl.id = ranges[ctrl.vendor] + id + 1
- ctrls_doc = {}
- ctrls_def = {}
- ctrls_map = []
+ if ctrl.vendor != 'libcamera':
+ ctrl.namespace = f'{ctrl.vendor}::'
+ else:
+ ctrl.namespace = ''
- for ctrl in controls:
- id_name = snake_case(ctrl.name).upper()
+ ctrl.documentation = format_description(ctrl.description)
- 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)
+ return ctrl
def main(argv):
@@ -358,29 +165,47 @@ def main(argv):
data = open(args.ranges, 'rb').read()
ranges = yaml.safe_load(data)['ranges']
- controls = []
+ 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]
+ data = yaml.safe_load(open(input, 'rb').read())
- 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')
+ vendor = data['vendor']
+ if vendor not in ranges.keys():
+ raise RuntimeError(f'Control id range is not defined for vendor {vendor}')
- 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:
- output = open(args.output, 'wb')
- output.write(data.encode('utf-8'))
+ output = open(args.output, 'w', encoding='utf-8')
+ output.write(string)
output.close()
else:
- sys.stdout.write(data)
+ sys.stdout.write(string)
return 0
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list