[PATCH 07/10] utils: codegen: gen-controls.py: Convert to jinja2 templates

Paul Elder paul.elder at ideasonboard.com
Thu Aug 15 06:16:40 CEST 2024


On Fri, Aug 09, 2024 at 03:59:11AM +0300, Laurent Pinchart wrote:
> 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)

This is unused.

Other than that, looks good to me. I'm impressed at how easy it was to
read (it still took a while though).

Reviewed-by: Paul Elder <paul.elder at ideasonboard.com>

>  
> -        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
>  


More information about the libcamera-devel mailing list