Skip to content

Commit 6b305f9

Browse files
YUNQIUGUOrachguoskottmckay
authored
Support xcframework for mac catalyst builds. (microsoft#19534)
### Description <!-- Describe your changes. --> ### Motivation and Context <!-- - Why is this change required? What problem does it solve? - If it fixes an open issue, please link to the issue here. --> MAUI on macOS uses mac-catalyst which requires a different native binary. --------- Co-authored-by: rachguo <[email protected]> Co-authored-by: Scott McKay <[email protected]>
1 parent 19ff4a6 commit 6b305f9

14 files changed

+225
-24
lines changed

cmake/adjust_global_compile_flags.cmake

+9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Android")
88
string(APPEND CMAKE_ASM_FLAGS_RELEASE " -O3")
99
endif()
1010

11+
# Suggested by https://gitlab.kitware.com/cmake/cmake/-/issues/20132
12+
# MacCatalyst is not well supported in CMake
13+
# The error that can emerge without this flag can look like:
14+
# "clang : error : overriding '-mmacosx-version-min=11.0' option with '-target x86_64-apple-ios14.0-macabi' [-Werror,-Woverriding-t-option]"
15+
if (PLATFORM_NAME STREQUAL "macabi")
16+
add_compile_options(-Wno-overriding-t-option)
17+
add_link_options(-Wno-overriding-t-option)
18+
endif()
19+
1120
# Enable space optimization for gcc/clang
1221
# Cannot use "-ffunction-sections -fdata-sections" if we enable bitcode (iOS)
1322
if (NOT MSVC AND NOT onnxruntime_ENABLE_BITCODE)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License.
4+
5+
import os
6+
import shutil
7+
import sys
8+
9+
10+
# Note: This script is mainly used for sanity checking/validating the files in the .a library equal to the .o files
11+
# in the source dir to handle the case of source files having duplicate names under different subdirectories for
12+
# each onnxruntime library. (Only applicable when doing a Mac Catalyst build.)
13+
def main():
14+
source_dir = sys.argv[1]
15+
dest_dir = sys.argv[2]
16+
files_from_static_lib = sys.argv[3]
17+
files_from_source_dir = []
18+
for subdir, _, files in os.walk(source_dir):
19+
for file_name in files:
20+
if file_name.endswith(".o"):
21+
files_from_source_dir.append(file_name.strip())
22+
dest_name_without_extension, _ = os.path.splitext(file_name)
23+
counter = 0
24+
25+
dest_file = f"{dest_name_without_extension}.o"
26+
while os.path.exists(os.path.join(dest_dir, dest_file)):
27+
print("Duplicate file name from source: " + os.path.join(source_dir, subdir, file_name))
28+
counter += 1
29+
dest_file = f"{dest_name_without_extension}_{counter}.o"
30+
print("Renamed file name in destination: " + os.path.join(dest_dir, dest_file))
31+
32+
destination_path = os.path.join(dest_dir, dest_file)
33+
source_file = os.path.join(source_dir, subdir, file_name)
34+
shutil.copy(source_file, destination_path)
35+
36+
# Sanity check to ensure the number of .o object from the original cmake source directory matches with the number
37+
# of .o files extracted from each .a onnxruntime library
38+
file_lists_from_static_lib = []
39+
with open(files_from_static_lib) as file:
40+
filenames = file.readlines()
41+
for filename in filenames:
42+
file_lists_from_static_lib.append(filename.strip())
43+
44+
sorted_list1 = sorted(file_lists_from_static_lib)
45+
sorted_list2 = sorted(files_from_source_dir)
46+
47+
if len(sorted_list1) != len(sorted_list2):
48+
print(
49+
"Caught a mismatch in the number of .o object files from the original cmake source directory: ",
50+
len(sorted_list1),
51+
"the number of .o files extracted from the static onnxruntime lib: ",
52+
len(sorted_list2),
53+
"for: ",
54+
os.path.basename(source_dir),
55+
)
56+
57+
if sorted_list1 == sorted_list2:
58+
print(
59+
"Sanity check passed: object files from original source directory matches with files extracted "
60+
"from static library for: ",
61+
os.path.basename(source_dir),
62+
)
63+
else:
64+
print(
65+
"Error: Mismatch between object files from original source directory "
66+
"and the .o files extracted from static library for: ",
67+
os.path.basename(source_dir),
68+
)
69+
70+
71+
if __name__ == "__main__":
72+
main()

cmake/onnxruntime.cmake

+29-7
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,13 @@ endif()
281281

282282
# Assemble the Apple static framework (iOS and macOS)
283283
if(onnxruntime_BUILD_APPLE_FRAMEWORK)
284-
set(STATIC_FRAMEWORK_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}-${CMAKE_OSX_SYSROOT})
284+
# when building for mac catalyst, the CMAKE_OSX_SYSROOT is set to MacOSX as well, to avoid duplication,
285+
# we specify as `-macabi` in the name of the output static apple framework directory.
286+
if (PLATFORM_NAME STREQUAL "macabi")
287+
set(STATIC_FRAMEWORK_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}-macabi)
288+
else()
289+
set(STATIC_FRAMEWORK_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}-${CMAKE_OSX_SYSROOT})
290+
endif()
285291

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

302-
# If it's an onnxruntime library, extract .o files to a separate directory for each library to avoid any clashes
303-
# with filenames (e.g. utils.o)
308+
# If it's an onnxruntime library, extract .o files from the original cmake build path to a separate directory for
309+
# each library to avoid any clashes with filenames (e.g. utils.o)
304310
foreach(_LIB ${onnxruntime_INTERNAL_LIBRARIES} )
305311
GET_TARGET_PROPERTY(_LIB_TYPE ${_LIB} TYPE)
306312
if(_LIB_TYPE STREQUAL "STATIC_LIBRARY")
307313
set(CUR_STATIC_LIB_OBJ_DIR ${STATIC_LIB_TEMP_DIR}/$<TARGET_LINKER_FILE_BASE_NAME:${_LIB}>)
308314
add_custom_command(TARGET onnxruntime POST_BUILD
309315
COMMAND ${CMAKE_COMMAND} -E make_directory ${CUR_STATIC_LIB_OBJ_DIR})
310-
311-
add_custom_command(TARGET onnxruntime POST_BUILD
312-
COMMAND ar ARGS -x $<TARGET_FILE:${_LIB}>
313-
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
316+
if (PLATFORM_NAME STREQUAL "macabi")
317+
# There exists several duplicate names for source files under different subdirectories within
318+
# each onnxruntime library. (e.g. onnxruntime/contrib_ops/cpu/element_wise_ops.o
319+
# vs. onnxruntime/providers/core/cpu/math/element_wise_ops.o)
320+
# In that case, using 'ar ARGS -x' to extract the .o files from .a lib would possibly cause duplicate naming files being overwritten
321+
# and lead to missing undefined symbol error in the generated binary.
322+
# 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}
323+
# and verifies that matches the content of the .a, and then copy from the source dir.
324+
# 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
325+
# make both maccatalyst and other builds do the same thing.
326+
set(CUR_TARGET_CMAKE_SOURCE_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${_LIB}.dir)
327+
add_custom_command(TARGET onnxruntime POST_BUILD
328+
COMMAND ar -t $<TARGET_FILE:${_LIB}> | grep "\.o$" > ${_LIB}.object_file_list.txt
329+
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
330+
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
331+
else()
332+
add_custom_command(TARGET onnxruntime POST_BUILD
333+
COMMAND ar ARGS -x $<TARGET_FILE:${_LIB}>
334+
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
335+
endif()
314336
endif()
315337
endforeach()
316338

cmake/onnxruntime_mlas.cmake

+6
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,12 @@ if (WIN32)
631631
endif()
632632
endif()
633633

634+
if (PLATFORM_NAME STREQUAL "macabi")
635+
# Needed for maccatalyst C compilation
636+
# i.e. the flags below add "--target=x86_64-apple-ios14.0-macabi -ffunction-sections -fdata-sections"
637+
target_compile_options(onnxruntime_mlas PRIVATE ${CMAKE_C_FLAGS})
638+
endif()
639+
634640
if (NOT onnxruntime_BUILD_SHARED_LIB)
635641
install(TARGETS onnxruntime_mlas
636642
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}

onnxruntime/test/platform/apple/apple_package_test/apple_package_test.xcodeproj/project.pbxproj

+16-8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
229E595826586B4A006E41AE /* sigmoid.ort */ = {isa = PBXFileReference; lastKnownFileType = file; path = sigmoid.ort; sourceTree = "<group>"; };
5050
22C1D8DE271A79AF002CEE67 /* ios_package_testUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ios_package_testUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5151
22C1D8E9271A79FD002CEE67 /* ios_package_uitest_cpp_api.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ios_package_uitest_cpp_api.mm; sourceTree = "<group>"; };
52+
513C65792B85789400E4EDFD /* ios_package_test.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ios_package_test.entitlements; sourceTree = "<group>"; };
5253
51C316B92B0881450033C70B /* macos_package_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = macos_package_test.app; sourceTree = BUILT_PRODUCTS_DIR; };
5354
51C316BB2B0881450033C70B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
5455
51C316BC2B0881450033C70B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
@@ -117,6 +118,7 @@
117118
229E591E265869BF006E41AE /* ios_package_test */ = {
118119
isa = PBXGroup;
119120
children = (
121+
513C65792B85789400E4EDFD /* ios_package_test.entitlements */,
120122
229E591F265869BF006E41AE /* AppDelegate.h */,
121123
229E5920265869BF006E41AE /* AppDelegate.m */,
122124
229E5928265869BF006E41AE /* Main.storyboard */,
@@ -521,18 +523,21 @@
521523
buildSettings = {
522524
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
523525
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
524-
CODE_SIGN_STYLE = Automatic;
526+
CODE_SIGNING_REQUIRED = NO;
527+
CODE_SIGNING_STYLE = Automatic;
528+
CODE_SIGN_ENTITLEMENTS = ios_package_test/ios_package_test.entitlements;
525529
INFOPLIST_FILE = ios_package_test/Info.plist;
530+
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
526531
LD_RUNPATH_SEARCH_PATHS = (
527532
"$(inherited)",
528533
"@executable_path/Frameworks",
529534
);
530535
PRODUCT_BUNDLE_IDENTIFIER = "ai.onnxruntime.tests.ios-package-test";
531536
PRODUCT_NAME = "$(TARGET_NAME)";
532537
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
533-
SUPPORTS_MACCATALYST = NO;
538+
SUPPORTS_MACCATALYST = YES;
534539
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
535-
TARGETED_DEVICE_FAMILY = 1;
540+
TARGETED_DEVICE_FAMILY = "1,2";
536541
};
537542
name = Debug;
538543
};
@@ -541,18 +546,21 @@
541546
buildSettings = {
542547
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
543548
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
544-
CODE_SIGN_STYLE = Automatic;
549+
CODE_SIGNING_REQUIRED = NO;
550+
CODE_SIGNING_STYLE = Automatic;
551+
CODE_SIGN_ENTITLEMENTS = ios_package_test/ios_package_test.entitlements;
545552
INFOPLIST_FILE = ios_package_test/Info.plist;
553+
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
546554
LD_RUNPATH_SEARCH_PATHS = (
547555
"$(inherited)",
548556
"@executable_path/Frameworks",
549557
);
550558
PRODUCT_BUNDLE_IDENTIFIER = "ai.onnxruntime.tests.ios-package-test";
551559
PRODUCT_NAME = "$(TARGET_NAME)";
552560
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
553-
SUPPORTS_MACCATALYST = NO;
561+
SUPPORTS_MACCATALYST = YES;
554562
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
555-
TARGETED_DEVICE_FAMILY = 1;
563+
TARGETED_DEVICE_FAMILY = "1,2";
556564
};
557565
name = Release;
558566
};
@@ -563,7 +571,7 @@
563571
CODE_SIGN_STYLE = Automatic;
564572
CURRENT_PROJECT_VERSION = 1;
565573
GENERATE_INFOPLIST_FILE = YES;
566-
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
574+
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
567575
LD_RUNPATH_SEARCH_PATHS = (
568576
"$(inherited)",
569577
"@executable_path/Frameworks",
@@ -585,7 +593,7 @@
585593
CODE_SIGN_STYLE = Automatic;
586594
CURRENT_PROJECT_VERSION = 1;
587595
GENERATE_INFOPLIST_FILE = YES;
588-
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
596+
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
589597
LD_RUNPATH_SEARCH_PATHS = (
590598
"$(inherited)",
591599
"@executable_path/Frameworks",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.app-sandbox</key>
6+
<true/>
7+
<key>com.apple.security.network.client</key>
8+
<true/>
9+
</dict>
10+
</plist>

tools/ci_build/build.py

+38-4
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ def convert_arg_line_to_args(self, arg_line):
400400

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

403+
parser.add_argument(
404+
"--macos",
405+
choices=["MacOSX", "Catalyst"],
406+
help="Specify the target platform for macOS build. Only specify this argument when --build_apple_framework is present.",
407+
)
408+
403409
parser.add_argument(
404410
"--apple_sysroot", default="", help="Specify the location name of the macOS platform SDK to be used"
405411
)
@@ -419,7 +425,7 @@ def convert_arg_line_to_args(self, arg_line):
419425
action="store_const",
420426
const="Xcode",
421427
dest="cmake_generator",
422-
help="Use Xcode as cmake generator, this is only supported on MacOS. Equivalent to '--cmake_generator Xcode'.",
428+
help="Use Xcode as cmake generator, this is only supported on MacOS. (non Catalyst build). Equivalent to '--cmake_generator Xcode'.",
423429
)
424430
parser.add_argument(
425431
"--osx_arch",
@@ -1323,8 +1329,12 @@ def generate_build_tree(
13231329
if args.use_snpe:
13241330
cmake_args += ["-Donnxruntime_USE_SNPE=ON"]
13251331

1326-
if args.build_apple_framework or args.ios:
1327-
if not args.cmake_generator == "Xcode":
1332+
if args.macos or args.ios:
1333+
# Note: Xcode CMake generator doesn't have a good support for Mac Catalyst yet.
1334+
if args.macos == "Catalyst" and args.cmake_generator == "Xcode":
1335+
raise BuildError("Xcode CMake generator ('--cmake_generator Xcode') doesn't support Mac Catalyst build.")
1336+
1337+
if (args.ios or args.macos == "MacOSX") and not args.cmake_generator == "Xcode":
13281338
raise BuildError(
13291339
"iOS/MacOS framework build requires use of the Xcode CMake generator ('--cmake_generator Xcode')."
13301340
)
@@ -1342,19 +1352,37 @@ def generate_build_tree(
13421352
"iOS/MacOS framework build on MacOS canceled due to missing arguments: "
13431353
+ ", ".join(val for val, cond in zip(arg_names, needed_args) if not cond)
13441354
)
1355+
# note: this value is mainly used in framework_info.json file to specify the build osx type
1356+
platform_name = "macabi" if args.macos == "Catalyst" else args.apple_sysroot
13451357
cmake_args += [
13461358
"-Donnxruntime_BUILD_SHARED_LIB=ON",
13471359
"-DCMAKE_OSX_SYSROOT=" + args.apple_sysroot,
13481360
"-DCMAKE_OSX_DEPLOYMENT_TARGET=" + args.apple_deploy_target,
13491361
# we do not need protoc binary for ios cross build
13501362
"-Dprotobuf_BUILD_PROTOC_BINARIES=OFF",
1363+
"-DPLATFORM_NAME=" + platform_name,
13511364
]
13521365
if args.ios:
13531366
cmake_args += [
13541367
"-DCMAKE_SYSTEM_NAME=iOS",
13551368
"-DCMAKE_TOOLCHAIN_FILE="
13561369
+ (args.ios_toolchain_file if args.ios_toolchain_file else "../cmake/onnxruntime_ios.toolchain.cmake"),
13571370
]
1371+
# for catalyst build, we need to manually specify cflags for target e.g. x86_64-apple-ios14.0-macabi, etc.
1372+
# https://forums.developer.apple.com/forums/thread/122571
1373+
if args.macos == "Catalyst":
1374+
macabi_target = f"{args.osx_arch}-apple-ios{args.apple_deploy_target}-macabi"
1375+
cmake_args += [
1376+
"-DCMAKE_CXX_COMPILER_TARGET=" + macabi_target,
1377+
"-DCMAKE_C_COMPILER_TARGET=" + macabi_target,
1378+
"-DCMAKE_CC_COMPILER_TARGET=" + macabi_target,
1379+
f"-DCMAKE_CXX_FLAGS=--target={macabi_target}",
1380+
f"-DCMAKE_CXX_FLAGS_RELEASE=-O3 -DNDEBUG --target={macabi_target}",
1381+
f"-DCMAKE_C_FLAGS=--target={macabi_target}",
1382+
f"-DCMAKE_C_FLAGS_RELEASE=-O3 -DNDEBUG --target={macabi_target}",
1383+
f"-DCMAKE_CC_FLAGS=--target={macabi_target}",
1384+
f"-DCMAKE_CC_FLAGS_RELEASE=-O3 -DNDEBUG --target={macabi_target}",
1385+
]
13581386

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

27422770
if is_macOS():
2743-
if not args.ios and not args.android and args.osx_arch == "arm64" and platform.machine() == "x86_64":
2771+
if (
2772+
not args.ios
2773+
and args.macos != "Catalyst"
2774+
and not args.android
2775+
and args.osx_arch == "arm64"
2776+
and platform.machine() == "x86_64"
2777+
):
27442778
if args.test:
27452779
log.warning("Cannot test ARM64 build on X86_64. Will skip test running after build.")
27462780
args.test = False

tools/ci_build/github/apple/build_apple_framework.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ def _build_for_apple_sysroot(
5050
# Build binary for each arch, one by one
5151
for current_arch in archs:
5252
build_dir_current_arch = os.path.join(intermediates_dir, sysroot + "_" + current_arch)
53+
# Use MacOS SDK for Catalyst builds
54+
apple_sysroot = "macosx" if sysroot == "macabi" else sysroot
5355
build_command = [
5456
*base_build_command,
55-
"--apple_sysroot=" + sysroot,
57+
"--apple_sysroot=" + apple_sysroot,
5658
"--osx_arch=" + current_arch,
5759
"--build_dir=" + build_dir_current_arch,
5860
]

tools/ci_build/github/apple/default_full_apple_framework_build_settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"--cmake_extra_defines=onnxruntime_BUILD_UNIT_TESTS=OFF"
2424
],
2525
"macosx": [
26+
"--macos=MacOSX",
2627
"--apple_deploy_target=11.0"
2728
],
2829
"iphoneos": [

0 commit comments

Comments
 (0)