Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support xcframework for mac catalyst builds. #19534

Merged
merged 24 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmake/adjust_global_compile_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Android")
string(APPEND CMAKE_ASM_FLAGS_RELEASE " -O3")
endif()

# Suggested by https://gitlab.kitware.com/cmake/cmake/-/issues/20132
# MacCatalyst is not well supported in CMake
# The error that can emerge without this flag can look like:
# "clang : error : overriding '-mmacosx-version-min=11.0' option with '-target x86_64-apple-ios14.0-macabi' [-Werror,-Woverriding-t-option]"
if (PLATFORM_NAME STREQUAL "macabi")
add_compile_options(-Wno-overriding-t-option)
add_link_options(-Wno-overriding-t-option)
endif()

# Enable space optimization for gcc/clang
# Cannot use "-ffunction-sections -fdata-sections" if we enable bitcode (iOS)
if (NOT MSVC AND NOT onnxruntime_ENABLE_BITCODE)
Expand Down
72 changes: 72 additions & 0 deletions cmake/maccatalyst_prepare_objects_for_prelink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os
import shutil
import sys


# Note: This script is mainly used for sanity checking/validating the files in the .a library equal to the .o files
# in the source dir to handle the case of source files having duplicate names under different subdirectories for
# each onnxruntime library. (Only applicable when doing a Mac Catalyst build.)
def main():
source_dir = sys.argv[1]
dest_dir = sys.argv[2]
files_from_static_lib = sys.argv[3]
files_from_source_dir = []
for subdir, _, files in os.walk(source_dir):
for file_name in files:
if file_name.endswith(".o"):
files_from_source_dir.append(file_name.strip())
dest_name_without_extension, _ = os.path.splitext(file_name)
counter = 0

dest_file = f"{dest_name_without_extension}.o"
while os.path.exists(os.path.join(dest_dir, dest_file)):
print("Duplicate file name from source: " + os.path.join(source_dir, subdir, file_name))
counter += 1
dest_file = f"{dest_name_without_extension}_{counter}.o"
print("Renamed file name in destination: " + os.path.join(dest_dir, dest_file))

destination_path = os.path.join(dest_dir, dest_file)
source_file = os.path.join(source_dir, subdir, file_name)
shutil.copy(source_file, destination_path)

# Sanity check to ensure the number of .o object from the original cmake source directory matches with the number
# of .o files extracted from each .a onnxruntime library
file_lists_from_static_lib = []
with open(files_from_static_lib) as file:
filenames = file.readlines()
for filename in filenames:
file_lists_from_static_lib.append(filename.strip())

sorted_list1 = sorted(file_lists_from_static_lib)
sorted_list2 = sorted(files_from_source_dir)

if len(sorted_list1) != len(sorted_list2):
print(
"Caught a mismatch in the number of .o object files from the original cmake source directory: ",
len(sorted_list1),
"the number of .o files extracted from the static onnxruntime lib: ",
len(sorted_list2),
"for: ",
os.path.basename(source_dir),
)

if sorted_list1 == sorted_list2:
print(
"Sanity check passed: object files from original source directory matches with files extracted "
"from static library for: ",
os.path.basename(source_dir),
)
else:
print(
"Error: Mismatch between object files from original source directory "
"and the .o files extracted from static library for: ",
os.path.basename(source_dir),
)


if __name__ == "__main__":
main()
36 changes: 29 additions & 7 deletions cmake/onnxruntime.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,13 @@ endif()

# Assemble the Apple static framework (iOS and macOS)
if(onnxruntime_BUILD_APPLE_FRAMEWORK)
set(STATIC_FRAMEWORK_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}-${CMAKE_OSX_SYSROOT})
# when building for mac catalyst, the CMAKE_OSX_SYSROOT is set to MacOSX as well, to avoid duplication,
# we specify as `-macabi` in the name of the output static apple framework directory.
if (PLATFORM_NAME STREQUAL "macabi")
set(STATIC_FRAMEWORK_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}-macabi)
else()
set(STATIC_FRAMEWORK_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}-${CMAKE_OSX_SYSROOT})
endif()
YUNQIUGUO marked this conversation as resolved.
Show resolved Hide resolved

# Setup the various directories required. Remove any existing ones so we start with a clean directory.
set(STATIC_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/static_libraries)
Expand All @@ -299,18 +305,34 @@ if(onnxruntime_BUILD_APPLE_FRAMEWORK)
# to enforce symbol visibility. doing it this way limits the symbols included from the .a files to symbols used
# by the ORT .o files.

# If it's an onnxruntime library, extract .o files to a separate directory for each library to avoid any clashes
# with filenames (e.g. utils.o)
# If it's an onnxruntime library, extract .o files from the original cmake build path to a separate directory for
# each library to avoid any clashes with filenames (e.g. utils.o)
foreach(_LIB ${onnxruntime_INTERNAL_LIBRARIES} )
GET_TARGET_PROPERTY(_LIB_TYPE ${_LIB} TYPE)
if(_LIB_TYPE STREQUAL "STATIC_LIBRARY")
set(CUR_STATIC_LIB_OBJ_DIR ${STATIC_LIB_TEMP_DIR}/$<TARGET_LINKER_FILE_BASE_NAME:${_LIB}>)
add_custom_command(TARGET onnxruntime POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CUR_STATIC_LIB_OBJ_DIR})

add_custom_command(TARGET onnxruntime POST_BUILD
COMMAND ar ARGS -x $<TARGET_FILE:${_LIB}>
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
if (PLATFORM_NAME STREQUAL "macabi")
# There exists several duplicate names for source files under different subdirectories within
# each onnxruntime library. (e.g. onnxruntime/contrib_ops/cpu/element_wise_ops.o
# vs. onnxruntime/providers/core/cpu/math/element_wise_ops.o)
# In that case, using 'ar ARGS -x' to extract the .o files from .a lib would possibly cause duplicate naming files being overwritten
# and lead to missing undefined symbol error in the generated binary.
# So we use the below python script as a sanity check to do a recursive find of all .o files in ${CUR_TARGET_CMAKE_SOURCE_LIB_DIR}
# and verifies that matches the content of the .a, and then copy from the source dir.
# TODO: The copying action here isn't really necessary. For future fix, consider using the script extracts from the ar with the rename to potentially
# make both maccatalyst and other builds do the same thing.
set(CUR_TARGET_CMAKE_SOURCE_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_LIB}.dir)
add_custom_command(TARGET onnxruntime POST_BUILD
COMMAND ar -t $<TARGET_FILE:${_LIB}> | grep "\.o$" > ${_LIB}.object_file_list.txt
COMMAND ${CMAKE_COMMAND} -E env python3 ${CMAKE_CURRENT_SOURCE_DIR}/maccatalyst_prepare_objects_for_prelink.py ${CUR_TARGET_CMAKE_SOURCE_LIB_DIR} ${CUR_STATIC_LIB_OBJ_DIR} ${CUR_STATIC_LIB_OBJ_DIR}/${_LIB}.object_file_list.txt
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
else()
add_custom_command(TARGET onnxruntime POST_BUILD
COMMAND ar ARGS -x $<TARGET_FILE:${_LIB}>
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
endif()
endif()
endforeach()

Expand Down
6 changes: 6 additions & 0 deletions cmake/onnxruntime_mlas.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,12 @@ if (WIN32)
endif()
endif()

if (PLATFORM_NAME STREQUAL "macabi")
# Needed for maccatalyst C compilation
# i.e. the flags below add "--target=x86_64-apple-ios14.0-macabi -ffunction-sections -fdata-sections"
target_compile_options(onnxruntime_mlas PRIVATE ${CMAKE_C_FLAGS})
endif()

if (NOT onnxruntime_BUILD_SHARED_LIB)
install(TARGETS onnxruntime_mlas
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
229E595826586B4A006E41AE /* sigmoid.ort */ = {isa = PBXFileReference; lastKnownFileType = file; path = sigmoid.ort; sourceTree = "<group>"; };
22C1D8DE271A79AF002CEE67 /* ios_package_testUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ios_package_testUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
22C1D8E9271A79FD002CEE67 /* ios_package_uitest_cpp_api.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ios_package_uitest_cpp_api.mm; sourceTree = "<group>"; };
513C65792B85789400E4EDFD /* ios_package_test.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ios_package_test.entitlements; sourceTree = "<group>"; };
51C316B92B0881450033C70B /* macos_package_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = macos_package_test.app; sourceTree = BUILT_PRODUCTS_DIR; };
51C316BB2B0881450033C70B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
51C316BC2B0881450033C70B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -117,6 +118,7 @@
229E591E265869BF006E41AE /* ios_package_test */ = {
isa = PBXGroup;
children = (
513C65792B85789400E4EDFD /* ios_package_test.entitlements */,
229E591F265869BF006E41AE /* AppDelegate.h */,
229E5920265869BF006E41AE /* AppDelegate.m */,
229E5928265869BF006E41AE /* Main.storyboard */,
Expand Down Expand Up @@ -521,18 +523,21 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CODE_SIGNING_REQUIRED = NO;
CODE_SIGNING_STYLE = Automatic;
CODE_SIGN_ENTITLEMENTS = ios_package_test/ios_package_test.entitlements;
INFOPLIST_FILE = ios_package_test/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "ai.onnxruntime.tests.ios-package-test";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
Expand All @@ -541,18 +546,21 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CODE_SIGNING_REQUIRED = NO;
CODE_SIGNING_STYLE = Automatic;
CODE_SIGN_ENTITLEMENTS = ios_package_test/ios_package_test.entitlements;
INFOPLIST_FILE = ios_package_test/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "ai.onnxruntime.tests.ios-package-test";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
TARGETED_DEVICE_FAMILY = 1;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
Expand All @@ -563,7 +571,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -585,7 +593,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
42 changes: 38 additions & 4 deletions tools/ci_build/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,12 @@ def convert_arg_line_to_args(self, arg_line):

parser.add_argument("--ios", action="store_true", help="build for ios")

parser.add_argument(
"--macos",
choices=["MacOSX", "Catalyst"],
YUNQIUGUO marked this conversation as resolved.
Show resolved Hide resolved
help="Specify the target platform for macOS build. Only specify this argument when --build_apple_framework is present.",
)

parser.add_argument(
"--apple_sysroot", default="", help="Specify the location name of the macOS platform SDK to be used"
)
Expand All @@ -419,7 +425,7 @@ def convert_arg_line_to_args(self, arg_line):
action="store_const",
const="Xcode",
dest="cmake_generator",
help="Use Xcode as cmake generator, this is only supported on MacOS. Equivalent to '--cmake_generator Xcode'.",
help="Use Xcode as cmake generator, this is only supported on MacOS. (non Catalyst build). Equivalent to '--cmake_generator Xcode'.",
)
parser.add_argument(
"--osx_arch",
Expand Down Expand Up @@ -1323,8 +1329,12 @@ def generate_build_tree(
if args.use_snpe:
cmake_args += ["-Donnxruntime_USE_SNPE=ON"]

if args.build_apple_framework or args.ios:
if not args.cmake_generator == "Xcode":
if args.macos or args.ios:
# Note: Xcode CMake generator doesn't have a good support for Mac Catalyst yet.
if args.macos == "Catalyst" and args.cmake_generator == "Xcode":
raise BuildError("Xcode CMake generator ('--cmake_generator Xcode') doesn't support Mac Catalyst build.")

if (args.ios or args.macos == "MacOSX") and not args.cmake_generator == "Xcode":
raise BuildError(
"iOS/MacOS framework build requires use of the Xcode CMake generator ('--cmake_generator Xcode')."
)
Expand All @@ -1342,19 +1352,37 @@ def generate_build_tree(
"iOS/MacOS framework build on MacOS canceled due to missing arguments: "
+ ", ".join(val for val, cond in zip(arg_names, needed_args) if not cond)
)
# note: this value is mainly used in framework_info.json file to specify the build osx type
platform_name = "macabi" if args.macos == "Catalyst" else args.apple_sysroot
cmake_args += [
"-Donnxruntime_BUILD_SHARED_LIB=ON",
"-DCMAKE_OSX_SYSROOT=" + args.apple_sysroot,
"-DCMAKE_OSX_DEPLOYMENT_TARGET=" + args.apple_deploy_target,
# we do not need protoc binary for ios cross build
"-Dprotobuf_BUILD_PROTOC_BINARIES=OFF",
"-DPLATFORM_NAME=" + platform_name,
]
if args.ios:
cmake_args += [
"-DCMAKE_SYSTEM_NAME=iOS",
"-DCMAKE_TOOLCHAIN_FILE="
+ (args.ios_toolchain_file if args.ios_toolchain_file else "../cmake/onnxruntime_ios.toolchain.cmake"),
]
# for catalyst build, we need to manually specify cflags for target e.g. x86_64-apple-ios14.0-macabi, etc.
# https://forums.developer.apple.com/forums/thread/122571
if args.macos == "Catalyst":
macabi_target = f"{args.osx_arch}-apple-ios{args.apple_deploy_target}-macabi"
cmake_args += [
"-DCMAKE_CXX_COMPILER_TARGET=" + macabi_target,
"-DCMAKE_C_COMPILER_TARGET=" + macabi_target,
"-DCMAKE_CC_COMPILER_TARGET=" + macabi_target,
f"-DCMAKE_CXX_FLAGS=--target={macabi_target}",
f"-DCMAKE_CXX_FLAGS_RELEASE=-O3 -DNDEBUG --target={macabi_target}",
f"-DCMAKE_C_FLAGS=--target={macabi_target}",
f"-DCMAKE_C_FLAGS_RELEASE=-O3 -DNDEBUG --target={macabi_target}",
f"-DCMAKE_CC_FLAGS=--target={macabi_target}",
f"-DCMAKE_CC_FLAGS_RELEASE=-O3 -DNDEBUG --target={macabi_target}",
]

if args.build_wasm:
emsdk_dir = os.path.join(cmake_dir, "external", "emsdk")
Expand Down Expand Up @@ -2740,7 +2768,13 @@ def main():
cmake_extra_args += ["-G", args.cmake_generator]

if is_macOS():
if not args.ios and not args.android and args.osx_arch == "arm64" and platform.machine() == "x86_64":
if (
not args.ios
and args.macos != "Catalyst"
and not args.android
and args.osx_arch == "arm64"
and platform.machine() == "x86_64"
Fixed Show fixed Hide fixed
):
if args.test:
log.warning("Cannot test ARM64 build on X86_64. Will skip test running after build.")
args.test = False
Expand Down
4 changes: 3 additions & 1 deletion tools/ci_build/github/apple/build_apple_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ def _build_for_apple_sysroot(
# Build binary for each arch, one by one
for current_arch in archs:
build_dir_current_arch = os.path.join(intermediates_dir, sysroot + "_" + current_arch)
# Use MacOS SDK for Catalyst builds
apple_sysroot = "macosx" if sysroot == "macabi" else sysroot
build_command = [
*base_build_command,
"--apple_sysroot=" + sysroot,
"--apple_sysroot=" + apple_sysroot,
"--osx_arch=" + current_arch,
"--build_dir=" + build_dir_current_arch,
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"--cmake_extra_defines=onnxruntime_BUILD_UNIT_TESTS=OFF"
],
"macosx": [
"--macos=MacOSX",
"--apple_deploy_target=11.0"
],
"iphoneos": [
Expand Down
Loading
Loading