Merge pull request #18094 from komakai:macos-universal-binary
authorGiles Payne <gilespayne@gmail.com>
Sat, 29 Aug 2020 22:41:54 +0000 (07:41 +0900)
committerGitHub <noreply@github.com>
Sat, 29 Aug 2020 22:41:54 +0000 (01:41 +0300)
* Universal Build for Big Sur

* Refactor MacOS/iOS build to only ever build one architecture at a time + improve code readability

* Workaround for CMake issue 20989

CMakeLists.txt
platforms/ios/build_framework.py
platforms/osx/build_framework.py

index 3273eda..6e82d18 100644 (file)
@@ -103,7 +103,13 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ${ENABLE_PIC})
 ocv_cmake_hook(PRE_CMAKE_BOOTSTRAP)
 
 # Bootstap CMake system: setup CMAKE_SYSTEM_NAME and other vars
+if(OPENCV_WORKAROUND_CMAKE_20989)
+  set(CMAKE_SYSTEM_PROCESSOR_BACKUP ${CMAKE_SYSTEM_PROCESSOR})
+endif()
 enable_language(CXX C)
+if(OPENCV_WORKAROUND_CMAKE_20989)
+  set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR_BACKUP})
+endif()
 
 ocv_cmake_hook(POST_CMAKE_BOOTSTRAP)
 
index 1c1d52d..c623610 100755 (executable)
@@ -84,12 +84,9 @@ class Builder:
         self.run_tests = run_tests
         self.build_docs = build_docs
 
-    def getBD(self, parent, t):
+    def getBuildDir(self, parent, target):
 
-        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())
+        res = os.path.join(parent, 'build-%s-%s' % (target[0].lower(), target[1].lower()))
 
         if not os.path.isdir(res):
             os.makedirs(res)
@@ -99,39 +96,35 @@ class Builder:
         outdir = os.path.abspath(outdir)
         if not os.path.isdir(outdir):
             os.makedirs(outdir)
-        mainWD = os.path.join(outdir, "build")
+        main_working_dir = os.path.join(outdir, "build")
         dirs = []
 
         xcode_ver = getXCodeMajor()
 
-        if self.dynamic and not self.build_objc_wrapper:
-            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] )
+        # build each architecture separately
+        alltargets = []
 
-                    alltargets.append(current)
+        for target_group in self.targets:
+            for arch in target_group[0]:
+                current = ( arch, target_group[1] )
+                alltargets.append(current)
 
-        for t in alltargets:
-            mainBD = self.getBD(mainWD, t)
-            dirs.append(mainBD)
+        for target in alltargets:
+            main_build_dir = self.getBuildDir(main_working_dir, target)
+            dirs.append(main_build_dir)
 
             cmake_flags = []
             if self.contrib:
                 cmake_flags.append("-DOPENCV_EXTRA_MODULES_PATH=%s" % self.contrib)
-            if xcode_ver >= 7 and t[1] == 'iPhoneOS' and self.bitcodedisabled == False:
+            if xcode_ver >= 7 and target[1] == 'iPhoneOS' and self.bitcodedisabled == False:
                 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.buildOne(target[0], target[1], main_build_dir, cmake_flags)
 
             if not self.dynamic:
-                self.mergeLibs(mainBD)
-            elif self.dynamic and self.build_objc_wrapper:
-                self.makeDynamicLib(mainBD)
+                self.mergeLibs(main_build_dir)
+            else:
+                self.makeDynamicLib(main_build_dir)
         self.makeFramework(outdir, dirs)
         if self.build_objc_wrapper:
             if self.run_tests:
@@ -198,7 +191,7 @@ class Builder:
 
         return args
 
-    def getBuildCommand(self, archs, target):
+    def getBuildCommand(self, arch, target):
 
         buildcmd = [
             "xcodebuild",
@@ -207,28 +200,17 @@ class Builder:
         if (self.dynamic or self.build_objc_wrapper) and not self.bitcodedisabled and target == "iPhoneOS":
             buildcmd.append("BITCODE_GENERATION_MODE=bitcode")
 
-        if self.dynamic and not self.build_objc_wrapper:
-            buildcmd += [
-                "IPHONEOS_DEPLOYMENT_TARGET=" + os.environ['IPHONEOS_DEPLOYMENT_TARGET'],
-                "ONLY_ACTIVE_ARCH=NO",
-            ]
-
-            for arch in archs:
-                buildcmd.append("-arch")
-                buildcmd.append(arch.lower())
-        else:
-            arch = ";".join(archs)
-            buildcmd += [
-                "IPHONEOS_DEPLOYMENT_TARGET=" + os.environ['IPHONEOS_DEPLOYMENT_TARGET'],
-                "ARCHS=%s" % arch,
-            ]
+        buildcmd += [
+            "IPHONEOS_DEPLOYMENT_TARGET=" + os.environ['IPHONEOS_DEPLOYMENT_TARGET'],
+            "ARCHS=%s" % arch,
+        ]
 
         buildcmd += [
                 "-sdk", target.lower(),
                 "-configuration", self.getConfiguration(),
                 "-parallelizeTargets",
                 "-jobs", str(multiprocessing.cpu_count()),
-            ] + (["-target","ALL_BUILD"] if self.dynamic and not self.build_objc_wrapper else [])
+            ]
 
         return buildcmd
 
@@ -241,6 +223,15 @@ class Builder:
             (["-DCMAKE_TOOLCHAIN_FILE=%s" % toolchain] if toolchain is not None else [])
         if target.lower().startswith("iphoneos"):
             cmakecmd.append("-DCPU_BASELINE=DETECT")
+        if target.lower() == "macosx":
+            build_arch = check_output(["uname", "-m"]).rstrip()
+            if build_arch != arch:
+                cmakecmd.append("-DCMAKE_SYSTEM_PROCESSOR=" + arch)
+                cmakecmd.append("-DCMAKE_OSX_ARCHITECTURES=" + arch)
+                cmakecmd.append("-DCPU_BASELINE=DETECT")
+                cmakecmd.append("-DCMAKE_CROSSCOMPILING=ON")
+                cmakecmd.append("-DOPENCV_WORKAROUND_CMAKE_20989=ON")
+
         cmakecmd.append(dir)
         cmakecmd.extend(cmakeargs)
         return cmakecmd
@@ -344,6 +335,7 @@ class Builder:
                     "x86_64": "x86_64-apple-ios-simulator",
                 } if builddirs[0].find("iphone") != -1 else {
                     "x86_64": "x86_64-apple-macos",
+                    "arm64": "arm64-apple-macos",
                 }
             for d in builddirs:
                 copy_tree(os.path.join(d, "install", "lib", name + ".framework", "Modules"), os.path.join(dstdir, "Modules"))
@@ -398,8 +390,6 @@ class iOSBuilder(Builder):
         return toolchain
 
     def getCMakeArgs(self, arch, target):
-        arch = ";".join(arch)
-
         args = Builder.getCMakeArgs(self, arch, target)
         args = args + [
             '-DIOS_ARCH=%s' % arch
index 14c5926..ccca582 100755 (executable)
@@ -17,11 +17,11 @@ class OSXBuilder(Builder):
     def getToolchain(self, arch, target):
         return None
 
-    def getBuildCommand(self, archs, target):
+    def getBuildCommand(self, arch, target):
         buildcmd = [
             "xcodebuild",
             "MACOSX_DEPLOYMENT_TARGET=" + os.environ['MACOSX_DEPLOYMENT_TARGET'],
-            "ARCHS=%s" % archs[0],
+            "ARCHS=%s" % arch,
             "-sdk", target.lower(),
             "-configuration", "Debug" if self.debug else "Release",
             "-parallelizeTargets",
@@ -43,6 +43,7 @@ if __name__ == "__main__":
     parser.add_argument('--disable', metavar='FEATURE', default=[], action='append', help='OpenCV features to disable (add WITH_*=OFF)')
     parser.add_argument('--enable_nonfree', default=False, dest='enablenonfree', action='store_true', help='enable non-free modules (disabled by default)')
     parser.add_argument('--macosx_deployment_target', default=os.environ.get('MACOSX_DEPLOYMENT_TARGET', MACOSX_DEPLOYMENT_TARGET), help='specify MACOSX_DEPLOYMENT_TARGET')
+    parser.add_argument('--archs', default='x86_64', help='Select target ARCHS (set to "x86_64,arm64" to build Universal Binary for Big Sur and later)')
     parser.add_argument('--debug', action='store_true', help='Build "Debug" binaries (CMAKE_BUILD_TYPE=Debug)')
     parser.add_argument('--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)')
@@ -54,6 +55,9 @@ if __name__ == "__main__":
 
     os.environ['MACOSX_DEPLOYMENT_TARGET'] = args.macosx_deployment_target
     print('Using MACOSX_DEPLOYMENT_TARGET=' + os.environ['MACOSX_DEPLOYMENT_TARGET'])
+    archs = args.archs.split(',')
+    print('Using ARCHS=' + str(archs))
+
     if args.legacy_build:
         args.framework_name = "opencv2"
         if not "objc" in args.without:
@@ -61,6 +65,6 @@ if __name__ == "__main__":
 
     b = OSXBuilder(args.opencv, args.contrib, False, False, args.without, args.disable, args.enablenonfree,
         [
-            (["x86_64"], "MacOSX")
+            (archs, "MacOSX")
         ], args.debug, args.debug_info, args.framework_name, args.run_tests, args.build_docs)
     b.build(args.out)