Message ID | 20240809005914.20662-11-laurent.pinchart@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
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@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@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@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@ideasonboard.com> Tomi
On Fri, Aug 09, 2024 at 03:59:14AM +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@ideasonboard.com> Looks good to me. Acked-by: Paul Elder <paul.elder@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@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@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} > -}
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@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@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} -}
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@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