262a52f306
and fix the compilation for iOS in the same time
206 lines
7.8 KiB
Python
206 lines
7.8 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
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.
|
|
|
|
Usage:
|
|
./build_framework.py <outputdir>
|
|
|
|
By cmake conventions (and especially if you work with OpenCV repository),
|
|
the output dir should not be a subdirectory of OpenCV source tree.
|
|
|
|
Script will create <outputdir>, if it's missing, and a few its subdirectories:
|
|
|
|
<outputdir>
|
|
build/
|
|
iPhoneOS-*/
|
|
[cmake-generated build tree for an iOS device target]
|
|
iPhoneSimulator-*/
|
|
[cmake-generated build tree for iOS simulator]
|
|
opencv2.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.
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
import glob, re, os, os.path, shutil, string, sys, argparse, traceback
|
|
from subprocess import check_call, check_output, CalledProcessError
|
|
|
|
def execute(cmd, cwd = None):
|
|
print("Executing: %s in %s" % (cmd, cwd), file=sys.stderr)
|
|
retcode = check_call(cmd, cwd = cwd)
|
|
if retcode != 0:
|
|
raise Exception("Child returned:", retcode)
|
|
|
|
def getXCodeMajor():
|
|
ret = check_output(["xcodebuild", "-version"])
|
|
m = re.match(r'XCode\s+(\d)\..*', ret, flags=re.IGNORECASE)
|
|
if m:
|
|
return int(m.group(1))
|
|
return 0
|
|
|
|
class Builder:
|
|
def __init__(self, opencv, contrib, targets):
|
|
self.opencv = os.path.abspath(opencv)
|
|
self.contrib = None
|
|
if contrib:
|
|
modpath = os.path.join(contrib, "modules")
|
|
if os.path.isdir(modpath):
|
|
self.contrib = os.path.abspath(modpath)
|
|
else:
|
|
print("Note: contrib repository is bad - modules subfolder not found", file=sys.stderr)
|
|
self.targets = targets
|
|
|
|
def getBD(self, parent, t):
|
|
res = os.path.join(parent, '%s-%s' % t)
|
|
if not os.path.isdir(res):
|
|
os.makedirs(res)
|
|
return os.path.abspath(res)
|
|
|
|
def _build(self, outdir):
|
|
outdir = os.path.abspath(outdir)
|
|
if not os.path.isdir(outdir):
|
|
os.makedirs(outdir)
|
|
mainWD = os.path.join(outdir, "build")
|
|
dirs = []
|
|
|
|
xcode_ver = getXCodeMajor()
|
|
|
|
for t in self.targets:
|
|
mainBD = self.getBD(mainWD, t)
|
|
dirs.append(mainBD)
|
|
cmake_flags = []
|
|
if self.contrib:
|
|
cmake_flags.append("-DOPENCV_EXTRA_MODULES_PATH=%s" % self.contrib)
|
|
if xcode_ver >= 7 and t[1] == 'iPhoneOS':
|
|
cmake_flags.append("-DCMAKE_C_FLAGS=-fembed-bitcode")
|
|
cmake_flags.append("-DCMAKE_CXX_FLAGS=-fembed-bitcode")
|
|
self.buildOne(t[0], t[1], mainBD, cmake_flags)
|
|
self.mergeLibs(mainBD)
|
|
self.makeFramework(outdir, dirs)
|
|
|
|
def build(self, outdir):
|
|
try:
|
|
self._build(outdir)
|
|
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, arch, target):
|
|
toolchain = os.path.join(self.opencv, "platforms", "ios", "cmake", "Toolchains", "Toolchain-%s_Xcode.cmake" % target)
|
|
return toolchain
|
|
|
|
def getCMakeArgs(self, arch, target):
|
|
args = [
|
|
"cmake",
|
|
"-GXcode",
|
|
"-DAPPLE_FRAMEWORK=ON",
|
|
"-DCMAKE_INSTALL_PREFIX=install",
|
|
"-DCMAKE_BUILD_TYPE=Release",
|
|
]
|
|
return args
|
|
|
|
def getBuildCommand(self, arch, target):
|
|
buildcmd = [
|
|
"xcodebuild",
|
|
"IPHONEOS_DEPLOYMENT_TARGET=6.0",
|
|
"ARCHS=%s" % arch,
|
|
"-sdk", target.lower(),
|
|
"-configuration", "Release",
|
|
"-parallelizeTargets",
|
|
"-jobs", "4"
|
|
]
|
|
return buildcmd
|
|
|
|
def getInfoPlist(self, builddirs):
|
|
return os.path.join(builddirs[0], "ios", "Info.plist")
|
|
|
|
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 arch.startswith("armv") or arch.startswith("arm64"):
|
|
cmakecmd.append("-DENABLE_NEON=ON")
|
|
cmakecmd.append(self.opencv)
|
|
cmakecmd.extend(cmakeargs)
|
|
execute(cmakecmd, cwd = builddir)
|
|
# Clean and build
|
|
clean_dir = os.path.join(builddir, "install")
|
|
if os.path.isdir(clean_dir):
|
|
shutil.rmtree(clean_dir)
|
|
buildcmd = self.getBuildCommand(arch, target)
|
|
execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir)
|
|
execute(["cmake", "-P", "cmake_install.cmake"], cwd = builddir)
|
|
|
|
def mergeLibs(self, builddir):
|
|
res = os.path.join(builddir, "lib", "Release", "libopencv_merged.a")
|
|
libs = glob.glob(os.path.join(builddir, "install", "lib", "*.a"))
|
|
libs3 = glob.glob(os.path.join(builddir, "install", "share", "OpenCV", "3rdparty", "lib", "*.a"))
|
|
print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3), file=sys.stderr)
|
|
execute(["libtool", "-static", "-o", res] + libs + libs3)
|
|
|
|
def makeFramework(self, outdir, builddirs):
|
|
name = "opencv2"
|
|
libname = "libopencv_merged.a"
|
|
|
|
# set the current dir to the dst root
|
|
framework_dir = os.path.join(outdir, "%s.framework" % name)
|
|
if os.path.isdir(framework_dir):
|
|
shutil.rmtree(framework_dir)
|
|
os.makedirs(framework_dir)
|
|
|
|
dstdir = os.path.join(framework_dir, "Versions", "A")
|
|
|
|
# copy headers from one of build folders
|
|
shutil.copytree(os.path.join(builddirs[0], "install", "include", "opencv2"), os.path.join(dstdir, "Headers"))
|
|
|
|
# make universal static lib
|
|
libs = [os.path.join(d, "lib", "Release", libname) for d in builddirs]
|
|
lipocmd = ["lipo", "-create"]
|
|
lipocmd.extend(libs)
|
|
lipocmd.extend(["-o", os.path.join(dstdir, name)])
|
|
print("Creating universal library from:\n\t%s" % "\n\t".join(libs), file=sys.stderr)
|
|
execute(lipocmd)
|
|
|
|
# copy Info.plist
|
|
resdir = os.path.join(dstdir, "Resources")
|
|
os.makedirs(resdir)
|
|
shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist"))
|
|
|
|
# make symbolic links
|
|
links = [
|
|
(["A"], ["Versions", "Current"]),
|
|
(["Versions", "Current", "Headers"], ["Headers"]),
|
|
(["Versions", "Current", "Resources"], ["Resources"]),
|
|
(["Versions", "Current", name], [name])
|
|
]
|
|
for l in links:
|
|
s = os.path.join(*l[0])
|
|
d = os.path.join(framework_dir, *l[1])
|
|
os.symlink(s, d)
|
|
|
|
if __name__ == "__main__":
|
|
folder = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../.."))
|
|
parser = argparse.ArgumentParser(description='The script builds OpenCV.framework for iOS.')
|
|
parser.add_argument('out', metavar='OUTDIR', help='folder to put built framework')
|
|
parser.add_argument('--opencv', metavar='DIR', default=folder, help='folder with opencv repository (default is "../.." relative to script location)')
|
|
parser.add_argument('--contrib', metavar='DIR', default=None, help='folder with opencv_contrib repository (default is "None" - build only main framework)')
|
|
args = parser.parse_args()
|
|
|
|
b = Builder(args.opencv, args.contrib,
|
|
[
|
|
("armv7", "iPhoneOS"),
|
|
("armv7s", "iPhoneOS"),
|
|
("arm64", "iPhoneOS"),
|
|
("i386", "iPhoneSimulator"),
|
|
("x86_64", "iPhoneSimulator"),
|
|
])
|
|
b.build(args.out)
|