3 The script builds OpenCV.framework for iOS.
4 The built framework is universal, it can be used to build app and run it on either iOS simulator or real device.
7 ./build_framework.py <outputdir>
9 By cmake conventions (and especially if you work with OpenCV repository),
10 the output dir should not be a subdirectory of OpenCV source tree.
12 Script will create <outputdir>, if it's missing, and a few its subdirectories:
17 [cmake-generated build tree for an iOS device target]
19 [cmake-generated build tree for iOS simulator]
21 [the framework content]
23 The script should handle minor OpenCV updates efficiently
24 - it does not recompile the library from scratch each time.
25 However, opencv2.framework directory is erased and recreated on each run.
27 Adding --dynamic parameter will build opencv2.framework as App Store dynamic framework. Only iOS 8+ versions are supported.
30 from __future__ import print_function
31 import glob, re, os, os.path, shutil, string, sys, argparse, traceback, multiprocessing
32 from subprocess import check_call, check_output, CalledProcessError
34 def execute(cmd, cwd = None):
35 print("Executing: %s in %s" % (cmd, cwd), file=sys.stderr)
36 retcode = check_call(cmd, cwd = cwd)
38 raise Exception("Child returned:", retcode)
41 ret = check_output(["xcodebuild", "-version"])
42 m = re.match(r'XCode\s+(\d)\..*', ret, flags=re.IGNORECASE)
44 return int(m.group(1))
48 def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, targets):
49 self.opencv = os.path.abspath(opencv)
52 modpath = os.path.join(contrib, "modules")
53 if os.path.isdir(modpath):
54 self.contrib = os.path.abspath(modpath)
56 print("Note: contrib repository is bad - modules subfolder not found", file=sys.stderr)
57 self.dynamic = dynamic
58 self.bitcodedisabled = bitcodedisabled
59 self.exclude = exclude
60 self.targets = targets
62 def getBD(self, parent, t):
65 res = os.path.join(parent, 'build-%s-%s' % (t[0][0].lower(), t[1].lower()))
67 res = os.path.join(parent, 'build-%s' % t[1].lower())
69 if not os.path.isdir(res):
71 return os.path.abspath(res)
73 def _build(self, outdir):
74 outdir = os.path.abspath(outdir)
75 if not os.path.isdir(outdir):
77 mainWD = os.path.join(outdir, "build")
80 xcode_ver = getXCodeMajor()
83 alltargets = self.targets
85 # if we are building a static library, we must build each architecture separately
88 for t in self.targets:
90 current = ( [at], t[1] )
92 alltargets.append(current)
95 mainBD = self.getBD(mainWD, t)
100 cmake_flags.append("-DOPENCV_EXTRA_MODULES_PATH=%s" % self.contrib)
101 if xcode_ver >= 7 and t[1] == 'iPhoneOS' and self.bitcodedisabled == False:
102 cmake_flags.append("-DCMAKE_C_FLAGS=-fembed-bitcode")
103 cmake_flags.append("-DCMAKE_CXX_FLAGS=-fembed-bitcode")
104 self.buildOne(t[0], t[1], mainBD, cmake_flags)
106 if self.dynamic == False:
107 self.mergeLibs(mainBD)
108 self.makeFramework(outdir, dirs)
110 def build(self, outdir):
113 except Exception as e:
114 print("="*60, file=sys.stderr)
115 print("ERROR: %s" % e, file=sys.stderr)
116 print("="*60, file=sys.stderr)
117 traceback.print_exc(file=sys.stderr)
120 def getToolchain(self, arch, target):
123 def getCMakeArgs(self, arch, target):
128 "-DAPPLE_FRAMEWORK=ON",
129 "-DCMAKE_INSTALL_PREFIX=install",
130 "-DCMAKE_BUILD_TYPE=Release",
132 "-DBUILD_SHARED_LIBS=ON",
133 "-DCMAKE_MACOSX_BUNDLE=ON",
134 "-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO",
135 ] if self.dynamic else [])
137 if len(self.exclude) > 0:
138 args += ["-DBUILD_opencv_world=OFF"] if not self.dynamic else []
139 args += ["-DBUILD_opencv_%s=OFF" % m for m in self.exclude]
143 def getBuildCommand(self, archs, target):
151 "IPHONEOS_DEPLOYMENT_TARGET=8.0",
152 "ONLY_ACTIVE_ARCH=NO",
156 buildcmd.append("-arch")
157 buildcmd.append(arch.lower())
159 arch = ";".join(archs)
161 "IPHONEOS_DEPLOYMENT_TARGET=6.0",
166 "-sdk", target.lower(),
167 "-configuration", "Release",
168 "-parallelizeTargets",
169 "-jobs", multiprocessing.cpu_count(),
170 ] + (["-target","ALL_BUILD"] if self.dynamic else [])
174 def getInfoPlist(self, builddirs):
175 return os.path.join(builddirs[0], "ios", "Info.plist")
177 def buildOne(self, arch, target, builddir, cmakeargs = []):
179 toolchain = self.getToolchain(arch, target)
180 cmakecmd = self.getCMakeArgs(arch, target) + \
181 (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else [])
182 if target.lower().startswith("iphoneos"):
183 cmakecmd.append("-DENABLE_NEON=ON")
184 cmakecmd.append(self.opencv)
185 cmakecmd.extend(cmakeargs)
186 execute(cmakecmd, cwd = builddir)
189 clean_dir = os.path.join(builddir, "install")
190 if os.path.isdir(clean_dir):
191 shutil.rmtree(clean_dir)
192 buildcmd = self.getBuildCommand(arch, target)
193 execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir)
194 execute(["cmake", "-P", "cmake_install.cmake"], cwd = builddir)
196 def mergeLibs(self, builddir):
197 res = os.path.join(builddir, "lib", "Release", "libopencv_merged.a")
198 libs = glob.glob(os.path.join(builddir, "install", "lib", "*.a"))
199 libs3 = glob.glob(os.path.join(builddir, "install", "share", "OpenCV", "3rdparty", "lib", "*.a"))
200 print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3), file=sys.stderr)
201 execute(["libtool", "-static", "-o", res] + libs + libs3)
203 def makeFramework(self, outdir, builddirs):
206 # set the current dir to the dst root
207 framework_dir = os.path.join(outdir, "%s.framework" % name)
208 if os.path.isdir(framework_dir):
209 shutil.rmtree(framework_dir)
210 os.makedirs(framework_dir)
213 dstdir = framework_dir
214 libname = "opencv2.framework/opencv2"
216 dstdir = os.path.join(framework_dir, "Versions", "A")
217 libname = "libopencv_merged.a"
219 # copy headers from one of build folders
220 shutil.copytree(os.path.join(builddirs[0], "install", "include", "opencv2"), os.path.join(dstdir, "Headers"))
222 # make universal static lib
223 libs = [os.path.join(d, "lib", "Release", libname) for d in builddirs]
224 lipocmd = ["lipo", "-create"]
226 lipocmd.extend(["-o", os.path.join(dstdir, name)])
227 print("Creating universal library from:\n\t%s" % "\n\t".join(libs), file=sys.stderr)
230 # dynamic framework has different structure, just copy the Plist directly
233 shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist"))
236 resdir = os.path.join(dstdir, "Resources")
238 shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist"))
240 # make symbolic links
242 (["A"], ["Versions", "Current"]),
243 (["Versions", "Current", "Headers"], ["Headers"]),
244 (["Versions", "Current", "Resources"], ["Resources"]),
245 (["Versions", "Current", name], [name])
248 s = os.path.join(*l[0])
249 d = os.path.join(framework_dir, *l[1])
252 class iOSBuilder(Builder):
254 def getToolchain(self, arch, target):
255 toolchain = os.path.join(self.opencv, "platforms", "ios", "cmake", "Toolchains", "Toolchain-%s_Xcode.cmake" % target)
258 def getCMakeArgs(self, arch, target):
259 arch = ";".join(arch)
261 args = Builder.getCMakeArgs(self, arch, target)
263 '-DIOS_ARCH=%s' % arch
268 if __name__ == "__main__":
269 folder = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../.."))
270 parser = argparse.ArgumentParser(description='The script builds OpenCV.framework for iOS.')
271 parser.add_argument('out', metavar='OUTDIR', help='folder to put built framework')
272 parser.add_argument('--opencv', metavar='DIR', default=folder, help='folder with opencv repository (default is "../.." relative to script location)')
273 parser.add_argument('--contrib', metavar='DIR', default=None, help='folder with opencv_contrib repository (default is "None" - build only main framework)')
274 parser.add_argument('--without', metavar='MODULE', default=[], action='append', help='OpenCV modules to exclude from the framework')
275 parser.add_argument('--dynamic', default=False, action='store_true', help='build dynamic framework (default is "False" - builds static framework)')
276 parser.add_argument('--disable-bitcode', default=False, dest='bitcodedisabled', action='store_true', help='disable bitcode (enabled by default)')
277 args = parser.parse_args()
279 b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.bitcodedisabled, args.without,
281 (["armv7", "arm64"], "iPhoneOS"),
282 ] if os.environ.get('BUILD_PRECOMMIT', None) else
284 (["armv7", "armv7s", "arm64"], "iPhoneOS"),
285 (["i386", "x86_64"], "iPhoneSimulator"),