From patchwork Fri Aug 9 00:59:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 20860 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id B1573C324E for ; Fri, 9 Aug 2024 00:59:58 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4BC9E633B9; Fri, 9 Aug 2024 02:59:58 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ssTniF5E"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 93A17633CF for ; Fri, 9 Aug 2024 02:59:53 +0200 (CEST) Received: from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi [81.175.209.231]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AB482B7E for ; Fri, 9 Aug 2024 02:58:59 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1723165139; bh=tuKkHfuK0EwhaQlqsgtId1QaRGXsZiYe4uQdLq5do6Y=; h=From:To:Subject:Date:In-Reply-To:References:From; b=ssTniF5Ei5VwbVD85ts04dRYlgrDZ/zlXEgcNkSmRxlZFd7SOOAgzUipeDIfyzM8i fD2NBoU6JdlgypUHJ2lrmsjZ05NPe4/ndJ/mwhwTCJ+8EEIvjCFPkgrY4xwdQNJq5f waj3vZzxCBsHHZ7sNzkDtz5i9kHz9bT861S+RX0E= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Subject: [PATCH 10/10] py: gen-py-controls: Convert to jinja2 templates Date: Fri, 9 Aug 2024 03:59:14 +0300 Message-ID: <20240809005914.20662-11-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.44.2 In-Reply-To: <20240809005914.20662-1-laurent.pinchart@ideasonboard.com> References: <20240809005914.20662-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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 Reviewed-by: Tomi Valkeinen Acked-by: Paul Elder --- 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_(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(&{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 * - * 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 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 - * - * 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} -}