diff --git a/modules/objc/doc/LandingPage_ios.md b/modules/objc/doc/LandingPage_ios.md new file mode 100644 index 0000000000..35ce880f02 --- /dev/null +++ b/modules/objc/doc/LandingPage_ios.md @@ -0,0 +1,69 @@ + +This is the documentation for the iOS OpenCV framework. + +## Quick Start Tutorial + +Follow the steps below to get a simple "Hello World" app running on iOS + +* Open Xcode and create a new project from the **File** > **New** > **Project...** menu + +![New Project menu](common-create-project.png) + +* From template selection dialog select the `iOS` tab and then select `App` + +![Template selection dialog](ios-create-app.png) + +* Enter a name for the project and set **Interface** to **Storyboard** and **Language** to **Swift** + +![App settings dialog](ios-create-app-settings.png) + +* Choose a location to save the project + +![Choose project location dialog](common-create-app-location.png) + +* Create a new folder `Framework` + +![Create new folder menu](ios-create-framework-folder.png) + +* In a Finder window copy the framework file to the newly created folder. The framework should appear in the project navigator as below. (Note: in this case the framework has extension `.framework` but depending on how you built it or from where you obtained it your framework may have extension `.xcframework`) + +![Add framework](ios-add-framework.png) + +* In the project navigator select the `ViewController.swift` file + +![Select ViewController file](ios-select-viewcontroller.png) + +* Add an `import` for the OpenCV framework below the existing import for `UIKit` + +```swift, copy +import opencv2 +``` + +* Replace the implementation of the `viewDidLoad` function with the following code + +```swift, copy +override func viewDidLoad() { + super.viewDidLoad() + let white = Scalar(255, 255, 255, 255) + let black = Scalar(0, 0, 0, 255) + let m = Mat(rows: 400, cols: Int32(view.frame.width), type: CvType.CV_8UC4, scalar: white) + Imgproc.putText(img: m, text: "Hello World", org: Point(x: 10, y: 150), fontFace: HersheyFonts.FONT_HERSHEY_DUPLEX, fontScale: 2, color: black) + let image = m.toUIImage() + let imageView = UIImageView(image: image) + self.view.addSubview(imageView) +} +``` + +* The `ViewController.swift` file should now look like this + +![Updated ViewController code](ios-add-opencv-code.png) + +* Launch the app + +![Launch app button](ios-launch-app.png) + +* The app home screen should look like this + +![App screenshot](ios-app-screenshot.png) + +If that has all worked well then congratulations. If not then feel free to reach out to the OpenCV community for help. Also feel free to contribute fixes and improvements. diff --git a/modules/objc/doc/LandingPage_osx.md b/modules/objc/doc/LandingPage_osx.md new file mode 100644 index 0000000000..a24a8e68aa --- /dev/null +++ b/modules/objc/doc/LandingPage_osx.md @@ -0,0 +1,85 @@ + +This is the documentation for the macOS OpenCV framework. + +## Quick Start Tutorial + +Follow the steps below to get a simple "Hello World" app running on macOS + +* Open Xcode and create a new project from the **File** > **New** > **Project...** menu + +![New Project menu](common-create-project.png) + +* From the template selection dialog select the **macOS** tab and then select **App** + +![Template selection dialog](macos-create-app.png) + +* Enter a name for the project and set **Interface** to **Storyboard** and **Language** to **Swift** + +![App settings dialog](macos-create-app-settings.png) + +* Choose a location to save the project + +![Choose project location dialog](common-create-app-location.png) + +* Create a new folder `Framework` + +![Create new folder menu](macos-create-framework-folder.png) + +* In a Finder window copy the framework file to the newly created folder. The framework should appear in the project navigator as below. (Note: in this case the framework has extension `.framework` but depending on how you built it or from where you obtained it your framework may have extension `.xcframework`) + +![Add framework](macos-add-framework.png) + +* Select the project root in the project navigator and on the **General** tab locate the **Frameworks, Libraries and Embedded Content** section + +![Frameworks, Libraries and Embedded Content](macos-add-dependencies-1.png) + +* In the **Choose frameworks and libraries to add** dialog add the following + * OpenCL.framework + * liblapack.tbd + * libblas.tbd + +![Choose frameworks and libraries to add](macos-add-dependencies-2.png) + +* The **Frameworks, Libraries and Embedded Content** section should now look like this + +![Choose frameworks and libraries to add](macos-add-dependencies-3.png) + +* In the project navigator select the `ViewController.swift` file + +![Select ViewController file](macos-select-viewcontroller.png) + +* Add an `import` for the OpenCV framework below the existing import for `Cocoa` + +```swift, copy +import opencv2 +``` + +* Replace the implementation of the `viewDidLoad` function with the following code + +```swift, copy +override func viewDidLoad() { + super.viewDidLoad() + let white = Scalar(255, 255, 255, 255) + let black = Scalar(0, 0, 0, 255) + let m = Mat(rows: 400, cols: Int32(view.frame.width), type: CvType.CV_8UC4, scalar: white) + Imgproc.putText(img: m, text: "Hello World", org: Point(x: 10, y: 250), fontFace: HersheyFonts.FONT_HERSHEY_DUPLEX, fontScale: 2, color: black) + let image = m.toNSImage() + let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: Int(m.cols()), height: Int(m.rows()))) + imageView.image = image + self.view.addSubview(imageView) +} +``` + +* The `ViewController.swift` file should now look like this + +![Updated ViewController code](macos-add-opencv-code.png) + +* Launch the app + +![Launch app button](macos-launch-app.png) + +* The app home screen should look like this + +![App screenshot](macos-app-screenshot.png) + +If that has all worked well then congratulations. If not then feel free to reach out to the OpenCV community for help. Also feel free to contribute fixes and improvements. diff --git a/modules/objc/doc/README.md b/modules/objc/doc/README.md deleted file mode 100644 index 79bedec02c..0000000000 --- a/modules/objc/doc/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## About - -This is the documentation for the Objective-C/Swift OpenCV wrapper - -To get started: add the OpenCV framework to your project and add the following to your source - -###Objective-C - - #import - -###Swift - - import OpenCV diff --git a/modules/objc/doc/pics/common/common-create-app-location.png b/modules/objc/doc/pics/common/common-create-app-location.png new file mode 100644 index 0000000000..5ead9dd263 Binary files /dev/null and b/modules/objc/doc/pics/common/common-create-app-location.png differ diff --git a/modules/objc/doc/pics/common/common-create-project.png b/modules/objc/doc/pics/common/common-create-project.png new file mode 100644 index 0000000000..198e139936 Binary files /dev/null and b/modules/objc/doc/pics/common/common-create-project.png differ diff --git a/modules/objc/doc/pics/ios/ios-add-framework.png b/modules/objc/doc/pics/ios/ios-add-framework.png new file mode 100644 index 0000000000..4275cab089 Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-add-framework.png differ diff --git a/modules/objc/doc/pics/ios/ios-add-opencv-code.png b/modules/objc/doc/pics/ios/ios-add-opencv-code.png new file mode 100644 index 0000000000..e3356037bf Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-add-opencv-code.png differ diff --git a/modules/objc/doc/pics/ios/ios-app-screenshot.png b/modules/objc/doc/pics/ios/ios-app-screenshot.png new file mode 100644 index 0000000000..76e5e3cec3 Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-app-screenshot.png differ diff --git a/modules/objc/doc/pics/ios/ios-create-app-settings.png b/modules/objc/doc/pics/ios/ios-create-app-settings.png new file mode 100644 index 0000000000..034fa9b937 Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-create-app-settings.png differ diff --git a/modules/objc/doc/pics/ios/ios-create-app.png b/modules/objc/doc/pics/ios/ios-create-app.png new file mode 100644 index 0000000000..f4c0ddccba Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-create-app.png differ diff --git a/modules/objc/doc/pics/ios/ios-create-framework-folder.png b/modules/objc/doc/pics/ios/ios-create-framework-folder.png new file mode 100644 index 0000000000..6f85bab260 Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-create-framework-folder.png differ diff --git a/modules/objc/doc/pics/ios/ios-launch-app.png b/modules/objc/doc/pics/ios/ios-launch-app.png new file mode 100644 index 0000000000..a301cbc16b Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-launch-app.png differ diff --git a/modules/objc/doc/pics/ios/ios-select-viewcontroller.png b/modules/objc/doc/pics/ios/ios-select-viewcontroller.png new file mode 100644 index 0000000000..cece70ee93 Binary files /dev/null and b/modules/objc/doc/pics/ios/ios-select-viewcontroller.png differ diff --git a/modules/objc/doc/pics/osx/macos-add-dependencies-1.png b/modules/objc/doc/pics/osx/macos-add-dependencies-1.png new file mode 100644 index 0000000000..57558b3aaa Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-add-dependencies-1.png differ diff --git a/modules/objc/doc/pics/osx/macos-add-dependencies-2.png b/modules/objc/doc/pics/osx/macos-add-dependencies-2.png new file mode 100644 index 0000000000..52b9d5c4a8 Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-add-dependencies-2.png differ diff --git a/modules/objc/doc/pics/osx/macos-add-dependencies-3.png b/modules/objc/doc/pics/osx/macos-add-dependencies-3.png new file mode 100644 index 0000000000..7925844c1e Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-add-dependencies-3.png differ diff --git a/modules/objc/doc/pics/osx/macos-add-framework.png b/modules/objc/doc/pics/osx/macos-add-framework.png new file mode 100644 index 0000000000..834752cf6d Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-add-framework.png differ diff --git a/modules/objc/doc/pics/osx/macos-add-opencv-code.png b/modules/objc/doc/pics/osx/macos-add-opencv-code.png new file mode 100644 index 0000000000..14eddd3bc8 Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-add-opencv-code.png differ diff --git a/modules/objc/doc/pics/osx/macos-app-screenshot.png b/modules/objc/doc/pics/osx/macos-app-screenshot.png new file mode 100644 index 0000000000..f4dbd63ae4 Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-app-screenshot.png differ diff --git a/modules/objc/doc/pics/osx/macos-create-app-settings.png b/modules/objc/doc/pics/osx/macos-create-app-settings.png new file mode 100644 index 0000000000..d29aad56bf Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-create-app-settings.png differ diff --git a/modules/objc/doc/pics/osx/macos-create-app.png b/modules/objc/doc/pics/osx/macos-create-app.png new file mode 100644 index 0000000000..81d87504e4 Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-create-app.png differ diff --git a/modules/objc/doc/pics/osx/macos-create-framework-folder.png b/modules/objc/doc/pics/osx/macos-create-framework-folder.png new file mode 100644 index 0000000000..2655a3ed85 Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-create-framework-folder.png differ diff --git a/modules/objc/doc/pics/osx/macos-launch-app.png b/modules/objc/doc/pics/osx/macos-launch-app.png new file mode 100644 index 0000000000..326cd22541 Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-launch-app.png differ diff --git a/modules/objc/doc/pics/osx/macos-select-viewcontroller.png b/modules/objc/doc/pics/osx/macos-select-viewcontroller.png new file mode 100644 index 0000000000..6dce9833ff Binary files /dev/null and b/modules/objc/doc/pics/osx/macos-select-viewcontroller.png differ diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py index 2f6f974a02..10071267e6 100755 --- a/modules/objc/generator/gen_objc.py +++ b/modules/objc/generator/gen_objc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function, unicode_literals import sys, re, os.path, errno, fnmatch @@ -100,6 +100,9 @@ method_dict = { ("Mat", "dot") : "-dot:" } +enum_value_lookup = {} +enums = set() + modules = [] @@ -708,12 +711,25 @@ def see_lookup(objc_class, see): if (see_class, see_method) in method_dict: method = method_dict[(see_class, see_method)] if see_class == objc_class: - return method + return "``{}``".format(method[1:]) else: - return ("-" if method[0] == "-" else "") + "[" + see_class + " " + method[1:] + "]" + return "``{}/{}``".format(see_class, method[1:]) else: return see +def hash_link(link_text, objc_class, function_name): + if link_text == function_name: + return "*{}*".format(link_text) # make bold + elif link_text in enums: + return "``{}``".format(link_text) + elif link_text in enum_value_lookup: + return "``{}/{}``".format(enum_value_lookup[link_text], link_text) + elif (objc_class, link_text) in method_dict: + return "``{}/{}``".format(objc_class, link_text) + elif ("Core", link_text) in method_dict: + return "``Core/{}``".format(link_text) + else: + return "#{}".format(link_text) # leave as is class ObjectiveCWrapperGenerator(object): def __init__(self): @@ -1115,13 +1131,16 @@ class ObjectiveCWrapperGenerator(object): if fi.docstring: lines = fi.docstring.splitlines() toWrite = [] + last_line_was_param = False for index, line in enumerate(lines): + line = re.sub(r"([(\s])#(.+?)([).,\s]|$)", lambda x: x.group(1) + hash_link(x.group(2), ci.objc_name, fi.name) + x.group(3), line) 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] + last_line_was_param = True for arg in args: if arg.name == name: toWrite.append(re.sub(r'\*\s*@param ', '* @param ', line)) @@ -1129,10 +1148,14 @@ class ObjectiveCWrapperGenerator(object): else: s0 = line.find("@see") if s0 != -1: + if last_line_was_param: + # separate from @param lines + toWrite.append(" *") sees = line[(s0 + 5):].split(",") - toWrite.append(line[:(s0 + 5)] + ", ".join(["`" + see_lookup(ci.objc_name, see.strip()) + "`" for see in sees])) + toWrite.append(line[:s0] + "- SeeAlso " + ", ".join([see_lookup(ci.objc_name, see.strip()) for see in sees])) else: toWrite.append(line) + last_line_was_param = False for line in toWrite: method_declarations.write(line + "\n") @@ -1317,6 +1340,10 @@ $unrefined_call$epilogue$ret if ci.cname in enum_fix: typeNameShort = enum_fix[ci.cname].get(typeNameShort, typeNameShort) + enums.add(typeNameShort) + for c in consts: + enum_value_lookup[c.name] = typeNameShort + ci.enum_declarations.write(""" // C++: enum {1} ({2}) typedef NS_ENUM(int, {1}) {{ @@ -1462,16 +1489,37 @@ typedef NS_ENUM(int, {1}) {{ self.save(opencv_modulemap_file, opencv_modulemap) available_modules = " ".join(["-DAVAILABLE_" + m['name'].upper() for m in config['modules']]) 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, module_availability_defines=available_modules) + doc_catalog = output_objc_path + "/../Documentation.docc" + doc_resources = os.path.join(doc_catalog, "Resources") + cmakelist = Template(cmakelist_template).substitute( + modules = ";".join(modules), + framework = framework_name, + objc_target = objc_target, + module_availability_defines = available_modules, + ) self.save(os.path.join(dstdir, "CMakeLists.txt"), cmakelist) + mkdir_p(doc_resources) 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) + landing_page_body_in = "" + if objc_target == "ios" or objc_target == "osx": + with open(os.path.join(SCRIPT_DIR, '../doc/LandingPage_' + objc_target + '.md')) as landing_page_in: + landing_page_body_in = landing_page_in.read() + landing_page_body_out = "# ``" + framework_name + "`` Framework\n\n" + landing_page_body_in + landing_page_body_out += "\n\n\n## Modules\n\n" + ", ".join(["``" + m.capitalize() + "``" for m in modules]) + "\n" + with open(os.path.join(doc_catalog, "LandingPage.md"), "w") as landing_page_out: + landing_page_out.write(landing_page_body_out) + for m in config['modules']: + pic_files = os.path.join(ROOT_DIR, m['location'], 'doc', 'pics') + if os.path.exists(pic_files): + copy_tree(pic_files, doc_resources) + if objc_target == "ios": + copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/ios'), doc_resources) + if objc_target == "osx": + copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/osx'), doc_resources) + if objc_target == "ios" or objc_target == "osx": + copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/common'), doc_resources) + if framework_name != "OpenCV": for dirname, dirs, files in os.walk(os.path.join(testdir, "test")): if dirname.endswith('/resources'): @@ -1541,10 +1589,11 @@ def sanitize_documentation_string(doc, type): 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 = re.sub(r"!\[(.*)]\((?:.*/)*(.+)\.(?:png|jpg|svg)\)", "![\\1](\\2)", doc) doc = doc.replace("@anchor", "") \ .replace("@brief ", "").replace("\\brief ", "") \ - .replace("@cite", "CITE:") \ + .replace("@cite", "*Cite:*") \ .replace("@code{.cpp}", "") \ .replace("@code{.txt}", "") \ .replace("@code", "") \ @@ -1556,14 +1605,14 @@ def sanitize_documentation_string(doc, type): .replace("@endcode", "") \ .replace("@endinternal", "") \ .replace("@file", "") \ - .replace("@include", "INCLUDE:") \ + .replace("@include", "Include:") \ .replace("@ingroup", "") \ .replace("@internal", "") \ .replace("@overload", "") \ .replace("@param[in]", "@param") \ .replace("@param[out]", "@param") \ - .replace("@ref", "REF:") \ - .replace("@note", "NOTE:") \ + .replace("@ref", "*Ref:*") \ + .replace("@note", "*Note:*") \ .replace("@returns", "@return") \ .replace("@sa ", "@see ") \ .replace("@snippet", "SNIPPET:") \ diff --git a/modules/objc/generator/templates/cmakelists.template b/modules/objc/generator/templates/cmakelists.template index 4303a140db..14937ca066 100644 --- a/modules/objc/generator/templates/cmakelists.template +++ b/modules/objc/generator/templates/cmakelists.template @@ -22,7 +22,14 @@ else() endif() file(GLOB_RECURSE objc_headers "*\.h") -add_library($framework STATIC $${objc_sources}) +set_source_files_properties(Documentation.docc PROPERTIES + XCODE_LAST_KNOWN_FILE_TYPE folder.documentationcatalog +) + +add_library($framework STATIC + $${objc_sources} + Documentation.docc +) set_target_properties($framework PROPERTIES LINKER_LANGUAGE CXX) diff --git a/modules/objc/generator/templates/doc_howto.template b/modules/objc/generator/templates/doc_howto.template new file mode 100644 index 0000000000..a51da54bfa --- /dev/null +++ b/modules/objc/generator/templates/doc_howto.template @@ -0,0 +1,11 @@ +# $framework Documentation - How To View + +## View Locally + +* Start a web server in the directory where this HowTo file is located. For example ``python3 -m http.server 8000`` +* In a browser navigate to URL http://127.0.0.1:8000/$hosting_base_path/documentation/$framework/ + +## Deploying to a server + +* Upload the content of the directory where this HowTo file is located to the document root of the web server maintaining the same folder hierarchy +* In a browser navigate to URL https://{server-domain}/$hosting_base_path/documentation/$framework/ diff --git a/platforms/apple/build_xcframework.py b/platforms/apple/build_xcframework.py index 3916c135f9..73d3807ee9 100755 --- a/platforms/apple/build_xcframework.py +++ b/platforms/apple/build_xcframework.py @@ -82,6 +82,7 @@ if __name__ == "__main__": visionos_script_path = os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../ios/build_visionos_framework.py') build_folders = [] + docs_build_folder_dict = {} def get_or_create_build_folder(base_dir, platform): build_folder = "{}/{}".format(base_dir, platform).replace(" ", "\\ ") # Escape spaces in output path @@ -91,6 +92,7 @@ if __name__ == "__main__": if iphoneos_archs: build_folder = get_or_create_build_folder(args.out, "iphoneos") build_folders.append(build_folder) + docs_build_folder_dict["ios"] = build_folder command = ["python3", ios_script_path, build_folder, "--iphoneos_archs", iphoneos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args print_header("Building iPhoneOS frameworks") print(command) @@ -98,12 +100,15 @@ if __name__ == "__main__": if iphonesimulator_archs: build_folder = get_or_create_build_folder(args.out, "iphonesimulator") build_folders.append(build_folder) + if not iphoneos_archs: + docs_build_folder_dict["ios"] = build_folder command = ["python3", ios_script_path, build_folder, "--iphonesimulator_archs", iphonesimulator_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args print_header("Building iPhoneSimulator frameworks") execute(command, cwd=os.getcwd()) if visionos_archs: build_folder = get_or_create_build_folder(args.out, "visionos") build_folders.append(build_folder) + docs_build_folder_dict["visionos"] = build_folder command = ["python3", visionos_script_path, build_folder, "--visionos_archs", visionos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args print_header("Building visionOS frameworks") print(command) @@ -111,18 +116,22 @@ if __name__ == "__main__": if visionsimulator_archs: build_folder = get_or_create_build_folder(args.out, "visionsimulator") build_folders.append(build_folder) + if not visionos_archs: + docs_build_folder_dict["visionos"] = build_folder command = ["python3", visionos_script_path, build_folder, "--visionsimulator_archs", visionsimulator_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args print_header("Building visionSimulator frameworks") execute(command, cwd=os.getcwd()) if macos_archs: build_folder = get_or_create_build_folder(args.out, "macos") build_folders.append(build_folder) + docs_build_folder_dict["macos"] = build_folder command = ["python3", osx_script_path, build_folder, "--macos_archs", macos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args print_header("Building MacOS frameworks") execute(command, cwd=os.getcwd()) if catalyst_archs: build_folder = get_or_create_build_folder(args.out, "catalyst") build_folders.append(build_folder) + docs_build_folder_dict["catalyst"] = build_folder command = ["python3", osx_script_path, build_folder, "--catalyst_archs", catalyst_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args print_header("Building Catalyst frameworks") execute(command, cwd=os.getcwd()) @@ -150,6 +159,23 @@ if __name__ == "__main__": print("") print_header("Finished building {}".format(xcframework_path)) + + # Phase 3: copy documentation + + print_header("Copying documentation") + + for platform, build_folder in docs_build_folder_dict.items(): + docs_src = "{}/docs".format(build_folder) + docs_dst = "{}/docs_{}".format(args.out, platform) + # Remove the docs folder if it exists + with contextlib.suppress(FileNotFoundError): + shutil.rmtree(docs_dst) + print("Removed existing documentation at {}".format(docs_dst)) + shutil.copytree(docs_src, docs_dst) + + print("") + print_header("Finished copying documentation") + except Exception as e: print_error(e) traceback.print_exc(file=sys.stderr) diff --git a/platforms/apple/cv_build_utils.py b/platforms/apple/cv_build_utils.py index d764b70fd1..cfb43721cb 100644 --- a/platforms/apple/cv_build_utils.py +++ b/platforms/apple/cv_build_utils.py @@ -63,3 +63,17 @@ def get_cmake_version(): return (int(m.group(1)), int(m.group(2)), int(m.group(3))) else: raise Exception("Failed to parse CMake version") + +def get_current_branch(opencv_dir): + ret = check_output(["git", "branch", "--show-current"], cwd = opencv_dir).decode('utf-8').strip() + if ret != "": + return ret + else: + raise Exception("Failed to get current branch") + +def find_directory(base_dir, search_dir): + dirs = check_output(["find", base_dir, "-type", "d", "-name", search_dir]).decode('utf-8').splitlines() + if dirs and len(dirs) > 0: + return dirs[0] + else: + raise Exception("Failed to find directory: " + search_dir) diff --git a/platforms/ios/build_docs.py b/platforms/ios/build_docs.py deleted file mode 100755 index e5dc9b2e81..0000000000 --- a/platforms/ios/build_docs.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/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 50c15dd71e..5bf6764361 100755 --- a/platforms/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ The script builds OpenCV.framework for iOS. The built framework is universal, it can be used to build app and run it on either iOS simulator or real device. @@ -42,7 +42,7 @@ else: from distutils.dir_util import copy_tree sys.path.insert(0, os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../apple')) -from cv_build_utils import execute, print_error, get_xcode_major, get_xcode_setting, get_xcode_version, get_cmake_version +from cv_build_utils import execute, print_error, get_xcode_major, get_xcode_setting, get_xcode_version, get_cmake_version, get_current_branch, find_directory IPHONEOS_DEPLOYMENT_TARGET='9.0' # default, can be changed via command line options or environment variable @@ -50,7 +50,7 @@ CURRENT_FILE_DIR = os.path.dirname(__file__) class Builder: - def __init__(self, opencv, contrib, dynamic, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name, run_tests, build_docs, swiftdisabled): + def __init__(self, opencv, contrib, dynamic, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name, run_tests, hosting_base_path, swiftdisabled): self.opencv = os.path.abspath(opencv) self.contrib = None if contrib: @@ -69,8 +69,15 @@ class Builder: self.debug_info = debug_info self.framework_name = framework_name self.run_tests = run_tests - self.build_docs = build_docs + if hosting_base_path is None: + current_branch = get_current_branch(self.opencv) + objc_target = self.getObjcTarget(self.targets[0][1]) + self.hosting_base_path = os.path.join(current_branch, "macos" if objc_target == "osx" else objc_target) + else: + self.hosting_base_path = hosting_base_path self.swiftdisabled = swiftdisabled + self.docs_built = False + self.build_docs = False def checkCMakeVersion(self): if get_xcode_version() >= (12, 2): @@ -95,12 +102,17 @@ class Builder: dirs = [] xcode_ver = get_xcode_major() + xcode_supports_ios_32bit_arch = xcode_ver <= 13 + self.build_docs = xcode_ver >= 13 # build each architecture separately alltargets = [] for target_group in self.targets: for arch in target_group[0]: + if arch in ["armv7", "armv7s", "i386"] and not xcode_supports_ios_32bit_arch: + print("Skipping unsupported architecture: " + arch) + continue current = ( arch, target_group[1] ) alltargets.append(current) @@ -149,20 +161,20 @@ class Builder: self.makeFramework(outdir, dirs) if self.build_objc_wrapper: + doc_output = os.path.join(outdir, "docs") + if os.path.exists(doc_output): + shutil.rmtree(doc_output) + + doc_build_path = os.path.join(dirs[0], "lib", self.getConfiguration(), "docs") + if os.path.exists(doc_build_path): + copy_tree(doc_build_path, doc_output) + else: + print("Documentation not found at: " + doc_build_path); 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_bindings_generator/{}/test".format(self.getObjcTarget(target[1]))]) 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_bindings_generator/{}/test".format(self.getObjcTarget(target[1]))) - 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) if self.swiftdisabled: swift_sources_dir = os.path.join(outdir, "SwiftSources") @@ -245,6 +257,31 @@ class Builder: return buildcmd + def getDocBuildCommand(self, base_build_dir, source_dir, framework_build_dir, docs_dir): + output_dir = docs_dir if self.hosting_base_path == "" else os.path.join(docs_dir, self.hosting_base_path) + if not os.path.exists(output_dir): + os.makedirs(output_dir) + symbol_graph_dir = find_directory(os.path.join(framework_build_dir, "build", self.framework_name + ".build"), "symbol-graph") + doc_buildcmd = [ + "xcrun", + "docc", + "convert", + "--emit-lmdb-index", + "--fallback-display-name", self.framework_name, + "--fallback-bundle-identifier", "org.opencv." + self.framework_name, + "--fallback-bundle-version", "1", + "--output-dir", output_dir, + "--transform-for-static-hosting", + "--ide-console-output", + os.path.join(source_dir, "Documentation.docc"), + "--additional-symbol-graph-dir", symbol_graph_dir + ] + + if self.hosting_base_path != "": + doc_buildcmd += ["--hosting-base-path", self.hosting_base_path] + + return doc_buildcmd + def getInfoPlist(self, builddirs): return os.path.join(builddirs[0], "ios", "Info.plist") @@ -320,16 +357,33 @@ class Builder: execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir) execute(["cmake", "-DBUILD_TYPE=%s" % self.getConfiguration(), "-P", "cmake_install.cmake"], cwd = builddir) if self.build_objc_wrapper: - cmakecmd = self.makeCMakeCmd(arch, target, builddir + "/modules/objc_bindings_generator/{}/gen".format(self.getObjcTarget(target)), cmakeargs) + objc_source_dir = builddir + "/modules/objc_bindings_generator/{}/gen".format(self.getObjcTarget(target)) + cmakecmd = self.makeCMakeCmd(arch, target, objc_source_dir, cmakeargs) if self.swiftdisabled: cmakecmd.append("-DSWIFT_DISABLED=1") cmakecmd.append("-DBUILD_ROOT=%s" % builddir) cmakecmd.append("-DCMAKE_INSTALL_NAME_TOOL=install_name_tool") cmakecmd.append("--no-warn-unused-cli") - execute(cmakecmd, cwd = builddir + "/modules/objc/framework_build") - - execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir + "/modules/objc/framework_build") - execute(["cmake", "-DBUILD_TYPE=%s" % self.getConfiguration(), "-DCMAKE_INSTALL_PREFIX=%s" % (builddir + "/install"), "-P", "cmake_install.cmake"], cwd = builddir + "/modules/objc/framework_build") + framework_build_dir = builddir + "/modules/objc/framework_build" + execute(cmakecmd, cwd = framework_build_dir) + execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = framework_build_dir) + if self.build_docs and not self.docs_built: + # build the syntax graphs + execute(buildcmd + ["-target", "ALL_BUILD", "docbuild"], cwd = framework_build_dir) + # build the document catalog + docs_dir = os.path.join(builddir, "lib", self.getConfiguration(), "docs") + doc_buildcmd2 = self.getDocBuildCommand(builddir, objc_source_dir, framework_build_dir, docs_dir) + execute(doc_buildcmd2, cwd = objc_source_dir) + with open(os.path.join(self.opencv, "modules", "objc", "generator", "templates", "doc_howto.template"), "r") as f: + howto_template = f.read() + howto = string.Template(howto_template).substitute( + framework = self.framework_name, + hosting_base_path = self.hosting_base_path + ) + with codecs.open(os.path.join(docs_dir, "HOWTO.md"), "w", "utf-8") as file: + file.write(howto) + self.docs_built = True + execute(["cmake", "-DBUILD_TYPE=%s" % self.getConfiguration(), "-DCMAKE_INSTALL_PREFIX=%s" % (builddir + "/install"), "-P", "cmake_install.cmake"], cwd = framework_build_dir) def mergeLibs(self, builddir): res = os.path.join(builddir, "lib", self.getConfiguration(), "libopencv_merged.a") @@ -540,7 +594,7 @@ if __name__ == "__main__": 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') + parser.add_argument('--doc_hosting_base_path', default=None, dest='hosting_base_path', action='store_true', help='Documentation hosting base path') parser.add_argument('--disable-swift', default=False, dest='swiftdisabled', action='store_true', help='Disable building of Swift extensions') args, unknown_args = parser.parse_known_args() @@ -595,6 +649,6 @@ if __name__ == "__main__": if iphonesimulator_archs: targets.append((iphonesimulator_archs, "iPhoneSimulator")) - b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs, args.swiftdisabled) + b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.hosting_base_path, args.swiftdisabled) b.build(args.out) diff --git a/platforms/ios/build_visionos_framework.py b/platforms/ios/build_visionos_framework.py index d99c5feca6..5575934966 100755 --- a/platforms/ios/build_visionos_framework.py +++ b/platforms/ios/build_visionos_framework.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ The script builds OpenCV.framework for visionOS. """ @@ -69,7 +69,7 @@ if __name__ == "__main__": 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 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') + parser.add_argument('--doc_hosting_base_path', default=None, dest='hosting_base_path', action='store_true', help='Documentation hosting base path') parser.add_argument('--disable-swift', default=False, dest='swiftdisabled', action='store_true', help='Disable building of Swift extensions') args, unknown_args = parser.parse_known_args() @@ -112,5 +112,5 @@ if __name__ == "__main__": if visionsimulator_archs: targets.append((visionsimulator_archs, "XRSimulator")), - b = visionOSBuilder(args.opencv, args.contrib, args.dynamic, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs, args.swiftdisabled) + b = visionOSBuilder(args.opencv, args.contrib, args.dynamic, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.hosting_base_path, args.swiftdisabled) b.build(args.out) diff --git a/platforms/ios/run_tests.py b/platforms/ios/run_tests.py index 12603b2226..d9639370d9 100755 --- a/platforms/ios/run_tests.py +++ b/platforms/ios/run_tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ This script runs OpenCV.framework tests for iOS. """ diff --git a/platforms/osx/build_docs.py b/platforms/osx/build_docs.py deleted file mode 100755 index 9021eba000..0000000000 --- a/platforms/osx/build_docs.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/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 0f07717fb4..1ca895cdbc 100755 --- a/platforms/osx/build_framework.py +++ b/platforms/osx/build_framework.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ The script builds OpenCV.framework for OSX. """ @@ -76,7 +76,7 @@ if __name__ == "__main__": 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 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') + parser.add_argument('--doc_hosting_base_path', default=None, dest='hosting_base_path', action='store_true', help='Documentation hosting base path') parser.add_argument('--disable-swift', default=False, dest='swiftdisabled', action='store_true', help='Disable building of Swift extensions') args, unknown_args = parser.parse_known_args() @@ -128,5 +128,5 @@ if __name__ == "__main__": if catalyst_archs: targets.append((catalyst_archs, "Catalyst")), - b = OSXBuilder(args.opencv, args.contrib, args.dynamic, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs, args.swiftdisabled) + b = OSXBuilder(args.opencv, args.contrib, args.dynamic, args.without, args.disable, args.enablenonfree, targets, args.debug, args.debug_info, args.framework_name, args.run_tests, args.hosting_base_path, args.swiftdisabled) b.build(args.out) diff --git a/platforms/osx/run_tests.py b/platforms/osx/run_tests.py index afdcf8426a..f7eb294381 100755 --- a/platforms/osx/run_tests.py +++ b/platforms/osx/run_tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ This script runs OpenCV.framework tests for OSX. """