opencv/modules/objc/generator/gen_objc.py
Alexander Alekhin a0d5277e0d Merge branch 4.x
2021-12-30 21:43:45 +00:00

1693 lines
79 KiB
Python
Executable File

#!/usr/bin/env python
from __future__ import print_function, unicode_literals
import sys, re, os.path, errno, fnmatch
import json
import logging
import codecs
import io
from shutil import copyfile
from pprint import pformat
from string import Template
if sys.version_info >= (3, 8): # Python 3.8+
from shutil import copytree
def copy_tree(src, dst):
copytree(src, dst, dirs_exist_ok=True)
else:
from distutils.dir_util import copy_tree
try:
from io import StringIO # Python 3
except:
from io import BytesIO as StringIO
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
# list of modules
config = None
ROOT_DIR = None
total_files = 0
updated_files = 0
module_imports = []
# list of namespaces, which should be skipped by wrapper generator
# the list is loaded from misc/objc/gen_dict.json defined for the module only
namespace_ignore_list = []
# list of class names, which should be skipped by wrapper generator
# the list is loaded from misc/objc/gen_dict.json defined for the module and its dependencies
class_ignore_list = []
# list of enum names, which should be skipped by wrapper generator
enum_ignore_list = []
# list of constant names, which should be skipped by wrapper generator
# ignored constants can be defined using regular expressions
const_ignore_list = []
# list of private constants
const_private_list = []
# { Module : { public : [[name, val],...], private : [[]...] } }
missing_consts = {}
type_dict = {
"" : {"objc_type" : ""}, # c-tor ret_type
"void" : {"objc_type" : "void", "is_primitive" : True, "swift_type": "Void"},
"bool" : {"objc_type" : "BOOL", "is_primitive" : True, "to_cpp": "(bool)%(n)s", "swift_type": "Bool"},
"char" : {"objc_type" : "char", "is_primitive" : True, "swift_type": "Int8"},
"int" : {"objc_type" : "int", "is_primitive" : True, "out_type" : "int*", "out_type_ptr": "%(n)s", "out_type_ref": "*(int*)(%(n)s)", "swift_type": "Int32"},
"long" : {"objc_type" : "long", "is_primitive" : True, "swift_type": "Int"},
"float" : {"objc_type" : "float", "is_primitive" : True, "out_type" : "float*", "out_type_ptr": "%(n)s", "out_type_ref": "*(float*)(%(n)s)", "swift_type": "Float"},
"double" : {"objc_type" : "double", "is_primitive" : True, "out_type" : "double*", "out_type_ptr": "%(n)s", "out_type_ref": "*(double*)(%(n)s)", "swift_type": "Double"},
"size_t" : {"objc_type" : "size_t", "is_primitive" : True},
"int64" : {"objc_type" : "long", "is_primitive" : True, "swift_type": "Int"},
"string" : {"objc_type" : "NSString*", "is_primitive" : True, "from_cpp": "[NSString stringWithUTF8String:%(n)s.c_str()]", "cast_to": "std::string", "swift_type": "String"}
}
# Defines a rule to add extra prefixes for names from specific namespaces.
# In example, cv::fisheye::stereoRectify from namespace fisheye is wrapped as fisheye_stereoRectify
namespaces_dict = {}
# { module: { class | "*" : [ header ]} }
AdditionalImports = {}
# { class : { func : {declaration, implementation} } }
ManualFuncs = {}
# { class : { func : { arg_name : {"ctype" : ctype, "attrib" : [attrib]} } } }
func_arg_fix = {}
# { class : { enum: fixed_enum } }
enum_fix = {}
# { class : { enum: { const: fixed_const} } }
const_fix = {}
# { (class, func) : objc_signature }
method_dict = {
("Mat", "convertTo") : "-convertTo:rtype:alpha:beta:",
("Mat", "setTo") : "-setToScalar:mask:",
("Mat", "zeros") : "+zeros:cols:type:",
("Mat", "ones") : "+ones:cols:type:",
("Mat", "dot") : "-dot:"
}
modules = []
class SkipSymbolException(Exception):
def __init__(self, text):
self.t = text
def __str__(self):
return self.t
def read_contents(fname):
with open(fname, 'r') as f:
data = f.read()
return data
def mkdir_p(path):
''' mkdir -p '''
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
def header_import(hdr):
""" converts absolute header path to import parameter """
pos = hdr.find('/include/')
hdr = hdr[pos+9 if pos >= 0 else 0:]
#pos = hdr.find('opencv2/')
#hdr = hdr[pos+8 if pos >= 0 else 0:]
return hdr
def make_objcname(m):
return "Cv"+m if (m[0] in "0123456789") else m
def make_objcmodule(m):
return "cv"+m if (m[0] in "0123456789") else m
T_OBJC_CLASS_HEADER = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_class_header.template'))
T_OBJC_CLASS_BODY = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_class_body.template'))
T_OBJC_MODULE_HEADER = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_module_header.template'))
T_OBJC_MODULE_BODY = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_module_body.template'))
class GeneralInfo():
def __init__(self, type, decl, namespaces):
self.symbol_id, self.namespace, self.classpath, self.classname, self.name = self.parseName(decl[0], namespaces)
for ns_ignore in namespace_ignore_list:
if self.symbol_id.startswith(ns_ignore + '.'):
raise SkipSymbolException('ignored namespace ({}): {}'.format(ns_ignore, self.symbol_id))
# parse doxygen comments
self.params={}
self.deprecated = False
if type == "class":
docstring = "// C++: class " + self.name + "\n"
else:
docstring=""
if len(decl)>5 and decl[5]:
doc = decl[5]
if re.search("(@|\\\\)deprecated", doc):
self.deprecated = True
docstring += sanitize_documentation_string(doc, type)
elif type == "class":
docstring += "/**\n * The " + self.name + " module\n */\n"
self.docstring = docstring
def parseName(self, name, namespaces):
'''
input: full name and available namespaces
returns: (namespace, classpath, classname, name)
'''
name = name[name.find(" ")+1:].strip() # remove struct/class/const prefix
spaceName = ""
localName = name # <classes>.<name>
for namespace in sorted(namespaces, key=len, reverse=True):
if name.startswith(namespace + "."):
spaceName = namespace
localName = name.replace(namespace + ".", "")
break
pieces = localName.split(".")
if len(pieces) > 2: # <class>.<class>.<class>.<name>
return name, spaceName, ".".join(pieces[:-1]), pieces[-2], pieces[-1]
elif len(pieces) == 2: # <class>.<name>
return name, spaceName, pieces[0], pieces[0], pieces[1]
elif len(pieces) == 1: # <name>
return name, spaceName, "", "", pieces[0]
else:
return name, spaceName, "", "" # error?!
def fullName(self, isCPP=False):
result = ".".join([self.fullClass(), self.name])
return result if not isCPP else get_cname(result)
def fullClass(self, isCPP=False):
result = ".".join([f for f in [self.namespace] + self.classpath.split(".") if len(f)>0])
return result if not isCPP else get_cname(result)
class ConstInfo(GeneralInfo):
def __init__(self, decl, addedManually=False, namespaces=[], enumType=None):
GeneralInfo.__init__(self, "const", decl, namespaces)
self.cname = get_cname(self.name)
self.value = decl[1]
self.enumType = enumType
self.addedManually = addedManually
if self.namespace in namespaces_dict:
self.name = '%s_%s' % (namespaces_dict[self.namespace], self.name)
def __repr__(self):
return Template("CONST $name=$value$manual").substitute(name=self.name,
value=self.value,
manual="(manual)" if self.addedManually else "")
def isIgnored(self):
for c in const_ignore_list:
if re.match(c, self.name):
return True
return False
def normalize_field_name(name):
return name.replace(".","_").replace("[","").replace("]","").replace("_getNativeObjAddr()","_nativeObj")
def normalize_class_name(name):
return re.sub(r"^cv\.", "", name).replace(".", "_")
def get_cname(name):
return name.replace(".", "::")
def cast_from(t):
if t in type_dict and "cast_from" in type_dict[t]:
return type_dict[t]["cast_from"]
return t
def cast_to(t):
if t in type_dict and "cast_to" in type_dict[t]:
return type_dict[t]["cast_to"]
return t
def gen_class_doc(docstring, module, members, enums):
lines = docstring.splitlines()
lines.insert(len(lines)-1, " *")
if len(members) > 0:
lines.insert(len(lines)-1, " * Member classes: " + ", ".join([("`" + m + "`") for m in members]))
lines.insert(len(lines)-1, " *")
else:
lines.insert(len(lines)-1, " * Member of `" + module + "`")
if len(enums) > 0:
lines.insert(len(lines)-1, " * Member enums: " + ", ".join([("`" + m + "`") for m in enums]))
return "\n".join(lines)
class ClassPropInfo():
def __init__(self, decl): # [f_ctype, f_name, '', '/RW']
self.ctype = decl[0]
self.name = decl[1]
self.rw = "/RW" in decl[3]
def __repr__(self):
return Template("PROP $ctype $name").substitute(ctype=self.ctype, name=self.name)
class ClassInfo(GeneralInfo):
def __init__(self, decl, namespaces=[]): # [ 'class/struct cname', ': base', [modlist] ]
GeneralInfo.__init__(self, "class", decl, namespaces)
self.cname = self.name if not self.classname else self.classname + "_" + self.name
self.real_cname = self.name if not self.classname else self.classname + "::" + self.name
self.methods = []
self.methods_suffixes = {}
self.consts = [] # using a list to save the occurrence order
self.private_consts = []
self.imports = set()
self.props= []
self.objc_name = self.name if not self.classname else self.classname + self.name
self.smart = None # True if class stores Ptr<T>* instead of T* in nativeObj field
self.additionalImports = None # additional import files
self.enum_declarations = None # Objective-C enum declarations stream
self.method_declarations = None # Objective-C method declarations stream
self.method_implementations = None # Objective-C method implementations stream
self.objc_header_template = None # Objective-C header code
self.objc_body_template = None # Objective-C body code
for m in decl[2]:
if m.startswith("="):
self.objc_name = m[1:]
self.base = ''
self.is_base_class = True
self.native_ptr_name = "nativePtr"
self.member_classes = [] # Only relevant for modules
self.member_enums = [] # Only relevant for modules
if decl[1]:
self.base = re.sub(r"^.*:", "", decl[1].split(",")[0]).strip().replace(self.objc_name, "")
if self.base:
self.is_base_class = False
self.native_ptr_name = "nativePtr" + self.objc_name
def __repr__(self):
return Template("CLASS $namespace::$classpath.$name : $base").substitute(**self.__dict__)
def getImports(self, module):
return ["#import \"%s.h\"" % make_objcname(c) for c in sorted([m for m in [type_dict[m]["import_module"] if m in type_dict and "import_module" in type_dict[m] else m for m in self.imports] if m != self.name])]
def isEnum(self, c):
return c in type_dict and type_dict[c].get("is_enum", False)
def getForwardDeclarations(self, module):
enum_decl = [x for x in self.imports if self.isEnum(x) and type_dict[x]["import_module"] != module]
enum_imports = sorted(list(set([type_dict[m]["import_module"] for m in enum_decl])))
class_decl = [x for x in self.imports if not self.isEnum(x)]
return ["#import \"%s.h\"" % make_objcname(c) for c in enum_imports] + [""] + ["@class %s;" % c for c in sorted(class_decl)]
def addImports(self, ctype, is_out_type):
if ctype == self.cname:
return
if ctype in type_dict:
objc_import = None
if "v_type" in type_dict[ctype]:
objc_import = type_dict[type_dict[ctype]["v_type"]]["objc_type"]
elif "v_v_type" in type_dict[ctype]:
objc_import = type_dict[type_dict[ctype]["v_v_type"]]["objc_type"]
elif not type_dict[ctype].get("is_primitive", False):
objc_import = type_dict[ctype]["objc_type"]
if objc_import is not None and objc_import not in ["NSNumber*", "NSString*"] and not (objc_import in type_dict and type_dict[objc_import].get("is_primitive", False)):
objc_import = objc_import[:-1] if objc_import[-1] == "*" else objc_import # remove trailing "*"
if objc_import != self.cname:
self.imports.add(objc_import) # remove trailing "*"
def getAllMethods(self):
result = []
result += [fi for fi in self.methods if fi.isconstructor]
result += [fi for fi in self.methods if not fi.isconstructor]
return result
def addMethod(self, fi):
self.methods.append(fi)
def getConst(self, name):
for cand in self.consts + self.private_consts:
if cand.name == name:
return cand
return None
def addConst(self, constinfo):
# choose right list (public or private)
consts = self.consts
for c in const_private_list:
if re.match(c, constinfo.name):
consts = self.private_consts
break
consts.append(constinfo)
def initCodeStreams(self, Module):
self.additionalImports = StringIO()
self.enum_declarations = StringIO()
self.method_declarations = StringIO()
self.method_implementations = StringIO()
if self.base:
self.objc_header_template = T_OBJC_CLASS_HEADER
self.objc_body_template = T_OBJC_CLASS_BODY
else:
self.base = "NSObject"
if self.name != Module:
self.objc_header_template = T_OBJC_CLASS_HEADER
self.objc_body_template = T_OBJC_CLASS_BODY
else:
self.objc_header_template = T_OBJC_MODULE_HEADER
self.objc_body_template = T_OBJC_MODULE_BODY
# misc handling
if self.name == Module:
for i in module_imports or []:
self.imports.add(i)
def cleanupCodeStreams(self):
self.additionalImports.close()
self.enum_declarations.close()
self.method_declarations.close()
self.method_implementations.close()
def generateObjcHeaderCode(self, m, M, objcM):
return Template(self.objc_header_template + "\n\n").substitute(
module = M,
additionalImports = self.additionalImports.getvalue(),
importBaseClass = '#import "' + make_objcname(self.base) + '.h"' if not self.is_base_class else "",
forwardDeclarations = "\n".join([_f for _f in self.getForwardDeclarations(objcM) if _f]),
enumDeclarations = self.enum_declarations.getvalue(),
nativePointerHandling = Template(
"""
#ifdef __cplusplus
@property(readonly)cv::Ptr<$cName> $native_ptr_name;
#endif
#ifdef __cplusplus
- (instancetype)initWithNativePtr:(cv::Ptr<$cName>)nativePtr;
+ (instancetype)fromNative:(cv::Ptr<$cName>)nativePtr;
#endif
"""
).substitute(
cName = self.fullName(isCPP=True),
native_ptr_name = self.native_ptr_name
),
manualMethodDeclations = "",
methodDeclarations = self.method_declarations.getvalue(),
name = self.name,
objcName = make_objcname(self.objc_name),
cName = self.cname,
imports = "\n".join(self.getImports(M)),
docs = gen_class_doc(self.docstring, M, self.member_classes, self.member_enums),
base = self.base)
def generateObjcBodyCode(self, m, M):
return Template(self.objc_body_template + "\n\n").substitute(
module = M,
objcname = make_objcname(M),
nativePointerHandling=Template(
"""
- (instancetype)initWithNativePtr:(cv::Ptr<$cName>)nativePtr {
self = [super $init_call];
if (self) {
_$native_ptr_name = nativePtr;
}
return self;
}
+ (instancetype)fromNative:(cv::Ptr<$cName>)nativePtr {
return [[$objcName alloc] initWithNativePtr:nativePtr];
}
"""
).substitute(
cName = self.fullName(isCPP=True),
objcName = make_objcname(self.objc_name),
native_ptr_name = self.native_ptr_name,
init_call = "init" if self.is_base_class else "initWithNativePtr:nativePtr"
),
manualMethodDeclations = "",
methodImplementations = self.method_implementations.getvalue(),
name = self.name,
objcName = make_objcname(self.objc_name),
cName = self.cname,
imports = "\n".join(self.getImports(M)),
docs = gen_class_doc(self.docstring, M, self.member_classes, self.member_enums),
base = self.base)
class ArgInfo():
def __init__(self, arg_tuple): # [ ctype, name, def val, [mod], argno ]
self.pointer = False
ctype = arg_tuple[0]
if ctype.endswith("*"):
ctype = ctype[:-1]
self.pointer = True
self.ctype = ctype
self.name = arg_tuple[1]
self.defval = arg_tuple[2]
self.out = ""
if "/O" in arg_tuple[3]:
self.out = "O"
if "/IO" in arg_tuple[3]:
self.out = "IO"
def __repr__(self):
return Template("ARG $ctype$p $name=$defval").substitute(ctype=self.ctype,
p=" *" if self.pointer else "",
name=self.name,
defval=self.defval)
class FuncInfo(GeneralInfo):
def __init__(self, decl, module, namespaces=[]): # [ funcname, return_ctype, [modifiers], [args] ]
GeneralInfo.__init__(self, "func", decl, namespaces)
self.cname = get_cname(decl[0])
nested_type = self.classpath.find(".") != -1
self.objc_name = self.name if not nested_type else self.classpath.replace(".", "")
self.classname = self.classname if not nested_type else self.classpath.replace(".", "_")
self.swift_name = self.name
self.cv_name = self.fullName(isCPP=True)
self.isconstructor = self.name == self.classname
if "[" in self.name:
self.objc_name = "getelem"
if self.namespace in namespaces_dict:
self.objc_name = '%s_%s' % (namespaces_dict[self.namespace], self.objc_name)
for m in decl[2]:
if m.startswith("="):
self.objc_name = m[1:]
self.static = ["","static"][ "/S" in decl[2] ]
self.ctype = re.sub(r"^CvTermCriteria", "TermCriteria", decl[1] or "")
self.args = []
func_fix_map = func_arg_fix.get(self.classname or module, {}).get(self.objc_name, {})
for a in decl[3]:
arg = a[:]
arg_fix_map = func_fix_map.get(arg[1], {})
arg[0] = arg_fix_map.get('ctype', arg[0]) #fixing arg type
arg[2] = arg_fix_map.get('defval', arg[2]) #fixing arg defval
arg[3] = arg_fix_map.get('attrib', arg[3]) #fixing arg attrib
self.args.append(ArgInfo(arg))
if type_complete(self.args, self.ctype):
func_fix_map = func_arg_fix.get(self.classname or module, {}).get(self.signature(self.args), {})
name_fix_map = func_fix_map.get(self.name, {})
self.objc_name = name_fix_map.get('name', self.objc_name)
self.swift_name = name_fix_map.get('swift_name', self.swift_name)
for arg in self.args:
arg_fix_map = func_fix_map.get(arg.name, {})
arg.ctype = arg_fix_map.get('ctype', arg.ctype) #fixing arg type
arg.defval = arg_fix_map.get('defval', arg.defval) #fixing arg type
arg.name = arg_fix_map.get('name', arg.name) #fixing arg name
def __repr__(self):
return Template("FUNC <$ctype $namespace.$classpath.$name $args>").substitute(**self.__dict__)
def __lt__(self, other):
return self.__repr__() < other.__repr__()
def signature(self, args):
objc_args = build_objc_args(args)
return "(" + type_dict[self.ctype]["objc_type"] + ")" + self.objc_name + " ".join(objc_args)
def type_complete(args, ctype):
for a in args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
return False
if ctype not in type_dict:
return False
return True
def build_objc_args(args):
objc_args = []
for a in args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
if not a.ctype: # hidden
continue
objc_type = type_dict[a.ctype]["objc_type"]
if "v_type" in type_dict[a.ctype]:
if "O" in a.out:
objc_type = "NSMutableArray<" + objc_type + ">*"
else:
objc_type = "NSArray<" + objc_type + ">*"
elif "v_v_type" in type_dict[a.ctype]:
if "O" in a.out:
objc_type = "NSMutableArray<NSMutableArray<" + objc_type + ">*>*"
else:
objc_type = "NSArray<NSArray<" + objc_type + ">*>*"
if a.out and type_dict[a.ctype].get("out_type", ""):
objc_type = type_dict[a.ctype]["out_type"]
objc_args.append((a.name if len(objc_args) > 0 else '') + ':(' + objc_type + ')' + a.name)
return objc_args
def build_objc_method_name(args):
objc_method_name = ""
for a in args[1:]:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
if not a.ctype: # hidden
continue
objc_method_name += a.name + ":"
return objc_method_name
def get_swift_type(ctype):
has_swift_type = "swift_type" in type_dict[ctype]
swift_type = type_dict[ctype]["swift_type"] if has_swift_type else type_dict[ctype]["objc_type"]
if swift_type[-1:] == "*":
swift_type = swift_type[:-1]
if not has_swift_type:
if "v_type" in type_dict[ctype]:
swift_type = "[" + swift_type + "]"
elif "v_v_type" in type_dict[ctype]:
swift_type = "[[" + swift_type + "]]"
return swift_type
def build_swift_extension_decl(name, args, constructor, static, ret_type):
extension_decl = "@nonobjc " + ("class " if static else "") + (("func " + name) if not constructor else "convenience init") + "("
swift_args = []
for a in args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
if not a.ctype: # hidden
continue
swift_type = get_swift_type(a.ctype)
if "O" in a.out:
if type_dict[a.ctype].get("primitive_type", False):
swift_type = "UnsafeMutablePointer<" + swift_type + ">"
elif "v_type" in type_dict[a.ctype] or "v_v_type" in type_dict[a.ctype] or type_dict[a.ctype].get("primitive_vector", False) or type_dict[a.ctype].get("primitive_vector_vector", False):
swift_type = "inout " + swift_type
swift_args.append(a.name + ': ' + swift_type)
extension_decl += ", ".join(swift_args) + ")"
if ret_type:
extension_decl += " -> " + get_swift_type(ret_type)
return extension_decl
def extension_arg(a):
return a.ctype in type_dict and (type_dict[a.ctype].get("primitive_vector", False) or type_dict[a.ctype].get("primitive_vector_vector", False) or (("v_type" in type_dict[a.ctype] or "v_v_type" in type_dict[a.ctype]) and "O" in a.out))
def extension_tmp_arg(a):
if a.ctype in type_dict:
if type_dict[a.ctype].get("primitive_vector", False) or type_dict[a.ctype].get("primitive_vector_vector", False):
return a.name + "Vector"
elif ("v_type" in type_dict[a.ctype] or "v_v_type" in type_dict[a.ctype]) and "O" in a.out:
return a.name + "Array"
return a.name
def make_swift_extension(args):
for a in args:
if extension_arg(a):
return True
return False
def build_swift_signature(args):
swift_signature = ""
for a in args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
if not a.ctype: # hidden
continue
swift_signature += a.name + ":"
return swift_signature
def build_unrefined_call(name, args, constructor, static, classname, has_ret):
swift_refine_call = ("let ret = " if has_ret and not constructor else "") + ((make_objcname(classname) + ".") if static else "") + (name if not constructor else "self.init")
call_args = []
for a in args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
if not a.ctype: # hidden
continue
call_args.append(a.name + ": " + extension_tmp_arg(a))
swift_refine_call += "(" + ", ".join(call_args) + ")"
return swift_refine_call
def build_swift_logues(args):
prologue = []
epilogue = []
for a in args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
if not a.ctype: # hidden
continue
if a.ctype in type_dict:
if type_dict[a.ctype].get("primitive_vector", False):
prologue.append("let " + extension_tmp_arg(a) + " = " + type_dict[a.ctype]["objc_type"][:-1] + "(" + a.name + ")")
if "O" in a.out:
unsigned = type_dict[a.ctype].get("unsigned", False)
array_prop = "array" if not unsigned else "unsignedArray"
epilogue.append(a.name + ".removeAll()")
epilogue.append(a.name + ".append(contentsOf: " + extension_tmp_arg(a) + "." + array_prop + ")")
elif type_dict[a.ctype].get("primitive_vector_vector", False):
if not "O" in a.out:
prologue.append("let " + extension_tmp_arg(a) + " = " + a.name + ".map {" + type_dict[a.ctype]["objc_type"][:-1] + "($0) }")
else:
prologue.append("let " + extension_tmp_arg(a) + " = NSMutableArray(array: " + a.name + ".map {" + type_dict[a.ctype]["objc_type"][:-1] + "($0) })")
epilogue.append(a.name + ".removeAll()")
epilogue.append(a.name + ".append(contentsOf: " + extension_tmp_arg(a) + ".map { ($.0 as! " + type_dict[a.ctype]["objc_type"][:-1] + ").array })")
elif ("v_type" in type_dict[a.ctype] or "v_v_type" in type_dict[a.ctype]) and "O" in a.out:
prologue.append("let " + extension_tmp_arg(a) + " = NSMutableArray(array: " + a.name + ")")
epilogue.append(a.name + ".removeAll()")
epilogue.append(a.name + ".append(contentsOf: " + extension_tmp_arg(a) + " as! " + get_swift_type(a.ctype) + ")")
return prologue, epilogue
def add_method_to_dict(class_name, fi):
static = fi.static if fi.classname else True
if (class_name, fi.objc_name) not in method_dict:
objc_method_name = ("+" if static else "-") + fi.objc_name + ":" + build_objc_method_name(fi.args)
method_dict[(class_name, fi.objc_name)] = objc_method_name
def see_lookup(objc_class, see):
semi_colon = see.find("::")
see_class = see[:semi_colon] if semi_colon > 0 else objc_class
see_method = see[(semi_colon + 2):] if semi_colon != -1 else see
if (see_class, see_method) in method_dict:
method = method_dict[(see_class, see_method)]
if see_class == objc_class:
return method
else:
return ("-" if method[0] == "-" else "") + "[" + see_class + " " + method[1:] + "]"
else:
return see
class ObjectiveCWrapperGenerator(object):
def __init__(self):
self.header_files = []
self.clear()
def clear(self):
self.namespaces = ["cv"]
mat_class_info = ClassInfo([ 'class Mat', '', [], [] ], self.namespaces)
mat_class_info.namespace = "cv"
self.classes = { "Mat" : mat_class_info }
self.classes["Mat"].namespace = "cv"
self.module = ""
self.Module = ""
self.extension_implementations = None # Swift extensions implementations stream
self.ported_func_list = []
self.skipped_func_list = []
self.def_args_hist = {} # { def_args_cnt : funcs_cnt }
def add_class(self, decl):
classinfo = ClassInfo(decl, namespaces=self.namespaces)
if classinfo.name in class_ignore_list:
logging.info('ignored: %s', classinfo)
return None
if classinfo.name != self.Module:
self.classes[self.Module].member_classes.append(classinfo.objc_name)
name = classinfo.cname
if self.isWrapped(name) and not classinfo.base:
logging.warning('duplicated: %s', classinfo)
return None
if name in self.classes: # TODO implement inner namespaces
if self.classes[name].symbol_id != classinfo.symbol_id:
logging.warning('duplicated under new id: {} (was {})'.format(classinfo.symbol_id, self.classes[name].symbol_id))
return None
self.classes[name] = classinfo
if name in type_dict and not classinfo.base:
logging.warning('duplicated: %s', classinfo)
return None
if name != self.Module:
type_dict.setdefault(name, {}).update(
{ "objc_type" : classinfo.objc_name + "*",
"from_cpp" : "[" + classinfo.objc_name + " fromNative:%(n)s]",
"to_cpp" : "*(%(n)s." + classinfo.native_ptr_name + ")" }
)
# missing_consts { Module : { public : [[name, val],...], private : [[]...] } }
if name in missing_consts:
if 'public' in missing_consts[name]:
for (n, val) in missing_consts[name]['public']:
classinfo.consts.append( ConstInfo([n, val], addedManually=True) )
# class props
for p in decl[3]:
classinfo.props.append( ClassPropInfo(p) )
if name != self.Module:
type_dict.setdefault("Ptr_"+name, {}).update(
{ "objc_type" : classinfo.objc_name + "*",
"c_type" : name,
"real_c_type" : classinfo.real_cname,
"to_cpp": "%(n)s." + classinfo.native_ptr_name,
"from_cpp": "[" + name + " fromNative:%(n)s]"}
)
logging.info('ok: class %s, name: %s, base: %s', classinfo, name, classinfo.base)
return classinfo
def add_const(self, decl, scope=None, enumType=None): # [ "const cname", val, [], [] ]
constinfo = ConstInfo(decl, namespaces=self.namespaces, enumType=enumType)
if constinfo.isIgnored():
logging.info('ignored: %s', constinfo)
else:
objc_type = enumType.rsplit(".", 1)[-1] if enumType else ""
if constinfo.classname in const_fix and objc_type in const_fix[constinfo.classname] and constinfo.name in const_fix[constinfo.classname][objc_type]:
fixed_const = const_fix[constinfo.classname][objc_type][constinfo.name]
constinfo.name = fixed_const
constinfo.cname = fixed_const
if not self.isWrapped(constinfo.classname):
logging.info('class not found: %s', constinfo)
constinfo.name = constinfo.classname + '_' + constinfo.name
constinfo.classname = ''
ci = self.getClass(constinfo.classname)
duplicate = ci.getConst(constinfo.name)
if duplicate:
if duplicate.addedManually:
logging.info('manual: %s', constinfo)
else:
logging.warning('duplicated: %s', constinfo)
else:
ci.addConst(constinfo)
logging.info('ok: %s', constinfo)
def add_enum(self, decl): # [ "enum cname", "", [], [] ]
enumType = decl[0].rsplit(" ", 1)[1]
if enumType.endswith("<unnamed>"):
enumType = None
else:
ctype = normalize_class_name(enumType)
constinfo = ConstInfo(decl[3][0], namespaces=self.namespaces, enumType=enumType)
objc_type = enumType.rsplit(".", 1)[-1]
if objc_type in enum_ignore_list:
return
if constinfo.classname in enum_fix:
objc_type = enum_fix[constinfo.classname].get(objc_type, objc_type)
import_module = constinfo.classname if constinfo.classname and constinfo.classname != objc_type else self.Module
type_dict[ctype] = { "cast_from" : "int",
"cast_to" : get_cname(enumType),
"objc_type" : objc_type,
"is_enum" : True,
"import_module" : import_module,
"from_cpp" : "(" + objc_type + ")%(n)s"}
type_dict[objc_type] = { "cast_to" : get_cname(enumType),
"objc_type": objc_type,
"is_enum": True,
"import_module": import_module,
"from_cpp": "(" + objc_type + ")%(n)s"}
self.classes[self.Module].member_enums.append(objc_type)
const_decls = decl[3]
for decl in const_decls:
self.add_const(decl, self.Module, enumType)
def add_func(self, decl):
fi = FuncInfo(decl, self.Module, namespaces=self.namespaces)
classname = fi.classname or self.Module
if classname in class_ignore_list:
logging.info('ignored: %s', fi)
elif classname in ManualFuncs and fi.objc_name in ManualFuncs[classname]:
logging.info('manual: %s', fi)
if "objc_method_name" in ManualFuncs[classname][fi.objc_name]:
method_dict[(classname, fi.objc_name)] = ManualFuncs[classname][fi.objc_name]["objc_method_name"]
elif not self.isWrapped(classname):
logging.warning('not found: %s', fi)
else:
ci = self.getClass(classname)
if ci.symbol_id != fi.symbol_id[0:fi.symbol_id.rfind('.')] and ci.symbol_id != self.Module:
# TODO fix this (inner namepaces)
logging.warning('SKIP: mismatched class: {} (class: {})'.format(fi.symbol_id, ci.symbol_id))
return
ci.addMethod(fi)
logging.info('ok: %s', fi)
# calc args with def val
cnt = len([a for a in fi.args if a.defval])
self.def_args_hist[cnt] = self.def_args_hist.get(cnt, 0) + 1
add_method_to_dict(classname, fi)
def save(self, path, buf):
global total_files, updated_files
if len(buf) == 0:
return
total_files += 1
if os.path.exists(path):
with open(path, "rt") as f:
content = f.read()
if content == buf:
return
with codecs.open(path, "w", "utf-8") as f:
f.write(buf)
updated_files += 1
def get_namespace_prefix(self, cname):
namespace = self.classes[cname].namespace if cname in self.classes else "cv"
return namespace.replace(".", "::") + "::"
def gen(self, srcfiles, module, output_path, output_objc_path, common_headers, manual_classes):
self.clear()
self.module = module
self.objcmodule = make_objcmodule(module)
self.Module = module.capitalize()
extension_implementations = StringIO() # Swift extensions implementations stream
extension_signatures = []
# TODO: support UMat versions of declarations (implement UMat-wrapper for Java)
parser = hdr_parser.CppHeaderParser(generate_umat_decls=False)
module_ci = self.add_class( ['class ' + self.Module, '', [], []]) # [ 'class/struct cname', ':bases', [modlist] [props] ]
module_ci.header_import = module + '.hpp'
# scan the headers and build more descriptive maps of classes, consts, functions
includes = []
for hdr in common_headers:
logging.info("\n===== Common header : %s =====", hdr)
includes.append(header_import(hdr))
for hdr in srcfiles:
decls = parser.parse(hdr)
self.namespaces = sorted(parser.namespaces)
logging.info("\n\n===== Header: %s =====", hdr)
logging.info("Namespaces: %s", sorted(parser.namespaces))
if decls:
includes.append(header_import(hdr))
else:
logging.info("Ignore header: %s", hdr)
for decl in decls:
logging.info("\n--- Incoming ---\n%s", pformat(decl[:5], 4)) # without docstring
name = decl[0]
try:
if name.startswith("struct") or name.startswith("class"):
ci = self.add_class(decl)
if ci:
ci.header_import = header_import(hdr)
elif name.startswith("const"):
self.add_const(decl)
elif name.startswith("enum"):
# enum
self.add_enum(decl)
else: # function
self.add_func(decl)
except SkipSymbolException as e:
logging.info('SKIP: {} due to {}'.format(name, e))
self.classes[self.Module].member_classes += manual_classes
logging.info("\n\n===== Generating... =====")
package_path = os.path.join(output_objc_path, self.objcmodule)
mkdir_p(package_path)
extension_file = "%s/%sExt.swift" % (package_path, make_objcname(self.Module))
for ci in sorted(self.classes.values(), key=lambda x: x.symbol_id):
if ci.name == "Mat":
continue
ci.initCodeStreams(self.Module)
self.gen_class(ci, self.module, extension_implementations, extension_signatures)
classObjcHeaderCode = ci.generateObjcHeaderCode(self.module, self.Module, ci.objc_name)
objc_mangled_name = make_objcname(ci.objc_name)
header_file = "%s/%s.h" % (package_path, objc_mangled_name)
self.save(header_file, classObjcHeaderCode)
self.header_files.append(header_file)
classObjcBodyCode = ci.generateObjcBodyCode(self.module, self.Module)
self.save("%s/%s.mm" % (package_path, objc_mangled_name), classObjcBodyCode)
ci.cleanupCodeStreams()
self.save(extension_file, extension_implementations.getvalue())
extension_implementations.close()
self.save(os.path.join(output_path, self.objcmodule+".txt"), self.makeReport())
def makeReport(self):
'''
Returns string with generator report
'''
report = StringIO()
total_count = len(self.ported_func_list)+ len(self.skipped_func_list)
report.write("PORTED FUNCs LIST (%i of %i):\n\n" % (len(self.ported_func_list), total_count))
report.write("\n".join(self.ported_func_list))
report.write("\n\nSKIPPED FUNCs LIST (%i of %i):\n\n" % (len(self.skipped_func_list), total_count))
report.write("".join(self.skipped_func_list))
for i in sorted(self.def_args_hist.keys()):
report.write("\n%i def args - %i funcs" % (i, self.def_args_hist[i]))
return report.getvalue()
def fullTypeName(self, t):
if not type_dict[t].get("is_primitive", False) or "cast_to" in type_dict[t]:
if "cast_to" in type_dict[t]:
return type_dict[t]["cast_to"]
else:
namespace_prefix = self.get_namespace_prefix(t)
return namespace_prefix + t
else:
return t
def build_objc2cv_prologue(self, prologue, vector_type, vector_full_type, objc_type, vector_name, array_name):
if not (vector_type in type_dict and "to_cpp" in type_dict[vector_type] and type_dict[vector_type]["to_cpp"] != "%(n)s.nativeRef"):
prologue.append("OBJC2CV(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ");")
else:
conv_macro = "CONV_" + array_name
prologue.append("#define " + conv_macro + "(e) " + type_dict[vector_type]["to_cpp"] % {"n": "e"})
prologue.append("OBJC2CV_CUSTOM(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ", " + conv_macro + ");")
prologue.append("#undef " + conv_macro)
def build_cv2objc_epilogue(self, epilogue, vector_type, vector_full_type, objc_type, vector_name, array_name):
if not (vector_type in type_dict and "from_cpp" in type_dict[vector_type] and type_dict[vector_type]["from_cpp"] != ("[" + objc_type[:-1] + " fromNative:%(n)s]")):
epilogue.append("CV2OBJC(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ");")
else:
unconv_macro = "UNCONV_" + array_name
epilogue.append("#define " + unconv_macro + "(e) " + type_dict[vector_type]["from_cpp"] % {"n": "e"})
epilogue.append("CV2OBJC_CUSTOM(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ", " + unconv_macro + ");")
epilogue.append("#undef " + unconv_macro)
def gen_func(self, ci, fi, extension_implementations, extension_signatures):
logging.info("%s", fi)
method_declarations = ci.method_declarations
method_implementations = ci.method_implementations
decl_args = []
for a in fi.args:
s = a.ctype or ' _hidden_ '
if a.pointer:
s += "*"
elif a.out:
s += "&"
s += " " + a.name
if a.defval:
s += " = " + str(a.defval)
decl_args.append(s)
c_decl = "%s %s %s(%s)" % ( fi.static, fi.ctype, fi.cname, ", ".join(decl_args) )
# comment
method_declarations.write( "\n//\n// %s\n//\n" % c_decl )
method_implementations.write( "\n//\n// %s\n//\n" % c_decl )
# check if we 'know' all the types
if fi.ctype not in type_dict: # unsupported ret type
msg = "// Return type '%s' is not supported, skipping the function\n\n" % fi.ctype
self.skipped_func_list.append(c_decl + "\n" + msg)
method_declarations.write( " "*4 + msg )
logging.warning("SKIP:" + c_decl.strip() + "\t due to RET type " + fi.ctype)
return
for a in fi.args:
if a.ctype not in type_dict:
if not a.defval and a.ctype.endswith("*"):
a.defval = 0
if a.defval:
a.ctype = ''
continue
msg = "// Unknown type '%s' (%s), skipping the function\n\n" % (a.ctype, a.out or "I")
self.skipped_func_list.append(c_decl + "\n" + msg)
method_declarations.write( msg )
logging.warning("SKIP:" + c_decl.strip() + "\t due to ARG type " + a.ctype + "/" + (a.out or "I"))
return
self.ported_func_list.append(c_decl)
# args
args = fi.args[:] # copy
objc_signatures=[]
while True:
# method args
cv_args = []
prologue = []
epilogue = []
if fi.ctype:
ci.addImports(fi.ctype, False)
for a in args:
if not "v_type" in type_dict[a.ctype] and not "v_v_type" in type_dict[a.ctype]:
cast = ("(" + type_dict[a.ctype]["cast_to"] + ")") if "cast_to" in type_dict[a.ctype] else ""
cv_name = type_dict[a.ctype].get("to_cpp", cast + "%(n)s") if a.ctype else a.defval
if a.pointer and not cv_name == "0":
cv_name = "&(" + cv_name + ")"
if "O" in a.out and type_dict[a.ctype].get("out_type", ""):
cv_name = type_dict[a.ctype].get("out_type_ptr" if a.pointer else "out_type_ref", "%(n)s")
cv_args.append(type_dict[a.ctype].get("cv_name", cv_name) % {"n": a.name})
if not a.ctype: # hidden
continue
ci.addImports(a.ctype, "O" in a.out)
if "v_type" in type_dict[a.ctype]: # pass as vector
vector_cpp_type = type_dict[a.ctype]["v_type"]
objc_type = type_dict[a.ctype]["objc_type"]
has_namespace = vector_cpp_type.find("::") != -1
ci.addImports(a.ctype, False)
vector_full_cpp_type = self.fullTypeName(vector_cpp_type) if not has_namespace else vector_cpp_type
vector_cpp_name = a.name + "Vector"
cv_args.append(vector_cpp_name)
self.build_objc2cv_prologue(prologue, vector_cpp_type, vector_full_cpp_type, objc_type, vector_cpp_name, a.name)
if "O" in a.out:
self.build_cv2objc_epilogue(epilogue, vector_cpp_type, vector_full_cpp_type, objc_type, vector_cpp_name, a.name)
if "v_v_type" in type_dict[a.ctype]: # pass as vector of vector
vector_cpp_type = type_dict[a.ctype]["v_v_type"]
objc_type = type_dict[a.ctype]["objc_type"]
ci.addImports(a.ctype, False)
vector_full_cpp_type = self.fullTypeName(vector_cpp_type)
vector_cpp_name = a.name + "Vector2"
cv_args.append(vector_cpp_name)
prologue.append("OBJC2CV2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", " + vector_cpp_name + ", " + a.name + ");")
if "O" in a.out:
epilogue.append(
"CV2OBJC2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", " + vector_cpp_name + ", " + a.name + ");")
# calculate method signature to check for uniqueness
objc_args = build_objc_args(args)
objc_signature = fi.signature(args)
swift_ext = make_swift_extension(args)
logging.info("Objective-C: " + objc_signature)
if objc_signature in objc_signatures:
if args:
args.pop()
continue
else:
break
# doc comment
if fi.docstring:
lines = fi.docstring.splitlines()
toWrite = []
for index, line in enumerate(lines):
p0 = line.find("@param")
if p0 != -1:
p0 += 7 # len("@param" + 1)
p1 = line.find(' ', p0)
p1 = len(line) if p1 == -1 else p1
name = line[p0:p1]
for arg in args:
if arg.name == name:
toWrite.append(re.sub('\*\s*@param ', '* @param ', line))
break
else:
s0 = line.find("@see")
if s0 != -1:
sees = line[(s0 + 5):].split(",")
toWrite.append(line[:(s0 + 5)] + ", ".join(["`" + see_lookup(ci.objc_name, see.strip()) + "`" for see in sees]))
else:
toWrite.append(line)
for line in toWrite:
method_declarations.write(line + "\n")
# public wrapper method impl (calling native one above)
# e.g.
# public static void add( Mat src1, Mat src2, Mat dst, Mat mask, int dtype )
# { add_0( src1.nativeObj, src2.nativeObj, dst.nativeObj, mask.nativeObj, dtype ); }
ret_type = fi.ctype
if fi.ctype.endswith('*'):
ret_type = ret_type[:-1]
ret_val = self.fullTypeName(fi.ctype) + " retVal = "
ret = "return retVal;"
tail = ""
constructor = False
if "v_type" in type_dict[ret_type]:
objc_type = type_dict[ret_type]["objc_type"]
vector_type = type_dict[ret_type]["v_type"]
full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type
prologue.append("NSMutableArray<" + objc_type + ">* retVal = [NSMutableArray new];")
ret_val = "std::vector<" + full_cpp_type + "> retValVector = "
self.build_cv2objc_epilogue(epilogue, vector_type, full_cpp_type, objc_type, "retValVector", "retVal")
elif "v_v_type" in type_dict[ret_type]:
objc_type = type_dict[ret_type]["objc_type"]
cpp_type = type_dict[ret_type]["v_v_type"]
if cpp_type.find("::") == -1:
cpp_type = self.get_namespace_prefix(cpp_type) + cpp_type
prologue.append("NSMutableArray<NSMutableArray<" + objc_type + ">*>* retVal = [NSMutableArray new];")
ret_val = "std::vector< std::vector<" + cpp_type + "> > retValVector = "
epilogue.append("CV2OBJC2(" + cpp_type + ", " + objc_type[:-1] + ", retValVector, retVal);")
elif ret_type.startswith("Ptr_"):
cpp_type = type_dict[ret_type]["c_type"]
real_cpp_type = type_dict[ret_type].get("real_c_type", cpp_type)
namespace_prefix = self.get_namespace_prefix(cpp_type)
ret_val = "cv::Ptr<" + namespace_prefix + real_cpp_type + "> retVal = "
ret = "return [" + type_dict[ret_type]["objc_type"][:-1] + " fromNative:retVal];"
elif ret_type == "void":
ret_val = ""
ret = ""
elif ret_type == "": # c-tor
constructor = True
ret_val = "return [self initWithNativePtr:cv::Ptr<" + fi.fullClass(isCPP=True) + ">(new "
tail = ")]"
ret = ""
elif self.isWrapped(ret_type): # wrapped class
namespace_prefix = self.get_namespace_prefix(ret_type)
ret_val = "cv::Ptr<" + namespace_prefix + ret_type + "> retVal = new " + namespace_prefix + ret_type + "("
tail = ")"
ret_type_dict = type_dict[ret_type]
from_cpp = ret_type_dict["from_cpp_ptr"] if "from_cpp_ptr" in ret_type_dict else ret_type_dict["from_cpp"]
ret = "return " + (from_cpp % { "n" : "retVal" }) + ";"
elif "from_cpp" in type_dict[ret_type]:
ret = "return " + (type_dict[ret_type]["from_cpp"] % { "n" : "retVal" }) + ";"
static = fi.static if fi.classname else True
objc_ret_type = type_dict[fi.ctype]["objc_type"] if type_dict[fi.ctype]["objc_type"] else "void" if not constructor else "instancetype"
if "v_type" in type_dict[ret_type]:
objc_ret_type = "NSArray<" + objc_ret_type + ">*"
elif "v_v_type" in type_dict[ret_type]:
objc_ret_type = "NSArray<NSArray<" + objc_ret_type + ">*>*"
prototype = Template("$static ($objc_ret_type)$objc_name$objc_args").substitute(
static = "+" if static else "-",
objc_ret_type = objc_ret_type,
objc_args = " ".join(objc_args),
objc_name = fi.objc_name if not constructor else ("init" + ("With" + (args[0].name[0].upper() + args[0].name[1:]) if len(args) > 0 else ""))
)
method_declarations.write( Template(
"""$prototype$swift_name$deprecation_decl;
"""
).substitute(
prototype = prototype,
swift_name = " NS_SWIFT_NAME(" + fi.swift_name + "(" + build_swift_signature(args) + "))" if not constructor else "",
deprecation_decl = " DEPRECATED_ATTRIBUTE" if fi.deprecated else ""
)
)
method_implementations.write( Template(
"""$prototype {$prologue
$ret_val$obj_deref$cv_name($cv_args)$tail;$epilogue$ret
}
"""
).substitute(
prototype = prototype,
ret = "\n " + ret if ret else "",
ret_val = ret_val,
prologue = "\n " + "\n ".join(prologue) if prologue else "",
epilogue = "\n " + "\n ".join(epilogue) if epilogue else "",
static = "+" if static else "-",
obj_deref = ("self." + ci.native_ptr_name + "->") if not static and not constructor else "",
cv_name = fi.cv_name if static else fi.fullClass(isCPP=True) if constructor else fi.name,
cv_args = ", ".join(cv_args),
tail = tail
)
)
if swift_ext:
prototype = build_swift_extension_decl(fi.swift_name, args, constructor, static, ret_type)
if not (ci.name, prototype) in extension_signatures and not (ci.base, prototype) in extension_signatures:
(pro, epi) = build_swift_logues(args)
extension_implementations.write( Template(
"""public extension $classname {
$deprecation_decl$prototype {
$prologue
$unrefined_call$epilogue$ret
}
}
"""
).substitute(
classname = make_objcname(ci.name),
deprecation_decl = "@available(*, deprecated)\n " if fi.deprecated else "",
prototype = prototype,
prologue = " " + "\n ".join(pro),
unrefined_call = " " + build_unrefined_call(fi.swift_name, args, constructor, static, ci.name, ret_type is not None and ret_type != "void"),
epilogue = "\n " + "\n ".join(epi) if len(epi) > 0 else "",
ret = "\n return ret" if ret_type is not None and ret_type != "void" and not constructor else ""
)
)
extension_signatures.append((ci.name, prototype))
# adding method signature to dictionary
objc_signatures.append(objc_signature)
# processing args with default values
if args and args[-1].defval:
args.pop()
else:
break
def gen_class(self, ci, module, extension_implementations, extension_signatures):
logging.info("%s", ci)
additional_imports = []
if module in AdditionalImports:
if "*" in AdditionalImports[module]:
additional_imports += AdditionalImports[module]["*"]
if ci.name in AdditionalImports[module]:
additional_imports += AdditionalImports[module][ci.name]
if hasattr(ci, 'header_import'):
h = '"{}"'.format(ci.header_import)
if not h in additional_imports:
additional_imports.append(h)
h = '"{}.hpp"'.format(module)
if h in additional_imports:
additional_imports.remove(h)
h = '"opencv2/{}.hpp"'.format(module)
if not h in additional_imports:
additional_imports.insert(0, h)
if additional_imports:
ci.additionalImports.write('\n'.join(['#import %s' % make_objcname(h) for h in additional_imports]))
# constants
wrote_consts_pragma = False
consts_map = {c.name: c for c in ci.private_consts}
consts_map.update({c.name: c for c in ci.consts})
def const_value(v):
if v in consts_map:
target = consts_map[v]
assert target.value != v
return const_value(target.value)
return v
if ci.consts:
enumTypes = set([c.enumType for c in ci.consts])
grouped_consts = {enumType: [c for c in ci.consts if c.enumType == enumType] for enumType in enumTypes}
for typeName in sorted(grouped_consts.keys(), key=lambda x: str(x) if x is not None else ""):
consts = grouped_consts[typeName]
logging.info("%s", consts)
if typeName:
typeNameShort = typeName.rsplit(".", 1)[-1]
if ci.cname in enum_fix:
typeNameShort = enum_fix[ci.cname].get(typeNameShort, typeNameShort)
ci.enum_declarations.write("""
// C++: enum {1} ({2})
typedef NS_ENUM(int, {1}) {{
{0}\n}};\n\n""".format(",\n ".join(["%s = %s" % (c.name, c.value) for c in consts]), typeNameShort, typeName)
)
else:
if not wrote_consts_pragma:
ci.method_declarations.write("#pragma mark - Class Constants\n\n")
wrote_consts_pragma = True
ci.method_declarations.write("""
{0}\n\n""".format("\n".join(["@property (class, readonly) int %s NS_SWIFT_NAME(%s);" % (c.name, c.name) for c in consts]))
)
declared_consts = []
match_alphabet = re.compile("[a-zA-Z]")
for c in consts:
value = str(c.value)
if match_alphabet.search(value):
for declared_const in sorted(declared_consts, key=len, reverse=True):
regex = re.compile("(?<!" + ci.cname + ".)" + declared_const)
value = regex.sub(ci.cname + "." + declared_const, value)
ci.method_implementations.write("+ (int)%s {\n return %s;\n}\n\n" % (c.name, value))
declared_consts.append(c.name)
ci.method_declarations.write("#pragma mark - Methods\n\n")
# methods
for fi in ci.getAllMethods():
self.gen_func(ci, fi, extension_implementations, extension_signatures)
# props
for pi in ci.props:
ci.method_declarations.write("\n //\n // C++: %s %s::%s\n //\n\n" % (pi.ctype, ci.fullName(isCPP=True), pi.name))
type_data = type_dict[pi.ctype] if pi.ctype != "uchar" else {"objc_type" : "unsigned char", "is_primitive" : True}
objc_type = type_data.get("objc_type", pi.ctype)
ci.addImports(pi.ctype, False)
ci.method_declarations.write("@property " + ("(readonly) " if not pi.rw else "") + objc_type + " " + pi.name + ";\n")
ptr_ref = "self." + ci.native_ptr_name + "->" if not ci.is_base_class else "self.nativePtr->"
if "v_type" in type_data:
vector_cpp_type = type_data["v_type"]
has_namespace = vector_cpp_type.find("::") != -1
vector_full_cpp_type = self.fullTypeName(vector_cpp_type) if not has_namespace else vector_cpp_type
ret_val = "std::vector<" + vector_full_cpp_type + "> retValVector = "
ci.method_implementations.write("-(NSArray<" + objc_type + ">*)" + pi.name + " {\n")
ci.method_implementations.write("\tNSMutableArray<" + objc_type + ">* retVal = [NSMutableArray new];\n")
ci.method_implementations.write("\t" + ret_val + ptr_ref + pi.name + ";\n")
epilogue = []
self.build_cv2objc_epilogue(epilogue, vector_cpp_type, vector_full_cpp_type, objc_type, "retValVector", "retVal")
ci.method_implementations.write("\t" + ("\n\t".join(epilogue)) + "\n")
ci.method_implementations.write("\treturn retVal;\n}\n\n")
elif "v_v_type" in type_data:
vector_cpp_type = type_data["v_v_type"]
has_namespace = vector_cpp_type.find("::") != -1
vector_full_cpp_type = self.fullTypeName(vector_cpp_type) if not has_namespace else vector_cpp_type
ret_val = "std::vector<std::vector<" + vector_full_cpp_type + ">> retValVectorVector = "
ci.method_implementations.write("-(NSArray<NSArray<" + objc_type + ">*>*)" + pi.name + " {\n")
ci.method_implementations.write("\tNSMutableArray<NSMutableArray<" + objc_type + ">*>* retVal = [NSMutableArray new];\n")
ci.method_implementations.write("\t" + ret_val + ptr_ref + pi.name + ";\n")
ci.method_implementations.write("\tCV2OBJC2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", retValVectorVector, retVal);\n")
ci.method_implementations.write("\treturn retVal;\n}\n\n")
elif self.isWrapped(pi.ctype): # wrapped class
namespace_prefix = self.get_namespace_prefix(pi.ctype)
ci.method_implementations.write("-(" + objc_type + ")" + pi.name + " {\n")
ci.method_implementations.write("\tcv::Ptr<" + namespace_prefix + pi.ctype + "> retVal = new " + namespace_prefix + pi.ctype + "(" + ptr_ref + pi.name + ");\n")
from_cpp = type_data["from_cpp_ptr"] if "from_cpp_ptr" in type_data else type_data["from_cpp"]
ci.method_implementations.write("\treturn " + (from_cpp % {"n": "retVal"}) + ";\n}\n\n")
else:
from_cpp = type_data.get("from_cpp", "%(n)s")
retVal = from_cpp % {"n": (ptr_ref + pi.name)}
ci.method_implementations.write("-(" + objc_type + ")" + pi.name + " {\n\treturn " + retVal + ";\n}\n\n")
if pi.rw:
if "v_type" in type_data:
vector_cpp_type = type_data["v_type"]
has_namespace = vector_cpp_type.find("::") != -1
vector_full_cpp_type = self.fullTypeName(vector_cpp_type) if not has_namespace else vector_cpp_type
ci.method_implementations.write("-(void)set" + pi.name[0].upper() + pi.name[1:] + ":(NSArray<" + objc_type + ">*)" + pi.name + "{\n")
prologue = []
self.build_objc2cv_prologue(prologue, vector_cpp_type, vector_full_cpp_type, objc_type, "valVector", pi.name)
ci.method_implementations.write("\t" + ("\n\t".join(prologue)) + "\n")
ci.method_implementations.write("\t" + ptr_ref + pi.name + " = valVector;\n}\n\n")
else:
to_cpp = type_data.get("to_cpp", ("(" + type_data.get("cast_to") + ")%(n)s") if "cast_to" in type_data else "%(n)s")
val = to_cpp % {"n": pi.name}
ci.method_implementations.write("-(void)set" + pi.name[0].upper() + pi.name[1:] + ":(" + objc_type + ")" + pi.name + " {\n\t" + ptr_ref + pi.name + " = " + val + ";\n}\n\n")
# manual ports
if ci.name in ManualFuncs:
for func in sorted(ManualFuncs[ci.name].keys()):
logging.info("manual function: %s", func)
fn = ManualFuncs[ci.name][func]
ci.method_declarations.write( "\n".join(fn["declaration"]) )
ci.method_implementations.write( "\n".join(fn["implementation"]) )
def getClass(self, classname):
return self.classes[classname or self.Module]
def isWrapped(self, classname):
name = classname or self.Module
return name in self.classes
def isSmartClass(self, ci):
'''
Check if class stores Ptr<T>* instead of T* in nativeObj field
'''
if ci.smart != None:
return ci.smart
# if parents are smart (we hope) then children are!
# if not we believe the class is smart if it has "create" method
ci.smart = False
if ci.base or ci.name == 'Algorithm':
ci.smart = True
else:
for fi in ci.methods:
if fi.name == "create":
ci.smart = True
break
return ci.smart
def smartWrap(self, ci, fullname):
'''
Wraps fullname with Ptr<> if needed
'''
if self.isSmartClass(ci):
return "Ptr<" + fullname + ">"
return fullname
def finalize(self, objc_target, output_objc_path, output_objc_build_path):
opencv_header_file = os.path.join(output_objc_path, framework_name + ".h")
opencv_header = "#import <Foundation/Foundation.h>\n\n"
opencv_header += "// ! Project version number\nFOUNDATION_EXPORT double " + framework_name + "VersionNumber;\n\n"
opencv_header += "// ! Project version string\nFOUNDATION_EXPORT const unsigned char " + framework_name + "VersionString[];\n\n"
opencv_header += "\n".join(["#import <" + framework_name + "/%s>" % os.path.basename(f) for f in self.header_files])
self.save(opencv_header_file, opencv_header)
opencv_modulemap_file = os.path.join(output_objc_path, framework_name + ".modulemap")
opencv_modulemap = "framework module " + framework_name + " {\n"
opencv_modulemap += " umbrella header \"" + framework_name + ".h\"\n"
opencv_modulemap += "\n".join([" header \"%s\"" % os.path.basename(f) for f in self.header_files])
opencv_modulemap += "\n export *\n module * {export *}\n}\n"
self.save(opencv_modulemap_file, opencv_modulemap)
cmakelist_template = read_contents(os.path.join(SCRIPT_DIR, 'templates/cmakelists.template'))
cmakelist = Template(cmakelist_template).substitute(modules = ";".join(modules), framework = framework_name, objc_target=objc_target)
self.save(os.path.join(dstdir, "CMakeLists.txt"), cmakelist)
mkdir_p(os.path.join(output_objc_build_path, "framework_build"))
mkdir_p(os.path.join(output_objc_build_path, "test_build"))
mkdir_p(os.path.join(output_objc_build_path, "doc_build"))
with open(os.path.join(SCRIPT_DIR, '../doc/README.md')) as readme_in:
readme_body = readme_in.read()
readme_body += "\n\n\n##Modules\n\n" + ", ".join(["`" + m.capitalize() + "`" for m in modules])
with open(os.path.join(output_objc_build_path, "doc_build/README.md"), "w") as readme_out:
readme_out.write(readme_body)
if framework_name != "OpenCV":
for dirname, dirs, files in os.walk(os.path.join(testdir, "test")):
if dirname.endswith('/resources'):
continue # don't touch resource binary files
for filename in files:
filepath = os.path.join(dirname, filename)
with io.open(filepath, encoding="utf-8", errors="ignore") as file:
body = file.read()
body = body.replace("import OpenCV", "import " + framework_name)
body = body.replace("#import <OpenCV/OpenCV.h>", "#import <" + framework_name + "/" + framework_name + ".h>")
with codecs.open(filepath, "w", "utf-8") as file:
file.write(body)
def copy_objc_files(objc_files_dir, objc_base_path, module_path, include = False):
global total_files, updated_files
objc_files = []
re_filter = re.compile(r'^.+\.(h|m|mm|swift)$')
for root, dirnames, filenames in os.walk(objc_files_dir):
objc_files += [os.path.join(root, filename) for filename in filenames if re_filter.match(filename)]
objc_files = [f.replace('\\', '/') for f in objc_files]
re_prefix = re.compile(r'^.+/(.+)\.(h|m|mm|swift)$')
for objc_file in objc_files:
src = objc_file
m = re_prefix.match(objc_file)
target_fname = (m.group(1) + '.' + m.group(2)) if m else os.path.basename(objc_file)
dest = os.path.join(objc_base_path, os.path.join(module_path, target_fname))
mkdir_p(os.path.dirname(dest))
total_files += 1
if include and m.group(2) == 'h':
generator.header_files.append(dest)
if (not os.path.exists(dest)) or (os.stat(src).st_mtime - os.stat(dest).st_mtime > 1):
copyfile(src, dest)
updated_files += 1
return objc_files
def unescape(str):
return str.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&")
def escape_underscore(str):
return str.replace('_', '\\_')
def escape_texttt(str):
return re.sub(re.compile('texttt{(.*?)\}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str)
def get_macros(tex):
out = ""
if re.search("\\\\fork\s*{", tex):
out += "\\newcommand{\\fork}[4]{ \\left\\{ \\begin{array}{l l} #1 & \\text{#2}\\\\\\\\ #3 & \\text{#4}\\\\\\\\ \\end{array} \\right.} "
if re.search("\\\\vecthreethree\s*{", tex):
out += "\\newcommand{\\vecthreethree}[9]{ \\begin{bmatrix} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\end{bmatrix} } "
return out
def fix_tex(tex):
macros = get_macros(tex)
fix_escaping = escape_texttt(unescape(tex))
return macros + fix_escaping
def sanitize_documentation_string(doc, type):
if type == "class":
doc = doc.replace("@param ", "")
doc = re.sub(re.compile('`\\$\\$(.*?)\\$\\$`', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\{align\\*\\}\\{?(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$\\begin{aligned} ' + fix_tex(x.group(1)) + ' \\end{aligned}$$`', doc)
doc = re.sub(re.compile('\\\\f\\{equation\\*\\}\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$\\begin{aligned} ' + fix_tex(x.group(1)) + ' \\end{aligned}$$`', doc)
doc = re.sub(re.compile('\\\\f\\$(.*?)\\\\f\\$', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\[(.*?)\\\\f\\]', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = doc.replace("@anchor", "") \
.replace("@brief ", "").replace("\\brief ", "") \
.replace("@cite", "CITE:") \
.replace("@code{.cpp}", "<code>") \
.replace("@code{.txt}", "<code>") \
.replace("@code", "<code>") \
.replace("@copydoc", "") \
.replace("@copybrief", "") \
.replace("@date", "") \
.replace("@defgroup", "") \
.replace("@details ", "") \
.replace("@endcode", "</code>") \
.replace("@endinternal", "") \
.replace("@file", "") \
.replace("@include", "INCLUDE:") \
.replace("@ingroup", "") \
.replace("@internal", "") \
.replace("@overload", "") \
.replace("@param[in]", "@param") \
.replace("@param[out]", "@param") \
.replace("@ref", "REF:") \
.replace("@note", "NOTE:") \
.replace("@returns", "@return") \
.replace("@sa ", "@see ") \
.replace("@snippet", "SNIPPET:") \
.replace("@todo", "TODO:") \
lines = doc.splitlines()
in_code = False
for i,line in enumerate(lines):
if line.find("</code>") != -1:
in_code = False
lines[i] = line.replace("</code>", "")
if in_code:
lines[i] = unescape(line)
if line.find("<code>") != -1:
in_code = True
lines[i] = line.replace("<code>", "")
lines = list([x[x.find('*'):].strip() if x.lstrip().startswith("*") else x for x in lines])
lines = list(["* " + x[1:].strip() if x.startswith("*") and x != "*" else x for x in lines])
lines = list([x if x.startswith("*") else "* " + x if x and x != "*" else "*" for x in lines])
hasValues = False
for line in lines:
if line != "*":
hasValues = True
break
return "/**\n " + "\n ".join(lines) + "\n */" if hasValues else ""
if __name__ == "__main__":
# initialize logger
logging.basicConfig(filename='gen_objc.log', format=None, filemode='w', level=logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(os.environ.get('LOG_LEVEL', logging.WARNING))
logging.getLogger().addHandler(handler)
# parse command line parameters
import argparse
arg_parser = argparse.ArgumentParser(description='OpenCV Objective-C Wrapper Generator')
arg_parser.add_argument('-p', '--parser', required=True, help='OpenCV header parser')
arg_parser.add_argument('-c', '--config', required=True, help='OpenCV modules config')
arg_parser.add_argument('-t', '--target', required=True, help='Target (either ios or osx)')
arg_parser.add_argument('-f', '--framework', required=True, help='Framework name')
args=arg_parser.parse_args()
# import header parser
hdr_parser_path = os.path.abspath(args.parser)
if hdr_parser_path.endswith(".py"):
hdr_parser_path = os.path.dirname(hdr_parser_path)
sys.path.append(hdr_parser_path)
import hdr_parser
with open(args.config) as f:
config = json.load(f)
ROOT_DIR = config['rootdir']; assert os.path.exists(ROOT_DIR)
if 'objc_build_dir' in config:
objc_build_dir = config['objc_build_dir']
assert os.path.exists(objc_build_dir), objc_build_dir
else:
objc_build_dir = os.getcwd()
dstdir = "./gen"
testdir = "./test"
objc_base_path = os.path.join(dstdir, 'objc'); mkdir_p(objc_base_path)
objc_test_base_path = testdir; mkdir_p(objc_test_base_path)
copy_objc_files(os.path.join(SCRIPT_DIR, '../test/test'), objc_test_base_path, 'test', False)
copy_objc_files(os.path.join(SCRIPT_DIR, '../test/dummy'), objc_test_base_path, 'dummy', False)
copyfile(os.path.join(SCRIPT_DIR, '../test/cmakelists.template'), os.path.join(objc_test_base_path, 'CMakeLists.txt'))
# launch Objective-C Wrapper generator
generator = ObjectiveCWrapperGenerator()
gen_dict_files = []
framework_name = args.framework
print("Objective-C: Processing OpenCV modules: %d" % len(config['modules']))
for e in config['modules']:
(module, module_location) = (e['name'], os.path.join(ROOT_DIR, e['location']))
logging.info("\n=== MODULE: %s (%s) ===\n" % (module, module_location))
modules.append(module)
module_imports = []
srcfiles = []
common_headers = []
misc_location = os.path.join(module_location, 'misc/objc')
srcfiles_fname = os.path.join(misc_location, 'filelist')
if os.path.exists(srcfiles_fname):
with open(srcfiles_fname) as f:
srcfiles = [os.path.join(module_location, str(l).strip()) for l in f.readlines() if str(l).strip()]
else:
re_bad = re.compile(r'(private|.inl.hpp$|_inl.hpp$|.detail.hpp$|.details.hpp$|_winrt.hpp$|/cuda/|/legacy/)')
# .h files before .hpp
h_files = []
hpp_files = []
for root, dirnames, filenames in os.walk(os.path.join(module_location, 'include')):
h_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.h')]
hpp_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.hpp')]
srcfiles = h_files + hpp_files
srcfiles = [f for f in srcfiles if not re_bad.search(f.replace('\\', '/'))]
logging.info("\nFiles (%d):\n%s", len(srcfiles), pformat(srcfiles))
common_headers_fname = os.path.join(misc_location, 'filelist_common')
if os.path.exists(common_headers_fname):
with open(common_headers_fname) as f:
common_headers = [os.path.join(module_location, str(l).strip()) for l in f.readlines() if str(l).strip()]
logging.info("\nCommon headers (%d):\n%s", len(common_headers), pformat(common_headers))
gendict_fname = os.path.join(misc_location, 'gen_dict.json')
if os.path.exists(gendict_fname):
with open(gendict_fname) as f:
gen_type_dict = json.load(f)
namespace_ignore_list = gen_type_dict.get("namespace_ignore_list", [])
class_ignore_list += gen_type_dict.get("class_ignore_list", [])
enum_ignore_list += gen_type_dict.get("enum_ignore_list", [])
const_ignore_list += gen_type_dict.get("const_ignore_list", [])
const_private_list += gen_type_dict.get("const_private_list", [])
missing_consts.update(gen_type_dict.get("missing_consts", {}))
type_dict.update(gen_type_dict.get("type_dict", {}))
AdditionalImports[module] = gen_type_dict.get("AdditionalImports", {})
ManualFuncs.update(gen_type_dict.get("ManualFuncs", {}))
func_arg_fix.update(gen_type_dict.get("func_arg_fix", {}))
enum_fix.update(gen_type_dict.get("enum_fix", {}))
const_fix.update(gen_type_dict.get("const_fix", {}))
namespaces_dict.update(gen_type_dict.get("namespaces_dict", {}))
module_imports += gen_type_dict.get("module_imports", [])
objc_files_dir = os.path.join(misc_location, 'common')
copied_files = []
if os.path.exists(objc_files_dir):
copied_files += copy_objc_files(objc_files_dir, objc_base_path, module, True)
if args.target == 'ios':
ios_files_dir = os.path.join(misc_location, 'ios')
if os.path.exists(ios_files_dir):
copied_files += copy_objc_files(ios_files_dir, objc_base_path, module, True)
if args.target == 'osx':
osx_files_dir = os.path.join(misc_location, 'macosx')
if os.path.exists(osx_files_dir):
copied_files += copy_objc_files(osx_files_dir, objc_base_path, module, True)
objc_test_files_dir = os.path.join(misc_location, 'test')
if os.path.exists(objc_test_files_dir):
copy_objc_files(objc_test_files_dir, objc_test_base_path, 'test', False)
objc_test_resources_dir = os.path.join(objc_test_files_dir, 'resources')
if os.path.exists(objc_test_resources_dir):
copy_tree(objc_test_resources_dir, os.path.join(objc_test_base_path, 'test', 'resources'))
manual_classes = [x for x in [x[x.rfind('/')+1:-2] for x in [x for x in copied_files if x.endswith('.h')]] if x in type_dict]
if len(srcfiles) > 0:
generator.gen(srcfiles, module, dstdir, objc_base_path, common_headers, manual_classes)
else:
logging.info("No generated code for module: %s", module)
generator.finalize(args.target, objc_base_path, objc_build_dir)
print('Generated files: %d (updated %d)' % (total_files, updated_files))