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..b346b0f7 --- /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 -lq '\.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" + +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..e0ac6893 100644 --- a/.github/workflows/swift_package_test.yml +++ b/.github/workflows/swift_package_test.yml @@ -149,7 +149,7 @@ on: default: "swift build" android_sdk_triples: type: string - description: "The triples to use when building with the Swift SDK for Android" + description: "The triples to use when building with the Swift SDK for Android. The final triple in the list will be used for the emulator testing and should match the host architecture." default: "[\"aarch64-unknown-linux-android28\", \"x86_64-unknown-linux-android28\"]" android_ndk_versions: type: string @@ -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 }})