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:
@@ -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)
|
||||
|
||||
Executable
+114
@@ -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()
|
||||
Reference in New Issue
Block a user