Add initial support for API versioning (see #3836)

- Generated files are now created when running cef_create_projects or
  the new version_manager.py tool. These files are still created in the
  cef/ source tree (same location as before) but Git ignores them due to
  the generated .gitignore file.
- API hashes are committed to Git as a new cef_api_versions.json file.
  This file is used for both code generation and CEF version calculation
  (replacing the previous usage of cef_api_hash.h for this purpose).
  It will be updated by the CEF admin before merging breaking API
  changes upstream.
- As an added benefit to the above, contributor PRs will no longer
  contain generated code that is susceptible to frequent merge conflicts.
- From a code generation perspective, the main difference is that we now
  use versioned structs (e.g. cef_browser_0_t instead of cef_browser_t)
  on the libcef (dll/framework) side. Most of the make_*.py tool changes
  are related to supporting this.
- From the client perspective, you can now define CEF_API_VERSION in the
  project configuration (or get CEF_EXPERIMENTAL by default). This
  define will change the API exposed in CEF’s include/ and include/capi
  header files. All client-side targets including libcef_dll_wrapper
  will need be recompiled when changing this define.
- Examples of the new API-related define usage are provided in
  cef_api_version_test.h, api_version_test_impl.cc and
  api_version_unittest.cc.

To test:
- Run `ceftests --gtest_filter=ApiVersionTest.*`
- Add `cef_api_version=13300` to GN_DEFINES. Re-run configure, build and
  ceftests steps.
- Repeat with 13301, 13302, 13303 (all supported test versions).
This commit is contained in:
Marshall Greenblatt
2024-12-09 15:20:44 -05:00
parent 219bf3406c
commit dd81904a2f
68 changed files with 7466 additions and 1265 deletions

View File

@@ -3,6 +3,7 @@
# can be found in the LICENSE file.
from __future__ import absolute_import
import bisect
from date_util import *
from file_util import *
import os
@@ -12,11 +13,26 @@ import string
import sys
import textwrap
import time
from version_util import version_as_numeric, version_as_variable
_NOTIFY_CONTEXT = None
_NOTIFY_CONTEXT_LAST = None
def set_notify_context(context):
global _NOTIFY_CONTEXT
_NOTIFY_CONTEXT = context
def notify(msg):
""" Display a message. """
sys.stdout.write(' NOTE: ' + msg + '\n')
global _NOTIFY_CONTEXT_LAST
if not _NOTIFY_CONTEXT is None and _NOTIFY_CONTEXT != _NOTIFY_CONTEXT_LAST:
print('In %s:' % _NOTIFY_CONTEXT)
_NOTIFY_CONTEXT_LAST = _NOTIFY_CONTEXT
print(' NOTE: ' + msg)
def wrap_text(text, indent='', maxchars=80, listitem=False):
@@ -44,25 +60,28 @@ def is_base_class(clsname):
return clsname == 'CefBaseRefCounted' or clsname == 'CefBaseScoped'
def get_capi_file_name(cppname):
def get_capi_file_name(cppname, versions=False):
""" Convert a C++ header file name to a C API header file name. """
return cppname[:-2] + '_capi.h'
return cppname[:-2] + ('_capi_versions.h' if versions else '_capi.h')
def get_capi_name(cppname, isclassname, prefix=None):
def get_capi_name(cppname, isclassname, prefix=None, version=None):
""" Convert a C++ CamelCaps name to a C API underscore name. """
result = ''
lastchr = ''
for chr in cppname:
# add an underscore if the current character is an upper case letter
# and the last character was a lower case letter
if len(result) > 0 and not chr.isdigit() \
# and the last character was a lower case letter or number.
if len(result) > 0 and chr.isalpha() \
and chr.upper() == chr \
and not lastchr.upper() == lastchr:
and lastchr.isalnum() and lastchr.lower() == lastchr:
result += '_'
result += chr.lower()
lastchr = chr
if isclassname and not version is None:
result += '_%d' % version
if isclassname:
result += '_t'
@@ -259,7 +278,10 @@ def format_translation_changes(old, new):
return result
def format_translation_includes(header, body):
def format_translation_includes(header,
body,
with_versions=False,
other_includes=None):
""" Return the necessary list of includes based on the contents of the
body.
"""
@@ -269,47 +291,187 @@ def format_translation_includes(header, body):
if body.find('std::min') > 0 or body.find('std::max') > 0:
result += '#include <algorithm>\n'
if body.find('cef_api_hash(') > 0:
result += '#include "include/cef_api_hash.h"\n'
paths = set()
if body.find('cef_api_hash(') > 0 or body.find('cef_api_version(') > 0:
paths.add('include/cef_api_hash.h')
if body.find('template_util::has_valid_size(') > 0:
result += '#include "libcef_dll/template_util.h"\n'
paths.add('libcef_dll/template_util.h')
# identify what CppToC classes are being used
p = re.compile(r'([A-Za-z0-9_]{1,})CppToC')
list = sorted(set(p.findall(body)))
for item in list:
directory = ''
if not is_base_class(item):
cls = header.get_class(item)
dir = cls.get_file_directory()
if not dir is None:
directory = dir + '/'
result += '#include "libcef_dll/cpptoc/'+directory+ \
get_capi_name(item[3:], False)+'_cpptoc.h"\n'
# identify what CToCpp classes are being used
p = re.compile(r'([A-Za-z0-9_]{1,})CToCpp')
list = sorted(set(p.findall(body)))
for item in list:
directory = ''
if not is_base_class(item):
cls = header.get_class(item)
dir = cls.get_file_directory()
if not dir is None:
directory = dir + '/'
result += '#include "libcef_dll/ctocpp/'+directory+ \
get_capi_name(item[3:], False)+'_ctocpp.h"\n'
search = ((True, True, r'([A-Za-z0-9_]{1,})_[0-9]{1,}_CppToC'),
(True, False, r'([A-Za-z0-9_]{1,})CppToC'),
(False, True, r'([A-Za-z0-9_]{1,})_[0-9]{1,}_CToCpp'),
(False, False, r'([A-Za-z0-9_]{1,})CToCpp'))
for cpptoc, versioned, regex in search:
# identify what classes are being used
p = re.compile(regex)
items = set(p.findall(body))
for item in items:
if item == 'Cef':
continue
if not versioned and item[-1] == '_':
# skip versioned names that are picked up by the unversioned regex
continue
directory = ''
if not is_base_class(item):
cls = header.get_class(item)
if cls is None:
raise Exception('Class does not exist: ' + item)
dir = cls.get_file_directory()
if not dir is None:
directory = dir + '/'
type = 'cpptoc' if cpptoc else 'ctocpp'
paths.add('libcef_dll/' + type + '/'+directory+ \
get_capi_name(item[3:], False)+'_' + type + '.h')
if body.find('shutdown_checker') > 0:
result += '#include "libcef_dll/shutdown_checker.h"\n'
paths.add('libcef_dll/shutdown_checker.h')
if body.find('transfer_') > 0:
result += '#include "libcef_dll/transfer_util.h"\n'
paths.add('libcef_dll/transfer_util.h')
if not other_includes is None:
paths.update(other_includes)
if len(paths) > 0:
if len(result) > 0:
result += '\n'
paths = sorted(list(paths))
result += '\n'.join(['#include "%s"' % p for p in paths]) + '\n'
return result
def format_notreached(library_side, msg, default_retval='', indent=' '):
if library_side:
return 'NOTREACHED() << __func__ << ' + msg + ';'
return 'CHECK(false) << __func__ << ' + msg + ';\n' + \
indent + 'return%s;' % ((' ' + default_retval) if len(default_retval) > 0 else '')
def _has_version_added(attribs):
return 'added' in attribs
def _has_version_removed(attribs):
return 'removed' in attribs
def _has_version(attribs):
return _has_version_added(attribs) or _has_version_removed(attribs)
def get_version_check(attribs):
assert _has_version(attribs)
added = attribs.get('added', None)
if not added is None:
added = version_as_variable(added)
removed = attribs.get('removed', None)
if not removed is None:
removed = version_as_variable(removed)
if not added is None and not removed is None:
return 'CEF_API_RANGE(%s, %s)' % (added, removed)
elif not added is None:
return 'CEF_API_ADDED(%s)' % added
return 'CEF_API_REMOVED(%s)' % removed
def _get_version_attrib(attribs, key):
value = attribs.get(key, None)
if not value is None:
return version_as_numeric(value)
# Unversioned is always the first value.
return 0
def _get_version_added(attribs):
""" Returns a numeric 'added' value used for sorting purposes. """
return _get_version_attrib(attribs, 'added')
def _get_version_removed(attribs):
""" Returns a numeric 'removed' value used for sorting purposes. """
return _get_version_attrib(attribs, 'removed')
def get_version_surround(obj, long=False):
""" Returns (pre,post) strings for a version check. """
version_check = obj.get_version_check() if obj.has_version() else None
# Don't duplicate the surrounding class version check for a virtual method.
if not version_check is None and \
isinstance(obj, obj_function) and isinstance(obj.parent, obj_class) and \
obj.parent.has_version() and obj.parent.get_version_check() == version_check:
version_check = None
if not version_check is None:
return ('#if %s\n' % version_check, ('#endif // %s\n' % version_check)
if long else '#endif\n')
return ('', '')
def get_clsname(cls, version):
name = cls.get_name()
if not version is None:
# Select the appropriate version for this class.
closest_version = cls.get_closest_version(version)
if closest_version is None:
raise Exception('Cannot find version <= %d for %s' % (version, name))
return '%s_%d_' % (name, closest_version)
return name
def _version_order_funcs(funcs, max_version=None):
""" Applies version-based ordering to a list of funcs. """
versions = {0: []}
for func in funcs:
if func.has_version():
added = func.get_version_added()
if not added in versions:
versions[added] = [func]
else:
versions[added].append(func)
else:
# Unversioned funcs.
versions[0].append(func)
result = []
for version in sorted(versions.keys()):
if not max_version is None and version > max_version:
break
result.extend(versions[version])
return result
def _get_all_versions(funcs):
# Using a set to ensure uniqueness.
versions = set({0})
for func in funcs:
if func.has_version():
versions.add(func.get_version_added())
versions.add(func.get_version_removed())
return versions
def _find_closest_not_greater(lst, target):
assert isinstance(lst, list), lst
assert isinstance(target, int), target
idx = bisect.bisect_right(lst, target) - 1
if idx < 0:
return None
return lst[idx]
def str_to_dict(str):
""" Convert a string to a dictionary. If the same key has multiple values
the values will be stored in a list. """
@@ -357,6 +519,13 @@ def dict_to_str(dict):
return ','.join(str)
# Attribute keys allowed in CEF metadata comments.
COMMON_ATTRIB_KEYS = ('added', 'removed')
CLASS_ATTRIB_KEYS = COMMON_ATTRIB_KEYS + ('no_debugct_check', 'source')
FUNCTION_ATTRIB_KEYS = COMMON_ATTRIB_KEYS + ('api_hash_check', 'capi_name',
'count_func', 'default_retval',
'index_param', 'optional_param')
# regex for matching comment-formatted attributes
_cre_attrib = r'/\*--cef\(([A-Za-z0-9_ ,=:\n]{0,})\)--\*/'
# regex for matching class and function names
@@ -747,13 +916,18 @@ class obj_header:
res.append(cls)
return res
def get_class(self, classname, defined_structs=None):
def get_class(self, classname):
""" Return the specified class or None if not found. """
for cls in self.classes:
if cls.get_name() == classname:
return cls
elif not defined_structs is None:
defined_structs.append(cls.get_capi_name())
return None
def get_capi_class(self, classname):
""" Return the specified class or None if not found. """
for cls in self.classes:
if cls.get_capi_name() == classname:
return cls
return None
def get_class_names(self):
@@ -856,6 +1030,8 @@ class obj_class:
self.includes = includes
self.forward_declares = forward_declares
self._validate_attribs()
# extract typedefs
p = re.compile(
r'\n' + _cre_space + r'typedef' + _cre_space + _cre_typedef + r';',
@@ -895,13 +1071,19 @@ class obj_class:
# build the virtual function objects
self.virtualfuncs = []
self.has_versioned_funcs = False
for attrib, retval, argval, vfmod in list:
comment = get_comment(body, retval + '(' + argval + ')')
validate_comment(filename, retval, comment)
if not self.has_versioned_funcs and _has_version(attrib):
self.has_versioned_funcs = True
self.virtualfuncs.append(
obj_function_virtual(self, attrib, retval, argval, comment,
vfmod.strip()))
self.virtualfuncs_ordered = None
self.allversions = None
def __repr__(self):
result = '/* ' + dict_to_str(
self.attribs) + ' */ class ' + self.name + "\n{"
@@ -930,15 +1112,21 @@ class obj_class:
result += "\n};\n"
return result
def _validate_attribs(self):
for key in self.attribs.keys():
if not key in CLASS_ATTRIB_KEYS:
raise Exception('Invalid attribute key \"%s\" for class %s' %
(key, self.get_name()))
def get_file_name(self):
""" Return the C++ header file name. Includes the directory component,
if any. """
return self.filename
def get_capi_file_name(self):
def get_capi_file_name(self, versions=False):
""" Return the CAPI header file name. Includes the directory component,
if any. """
return get_capi_file_name(self.filename)
return get_capi_file_name(self.filename, versions)
def get_file_directory(self):
""" Return the file directory component, if any. """
@@ -947,21 +1135,44 @@ class obj_class:
return self.filename[:pos]
return None
def get_name(self):
def get_name(self, version=None):
""" Return the class name. """
if not version is None:
# Select the appropriate version for this class.
closest_version = self.get_closest_version(version)
if closest_version is None:
raise Exception('Cannot find version <= %d for %s' % (version,
self.name))
return '%s_%d_' % (self.name, closest_version)
return self.name
def get_capi_name(self):
def get_capi_name(self, version=None, first_version=False):
""" Return the CAPI structure name for this class. """
return get_capi_name(self.name, True)
# Select the appropriate version for this class.
if first_version:
version = self.get_first_version()
elif not version is None:
closest_version = self.get_closest_version(version)
if closest_version is None:
raise Exception('Cannot find version <= %d for %s' % (version,
self.name))
version = closest_version
return get_capi_name(self.name, True, version=version)
def get_parent_name(self):
""" Return the parent class name. """
return self.parent_name
def get_parent_capi_name(self):
def get_parent_capi_name(self, version=None):
""" Return the CAPI structure name for the parent class. """
return get_capi_name(self.parent_name, True)
if not version is None:
# Select the appropriate version for the parent class.
if is_base_class(self.parent_name):
version = None
else:
parent_cls = self.parent.get_class(self.parent_name)
version = parent_cls.get_closest_version(version)
return get_capi_name(self.parent_name, True, version=version)
def has_parent(self, parent_name):
""" Returns true if this class has the specified class anywhere in its
@@ -1043,8 +1254,17 @@ class obj_class:
""" Return the array of static function objects. """
return self.staticfuncs
def get_virtual_funcs(self):
def get_virtual_funcs(self, version_order=False, version=None):
""" Return the array of virtual function objects. """
if version_order and self.has_versioned_funcs:
if version is None:
# Cache the ordering result for future use.
if self.virtualfuncs_ordered is None:
self.virtualfuncs_ordered = _version_order_funcs(self.virtualfuncs)
return self.virtualfuncs_ordered
# Need to order each time to apply the max version.
return _version_order_funcs(self.virtualfuncs, version)
return self.virtualfuncs
def get_types(self, list):
@@ -1078,6 +1298,75 @@ class obj_class:
""" Returns true if the class is implemented by the client. """
return self.attribs['source'] == 'client'
def has_version(self):
""" Returns true if the class has an associated version. """
return _has_version(self.attribs)
def has_version_added(self):
""" Returns true if the class has an associated 'added' version. """
return _has_version_added(self.attribs)
def get_version_added(self):
""" Returns the associated 'added' version. """
return _get_version_added(self.attribs)
def has_version_removed(self):
""" Returns true if the class has an associated 'removed' version. """
return _has_version_removed(self.attribs)
def get_version_removed(self):
""" Returns the associated 'removed' version. """
return _get_version_removed(self.attribs)
def removed_at_version(self, version):
""" Returns true if this class is removed at the specified version. """
return self.has_version_removed() and self.get_version_removed() <= version
def exists_at_version(self, version):
""" Returns true if this class exists at the specified version. """
if self.has_version_added() and self.get_version_added() > version:
return False
return not self.removed_at_version(version)
def get_version_check(self):
""" Returns the #if check for the associated version. """
return get_version_check(self.attribs)
def get_all_versions(self):
""" Returns all distinct versions of this class. """
if not self.allversions is None:
return self.allversions
# Using a set to ensure uniqueness.
versions = set()
# Versions from class inheritance.
if not is_base_class(self.parent_name):
versions.update(
self.parent.get_class(self.parent_name).get_all_versions())
# Versions from virtual methods.
versions.update(_get_all_versions(self.virtualfuncs))
versions = list(versions)
# Clamp to class versions, if specified.
if self.has_version_added():
versions = [x for x in versions if x >= self.get_version_added()]
if self.has_version_removed():
versions = [x for x in versions if x < self.get_version_removed()]
self.allversions = sorted(versions)
return self.allversions
def get_first_version(self):
""" Returns the first version. """
return self.get_all_versions()[0]
def get_closest_version(self, version):
""" Returns the closest version to |version| that is not greater, or None. """
return _find_closest_not_greater(self.get_all_versions(), version)
class obj_typedef:
""" Class representing a typedef statement. """
@@ -1099,9 +1388,9 @@ class obj_typedef:
""" Return the C++ header file name. """
return self.filename
def get_capi_file_name(self):
def get_capi_file_name(self, versions=False):
""" Return the CAPI header file name. """
return get_capi_file_name(self.filename)
return get_capi_file_name(self.filename, versions)
def get_alias(self):
""" Return the alias. """
@@ -1131,6 +1420,8 @@ class obj_function:
self.name = self.retval.remove_name()
self.comment = comment
self._validate_attribs()
# build the argument objects
self.arguments = []
arglist = argval.split(',')
@@ -1163,13 +1454,19 @@ class obj_function:
def __repr__(self):
return '/* ' + dict_to_str(self.attribs) + ' */ ' + self.get_cpp_proto()
def _validate_attribs(self):
for key in self.attribs.keys():
if not key in FUNCTION_ATTRIB_KEYS:
raise Exception('Invalid attribute key \"%s\" for %s' %
(key, self.get_qualified_name()))
def get_file_name(self):
""" Return the C++ header file name. """
return self.filename
def get_capi_file_name(self):
def get_capi_file_name(self, versions=False):
""" Return the CAPI header file name. """
return get_capi_file_name(self.filename)
return get_capi_file_name(self.filename, versions)
def get_name(self):
""" Return the function name. """
@@ -1186,9 +1483,7 @@ class obj_function:
def get_capi_name(self, prefix=None):
""" Return the CAPI function name. """
if 'capi_name' in self.attribs:
return self.attribs['capi_name']
return get_capi_name(self.name, False, prefix)
return get_capi_name(self.get_attrib('capi_name', self.name), False, prefix)
def get_comment(self):
""" Return the function comment as an array of lines. """
@@ -1202,7 +1497,7 @@ class obj_function:
""" Return true if the specified attribute exists. """
return name in self.attribs
def get_attrib(self, name):
def get_attrib(self, name, default=None):
""" Return the first or only value for specified attribute. """
if name in self.attribs:
if isinstance(self.attribs[name], list):
@@ -1211,7 +1506,7 @@ class obj_function:
else:
# the value is a string
return self.attribs[name]
return None
return default
def get_attrib_list(self, name):
""" Return all values for specified attribute as a list. """
@@ -1237,10 +1532,15 @@ class obj_function:
for cls in self.arguments:
cls.get_types(list)
def get_capi_parts(self, defined_structs=[], isimpl=False, prefix=None):
def get_capi_parts(self,
defined_structs=[],
isimpl=False,
prefix=None,
version=None,
version_finder=None):
""" Return the parts of the C API function definition. """
retval = ''
dict = self.retval.get_type().get_capi(defined_structs)
dict = self.retval.get_type().get_capi(defined_structs, version_finder)
if dict['format'] == 'single':
retval = dict['value']
@@ -1249,7 +1549,7 @@ class obj_function:
if isinstance(self, obj_function_virtual):
# virtual functions get themselves as the first argument
str = 'struct _' + self.parent.get_capi_name() + '* self'
str = 'struct _' + self.parent.get_capi_name(version=version) + '* self'
if isinstance(self, obj_function_virtual) and self.is_const():
# const virtual functions get const self pointers
str = 'const ' + str
@@ -1260,7 +1560,7 @@ class obj_function:
if len(self.arguments) > 0:
for cls in self.arguments:
type = cls.get_type()
dict = type.get_capi(defined_structs)
dict = type.get_capi(defined_structs, version_finder)
if dict['format'] == 'single':
args.append(dict['value'])
elif dict['format'] == 'multi-arg':
@@ -1276,9 +1576,15 @@ class obj_function:
return {'retval': retval, 'name': name, 'args': args}
def get_capi_proto(self, defined_structs=[], isimpl=False, prefix=None):
def get_capi_proto(self,
defined_structs=[],
isimpl=False,
prefix=None,
version=None,
version_finder=None):
""" Return the prototype of the C API function. """
parts = self.get_capi_parts(defined_structs, isimpl, prefix)
parts = self.get_capi_parts(defined_structs, isimpl, prefix, version,
version_finder)
result = parts['retval']+' '+parts['name']+ \
'('+', '.join(parts['args'])+')'
return result
@@ -1336,6 +1642,14 @@ class obj_function:
return other_is_library_side == this_is_library_side
def has_version(self):
""" Returns true if the class has an associated version. """
return _has_version(self.attribs)
def get_version_check(self):
""" Returns the #if check for the associated version. """
return get_version_check(self.attribs)
class obj_function_static(obj_function):
""" Class representing a static function. """
@@ -1377,6 +1691,34 @@ class obj_function_virtual(obj_function):
""" Returns true if the method declaration is const. """
return self.isconst
def has_version_added(self):
""" Returns true if a 'added' value was specified. """
return _has_version_added(self.attribs)
def get_version_added(self):
""" Returns the numeric 'added' value, or 0 if unspecified.
Used for sorting purposes only. """
return _get_version_added(self.attribs)
def has_version_removed(self):
""" Returns true if a 'removed' value was specified. """
return _has_version_removed(self.attribs)
def get_version_removed(self):
""" Returns the numeric 'removed' value, or 0 if unspecified.
Used for sorting purposes only. """
return _get_version_removed(self.attribs)
def removed_at_version(self, version):
""" Returns true if this function is removed at the specified version. """
return self.has_version_removed() and self.get_version_removed() <= version
def exists_at_version(self, version):
""" Returns true if this function exists at the specified version. """
if self.has_version_added() and self.get_version_added() > version:
return False
return not self.removed_at_version(version)
class obj_argument:
""" Class representing a function argument. """
@@ -1907,12 +2249,15 @@ class obj_analysis:
""" Return the *Ptr type structure name. """
return self.result_value[:-1]
def get_result_ptr_type(self, defined_structs=[]):
def get_result_ptr_type(self, defined_structs=[], version_finder=None):
""" Return the *Ptr type. """
result = ''
if not self.result_value[:-1] in defined_structs:
name = self.result_value[:-1]
if not version_finder is None:
name = version_finder(name)
if not name in defined_structs:
result += 'struct _'
result += self.result_value
result += name + self.result_value[-1]
if self.is_byref() or self.is_byaddr():
result += '*'
return result
@@ -1951,16 +2296,22 @@ class obj_analysis:
return True
return False
def get_result_struct_type(self, defined_structs=[]):
def get_result_struct_type(self, defined_structs=[], version_finder=None):
""" Return the structure or enumeration type. """
result = ''
name = self.result_value
is_enum = self.is_result_struct_enum()
if not is_enum:
if self.is_const():
result += 'const '
if not self.result_value in defined_structs:
result += 'struct _'
result += self.result_value
if not version_finder is None:
name = version_finder(name)
result += name
if not is_enum:
result += '*'
return result
@@ -2026,7 +2377,7 @@ class obj_analysis:
""" Return the vector structure or basic type name. """
return self.result_value[0]['result_value']
def get_result_vector_type(self, defined_structs=[]):
def get_result_vector_type(self, defined_structs=[], version_finder=None):
""" Return the vector type. """
if not self.has_name():
raise Exception('Cannot use vector as a return type')
@@ -2048,9 +2399,15 @@ class obj_analysis:
result['value'] = str
elif type == 'refptr' or type == 'ownptr' or type == 'rawptr':
str = ''
if not value[:-1] in defined_structs:
# remove the * suffix
name = value[:-1]
if not version_finder is None:
name = version_finder(name)
if not name in defined_structs:
str += 'struct _'
str += value
str += name + value[-1]
if self.is_const():
str += ' const'
str += '*'
@@ -2087,16 +2444,16 @@ class obj_analysis:
return {'value': 'cef_string_multimap_t', 'format': 'multi'}
raise Exception('Only mappings of strings to strings are supported')
def get_capi(self, defined_structs=[]):
def get_capi(self, defined_structs=[], version_finder=None):
""" Format the value for the C API. """
result = ''
format = 'single'
if self.is_result_simple():
result += self.get_result_simple_type()
elif self.is_result_ptr():
result += self.get_result_ptr_type(defined_structs)
result += self.get_result_ptr_type(defined_structs, version_finder)
elif self.is_result_struct():
result += self.get_result_struct_type(defined_structs)
result += self.get_result_struct_type(defined_structs, version_finder)
elif self.is_result_string():
result += self.get_result_string_type()
elif self.is_result_map():
@@ -2106,7 +2463,7 @@ class obj_analysis:
else:
raise Exception('Unsupported map type')
elif self.is_result_vector():
resdict = self.get_result_vector_type(defined_structs)
resdict = self.get_result_vector_type(defined_structs, version_finder)
if resdict['format'] != 'single':
format = resdict['format']
result += resdict['value']