[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