opencv/platforms/apple/build_xcframework.py
Giles Payne 3d9cb5329c
Merge pull request #24136 from komakai:visionos_support
Add experimental support for Apple VisionOS platform #24136

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch

This is dependent on cmake support for VisionOs which is currently in progress.
Creating PR now to test that there are no regressions in iOS and macOS builds
2023-12-20 15:35:10 +03:00

157 lines
8.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
This script builds OpenCV into an xcframework compatible with the platforms
of your choice. Just run it and grab a snack; you'll be waiting a while.
"""
import sys, os, argparse, pathlib, traceback, contextlib, shutil
from cv_build_utils import execute, print_error, print_header, get_xcode_version, get_cmake_version
if __name__ == "__main__":
# Check for dependencies
assert sys.version_info >= (3, 6), "Python 3.6 or later is required! Current version is {}".format(sys.version_info)
# Need CMake 3.18.5/3.19 or later for a Silicon-related fix to building for the iOS Simulator.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/21425 for context.
assert get_cmake_version() >= (3, 18, 5), "CMake 3.18.5 or later is required. Current version is {}".format(get_cmake_version())
# Need Xcode 12.2 for Apple Silicon support
assert get_xcode_version() >= (12, 2), \
"Xcode 12.2 command line tools or later are required! Current version is {}. ".format(get_xcode_version()) + \
"Run xcode-select to switch if you have multiple Xcode installs."
# Parse arguments
description = """
This script builds OpenCV into an xcframework supporting the Apple platforms of your choice.
"""
epilog = """
Any arguments that are not recognized by this script are passed through to the ios/osx build_framework.py scripts.
"""
parser = argparse.ArgumentParser(description=description, epilog=epilog)
parser.add_argument('-o', '--out', metavar='OUTDIR', help='<Required> The directory where the xcframework will be created', required=True)
parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV xcframework (default: opencv2, will change to OpenCV in future version)')
parser.add_argument('--iphoneos_archs', default=None, help='select iPhoneOS target ARCHS. Default is "armv7,arm64"')
parser.add_argument('--iphonesimulator_archs', default=None, help='select iPhoneSimulator target ARCHS. Default is "x86_64,arm64"')
parser.add_argument('--visionos_archs', default=None, help='select visionOS target ARCHS. Default is "arm64"')
parser.add_argument('--visionsimulator_archs', default=None, help='select visionSimulator target ARCHS. Default is "arm64"')
parser.add_argument('--macos_archs', default=None, help='Select MacOS ARCHS. Default is "x86_64,arm64"')
parser.add_argument('--catalyst_archs', default=None, help='Select Catalyst ARCHS. Default is "x86_64,arm64"')
parser.add_argument('--build_only_specified_archs', default=False, action='store_true', help='if enabled, only directly specified archs are built and defaults are ignored')
args, unknown_args = parser.parse_known_args()
if unknown_args:
print("The following args are not recognized by this script and will be passed through to the ios/osx build_framework.py scripts: {}".format(unknown_args))
# Parse architectures from args
iphoneos_archs = args.iphoneos_archs
if not iphoneos_archs and not args.build_only_specified_archs:
# Supply defaults
iphoneos_archs = "armv7,arm64"
print('Using iPhoneOS ARCHS={}'.format(iphoneos_archs))
iphonesimulator_archs = args.iphonesimulator_archs
if not iphonesimulator_archs and not args.build_only_specified_archs:
# Supply defaults
iphonesimulator_archs = "x86_64,arm64"
print('Using iPhoneSimulator ARCHS={}'.format(iphonesimulator_archs))
# Parse architectures from args
visionos_archs = args.visionos_archs
print('Using visionOS ARCHS={}'.format(visionos_archs))
visionsimulator_archs = args.visionsimulator_archs
print('Using visionSimulator ARCHS={}'.format(visionsimulator_archs))
macos_archs = args.macos_archs
if not macos_archs and not args.build_only_specified_archs:
# Supply defaults
macos_archs = "x86_64,arm64"
print('Using MacOS ARCHS={}'.format(macos_archs))
catalyst_archs = args.catalyst_archs
if not catalyst_archs and not args.build_only_specified_archs:
# Supply defaults
catalyst_archs = "x86_64,arm64"
print('Using Catalyst ARCHS={}'.format(catalyst_archs))
# Build phase
try:
# Phase 1: build .frameworks for each platform
osx_script_path = os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../osx/build_framework.py')
ios_script_path = os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../ios/build_framework.py')
visionos_script_path = os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../ios/build_visionos_framework.py')
build_folders = []
def get_or_create_build_folder(base_dir, platform):
build_folder = "{}/{}".format(base_dir, platform).replace(" ", "\\ ") # Escape spaces in output path
pathlib.Path(build_folder).mkdir(parents=True, exist_ok=True)
return build_folder
if iphoneos_archs:
build_folder = get_or_create_build_folder(args.out, "iphoneos")
build_folders.append(build_folder)
command = ["python3", ios_script_path, build_folder, "--iphoneos_archs", iphoneos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
print_header("Building iPhoneOS frameworks")
print(command)
execute(command, cwd=os.getcwd())
if iphonesimulator_archs:
build_folder = get_or_create_build_folder(args.out, "iphonesimulator")
build_folders.append(build_folder)
command = ["python3", ios_script_path, build_folder, "--iphonesimulator_archs", iphonesimulator_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
print_header("Building iPhoneSimulator frameworks")
execute(command, cwd=os.getcwd())
if visionos_archs:
build_folder = get_or_create_build_folder(args.out, "visionos")
build_folders.append(build_folder)
command = ["python3", visionos_script_path, build_folder, "--visionos_archs", visionos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
print_header("Building visionOS frameworks")
print(command)
execute(command, cwd=os.getcwd())
if visionsimulator_archs:
build_folder = get_or_create_build_folder(args.out, "visionsimulator")
build_folders.append(build_folder)
command = ["python3", visionos_script_path, build_folder, "--visionsimulator_archs", visionsimulator_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
print_header("Building visionSimulator frameworks")
execute(command, cwd=os.getcwd())
if macos_archs:
build_folder = get_or_create_build_folder(args.out, "macos")
build_folders.append(build_folder)
command = ["python3", osx_script_path, build_folder, "--macos_archs", macos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
print_header("Building MacOS frameworks")
execute(command, cwd=os.getcwd())
if catalyst_archs:
build_folder = get_or_create_build_folder(args.out, "catalyst")
build_folders.append(build_folder)
command = ["python3", osx_script_path, build_folder, "--catalyst_archs", catalyst_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
print_header("Building Catalyst frameworks")
execute(command, cwd=os.getcwd())
# Phase 2: put all the built .frameworks together into a .xcframework
xcframework_path = "{}/{}.xcframework".format(args.out, args.framework_name)
print_header("Building {}".format(xcframework_path))
# Remove the xcframework if it exists, otherwise the existing
# file will cause the xcodebuild command to fail.
with contextlib.suppress(FileNotFoundError):
shutil.rmtree(xcframework_path)
print("Removed existing xcframework at {}".format(xcframework_path))
xcframework_build_command = [
"xcodebuild",
"-create-xcframework",
"-output",
xcframework_path,
]
for folder in build_folders:
xcframework_build_command += ["-framework", "{}/{}.framework".format(folder, args.framework_name)]
execute(xcframework_build_command, cwd=os.getcwd())
print("")
print_header("Finished building {}".format(xcframework_path))
except Exception as e:
print_error(e)
traceback.print_exc(file=sys.stderr)
sys.exit(1)