diff --git a/tools/ci_build/github/apple/build_ios_framework.py b/tools/ci_build/github/apple/build_ios_framework.py new file mode 100644 index 0000000000..e521025e50 --- /dev/null +++ b/tools/ci_build/github/apple/build_ios_framework.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import argparse +import glob +import json +import os +import pathlib +import shutil +import subprocess +import sys + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +REPO_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", "..", "..")) +BUILD_PY = os.path.join(REPO_DIR, "tools", "ci_build", "build.py") + +# We by default will build below 2 archs +DEFAULT_BUILD_OSX_ARCHS = [ + {'sysroot': 'iphoneos', 'arch': 'arm64'}, + {'sysroot': 'iphonesimulator', 'arch': 'x86_64'}, +] + + +def _parse_build_settings(args): + with open(args.build_settings_file.resolve()) as f: + build_settings_data = json.load(f) + + build_settings = {} + + build_settings["build_osx_archs"] = build_settings_data.get("build_osx_archs", DEFAULT_BUILD_OSX_ARCHS) + + build_params = [] + if 'build_params' in build_settings_data: + build_params += build_settings_data['build_params'] + else: + raise ValueError('build_params is required in the build config file') + + build_settings['build_params'] = build_params + return build_settings + + +def _build_package(args): + build_settings = _parse_build_settings(args) + build_dir = os.path.abspath(args.build_dir) + + # Temp dirs to hold building results + intermediates_dir = os.path.join(build_dir, 'intermediates') + build_config = args.config + base_build_command = [sys.executable, BUILD_PY, '--config=' + build_config] + build_settings['build_params'] + + # paths of the onnxruntime libraries for different archs + ort_libs = [] + info_plist_path = '' + + # Build binary for each arch, one by one + for osx_arch in build_settings['build_osx_archs']: + sysroot = osx_arch['sysroot'] + current_arch = osx_arch['arch'] + build_dir_current_arch = os.path.join(intermediates_dir, sysroot + "_" + current_arch) + build_command = base_build_command + [ + '--ios_sysroot=' + sysroot, + '--osx_arch=' + current_arch, + '--build_dir=' + build_dir_current_arch + ] + + if args.include_ops_by_config is not None: + build_command += ['--include_ops_by_config=' + str(args.include_ops_by_config.resolve())] + + # the actual build process for current arch + subprocess.run(build_command, shell=False, check=True, cwd=REPO_DIR) + + # get the compiled lib path + framework_dir = os.path.join( + build_dir_current_arch, build_config, build_config + "-" + sysroot, 'onnxruntime.framework') + ort_libs.append(os.path.join(framework_dir, 'onnxruntime')) + + # We actually only need to define the info.plist and headers once since they are all the same + if not info_plist_path: + info_plist_path = os.path.join(build_dir_current_arch, build_config, 'info.plist') + headers = glob.glob(os.path.join(framework_dir, 'Headers', '*.h')) + + # manually create the fat framework + framework_dir = os.path.join(build_dir, 'framework_out', 'onnxruntime.framework') + pathlib.Path(framework_dir).mkdir(parents=True, exist_ok=True) + + # copy the header files and info.plist + shutil.copy(info_plist_path, framework_dir) + header_dir = os.path.join(framework_dir, 'Headers') + pathlib.Path(header_dir).mkdir(parents=True, exist_ok=True) + for _header in headers: + shutil.copy(_header, header_dir) + + # use lipo to create a fat ort library + lipo_command = ['lipo', '-create'] + lipo_command += ort_libs + lipo_command += ['-output', os.path.join(framework_dir, 'onnxruntime')] + subprocess.run(lipo_command, shell=False, check=True) + + +def parse_args(): + parser = argparse.ArgumentParser( + os.path.basename(__file__), + description='''Create iOS framework and podspec for one or more osx_archs (fat framework) + and building properties specified in the given build config file, see + tools/ci_build/github/apple/default_mobile_ios_framework_build_settings.json for details. + The output of the final framework and podspec can be found under [build_dir]/framework_out. + Please note, this building script will only work on macOS. + ''' + ) + + parser.add_argument('--build_dir', type=pathlib.Path, default=os.path.join(REPO_DIR, 'build/iOS_framework'), + help='Provide the root directory for build output') + + parser.add_argument( + "--include_ops_by_config", type=pathlib.Path, + help="Include ops from config file. See /docs/Reduced_Operator_Kernel_build.md for more information.") + + parser.add_argument("--config", type=str, default="Release", + choices=["Debug", "MinSizeRel", "Release", "RelWithDebInfo"], + help="Configuration to build.") + + parser.add_argument('build_settings_file', type=pathlib.Path, + help='Provide the file contains settings for building iOS framework') + + args = parser.parse_args() + + if not args.build_settings_file.resolve().is_file(): + raise FileNotFoundError('Build config file {} is not a file.'.format(args.build_settings_file.resolve())) + + if args.include_ops_by_config is not None: + include_ops_by_config_file = args.include_ops_by_config.resolve() + if not include_ops_by_config_file.is_file(): + raise FileNotFoundError('Include ops config file {} is not a file.'.format(include_ops_by_config_file)) + + return args + + +def main(): + args = parse_args() + _build_package(args) + + +if __name__ == '__main__': + main() diff --git a/tools/ci_build/github/apple/default_mobile_ios_framework_build_settings.json b/tools/ci_build/github/apple/default_mobile_ios_framework_build_settings.json new file mode 100644 index 0000000000..51ca753509 --- /dev/null +++ b/tools/ci_build/github/apple/default_mobile_ios_framework_build_settings.json @@ -0,0 +1,25 @@ +{ + "build_osx_archs": [ + { + "sysroot": "iphoneos", + "arch": "arm64" + }, + { + "sysroot": "iphonesimulator", + "arch": "x86_64" + } + ], + "build_params": [ + "--ios", + "--parallel", + "--use_xcode", + "--build_apple_framework", + "--minimal_build", + "--disable_rtti", + "--disable_ml_ops", + "--disable_exceptions", + "--enable_reduced_operator_type_support", + "--skip_tests", + "--apple_deploy_target=11.0" + ] +} \ No newline at end of file