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

[DON'T MERGE] Build Apple xcframework #892

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
7 changes: 7 additions & 0 deletions .pipelines/nuget-publishing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ parameters:
type: boolean
default: true

- name: enable_apple_framework
displayName: 'Whether Apple framework for iOS & macOS is built.'
type: boolean
default: false

- name: ort_version
displayName: 'OnnxRuntime version'
type: string
Expand Down Expand Up @@ -89,6 +94,7 @@ stages:
enable_win_dml: ${{ parameters.enable_win_dml }}
enable_win_arm64: ${{ parameters.enable_win_arm64 }}
enable_macos_cpu: ${{ parameters.enable_macos_cpu }}
enable_apple_framework: ${{ parameters.enable_apple_framework }}
ort_version: ${{ parameters.ort_version }}
ort_cuda_version: ${{ parameters.ort_cuda_version }}
ort_dml_version: ${{ parameters.ort_dml_version }}
Expand All @@ -104,6 +110,7 @@ stages:
enable_win_dml: ${{ parameters.enable_win_dml }}
enable_win_arm64: ${{ parameters.enable_win_arm64 }}
enable_macos_cpu: ${{ parameters.enable_macos_cpu }}
enable_apple_framework: ${{ parameters.enable_apple_framework }}
ort_version: ${{ parameters.ort_version }}
ort_cuda_version: ${{ parameters.ort_cuda_version }}
ort_dml_version: ${{ parameters.ort_dml_version }}
Expand Down
11 changes: 11 additions & 0 deletions .pipelines/stages/capi-packaging-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ parameters:
type: boolean
- name: enable_macos_cpu
type: boolean
- name: enable_apple_framework
type: boolean
- name: ort_version
type: string
- name: ort_cuda_version
Expand Down Expand Up @@ -111,3 +113,12 @@ stages:
ort_version: ${{ parameters.ort_version }}
os: 'osx'
build_config: ${{ parameters.build_config }}

- ${{ if eq(parameters.enable_apple_framework, true) }}:
- template: jobs/capi-packaging-job.yml
parameters:
os: 'ios'
ep: 'cpu'
arch: 'arm64'
ort_version: ${{ parameters.ort_version }}
build_config: ${{ parameters.build_config }}
16 changes: 15 additions & 1 deletion .pipelines/stages/jobs/capi-packaging-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ parameters:
- 'linux'
- 'win'
- 'osx'
- 'ios'
- name: build_config
type: string
default: 'release'
Expand All @@ -34,7 +35,7 @@ jobs:
pool: 'onnxruntime-genai-windows-vs-2022-arm64'
${{ else }}:
pool: 'onnxruntime-Win-CPU-2022'
${{ if eq(parameters.os, 'osx') }}:
${{ if or(eq(parameters.os, 'osx'), eq(parameters.os, 'ios')) }}:
pool:
vmImage: 'macOS-latest'

Expand Down Expand Up @@ -76,6 +77,14 @@ jobs:
${{ else }}:
value: 'Microsoft.ML.OnnxRuntime'

- name: ort_native_package_path
${{ if eq(parameters.os, 'ios') }}:
value: 'ios'
${{ elseif eq(parameters.os, 'android') }}:
value: 'android'
${{ else }}:
value: ${{ parameters.os }}-${{ parameters.arch }}

- name: ortHome
value: 'ort'
- name: dml_dir
Expand Down Expand Up @@ -143,4 +152,9 @@ jobs:
ep: ${{ parameters.ep }}
build_config: ${{ parameters.build_config }}

- ${{ if eq(parameters.os, 'ios') }}:
- template: steps/capi-appleframework-step.yml
parameters:
build_config: ${{ parameters.build_config }}

- template: steps/compliant-and-cleanup-step.yml
6 changes: 6 additions & 0 deletions .pipelines/stages/jobs/nuget-packaging-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ parameters:
type: boolean
default: false

- name: enable_apple_framework
displayName: 'Whether Apple framework for iOS & macOS is built.'
type: boolean
default: false

- name: ort_version
type: string

Expand Down Expand Up @@ -85,6 +90,7 @@ jobs:
value: 'Microsoft.ML.OnnxRuntime.Gpu'
${{ if eq(parameters.ep, 'directml') }}:
value: 'Microsoft.ML.OnnxRuntime.DirectML'

steps:
- ${{ if and(eq(parameters.enable_win_cpu, true), eq(parameters.ep, 'cpu')) }}:
- template: steps/utils/flex-download-pipeline-artifact.yml
Expand Down
8 changes: 8 additions & 0 deletions .pipelines/stages/jobs/py-packaging-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ jobs:
${{ else }}:
value: 'Microsoft.ML.OnnxRuntime'

- name: ort_native_package_path
${{ if eq(parameters.os, 'ios') }}:
value: 'ios'
${{ if eq(parameters.os, 'android') }}:
value: 'android'
${{ else }}:
value: ${{ parameters.os }}-${{ parameters.arch }}

- name: dml_dir
value: 'Microsoft.AI.DirectML.1.15.1'
- name: dml_zip
Expand Down
42 changes: 42 additions & 0 deletions .pipelines/stages/jobs/steps/capi-appleframework-step.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
parameters:
- name: build_config
type: string
default: 'release'

steps:

- checkout: self
clean: true
path: onnxruntime-genai
submodules: recursive

- template: utils/set-genai-version.yml

- template: utils/set-nightly-build-option-variable.yml

- template: utils/download-ort.yml
parameters:
archiveType: 'zip'

- script: |
set -e
python3 tools/ci_build/github/apple/build_apple_framework.py \
--build_dir "$(Build.BinariesDirectory)/apple_framework" \
tools/ci_build/github/apple/default_full_ios_framework_build_settings.json

mkdir $(Build.BinariesDirectory)/artifacts
mkdir -p $(Build.BinariesDirectory)/artifacts_staging/onnxruntime-genai-ios-xcframework-$(genai_version)
cp -R $(Build.BinariesDirectory)/apple_framework/framework_out/onnxruntime-genai.xcframework \
$(Build.BinariesDirectory)/artifacts_staging/onnxruntime-genai-ios-xcframework-$(genai_version)
pushd $(Build.BinariesDirectory)/artifacts_staging
zip -vr $(Build.BinariesDirectory)/artifacts/onnxruntime_genai_ios_xcframework.zip \
onnxruntime-genai-ios-xcframework-$(genai_version)
popd
displayName: 'Build Apple XCFramework'
workingDirectory: '$(Build.Repository.LocalPath)'

- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: ONNXRuntime XCFramework'
inputs:
ArtifactName: capi-onnxruntime-genai-ios-xcframework
PathtoPublish: '$(Build.ArtifactStagingDirectory)/artifacts'
4 changes: 2 additions & 2 deletions .pipelines/stages/jobs/steps/utils/download-ort.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ steps:
displayName: Unzip OnnxRuntime
- task: CopyFiles@2
inputs:
SourceFolder: '$(Build.Repository.LocalPath)/ort/runtimes/$(os)-$(arch)/native'
SourceFolder: '$(Build.Repository.LocalPath)/ort/runtimes/$(ort_native_package_path)/native'
TargetFolder: '$(Build.Repository.LocalPath)/ort/lib'
- ${{ else }}:
- script: |
Expand All @@ -40,7 +40,7 @@ steps:
displayName: Unzip OnnxRuntime
- task: CopyFiles@2
inputs:
SourceFolder: '$(Build.Repository.LocalPath)/ort/runtimes/$(os)-$(arch)/native'
SourceFolder: '$(Build.Repository.LocalPath)/ort/runtimes/$(ort_native_package_path)/native'
TargetFolder: '$(Build.Repository.LocalPath)/ort/lib'
# TODO: Find out why do we need to to have libonnxruntime.so.ort_stable_version
- script: |
Expand Down
6 changes: 6 additions & 0 deletions .pipelines/stages/nuget-packaging-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ parameters:
type: boolean
default: true

- name: enable_apple_framework
displayName: 'Whether Apple framework for iOS & MacCatalyst built.'
type: boolean
default: false

- name: ort_version
type: string
- name: ort_cuda_version
Expand All @@ -57,6 +62,7 @@ stages:
enable_win_cpu: ${{ parameters.enable_win_cpu }}
enable_win_arm64: ${{ parameters.enable_win_arm64 }}
enable_macos_cpu: ${{ parameters.enable_macos_cpu }}
enable_apple_framework: ${{ parameters.enable_apple_framework }}
- ${{ if or(eq(parameters.enable_linux_cuda, true), eq(parameters.enable_win_cuda, true)) }}:
- template: jobs/nuget-packaging-job.yml
parameters:
Expand Down
133 changes: 132 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

cmake_minimum_required(VERSION 3.26)
cmake_minimum_required(VERSION 3.28)
include(FetchContent)
include(CMakeDependentOption)
project(Generators LANGUAGES C CXX)
Expand All @@ -13,6 +13,12 @@ if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_
message(FATAL_ERROR "GCC version must be greater than or equal to 11")
endif()

# Avoid warning of Calling FetchContent_Populate(Lib) is deprecated temporarily
# TODO: find a better way to handle the header-only 3rd party deps
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30.0")
cmake_policy(SET CMP0169 OLD)
endif()

if(MSVC)
# DLL initialization errors due to old conda msvcp140.dll dll are a result of the new MSVC compiler
# See https://developercommunity.visualstudio.com/t/Access-violation-with-std::mutex::lock-a/10664660#T-N10668856
Expand Down Expand Up @@ -45,6 +51,15 @@ if(MSVC)
)
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()

if(ENABLE_TESTS)
# call enable_testing so we can add tests from subdirectories (e.g. test and src/java)
# it applies recursively to all subdirectories
Expand Down Expand Up @@ -90,10 +105,19 @@ else()
target_link_libraries(onnxruntime-genai PRIVATE ${ONNXRUNTIME_LIB})
endif()

if(APPLE)
target_link_libraries(onnxruntime-genai PRIVATE "-framework Foundation" "-framework CoreML")
endif()

set_target_properties(onnxruntime-genai PROPERTIES FOLDER "Sources")
set_target_properties(onnxruntime-genai-static PROPERTIES FOLDER "Sources")
source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${generator_srcs})

set(ONNXRUNTIME_GENAI_PUBLIC_HEADERS
${SRC_ROOT}/ort_genai.h
${SRC_ROOT}/ort_genai_c.h
)

if(USE_CUDA AND CMAKE_CUDA_COMPILER)
set_target_properties(onnxruntime-genai PROPERTIES LINKER_LANGUAGE CUDA)
target_link_libraries(onnxruntime-genai PRIVATE cublasLt cublas curand cufft cudart)
Expand Down Expand Up @@ -153,6 +177,36 @@ if(USE_DML)
add_dependencies(onnxruntime-genai-static RESTORE_PACKAGES)
endif()

if (BUILD_APPLE_FRAMEWORK)
# create Info.plist for the framework and podspec for CocoaPods (optional)
set(MACOSX_FRAMEWORK_NAME "onnxruntime-genai")
set(MACOSX_FRAMEWORK_IDENTIFIER "com.microsoft.onnxruntime-genai")
# Need to include CoreML as a weaklink for CocoaPods package if the EP is enabled
if(USE_COREML)
set(APPLE_WEAK_FRAMEWORK "\\\"CoreML\\\"")
endif()
set(INFO_PLIST_PATH "${CMAKE_CURRENT_BINARY_DIR}/Info.plist")
configure_file(${REPO_ROOT}/cmake/Info.plist.in ${INFO_PLIST_PATH})
configure_file(
${REPO_ROOT}/tools/ci_build/github/apple/framework_info.json.template
${CMAKE_CURRENT_BINARY_DIR}/framework_info.json)

set_target_properties(onnxruntime-genai PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION A
MACOSX_FRAMEWORK_INFO_PLIST ${INFO_PLIST_PATH}
PUBLIC_HEADER "${ONNXRUNTIME_GENAI_PUBLIC_HEADERS}"
)

if (${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
set_target_properties(onnxruntime-genai PROPERTIES
MACOSX_RPATH TRUE
)
else()
set_target_properties(onnxruntime-genai PROPERTIES INSTALL_RPATH "@loader_path")
endif()
endif()

if(ANDROID)
# strip the binary if it's not a build with debug info
set_target_properties(onnxruntime-genai PROPERTIES LINK_FLAGS_RELEASE -s)
Expand Down Expand Up @@ -188,6 +242,83 @@ foreach(DLL_FILE ${onnxruntime_libs})
)
endforeach()

# Assemble the Apple static framework (iOS and macOS)
if(BUILD_APPLE_FRAMEWORK)
# 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()

# 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)
set(STATIC_LIB_TEMP_DIR ${STATIC_LIB_DIR}/temp)
add_custom_command(TARGET onnxruntime-genai PRE_BUILD COMMAND ${CMAKE_COMMAND} -E rm -rf ${STATIC_LIB_DIR})
add_custom_command(TARGET onnxruntime-genai PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${STATIC_LIB_DIR})
add_custom_command(TARGET onnxruntime-genai PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${STATIC_LIB_TEMP_DIR})

set(STATIC_FRAMEWORK_DIR ${STATIC_FRAMEWORK_OUTPUT_DIR}/static_framework/onnxruntime-genai.framework)
add_custom_command(TARGET onnxruntime-genai PRE_BUILD COMMAND ${CMAKE_COMMAND} -E rm -rf ${STATIC_FRAMEWORK_DIR})
add_custom_command(TARGET onnxruntime-genai PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${STATIC_FRAMEWORK_DIR})

set(INTERNAL_LIBRARIES)
list(APPEND INTERNAL_LIBRARIES onnxruntime-genai-static)

# 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 ${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-genai POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CUR_STATIC_LIB_OBJ_DIR})

add_custom_command(TARGET onnxruntime-genai POST_BUILD
COMMAND ar ARGS -x $<TARGET_FILE:${_LIB}>
WORKING_DIRECTORY ${CUR_STATIC_LIB_OBJ_DIR})
endif()
endforeach()

# for external libraries we create a symlink to the .a file
foreach(_LIB ${EXTERNAL_LIBRARIES})
GET_TARGET_PROPERTY(_LIB_TYPE ${_LIB} TYPE)
if(_LIB_TYPE STREQUAL "STATIC_LIBRARY")
add_custom_command(TARGET onnxruntime-genai POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink
$<TARGET_FILE:${_LIB}> ${STATIC_LIB_DIR}/$<TARGET_LINKER_FILE_NAME:${_LIB}>)
endif()
endforeach()

# do the pre-link with `ld -r` to create a single relocatable object with correct symbol visibility
add_custom_command(TARGET onnxruntime-genai POST_BUILD
COMMAND ld ARGS -r -o ${STATIC_LIB_DIR}/prelinked_objects.o */*.o ../*.a
WORKING_DIRECTORY ${STATIC_LIB_TEMP_DIR})

# create the static library
add_custom_command(TARGET onnxruntime-genai POST_BUILD
COMMAND libtool -static -o ${STATIC_FRAMEWORK_DIR}/onnxruntime-genai prelinked_objects.o
WORKING_DIRECTORY ${STATIC_LIB_DIR})

# Assemble the other pieces of the static framework
add_custom_command(TARGET onnxruntime-genai POST_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy_if_different ${INFO_PLIST_PATH} ${STATIC_FRAMEWORK_DIR}/Info.plist)

# add the framework header files
set(STATIC_FRAMEWORK_HEADER_DIR ${STATIC_FRAMEWORK_DIR}/Headers)
file(MAKE_DIRECTORY ${STATIC_FRAMEWORK_HEADER_DIR})

foreach(h_ ${ONNXRUNTIME_GENAI_PUBLIC_HEADERS})
get_filename_component(HEADER_NAME_ ${h_} NAME)
add_custom_command(TARGET onnxruntime-genai POST_BUILD
COMMAND ${CMAKE_COMMAND} -E
copy_if_different ${h_} ${STATIC_FRAMEWORK_HEADER_DIR}/${HEADER_NAME_})
endforeach()

endif()

# Have visual studio put all files into one single folder vs the default split of header files into a separate folder
source_group(TREE ${GENERATORS_ROOT} FILES ${generator_srcs})

Expand Down
Loading