diff --git a/.github/actions/setup-android-ndk/action.yml b/.github/actions/setup-android-ndk/action.yml new file mode 100644 index 0000000000000..fea9745396e81 --- /dev/null +++ b/.github/actions/setup-android-ndk/action.yml @@ -0,0 +1,98 @@ +# .github/actions/setup-android-ndk/action.yml +name: 'Setup Android NDK' +description: 'Installs and configures a specific version of the Android NDK' +inputs: + ndk-version: + description: 'The version of the Android NDK to install (e.g., 27.2.12479018)' + required: true + default: '27.2.12479018' + android-sdk-root: + description: 'The root directory of the Android SDK' + required: true + default: '/usr/local/lib/android/sdk' + +runs: + using: "composite" # Use a composite action for multiple shell commands + steps: + - name: Install coreutils and ninja + shell: bash + run: sudo apt-get update -y && sudo apt-get install -y coreutils ninja-build + + - name: Install Android NDK + shell: bash + run: | + set -e + "${{ inputs.android-sdk-root }}/cmdline-tools/latest/bin/sdkmanager" --install "ndk;${{ inputs.ndk-version }}" + + NDK_PATH="${{ inputs.android-sdk-root }}/ndk/${{ inputs.ndk-version }}" + if [[ ! -d "${NDK_PATH}" ]]; then + echo "NDK directory is not in expected location: ${NDK_PATH}" + exit 1 + fi + + # Use standard environment variable setting in bash and add to GITHUB_ENV + echo "ANDROID_NDK_HOME=${NDK_PATH}" >> $GITHUB_ENV + echo "ANDROID_NDK_ROOT=${NDK_PATH}" >> $GITHUB_ENV + echo "ANDROID_NDK_HOME: ${NDK_PATH}" + echo "ANDROID_NDK_ROOT: ${NDK_PATH}" + + - name: Check if emulator are installed and add to PATH + shell: bash + run: | + if [[ ":$PATH:" == *":${ANDROID_SDK_ROOT}/emulator:"* ]]; then + echo "${ANDROID_SDK_ROOT}/emulator is in PATH" + else + ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "emulator" + echo "${ANDROID_SDK_ROOT}/emulator" >> $GITHUB_PATH + fi + + - name: Check if platform tools are installed and add to PATH + shell: bash + run: | + if [[ ":$PATH:" == *":${ANDROID_SDK_ROOT}/platform-tools:"* ]]; then + echo "${ANDROID_SDK_ROOT}/platform-tools is in PATH" + else + ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "platform-tools" + echo "${ANDROID_SDK_ROOT}/platform-tools" >> $GITHUB_PATH + fi + ls -R "${ANDROID_SDK_ROOT}/platform-tools" + + - name: Create Android Emulator + shell: bash + env: + ANDROID_AVD_HOME: ${{ runner.temp }}/android-avd + run: | + python3 tools/python/run_android_emulator.py \ + --android-sdk-root "${ANDROID_SDK_ROOT}" \ + --create-avd --system-image "system-images;android-31;default;x86_64" + + - name: List Android AVDs + shell: bash + env: + ANDROID_AVD_HOME: ${{ runner.temp }}/android-avd + run: | + "${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/avdmanager" list avd + + - name: Check emulator.pid does not exist + shell: bash + run: | + if test -f ./emulator.pid; then + echo "Emulator PID file was not expected to exist but does and has pid: `cat ./emulator.pid`" + exit 1 + fi + + - name: Start Android Emulator + shell: bash + env: + ANDROID_AVD_HOME: ${{ runner.temp }}/android-avd + run: | + set -e -x + python3 tools/python/run_android_emulator.py \ + --android-sdk-root "${ANDROID_SDK_ROOT}" \ + --start --emulator-extra-args="-partition-size 2047" \ + --emulator-pid-file ./emulator.pid + echo "Emulator PID: `cat ./emulator.pid`" + + - name: View Android ENVs + shell: bash + run: env | grep ANDROID \ No newline at end of file diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000000000..64c40946c49c5 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,147 @@ +name: Android CI +# This workflow is used to build and test on Android Emulator on Linux + +on: + push: + branches: + - main + - rel-* + pull_request: + branches: + - main + - rel-* + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'workflow_dispatch' }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + android_nnapi_ep: + runs-on: ["self-hosted", "1ES.Pool=onnxruntime-github-Ubuntu2204-AMD-CPU"] + steps: + - uses: actions/checkout@v4 + + - name: Use jdk 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + architecture: x64 + + - name: Setup Android NDK + uses: ./.github/actions/setup-android-ndk + with: + ndk-version: 27.2.12479018 + + - name: Export GitHub Actions cache environment variables + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: NNAPI EP, Build, Test on Android Emulator + run: >- + python3 tools/ci_build/build.py + --enable_lto + --android + --build_dir build_nnapi + --android_sdk_path "$ANDROID_HOME" + --android_ndk_path "$ANDROID_NDK_HOME" + --android_abi=x86_64 + --android_api=29 + --skip_submodule_sync + --parallel --use_vcpkg --use_vcpkg_ms_internal_asset_cache + --use_nnapi + --build_shared_lib + --cmake_generator=Ninja + --build_java + shell: bash + + + - name: Build Minimal ORT with NNAPI and run tests + run: tools/ci_build/github/linux/ort_minimal/nnapi_minimal_build_minimal_ort_and_run_tests.sh "$(pwd)" + shell: bash + + - name: Install psutil for emulator shutdown by run_android_emulator.py + if: always() + run: python3 -m pip install psutil + shell: bash + + - name: Stop Android Emulator + if: always() + run: | + env | grep ANDROID + if test -f ${{ github.workspace }}/emulator.pid; then + echo "Emulator PID:"`cat ${{ github.workspace }}/emulator.pid` + python3 tools/python/run_android_emulator.py \ + --android-sdk-root "${ANDROID_SDK_ROOT}" \ + --stop \ + --emulator-pid-file ${{ github.workspace }}/emulator.pid + rm ${{ github.workspace }}/emulator.pid + else + echo "Emulator PID file was expected to exist but does not." + fi + shell: bash + + android_cpu_ep: + name: Android CI Pipeline + runs-on: ["self-hosted", "1ES.Pool=onnxruntime-github-Ubuntu2204-AMD-CPU"] + steps: + - uses: actions/checkout@v4 + + - name: Use jdk 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + architecture: x64 + + - name: Setup Android NDK + uses: ./.github/actions/setup-android-ndk + with: + ndk-version: 27.2.12479018 + + - name: Export GitHub Actions cache environment variables + uses: actions/github-script@v7 + with: + script: | + core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); + core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); + + - name: CPU EP, Build and Test + run: >- + python3 tools/ci_build/build.py + --enable_lto + --android + --build_dir build + --android_sdk_path $ANDROID_HOME + --android_ndk_path $ANDROID_NDK_HOME + --android_abi=x86_64 + --android_api=30 + --skip_submodule_sync + --parallel --use_vcpkg --use_vcpkg_ms_internal_asset_cache + --cmake_generator=Ninja + --build_java + shell: bash + + - name: Install psutil for emulator shutdown by run_android_emulator.py + if: always() + run: python3 -m pip install psutil + shell: bash + + - name: Stop Android Emulator + if: always() + run: | + if test -f ${{ github.workspace }}/emulator.pid; then + echo "Emulator PID:"`cat ${{ github.workspace }}/emulator.pid` + python3 tools/python/run_android_emulator.py \ + --android-sdk-root "${ANDROID_SDK_ROOT}" \ + --stop \ + --emulator-pid-file ${{ github.workspace }}/emulator.pid + rm ${{ github.workspace }}/emulator.pid + else + echo "Emulator PID file was expected to exist but does not." + fi + shell: bash diff --git a/tools/ci_build/github/azure-pipelines/android-x86_64-crosscompile-ci-pipeline.yml b/tools/ci_build/github/azure-pipelines/android-x86_64-crosscompile-ci-pipeline.yml deleted file mode 100644 index 3cceadd1b8ef5..0000000000000 --- a/tools/ci_build/github/azure-pipelines/android-x86_64-crosscompile-ci-pipeline.yml +++ /dev/null @@ -1,241 +0,0 @@ -##### start trigger Don't edit it manually, Please do edit set-trigger-rules.py #### -### please do rerun set-trigger-rules.py ### -trigger: - branches: - include: - - main - - rel-* - paths: - exclude: - - docs/** - - README.md - - CONTRIBUTING.md - - BUILD.md - - 'js/web' - - 'onnxruntime/core/providers/js' -pr: - branches: - include: - - main - - rel-* - paths: - exclude: - - docs/** - - README.md - - CONTRIBUTING.md - - BUILD.md - - 'js/web' - - 'onnxruntime/core/providers/js' -#### end trigger #### - -# Known Limits -# 1. Anchors are not supported in GHA -# https://github.community/t/support-for-yaml-anchors/16128/90 -# 2. today most cloud-based CI services are still lacking hardware acceleration support from the host VM, -# which is the no.1 blocker for running tests on modern Android Emulators (especially on recent API levels) on CI. - -# It'd better to check out https://github.com/microsoft/onnxruntime/wiki/Leverage-Existing-Artifacts -# to save debugging time. -parameters: -- name: specificArtifact - displayName: Use Specific Artifact - type: boolean - default: false -- name: runId - displayName: Specific Artifact's RunId - type: number - default: 0 - -stages: -# Separate stage for building CPU vs NNAPI as we only want CodeQL to run on one of them so we don't get duplicate -# issues for code that is built in both. We pick NNAPI as that includes the NNAPI EP code. -- stage: BUILD_AND_TEST_CPU - dependsOn: [] - variables: - Codeql.Enabled: false - ANDROID_AVD_HOME: $(Agent.TempDirectory) - jobs: - - job: BUILD_AND_TEST_CPU - pool: onnxruntime-Ubuntu2204-AMD-CPU - workspace: - clean: all - timeoutInMinutes: 30 - steps: - - task: JavaToolInstaller@0 - displayName: Use jdk 17 - inputs: - versionSpec: '17' - jdkArchitectureOption: 'x64' - jdkSourceOption: 'PreInstalled' - - - script: sudo apt-get update -y && sudo apt-get install -y coreutils ninja-build - displayName: Install coreutils and ninja - - - template: templates/use-android-ndk.yml - - template: templates/use-android-emulator.yml - parameters: - create: true - start: true - - script: | - env | grep ANDROID - displayName: View Android ENVs - - script: | - python3 tools/ci_build/build.py \ - --enable_lto \ - --android \ - --build_dir build \ - --android_sdk_path $ANDROID_HOME \ - --android_ndk_path $ANDROID_NDK_HOME \ - --android_abi=x86_64 \ - --android_api=30 \ - --skip_submodule_sync \ - --parallel --use_vcpkg --use_vcpkg_ms_internal_asset_cache \ - --cmake_generator=Ninja \ - --build_java - displayName: CPU EP, Build and Test - - template: templates/use-android-emulator.yml - parameters: - stop: true - - - template: templates/clean-agent-build-directory-step.yml - -- stage: BUILD_AND_TEST_NNAPI_EP - dependsOn: [] - condition: notIn(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') - variables: - ANDROID_AVD_HOME: $(Agent.TempDirectory) - Codeql.ProjectConfigPath: .github/workflows - Codeql.Enabled: true - Codeql.Language: cpp - ${{ if variables['Codeql.Enabled'] }}: - JobsTimeout: 120 - ${{ else }}: - JobsTimeout: 60 - jobs: - - job: BUILD_AND_TEST_NNAPI_EP - pool: onnxruntime-Ubuntu2204-AMD-CPU - timeoutInMinutes: ${{ variables.JobsTimeout }} - workspace: - clean: all - steps: - - task: JavaToolInstaller@0 - displayName: Use jdk 17 - inputs: - versionSpec: '17' - jdkArchitectureOption: 'x64' - jdkSourceOption: 'PreInstalled' - - - script: sudo apt-get update -y && sudo apt-get install -y coreutils ninja-build - displayName: Install coreutils and ninja - - template: templates/use-android-emulator.yml - parameters: - create: true - start: true - - - script: | - env | grep ANDROID - displayName: View Android ENVs - - - script: | - python3 tools/ci_build/build.py \ - --enable_lto \ - --android \ - --build_dir build_nnapi \ - --android_sdk_path $ANDROID_HOME \ - --android_ndk_path $ANDROID_NDK_HOME \ - --android_abi=x86_64 \ - --android_api=29 \ - --skip_submodule_sync \ - --parallel --use_vcpkg --use_vcpkg_ms_internal_asset_cache \ - --use_nnapi \ - --build_shared_lib \ - --cmake_generator=Ninja \ - --build_java - displayName: NNAPI EP, Build, Test on Android Emulator - - - script: /bin/bash tools/ci_build/github/linux/ort_minimal/nnapi_minimal_build_minimal_ort_and_run_tests.sh $(pwd) - # Build Minimal ORT with NNAPI and reduced Ops, run unit tests on Android Emulator - displayName: Build Minimal ORT with NNAPI and run tests - - - template: templates/use-android-emulator.yml - parameters: - stop: true - - - template: templates/clean-agent-build-directory-step.yml - -- stage: MAIN_BUILD_STAGE - # The below jobs only run on build of main branch. - # because coverage report is hard to support in cross machines. - displayName: NNAPI MAIN BUILD&TEST - dependsOn: [] - condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') - variables: - ANDROID_AVD_HOME: $(Agent.TempDirectory) - jobs: - - job: NNAPI_EP_MASTER - pool: onnxruntime-Ubuntu2204-AMD-CPU - timeoutInMinutes: 180 - workspace: - clean: all - condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') - steps: - - task: JavaToolInstaller@0 - displayName: Use jdk 17 - inputs: - versionSpec: '17' - jdkArchitectureOption: 'x64' - jdkSourceOption: 'PreInstalled' - - - template: templates/use-android-ndk.yml - - - template: templates/use-android-emulator.yml - parameters: - create: true - start: true - - - script: | - python3 tools/ci_build/build.py \ - --enable_lto \ - --android \ - --build_dir build_nnapi \ - --android_sdk_path $ANDROID_HOME \ - --android_ndk_path $ANDROID_NDK_HOME \ - --android_abi=x86_64 \ - --android_api=29 \ - --skip_submodule_sync \ - --parallel --use_vcpkg --use_vcpkg_ms_internal_asset_cache \ - --use_nnapi \ - --build_shared_lib \ - --cmake_generator=Ninja \ - --build_java \ - --code_coverage - displayName: NNAPI EP, Build, Test, CodeCoverage on Android Emulator - - # We need to use llvm-cov from the NDK. - - script: | - export GCOV="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-cov gcov" - python3 -m pip install gcovr - python3 tools/ci_build/coverage.py --build_dir build_nnapi --android_sdk_path $ANDROID_HOME - displayName: Retrieve runtime code coverage files from the emulator and analyze - - - script: cat '$(Build.SourcesDirectory)/build_nnapi/Debug/coverage_rpt.txt' - displayName: Print coverage report - - # - task: AzureCLI@2 - # displayName: 'Post Android Code Coverage To DashBoard' - # inputs: - # azureSubscription: AIInfraBuild - # scriptType: bash - # scriptPath: $(Build.SourcesDirectory)/tools/ci_build/github/linux/upload_code_coverage_data.sh - # arguments: '"$(Build.SourcesDirectory)/build_nnapi/Debug/coverage_rpt.txt" "https://dev.azure.com/onnxruntime/onnxruntime/_build/results?buildId=$(Build.BuildId)" arm android nnapi' - # workingDirectory: '$(Build.BinariesDirectory)' - - - script: /bin/bash tools/ci_build/github/linux/ort_minimal/nnapi_minimal_build_minimal_ort_and_run_tests.sh $(pwd) - # Build Minimal ORT with NNAPI and reduced Ops, run unit tests on Android Emulator - displayName: Build Minimal ORT with NNAPI and run tests - - - template: templates/use-android-emulator.yml - parameters: - stop: true - - - template: templates/clean-agent-build-directory-step.yml diff --git a/tools/python/util/android/android.py b/tools/python/util/android/android.py index 8f3ed97cae53f..cd420ca1483c7 100644 --- a/tools/python/util/android/android.py +++ b/tools/python/util/android/android.py @@ -46,18 +46,36 @@ def filename(name, windows_extension): def create_virtual_device(sdk_tool_paths: SdkToolPaths, system_image_package_name: str, avd_name: str): run(sdk_tool_paths.sdkmanager, "--install", system_image_package_name, input=b"y") - - run( - sdk_tool_paths.avdmanager, - "create", - "avd", - "--name", - avd_name, - "--package", - system_image_package_name, - "--force", - input=b"no", - ) + android_avd_home = os.environ["ANDROID_AVD_HOME"] + + if android_avd_home is not None: + if not os.path.exists(android_avd_home): + os.makedirs(android_avd_home) + run( + sdk_tool_paths.avdmanager, + "create", + "avd", + "--name", + avd_name, + "--package", + system_image_package_name, + "--force", + "--path", + android_avd_home, + input=b"no", + ) + else: + run( + sdk_tool_paths.avdmanager, + "create", + "avd", + "--name", + avd_name, + "--package", + system_image_package_name, + "--force", + input=b"no", + ) _process_creationflags = subprocess.CREATE_NEW_PROCESS_GROUP if is_windows() else 0