[libcamera-devel] [PATCH v4 04/37] utils: ipc: add templates for code generation for IPC mechanism

Laurent Pinchart laurent.pinchart at ideasonboard.com
Thu Nov 19 13:39:17 CET 2020


Hi Paul,

Thank you for the patch.

On Fri, Nov 06, 2020 at 07:36:34PM +0900, Paul Elder wrote:
> Add templates to mojo to generate code for the IPC mechanism. These
> templates generate:
> - module header
> - module serializer
> - IPA proxy cpp, header, and worker
> 
> Given an input data definition mojom file for a pipeline.
> 
> Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
> 
> ---
> Changes in v4:
> For the non-template files:
> - rename IPA{pipeline_name}CallbackInterface to
>   IPA{pipeline_name}EventInterface
>   - to avoid the notion of "callback" and emphasize that it's an event
> - add support for strings in custom structs
> - add validation, that async methods must not have return values
>   - it throws exception and isn't very clear though...?
> - rename controls to libcamera::{pipeline_name}::controls (controls is
>   now lowercase)
> - rename {pipeline_name}_generated.h to {pipeline_name}_ipa_interface.h,
>   and {pipeline_name}_serializer.h to {pipeline_name}_ipa_serializer.h
>   - same for their corresponding template files
> For the template files:
> - fix spacing (now it's all {{var}} instead of some {{ var }})
>   - except if it's code, so code is still {{ code }}
> - move inclusion of corresponding header to first in the inclusion list
> - fix copy&paste errors
> - change snake_case to camelCase in the generated code
>   - template code still uses snake_case
> - change the generated command enums to an enum class, and make it
>   capitalized (instead of allcaps)
> - add length checks to recvIPC (in proxy)
> - fix some template spacing
> - don't use const for PODs in function/signal parameters
> - add the proper length checks to readPOD/appendPOD
>   - the helper functions for reading and writing PODs to and from
>     serialized data
> - rename readUInt/appendUInt to readPOD/appendPOD
> - add support for strings in custom structs
> 
> Changes in v3:
> - add support for namespaces
> - fix enum assignment (used to have +1 for CMD applied to all enums)
> - use readHeader, writeHeader, and eraseHeader as static class functions
>   of IPAIPCUnixSocket (in the proxy worker)
> - add requirement that base controls *must* be defined in
>   libcamera::{pipeline_name}::Controls
> 
> Changes in v2:
> - mandate the main and callback interfaces, and init(), start(), stop()
>   and their parameters
> - fix returning single pod value from IPC-called function
> - add licenses
> - improve auto-generated message
> - other fixes related to serdes
> ---
>  .../module_ipa_interface.h.tmpl               | 113 ++++
>  .../module_ipa_proxy.cpp.tmpl                 | 238 +++++++++
>  .../module_ipa_proxy.h.tmpl                   | 118 +++++
>  .../module_ipa_proxy_worker.cpp.tmpl          | 187 +++++++
>  .../module_ipa_serializer.h.tmpl              |  44 ++
>  .../libcamera_templates/proxy_functions.tmpl  | 205 ++++++++
>  .../libcamera_templates/serializer.tmpl       | 280 ++++++++++
>  .../generators/mojom_libcamera_generator.py   | 488 ++++++++++++++++++
>  8 files changed, 1673 insertions(+)
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
>  create mode 100644 utils/ipc/generators/libcamera_templates/serializer.tmpl
>  create mode 100644 utils/ipc/generators/mojom_libcamera_generator.py

Let's start with the generator.

[snip]

> diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/ipc/generators/mojom_libcamera_generator.py
> new file mode 100644
> index 00000000..c4fbf9b3
> --- /dev/null
> +++ b/utils/ipc/generators/mojom_libcamera_generator.py
> @@ -0,0 +1,488 @@

Missing SPDX header and copyright notice.

> +'''Generates libcamera files from a mojom.Module.'''

Comments in Python use # :-)

> +
> +import argparse
> +import ast
> +import datetime
> +import contextlib
> +import os
> +import re
> +import shutil
> +import sys
> +import tempfile

ast, contextlib, shutil, sys and tempfile don't seem to be used.

> +
> +from jinja2 import contextfilter

contextfilter isn't used either.

> +
> +import mojom.fileutil as fileutil
> +import mojom.generate.generator as generator
> +import mojom.generate.module as mojom
> +from mojom.generate.template_expander import UseJinja
> +
> +
> +GENERATOR_PREFIX = 'libcamera'
> +
> +_kind_to_cpp_type = {
> +    mojom.BOOL:   'bool',
> +    mojom.INT8:   'int8_t',
> +    mojom.UINT8:  'uint8_t',
> +    mojom.INT16:  'int16_t',
> +    mojom.UINT16: 'uint16_t',
> +    mojom.INT32:  'int32_t',
> +    mojom.UINT32: 'uint32_t',
> +    mojom.FLOAT:  'float',
> +    mojom.INT64:  'int64_t',
> +    mojom.UINT64: 'uint64_t',
> +    mojom.DOUBLE: 'double',
> +}
> +
> +_bit_widths = {
> +    mojom.BOOL:   '8',
> +    mojom.INT8:   '8',
> +    mojom.UINT8:  '8',
> +    mojom.INT16:  '16',
> +    mojom.UINT16: '16',
> +    mojom.INT32:  '32',
> +    mojom.UINT32: '32',
> +    mojom.FLOAT:  '32',
> +    mojom.INT64:  '64',
> +    mojom.UINT64: '64',
> +    mojom.DOUBLE: '64',
> +}
> +
> +def ModuleName(path):
> +    return path.split('/')[-1].split('.')[0]
> +
> +def ModuleClassName(module):
> +    s = re.sub(r'^IPA', '',  module.interfaces[0].mojom_name)
> +    return re.sub(r'Interface$', '', s)

You could combine the two with

    return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1), module)

and possibly optimize it further by pre-compiling the regular
expression. I'll let you decide if it's worth it.

> +
> +def UpperCamelCase(name):
> +    return ''.join([x.capitalize() for x in generator.SplitCamelCase(name)])
> +
> +def CamelCase(name):
> +    uccc = UpperCamelCase(name)
> +    return uccc[0].lower() + uccc[1:]

These two functions don't seem to be used.

> +
> +def Capitalize(name):
> +    return name[0].upper() + name[1:]
> +
> +def ConstantStyle(name):
> +    return generator.ToUpperSnakeCase(name)
> +
> +def Choose(cond, t, f):
> +    return t if cond else f
> +
> +def CommaSep(l):
> +    return ', '.join([m for m in l])
> +
> +def ParamsCommaSep(l):
> +    return ', '.join([m.mojom_name for m in l])
> +
> +def GetDefaultValue(element):
> +    if element.default is not None:
> +        return element.default
> +    if type(element.kind) == mojom.Kind:
> +        return '0'
> +    if mojom.IsEnumKind(element.kind):
> +        return f'static_cast<{element.kind.mojom_name}>(0)'
> +    if (isinstance(element.kind, mojom.Struct) and
> +       element.kind.mojom_name == 'FileDescriptor'):

Is there a need for the outer parentheses ? Or is this dictated by the
mojom coding style ? Same comment for several locations below.

> +        return '-1'
> +    return ''
> +
> +def WithDefaultValues(element):
> +    return [x for x in element if HasDefaultValue(x)]
> +
> +def WithFds(element):
> +    return [x for x in element if HasFd(x)]

Should these be moved a little bit further down, after the functions
they call ? The code works fine, but it would create an easier to read
flow.

> +
> +def HasDefaultValue(element):
> +    return GetDefaultValue(element) != ''
> +
> +def HasDefaultFields(element):
> +    return True in [HasDefaultValue(x) for x in element.fields]
> +
> +def GetAllTypes(element):
> +    if mojom.IsArrayKind(element):
> +        return GetAllTypes(element.kind)
> +    if mojom.IsMapKind(element):
> +        return GetAllTypes(element.key_kind) + GetAllTypes(element.value_kind)
> +    if isinstance(element, mojom.Parameter):
> +        return GetAllTypes(element.kind)
> +    if mojom.IsEnumKind(element):
> +        return [element.mojom_name]
> +    if not mojom.IsStructKind(element):
> +        return [element.spec]
> +    if len(element.fields) == 0:
> +        return [element.mojom_name]
> +    ret = [GetAllTypes(x.kind) for x in element.fields]
> +    ret = [x for sublist in ret for x in sublist]
> +    return list(set(ret))
> +
> +def GetAllAttrs(element):
> +    if mojom.IsArrayKind(element):
> +        return GetAllAttrs(element.kind)
> +    if mojom.IsMapKind(element):
> +        return {**GetAllAttrs(element.key_kind), **GetAllAttrs(element.value_kind)}
> +    if isinstance(element, mojom.Parameter):
> +        return GetAllAttrs(element.kind)
> +    if mojom.IsEnumKind(element):
> +        return element.attributes if element.attributes is not None else {}
> +    if mojom.IsStructKind(element) and len(element.fields) == 0:
> +        return element.attributes if element.attributes is not None else {}
> +    if not mojom.IsStructKind(element):
> +        return {}
> +    attrs = [(x.attributes) for x in element.fields]
> +    ret = {}
> +    for d in attrs:
> +        if d is not None:
> +            ret = {**ret, **d}

Maybe

            ret.update(d)

> +    return ret
> +
> +def NeedsControlSerializer(element):
> +    types = GetAllTypes(element)
> +    return "ControlList" in types or "ControlInfoMap" in types
> +
> +def HasFd(element):
> +    attrs = GetAllAttrs(element)
> +    if isinstance(element, mojom.Kind):
> +        types = GetAllTypes(element)
> +    else:
> +        types = GetAllTypes(element.kind)
> +    return "FileDescriptor" in types or (attrs is not None and "hasFd" in attrs)
> +
> +def MethodInputHasFd(method):
> +    if len([x for x in method.parameters if HasFd(x)]) > 0:

You can drop the len() call, [] evaluates to False.

> +        return True
> +    return False
> +
> +def MethodOutputHasFd(method):
> +    if (MethodReturnValue(method) != 'void' or
> +        method.response_parameters is None):
> +        return False
> +    if len([x for x in method.parameters if HasFd(x)]) > 0:

Same here.

Shouldn't it be method.response_parameters instead of method.parameters
?

> +        return True
> +    return False
> +
> +def MethodParamInputs(method):
> +    return method.parameters
> +
> +def MethodParamOutputs(method):
> +    if (MethodReturnValue(method) != 'void' or
> +        method.response_parameters is None):
> +        return []
> +    return method.response_parameters

The increase code reuse, you could implement these functions as follows.


def MethodParamInputs(method):
    return method.parameters

def MethodParamOutputs(method):
    if (MethodReturnValue(method) != 'void' or
        method.response_parameters is None):
        return []
    return method.response_parameters

def MethodParamHasFd(parameters):
    if [x for x in parameters if HasFd(x)]:
        return True
    return False

def MethodInputHasFd(method):
    return MethodParamHasFd(MethodParamInputs(method))

def MethodOutputHasFd(method):
    return MethodParamHasFd(MethodParamOutputs(method))

> +
> +def MethodParamNames(method):
> +    params = []
> +    for param in method.parameters:
> +        params.append(param.mojom_name)
> +    if MethodReturnValue(method) == 'void':
> +        if method.response_parameters is None:
> +            return params
> +        for param in method.response_parameters:
> +            params.append(param.mojom_name)
> +    return params
> +
> +def MethodParameters(method):
> +    params = []
> +    for param in method.parameters:
> +        params.append('const %s %s%s' % (GetNameForElement(param),
> +                                         ('&' if not IsPod(param) else ''),

No need for the outer parentheses.

> +                                         param.mojom_name))
> +    if MethodReturnValue(method) == 'void':
> +        if method.response_parameters is None:
> +            return params
> +        for param in method.response_parameters:
> +            params.append(f'{GetNameForElement(param)} *{param.mojom_name}')
> +    return params
> +
> +def MethodReturnValue(method):
> +    if method.response_parameters is None:
> +        return 'void'
> +    if len(method.response_parameters) == 1 and IsPod(method.response_parameters[0]):
> +        return GetNameForElement(method.response_parameters[0])
> +    return 'void'
> +
> +def IsAsync(method):
> +    # callbacks are always async

s/callbacks/Events/ ?

> +    if re.match("^IPA.*EventInterface$", method.interface.mojom_name):
> +        return True
> +    elif re.match("^IPA.*Interface$", method.interface.mojom_name):
> +        if method.attributes is None:
> +            return False
> +        elif 'async' in method.attributes and method.attributes['async']:
> +            return True
> +    return False
> +
> +def IsArray(element):
> +    return mojom.IsArrayKind(element.kind)
> +
> +def IsControls(element):
> +    return mojom.IsStructKind(element.kind) and (element.kind.mojom_name == "ControlList" or
> +                                                 element.kind.mojom_name == "ControlInfoMap")
> +
> +def IsEnum(element):
> +    return mojom.IsEnumKind(element.kind)
> +
> +def IsFd(element):
> +    return mojom.IsStructKind(element.kind) and element.kind.mojom_name == "FileDescriptor"
> +
> +def IsMap(element):
> +    return mojom.IsMapKind(element.kind)
> +
> +def IsPlainStruct(element):
> +    return mojom.IsStructKind(element.kind) and not IsControls(element) and not IsFd(element)
> +
> +def IsPod(element):
> +    return element.kind in _kind_to_cpp_type
> +
> +def IsStr(element):
> +    return element.kind.spec == 's'
> +
> +def BitWidth(element):
> +    if element.kind in _bit_widths:
> +        return _bit_widths[element.kind]
> +    if mojom.IsEnumKind(element.kind):
> +        return '32'
> +    return ''
> +
> +def GetNameForElement(element):
> +    if (mojom.IsEnumKind(element) or
> +        mojom.IsInterfaceKind(element) or
> +        mojom.IsStructKind(element)):
> +        return element.mojom_name
> +    if (mojom.IsArrayKind(element)):
> +        elem_name = GetNameForElement(element.kind)
> +        return f'std::vector<{elem_name}>'
> +    if (mojom.IsMapKind(element)):
> +        key_name = GetNameForElement(element.key_kind)
> +        value_name = GetNameForElement(element.value_kind)
> +        return f'std::map<{key_name}, {value_name}>'
> +    if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)):
> +        if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)):
> +            return GetNameForElement(element.kind)
> +        if (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'):
> +            return 'std::string'
> +        if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type):

Is the hasattr() check needed ? If element has no kind attribute, I
would expect the previous two if's to raise an exception.

> +            return _kind_to_cpp_type[element.kind]
> +        return element.kind.mojom_name
> +    if isinstance(element,  mojom.EnumValue):

Extra space after comma.

> +        return (GetNameForElement(element.enum) + '.' +
> +                ConstantStyle(element.name))
> +    if isinstance(element, (mojom.NamedValue,
> +                            mojom.Constant,
> +                            mojom.EnumField)):
> +        return ConstantStyle(element.name)
> +    if (hasattr(element, '__hash__') and element in _kind_to_cpp_type):
> +        return _kind_to_cpp_type[element]

For my own education, what type of elements does this cover ?

> +    if (hasattr(element, 'kind') and element.kind in _kind_to_cpp_type):
> +        return _kind_to_cpp_type[element.kind]
> +    if (hasattr(element, 'spec')):
> +        if (element.spec == 's'):
> +            return 'std::string'
> +        return element.spec.split(':')[-1]
> +    if (mojom.IsInterfaceRequestKind(element) or
> +        mojom.IsAssociatedKind(element) or
> +        mojom.IsPendingRemoteKind(element) or
> +        mojom.IsPendingReceiverKind(element) or
> +        mojom.IsUnionKind(element)):
> +        raise Exception('Unsupported element: %s' % element)
> +    raise Exception('Unexpected element: %s' % element)
> +
> +def GetFullNameForElement(element, namespace_str):
> +    name = GetNameForElement(element)
> +    if namespace_str == '':
> +        return name
> +    return f'{namespace_str}::{name}'
> +
> +def ValidateZeroLength(l, s, cap=True):
> +    if l is None:
> +        return
> +    if len(l) > 0:
> +        raise Exception(f'{s.capitalize() if cap else s} should be empty')
> +
> +def ValidateSingleLength(l, s, cap=True):
> +    if len(l) > 1:
> +        raise Exception(f'Only one {s} allowed')
> +    if len(l) < 1:
> +        raise Exception(f'{s.capitalize() if cap else s} is required')
> +
> +def ValidateInterfaces(interfaces):
> +    # Validate presence of main interface
> +    intf = [x for x in interfaces
> +            if re.match("^IPA.*Interface", x.mojom_name) and
> +               not re.match("^IPA.*EventInterface", x.mojom_name)]
> +    ValidateSingleLength(intf, 'main interface')
> +    intf = intf[0]
> +
> +    # Validate presence of callback interface

s/callback/event/

> +    cb = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)]

s/cb/event/ ?

> +    ValidateSingleLength(cb, 'event interface')
> +    cb = cb[0]
> +
> +    # Validate required main interface functions
> +    f_init  = [x for x in intf.methods if x.mojom_name == 'init']
> +    f_start = [x for x in intf.methods if x.mojom_name == 'start']
> +    f_stop  = [x for x in intf.methods if x.mojom_name == 'stop']
> +
> +    ValidateSingleLength(f_init, 'init()', False)
> +    ValidateSingleLength(f_start, 'start()', False)
> +    ValidateSingleLength(f_stop, 'stop()', False)
> +
> +    f_init  = f_init[0]
> +    f_start = f_start[0]
> +    f_stop  = f_stop[0]
> +
> +    # Validate parameters to init()
> +    ValidateSingleLength(f_init.parameters, 'input parameter to init()')
> +    ValidateSingleLength(f_init.response_parameters, 'output parameter from init()')
> +    if f_init.parameters[0].kind.mojom_name != 'IPASettings':
> +        raise Exception('init() must have single IPASettings input parameter')
> +    if f_init.response_parameters[0].kind.spec != 'i32':
> +        raise Exception('init() must have single int32 output parameter')
> +
> +    # Validate parameters to start()
> +    ValidateZeroLength(f_start.parameters, 'input parameter to start()')
> +    ValidateSingleLength(f_start.response_parameters, 'output parameter from start()')
> +    if f_start.response_parameters[0].kind.spec != 'i32':
> +        raise Exception('start() must have single int32 output parameter')
> +
> +    # Validate parameters to stop()
> +    ValidateZeroLength(f_stop.parameters, 'input parameter to stop()')
> +    ValidateZeroLength(f_stop.parameters, 'output parameter from stop()')
> +
> +    # Validate that all async methods don't have return values
> +    intf_methods_async = [x for x in intf.methods if IsAsync(x)]
> +    for method in intf_methods_async:
> +        ValidateZeroLength(method.response_parameters,
> +                           f'{method.mojom_name} response parameters', False)
> +
> +    cb_methods_async = [x for x in cb.methods if IsAsync(x)]

s/cb_method_async/event_methods_async/

> +    for method in cb_methods_async:
> +        ValidateZeroLength(method.response_parameters,
> +                           f'{method.mojom_name} response parameters', False)
> +
> +class Generator(generator.Generator):
> +
> +    @staticmethod
> +    def GetTemplatePrefix():
> +        return 'libcamera_templates'
> +
> +    def GetFilters(self):
> +        libcamera_filters = {
> +            'all_types': GetAllTypes,
> +            'bit_width': BitWidth,
> +            'cap': Capitalize,

            'cap': str.capitalize,

and drop the custom Capitalize function.

> +            'choose': Choose,
> +            'comma_sep': CommaSep,
> +            'default_value': GetDefaultValue,
> +            'has_default_fields': HasDefaultFields,
> +            'has_fd': HasFd,
> +            'is_async': IsAsync,
> +            'is_array': IsArray,
> +            'is_controls': IsControls,
> +            'is_enum': IsEnum,
> +            'is_fd': IsFd,
> +            'is_map': IsMap,
> +            'is_plain_struct': IsPlainStruct,
> +            'is_pod': IsPod,
> +            'is_str': IsStr,
> +            'method_input_has_fd': MethodInputHasFd,
> +            'method_output_has_fd': MethodOutputHasFd,
> +            'method_param_names': MethodParamNames,
> +            'method_param_inputs': MethodParamInputs,
> +            'method_param_outputs': MethodParamOutputs,
> +            'method_parameters': MethodParameters,
> +            'method_return_value': MethodReturnValue,
> +            'name': GetNameForElement,
> +            'name_full': GetFullNameForElement,
> +            'needs_control_serializer': NeedsControlSerializer,
> +            'params_comma_sep': ParamsCommaSep,
> +            'with_default_values': WithDefaultValues,
> +            'with_fds': WithFds,
> +        }
> +        return libcamera_filters
> +
> +    def _GetJinjaExports(self):

Would it make sense to store the return value of
ModuleClassName(self.module) in a local variable instead of calling the
function several times below ? If self.module is constant during the
lifetime of the Generator (I think it is), you could even compute it in
the constructor and store it in a member variable.

> +        return {
> +            'base_controls': '%s::controls' % ModuleClassName(self.module),
> +            'cmd_enum_name': '_%sCmd' % ModuleClassName(self.module),
> +            'cmd_event_enum_name': '_%sEventCmd' % ModuleClassName(self.module),
> +            'enums': self.module.enums,
> +            'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
> +            'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
> +            'has_namespace': self.module.mojom_namespace != '',
> +            'imports': self.module.imports,

I don't see any mention of 'imports' in the templates. Is this used
internally by mojom and/or jinja, or is it unused ?

> +            'interface_cb': self.module.interfaces[1],

interface_event ?

> +            'interface_main': self.module.interfaces[0],

Is there a guarantee the interfaces will always be specified in this
order ? The code in ValidateInterfaces() finds interfaces based on their
name.

> +            'interface_name': 'IPA%sInterface' % ModuleClassName(self.module),
> +            'ipc_name': 'IPAIPCUnixSocket',

This seems unused.

> +            'kinds': self.module.kinds,
> +            'module': self.module,

Same for those two (unless they are used elsewhere than in the
templates).

> +            'module_name': ModuleName(self.module.path),
> +            'module_class_name': ModuleClassName(self.module),

Ditto.

> +            'namespace': self.module.mojom_namespace.split('.'),
> +            'namespace_str': self.module.mojom_namespace.replace('.', '::') if
> +                             self.module.mojom_namespace is not None else '',
> +            'proxy_name': 'IPAProxy%s' % ModuleClassName(self.module),
> +            'proxy_worker_name': 'IPAProxy%sWorker' % ModuleClassName(self.module),

Same here.

> +            'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0],
> +            'year': datetime.datetime.now().year,
> +        }
> +
> +
> +    @UseJinja('module_ipa_interface.h.tmpl')
> +    def _GenerateDataHeader(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_serializer.h.tmpl')
> +    def _GenerateSerializer(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_proxy.cpp.tmpl')
> +    def _GenerateProxyCpp(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_proxy.h.tmpl')
> +    def _GenerateProxyHeader(self):
> +        return self._GetJinjaExports()
> +
> +    @UseJinja('module_ipa_proxy_worker.cpp.tmpl')
> +    def _GenerateProxyWorker(self):
> +        return self._GetJinjaExports()
> +
> +    def GenerateFiles(self, unparsed_args):
> +        parser = argparse.ArgumentParser()
> +        parser.add_argument('--libcamera_generate_header',       action='store_true')
> +        parser.add_argument('--libcamera_generate_serializer',   action='store_true')
> +        parser.add_argument('--libcamera_generate_proxy_cpp',    action='store_true')
> +        parser.add_argument('--libcamera_generate_proxy_h',      action='store_true')
> +        parser.add_argument('--libcamera_generate_proxy_worker', action='store_true')
> +        parser.add_argument('--libcamera_output_path')
> +        args = parser.parse_args(unparsed_args)
> +
> +        ValidateInterfaces(self.module.interfaces)
> +
> +        fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path))
> +
> +        module_name = ModuleName(self.module.path)

This seems unused.

> +
> +        if args.libcamera_generate_header:
> +            self.Write(self._GenerateDataHeader(),
> +                       args.libcamera_output_path)
> +
> +        if args.libcamera_generate_serializer:
> +            self.Write(self._GenerateSerializer(),
> +                       args.libcamera_output_path)
> +
> +        if args.libcamera_generate_proxy_cpp:
> +            self.Write(self._GenerateProxyCpp(),
> +                       args.libcamera_output_path)
> +
> +        if args.libcamera_generate_proxy_h:
> +            self.Write(self._GenerateProxyHeader(),
> +                       args.libcamera_output_path)
> +
> +        if args.libcamera_generate_proxy_worker:
> +            self.Write(self._GenerateProxyWorker(),
> +                       args.libcamera_output_path)

I'm sure we could avoid the manual unrolling of all this, using a
dictionary to store the template files and iterating over it to add
arguments to the parser and replacing the above code, but it's probably
not worth it as I don't expect this will change a lot.

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list