[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