2012-06-28 23:13:57 +08:00
#!/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 >
2012-08-07 17:29:43 +08:00
By cmake conventions ( and especially if you work with OpenCV repository ) ,
2012-06-28 23:13:57 +08:00
the output dir should not be a subdirectory of OpenCV source tree .
2012-08-07 17:29:43 +08:00
2012-06-28 23:13:57 +08:00
Script will create < outputdir > , if it ' s missing, and a few its subdirectories:
2012-08-07 17:29:43 +08:00
2012-06-28 23:13:57 +08:00
< outputdir >
build /
2012-11-01 22:15:48 +08:00
iPhoneOS - * /
2012-06-28 23:13:57 +08:00
[ cmake - generated build tree for an iOS device target ]
2014-10-16 04:23:39 +08:00
iPhoneSimulator - * /
2012-06-28 23:13:57 +08:00
[ cmake - generated build tree for iOS simulator ]
2012-11-01 22:15:48 +08:00
opencv2 . framework /
2012-06-28 23:13:57 +08:00
[ the framework content ]
The script should handle minor OpenCV updates efficiently
- it does not recompile the library from scratch each time .
2012-11-01 22:15:48 +08:00
However , opencv2 . framework directory is erased and recreated on each run .
2017-01-21 00:16:01 +08:00
Adding - - dynamic parameter will build opencv2 . framework as App Store dynamic framework . Only iOS 8 + versions are supported .
2012-06-28 23:13:57 +08:00
"""
2015-11-11 21:12:35 +08:00
from __future__ import print_function
2017-05-02 23:48:54 +08:00
import glob , re , os , os . path , shutil , string , sys , argparse , traceback , multiprocessing
2015-11-11 21:12:35 +08:00
from subprocess import check_call , check_output , CalledProcessError
2018-10-24 12:37:31 +08:00
IPHONEOS_DEPLOYMENT_TARGET = ' 8.0 ' # default, can be changed via command line options or environemnt variable
2015-11-11 21:12:35 +08:00
def execute ( cmd , cwd = None ) :
print ( " Executing: %s in %s " % ( cmd , cwd ) , file = sys . stderr )
2018-10-24 12:37:31 +08:00
print ( ' Executing: ' + ' ' . join ( cmd ) )
2015-11-11 21:12:35 +08:00
retcode = check_call ( cmd , cwd = cwd )
if retcode != 0 :
raise Exception ( " Child returned: " , retcode )
def getXCodeMajor ( ) :
ret = check_output ( [ " xcodebuild " , " -version " ] )
2018-09-27 05:20:37 +08:00
m = re . match ( r ' Xcode \ s+( \ d+) \ ..* ' , ret , flags = re . IGNORECASE )
2015-11-11 21:12:35 +08:00
if m :
return int ( m . group ( 1 ) )
2018-09-27 05:20:37 +08:00
else :
raise Exception ( " Failed to parse Xcode version " )
2015-11-11 21:12:35 +08:00
class Builder :
2017-01-21 00:16:01 +08:00
def __init__ ( self , opencv , contrib , dynamic , bitcodedisabled , exclude , targets ) :
2015-11-11 21:12:35 +08:00
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 )
2017-01-21 00:16:01 +08:00
self . dynamic = dynamic
self . bitcodedisabled = bitcodedisabled
2016-09-29 05:32:23 +08:00
self . exclude = exclude
2015-12-16 22:28:03 +08:00
self . targets = targets
2015-11-11 21:12:35 +08:00
def getBD ( self , parent , t ) :
2017-01-21 00:16:01 +08:00
if len ( t [ 0 ] ) == 1 :
res = os . path . join ( parent , ' build- %s - %s ' % ( t [ 0 ] [ 0 ] . lower ( ) , t [ 1 ] . lower ( ) ) )
else :
res = os . path . join ( parent , ' build- %s ' % t [ 1 ] . lower ( ) )
2015-11-11 21:12:35 +08:00
if not os . path . isdir ( res ) :
os . makedirs ( res )
return os . path . abspath ( res )
2015-12-16 22:28:03 +08:00
def _build ( self , outdir ) :
2015-11-11 21:12:35 +08:00
outdir = os . path . abspath ( outdir )
if not os . path . isdir ( outdir ) :
os . makedirs ( outdir )
mainWD = os . path . join ( outdir , " build " )
dirs = [ ]
xcode_ver = getXCodeMajor ( )
2017-01-21 00:16:01 +08:00
if self . dynamic :
alltargets = self . targets
else :
# if we are building a static library, we must build each architecture separately
alltargets = [ ]
for t in self . targets :
for at in t [ 0 ] :
current = ( [ at ] , t [ 1 ] )
alltargets . append ( current )
for t in alltargets :
2015-11-11 21:12:35 +08:00
mainBD = self . getBD ( mainWD , t )
dirs . append ( mainBD )
2017-01-21 00:16:01 +08:00
2015-11-11 21:12:35 +08:00
cmake_flags = [ ]
if self . contrib :
cmake_flags . append ( " -DOPENCV_EXTRA_MODULES_PATH= %s " % self . contrib )
2017-01-21 00:16:01 +08:00
if xcode_ver > = 7 and t [ 1 ] == ' iPhoneOS ' and self . bitcodedisabled == False :
2015-11-11 21:12:35 +08:00
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 )
2017-01-21 00:16:01 +08:00
if self . dynamic == False :
self . mergeLibs ( mainBD )
2015-11-11 21:12:35 +08:00
self . makeFramework ( outdir , dirs )
2015-12-16 22:28:03 +08:00
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 ) :
2016-10-11 19:37:53 +08:00
return None
2015-12-16 22:28:03 +08:00
def getCMakeArgs ( self , arch , target ) :
2017-01-21 00:16:01 +08:00
2017-02-21 17:50:09 +08:00
args = [
" cmake " ,
" -GXcode " ,
" -DAPPLE_FRAMEWORK=ON " ,
" -DCMAKE_INSTALL_PREFIX=install " ,
" -DCMAKE_BUILD_TYPE=Release " ,
2018-09-04 06:40:16 +08:00
" -DOPENCV_INCLUDE_INSTALL_PATH=include " ,
" -DOPENCV_3P_LIB_INSTALL_PATH=lib/3rdparty "
2017-02-21 17:50:09 +08:00
] + ( [
" -DBUILD_SHARED_LIBS=ON " ,
" -DCMAKE_MACOSX_BUNDLE=ON " ,
" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=NO " ,
] if self . dynamic else [ ] )
2016-09-29 05:32:23 +08:00
if len ( self . exclude ) > 0 :
2017-02-21 17:50:40 +08:00
args + = [ " -DBUILD_opencv_world=OFF " ] if not self . dynamic else [ ]
2017-02-21 17:50:09 +08:00
args + = [ " -DBUILD_opencv_ %s =OFF " % m for m in self . exclude ]
2016-09-29 05:32:23 +08:00
2015-12-16 22:28:03 +08:00
return args
2017-01-21 00:16:01 +08:00
def getBuildCommand ( self , archs , target ) :
2017-02-21 17:50:09 +08:00
buildcmd = [
" xcodebuild " ,
]
2017-01-21 00:16:01 +08:00
if self . dynamic :
2017-02-21 17:50:09 +08:00
buildcmd + = [
2018-10-24 12:37:31 +08:00
" IPHONEOS_DEPLOYMENT_TARGET= " + os . environ [ ' IPHONEOS_DEPLOYMENT_TARGET ' ] ,
2017-01-21 00:16:01 +08:00
" ONLY_ACTIVE_ARCH=NO " ,
]
2018-03-13 21:09:26 +08:00
if not self . bitcodedisabled :
buildcmd . append ( " BITCODE_GENERATION_MODE=bitcode " )
2017-01-21 00:16:01 +08:00
for arch in archs :
buildcmd . append ( " -arch " )
buildcmd . append ( arch . lower ( ) )
else :
arch = " ; " . join ( archs )
2017-02-21 17:50:09 +08:00
buildcmd + = [
2018-10-24 12:37:31 +08:00
" IPHONEOS_DEPLOYMENT_TARGET= " + os . environ [ ' IPHONEOS_DEPLOYMENT_TARGET ' ] ,
2017-01-21 00:16:01 +08:00
" ARCHS= %s " % arch ,
2017-02-21 17:50:09 +08:00
]
buildcmd + = [
2017-01-21 00:16:01 +08:00
" -sdk " , target . lower ( ) ,
" -configuration " , " Release " ,
" -parallelizeTargets " ,
2017-05-02 23:59:37 +08:00
" -jobs " , str ( multiprocessing . cpu_count ( ) ) ,
2017-02-21 17:50:09 +08:00
] + ( [ " -target " , " ALL_BUILD " ] if self . dynamic else [ ] )
2017-01-21 00:16:01 +08:00
2015-12-16 22:28:03 +08:00
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 [ ] )
2017-01-21 00:16:01 +08:00
if target . lower ( ) . startswith ( " iphoneos " ) :
2018-10-24 12:37:31 +08:00
cmakecmd . append ( " -DCPU_BASELINE=DETECT " )
2015-12-16 22:28:03 +08:00
cmakecmd . append ( self . opencv )
cmakecmd . extend ( cmakeargs )
execute ( cmakecmd , cwd = builddir )
2017-01-21 00:16:01 +08:00
2015-12-16 22:28:03 +08:00
# 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 )
2015-11-11 21:12:35 +08:00
execute ( buildcmd + [ " -target " , " ALL_BUILD " , " build " ] , cwd = builddir )
2015-12-16 22:28:03 +08:00
execute ( [ " cmake " , " -P " , " cmake_install.cmake " ] , cwd = builddir )
2015-11-11 21:12:35 +08:00
def mergeLibs ( self , builddir ) :
res = os . path . join ( builddir , " lib " , " Release " , " libopencv_merged.a " )
2015-12-16 22:28:03 +08:00
libs = glob . glob ( os . path . join ( builddir , " install " , " lib " , " *.a " ) )
2018-09-04 06:40:16 +08:00
libs3 = glob . glob ( os . path . join ( builddir , " install " , " lib " , " 3rdparty " , " *.a " ) )
2015-11-11 21:12:35 +08:00
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 "
# 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 )
2017-01-21 00:16:01 +08:00
if self . dynamic :
dstdir = framework_dir
libname = " opencv2.framework/opencv2 "
else :
dstdir = os . path . join ( framework_dir , " Versions " , " A " )
libname = " libopencv_merged.a "
2015-11-11 21:12:35 +08:00
# 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 )
2017-01-21 00:16:01 +08:00
# dynamic framework has different structure, just copy the Plist directly
if self . dynamic :
resdir = dstdir
shutil . copyfile ( self . getInfoPlist ( builddirs ) , os . path . join ( resdir , " Info.plist " ) )
else :
# 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 )
2012-06-28 23:13:57 +08:00
2016-10-11 19:37:53 +08:00
class iOSBuilder ( Builder ) :
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 ) :
2017-01-21 00:16:01 +08:00
arch = " ; " . join ( arch )
2016-10-11 19:37:53 +08:00
args = Builder . getCMakeArgs ( self , arch , target )
args = args + [
' -DIOS_ARCH= %s ' % arch
]
return args
2012-06-28 23:13:57 +08:00
if __name__ == " __main__ " :
2015-11-11 21:12:35 +08:00
folder = os . path . abspath ( os . path . join ( os . path . dirname ( sys . argv [ 0 ] ) , " ../.. " ) )
2014-12-05 22:48:28 +08:00
parser = argparse . ArgumentParser ( description = ' The script builds OpenCV.framework for iOS. ' )
2015-11-11 21:12:35 +08:00
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) ' )
2016-09-29 05:32:23 +08:00
parser . add_argument ( ' --without ' , metavar = ' MODULE ' , default = [ ] , action = ' append ' , help = ' OpenCV modules to exclude from the framework ' )
2017-01-21 00:16:01 +08:00
parser . add_argument ( ' --dynamic ' , default = False , action = ' store_true ' , help = ' build dynamic framework (default is " False " - builds static framework) ' )
parser . add_argument ( ' --disable-bitcode ' , default = False , dest = ' bitcodedisabled ' , action = ' store_true ' , help = ' disable bitcode (enabled by default) ' )
2018-10-24 12:37:31 +08:00
parser . add_argument ( ' --iphoneos_deployment_target ' , default = os . environ . get ( ' IPHONEOS_DEPLOYMENT_TARGET ' , IPHONEOS_DEPLOYMENT_TARGET ) , help = ' specify IPHONEOS_DEPLOYMENT_TARGET ' )
parser . add_argument ( ' --iphoneos_archs ' , default = ' armv7,armv7s,arm64 ' , help = ' select iPhoneOS target ARCHS ' )
2014-12-05 22:48:28 +08:00
args = parser . parse_args ( )
2018-10-24 12:37:31 +08:00
os . environ [ ' IPHONEOS_DEPLOYMENT_TARGET ' ] = args . iphoneos_deployment_target
print ( ' Using IPHONEOS_DEPLOYMENT_TARGET= ' + os . environ [ ' IPHONEOS_DEPLOYMENT_TARGET ' ] )
iphoneos_archs = args . iphoneos_archs . split ( ' , ' )
print ( ' Using iPhoneOS ARCHS= ' + str ( iphoneos_archs ) )
2017-01-21 00:16:01 +08:00
b = iOSBuilder ( args . opencv , args . contrib , args . dynamic , args . bitcodedisabled , args . without ,
2016-08-11 18:47:04 +08:00
[
2018-10-24 12:37:31 +08:00
( iphoneos_archs , " iPhoneOS " ) ,
2016-08-11 18:47:04 +08:00
] if os . environ . get ( ' BUILD_PRECOMMIT ' , None ) else
2015-12-16 22:28:03 +08:00
[
2018-10-24 12:37:31 +08:00
( iphoneos_archs , " iPhoneOS " ) ,
2017-01-21 00:16:01 +08:00
( [ " i386 " , " x86_64 " ] , " iPhoneSimulator " ) ,
2015-12-16 22:28:03 +08:00
] )
b . build ( args . out )