[PATCH 10/10] py: gen-py-controls: Convert to jinja2 templates

Tomi Valkeinen tomi.valkeinen at ideasonboard.com
Mon Aug 12 16:45:32 CEST 2024



On 09/08/2024 03:59, 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>
> ---
>   src/py/libcamera/gen-py-controls.py           | 114 ++++++++----------
>   src/py/libcamera/meson.build                  |   8 +-
>   src/py/libcamera/py_controls_generated.cpp.in |  35 ++++--
>   .../libcamera/py_properties_generated.cpp.in  |  30 -----
>   4 files changed, 78 insertions(+), 109 deletions(-)
>   delete mode 100644 src/py/libcamera/py_properties_generated.cpp.in
> 
> diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py
> index a18dc5337090..cf09c146084d 100755
> --- a/src/py/libcamera/gen-py-controls.py
> +++ b/src/py/libcamera/gen-py-controls.py
> @@ -4,7 +4,7 @@
>   # Generate Python bindings controls from YAML
>   
>   import argparse
> -import string
> +import jinja2
>   import sys
>   import yaml
>   
> @@ -23,67 +23,39 @@ def find_common_prefix(strings):
>       return prefix
>   
>   
> -def generate_py(controls, mode):
> -    out = ''
> +def extend_control(ctrl, mode):
> +    if ctrl.vendor != 'libcamera':
> +        ctrl.klass = ctrl.vendor
> +        ctrl.namespace = f'{ctrl.vendor}::'
> +    else:
> +        ctrl.klass = mode
> +        ctrl.namespace = ''
>   
> -    vendors_class_def = []
> -    vendor_defs = []
> -    vendors = []
> -    for vendor, ctrl_list in controls.items():
> -        for ctrl in ctrl_list:
> -            if vendor not in vendors and vendor != 'libcamera':
> -                vendor_mode_str = f'{vendor.capitalize()}{mode.capitalize()}'
> -                vendors_class_def.append('class Py{}\n{{\n}};\n'.format(vendor_mode_str))
> -                vendor_defs.append('\tauto {} = py::class_<Py{}>(controls, \"{}\");'.format(vendor, vendor_mode_str, vendor))
> -                vendors.append(vendor)
> +    if not ctrl.is_enum:
> +        return ctrl
>   
> -            if vendor != 'libcamera':
> -                ns = 'libcamera::{}::{}::'.format(mode, vendor)
> -                container = vendor
> -            else:
> -                ns = 'libcamera::{}::'.format(mode)
> -                container = 'controls'
> +    if mode == 'controls':
> +        # Adjustments for controls
> +        if ctrl.name == 'LensShadingMapMode':
> +            prefix = 'LensShadingMapMode'
> +        else:
> +            prefix = find_common_prefix([e.name for e in ctrl.enum_values])
> +    else:
> +        # Adjustments for properties
> +        prefix = find_common_prefix([e.name for e in ctrl.enum_values])
>   
> -            out += f'\t{container}.def_readonly_static("{ctrl.name}", static_cast<const libcamera::ControlId *>(&{ns}{ctrl.name}));\n\n'
> +    for enum in ctrl.enum_values:
> +        enum.py_name = enum.name[len(prefix):]
>   
> -            if not ctrl.is_enum:
> -                continue
> -
> -            cpp_enum = ctrl.name + 'Enum'
> -
> -            out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
> -
> -            if mode == 'controls':
> -                # Adjustments for controls
> -                if ctrl.name == 'LensShadingMapMode':
> -                    prefix = 'LensShadingMapMode'
> -                else:
> -                    prefix = find_common_prefix([e.name for e in ctrl.enum_values])
> -            else:
> -                # Adjustments for properties
> -                prefix = find_common_prefix([e.name for e in ctrl.enum_values])
> -
> -            for entry in ctrl.enum_values:
> -                cpp_enum = entry.name
> -                py_enum = entry.name[len(prefix):]
> -
> -                out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
> -
> -            out += '\t;\n\n'
> -
> -    return {'controls': out,
> -            'vendors_class_def': '\n'.join(vendors_class_def),
> -            'vendors_defs': '\n'.join(vendor_defs)}
> -
> -
> -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):
> +    headers = {
> +        'controls': 'control_ids.h',
> +        'properties': 'property_ids.h',
> +    }
> +
>       # Parse command line arguments
>       parser = argparse.ArgumentParser()
>       parser.add_argument('--mode', '-m', type=str, required=True,
> @@ -96,27 +68,41 @@ def main(argv):
>                           help='Input file name.')
>       args = parser.parse_args(argv[1:])
>   
> -    if args.mode not in ['controls', 'properties']:
> +    if not headers.get(args.mode):
>           print(f'Invalid mode option "{args.mode}"', file=sys.stderr)
>           return -1
>   
> -    controls = {}
> +    controls = []
> +    vendors = []
> +
>       for input in args.input:
>           data = yaml.safe_load(open(input, 'rb').read())
> +
>           vendor = data['vendor']
> -        ctrls = data['controls']
> -        controls[vendor] = [Control(*ctrl.popitem(), vendor) for ctrl in ctrls]
> +        if vendor != 'libcamera':
> +            vendors.append(vendor)
>   
> -    data = generate_py(controls, args.mode)
> +        for ctrl in data['controls']:
> +            ctrl = Control(*ctrl.popitem(), vendor)
> +            controls.append(extend_control(ctrl, args.mode))
>   
> -    data = fill_template(args.template, data)
> +    data = {
> +        'mode': args.mode,
> +        'header': headers[args.mode],
> +        'vendors': vendors,
> +        'controls': controls,
> +    }
> +
> +    env = jinja2.Environment()
> +    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
>   
> diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build
> index 6ad2d7713e4d..596a203ca4cc 100644
> --- a/src/py/libcamera/meson.build
> +++ b/src/py/libcamera/meson.build
> @@ -26,7 +26,7 @@ pycamera_sources = files([
>       'py_transform.cpp',
>   ])
>   
> -# Generate controls
> +# Generate controls and properties
>   
>   gen_py_controls_template = files('py_controls_generated.cpp.in')
>   gen_py_controls = files('gen-py-controls.py')
> @@ -38,15 +38,11 @@ pycamera_sources += custom_target('py_gen_controls',
>                                                '-t', gen_py_controls_template, '@INPUT@'],
>                                     env : py_build_env)
>   
> -# Generate properties
> -
> -gen_py_properties_template = files('py_properties_generated.cpp.in')
> -
>   pycamera_sources += custom_target('py_gen_properties',
>                                     input : properties_files,
>                                     output : ['py_properties_generated.cpp'],
>                                     command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@',
> -                                             '-t', gen_py_properties_template, '@INPUT@'],
> +                                             '-t', gen_py_controls_template, '@INPUT@'],
>                                     env : py_build_env)
>   
>   # Generate formats
> diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in
> index 26d5a104f209..22a132d19ea9 100644
> --- a/src/py/libcamera/py_controls_generated.cpp.in
> +++ b/src/py/libcamera/py_controls_generated.cpp.in
> @@ -2,12 +2,12 @@
>   /*
>    * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>
>    *
> - * Python bindings - Auto-generated controls
> + * Python bindings - Auto-generated {{mode}}
>    *
>    * This file is auto-generated. Do not edit.
>    */
>   
> -#include <libcamera/control_ids.h>
> +#include <libcamera/{{header}}>
>   
>   #include <pybind11/pybind11.h>
>   
> @@ -15,16 +15,33 @@
>   
>   namespace py = pybind11;
>   
> -class PyControls
> +class Py{{mode|capitalize}}
>   {
>   };
>   
> -${vendors_class_def}
> -
> -void init_py_controls_generated(py::module& m)
> +{% for vendor in vendors -%}
> +class Py{{vendor|capitalize}}{{mode|capitalize}}
>   {
> -	auto controls = py::class_<PyControls>(m, "controls");
> -${vendors_defs}
> +};
>   
> -${controls}
> +{% endfor -%}
> +
> +void init_py_{{mode}}_generated(py::module& m)
> +{
> +	auto {{mode}} = py::class_<Py{{mode|capitalize}}>(m, "{{mode}}");
> +{%- for vendor in vendors %}
> +	auto {{vendor}} = py::class_<Py{{vendor|capitalize}}{{mode|capitalize}}>({{mode}}, "{{vendor}}");
> +{%- endfor %}
> +
> +{% for ctrl in controls %}
> +        {{ctrl.klass}}.def_readonly_static("{{ctrl.name}}", static_cast<const libcamera::ControlId *>(&libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}));
> +{%- if ctrl.is_enum %}
> +
> +        py::enum_<libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}Enum>({{ctrl.klass}}, "{{ctrl.name}}Enum")
> +{%- for enum in ctrl.enum_values %}
> +                .value("{{enum.py_name}}", libcamera::{{mode}}::{{ctrl.namespace}}{{enum.name}})
> +{%- endfor %}
> +        ;
> +{%- endif %}
> +{% endfor -%}
>   }
> diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in
> deleted file mode 100644
> index d28f1ab8b61a..000000000000
> --- a/src/py/libcamera/py_properties_generated.cpp.in
> +++ /dev/null
> @@ -1,30 +0,0 @@
> -/* SPDX-License-Identifier: LGPL-2.1-or-later */
> -/*
> - * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>
> - *
> - * Python bindings - Auto-generated properties
> - *
> - * This file is auto-generated. Do not edit.
> - */
> -
> -#include <libcamera/property_ids.h>
> -
> -#include <pybind11/pybind11.h>
> -
> -#include "py_main.h"
> -
> -namespace py = pybind11;
> -
> -class PyProperties
> -{
> -};
> -
> -${vendors_class_def}
> -
> -void init_py_properties_generated(py::module& m)
> -{
> -	auto controls = py::class_<PyProperties>(m, "properties");
> -${vendors_defs}
> -
> -${controls}
> -}

I'm not familiar with jinja2, but looks fine to me and works fine.

Reviewed-by: Tomi Valkeinen <tomi.valkeinen at ideasonboard.com>

  Tomi



More information about the libcamera-devel mailing list