diff --git a/tools/ci_build/github/apple/assemble_apple_packaging_artifacts.sh b/tools/ci_build/github/apple/assemble_apple_packaging_artifacts.sh index f96227a750346..a2178337e6876 100755 --- a/tools/ci_build/github/apple/assemble_apple_packaging_artifacts.sh +++ b/tools/ci_build/github/apple/assemble_apple_packaging_artifacts.sh @@ -23,29 +23,6 @@ ORT_POD_VERSION=${4:?${USAGE_TEXT}} POD_ARCHIVE_BASENAME="pod-archive-${POD_NAME}-${ORT_POD_VERSION}.zip" PODSPEC_BASENAME="${POD_NAME}.podspec" - -# Macos requires a different structure for the framework -# This will create the necessary symlinks for the macos framework before packaging -# Adding the symlinks here rather than in the build script ensures that symlinks are not lost -for MACOS_DIR in "${BINARIES_STAGING_DIR}/${POD_NAME}/onnxruntime.xcframework/macos"*; do - if [ -d "${MACOS_DIR}" ]; then - echo "Creating symlinks for ${MACOS_DIR}" - pushd "${MACOS_DIR}/onnxruntime.framework" - - rm -rf Headers Resources onnxruntime - rm -rf Versions/Current - - ln -sfn A Versions/Current - ln -sfn Versions/Current/Headers Headers - ln -sfn Versions/Current/Resources Resources - ln -sfn Versions/Current/onnxruntime onnxruntime - - popd - - fi -done - - echo "Contents of ${BINARIES_STAGING_DIR}/${POD_NAME}:" ls -lR "${BINARIES_STAGING_DIR}/${POD_NAME}" diff --git a/tools/ci_build/github/apple/build_and_assemble_apple_pods.py b/tools/ci_build/github/apple/build_and_assemble_apple_pods.py index 5014ba11d983d..71aeb9e7b0304 100755 --- a/tools/ci_build/github/apple/build_and_assemble_apple_pods.py +++ b/tools/ci_build/github/apple/build_and_assemble_apple_pods.py @@ -57,6 +57,11 @@ def parse_args(): ) parser.add_argument("--test", action="store_true", help="Run tests on the framework and pod package files.") + parser.add_argument( + "--skip-build", + action="store_true", + help="Use build from previous run. Useful to debug test issues or packaging changes.", + ) build_framework_group = parser.add_argument_group( title="iOS framework build arguments", @@ -114,7 +119,8 @@ def main(): build_apple_framework_args += ["--build_dir", str(build_dir), args.build_settings_file] - run(build_apple_framework_args) + if not args.skip_build: + run(build_apple_framework_args) if args.test: test_apple_packages_args = [ @@ -171,7 +177,8 @@ def main(): def move_dir(src, dst): if dst.is_dir(): shutil.rmtree(dst) - shutil.move(src, dst) + shutil.copytree(src, dst, symlinks=True) + shutil.rmtree(src) move_dir(c_pod_staging_dir, staging_dir / c_pod_name) move_dir(objc_pod_staging_dir, staging_dir / objc_pod_name) diff --git a/tools/ci_build/github/apple/build_apple_framework.py b/tools/ci_build/github/apple/build_apple_framework.py index 7270bdd56523c..5a3b242c2a389 100644 --- a/tools/ci_build/github/apple/build_apple_framework.py +++ b/tools/ci_build/github/apple/build_apple_framework.py @@ -200,7 +200,7 @@ def _build_package(args): xcframework_dir = os.path.join(build_dir, "framework_out") pathlib.Path(xcframework_dir).mkdir(parents=True, exist_ok=True) shutil.copy(os.path.join(REPO_DIR, "LICENSE"), xcframework_dir) - shutil.copytree(public_headers_path, os.path.join(xcframework_dir, "Headers"), dirs_exist_ok=True) + shutil.copytree(public_headers_path, os.path.join(xcframework_dir, "Headers"), dirs_exist_ok=True, symlinks=True) _merge_framework_info_files(framework_info_files_to_merge, os.path.join(build_dir, "xcframework_info.json")) # remove existing xcframework if any diff --git a/tools/ci_build/github/apple/c/assemble_c_pod_package.py b/tools/ci_build/github/apple/c/assemble_c_pod_package.py index ca4f01cf65bd9..59052734ddd26 100644 --- a/tools/ci_build/github/apple/c/assemble_c_pod_package.py +++ b/tools/ci_build/github/apple/c/assemble_c_pod_package.py @@ -16,6 +16,7 @@ PackageVariant, copy_repo_relative_to_dir, gen_file_from_template, + get_podspec_values, load_json_config, ) @@ -66,23 +67,25 @@ def assemble_c_pod_package( print("Warning: staging directory already exists", file=sys.stderr) # copy the necessary files to the staging directory - shutil.copytree(framework_dir, staging_dir / framework_dir.name, dirs_exist_ok=True) - shutil.copytree(public_headers_dir, staging_dir / public_headers_dir.name, dirs_exist_ok=True) + shutil.copytree(framework_dir, staging_dir / framework_dir.name, dirs_exist_ok=True, symlinks=True) + shutil.copytree(public_headers_dir, staging_dir / public_headers_dir.name, dirs_exist_ok=True, symlinks=True) copy_repo_relative_to_dir(["LICENSE"], staging_dir) + (ios_deployment_target, macos_deployment_target, weak_framework) = get_podspec_values(framework_info) + # generate the podspec file from the template variable_substitutions = { "DESCRIPTION": pod_config["description"], # By default, we build both "iphoneos" and "iphonesimulator" architectures, and the deployment target should be the same between these two. - "IOS_DEPLOYMENT_TARGET": framework_info["iphonesimulator"]["APPLE_DEPLOYMENT_TARGET"], - "MACOSX_DEPLOYMENT_TARGET": framework_info.get("macosx", {}).get("APPLE_DEPLOYMENT_TARGET", ""), + "IOS_DEPLOYMENT_TARGET": ios_deployment_target, + "MACOSX_DEPLOYMENT_TARGET": macos_deployment_target, "LICENSE_FILE": "LICENSE", "NAME": pod_name, "ORT_C_FRAMEWORK": framework_dir.name, "ORT_C_HEADERS_DIR": public_headers_dir.name, "SUMMARY": pod_config["summary"], "VERSION": pod_version, - "WEAK_FRAMEWORK": framework_info["iphonesimulator"]["WEAK_FRAMEWORK"], + "WEAK_FRAMEWORK": weak_framework, } podspec_template = _script_dir / "c.podspec.template" diff --git a/tools/ci_build/github/apple/objectivec/assemble_objc_pod_package.py b/tools/ci_build/github/apple/objectivec/assemble_objc_pod_package.py index 1e26482440eae..b7eb34cb09219 100755 --- a/tools/ci_build/github/apple/objectivec/assemble_objc_pod_package.py +++ b/tools/ci_build/github/apple/objectivec/assemble_objc_pod_package.py @@ -17,6 +17,7 @@ copy_repo_relative_to_dir, filter_files, gen_file_from_template, + get_podspec_values, load_json_config, ) @@ -147,12 +148,14 @@ def assemble_objc_pod_package( def path_patterns_as_variable_value(patterns: list[str]): return ", ".join([f'"{pattern}"' for pattern in patterns]) + (ios_deployment_target, macos_deployment_target, _) = get_podspec_values(framework_info) + variable_substitutions = { "C_POD_NAME": c_pod_config["name"], "DESCRIPTION": pod_config["description"], "INCLUDE_DIR_LIST": path_patterns_as_variable_value(include_dirs), - "IOS_DEPLOYMENT_TARGET": framework_info["iphonesimulator"]["APPLE_DEPLOYMENT_TARGET"], - "MACOSX_DEPLOYMENT_TARGET": framework_info.get("macosx", {}).get("APPLE_DEPLOYMENT_TARGET", ""), + "IOS_DEPLOYMENT_TARGET": ios_deployment_target, + "MACOSX_DEPLOYMENT_TARGET": macos_deployment_target, "LICENSE_FILE": license_file, "NAME": pod_name, "PUBLIC_HEADER_FILE_LIST": path_patterns_as_variable_value(pod_files["public_header_files"]), diff --git a/tools/ci_build/github/apple/package_assembly_utils.py b/tools/ci_build/github/apple/package_assembly_utils.py index 8ab8ccdb3f966..c6822466d73d0 100644 --- a/tools/ci_build/github/apple/package_assembly_utils.py +++ b/tools/ci_build/github/apple/package_assembly_utils.py @@ -118,6 +118,44 @@ def load_json_config(json_config_file: pathlib.Path): return json.load(config) +def get_podspec_values(framework_info): + """ + Get the podspec deployement targets and weak framework info from the dictionary that load_json_config returned. + Looks for iphonesimulator, iphoneos and macos settings. + Handles missing platforms and checks consistency. + Returns empty string for deployment target if that platofrm is not enabled. + + :return (ios_deployment_target, macos_deployment_target, weak_framework) + """ + ios_deployment_target = "" + macos_deployment_target = "" + weak_framework = "" # should be the same for all platforms + # get info, allowing for a subset of platforms to be specified + for framework in ("iphonesimulator", "iphoneos", "macosx"): + if framework not in framework_info: + continue + + target = framework_info[framework]["APPLE_DEPLOYMENT_TARGET"] + weak = framework_info[framework]["WEAK_FRAMEWORK"] + + if not weak_framework: + weak_framework = weak + else: + # should be consistent + assert weak == weak_framework + + if framework == "macosx": + macos_deployment_target = target + else: + if not ios_deployment_target: + ios_deployment_target = target + else: + # should be consistent + assert ios_deployment_target == target + + return (ios_deployment_target, macos_deployment_target, weak_framework) + + def get_ort_version(): """ Gets the ONNX Runtime version string from the repo. diff --git a/tools/ci_build/github/apple/test_apple_packages.py b/tools/ci_build/github/apple/test_apple_packages.py index 8f06d6dd68fbc..14c0b46676ac6 100644 --- a/tools/ci_build/github/apple/test_apple_packages.py +++ b/tools/ci_build/github/apple/test_apple_packages.py @@ -89,8 +89,9 @@ def _test_apple_packages(args): # create a zip file contains the framework zip_file_path = local_pods_dir / f"{pod_name}.zip" - # shutil.make_archive require target file as full path without extension - shutil.make_archive(zip_file_path.with_suffix(""), "zip", root_dir=local_pods_dir) + + # shutil.make_archive doesn't preserve symlinks. we know this is running on macOS so use zip + subprocess.run(["zip", "-r", "-y", str(zip_file_path), "."], cwd=local_pods_dir, check=True) # update the podspec to point to the local framework zip file with open(podspec) as file: diff --git a/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml b/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml index ec97da3786fd9..0368c91290d5e 100644 --- a/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml +++ b/tools/ci_build/github/azure-pipelines/templates/c-api-cpu.yml @@ -111,7 +111,7 @@ stages: cp -R $(Build.BinariesDirectory)/ios_framework/framework_out/onnxruntime.xcframework \ $(Build.BinariesDirectory)/artifacts_staging/onnxruntime-ios-xcframework-$(OnnxRuntimeVersion) pushd $(Build.BinariesDirectory)/artifacts_staging - zip -vr $(Build.BinariesDirectory)/artifacts/onnxruntime_xcframework.zip \ + zip -vry $(Build.BinariesDirectory)/artifacts/onnxruntime_xcframework.zip \ onnxruntime-ios-xcframework-$(OnnxRuntimeVersion) popd displayName: "Build Apple xcframework"