build_xcframework.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #!/usr/bin/env python3
  2. """
  3. This script builds OpenCV into an xcframework compatible with the platforms
  4. of your choice. Just run it and grab a snack; you'll be waiting a while.
  5. """
  6. import sys, os, argparse, pathlib, traceback, contextlib, shutil
  7. from cv_build_utils import execute, print_error, print_header, get_xcode_version, get_cmake_version
  8. if __name__ == "__main__":
  9. # Check for dependencies
  10. assert sys.version_info >= (3, 6), "Python 3.6 or later is required! Current version is {}".format(sys.version_info)
  11. # Need CMake 3.18.5/3.19 or later for a Silicon-related fix to building for the iOS Simulator.
  12. # See https://gitlab.kitware.com/cmake/cmake/-/issues/21425 for context.
  13. assert get_cmake_version() >= (3, 18, 5), "CMake 3.18.5 or later is required. Current version is {}".format(get_cmake_version())
  14. # Need Xcode 12.2 for Apple Silicon support
  15. assert get_xcode_version() >= (12, 2), \
  16. "Xcode 12.2 command line tools or later are required! Current version is {}. ".format(get_xcode_version()) + \
  17. "Run xcode-select to switch if you have multiple Xcode installs."
  18. # Parse arguments
  19. description = """
  20. This script builds OpenCV into an xcframework supporting the Apple platforms of your choice.
  21. """
  22. epilog = """
  23. Any arguments that are not recognized by this script are passed through to the ios/osx build_framework.py scripts.
  24. """
  25. parser = argparse.ArgumentParser(description=description, epilog=epilog)
  26. parser.add_argument('-o', '--out', metavar='OUTDIR', help='<Required> The directory where the xcframework will be created', required=True)
  27. parser.add_argument('--framework_name', default='opencv2', help='Name of OpenCV xcframework (default: opencv2, will change to OpenCV in future version)')
  28. parser.add_argument('--iphoneos_archs', default=None, help='select iPhoneOS target ARCHS. Default is "armv7,arm64"')
  29. parser.add_argument('--iphonesimulator_archs', default=None, help='select iPhoneSimulator target ARCHS. Default is "x86_64,arm64"')
  30. parser.add_argument('--macos_archs', default=None, help='Select MacOS ARCHS. Default is "x86_64,arm64"')
  31. parser.add_argument('--catalyst_archs', default=None, help='Select Catalyst ARCHS. Default is "x86_64,arm64"')
  32. 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')
  33. args, unknown_args = parser.parse_known_args()
  34. if unknown_args:
  35. 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))
  36. # Parse architectures from args
  37. iphoneos_archs = args.iphoneos_archs
  38. if not iphoneos_archs and not args.build_only_specified_archs:
  39. # Supply defaults
  40. iphoneos_archs = "armv7,arm64"
  41. print('Using iPhoneOS ARCHS={}'.format(iphoneos_archs))
  42. iphonesimulator_archs = args.iphonesimulator_archs
  43. if not iphonesimulator_archs and not args.build_only_specified_archs:
  44. # Supply defaults
  45. iphonesimulator_archs = "x86_64,arm64"
  46. print('Using iPhoneSimulator ARCHS={}'.format(iphonesimulator_archs))
  47. macos_archs = args.macos_archs
  48. if not macos_archs and not args.build_only_specified_archs:
  49. # Supply defaults
  50. macos_archs = "x86_64,arm64"
  51. print('Using MacOS ARCHS={}'.format(macos_archs))
  52. catalyst_archs = args.catalyst_archs
  53. if not catalyst_archs and not args.build_only_specified_archs:
  54. # Supply defaults
  55. catalyst_archs = "x86_64,arm64"
  56. print('Using Catalyst ARCHS={}'.format(catalyst_archs))
  57. # Build phase
  58. try:
  59. # Phase 1: build .frameworks for each platform
  60. osx_script_path = os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../osx/build_framework.py')
  61. ios_script_path = os.path.abspath(os.path.abspath(os.path.dirname(__file__))+'/../ios/build_framework.py')
  62. build_folders = []
  63. def get_or_create_build_folder(base_dir, platform):
  64. build_folder = "{}/{}".format(base_dir, platform).replace(" ", "\\ ") # Escape spaces in output path
  65. pathlib.Path(build_folder).mkdir(parents=True, exist_ok=True)
  66. return build_folder
  67. if iphoneos_archs:
  68. build_folder = get_or_create_build_folder(args.out, "iphoneos")
  69. build_folders.append(build_folder)
  70. command = ["python3", ios_script_path, build_folder, "--iphoneos_archs", iphoneos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
  71. print_header("Building iPhoneOS frameworks")
  72. print(command)
  73. execute(command, cwd=os.getcwd())
  74. if iphonesimulator_archs:
  75. build_folder = get_or_create_build_folder(args.out, "iphonesimulator")
  76. build_folders.append(build_folder)
  77. command = ["python3", ios_script_path, build_folder, "--iphonesimulator_archs", iphonesimulator_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
  78. print_header("Building iPhoneSimulator frameworks")
  79. execute(command, cwd=os.getcwd())
  80. if macos_archs:
  81. build_folder = get_or_create_build_folder(args.out, "macos")
  82. build_folders.append(build_folder)
  83. command = ["python3", osx_script_path, build_folder, "--macos_archs", macos_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
  84. print_header("Building MacOS frameworks")
  85. execute(command, cwd=os.getcwd())
  86. if catalyst_archs:
  87. build_folder = get_or_create_build_folder(args.out, "catalyst")
  88. build_folders.append(build_folder)
  89. command = ["python3", osx_script_path, build_folder, "--catalyst_archs", catalyst_archs, "--framework_name", args.framework_name, "--build_only_specified_archs"] + unknown_args
  90. print_header("Building Catalyst frameworks")
  91. execute(command, cwd=os.getcwd())
  92. # Phase 2: put all the built .frameworks together into a .xcframework
  93. xcframework_path = "{}/{}.xcframework".format(args.out, args.framework_name)
  94. print_header("Building {}".format(xcframework_path))
  95. # Remove the xcframework if it exists, otherwise the existing
  96. # file will cause the xcodebuild command to fail.
  97. with contextlib.suppress(FileNotFoundError):
  98. shutil.rmtree(xcframework_path)
  99. print("Removed existing xcframework at {}".format(xcframework_path))
  100. xcframework_build_command = [
  101. "xcodebuild",
  102. "-create-xcframework",
  103. "-output",
  104. xcframework_path,
  105. ]
  106. for folder in build_folders:
  107. xcframework_build_command += ["-framework", "{}/{}.framework".format(folder, args.framework_name)]
  108. execute(xcframework_build_command, cwd=os.getcwd())
  109. print("")
  110. print_header("Finished building {}".format(xcframework_path))
  111. except Exception as e:
  112. print_error(e)
  113. traceback.print_exc(file=sys.stderr)
  114. sys.exit(1)