summaryrefslogtreecommitdiff
path: root/utils/codegen/ipc/generators/mojom_libcamera_generator.py
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>2024-08-08 18:13:00 +0300
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2024-08-15 23:59:08 +0300
commit50c92cc7e2924009ecab3e4004448b01d687707c (patch)
treec22b49816a3c79dae4727780962aa0928df42b52 /utils/codegen/ipc/generators/mojom_libcamera_generator.py
parentd3bf27180ef1d91b86b7b87a2378e559eaff5455 (diff)
meson: Move all code generation scripts to utils/codegen/
We have multiple code generation scripts in utils/, mixed with other miscellaneous utilities, as well as a larger code base based on mojom in utils/ipc/. To make code sharing easier between the generator scripts, without creating a mess in the utils/ directory, move all the code generation code to utils/codegen/. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>
Diffstat (limited to 'utils/codegen/ipc/generators/mojom_libcamera_generator.py')
-rw-r--r--utils/codegen/ipc/generators/mojom_libcamera_generator.py553
1 files changed, 553 insertions, 0 deletions
diff --git a/utils/codegen/ipc/generators/mojom_libcamera_generator.py b/utils/codegen/ipc/generators/mojom_libcamera_generator.py
new file mode 100644
index 00000000..b8209e51
--- /dev/null
+++ b/utils/codegen/ipc/generators/mojom_libcamera_generator.py
@@ -0,0 +1,553 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2020, Google Inc.
+#
+# Author: Paul Elder <paul.elder@ideasonboard.com>
+#
+# Generates libcamera files from a mojom.Module.
+
+import argparse
+import datetime
+import os
+import re
+
+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):
+ return re.sub(r'^IPA(.*)Interface$', lambda match: match.group(1),
+ module.interfaces[0].mojom_name)
+
+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.ValueKind:
+ return '0'
+ if IsFlags(element):
+ return ''
+ 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 == 'SharedFD':
+ return '-1'
+ return ''
+
+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):
+ if hasattr(element, 'attributes'):
+ return element.attributes or {}
+ return {}
+ attrs = [(x.attributes) for x in element.fields]
+ ret = {}
+ for d in attrs:
+ ret.update(d or {})
+ if hasattr(element, 'attributes'):
+ ret.update(element.attributes or {})
+ return ret
+
+def NeedsControlSerializer(element):
+ types = GetAllTypes(element)
+ for type in ['ControlList', 'ControlInfoMap']:
+ if f'x:{type}' in types:
+ raise Exception(f'Unknown type "{type}" in {element.mojom_name}, did you mean "libcamera.{type}"?')
+ 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 "SharedFD" in types or (attrs is not None and "hasFd" in attrs)
+
+def WithDefaultValues(element):
+ return [x for x in element if HasDefaultValue(x)]
+
+def WithFds(element):
+ return [x for x in element if HasFd(x)]
+
+def MethodParamInputs(method):
+ return method.parameters
+
+def MethodParamOutputs(method):
+ if method.response_parameters is None:
+ return []
+
+ if MethodReturnValue(method) == 'void':
+ return method.response_parameters
+
+ if len(method.response_parameters) <= 1:
+ return []
+
+ return method.response_parameters[1:]
+
+def MethodParamsHaveFd(parameters):
+ return len([x for x in parameters if HasFd(x)]) > 0
+
+def MethodInputHasFd(method):
+ return MethodParamsHaveFd(method.parameters)
+
+def MethodOutputHasFd(method):
+ return MethodParamsHaveFd(MethodParamOutputs(method))
+
+def MethodParamNames(method):
+ params = []
+ for param in method.parameters:
+ params.append(param.mojom_name)
+ for param in MethodParamOutputs(method):
+ 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 IsPod(param) or IsEnum(param) else '&',
+ param.mojom_name))
+ for param in MethodParamOutputs(method):
+ params.append(f'{GetNameForElement(param)} *{param.mojom_name}')
+ return params
+
+def MethodReturnValue(method):
+ if method.response_parameters is None or len(method.response_parameters) == 0:
+ return 'void'
+ first_output = method.response_parameters[0]
+ if ((len(method.response_parameters) == 1 and IsPod(first_output)) or
+ first_output.kind == mojom.INT32):
+ return GetNameForElement(first_output)
+ return 'void'
+
+def IsAsync(method):
+ # Events are always async
+ 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)
+
+
+# Only works the enum definition, not types
+def IsScoped(element):
+ attributes = getattr(element, 'attributes', None)
+ if not attributes:
+ return False
+ return 'scopedEnum' in attributes
+
+
+def IsEnumScoped(element):
+ if not IsEnum(element):
+ return False
+ return IsScoped(element.kind)
+
+def IsFd(element):
+ return mojom.IsStructKind(element.kind) and element.kind.mojom_name == "SharedFD"
+
+
+def IsFlags(element):
+ attributes = getattr(element, 'attributes', None)
+ if not attributes:
+ return False
+ return 'flags' in attributes
+
+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 ByteWidthFromCppType(t):
+ key = None
+ for mojo_type, cpp_type in _kind_to_cpp_type.items():
+ if t == cpp_type:
+ key = mojo_type
+ if key is None:
+ raise Exception('invalid type')
+ return str(int(_bit_widths[key]) // 8)
+
+# Get the type name for a given element
+def GetNameForElement(element):
+ # Flags
+ if IsFlags(element):
+ return f'Flags<{GetFullNameForElement(element.kind)}>'
+ # structs
+ if (mojom.IsEnumKind(element) or
+ mojom.IsInterfaceKind(element) or
+ mojom.IsStructKind(element)):
+ return element.mojom_name
+ # vectors
+ if (mojom.IsArrayKind(element)):
+ elem_name = GetFullNameForElement(element.kind)
+ return f'std::vector<{elem_name}>'
+ # maps
+ if (mojom.IsMapKind(element)):
+ key_name = GetFullNameForElement(element.key_kind)
+ value_name = GetFullNameForElement(element.value_kind)
+ return f'std::map<{key_name}, {value_name}>'
+ # struct fields and function parameters
+ if isinstance(element, (mojom.Field, mojom.Method, mojom.Parameter)):
+ # maps and vectors
+ if (mojom.IsArrayKind(element.kind) or mojom.IsMapKind(element.kind)):
+ return GetNameForElement(element.kind)
+ # strings
+ if (mojom.IsReferenceKind(element.kind) and element.kind.spec == 's'):
+ return 'std::string'
+ # PODs
+ if element.kind in _kind_to_cpp_type:
+ return _kind_to_cpp_type[element.kind]
+ # structs and enums
+ return element.kind.mojom_name
+ # PODs that are members of vectors/maps
+ if (hasattr(element, '__hash__') and element in _kind_to_cpp_type):
+ return _kind_to_cpp_type[element]
+ if (hasattr(element, 'spec')):
+ # strings that are members of vectors/maps
+ if (element.spec == 's'):
+ return 'std::string'
+ # structs that aren't defined in mojom that are members of vectors/maps
+ if (element.spec[0] == 'x'):
+ return element.spec.replace('x:', '').replace('.', '::')
+ 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):
+ name = GetNameForElement(element)
+ namespace_str = ''
+ if (mojom.IsStructKind(element) or mojom.IsEnumKind(element)):
+ namespace_str = element.module.mojom_namespace.replace('.', '::')
+ elif (hasattr(element, 'kind') and
+ (mojom.IsStructKind(element.kind) or mojom.IsEnumKind(element.kind))):
+ namespace_str = element.kind.module.mojom_namespace.replace('.', '::')
+
+ if namespace_str == '':
+ return name
+
+ if IsFlags(element):
+ return GetNameForElement(element)
+
+ 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 GetMainInterface(interfaces):
+ 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')
+ return None if len(intf) == 0 else intf[0]
+
+def GetEventInterface(interfaces):
+ event = [x for x in interfaces if re.match("^IPA.*EventInterface", x.mojom_name)]
+ ValidateSingleLength(event, 'event interface')
+ return None if len(event) == 0 else event[0]
+
+def ValidateNamespace(namespace):
+ if namespace == '':
+ raise Exception('Must have a namespace')
+
+ if not re.match(r'^ipa\.[0-9A-Za-z_]+', namespace):
+ raise Exception('Namespace must be of the form "ipa.{pipeline_name}"')
+
+def ValidateInterfaces(interfaces):
+ # Validate presence of main interface
+ intf = GetMainInterface(interfaces)
+ if intf is None:
+ raise Exception('Must have main IPA interface')
+
+ # Validate presence of event interface
+ event = GetEventInterface(interfaces)
+ if intf is None:
+ raise Exception('Must have event IPA interface')
+
+ # 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_stop = f_stop[0]
+
+ # No need to validate init() and start() as they are customizable
+
+ # Validate parameters to stop()
+ ValidateZeroLength(f_stop.parameters, 'input parameter to stop()')
+ ValidateZeroLength(f_stop.parameters, 'output parameter from stop()')
+
+ # Validate that event interface has at least one event
+ if len(event.methods) < 1:
+ raise Exception('Event interface must have at least one event')
+
+ # 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)
+
+ event_methods_async = [x for x in event.methods if IsAsync(x)]
+ for method in event_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,
+ 'byte_width' : ByteWidthFromCppType,
+ 'cap': Capitalize,
+ '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_enum_scoped': IsEnumScoped,
+ 'is_fd': IsFd,
+ 'is_flags': IsFlags,
+ 'is_map': IsMap,
+ 'is_plain_struct': IsPlainStruct,
+ 'is_pod': IsPod,
+ 'is_scoped': IsScoped,
+ '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):
+ return {
+ 'cmd_enum_name': '_%sCmd' % self.module_name,
+ 'cmd_event_enum_name': '_%sEventCmd' % self.module_name,
+ 'consts': self.module.constants,
+ '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 != '',
+ 'interface_event': GetEventInterface(self.module.interfaces),
+ 'interface_main': GetMainInterface(self.module.interfaces),
+ 'interface_name': 'IPA%sInterface' % self.module_name,
+ 'module_name': ModuleName(self.module.path),
+ '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' % self.module_name,
+ 'proxy_worker_name': 'IPAProxy%sWorker' % self.module_name,
+ 'structs_nonempty': [x for x in self.module.structs if len(x.fields) > 0],
+ }
+
+ def _GetJinjaExportsForCore(self):
+ return {
+ 'consts': self.module.constants,
+ 'enums_gen_header': [x for x in self.module.enums if x.attributes is None or 'skipHeader' not in x.attributes],
+ '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,
+ 'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes],
+ 'structs_gen_serializer': [x for x in self.module.structs if x.attributes is None or 'skipSerdes' not in x.attributes],
+ }
+
+ @UseJinja('core_ipa_interface.h.tmpl')
+ def _GenerateCoreHeader(self):
+ return self._GetJinjaExportsForCore()
+
+ @UseJinja('core_ipa_serializer.h.tmpl')
+ def _GenerateCoreSerializer(self):
+ return self._GetJinjaExportsForCore()
+
+ @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_core_header', action='store_true')
+ parser.add_argument('--libcamera_generate_core_serializer', action='store_true')
+ 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)
+
+ if not args.libcamera_generate_core_header and \
+ not args.libcamera_generate_core_serializer:
+ ValidateNamespace(self.module.mojom_namespace)
+ ValidateInterfaces(self.module.interfaces)
+ self.module_name = ModuleClassName(self.module)
+
+ fileutil.EnsureDirectoryExists(os.path.dirname(args.libcamera_output_path))
+
+ gen_funcs = [
+ [args.libcamera_generate_core_header, self._GenerateCoreHeader],
+ [args.libcamera_generate_core_serializer, self._GenerateCoreSerializer],
+ [args.libcamera_generate_header, self._GenerateDataHeader],
+ [args.libcamera_generate_serializer, self._GenerateSerializer],
+ [args.libcamera_generate_proxy_cpp, self._GenerateProxyCpp],
+ [args.libcamera_generate_proxy_h, self._GenerateProxyHeader],
+ [args.libcamera_generate_proxy_worker, self._GenerateProxyWorker],
+ ]
+
+ for pair in gen_funcs:
+ if pair[0]:
+ self.Write(pair[1](), args.libcamera_output_path)