Add iOS Swift Package Manager support (#15297)

### Description
<!-- Describe your changes. -->
Add Swift Package Manager (SPM) support for ORT based on  #14621
- uses the existing objective-c bindings
- some re-organization of the directory structure was required but the
contents of the files are unchanged, apart from adjustments due to file
movements

Add tool for updating ORT native pod used in the SPM package
Update CIs to use ORT native pod from build, and build/test using SPM



### 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. -->
iOS developers are using SPM as much as cocoapods, so adding SPM means
both are catered for.
This commit is contained in:
Scott McKay 2023-04-20 16:18:35 +10:00 коммит произвёл GitHub
Родитель 64b63921a2
Коммит 446c478fbd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
25 изменённых файлов: 270 добавлений и 37 удалений

8
.gitignore поставляемый
Просмотреть файл

@ -44,6 +44,7 @@ cmake/external/FeaturizersLibrary/
# Java specific ignores
java/.gradle
java/hs_*.log
/java/bin
onnxruntime/python/version_info.py
/orttraining/orttraining/eager/ort_aten.g.cpp
/orttraining/orttraining/eager/ort_customops.g.cpp
@ -188,3 +189,10 @@ dmypy.json
# Cython debug symbols
cython_debug/
# Swift Package Manager
Packages/
Package.pins
Package.resolved
.build/
.swiftpm/

102
Package.swift Normal file
Просмотреть файл

@ -0,0 +1,102 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package and MUST be the first
// line of this file. 5.6 is required to support zip files for the pod archive binaryTarget.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// A user of the Swift Package Manager (SPM) package will consume this file directly from the ORT github repository.
// For context, the end user's config will look something like:
//
// dependencies: [
// .package(url: "https://github.com/microsoft/onnxruntime", branch: "rel-1.15.0"),
// ...
// ],
//
// NOTE: The direct consumption creates a somewhat complicated setup to 'release' a new version of the ORT SPM package.
// TBD: how to manage the release process
import PackageDescription
import class Foundation.ProcessInfo
let package = Package(
name: "onnxruntime",
platforms: [.iOS(.v11)],
products: [
.library(name: "onnxruntime",
type: .static,
targets: ["OnnxRuntimeBindings"]),
],
dependencies: [],
targets: [
.target(name: "OnnxRuntimeBindings",
dependencies: ["onnxruntime"],
path: "objectivec",
exclude: ["test", "docs", "ReadMe.md", "format_objc.sh"],
cxxSettings: [
.define("SPM_BUILD"),
.unsafeFlags(["-std=c++17",
"-fobjc-arc-exceptions"
]),
], linkerSettings: [
.unsafeFlags(["-ObjC"]),
]),
.testTarget(name: "OnnxRuntimeBindingsTests",
dependencies: ["OnnxRuntimeBindings"],
path: "swift/OnnxRuntimeBindingsTests",
resources: [
.copy("Resources/single_add.basic.ort")
]),
]
)
// Add the ORT iOS Pod archive as a binary target.
//
// There are 2 scenarios:
//
// Release branch of ORT github repo:
// Target will be set to the released pod archive and its checksum.
//
// Any other branch/tag of ORT github repo:
// Invalid by default. We do not have a pod archive that is guaranteed to work
// as the objective-c bindings may have changed since the pod archive was released.
// CI or local testing where you have built/obtained the iOS Pod archive matching the current source code.
// Requires the ORT_IOS_POD_LOCAL_PATH environment variable to be set to specify the location of the pod.
if let pod_archive_path = ProcessInfo.processInfo.environment["ORT_IOS_POD_LOCAL_PATH"] {
// ORT_IOS_POD_LOCAL_PATH MUST be a path that is relative to Package.swift.
//
// To build locally, tools/ci_build/github/apple/build_and_assemble_ios_pods.py can be used
// See https://onnxruntime.ai/docs/build/custom.html#ios
// Example command:
// python3 tools/ci_build/github/apple/build_and_assemble_ios_pods.py \
// --variant Full \
// --build-settings-file tools/ci_build/github/apple/default_full_ios_framework_build_settings.json
//
// This should produce the pod archive in build/ios_pod_staging, and ORT_IOS_POD_LOCAL_PATH can be set to
// "build/ios_pod_staging/pod-archive-onnxruntime-c-???.zip" where '???' is replaced by the version info in the
// actual filename.
package.targets.append(Target.binaryTarget(name: "onnxruntime", path: pod_archive_path))
} else {
// When creating the release version:
// - remove the fatalError
// - uncomment the package.targets.append call
// - update the major/minor/patch version info in the url
// - insert the checksum info from the onnxruntime-ios-packaging-pipeline CI's 'Print ORT iOS Pod checksum'
// stage output (or download the pod archive artifact from the CI and run `shasum -a 256 <path to pod zip>`
// to manually calculate it).
// The checksum length and chars should look something like
// "c89cd106ff02eb3892243acd7c4f2bd8e68c2c94f2751b5e35f98722e10c042b"
//
// package.targets.append(
// Target.binaryTarget(name: "onnxruntime",
// url: "https://onnxruntimepackages.z14.web.core.windows.net/pod-archive-onnxruntime-c-<major.minor.patch>.zip",
// checksum: "Insert checksum here")
// )
fatalError("It is not valid to use a non-release branch from https://github.com/microsoft/onnxruntime.\n" +
"Please use a release branch (e.g. rel-1.15.0), or build the ONNX Runtime iOS pod archive locally " +
"and set the ORT_IOS_POD_LOCAL_PATH environment variable.\n" +
"See Package.swift for more information on using a local pod archive.")
}

Просмотреть файл

@ -36,9 +36,9 @@ file(GLOB onnxruntime_objc_headers CONFIGURE_DEPENDS
"${OBJC_ROOT}/include/*.h")
file(GLOB onnxruntime_objc_srcs CONFIGURE_DEPENDS
"${OBJC_ROOT}/src/*.h"
"${OBJC_ROOT}/src/*.m"
"${OBJC_ROOT}/src/*.mm")
"${OBJC_ROOT}/*.h"
"${OBJC_ROOT}/*.m"
"${OBJC_ROOT}/*.mm")
source_group(TREE "${OBJC_ROOT}" FILES
${onnxruntime_objc_headers}

2
objectivec/ReadMe.md Normal file
Просмотреть файл

@ -0,0 +1,2 @@
NOTE: Flat directory structure to work with both the Objective-C build and the Swift Package Manager build which is done
via ../Package.swift

Просмотреть файл

Просмотреть файл

@ -10,17 +10,30 @@
#pragma clang diagnostic ignored "-Wdocumentation"
#endif // defined(__clang__)
// paths are different when building the Swift Package Manager package as the headers come from the iOS pod archive
#ifdef SPM_BUILD
#include "onnxruntime/onnxruntime_c_api.h"
#include "onnxruntime/onnxruntime_cxx_api.h"
#if __has_include("onnxruntime/coreml_provider_factory.h")
#define ORT_OBJC_API_COREML_EP_AVAILABLE 1
#include "onnxruntime/coreml_provider_factory.h"
#else
#define ORT_OBJC_API_COREML_EP_AVAILABLE 0
#endif
#else
#include "onnxruntime_c_api.h"
#include "onnxruntime_cxx_api.h"
#if __has_include("coreml_provider_factory.h")
#define ORT_OBJC_API_COREML_EP_AVAILABLE 1
#include "coreml_provider_factory.h"
#else
#define ORT_OBJC_API_COREML_EP_AVAILABLE 0
#endif
#if ORT_OBJC_API_COREML_EP_AVAILABLE
#include "coreml_provider_factory.h"
#endif
#if defined(__clang__)

Просмотреть файл

@ -5,7 +5,7 @@
#include <exception>
#import "src/cxx_api.h"
#import "cxx_api.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import "src/error_utils.h"
#import "error_utils.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -7,6 +7,21 @@
NS_ASSUME_NONNULL_BEGIN
#ifdef __cplusplus
extern "C" {
#endif
/**
* Gets the ORT version string in format major.minor.patch.
*
* Available since 1.15.
*/
NSString* ORTVersion(void);
#ifdef __cplusplus
}
#endif
/**
* The ORT environment.
*/

Просмотреть файл

@ -3,9 +3,9 @@
#import "ort_coreml_execution_provider.h"
#import "src/cxx_api.h"
#import "src/error_utils.h"
#import "src/ort_session_internal.h"
#import "cxx_api.h"
#import "error_utils.h"
#import "ort_session_internal.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -5,7 +5,7 @@
#include <algorithm>
#import "src/cxx_api.h"
#import "cxx_api.h"
namespace {

Просмотреть файл

@ -3,7 +3,7 @@
#import "ort_enums.h"
#import "src/cxx_api.h"
#import "cxx_api.h"
OrtLoggingLevel PublicToCAPILoggingLevel(ORTLoggingLevel logging_level);

Просмотреть файл

@ -1,17 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import "src/ort_env_internal.h"
#import "ort_env_internal.h"
#include <optional>
#import "src/cxx_api.h"
#import "cxx_api.h"
#import "src/error_utils.h"
#import "src/ort_enums_internal.h"
#import "error_utils.h"
#import "ort_enums_internal.h"
NS_ASSUME_NONNULL_BEGIN
NSString* ORTVersion(void) {
std::string result = OrtGetApiBase()->GetVersionString();
return [NSString stringWithUTF8String:result.c_str()];
}
@implementation ORTEnv {
std::optional<Ort::Env> _env;
}

Просмотреть файл

@ -3,7 +3,7 @@
#import "ort_env.h"
#import "src/cxx_api.h"
#import "cxx_api.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import "src/ort_session_internal.h"
#import "ort_session_internal.h"
#include <optional>
#include <vector>
#import "src/cxx_api.h"
#import "src/error_utils.h"
#import "src/ort_enums_internal.h"
#import "src/ort_env_internal.h"
#import "src/ort_value_internal.h"
#import "cxx_api.h"
#import "error_utils.h"
#import "ort_enums_internal.h"
#import "ort_env_internal.h"
#import "ort_value_internal.h"
namespace {
enum class NamedValueType {

Просмотреть файл

@ -3,7 +3,7 @@
#import "ort_session.h"
#import "src/cxx_api.h"
#import "cxx_api.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import "src/ort_value_internal.h"
#import "ort_value_internal.h"
#include <optional>
#import "src/cxx_api.h"
#import "src/error_utils.h"
#import "src/ort_enums_internal.h"
#import "cxx_api.h"
#import "error_utils.h"
#import "ort_enums_internal.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -3,7 +3,7 @@
#import "ort_value.h"
#import "src/cxx_api.h"
#import "cxx_api.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -3,9 +3,9 @@
#import "ort_xnnpack_execution_provider.h"
#import "src/cxx_api.h"
#import "src/error_utils.h"
#import "src/ort_session_internal.h"
#import "cxx_api.h"
#import "error_utils.h"
#import "ort_session_internal.h"
NS_ASSUME_NONNULL_BEGIN

Просмотреть файл

@ -13,6 +13,10 @@ NS_ASSUME_NONNULL_BEGIN
@end
@implementation ORTEnvTest
- (void)testGetOrtVersion {
NSString* ver = ORTVersion();
XCTAssertNotNil(ver);
}
- (void)testInitOk {
NSError* err = nil;

Двоичные данные
swift/OnnxRuntimeBindingsTests/Resources/single_add.basic.ort Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import XCTest
import Foundation
@testable import OnnxRuntimeBindings
final class SwiftOnnxRuntimeBindingsTests: XCTestCase {
let modelPath: String = Bundle.module.url(forResource: "single_add.basic", withExtension: "ort")!.path
func testGetVersionString() throws {
do {
let version = ORTVersion()
XCTAssertNotNil(version)
} catch let error {
XCTFail(error.localizedDescription)
}
}
func testCreateSession() throws {
do {
let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose)
let options = try ORTSessionOptions()
try options.setLogSeverityLevel(ORTLoggingLevel.verbose)
try options.setIntraOpNumThreads(1)
// Create the ORTSession
_ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: options)
} catch let error {
XCTFail(error.localizedDescription)
}
}
func testAppendCoreMLEP() throws {
do {
let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose)
let sessionOptions: ORTSessionOptions = try ORTSessionOptions()
let coreMLOptions: ORTCoreMLExecutionProviderOptions = ORTCoreMLExecutionProviderOptions()
coreMLOptions.enableOnSubgraphs = true
try sessionOptions.appendCoreMLExecutionProvider(with: coreMLOptions)
XCTAssertTrue(ORTIsCoreMLExecutionProviderAvailable())
_ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: sessionOptions)
} catch let error {
XCTFail(error.localizedDescription)
}
}
func testAppendXnnpackEP() throws {
do {
let env = try ORTEnv(loggingLevel: ORTLoggingLevel.verbose)
let sessionOptions: ORTSessionOptions = try ORTSessionOptions()
let XnnpackOptions: ORTXnnpackExecutionProviderOptions = ORTXnnpackExecutionProviderOptions()
XnnpackOptions.intra_op_num_threads = 2
try sessionOptions.appendXnnpackExecutionProvider(with: XnnpackOptions)
XCTAssertTrue(ORTIsCoreMLExecutionProviderAvailable())
_ = try ORTSession(env: env, modelPath: modelPath, sessionOptions: sessionOptions)
} catch let error {
XCTFail(error.localizedDescription)
}
}
}

Просмотреть файл

@ -32,9 +32,9 @@ include_dirs = [
# pod source files
source_files = [
"objectivec/include/*.h",
"objectivec/src/*.h",
"objectivec/src/*.m",
"objectivec/src/*.mm",
"objectivec/*.h",
"objectivec/*.m",
"objectivec/*.mm",
]
# pod public header files

Просмотреть файл

@ -188,6 +188,28 @@ jobs:
"$(ORT_SHOULD_UPLOAD_ARCHIVES)"
displayName: "Assemble artifacts"
- script: |
set -e -x
ls -R "$(Build.ArtifactStagingDirectory)"
displayName: "List staged artifacts"
- script: |
set -e -x
shasum -a 256 "$(Build.ArtifactStagingDirectory)/pod-archive-onnxruntime-c-${ORT_POD_VERSION}.zip"
displayName: "Print ORT iOS Pod checksum"
# copy the pod archive to a path relative to Package.swift and set the env var required by Package.swift to use that.
# xcodebuild will implicitly use Package.swift and build/run the .testTarget (tests in swift/onnxTests).
# once that's done cleanup the copy of the pod zip file
- script: |
set -e -x
cp "$(Build.ArtifactStagingDirectory)/pod-archive-onnxruntime-c-${ORT_POD_VERSION}.zip" swift/
export ORT_IOS_POD_LOCAL_PATH="swift/pod-archive-onnxruntime-c-${ORT_POD_VERSION}.zip"
xcodebuild test -scheme onnxruntime -destination 'platform=iOS Simulator'
rm swift/pod-archive-onnxruntime-c-*.zip
displayName: "Test Package.swift usage"
- publish: "$(Build.ArtifactStagingDirectory)"
artifact: ios_packaging_artifacts
displayName: "Publish artifacts"

Просмотреть файл

@ -36,8 +36,8 @@ jobs:
"$(brew --prefix llvm@15)/bin/clang-tidy" \
-p="$(Build.BinariesDirectory)/Debug" \
--checks="-*,clang-analyzer-*" \
--header-filter="objectivec/include|objectivec/src|onnxruntime/core" \
./objectivec/src/*.mm \
--header-filter="objectivec/include|objectivec|onnxruntime/core" \
./objectivec/*.mm \
./onnxruntime/core/platform/apple/logging/apple_log_sink.mm \
./onnxruntime/core/providers/coreml/model/*.mm
displayName: Analyze Objective-C/C++ source code