Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/platforms/ios/build_framework.py
16337 views
1
#!/usr/bin/env python
2
"""
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.
5
6
Usage:
7
./build_framework.py <outputdir>
8
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.
11
12
Script will create <outputdir>, if it's missing, and a few its subdirectories:
13
14
<outputdir>
15
build/
16
iPhoneOS-*/
17
[cmake-generated build tree for an iOS device target]
18
iPhoneSimulator-*/
19
[cmake-generated build tree for iOS simulator]
20
opencv2.framework/
21
[the framework content]
22
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.
26
27
Adding --dynamic parameter will build opencv2.framework as App Store dynamic framework. Only iOS 8+ versions are supported.
28
"""
29
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
33
34
IPHONEOS_DEPLOYMENT_TARGET='8.0' # default, can be changed via command line options or environemnt variable
35
36
def execute(cmd, cwd = None):
37
print("Executing: %s in %s" % (cmd, cwd), file=sys.stderr)
38
print('Executing: ' + ' '.join(cmd))
39
retcode = check_call(cmd, cwd = cwd)
40
if retcode != 0:
41
raise Exception("Child returned:", retcode)
42
43
def getXCodeMajor():
44
ret = check_output(["xcodebuild", "-version"])
45
m = re.match(r'Xcode\s+(\d+)\..*', ret, flags=re.IGNORECASE)
46
if m:
47
return int(m.group(1))
48
else:
49
raise Exception("Failed to parse Xcode version")
50
51
class Builder:
52
def __init__(self, opencv, contrib, dynamic, bitcodedisabled, exclude, targets):
53
self.opencv = os.path.abspath(opencv)
54
self.contrib = None
55
if contrib:
56
modpath = os.path.join(contrib, "modules")
57
if os.path.isdir(modpath):
58
self.contrib = os.path.abspath(modpath)
59
else:
60
print("Note: contrib repository is bad - modules subfolder not found", file=sys.stderr)
61
self.dynamic = dynamic
62
self.bitcodedisabled = bitcodedisabled
63
self.exclude = exclude
64
self.targets = targets
65
66
def getBD(self, parent, t):
67
68
if len(t[0]) == 1:
69
res = os.path.join(parent, 'build-%s-%s' % (t[0][0].lower(), t[1].lower()))
70
else:
71
res = os.path.join(parent, 'build-%s' % t[1].lower())
72
73
if not os.path.isdir(res):
74
os.makedirs(res)
75
return os.path.abspath(res)
76
77
def _build(self, outdir):
78
outdir = os.path.abspath(outdir)
79
if not os.path.isdir(outdir):
80
os.makedirs(outdir)
81
mainWD = os.path.join(outdir, "build")
82
dirs = []
83
84
xcode_ver = getXCodeMajor()
85
86
if self.dynamic:
87
alltargets = self.targets
88
else:
89
# if we are building a static library, we must build each architecture separately
90
alltargets = []
91
92
for t in self.targets:
93
for at in t[0]:
94
current = ( [at], t[1] )
95
96
alltargets.append(current)
97
98
for t in alltargets:
99
mainBD = self.getBD(mainWD, t)
100
dirs.append(mainBD)
101
102
cmake_flags = []
103
if self.contrib:
104
cmake_flags.append("-DOPENCV_EXTRA_MODULES_PATH=%s" % self.contrib)
105
if xcode_ver >= 7 and t[1] == 'iPhoneOS' and self.bitcodedisabled == False:
106
cmake_flags.append("-DCMAKE_C_FLAGS=-fembed-bitcode")
107
cmake_flags.append("-DCMAKE_CXX_FLAGS=-fembed-bitcode")
108
self.buildOne(t[0], t[1], mainBD, cmake_flags)
109
110
if self.dynamic == False:
111
self.mergeLibs(mainBD)
112
self.makeFramework(outdir, dirs)
113
114
def build(self, outdir):
115
try:
116
self._build(outdir)
117
except Exception as e:
118
print("="*60, file=sys.stderr)
119
print("ERROR: %s" % e, file=sys.stderr)
120
print("="*60, file=sys.stderr)
121
traceback.print_exc(file=sys.stderr)
122
sys.exit(1)
123
124
def getToolchain(self, arch, target):
125
return None
126
127
def getCMakeArgs(self, arch, target):
128
129
args = [
130
"cmake",
131
"-GXcode",
132
"-DAPPLE_FRAMEWORK=ON",
133
"-DCMAKE_INSTALL_PREFIX=install",
134
"-DCMAKE_BUILD_TYPE=Release",
135
"-DOPENCV_INCLUDE_INSTALL_PATH=include",
136
"-DOPENCV_3P_LIB_INSTALL_PATH=lib/3rdparty"
137
] + ([
138
"-DBUILD_SHARED_LIBS=ON",
139
"-DCMAKE_MACOSX_BUNDLE=ON",
140
"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO",
141
] if self.dynamic else [])
142
143
if len(self.exclude) > 0:
144
args += ["-DBUILD_opencv_world=OFF"] if not self.dynamic else []
145
args += ["-DBUILD_opencv_%s=OFF" % m for m in self.exclude]
146
147
return args
148
149
def getBuildCommand(self, archs, target):
150
151
buildcmd = [
152
"xcodebuild",
153
]
154
155
if self.dynamic:
156
buildcmd += [
157
"IPHONEOS_DEPLOYMENT_TARGET=" + os.environ['IPHONEOS_DEPLOYMENT_TARGET'],
158
"ONLY_ACTIVE_ARCH=NO",
159
]
160
161
if not self.bitcodedisabled:
162
buildcmd.append("BITCODE_GENERATION_MODE=bitcode")
163
164
for arch in archs:
165
buildcmd.append("-arch")
166
buildcmd.append(arch.lower())
167
else:
168
arch = ";".join(archs)
169
buildcmd += [
170
"IPHONEOS_DEPLOYMENT_TARGET=" + os.environ['IPHONEOS_DEPLOYMENT_TARGET'],
171
"ARCHS=%s" % arch,
172
]
173
174
buildcmd += [
175
"-sdk", target.lower(),
176
"-configuration", "Release",
177
"-parallelizeTargets",
178
"-jobs", str(multiprocessing.cpu_count()),
179
] + (["-target","ALL_BUILD"] if self.dynamic else [])
180
181
return buildcmd
182
183
def getInfoPlist(self, builddirs):
184
return os.path.join(builddirs[0], "ios", "Info.plist")
185
186
def buildOne(self, arch, target, builddir, cmakeargs = []):
187
# Run cmake
188
toolchain = self.getToolchain(arch, target)
189
cmakecmd = self.getCMakeArgs(arch, target) + \
190
(["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else [])
191
if target.lower().startswith("iphoneos"):
192
cmakecmd.append("-DCPU_BASELINE=DETECT")
193
cmakecmd.append(self.opencv)
194
cmakecmd.extend(cmakeargs)
195
execute(cmakecmd, cwd = builddir)
196
197
# Clean and build
198
clean_dir = os.path.join(builddir, "install")
199
if os.path.isdir(clean_dir):
200
shutil.rmtree(clean_dir)
201
buildcmd = self.getBuildCommand(arch, target)
202
execute(buildcmd + ["-target", "ALL_BUILD", "build"], cwd = builddir)
203
execute(["cmake", "-P", "cmake_install.cmake"], cwd = builddir)
204
205
def mergeLibs(self, builddir):
206
res = os.path.join(builddir, "lib", "Release", "libopencv_merged.a")
207
libs = glob.glob(os.path.join(builddir, "install", "lib", "*.a"))
208
libs3 = glob.glob(os.path.join(builddir, "install", "lib", "3rdparty", "*.a"))
209
print("Merging libraries:\n\t%s" % "\n\t".join(libs + libs3), file=sys.stderr)
210
execute(["libtool", "-static", "-o", res] + libs + libs3)
211
212
def makeFramework(self, outdir, builddirs):
213
name = "opencv2"
214
215
# set the current dir to the dst root
216
framework_dir = os.path.join(outdir, "%s.framework" % name)
217
if os.path.isdir(framework_dir):
218
shutil.rmtree(framework_dir)
219
os.makedirs(framework_dir)
220
221
if self.dynamic:
222
dstdir = framework_dir
223
libname = "opencv2.framework/opencv2"
224
else:
225
dstdir = os.path.join(framework_dir, "Versions", "A")
226
libname = "libopencv_merged.a"
227
228
# copy headers from one of build folders
229
shutil.copytree(os.path.join(builddirs[0], "install", "include", "opencv2"), os.path.join(dstdir, "Headers"))
230
231
# make universal static lib
232
libs = [os.path.join(d, "lib", "Release", libname) for d in builddirs]
233
lipocmd = ["lipo", "-create"]
234
lipocmd.extend(libs)
235
lipocmd.extend(["-o", os.path.join(dstdir, name)])
236
print("Creating universal library from:\n\t%s" % "\n\t".join(libs), file=sys.stderr)
237
execute(lipocmd)
238
239
# dynamic framework has different structure, just copy the Plist directly
240
if self.dynamic:
241
resdir = dstdir
242
shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist"))
243
else:
244
# copy Info.plist
245
resdir = os.path.join(dstdir, "Resources")
246
os.makedirs(resdir)
247
shutil.copyfile(self.getInfoPlist(builddirs), os.path.join(resdir, "Info.plist"))
248
249
# make symbolic links
250
links = [
251
(["A"], ["Versions", "Current"]),
252
(["Versions", "Current", "Headers"], ["Headers"]),
253
(["Versions", "Current", "Resources"], ["Resources"]),
254
(["Versions", "Current", name], [name])
255
]
256
for l in links:
257
s = os.path.join(*l[0])
258
d = os.path.join(framework_dir, *l[1])
259
os.symlink(s, d)
260
261
class iOSBuilder(Builder):
262
263
def getToolchain(self, arch, target):
264
toolchain = os.path.join(self.opencv, "platforms", "ios", "cmake", "Toolchains", "Toolchain-%s_Xcode.cmake" % target)
265
return toolchain
266
267
def getCMakeArgs(self, arch, target):
268
arch = ";".join(arch)
269
270
args = Builder.getCMakeArgs(self, arch, target)
271
args = args + [
272
'-DIOS_ARCH=%s' % arch
273
]
274
return args
275
276
277
if __name__ == "__main__":
278
folder = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../.."))
279
parser = argparse.ArgumentParser(description='The script builds OpenCV.framework for iOS.')
280
parser.add_argument('out', metavar='OUTDIR', help='folder to put built framework')
281
parser.add_argument('--opencv', metavar='DIR', default=folder, help='folder with opencv repository (default is "../.." relative to script location)')
282
parser.add_argument('--contrib', metavar='DIR', default=None, help='folder with opencv_contrib repository (default is "None" - build only main framework)')
283
parser.add_argument('--without', metavar='MODULE', default=[], action='append', help='OpenCV modules to exclude from the framework')
284
parser.add_argument('--dynamic', default=False, action='store_true', help='build dynamic framework (default is "False" - builds static framework)')
285
parser.add_argument('--disable-bitcode', default=False, dest='bitcodedisabled', action='store_true', help='disable bitcode (enabled by default)')
286
parser.add_argument('--iphoneos_deployment_target', default=os.environ.get('IPHONEOS_DEPLOYMENT_TARGET', IPHONEOS_DEPLOYMENT_TARGET), help='specify IPHONEOS_DEPLOYMENT_TARGET')
287
parser.add_argument('--iphoneos_archs', default='armv7,armv7s,arm64', help='select iPhoneOS target ARCHS')
288
args = parser.parse_args()
289
290
os.environ['IPHONEOS_DEPLOYMENT_TARGET'] = args.iphoneos_deployment_target
291
print('Using IPHONEOS_DEPLOYMENT_TARGET=' + os.environ['IPHONEOS_DEPLOYMENT_TARGET'])
292
iphoneos_archs = args.iphoneos_archs.split(',')
293
print('Using iPhoneOS ARCHS=' + str(iphoneos_archs))
294
295
b = iOSBuilder(args.opencv, args.contrib, args.dynamic, args.bitcodedisabled, args.without,
296
[
297
(iphoneos_archs, "iPhoneOS"),
298
] if os.environ.get('BUILD_PRECOMMIT', None) else
299
[
300
(iphoneos_archs, "iPhoneOS"),
301
(["i386", "x86_64"], "iPhoneSimulator"),
302
])
303
b.build(args.out)
304
305