diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d50222c2..28c5cb73 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -37,10 +37,6 @@ jobs: wasm_sdk_pre_build_command: | cd tests/TestPackage enable_wasm_sdk_build: true - # Android - android_sdk_pre_build_command: | - cd tests/TestPackage - enable_android_sdk_build: true # Windows windows_build_command: | cd tests/TestPackage @@ -56,7 +52,7 @@ jobs: # Android android_sdk_pre_build_command: | cd tests/TestPackage - enable_android_sdk_build: true + enable_android_sdk_checks: true # Windows windows_build_command: | cd tests/TestPackage diff --git a/.github/workflows/scripts/android/android-emulator-tests.sh b/.github/workflows/scripts/android/android-emulator-tests.sh new file mode 100755 index 00000000..87fe5b42 --- /dev/null +++ b/.github/workflows/scripts/android/android-emulator-tests.sh @@ -0,0 +1,146 @@ +#!/bin/bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift.org open source project +## +## Copyright (c) 2025 Apple Inc. and the Swift project authors +## Licensed under Apache License v2.0 with Runtime Library Exception +## +## See https://swift.org/LICENSE.txt for license information +## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +## +##===----------------------------------------------------------------------===## + +set -euo pipefail + +log() { printf -- "** %s\n" "$*" >&2; } +error() { printf -- "** ERROR: %s\n" "$*" >&2; } +fatal() { error "$@"; exit 1; } + +ANDROID_PROFILE="Nexus 10" +ANDROID_EMULATOR_TIMEOUT=300 + +SWIFTPM_HOME="${XDG_CONFIG_HOME}"/swiftpm +# e.g., "${SWIFTPM_HOME}"/swift-sdks/swift-DEVELOPMENT-SNAPSHOT-2025-12-11-a_android.artifactbundle/ +SWIFT_ANDROID_SDK_HOME=$(find "${SWIFTPM_HOME}"/swift-sdks -maxdepth 1 -name 'swift-*android.artifactbundle' | tail -n 1) + +ANDROID_SDK_TRIPLE="x86_64-unknown-linux-android28" + +while [[ $# -gt 0 ]]; do + case $1 in + --android-sdk-triple=*) + ANDROID_SDK_TRIPLE="${1#*=}" + shift + ;; + --android-profile=*) + ANDROID_PROFILE="${1#*=}" + shift + ;; + --android-emulator-timeout=*) + ANDROID_EMULATOR_TIMEOUT="${1#*=}" + shift + ;; + -*) + fatal "Unknown option: $1" + ;; + *) + if [[ -z "$SWIFT_VERSION_INPUT" ]]; then + SWIFT_VERSION_INPUT="$1" + else + fatal "Multiple Swift versions specified: $SWIFT_VERSION_INPUT and $1" + fi + shift + ;; + esac +done + +# extract the API level from the end of the triple +ANDROID_API="${ANDROID_SDK_TRIPLE/*-unknown-linux-android/}" + +# extract the build arch from the beginning of the triple +ANDROID_EMULATOR_ARCH="${ANDROID_SDK_TRIPLE/-unknown-linux-android*/}" + +# x86_64=x86_64, armv7=arm +ANDROID_EMULATOR_ARCH_TRIPLE="${ANDROID_EMULATOR_ARCH}" + +log "Running tests for ${ANDROID_SDK_TRIPLE}" + +EMULATOR_SPEC="system-images;android-${ANDROID_API};default;${ANDROID_EMULATOR_ARCH}" + +log "SWIFT_ANDROID_SDK_HOME=${SWIFT_ANDROID_SDK_HOME}" + +# install and start an Android emulator +log "Listing installed Android SDKs" +export PATH="${PATH}:$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/build-tools/latest:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin" +sdkmanager --list_installed + +log "Updating Android SDK licenses" +yes | sdkmanager --licenses > /dev/null || true + +log "Installing Android emulator" +sdkmanager --install "emulator" "platform-tools" "platforms;android-${ANDROID_API}" "${EMULATOR_SPEC}" + +log "Creating Android emulator" +export ANDROID_AVD_HOME=${XDG_CONFIG_HOME:-$HOME}/.android/avd +ANDROID_EMULATOR_NAME="swiftemu" +avdmanager create avd --force -n "${ANDROID_EMULATOR_NAME}" --package "${EMULATOR_SPEC}" --device "${ANDROID_PROFILE}" + +log "Configuring Android emulators" +emulator -list-avds + +log "Check Hardware Acceleration (KVM)" +emulator -accel-check + +log "Starting Android emulator" +# launch the emulator in the background +nohup emulator -no-metrics -partition-size 1024 -memory 4096 -wipe-data -no-window -no-snapshot -noaudio -no-boot-anim -avd "${ANDROID_EMULATOR_NAME}" & + +log "Waiting for Android emulator startup" +timeout "${ANDROID_EMULATOR_TIMEOUT}" adb wait-for-any-device + +log "Prepare Swift test package" +# create a staging folder where we copy the test executable +# and all the dependent libraries to copy over to the emulator +STAGING_DIR="swift-android-test" +rm -rf "${STAGING_DIR}" +mkdir "${STAGING_DIR}" + +BUILD_DIR=.build/"${ANDROID_SDK_TRIPLE}"/debug + +find "${BUILD_DIR}" -name '*.xctest' -exec cp -av {} "${STAGING_DIR}" \; +find "${BUILD_DIR}" -name '*.resources' -exec cp -av {} "${STAGING_DIR}" \; + +# copy over the required library dependencies +cp -av "${SWIFT_ANDROID_SDK_HOME}"/swift-android/swift-resources/usr/lib/swift-"${ANDROID_EMULATOR_ARCH_TRIPLE}"/android/*.so "${STAGING_DIR}" +cp -av "${SWIFT_ANDROID_SDK_HOME}"/swift-android/ndk-sysroot/usr/lib/"${ANDROID_EMULATOR_ARCH_TRIPLE}"-linux-android/libc++_shared.so "${STAGING_DIR}" + +# for the common case of tests referencing +# their own files as hardwired paths instead of resources +if [[ -d Tests ]]; then + cp -a Tests "${STAGING_DIR}" +fi + +# warn about macros in packages, as per +# https://github.com/swiftlang/github-workflows/pull/215#discussion_r2621335245 +grep -q '\.macro(' Package.swift && log "WARNING: Packages with macros are known to have issues with cross-compilation: https://github.com/swiftlang/swift-package-manager/issues/8094" || true + +log "Copy Swift test package to emulator" + +ANDROID_TMP_FOLDER="/data/local/tmp/${STAGING_DIR}" +adb push "${STAGING_DIR}" "${ANDROID_TMP_FOLDER}" + +TEST_CMD="./*.xctest" +TEST_SHELL="cd ${ANDROID_TMP_FOLDER}" +TEST_SHELL="${TEST_SHELL} && ${TEST_CMD} --testing-library xctest" + +# Run test cases a second time with the Swift Testing library +# We additionally need to handle the special exit code +# EXIT_NO_TESTS_FOUND (69 on Android), which can happen +# when the tests link to Testing, but no tests are executed +# see: https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Commands/SwiftTestCommand.swift#L1571 +TEST_SHELL="${TEST_SHELL} && ${TEST_CMD} --testing-library swift-testing && [ \$? -eq 0 ] || [ \$? -eq 69 ]" + +log "Run Swift package tests" + +# run the test executable +adb shell "${TEST_SHELL}" diff --git a/.github/workflows/swift_package_test.yml b/.github/workflows/swift_package_test.yml index a81d047d..6ea529c4 100644 --- a/.github/workflows/swift_package_test.yml +++ b/.github/workflows/swift_package_test.yml @@ -198,6 +198,10 @@ on: type: boolean description: "Boolean to enable building with the Swift SDK for Android. Defaults to false" default: false + enable_android_sdk_checks: + type: boolean + description: "Boolean to enable testing with the Swift SDK for Android. Defaults to false" + default: false enable_macos_checks: type: boolean description: "Boolean to enable macOS testing. Defaults to false" @@ -582,21 +586,17 @@ jobs: ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/install-and-build-with-sdk.sh --embedded-wasm --flags="$BUILD_FLAGS" ${{ matrix.swift_version }} android-sdk-build: - name: Swift SDK for Android Build (${{ matrix.swift_version }} - ${{ matrix.os_version }} - NDK ${{ matrix.ndk_version }}) + name: Swift SDK for Android Build (${{ matrix.swift_version }} - NDK ${{ matrix.ndk_version }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: swift_version: ${{ fromJson(inputs.android_sdk_versions) }} ndk_version: ${{ fromJson(inputs.android_ndk_versions) }} - os_version: ${{ fromJson(inputs.linux_os_versions) }} exclude: - ${{ fromJson(inputs.android_exclude_swift_versions) }} - - ${{ fromJson((!inputs.enable_android_sdk_build && inputs.android_sdk_versions) || '[]') }} - - ${{ fromJson((!inputs.enable_android_sdk_build && inputs.android_ndk_versions) || '[]') }} - - ${{ fromJson((!inputs.enable_android_sdk_build && inputs.linux_os_versions) || '[]') }} - container: - image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + - ${{ fromJson((!(inputs.enable_android_sdk_build || inputs.enable_android_sdk_checks) && inputs.android_sdk_versions) || '[]') }} + - ${{ fromJson((!(inputs.enable_android_sdk_build || inputs.enable_android_sdk_checks) && inputs.android_ndk_versions) || '[]') }} steps: - name: Swift version run: swift --version @@ -604,23 +604,11 @@ jobs: run: clang --version - name: Checkout repository uses: actions/checkout@v4 - if: ${{ matrix.os_version != 'amazonlinux2' }} - - name: Checkout repository - uses: actions/checkout@v1 - if: ${{ matrix.os_version == 'amazonlinux2' }} - name: Checkout swiftlang/github-workflows repository - if: ${{ matrix.os_version != 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} uses: actions/checkout@v4 with: repository: swiftlang/github-workflows path: github-workflows - - name: Checkout swiftlang/github-workflows repository - if: ${{ matrix.os_version == 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} - uses: actions/checkout@v1 - with: - repository: swiftlang/github-workflows - path: ${{ github.event.repository.name }}/github-workflows - ref: main - name: Determine script-root path id: script_path run: | @@ -644,10 +632,29 @@ jobs: run: ${{ inputs.linux_pre_build_command }} - name: Install Swift SDK for Android and build env: - BUILD_FLAGS: ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} + BUILD_FLAGS: ${{ inputs.enable_android_sdk_checks && '--build-tests' }} ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} + shell: bash run: | ${{ inputs.android_sdk_pre_build_command }} ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/install-and-build-with-sdk.sh --android --flags="$BUILD_FLAGS" --build-command="${{ inputs.android_sdk_build_command }}" --android-sdk-triple=${{ join(fromJson(inputs.android_sdk_triples), ' --android-sdk-triple=') }} --android-ndk-version="${{ matrix.ndk_version }}" ${{ matrix.swift_version }} + - name: Enable KVM and free disk space + if: ${{ inputs.enable_android_sdk_checks }} + run: | + # enable KVM on Linux for tests, else error on emulator launch: + # CPU acceleration status: This user doesn't have permissions to use KVM (/dev/kvm). + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # need to free space or the emulator runs out + sudo rm -rf /opt/microsoft /opt/google /opt/az /opt/ghc /usr/share/dotnet /usr/local/share/boost /opt/hostedtoolcache /usr/local/share/chromium + df -h + - name: Install Android Emulator and run tests + if: ${{ inputs.enable_android_sdk_checks }} + shell: bash + run: | + ${{ inputs.android_sdk_pre_build_command }} + ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/android/android-emulator-tests.sh --android-sdk-triple=${{ join(fromJson(inputs.android_sdk_triples), ' --android-sdk-triple=') }} windows-build: name: Windows (${{ matrix.swift_version }} - ${{ matrix.os_version }})