From 39f01e9185857a872e2ae4617d8ea1bb341539f8 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 8 Aug 2024 03:41:32 +0300 Subject: py: gen-py-controls: Convert to jinja2 templates Jinja2 templates help separate the logic related to the template from the generation of the data. The python code becomes 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 Reviewed-by: Tomi Valkeinen Acked-by: Paul Elder --- src/py/libcamera/gen-py-controls.py | 118 +++++++++++------------- src/py/libcamera/meson.build | 8 +- src/py/libcamera/py_controls_generated.cpp.in | 35 +++++-- src/py/libcamera/py_properties_generated.cpp.in | 30 ------ 4 files changed, 80 insertions(+), 111 deletions(-) delete mode 100644 src/py/libcamera/py_properties_generated.cpp.in (limited to 'src/py') diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py index a18dc533..cf09c146 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 = '' - - 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_(controls, \"{}\");'.format(vendor, vendor_mode_str, vendor)) - vendors.append(vendor) - - if vendor != 'libcamera': - ns = 'libcamera::{}::{}::'.format(mode, vendor) - container = vendor - else: - ns = 'libcamera::{}::'.format(mode) - container = 'controls' - - out += f'\t{container}.def_readonly_static("{ctrl.name}", static_cast(&{ns}{ctrl.name}));\n\n' - - 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 extend_control(ctrl, mode): + if ctrl.vendor != 'libcamera': + ctrl.klass = ctrl.vendor + ctrl.namespace = f'{ctrl.vendor}::' + else: + ctrl.klass = mode + ctrl.namespace = '' + + if not ctrl.is_enum: + return ctrl + + 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 enum in ctrl.enum_values: + enum.py_name = enum.name[len(prefix):] -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) + + for ctrl in data['controls']: + ctrl = Control(*ctrl.popitem(), vendor) + controls.append(extend_control(ctrl, args.mode)) - data = generate_py(controls, args.mode) + data = { + 'mode': args.mode, + 'header': headers[args.mode], + 'vendors': vendors, + 'controls': controls, + } - data = fill_template(args.template, data) + 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 6ad2d771..596a203c 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 26d5a104..22a132d1 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 * - * Python bindings - Auto-generated controls + * Python bindings - Auto-generated {{mode}} * * This file is auto-generated. Do not edit. */ -#include +#include #include @@ -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_(m, "controls"); -${vendors_defs} +}; -${controls} +{% endfor -%} + +void init_py_{{mode}}_generated(py::module& m) +{ + auto {{mode}} = py::class_(m, "{{mode}}"); +{%- for vendor in vendors %} + auto {{vendor}} = py::class_({{mode}}, "{{vendor}}"); +{%- endfor %} + +{% for ctrl in controls %} + {{ctrl.klass}}.def_readonly_static("{{ctrl.name}}", static_cast(&libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}})); +{%- if ctrl.is_enum %} + + py::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 d28f1ab8..00000000 --- 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 - * - * Python bindings - Auto-generated properties - * - * This file is auto-generated. Do not edit. - */ - -#include - -#include - -#include "py_main.h" - -namespace py = pybind11; - -class PyProperties -{ -}; - -${vendors_class_def} - -void init_py_properties_generated(py::module& m) -{ - auto controls = py::class_(m, "properties"); -${vendors_defs} - -${controls} -} -- cgit v1.2.1