diff --git a/.az/mshost.yaml b/.az/mshost.yaml index 7c3afdce..379b0c48 100644 --- a/.az/mshost.yaml +++ b/.az/mshost.yaml @@ -442,7 +442,6 @@ jobs: ############# # iOS - # Only cmake==3.25.0 works OpenCV compiling now. ############# - job: IosPackage pool: @@ -456,23 +455,27 @@ jobs: architecture: 'x64' displayName: "Use Python 3.9" + # iOS xcframework build doesn't work with CMake 3.25.1, pin to 3.25.0 - script: | python -m pip install cmake==3.25.0 + displayName: "Install CMake 3.25.0" + + - template: ../.pipelines/templates/set-package-version-variable-step.yml + parameters: + PackageVersionVariableName: ORT_EXTENSIONS_POD_VERSION + + - script: | python ./tools/ios/build_xcframework.py \ --output-dir $(Build.BinariesDirectory)/xcframework_out \ --platform-arch iphonesimulator x86_64 \ --config Release - displayName: Build xcframework + displayName: Build xcframework for iphonesimulator x86_64 - script: | - set -e -x - POD_VERSION=$(cat ./version.txt)-dev python ./tools/ios/assemble_pod_package.py \ --staging-dir $(Build.BinariesDirectory)/pod_staging \ - --xcframework-dir $(Build.BinariesDirectory)/xcframework_out/onnxruntime_extensions.xcframework \ - --public-headers-dir $(Build.BinariesDirectory)/xcframework_out/Headers \ - --framework-info-file $(Build.BinariesDirectory)/xcframework_out/framework_info.json \ - --pod-version ${POD_VERSION} + --xcframework-output-dir $(Build.BinariesDirectory)/xcframework_out \ + --pod-version ${ORT_EXTENSIONS_POD_VERSION} displayName: Assemble pod - script: | @@ -481,7 +484,10 @@ jobs: displayName: Install pods for OrtExtensionsUsage workingDirectory: $(Build.SourcesDirectory)/test/ios/OrtExtensionsUsage - - template: templates/xcode-build-and-test-step.yml + - template: ../.pipelines/templates/with-ios-simulator-steps.yml parameters: - xcWorkspacePath: '$(Build.SourcesDirectory)/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcworkspace' - scheme: 'OrtExtensionsUsage' + steps: + - template: ../.pipelines/templates/xcode-build-and-test-step.yml + parameters: + xcWorkspacePath: '$(Build.SourcesDirectory)/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcworkspace' + scheme: 'OrtExtensionsUsage' diff --git a/.pipelines/ios_packaging.yml b/.pipelines/ios_packaging.yml new file mode 100644 index 00000000..a84eeb20 --- /dev/null +++ b/.pipelines/ios_packaging.yml @@ -0,0 +1,128 @@ +# packaging pipeline for iOS CocoaPods package + +parameters: +- name: IsReleaseBuild + displayName: "Is this is a release build?" + type: boolean + default: false + +jobs: +- job: IosPackaging + displayName: "iOS Packaging" + + pool: + vmImage: "macOS-12" + + timeoutInMinutes: 120 + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: "3.9" + addToPath: true + architecture: "x64" + + # iOS xcframework build doesn't work with CMake 3.25.1, pin to 3.25.0 + - script: | + python -m pip install cmake==3.25.0 + displayName: "Install CMake 3.25.0" + + - template: templates/set-package-version-variable-step.yml + parameters: + IsReleaseBuild: ${{ parameters.IsReleaseBuild }} + PackageVersionVariableName: ORT_EXTENSIONS_POD_VERSION + + - script: | + python ./tools/gen_selectedops.py ./tools/ios/package_ops.config + displayName: "Generate selected ops CMake file" + + - script: | + python ./tools/ios/build_xcframework.py \ + --output-dir $(Build.BinariesDirectory)/xcframework_out \ + --config Release \ + --cmake-extra-defines OCOS_ENABLE_SELECTED_OPLIST=ON + displayName: "Build xcframework" + + - script: | + python ./tools/ios/assemble_pod_package.py \ + --staging-dir $(Build.BinariesDirectory)/pod_staging \ + --xcframework-output-dir $(Build.BinariesDirectory)/xcframework_out \ + --pod-version ${ORT_EXTENSIONS_POD_VERSION} + displayName: "Assemble pod" + + - script: | + pod lib lint + displayName: "Lint pod" + workingDirectory: $(Build.BinariesDirectory)/pod_staging + + - script: | + ORT_EXTENSIONS_LOCAL_POD_PATH=$(Build.BinariesDirectory)/pod_staging \ + pod install + displayName: "Install pods for OrtExtensionsUsage" + workingDirectory: $(Build.SourcesDirectory)/test/ios/OrtExtensionsUsage + + - template: templates/with-ios-simulator-steps.yml + parameters: + steps: + - template: templates/xcode-build-and-test-step.yml + parameters: + xcWorkspacePath: '$(Build.SourcesDirectory)/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcworkspace' + scheme: 'OrtExtensionsUsage' + + - task: InstallAppleCertificate@2 + inputs: + certSecureFile: '$(ios_signing_certificate_name)' + certPwd: '$(ios_signing_certificate_password)' + keychain: 'temp' + displayName: "Install ORT Mobile Test Signing Certificate" + + - task: InstallAppleProvisioningProfile@1 + inputs: + provProfileSecureFile: '$(ios_provision_profile_name)' + removeProfile: true + displayName: "Install ORT Mobile Test Provisioning Profile" + + - task: Xcode@5 + inputs: + actions: 'build-for-testing' + configuration: 'Debug' + xcWorkspacePath: '$(Build.SourcesDirectory)/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcworkspace' + sdk: 'iphoneos' + scheme: 'OrtExtensionsUsage' + signingOption: 'manual' + signingIdentity: '$(APPLE_CERTIFICATE_SIGNING_IDENTITY)' + provisioningProfileName: 'iOS Team Provisioning Profile' + args: '-derivedDataPath $(Build.BinariesDirectory)/appcenter_test_derived_data' + displayName: "Build appcenter test" + + - script: | + appcenter test run xcuitest \ + --app "AI-Frameworks/ORT-Ext-Mobile-iOS-Testapp" \ + --devices "AI-Frameworks/apple-test-device-set" \ + --test-series "master" \ + --locale "en_US" \ + --build-dir $(Build.BinariesDirectory)/appcenter_test_derived_data/Build/Products/Debug-iphoneos \ + --token $(app_center_api_token) + displayName: "Run appcenter test" + + - script: | + set -e -x + + POD_STAGING_DIR="$(Build.BinariesDirectory)/pod_staging" + ARTIFACTS_STAGING_DIR="$(Build.ArtifactStagingDirectory)" + POD_NAME="onnxruntime-extensions-c" + POD_ARCHIVE_BASENAME="pod-archive-${POD_NAME}-${ORT_EXTENSIONS_POD_VERSION}.zip" + PODSPEC_BASENAME="${POD_NAME}.podspec" + + pushd ${POD_STAGING_DIR} + + # assemble the files in the artifacts staging directory + zip -r ${ARTIFACTS_STAGING_DIR}/${POD_ARCHIVE_BASENAME} * --exclude ${PODSPEC_BASENAME} + cp ${PODSPEC_BASENAME} ${ARTIFACTS_STAGING_DIR}/${PODSPEC_BASENAME} + + popd + displayName: "Assemble artifacts" + + - publish: "$(Build.ArtifactStagingDirectory)" + artifact: ios_packaging_artifacts + displayName: "Publish artifacts" diff --git a/.pipelines/templates/set-package-version-variable-step.yml b/.pipelines/templates/set-package-version-variable-step.yml new file mode 100644 index 00000000..6ea2de1d --- /dev/null +++ b/.pipelines/templates/set-package-version-variable-step.yml @@ -0,0 +1,28 @@ +parameters: +- name: IsReleaseBuild + displayName: | + If true, the version is a release version. Otherwise, the version has a development suffix. + type: boolean + default: false + +- name: PackageVersionVariableName + displayName: | + The name of the variable that will contain the version value. + type: string + +steps: +- bash: | + set -e -x + + VERSION_FILE="$(Build.SourcesDirectory)/version.txt" + BASE_VERSION="$(cat "${VERSION_FILE}")" + + if [[ "${{ parameters.IsReleaseBuild }}" == "true" ]]; then + VERSION="${BASE_VERSION}" + else + SHORT_COMMIT_HASH="$(git rev-parse --short HEAD)" + VERSION="${BASE_VERSION}-dev+$(Build.BuildId).${SHORT_COMMIT_HASH}" + fi + + echo "##vso[task.setvariable variable=${{ parameters.PackageVersionVariableName }}]${VERSION}" + displayName: "Set \"${{ parameters.PackageVersionVariableName }}\" variable to package version" diff --git a/.pipelines/templates/with-ios-simulator-steps.yml b/.pipelines/templates/with-ios-simulator-steps.yml new file mode 100644 index 00000000..7cade706 --- /dev/null +++ b/.pipelines/templates/with-ios-simulator-steps.yml @@ -0,0 +1,23 @@ +parameters: +- name: steps + type: stepList + +steps: +- bash: | + set -e -x + + ORT_EXTENSIONS_BUILD_SIMULATOR_ID=$(xcrun simctl create iPhoneSimulatorForPipeline com.apple.CoreSimulator.SimDeviceType.iPhone-8) + + echo "##vso[task.setvariable variable=ORT_EXTENSIONS_BUILD_SIMULATOR_ID]${ORT_EXTENSIONS_BUILD_SIMULATOR_ID}" + displayName: "Create iPhone simulator" + +- ${{ parameters.steps }} + +- bash: | + set -e -x + + if [[ -n "${ORT_EXTENSIONS_BUILD_SIMULATOR_ID-}" ]]; then + xcrun simctl delete ${ORT_EXTENSIONS_BUILD_SIMULATOR_ID} + fi + displayName: "Delete iPhone simulator" + condition: always() diff --git a/.az/templates/xcode-build-and-test-step.yml b/.pipelines/templates/xcode-build-and-test-step.yml similarity index 100% rename from .az/templates/xcode-build-and-test-step.yml rename to .pipelines/templates/xcode-build-and-test-step.yml diff --git a/build.ios_xcframework b/build.ios_xcframework new file mode 100755 index 00000000..53fe6fdd --- /dev/null +++ b/build.ios_xcframework @@ -0,0 +1,9 @@ +#!/bin/bash + +# builds an iOS xcframework +# see tools/ios/build_xcframework.py for details + +# Get directory this script is in +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +python ${DIR}/tools/ios/build_xcframework.py "$@" diff --git a/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcodeproj/project.pbxproj b/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcodeproj/project.pbxproj index 478e5937..187368e5 100644 --- a/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcodeproj/project.pbxproj +++ b/test/ios/OrtExtensionsUsage/OrtExtensionsUsage.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2E95FB6B2948166D00AA2CA7 /* OrtExtensionsUsageUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E95FB6A2948166D00AA2CA7 /* OrtExtensionsUsageUITests.swift */; }; 2ECE95DE293A742700039409 /* OrtExtensionsUsageApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECE95DD293A742700039409 /* OrtExtensionsUsageApp.swift */; }; 2ECE95E0293A742700039409 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ECE95DF293A742700039409 /* ContentView.swift */; }; 2ECE95E2293A742800039409 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2ECE95E1293A742800039409 /* Assets.xcassets */; }; @@ -18,6 +19,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 2E95FB6E2948166D00AA2CA7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 2ECE95D2293A742700039409 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2ECE95D9293A742700039409; + remoteInfo = OrtExtensionsUsage; + }; 2ECE95EB293A742800039409 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2ECE95D2293A742700039409 /* Project object */; @@ -28,6 +36,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2E95FB682948166D00AA2CA7 /* OrtExtensionsUsageUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OrtExtensionsUsageUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E95FB6A2948166D00AA2CA7 /* OrtExtensionsUsageUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrtExtensionsUsageUITests.swift; sourceTree = ""; }; 2ECE95DA293A742700039409 /* OrtExtensionsUsage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OrtExtensionsUsage.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2ECE95DD293A742700039409 /* OrtExtensionsUsageApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrtExtensionsUsageApp.swift; sourceTree = ""; }; 2ECE95DF293A742700039409 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -43,6 +53,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 2E95FB652948166D00AA2CA7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2ECE95D7293A742700039409 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -60,11 +77,20 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2E95FB692948166D00AA2CA7 /* OrtExtensionsUsageUITests */ = { + isa = PBXGroup; + children = ( + 2E95FB6A2948166D00AA2CA7 /* OrtExtensionsUsageUITests.swift */, + ); + path = OrtExtensionsUsageUITests; + sourceTree = ""; + }; 2ECE95D1293A742700039409 = { isa = PBXGroup; children = ( 2ECE95DC293A742700039409 /* OrtExtensionsUsage */, 2ECE95ED293A742800039409 /* OrtExtensionsUsageTests */, + 2E95FB692948166D00AA2CA7 /* OrtExtensionsUsageUITests */, 2ECE95DB293A742700039409 /* Products */, 614CBC9BE6DEBAEABF4B9145 /* Pods */, ); @@ -75,6 +101,7 @@ children = ( 2ECE95DA293A742700039409 /* OrtExtensionsUsage.app */, 2ECE95EA293A742800039409 /* OrtExtensionsUsageTests.xctest */, + 2E95FB682948166D00AA2CA7 /* OrtExtensionsUsageUITests.xctest */, ); name = Products; sourceTree = ""; @@ -121,6 +148,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 2E95FB672948166D00AA2CA7 /* OrtExtensionsUsageUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2E95FB722948166D00AA2CA7 /* Build configuration list for PBXNativeTarget "OrtExtensionsUsageUITests" */; + buildPhases = ( + 2E95FB642948166D00AA2CA7 /* Sources */, + 2E95FB652948166D00AA2CA7 /* Frameworks */, + 2E95FB662948166D00AA2CA7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2E95FB6F2948166D00AA2CA7 /* PBXTargetDependency */, + ); + name = OrtExtensionsUsageUITests; + productName = OrtExtensionsUsageUITests; + productReference = 2E95FB682948166D00AA2CA7 /* OrtExtensionsUsageUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 2ECE95D9293A742700039409 /* OrtExtensionsUsage */ = { isa = PBXNativeTarget; buildConfigurationList = 2ECE95FE293A742900039409 /* Build configuration list for PBXNativeTarget "OrtExtensionsUsage" */; @@ -166,6 +211,10 @@ LastSwiftUpdateCheck = 1410; LastUpgradeCheck = 1410; TargetAttributes = { + 2E95FB672948166D00AA2CA7 = { + CreatedOnToolsVersion = 14.1; + TestTargetID = 2ECE95D9293A742700039409; + }; 2ECE95D9293A742700039409 = { CreatedOnToolsVersion = 14.1; LastSwiftMigration = 1410; @@ -191,11 +240,19 @@ targets = ( 2ECE95D9293A742700039409 /* OrtExtensionsUsage */, 2ECE95E9293A742800039409 /* OrtExtensionsUsageTests */, + 2E95FB672948166D00AA2CA7 /* OrtExtensionsUsageUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 2E95FB662948166D00AA2CA7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2ECE95D8293A742700039409 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -217,6 +274,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 2E95FB642948166D00AA2CA7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2E95FB6B2948166D00AA2CA7 /* OrtExtensionsUsageUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2ECE95D6293A742700039409 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -238,6 +303,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 2E95FB6F2948166D00AA2CA7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2ECE95D9293A742700039409 /* OrtExtensionsUsage */; + targetProxy = 2E95FB6E2948166D00AA2CA7 /* PBXContainerItemProxy */; + }; 2ECE95EC293A742800039409 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 2ECE95D9293A742700039409 /* OrtExtensionsUsage */; @@ -246,6 +316,48 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 2E95FB702948166D00AA2CA7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.onnxruntime-extensions.OrtExtensionsUsageUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = OrtExtensionsUsage; + }; + name = Debug; + }; + 2E95FB712948166D00AA2CA7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.onnxruntime-extensions.OrtExtensionsUsageUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = OrtExtensionsUsage; + }; + name = Release; + }; 2ECE95FC293A742900039409 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -370,7 +482,6 @@ COMPRESS_PNG_FILES = NO; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"OrtExtensionsUsage/Preview Content\""; - DEVELOPMENT_TEAM = UBF8T346G9; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -405,7 +516,6 @@ COMPRESS_PNG_FILES = NO; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"OrtExtensionsUsage/Preview Content\""; - DEVELOPMENT_TEAM = UBF8T346G9; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; @@ -436,7 +546,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = UBF8T346G9; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.3; MARKETING_VERSION = 1.0; @@ -456,7 +565,6 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = UBF8T346G9; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.3; MARKETING_VERSION = 1.0; @@ -472,6 +580,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2E95FB722948166D00AA2CA7 /* Build configuration list for PBXNativeTarget "OrtExtensionsUsageUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2E95FB702948166D00AA2CA7 /* Debug */, + 2E95FB712948166D00AA2CA7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 2ECE95D5293A742700039409 /* Build configuration list for PBXProject "OrtExtensionsUsage" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/test/ios/OrtExtensionsUsage/OrtExtensionsUsage/ContentView.swift b/test/ios/OrtExtensionsUsage/OrtExtensionsUsage/ContentView.swift index f80bbae1..2ae4fad5 100644 --- a/test/ios/OrtExtensionsUsage/OrtExtensionsUsage/ContentView.swift +++ b/test/ios/OrtExtensionsUsage/OrtExtensionsUsage/ContentView.swift @@ -19,6 +19,7 @@ struct ContentView: View { .imageScale(.large) .foregroundColor(.accentColor) Text("Decode image result: \(runOrtDecodeAndCheckImage())") + .accessibilityIdentifier("decodeImageResult") } .padding() } diff --git a/test/ios/OrtExtensionsUsage/OrtExtensionsUsageUITests/OrtExtensionsUsageUITests.swift b/test/ios/OrtExtensionsUsage/OrtExtensionsUsageUITests/OrtExtensionsUsageUITests.swift new file mode 100644 index 00000000..708837d3 --- /dev/null +++ b/test/ios/OrtExtensionsUsage/OrtExtensionsUsageUITests/OrtExtensionsUsageUITests.swift @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import XCTest + +final class OrtExtensionsUsageUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testDecodeImageResultOk() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + let element = app.staticTexts.element(matching: XCUIElement.ElementType.staticText, identifier: "decodeImageResult") + XCTAssertEqual(element.label, "Decode image result: Ok") + } + +} diff --git a/test/ios/OrtExtensionsUsage/Podfile b/test/ios/OrtExtensionsUsage/Podfile index caa758ec..95dda658 100644 --- a/test/ios/OrtExtensionsUsage/Podfile +++ b/test/ios/OrtExtensionsUsage/Podfile @@ -21,3 +21,11 @@ target 'OrtExtensionsUsage' do # Pods for testing end end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end + end +end diff --git a/tools/gen_selectedops.py b/tools/gen_selectedops.py old mode 100644 new mode 100755 index ab6db312..5df90ddf --- a/tools/gen_selectedops.py +++ b/tools/gen_selectedops.py @@ -1,81 +1,136 @@ -import os +#!/usr/bin/env python3 + +import argparse +import pathlib import sys -OPMAP_TO_CMAKE_FLAGS = { - 'BlingFireSentenceBreaker': 'OCOS_ENABLE_BLINGFIRE', - 'GPT2Tokenizer': 'OCOS_ENABLE_GPT2_TOKENIZER', - 'WordpieceTokenizer': 'OCOS_ENABLE_WORDPIECE_TOKENIZER', - 'StringConcat': 'OCOS_ENABLE_TF_STRING', - 'StringECMARegexReplace': 'OCOS_ENABLE_TF_STRING', - 'StringECMARegexSplitWithOffsets': 'OCOS_ENABLE_TF_STRING', - 'StringEqual': 'OCOS_ENABLE_TF_STRING', - 'StringToHashBucket': 'OCOS_ENABLE_TF_STRING', - 'StringToHashBucketFast': 'OCOS_ENABLE_TF_STRING', - 'StringJoin': 'OCOS_ENABLE_TF_STRING', - 'StringLength': 'OCOS_ENABLE_TF_STRING', - 'StringLower': 'OCOS_ENABLE_TF_STRING', - 'StringRegexReplace': 'OCOS_ENABLE_RE2_REGEX', - 'StringRegexSplitWithOffsets': 'OCOS_ENABLE_RE2_REGEX', - 'StringSplit': 'OCOS_ENABLE_TF_STRING', - 'StringToVector': 'OCOS_ENABLE_TF_STRING', - 'StringUpper': 'OCOS_ENABLE_TF_STRING', - 'SegmentExtraction': 'OCOS_ENABLE_MATH', - 'StringMapping': 'OCOS_ENABLE_TF_STRING', - 'VectorToString': 'OCOS_ENABLE_TF_STRING', - 'MaskedFill': 'OCOS_ENABLE_TF_STRING', - 'BertTokenizer': 'OCOS_ENABLE_BERT_TOKENIZER', - 'BasicTokenizer': 'OCOS_ENABLE_BERT_TOKENIZER', - 'BertTokenizerDecoder': 'OCOS_ENABLE_BERT_TOKENIZER', - 'SentencepieceTokenizer': 'OCOS_ENABLE_SPM_TOKENIZER', - 'ImageDecode': 'OCOS_ENABLE_VISION', - 'ImageEncode': 'OCOS_ENABLE_VISION', - 'GaussianBlur': 'OCOS_ENABLE_CV2' +CMAKE_FLAG_TO_OPS = { + "OCOS_ENABLE_BERT_TOKENIZER": [ + "BasicTokenizer", + "BertTokenizer", + "BertTokenizerDecoder", + ], + "OCOS_ENABLE_BLINGFIRE": [ + "BlingFireSentenceBreaker", + ], + "OCOS_ENABLE_CV2": [ + "GaussianBlur", + ], + "OCOS_ENABLE_GPT2_TOKENIZER": [ + "GPT2Tokenizer", + ], + "OCOS_ENABLE_MATH": [ + "SegmentExtraction", + ], + "OCOS_ENABLE_RE2_REGEX": [ + "StringRegexReplace", + "StringRegexSplitWithOffsets", + ], + "OCOS_ENABLE_SPM_TOKENIZER": [ + "SentencepieceTokenizer", + ], + "OCOS_ENABLE_TF_STRING": [ + "MaskedFill", + "StringConcat", + "StringECMARegexReplace", + "StringECMARegexSplitWithOffsets", + "StringEqual", + "StringJoin", + "StringLength", + "StringLower", + "StringMapping", + "StringSplit", + "StringToHashBucket", + "StringToHashBucketFast", + "StringToVector", + "StringUpper", + "VectorToString", + ], + "OCOS_ENABLE_VISION": [ + "DecodeImage", + "EncodeImage", + ], + "OCOS_ENABLE_WORDPIECE_TOKENIZER": [ + "WordpieceTokenizer", + ], } -def gen_cmake_oplist(opconfig_file, oplist_cmake_file='_selectedoplist.cmake'): +def _gen_op_to_cmake_flag(): + op_to_cmake_flag = dict() + for cmake_flag, op_list in CMAKE_FLAG_TO_OPS.items(): + for op in op_list: + assert op not in op_to_cmake_flag, f"Duplicate op in CMAKE_FLAG_TO_OPS: {op}" + op_to_cmake_flag[op] = cmake_flag + return op_to_cmake_flag + + +OP_TO_CMAKE_FLAG = _gen_op_to_cmake_flag() + +SCRIPT_DIR = pathlib.Path(__file__).parent.resolve() +GENERATED_CMAKE_CONFIG_FILE = SCRIPT_DIR.parent / "cmake" / "_selectedoplist.cmake" + + +def gen_cmake_oplist(opconfig_file): + print( + "[onnxruntime-extensions] Generating CMake config file '{}' from op config file '{}'".format( + GENERATED_CMAKE_CONFIG_FILE, args.selected_op_config_file + ) + ) + ext_domain = "ai.onnx.contrib" # default_opset_domain() new_ext_domain = "com.microsoft.extensions" ext_domain_cnt = 0 cmake_options = set() - with open(oplist_cmake_file, 'w') as f: + with open(GENERATED_CMAKE_CONFIG_FILE, "w") as f: print("# Auto-Generated File, please do not edit!!!", file=f) - with open(opconfig_file, 'r') as opfile: + with open(opconfig_file, "r") as opfile: for _ln in opfile: if _ln.startswith(ext_domain) or _ln.startswith(new_ext_domain): ext_domain_cnt += 1 - items = _ln.strip().split(';') + items = _ln.strip().split(";") if len(items) < 3: raise RuntimeError("The malformed operator config file.") - for _op in items[2].split(','): + for _op in items[2].split(","): if not _op: continue # is None or "" - if _op not in OPMAP_TO_CMAKE_FLAGS: - raise RuntimeError("Cannot find the custom operator({})\'s build flags, please update " - "the OPMAP_TO_CMAKE_FLAGS dictionary.".format(_op)) - if OPMAP_TO_CMAKE_FLAGS[_op] not in cmake_options: - cmake_options.add(OPMAP_TO_CMAKE_FLAGS[_op]) - print("set({} ON CACHE INTERNAL \"\")".format(OPMAP_TO_CMAKE_FLAGS[_op]), file=f) + if _op not in OP_TO_CMAKE_FLAG: + raise RuntimeError( + "Cannot find the custom operator({})'s build flags, please update " + "the CMAKE_FLAG_TO_OPS dictionary.".format(_op) + ) + if OP_TO_CMAKE_FLAG[_op] not in cmake_options: + cmake_options.add(OP_TO_CMAKE_FLAG[_op]) + print('set({} ON CACHE INTERNAL "")'.format(OP_TO_CMAKE_FLAG[_op]), file=f) print("# End of Building the Operator CMake variables", file=f) if ext_domain_cnt == 0: - print('[onnxruntime-extensions] warning: lines starting with extension domains of ai.onnx.contrib or ' - 'com.microsoft.extensions in operators config file is 0') + print( + "[onnxruntime-extensions] warning: lines starting with extension domains of ai.onnx.contrib or " + "com.microsoft.extensions in operators config file is 0", + file=sys.stderr, + ) - print('[onnxruntime-extensions] The cmake tool file has been generated successfully.') + print("[onnxruntime-extensions] The cmake tool file has been generated successfully.") -if __name__ == '__main__': - # command: python gen_selectedops.py - # will generate the _selectedoplist.cmake file in ${PROJECT_SOURCE_DIR}/cmake/ folder - print('[onnxruntime-extensions] gen_selectedops.py arguments: ', sys.argv) +def parse_args(): + parser = argparse.ArgumentParser( + description="Generate the cmake/_selectedoplist.cmake file from the selected op config file.", + ) - if len(sys.argv) == 2: - print('[onnxruntime-extensions] Generating _selectedoplist.cmake file to folder: ${PROJECT_SOURCE_DIR}/cmake/') - current_dir = os.path.dirname(__file__) - target_cmake_path = os.path.abspath(os.path.join(current_dir, '../cmake/_selectedoplist.cmake')) - print('[onnxruntime-extensions] Target cmake file path: ', target_cmake_path) + parser.add_argument( + "selected_op_config_file", + type=pathlib.Path, + help="Path to selected op config file.", + ) - gen_cmake_oplist(sys.argv[1], target_cmake_path) - else: - print('[onnxruntime-extensions] gen_selectedops.py arguments error!') + args = parser.parse_args() + args.selected_op_config_file = args.selected_op_config_file.resolve(strict=True) + + return args + + +if __name__ == "__main__": + args = parse_args() + gen_cmake_oplist(args.selected_op_config_file) diff --git a/tools/ios/assemble_pod_package.py b/tools/ios/assemble_pod_package.py index 60d909ff..1fe5b127 100755 --- a/tools/ios/assemble_pod_package.py +++ b/tools/ios/assemble_pod_package.py @@ -113,32 +113,53 @@ def parse_args(): required=True, help="Path to the pod output staging directory.", ) - parser.add_argument( - "--xcframework-dir", - type=Path, - required=True, - help="Path to the onnxruntime_extensions xcframework to include in the pod.", - ) - parser.add_argument( - "--public-headers-dir", - type=Path, - required=True, - help="Path to the public header directory to include in the pod.", - ) - parser.add_argument( - "--framework-info-file", - type=Path, - required=True, - help="Path to the framework_info.json file containing additional values for the podspec. " - "This file should be generated by CMake in the build directory.", - ) + parser.add_argument( "--pod-version", required=True, help="The pod's version.", ) - return parser.parse_args() + input_paths_group = parser.add_argument_group(description="Input path arguments.") + + input_paths_group.add_argument( + "--xcframework-output-dir", + type=Path, + help=f"Path to the output directory produced by {_script_dir / 'build_xcframework.py'}. " + "Specify either this argument or the other input path arguments.", + ) + input_paths_group.add_argument( + "--xcframework-dir", + type=Path, + help="Path to the onnxruntime_extensions xcframework to include in the pod.", + ) + input_paths_group.add_argument( + "--public-headers-dir", + type=Path, + help="Path to the public header directory to include in the pod.", + ) + input_paths_group.add_argument( + "--framework-info-file", + type=Path, + help="Path to the framework_info.json file containing additional values for the podspec. " + "This file should be generated by CMake in the build directory.", + ) + + args = parser.parse_args() + + assert bool(args.xcframework_output_dir is not None) ^ bool( + args.xcframework_dir is not None + and args.public_headers_dir is not None + and args.framework_info_file is not None + ), "Specify either --xcframework-output-dir OR all of --xcframework-dir, --public-headers-dir, and " + "--framework-info-file." + + if args.xcframework_output_dir is not None: + args.xcframework_dir = args.xcframework_output_dir / "onnxruntime_extensions.xcframework" + args.public_headers_dir = args.xcframework_output_dir / "Headers" + args.framework_info_file = args.xcframework_output_dir / "framework_info.json" + + return args def main(): diff --git a/tools/ios/build_xcframework.py b/tools/ios/build_xcframework.py index 65202ef1..a586d626 100755 --- a/tools/ios/build_xcframework.py +++ b/tools/ios/build_xcframework.py @@ -35,7 +35,7 @@ def _get_opencv_toolchain_file(platform: str, opencv_dir: Path): def _run(cmd_args: List[str], **kwargs): import shlex - print(f"Running command:\n {shlex.join(cmd_args)}") + print(f"Running command:\n {shlex.join(cmd_args)}", flush=True) subprocess.run(cmd_args, check=True, **kwargs) @@ -47,31 +47,42 @@ def _rmtree_if_existing(dir: Path): def build_framework_for_platform_and_arch( - build_dir: Path, platform: str, arch: str, config: str, opencv_dir: Path, ios_deployment_target: str + build_dir: Path, + platform: str, + arch: str, + config: str, + opencv_dir: Path, + ios_deployment_target: str, + cmake_extra_defines: List[str], ) -> Path: build_dir.mkdir(parents=True, exist_ok=True) # generate build files - generate_args = [ - _cmake, - "-G=Xcode", - f"-S={_repo_dir}", - f"-B={build_dir}", - "-DCMAKE_SYSTEM_NAME=iOS", - f"-DCMAKE_OSX_DEPLOYMENT_TARGET={ios_deployment_target}", - f"-DCMAKE_OSX_SYSROOT={platform}", - f"-DCMAKE_OSX_ARCHITECTURES={arch}", - f"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO", - "-DOCOS_BUILD_APPLE_FRAMEWORK=ON", - # use OpenCV's CMake toolchain file - f"-DCMAKE_TOOLCHAIN_FILE={_get_opencv_toolchain_file(platform, opencv_dir)}", - # required by OpenCV CMake toolchain file - # https://github.com/opencv/opencv/blob/4223495e6cd67011f86b8ecd9be1fa105018f3b1/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake#L64-L66 - f"-DIOS_ARCH={arch}", - # required by OpenCV CMake toolchain file - # https://github.com/opencv/opencv/blob/4223495e6cd67011f86b8ecd9be1fa105018f3b1/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake#L96-L101 - f"-DIPHONEOS_DEPLOYMENT_TARGET={ios_deployment_target}", - ] + generate_args = ( + [ + _cmake, + "-G=Xcode", + f"-S={_repo_dir}", + f"-B={build_dir}", + ] + + [f"-D{cmake_extra_define}" for cmake_extra_define in cmake_extra_defines] + + [ + "-DCMAKE_SYSTEM_NAME=iOS", + f"-DCMAKE_OSX_DEPLOYMENT_TARGET={ios_deployment_target}", + f"-DCMAKE_OSX_SYSROOT={platform}", + f"-DCMAKE_OSX_ARCHITECTURES={arch}", + f"-DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO", + "-DOCOS_BUILD_APPLE_FRAMEWORK=ON", + # use OpenCV's CMake toolchain file + f"-DCMAKE_TOOLCHAIN_FILE={_get_opencv_toolchain_file(platform, opencv_dir)}", + # required by OpenCV CMake toolchain file + # https://github.com/opencv/opencv/blob/4223495e6cd67011f86b8ecd9be1fa105018f3b1/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake#L64-L66 + f"-DIOS_ARCH={arch}", + # required by OpenCV CMake toolchain file + # https://github.com/opencv/opencv/blob/4223495e6cd67011f86b8ecd9be1fa105018f3b1/platforms/ios/cmake/Toolchains/common-ios-toolchain.cmake#L96-L101 + f"-DIPHONEOS_DEPLOYMENT_TARGET={ios_deployment_target}", + ] + ) _run(generate_args) # build @@ -86,6 +97,7 @@ def build_xcframework( config: str, opencv_dir: Path, ios_deployment_target: str, + cmake_extra_defines: List[str], ): output_dir = output_dir.resolve() intermediate_build_dir = output_dir / "intermediates" @@ -110,6 +122,7 @@ def build_xcframework( config, opencv_dir, ios_deployment_target, + cmake_extra_defines, ) arch_framework_dirs.append(arch_framework_dir) @@ -186,6 +199,14 @@ def parse_args(): help="Specify a platform/arch pair to build. Repeat to specify multiple pairs. " "If no pairs are specified, all supported pairs will be built.", ) + parser.add_argument( + "--cmake-extra-defines", + action="append", + nargs="+", + default=[], + help="Extra definition(s) to pass to CMake (with the CMake -D option) during build system generation. " + "E.g., `--cmake-extra-defines OPTION1=ON OPTION2=OFF --cmake-extra-defines OPTION3=ON`.", + ) args = parser.parse_args() @@ -212,6 +233,9 @@ def parse_args(): args.platform_archs = platform_archs_from_args(args.platform_archs) + # convert from List[List[str]] to List[str] + args.cmake_extra_defines = [element for inner_list in args.cmake_extra_defines for element in inner_list] + return args @@ -224,6 +248,7 @@ def main(): config=args.config, opencv_dir=_repo_dir / "cmake/externals/opencv", ios_deployment_target=args.ios_deployment_target, + cmake_extra_defines=args.cmake_extra_defines, ) diff --git a/tools/ios/package_ops.config b/tools/ios/package_ops.config new file mode 100644 index 00000000..bea23828 --- /dev/null +++ b/tools/ios/package_ops.config @@ -0,0 +1 @@ +com.microsoft.extensions;1;DecodeImage,EncodeImage