diff --git a/.github/actions/make/android/bindings/action.yml b/.github/actions/make/android/bindings/action.yml new file mode 100644 index 0000000000..908321be28 --- /dev/null +++ b/.github/actions/make/android/bindings/action.yml @@ -0,0 +1,26 @@ +name: Fetch or make kotlin bindings for android +inputs: + gh-token: + required: true + +runs: + using: composite + steps: + - name: fetch ffi library + uses: ./.github/actions/make/ffi-library + with: + gh-token: ${{ inputs.gh-token }} + + - name: fetch uniffi bindgen artifact + uses: ./.github/actions/make/uniffi-bindgen + with: + gh-token: ${{ inputs.gh-token }} + + - name: make android bindings + uses: ./.github/actions/make + with: + key: bindings-kotlin-android + make-rule: bindings-kotlin-android + target-path: | + crypto-ffi/bindings/android/src/main/uniffi/com/wire/crypto/core_crypto_ffi.kt + gh-token: ${{ inputs.gh-token }} diff --git a/.github/actions/make/android/package/action.yml b/.github/actions/make/android/package/action.yml index aaa6f340b1..f3babf885e 100644 --- a/.github/actions/make/android/package/action.yml +++ b/.github/actions/make/android/package/action.yml @@ -24,25 +24,11 @@ runs: task: android-x86 gh-token: ${{ inputs.gh-token }} - - name: fetch ffi library - uses: ./.github/actions/make/ffi-library + - name: download android bindings + uses: ./.github/actions/make/android/bindings with: gh-token: ${{ inputs.gh-token }} - - name: fetch uniffi bindgen artifact - uses: ./.github/actions/make/uniffi-bindgen - with: - gh-token: ${{ inputs.gh-token }} - - - name: make android bindings - uses: ./.github/actions/make - with: - key: bindings-kotlin-android - make-rule: bindings-kotlin-android - target-path: | - crypto-ffi/bindings/android/src/main/uniffi/com/wire/crypto/core_crypto_ffi.kt - gh-token: ${{ inputs.gh-token }} - - name: make android package uses: ./.github/actions/make with: diff --git a/.github/actions/make/android/test/action.yml b/.github/actions/make/android/test/action.yml new file mode 100644 index 0000000000..e42d33fe79 --- /dev/null +++ b/.github/actions/make/android/test/action.yml @@ -0,0 +1,39 @@ +name: Test kotlin bindings for android via make +inputs: + gh-token: + required: true + +runs: + using: composite + steps: + - name: download armv7-linux-androideabi binaries + uses: ./.github/actions/make/android/lib + with: + task: android-armv7 + gh-token: ${{ inputs.gh-token }} + + - name: download aarch64-linux-android binaries + uses: ./.github/actions/make/android/lib + with: + task: android-armv8 + gh-token: ${{ inputs.gh-token }} + + - name: download x86_64-linux-android binaries + uses: ./.github/actions/make/android/lib + with: + task: android-x86 + gh-token: ${{ inputs.gh-token }} + + - name: fetch android bindings + uses: ./.github/actions/make/android/bindings + with: + gh-token: ${{ inputs.gh-token }} + + - name: make android-test + uses: ./.github/actions/make + with: + key: android-test + make-rule: android-test + target-path: | + .stamps/android-test + gh-token: ${{ inputs.gh-token }} diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index f25daedb09..e98c615676 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -44,15 +44,27 @@ jobs: build-android: uses: ./.github/workflows/build-android.yml - package-android: + bindings-android: + runs-on: ubuntu-latest needs: - uniffi-bindgen-linux - ffi-library-linux + steps: + - uses: actions/checkout@v6 + - uses: ./.github/actions/make/android/bindings + with: + gh-token: ${{ secrets.GITHUB_TOKEN }} + + package-android: + needs: + - bindings-android - build-android uses: ./.github/workflows/package-android.yml test-android: - needs: package-android + needs: + - bindings-android + - build-android uses: ./.github/workflows/test-android.yml ################# diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index fe1be3bbfa..8db014bfe9 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -13,9 +13,6 @@ jobs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest - env: - ANDROID_NDK_VERSION: 28.1.13356709 - steps: - uses: actions/checkout@v6 - name: set up jdk 17 @@ -33,16 +30,7 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: download android package - uses: ./.github/actions/make/android/package - with: - gh-token: ${{ secrets.GITHUB_TOKEN }} - - name: android instrumentation tests - uses: reactivecircus/android-emulator-runner@v2 + uses: ./.github/actions/make/android/test with: - api-level: 32 - arch: x86_64 - working-directory: ./crypto-ffi/bindings - script: ./gradlew android:connectedAndroidTest - + gh-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Makefile b/Makefile index f155d8df25..0858e59de2 100644 --- a/Makefile +++ b/Makefile @@ -298,6 +298,12 @@ $(STAMPS)/ios-create-xcframework: $(ios-create-xcframework-deps) .PHONY: ios-create-xcframework ios-create-xcframework: $(STAMPS)/ios-create-xcframework ## Build the XCode framework (macOS only) +ios-test-deps := $(IOS_SIMULATOR_ARM) $(UNIFFI_SWIFT_OUTPUT) + +$(STAMPS)/ios-test: $(ios-test-deps) + $(SHELL) scripts/run-ios-tests.sh $(XCODE_CONFIG) + $(TOUCH_STAMP) + #------------------------------------------------------------------------------- # Android builds #------------------------------------------------------------------------------- @@ -309,11 +315,12 @@ android-env: echo "ERROR: set ANDROID_NDK_HOME"; exit 1; \ fi @ndk_version=$$(perl -ne 's/Pkg\.Revision = // and print' $(ANDROID_NDK_HOME)/source.properties) && \ - echo "Using Android NDK $${ndk_version} at $(ANDROID_NDK_HOME)"; \ + echo "Using Android NDK $${ndk_version} at $(ANDROID_NDK_HOME)"; ANDROID_ARMv7 := target/armv7-linux-androideabi/$(RELEASE_MODE)/libcore_crypto_ffi.so android-armv7-deps := $(RUST_SOURCES) -$(ANDROID_ARMv7): $(android-armv7-deps) | android-env +$(ANDROID_ARMv7): $(android-armv7-deps) + $(MAKE) android-env; cargo rustc --locked \ --target armv7-linux-androideabi \ --package core-crypto-ffi \ @@ -325,7 +332,8 @@ android-armv7: $(ANDROID_ARMv7) ## Build core-crypto-ffi for armv7-linux-android ANDROID_ARMv8 := target/aarch64-linux-android/$(RELEASE_MODE)/libcore_crypto_ffi.so android-armv8-deps := $(RUST_SOURCES) -$(ANDROID_ARMv8): $(android-armv8-deps) | android-env +$(ANDROID_ARMv8): $(android-armv8-deps) + $(MAKE) android-env; cargo rustc --locked \ --target aarch64-linux-android \ --package core-crypto-ffi \ @@ -337,7 +345,8 @@ android-armv8: $(ANDROID_ARMv8) ## Build core-crypto-ffi for aarch64-linux-andro ANDROID_X86 := target/x86_64-linux-android/$(RELEASE_MODE)/libcore_crypto_ffi.so android-x86-deps := $(RUST_SOURCES) -$(ANDROID_X86): $(android-x86-deps) | android-env +$(ANDROID_X86): $(android-x86-deps) + $(MAKE) android-env; # Link clang_rt.builtins statically for x86_64 Android cargo rustc --locked \ --target x86_64-linux-android \ @@ -357,6 +366,24 @@ android: $(android-deps) ## Build all Android targets cd crypto-ffi/bindings && \ ./gradlew android:assemble$(GRADLE_BUILD_TYPE) +ifeq ($(UNAME_S),Linux) +ANDROID_TEST_LIB := $(ANDROID_X86) +android-test-lib-deps := $(android-x86-deps) +android-test-lib: android-x86 ## Build core-crypto-ffi for Android (automatically select the target based on the host machine) +else ifeq ($(UNAME_S),Darwin) +ANDROID_TEST_LIB := $(ANDROID_ARMv8) +android-test-lib-deps := $(android-armv8-deps) +android-test-lib: android-armv8 +else +$(error Unsupported host platform for android-test-lib: $(UNAME_S)) +endif + +android-test-deps := $(ANDROID_TEST_LIB) $(UNIFFI_ANDROID_OUTPUT) + +$(STAMPS)/android-test: $(android-test-deps) + $(SHELL) scripts/run-android-tests.sh + $(TOUCH_STAMP) + #------------------------------------------------------------------------------- # JVM native builds (Darwin + Linux) #------------------------------------------------------------------------------- @@ -402,10 +429,12 @@ else $(error Unsupported host platform for jvm: $(UNAME_S)) endif -.PHONY: jvm-test -jvm-test: $(JVM_LIB) bindings-kotlin-jvm ## Run Kotlin tests on JVM +jvm-test-deps := $(JVM_LIB) $(UNIFFI_JVM_OUTPUT) + +$(STAMPS)/jvm-test: $(jvm-test-deps) cd crypto-ffi/bindings && \ ./gradlew jvm:test --rerun + $(TOUCH_STAMP) #------------------------------------------------------------------------------- # TypeScript / JS tasks @@ -485,9 +514,10 @@ $(TS_OUT): $(ts-deps) .PHONY: ts ts: $(TS_OUT) ## Build the TypeScript wrapper +ts-test-deps := $(TS_OUT) + # run WebDriver tests + bun’s built-in tests -.PHONY: ts-test -ts-test: $(TS_OUT) ## Run TypeScript wrapper tests via wdio and bun. Optionally pass TEST= to filter by test name. +$(STAMPS)/ts-test: $(ts-test-deps) @set -euo pipefail; \ cd $(JS_DIR) && \ if [ -n "$(TEST)" ]; then \ @@ -497,6 +527,7 @@ ts-test: $(TS_OUT) ## Run TypeScript wrapper tests via wdio and bun. Optionally bun x wdio run wdio.conf.ts --spec test/wdio/*.test.ts; \ bun test; \ fi + $(TOUCH_STAMP) # run WebDriver benches .PHONY: ts-bench @@ -523,6 +554,12 @@ interop-build-deps := $(INTEROP_SOURCES) $(INTEROP_OUT): $(interop-build-deps) cargo build --bin interop +interop-test-deps := $(INTEROP_OUT) $(TS_OUT) $(IOS_SIMULATOR_ARM) $(UNIFFI_SWIFT_OUTPUT) $(ANDROID_ARMv8) $(ANDROID_ARMv7) $(ANDROID_X86) $(UNIFFI_ANDROID_OUTPUT) + +$(STAMPS)/interop-test: $(interop-test-deps) + $(SHELL) scripts/run-interop-test.sh $(XCODE_CONFIG) + $(TOUCH_STAMP) + #------------------------------------------------------------------------------- # Documentation targets #------------------------------------------------------------------------------- @@ -573,7 +610,7 @@ $(STAMPS)/docs-ts: $(TS_OUT) docs-ts: $(STAMPS)/docs-ts ## Generate TypeScript docs # Swift docs via Jazzy (macOS only) -docs-swift-deps := $(IOS_DEVICE) $(IOS_SIMULATOR_ARM) $(STAMPS)/bindings-swift +docs-swift-deps := $(IOS_DEVICE) $(IOS_SIMULATOR_ARM) $(UNIFFI_SWIFT_OUTPUT) $(STAMPS)/docs-swift: $(docs-swift-deps) mkdir -p target/swift/doc cd crypto-ffi/bindings/swift/WireCoreCrypto && \ @@ -693,3 +730,27 @@ fmt: rust-fmt swift-fmt kotlin-fmt ts-fmt ## Format all files .PHONY: check check: rust-check swift-check kotlin-check ts-check ## Run all linters + +#------------------------------------------------------------------------------- +# Lazy targets +#------------------------------------------------------------------------------- + +LAZY_TARGETS := jvm-test ts-test android-test ios-test interop-test + +ts-test: ## Run TypeScript wrapper tests via wdio and bun. Optionally pass TEST= to filter by test name. +jvm-test: ## Run Kotlin tests on JVM +android-test: ## Run Kotlin tests on Android +ios-test: ## Run Swift tests on iOS (macOS only) +interop-test: ## Run e2e interop test + +ifeq ($(LAZY_MAKE),) + +.PHONY: $(LAZY_TARGETS) +$(LAZY_TARGETS): + @rm -f $(STAMPS)/$@ + @$(MAKE) $(STAMPS)/$@ + +else +$(LAZY_TARGETS): %: $(STAMPS)/% + +endif diff --git a/scripts/create-android-virtual-device.sh b/scripts/create-android-virtual-device.sh new file mode 100755 index 0000000000..ff309c086e --- /dev/null +++ b/scripts/create-android-virtual-device.sh @@ -0,0 +1,92 @@ +set -e + +# ========================= +# SET ANDROID ARCH BASED ON OS +# ========================= +OS="$(uname -s)" + +if [ "$OS" = "Darwin" ]; then + ANDROID_ARCH="arm64-v8a" +elif [ "$OS" = "Linux" ]; then + ANDROID_ARCH="x86_64" +else + echo "Unsupported OS: $OS" + exit 1 +fi + +echo "Using Android architecture: $ANDROID_ARCH" + +# ========================= +# CONFIGURATION +# ========================= +ANDROID_API_LEVEL=32 +ANDROID_AVD=test_api_$ANDROID_API_LEVEL +NDK_VERSION=$(cat crypto-ffi/bindings/android/ndk.version) + +# Android SDK root - if command line tools installed via Homebrew, set +# ANDROID_HOME=/opt/homebrew/share/android-commandlinetools +export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION +export ANDROID_AVD_HOME=$HOME/.config/.android/avd + +# PATH updates +export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH + +# ========================= +# INSTALL REQUIRED COMPONENTS +# ========================= +LATEST_BUILD_TOOLS=$(sdkmanager --list \ + | grep "build-tools;" \ + | grep -v "rc" \ + | awk -F ';' '{print $2}' \ + | awk '{print $1}' \ + | sort -V \ + | tail -n1) + +echo "Installing platform-tools, build tools $LATEST_BUILD_TOOLS, emulator, platform $ANDROID_API_LEVEL, and system image ($ANDROID_ARCH)..." + +yes | sdkmanager --licenses >/dev/null +sdkmanager --install "build-tools;$LATEST_BUILD_TOOLS" +sdkmanager --install "platform-tools" +sdkmanager --install "platforms;android-$ANDROID_API_LEVEL" +sdkmanager --install "emulator" --channel=0 +sdkmanager --install "system-images;android-$ANDROID_API_LEVEL;default;$ANDROID_ARCH" --channel=0 + +# ========================= +# CREATE AVD +# ========================= +# Delete existing AVD if it exists +if avdmanager list avd | grep -q "^ Name: $ANDROID_AVD$"; then + echo "Deleting existing AVD $ANDROID_AVD..." + avdmanager delete avd -n $ANDROID_AVD +fi + +# Create new AVD +echo "Creating AVD $ANDROID_AVD..." +mkdir -p "$ANDROID_AVD_HOME" +echo "no" | avdmanager create avd \ + -n $ANDROID_AVD \ + -k "system-images;android-$ANDROID_API_LEVEL;default;$ANDROID_ARCH" \ + --force \ + -p "$ANDROID_AVD_HOME/$ANDROID_AVD" + +# ========================= +# LAUNCH EMULATOR HEADLESS +# ========================= +echo "Launching emulator $ANDROID_AVD headlessly..." +emulator -avd $ANDROID_AVD -no-window -no-snapshot-save >/dev/null & +EMULATOR_PID=$! + +echo "Waiting for Android emulator to be fully booted..." +adb wait-for-device + +until adb shell getprop sys.boot_completed | grep -m 1 '1'; do + sleep 1 +done + +until adb shell pm list packages >/dev/null 2>&1; do + sleep 1 +done + +echo "Emulator started. PID: $EMULATOR_PID" + +echo $EMULATOR_PID diff --git a/scripts/create-ios-sim-device.sh b/scripts/create-ios-sim-device.sh index 618f49c025..ab624acf54 100755 --- a/scripts/create-ios-sim-device.sh +++ b/scripts/create-ios-sim-device.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e NAME="$1" diff --git a/scripts/run-android-tests.sh b/scripts/run-android-tests.sh new file mode 100644 index 0000000000..32c37a52fa --- /dev/null +++ b/scripts/run-android-tests.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +./scripts/create-android-virtual-device.sh + +trap ' + echo "Shutting down Android emulator via adb" + $ANDROID_HOME/platform-tools/adb emu kill +' EXIT + +cd crypto-ffi/bindings + +./gradlew android:connectedAndroidTest --rerun diff --git a/scripts/run-interop-test.sh b/scripts/run-interop-test.sh new file mode 100644 index 0000000000..a7a356fdb0 --- /dev/null +++ b/scripts/run-interop-test.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -e + +XCODE_CONFIG=$1 + +OS="$(uname -s)" + +cleanup() { + if [ -n "$simulator_id" ]; then + echo "deleting simulator device $simulator_id" + scripts/delete-ios-sim-device.sh "$simulator_id" + fi + + echo "Shutting down Android emulator via adb" + $ANDROID_HOME/platform-tools/adb emu kill +} + +trap cleanup EXIT + +if [ "$OS" = "Darwin" ]; then + simulator_id=$(./scripts/create-ios-sim-device.sh "iPhone 16 e2e-interop-test") + + cd interop/src/clients/InteropClient + + xcodebuild \ + -scheme InteropClient \ + -configuration "$XCODE_CONFIG" \ + -sdk iphonesimulator \ + -destination "id=$simulator_id" \ + clean build install DSTROOT=./Products + + ./install-interop-client.sh $simulator_id + + cd - +fi + +./scripts/create-android-virtual-device.sh + +cd interop/src/clients + +./gradlew android-interop:install$XCODE_CONFIG + +cd - + +./target/debug/interop diff --git a/scripts/run-ios-tests.sh b/scripts/run-ios-tests.sh new file mode 100644 index 0000000000..41fe65c4cd --- /dev/null +++ b/scripts/run-ios-tests.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -e + +XCODE_CONFIG=$1 + +simulator_id=$(./scripts/create-ios-sim-device.sh "iPhone 16 test-ios") + +trap 'echo "deleting simulator device $simulator_id"; + ../../../../scripts/delete-ios-sim-device.sh "$simulator_id"' EXIT + +cd crypto-ffi/bindings/swift/WireCoreCrypto + +xcodebuild test \ + -scheme TestHost \ + -configuration "$XCODE_CONFIG" \ + -sdk iphonesimulator \ + -destination "id=$simulator_id"