From bedabc15aea6d1f6c6a9a3837b69be766a74251d Mon Sep 17 00:00:00 2001 From: Giles Payne Date: Sun, 9 Aug 2020 16:39:24 +0900 Subject: [PATCH] Obj-C/Swift docs improvements --- modules/core/misc/objc/common/Mat.h | 20 ++++- modules/objc/generator/gen_objc.py | 17 +++- .../generator/templates/cmakelists.template | 15 ---- platforms/ios/build_docs.py | 87 +++++++++++++++++++ platforms/ios/build_framework.py | 34 ++++++-- platforms/osx/build_docs.py | 32 +++++++ platforms/osx/build_framework.py | 4 +- 7 files changed, 179 insertions(+), 30 deletions(-) create mode 100755 platforms/ios/build_docs.py create mode 100755 platforms/osx/build_docs.py diff --git a/modules/core/misc/objc/common/Mat.h b/modules/core/misc/objc/common/Mat.h index 9808738078..72f81dd9b7 100644 --- a/modules/core/misc/objc/common/Mat.h +++ b/modules/core/misc/objc/common/Mat.h @@ -23,7 +23,19 @@ NS_ASSUME_NONNULL_BEGIN /** -* The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. + The class Mat represents an n-dimensional dense numerical single-channel or multi-channel array. + ####Swift Example + ```swift + let mat = Mat(rows: 2, cols: 3, type: CvType.CV_8U) + try! mat.put(row: 0, col: 0, data: [2, 3, 4, 4, 5, 6] as [Int8]) + print("mat: \(mat.dump())") + ``` + ####Objective-C Example + ```objc + Mat* mat = [[Mat alloc] initWithRows:2 cols:3 type: CV_8U]; + [m1 put:0 col:0 data:@[@2, @3, @4, @3, @4, @5]]; + NSLog(@"mat: %@", [m1 dump]); + ``` */ CV_EXPORTS @interface Mat : NSObject @@ -40,6 +52,12 @@ CV_EXPORTS @interface Mat : NSObject + (instancetype)fromNativePtr:(cv::Ptr)nativePtr; + (instancetype)fromNative:(cv::Mat&)nativeRef; #endif +/** + Creates a Mat object with the specified number of rows and columns and Mat type + @param rows Number of rows + @param cols Number of columns + @param type Mat type (refer: `CvType`) +*/ - (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type; - (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type data:(NSData*)data; - (instancetype)initWithRows:(int)rows cols:(int)cols type:(int)type data:(NSData*)data step:(long)step; diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py index 6462b62fe0..a67326dc0a 100755 --- a/modules/objc/generator/gen_objc.py +++ b/modules/objc/generator/gen_objc.py @@ -716,7 +716,7 @@ class ObjectiveCWrapperGenerator(object): namespace = self.classes[cname].namespace if self.classes.has_key(cname) else "cv" return namespace.replace(".", "::") + "::" - def gen(self, srcfiles, module, output_path, output_objc_path, common_headers): + def gen(self, srcfiles, module, output_path, output_objc_path, common_headers, manual_classes): self.clear() self.module = module self.Module = module.capitalize() @@ -751,6 +751,7 @@ class ObjectiveCWrapperGenerator(object): self.add_enum(decl) else: # function self.add_func(decl) + self.classes[self.Module].member_classes += manual_classes logging.info("\n\n===== Generating... =====") package_path = os.path.join(output_objc_path, module) @@ -1243,6 +1244,7 @@ def copy_objc_files(objc_files_dir, objc_base_path, module_path, include = False 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("<", "<").replace(">", ">").replace("&", "&") @@ -1298,6 +1300,7 @@ def sanitize_documentation_string(doc, type): .replace("@param[in]", "@param") \ .replace("@param[out]", "@param") \ .replace("@ref", "REF:") \ + .replace("@note", "NOTE:") \ .replace("@returns", "@return") \ .replace("@sa ", "@see ") \ .replace("@snippet", "SNIPPET:") \ @@ -1422,12 +1425,14 @@ if __name__ == "__main__": 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): - copy_objc_files(objc_files_dir, objc_base_path, module, True) + 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): - copy_objc_files(ios_files_dir, objc_base_path, module, True) + copied_files += copy_objc_files(ios_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): @@ -1436,8 +1441,12 @@ if __name__ == "__main__": 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 = filter(lambda x:type_dict.has_key(x), + map(lambda x: x[x.rfind('/')+1:-2], + filter(lambda x: x.endswith('.h'), copied_files))) + if len(srcfiles) > 0: - generator.gen(srcfiles, module, dstdir, objc_base_path, common_headers) + generator.gen(srcfiles, module, dstdir, objc_base_path, common_headers, manual_classes) else: logging.info("No generated code for module: %s", module) generator.finalize(objc_base_path) diff --git a/modules/objc/generator/templates/cmakelists.template b/modules/objc/generator/templates/cmakelists.template index 42acc276b7..e928a6d21a 100644 --- a/modules/objc/generator/templates/cmakelists.template +++ b/modules/objc/generator/templates/cmakelists.template @@ -42,18 +42,3 @@ set_target_properties(opencv_objc_framework PROPERTIES PUBLIC_HEADER "$${objc_headers}" DEFINE_SYMBOL CVAPI_EXPORTS ) - -find_program(JAZZY jazzy) - -if(JAZZY) - add_custom_command( - OUTPUT "$${BUILD_ROOT}/modules/objc/doc_build/doc/index.html" - COMMAND $${JAZZY} --objc --author OpenCV --author_url http://opencv.org --github_url https://github.com/opencv/opencv --umbrella-header "$${BUILD_ROOT}/lib/$${CMAKE_BUILD_TYPE}/$framework.framework/Headers/$framework.h" --framework-root "$${BUILD_ROOT}/lib/$${CMAKE_BUILD_TYPE}/$framework.framework" --module $framework --sdk iphonesimulator --undocumented-text \"\" - WORKING_DIRECTORY "$${BUILD_ROOT}/modules/objc/doc_build" - COMMENT "Generating Documentation" - ) - add_custom_target(opencv_objc_doc ALL DEPENDS "$${BUILD_ROOT}/modules/objc/doc_build/doc/index.html") - add_dependencies(opencv_objc_doc opencv_objc_framework) -else() - message("jazzy not found - documentation will not be generated!") -endif() diff --git a/platforms/ios/build_docs.py b/platforms/ios/build_docs.py new file mode 100755 index 0000000000..e5dc9b2e81 --- /dev/null +++ b/platforms/ios/build_docs.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +""" +This script builds OpenCV docs for iOS. +""" + +from __future__ import print_function +import os, sys, multiprocessing, argparse, traceback +from subprocess import check_call, check_output, CalledProcessError, Popen + +def execute(cmd, cwd = None, output = None): + if not output: + print("Executing: %s in %s" % (cmd, cwd), file=sys.stderr) + print('Executing: ' + ' '.join(cmd)) + retcode = check_call(cmd, cwd = cwd) + if retcode != 0: + raise Exception("Child returned:", retcode) + else: + with open(output, "a") as f: + f.flush() + p = Popen(cmd, cwd = cwd, stdout = f) + os.waitpid(p.pid, 0) + +class DocBuilder: + def __init__(self, script_dir, framework_dir, output_dir, framework_header, framework_name, arch, target): + self.script_dir = script_dir + self.framework_dir = framework_dir + self.output_dir = output_dir + self.framework_header = framework_header + self.framework_name = framework_name + self.arch = arch + self.target = target + + def _build(self): + if not os.path.isdir(self.output_dir): + os.makedirs(self.output_dir) + + self.buildDocs() + + def build(self): + try: + self._build() + except Exception as e: + print("="*60, file=sys.stderr) + print("ERROR: %s" % e, file=sys.stderr) + print("="*60, file=sys.stderr) + traceback.print_exc(file=sys.stderr) + sys.exit(1) + + def getToolchain(self): + return None + + def getSourceKitten(self): + ret = check_output(["gem", "which", "jazzy"]) + if ret.find('ERROR:') == 0: + raise Exception("Failed to find jazzy") + else: + return os.path.join(ret[0:ret.rfind('/')], '../bin/sourcekitten') + + def buildDocs(self): + sourceKitten = self.getSourceKitten() + sourceKittenSwiftDoc = [sourceKitten, "doc", "--module-name", self.framework_name, "--", "-project", self.framework_name + ".xcodeproj", "ARCHS=" + self.arch, "-sdk", self.target, "-configuration", "Release", "-parallelizeTargets", "-jobs", str(multiprocessing.cpu_count()), "-target", "opencv_objc_framework"] + execute(sourceKittenSwiftDoc, cwd = self.framework_dir, output = os.path.join(self.output_dir, "swiftDoc.json")) + sdk_dir = check_output(["xcrun", "--show-sdk-path", "--sdk", self.target]).rstrip() + sourceKittenObjcDoc = [sourceKitten, "doc", "--objc", self.framework_header, "--", "-x", "objective-c", "-isysroot", sdk_dir, "-fmodules"] + print(sourceKittenObjcDoc) + execute(sourceKittenObjcDoc, cwd = self.framework_dir, output = os.path.join(self.output_dir, "objcDoc.json")) + execute(["jazzy", "--author", "OpenCV", "--author_url", "http://opencv.org", "--github_url", "https://github.com/opencv/opencv", "--module", self.framework_name, "--undocumented-text", "\"\"", "--sourcekitten-sourcefile", "swiftDoc.json,objcDoc.json"], cwd = self.output_dir) + +class iOSDocBuilder(DocBuilder): + + def getToolchain(self): + return None + +if __name__ == "__main__": + script_dir = os.path.abspath(os.path.dirname(sys.argv[0])) + parser = argparse.ArgumentParser(description='The script builds OpenCV docs for iOS.') + parser.add_argument('framework_dir', metavar='FRAMEWORK_DIR', help='folder where framework build files are located') + parser.add_argument('--output_dir', default=None, help='folder where docs will be built (default is "../doc_build" relative to framework_dir)') + parser.add_argument('--framework_header', default=None, help='umbrella header for OpenCV framework (default is "../../../lib/Release/{framework_name}.framework/Headers/{framework_name}.h")') + parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') + args = parser.parse_args() + + arch = "x86_64" + target = "iphonesimulator" + + b = iOSDocBuilder(script_dir, args.framework_dir, args.output_dir if args.output_dir else os.path.join(args.framework_dir, "../doc_build"), args.framework_header if args.framework_header else os.path.join(args.framework_dir, "../../../lib/Release/" + args.framework_name + ".framework/Headers/" + args.framework_name + ".h"), args.framework_name, arch, target) + b.build() diff --git a/platforms/ios/build_framework.py b/platforms/ios/build_framework.py index 0f96f018ca..1c1d52d812 100755 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -19,6 +19,10 @@ Script will create , if it's missing, and a few its subdirectories: [cmake-generated build tree for iOS simulator] {framework_name}.framework/ [the framework content] + samples/ + [sample projects] + docs/ + [documentation] The script should handle minor OpenCV updates efficiently - it does not recompile the library from scratch each time. @@ -58,7 +62,7 @@ def getXCodeSetting(var, projectdir): raise Exception("Failed to parse Xcode settings") class Builder: - def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name): + def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name, run_tests, build_docs): self.opencv = os.path.abspath(opencv) self.contrib = None if contrib: @@ -77,6 +81,8 @@ class Builder: self.debug = debug self.debug_info = debug_info self.framework_name = framework_name + self.run_tests = run_tests + self.build_docs = build_docs def getBD(self, parent, t): @@ -128,8 +134,20 @@ class Builder: self.makeDynamicLib(mainBD) self.makeFramework(outdir, dirs) if self.build_objc_wrapper: - print("To run tests call:") - print(sys.argv[0].replace("build_framework", "run_tests") + " --framework_dir=" + outdir + " --framework_name=" + self.framework_name + " " + dirs[0] + "/modules/objc/test") + if self.run_tests: + check_call([sys.argv[0].replace("build_framework", "run_tests"), "--framework_dir=" + outdir, "--framework_name=" + self.framework_name, dirs[0] + "/modules/objc/test"]) + else: + print("To run tests call:") + print(sys.argv[0].replace("build_framework", "run_tests") + " --framework_dir=" + outdir + " --framework_name=" + self.framework_name + " " + dirs[0] + "/modules/objc/test") + if self.build_docs: + check_call([sys.argv[0].replace("build_framework", "build_docs"), dirs[0] + "/modules/objc/framework_build"]) + doc_path = os.path.join(dirs[0], "modules", "objc", "doc_build", "docs") + if os.path.exists(doc_path): + shutil.copytree(doc_path, os.path.join(outdir, "docs")) + shutil.copyfile(os.path.join(self.opencv, "doc", "opencv.ico"), os.path.join(outdir, "docs", "favicon.ico")) + else: + print("To build docs call:") + print(sys.argv[0].replace("build_framework", "build_docs") + " " + dirs[0] + "/modules/objc/framework_build") self.copy_samples(outdir) def build(self, outdir): @@ -370,11 +388,6 @@ class Builder: d = os.path.join(framework_dir, *l[1]) os.symlink(s, d) - doc_path = os.path.join(builddirs[0], "modules", "objc", "doc_build", "docs") - if os.path.exists(doc_path): - shutil.copytree(doc_path, os.path.join(outdir, "docs")) - shutil.copyfile(os.path.join(self.opencv, "doc", "opencv.ico"), os.path.join(outdir, "docs", "favicon.ico")) - def copy_samples(self, outdir): return @@ -431,6 +444,9 @@ if __name__ == "__main__": parser.add_argument('--debug_info', default=False, dest='debug_info', action='store_true', help='Build with debug information (useful for Release mode: BUILD_WITH_DEBUG_INFO=ON)') parser.add_argument('--framework_name', default='opencv2', dest='framework_name', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') parser.add_argument('--legacy_build', default=False, dest='legacy_build', action='store_true', help='Build legacy opencv2 framework (default: False, equivalent to "--framework_name=opencv2 --without=objc")') + parser.add_argument('--run_tests', default=False, dest='run_tests', action='store_true', help='Run tests') + parser.add_argument('--build_docs', default=False, dest='build_docs', action='store_true', help='Build docs') + args = parser.parse_args() os.environ['IPHONEOS_DEPLOYMENT_TARGET'] = args.iphoneos_deployment_target @@ -451,6 +467,6 @@ if __name__ == "__main__": [ (iphoneos_archs, "iPhoneOS"), (iphonesimulator_archs, "iPhoneSimulator"), - ], args.debug, args.debug_info, args.framework_name) + ], args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs) b.build(args.out) diff --git a/platforms/osx/build_docs.py b/platforms/osx/build_docs.py new file mode 100755 index 0000000000..9021eba000 --- /dev/null +++ b/platforms/osx/build_docs.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +""" +This script builds OpenCV docs for macOS. +""" + +from __future__ import print_function +import os, sys, multiprocessing, argparse, traceback +from subprocess import check_call, check_output, CalledProcessError, Popen + +# import common code +sys.path.insert(0, os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../ios')) +from build_docs import DocBuilder + +class OSXDocBuilder(DocBuilder): + + def getToolchain(self): + return None + +if __name__ == "__main__": + script_dir = os.path.abspath(os.path.dirname(sys.argv[0])) + parser = argparse.ArgumentParser(description='The script builds OpenCV docs for macOS.') + parser.add_argument('framework_dir', metavar='FRAMEWORK_DIR', help='folder where framework build files are located') + parser.add_argument('--output_dir', default=None, help='folder where docs will be built (default is "../doc_build" relative to framework_dir)') + parser.add_argument('--framework_header', default=None, help='umbrella header for OpenCV framework (default is "../../../lib/Release/{framework_name}.framework/Headers/{framework_name}.h")') + parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') + + args = parser.parse_args() + arch = "x86_64" + target = "macosx" + + b = OSXDocBuilder(script_dir, args.framework_dir, args.output_dir if args.output_dir else os.path.join(args.framework_dir, "../doc_build"), args.framework_header if args.framework_header else os.path.join(args.framework_dir, "../../../lib/Release/" + args.framework_name + ".framework/Headers/" + args.framework_name + ".h"), args.framework_name, arch, target) + b.build() diff --git a/platforms/osx/build_framework.py b/platforms/osx/build_framework.py index 73ab6c39e8..14c59265d7 100755 --- a/platforms/osx/build_framework.py +++ b/platforms/osx/build_framework.py @@ -47,6 +47,8 @@ if __name__ == "__main__": parser.add_argument('--debug_info', action='store_true', help='Build with debug information (useful for Release mode: BUILD_WITH_DEBUG_INFO=ON)') parser.add_argument('--framework_name', default='opencv2', dest='framework_name', action='store_true', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)') parser.add_argument('--legacy_build', default=False, dest='legacy_build', action='store_true', help='Build legacy framework (default: False, equivalent to "--framework_name=opencv2 --without=objc")') + parser.add_argument('--run_tests', default=False, dest='run_tests', action='store_true', help='Run tests') + parser.add_argument('--build_docs', default=False, dest='build_docs', action='store_true', help='Build docs') args = parser.parse_args() @@ -60,5 +62,5 @@ if __name__ == "__main__": b = OSXBuilder(args.opencv, args.contrib, False, False, args.without, args.disable, args.enablenonfree, [ (["x86_64"], "MacOSX") - ], args.debug, args.debug_info, args.framework_name) + ], args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs) b.build(args.out)