1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

Merge pull request #17165 from komakai:objc-binding

Objc binding

* Initial work on Objective-C wrapper

* Objective-C generator script; update manually generated wrappers

* Add Mat tests

* Core Tests

* Imgproc wrapper generation and tests

* Fixes for Imgcodecs wrapper

* Miscellaneous fixes. Swift build support

* Objective-C wrapper build/install

* Add Swift wrappers for videoio/objdetect/feature2d

* Framework build;iOS support

* Fix toArray functions;Use enum types whenever possible

* Use enum types where possible;prepare test build

* Update test

* Add test runner scripts for iOS and macOS

* Add test scripts and samples

* Build fixes

* Fix build (cmake 3.17.x compatibility)

* Fix warnings

* Fix enum name conflicting handling

* Add support for document generation with Jazzy

* Swift/Native fast accessor functions

* Add Objective-C wrapper for calib3d, dnn, ml, photo and video modules

* Remove IntOut/FloatOut/DoubleOut classes

* Fix iOS default test platform value

* Fix samples

* Revert default framework name to opencv2

* Add converter util functions

* Fix failing test

* Fix whitespace

* Add handling for deprecated methods;fix warnings;define __OPENCV_BUILD

* Suppress cmake warnings

* Reduce severity of "jazzy not found" log message

* Fix incorrect #include of compatibility header in ios.h

* Use explicit returns in subscript/get implementation

* Reduce minimum required cmake version to 3.15 for Objective-C/Swift binding
This commit is contained in:
Giles Payne
2020-06-09 03:32:53 +09:00
committed by GitHub
parent f30b5995b6
commit 02385472b6
237 changed files with 24107 additions and 28 deletions
+104 -13
View File
@@ -17,19 +17,20 @@ Script will create <outputdir>, if it's missing, and a few its subdirectories:
[cmake-generated build tree for an iOS device target]
iPhoneSimulator-*/
[cmake-generated build tree for iOS simulator]
opencv2.framework/
{framework_name}.framework/
[the framework content]
The script should handle minor OpenCV updates efficiently
- it does not recompile the library from scratch each time.
However, opencv2.framework directory is erased and recreated on each run.
However, {framework_name}.framework directory is erased and recreated on each run.
Adding --dynamic parameter will build opencv2.framework as App Store dynamic framework. Only iOS 8+ versions are supported.
Adding --dynamic parameter will build {framework_name}.framework as App Store dynamic framework. Only iOS 8+ versions are supported.
"""
from __future__ import print_function
import glob, re, os, os.path, shutil, string, sys, argparse, traceback, multiprocessing
from subprocess import check_call, check_output, CalledProcessError
from distutils.dir_util import copy_tree
IPHONEOS_DEPLOYMENT_TARGET='8.0' # default, can be changed via command line options or environment variable
@@ -49,7 +50,7 @@ def getXCodeMajor():
raise Exception("Failed to parse Xcode version")
class Builder:
def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info):
def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, disable, enablenonfree, targets, debug, debug_info, framework_name):
self.opencv = os.path.abspath(opencv)
self.contrib = None
if contrib:
@@ -61,11 +62,13 @@ class Builder:
self.dynamic = dynamic
self.bitcodedisabled = bitcodedisabled
self.exclude = exclude
self.build_objc_wrapper = not "objc" in self.exclude
self.disable = disable
self.enablenonfree = enablenonfree
self.targets = targets
self.debug = debug
self.debug_info = debug_info
self.framework_name = framework_name
def getBD(self, parent, t):
@@ -114,6 +117,10 @@ class Builder:
if self.dynamic == False:
self.mergeLibs(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")
self.copy_samples(outdir)
def build(self, outdir):
try:
@@ -140,7 +147,8 @@ class Builder:
"-DCMAKE_INSTALL_PREFIX=install",
"-DCMAKE_BUILD_TYPE=%s" % self.getConfiguration(),
"-DOPENCV_INCLUDE_INSTALL_PATH=include",
"-DOPENCV_3P_LIB_INSTALL_PATH=lib/3rdparty"
"-DOPENCV_3P_LIB_INSTALL_PATH=lib/3rdparty",
"-DFRAMEWORK_NAME=%s" % self.framework_name,
] + ([
"-DBUILD_SHARED_LIBS=ON",
"-DCMAKE_MACOSX_BUNDLE=ON",
@@ -197,15 +205,26 @@ class Builder:
def getInfoPlist(self, builddirs):
return os.path.join(builddirs[0], "ios", "Info.plist")
def buildOne(self, arch, target, builddir, cmakeargs = []):
# Run cmake
def makeCMakeCmd(self, arch, target, dir, cmakeargs = []):
toolchain = self.getToolchain(arch, target)
cmakecmd = self.getCMakeArgs(arch, target) + \
(["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else [])
if target.lower().startswith("iphoneos"):
cmakecmd.append("-DCPU_BASELINE=DETECT")
cmakecmd.append(self.opencv)
cmakecmd.append(dir)
cmakecmd.extend(cmakeargs)
return cmakecmd
def buildOne(self, arch, target, builddir, cmakeargs = []):
# Run cmake
#toolchain = self.getToolchain(arch, target)
#cmakecmd = self.getCMakeArgs(arch, target) + \
# (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else [])
#if target.lower().startswith("iphoneos"):
# cmakecmd.append("-DCPU_BASELINE=DETECT")
#cmakecmd.append(self.opencv)
#cmakecmd.extend(cmakeargs)
cmakecmd = self.makeCMakeCmd(arch, target, self.opencv, cmakeargs)
execute(cmakecmd, cwd = builddir)
# Clean and build
@@ -215,16 +234,27 @@ class Builder:
buildcmd = self.getBuildCommand(arch, target)
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/gen", cmakeargs)
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")
def mergeLibs(self, builddir):
res = os.path.join(builddir, "lib", self.getConfiguration(), "libopencv_merged.a")
libs = glob.glob(os.path.join(builddir, "install", "lib", "*.a"))
module = [os.path.join(builddir, "install", "lib", self.framework_name + ".framework", self.framework_name)] if self.build_objc_wrapper else []
libs3 = glob.glob(os.path.join(builddir, "install", "lib", "3rdparty", "*.a"))
print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3), file=sys.stderr)
execute(["libtool", "-static", "-o", res] + libs + libs3)
print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3 + module), file=sys.stderr)
execute(["libtool", "-static", "-o", res] + libs + libs3 + module)
def makeFramework(self, outdir, builddirs):
name = "opencv2"
name = self.framework_name
# set the current dir to the dst root
framework_dir = os.path.join(outdir, "%s.framework" % name)
@@ -234,13 +264,41 @@ class Builder:
if self.dynamic:
dstdir = framework_dir
libname = "opencv2.framework/opencv2"
libname = name + ".framework/" + name
else:
dstdir = os.path.join(framework_dir, "Versions", "A")
libname = "libopencv_merged.a"
# copy headers from one of build folders
shutil.copytree(os.path.join(builddirs[0], "install", "include", "opencv2"), os.path.join(dstdir, "Headers"))
if name != "opencv2":
for dirname, dirs, files in os.walk(os.path.join(dstdir, "Headers")):
for filename in files:
filepath = os.path.join(dirname, filename)
with open(filepath) as file:
body = file.read()
body = body.replace("include \"opencv2/", "include \"" + name + "/")
body = body.replace("include <opencv2/", "include <" + name + "/")
with open(filepath, "w") as file:
file.write(body)
if self.build_objc_wrapper:
copy_tree(os.path.join(builddirs[0], "install", "lib", name + ".framework", "Headers"), os.path.join(dstdir, "Headers"))
platform_name_map = {
"arm": "armv7-apple-ios",
"arm64": "arm64-apple-ios",
"i386": "i386-apple-ios-simulator",
"x86_64": "x86_64-apple-ios-simulator",
} if builddirs[0].find("iphone") != -1 else {
"x86_64": "x86_64-apple-macos",
}
for d in builddirs:
copy_tree(os.path.join(d, "install", "lib", name + ".framework", "Modules"), os.path.join(dstdir, "Modules"))
for dirname, dirs, files in os.walk(os.path.join(dstdir, "Modules")):
for filename in files:
filestem = os.path.splitext(filename)[0]
fileext = os.path.splitext(filename)[1]
if filestem in platform_name_map:
os.rename(os.path.join(dirname, filename), os.path.join(dirname, platform_name_map[filestem] + fileext))
# make universal static lib
libs = [os.path.join(d, "lib", self.getConfiguration(), libname) for d in builddirs]
@@ -265,6 +323,7 @@ class Builder:
(["A"], ["Versions", "Current"]),
(["Versions", "Current", "Headers"], ["Headers"]),
(["Versions", "Current", "Resources"], ["Resources"]),
(["Versions", "Current", "Modules"], ["Modules"]),
(["Versions", "Current", name], [name])
]
for l in links:
@@ -272,6 +331,13 @@ 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"))
def copy_samples(self, outdir):
return
class iOSBuilder(Builder):
def getToolchain(self, arch, target):
@@ -287,6 +353,25 @@ class iOSBuilder(Builder):
]
return args
def copy_samples(self, outdir):
print('Copying samples to: ' + outdir)
samples_dir = os.path.join(outdir, "samples")
shutil.copytree(os.path.join(self.opencv, "samples", "swift", "ios"), samples_dir)
if self.framework_name != "OpenCV":
for dirname, dirs, files in os.walk(samples_dir):
for filename in files:
if not filename.endswith((".h", ".swift", ".pbxproj")):
continue
filepath = os.path.join(dirname, filename)
with open(filepath) as file:
body = file.read()
body = body.replace("import OpenCV", "import " + self.framework_name)
body = body.replace("#import <OpenCV/OpenCV.h>", "#import <" + self.framework_name + "/" + self.framework_name + ".h>")
body = body.replace("OpenCV.framework", self.framework_name + ".framework")
body = body.replace("../../OpenCV/**", "../../" + self.framework_name + "/**")
with open(filepath, "w") as file:
file.write(body)
if __name__ == "__main__":
folder = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../.."))
@@ -304,6 +389,8 @@ if __name__ == "__main__":
parser.add_argument('--enable_nonfree', default=False, dest='enablenonfree', action='store_true', help='enable non-free modules (disabled by default)')
parser.add_argument('--debug', default=False, dest='debug', action='store_true', help='Build "Debug" binaries (disabled by default)')
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', 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 opencv2 framework (default: False, equivalent to "--framework_name=opencv2 --without=objc")')
args = parser.parse_args()
os.environ['IPHONEOS_DEPLOYMENT_TARGET'] = args.iphoneos_deployment_target
@@ -312,6 +399,10 @@ if __name__ == "__main__":
print('Using iPhoneOS ARCHS=' + str(iphoneos_archs))
iphonesimulator_archs = args.iphonesimulator_archs.split(',')
print('Using iPhoneSimulator ARCHS=' + str(iphonesimulator_archs))
if args.legacy_build:
args.framework_name = "opencv2"
if not "objc" in args.without:
args.without.append("objc")
b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.bitcodedisabled, args.without, args.disable, args.enablenonfree,
[
@@ -320,5 +411,5 @@ if __name__ == "__main__":
[
(iphoneos_archs, "iPhoneOS"),
(iphonesimulator_archs, "iPhoneSimulator"),
], args.debug, args.debug_info)
], args.debug, args.debug_info, args.framework_name)
b.build(args.out)
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env python
"""
This script runs OpenCV.framework tests for iOS.
"""
from __future__ import print_function
import glob, re, os, os.path, shutil, string, sys, argparse, traceback, multiprocessing
from subprocess import check_call, check_output, CalledProcessError
IPHONEOS_DEPLOYMENT_TARGET='8.0' # default, can be changed via command line options or environment variable
def execute(cmd, cwd = None):
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)
class TestRunner:
def __init__(self, script_dir, tests_dir, build_dir, framework_dir, framework_name, arch, target, platform):
self.script_dir = script_dir
self.tests_dir = tests_dir
self.build_dir = build_dir
self.framework_dir = framework_dir
self.framework_name = framework_name
self.arch = arch
self.target = target
self.platform = platform
def _run(self):
if not os.path.isdir(self.build_dir):
os.makedirs(self.build_dir)
self.runTest()
def run(self):
try:
self._run()
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 getCMakeArgs(self):
args = [
"cmake",
"-GXcode",
"-DFRAMEWORK_DIR=%s" % self.framework_dir,
"-DFRAMEWORK_NAME=%s" % self.framework_name,
]
return args
def makeCMakeCmd(self):
toolchain = self.getToolchain()
cmakecmd = self.getCMakeArgs() + \
(["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else []) + \
["-DCMAKE_INSTALL_NAME_TOOL=install_name_tool"]
cmakecmd.append(self.tests_dir)
return cmakecmd
def runTest(self):
cmakecmd = self.makeCMakeCmd()
execute(cmakecmd, cwd = self.build_dir)
buildcmd = self.getTestCommand()
execute(buildcmd, cwd = self.build_dir)
def getTestCommand(self):
testcmd = [
"xcodebuild",
"test",
"-project", "OpenCVTest.xcodeproj",
"-scheme", "OpenCVTestTests",
"-destination", "platform=%s" % self.platform
]
return testcmd
class iOSTestRunner(TestRunner):
def getToolchain(self):
toolchain = os.path.join(self.script_dir, "cmake", "Toolchains", "Toolchain-%s_Xcode.cmake" % self.target)
return toolchain
def getCMakeArgs(self):
args = TestRunner.getCMakeArgs(self)
args = args + [
"-DIOS_ARCH=%s" % self.arch,
"-DIPHONEOS_DEPLOYMENT_TARGET=%s" % os.environ['IPHONEOS_DEPLOYMENT_TARGET'],
]
return args
if __name__ == "__main__":
script_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
parser = argparse.ArgumentParser(description='The script builds OpenCV.framework for iOS.')
parser.add_argument('tests_dir', metavar='TEST_DIR', help='folder where test files are located')
parser.add_argument('--build_dir', default=None, help='folder where test will be built (default is "../test_build" relative to tests_dir)')
parser.add_argument('--framework_dir', default=None, help='folder where OpenCV framework is located')
parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV framework (default: opencv2, will change to OpenCV in future version)')
parser.add_argument('--iphoneos_deployment_target', default=os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', IPHONEOS_DEPLOYMENT_TARGET), help='specify IPHONEOS_DEPLOYMENT_TARGET')
parser.add_argument('--platform', default='iOS Simulator,name=iPhone 11', help='xcodebuild platform parameter (default is iOS 11 simulator)')
args = parser.parse_args()
os.environ['IPHONEOS_DEPLOYMENT_TARGET'] = args.iphoneos_deployment_target
print('Using IPHONEOS_DEPLOYMENT_TARGET=' + os.environ['IPHONEOS_DEPLOYMENT_TARGET'])
arch = "x86_64"
target = "iPhoneSimulator"
print('Using iPhoneSimulator ARCH=' + arch)
r = iOSTestRunner(script_dir, args.tests_dir, args.build_dir if args.build_dir else os.path.join(args.tests_dir, "../test_build"), args.framework_dir, args.framework_name, arch, target, args.platform)
r.run()