name: Build Artifacts (Android Binary)
on:
  workflow_dispatch:
    inputs:
      logLevel:
        description: 'Log level'
        required: true
        default: 'warning'
        type: choice
        options:
        - info
        - warning
        - debug
      tags:
        description: 'Test scenario tags'
        required: false
        type: boolean
  pull_request:
    branches: [master, develop]
    paths-ignore:
    - '**/*.md'
    - '.circleci/**'
    - '.cirrus.yml'
  push:
    branches: [master, develop, android]
    paths-ignore:
    - '**/*.md'
    - '.circleci/**'
    - '.cirrus.yml'
  release:
    types: [published]
  schedule:
    - cron: '0 16 * * *'
concurrency:
  group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.run_id }}
  cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }}
env:
  CACHE_EPOCH: 148-2
  GOPROXY: direct
jobs:
  android-binary-release:
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x86
            build_type: Release
            qemu_suffix: i386
            rust_target: i686-linux-android
            apilevel: 24
            ndk_version: 27.3.13750724
          - arch: x64
            build_type: Release
            qemu_suffix: amd64
            rust_target: x86_64-linux-android
            apilevel: 24
            ndk_version: 27.3.13750724
          - arch: arm
            build_type: Release
            qemu_suffix: armel
            rust_target: armv7-linux-androideabi
            apilevel: 24
            ndk_version: 27.3.13750724
          - arch: arm64
            build_type: Release
            qemu_suffix: aarch64
            rust_target: aarch64-linux-android
            apilevel: 24
            ndk_version: 27.3.13750724
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    env:
      ARCH: ${{ matrix.arch }}
      BUILD_TYPE: ${{ matrix.build_type }}
      SYSROOT: ${{ github.workspace }}/android-sysroot-${{ matrix.arch }}
      ANDROID_NDK_VER: ${{ matrix.ndk_version }}
    steps:
      - uses: actions/checkout@v5
      - name: Checkout with shallow submodules
        run: |
          # unshallow must come first otherwise submodule may be get unshallowed
          git fetch --tags --unshallow
          git submodule update --init --depth 1
      - name: Patch libcxxabi for both of armel and armhf
        if: ${{ matrix.arch == 'armel' || matrix.arch == 'arm' }}
        run: |
          cd third_party/libc++abi
          patch -p1 < v8-6.7.17-fix-gcc-unwind-header.patch
      - name: Cache toolchain
        id: toolchain-cache
        uses: actions/cache@v5
        with:
          path: |
            third_party/llvm-build/Release+Asserts
          key: ${{ runner.os }}-toolchain-${{ hashFiles('CLANG_REVISION') }}-v${{ env.CACHE_EPOCH }}
      - name: Cache tun2proxy build stage
        id: tun2proxy-cache
        uses: actions/cache@v5
        with:
          path: |
            third_party/tun2proxy/target/${{ matrix.rust_target }}/release/libtun2proxy.a
          key: ${{ runner.os }}-rust-${{ matrix.rust_target }}-tun2proxy-${{ hashFiles('RUST_REVISION') }}-${{ hashFiles('RUST_CARGO_REVISION') }}-${{ hashFiles('third_party/tun2proxy/Cargo.toml') }}-v${{ env.CACHE_EPOCH }}-v1
      - name: Cache gradle
        uses: actions/cache@v5
        with:
          path: ~/.gradle
          key: ${{ runner.os }}-gradle-${{ hashFiles('android/gradle/wrapper/*') }}-${{ hashFiles('android/**/*.gradle') }}
      - name: Cache qemu-user
        id: qemu-user-cache
        uses: actions/cache@v5
        with:
          path: |
            qemu-user*.deb
          key: ${{ runner.os }}-qemu-10.0.8-ds-0
      - name: Cache sysroot
        id: sysroot-cache
        uses: actions/cache@v5
        with:
          path: |
            ${{ env.SYSROOT }}
          key: android-sysroot-${{ matrix.arch }}-${{ hashFiles('scripts/get-android-sys.sh') }}-v1
      - uses: actions/setup-go@v6
        with:
          go-version: '>=1.20.0'
          cache-dependency-path: |
            tools/go.sum
            third_party/boringssl/src/go.sum
      - name: Build build tool
        run: |
          cd tools
          go build
      #- uses: actions/setup-java@v5
      #  with:
      #    distribution: 'temurin'
      #    java-version: '21'
      - name: Replace JAVA_HOME (pin to java 21)
        run: |
          echo "JAVA_HOME=${JAVA_HOME_21_X64}" >> $GITHUB_ENV
      - name: "Install dependency: prebuilt clang and clang-tidy binaries"
        if: ${{ steps.toolchain-cache.outputs.cache-hit != 'true' }}
        run: |
          ./scripts/download-clang-prebuilt-binaries.py
          rm -f third_party/llvm-build/Release+Asserts/*.tgz
      - name: "Install dependency: qemu user cache"
        if: ${{ steps.qemu-user-cache.outputs.cache-hit != 'true' }}
        run: |
          wget http://ftp.us.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.8+ds-0+deb13u1+b1_amd64.deb
      - name: Change ubuntu mirror
        run: |
          sudo sed -i 's/azure.archive.ubuntu.com/azure.archive.ubuntu.com/g' /etc/apt/sources.list
          sudo apt-get update -qq
      - name: Populate depedencies
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y pkgconf gettext bubblewrap
          # libc6-i386 interferes with x86 build
          sudo apt remove libc6-i386

          sudo dpkg -i qemu-user_*.deb
      - name: Build Binary (tun2proxy)
        if: ${{ steps.tun2proxy-cache.outputs.cache-hit != 'true' }}
        run: |
          ./scripts/setup-android-rust.sh
          WITH_OS=android WITH_CPU=${{ matrix.arch }} ./scripts/build-tun2proxy.sh
      - name: Build Binary (CLI)
        run: |
          ./tools/build --variant cli --arch ${{ matrix.arch }} --system android \
             -build-benchmark -build-test \
            -nc
      - name: Install the Release certificate
        if: ${{ github.event_name == 'release' }}
        env:
          SIGNING_STORE_BASE64: ${{ secrets.SIGNING_STORE_BASE64 }}
          SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
          SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
          SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
        run: |
          echo -n "$SIGNING_STORE_BASE64" | base64 --decode > $RUNNER_TEMP/release.jks
          echo "SIGNING_STORE_PATH=$RUNNER_TEMP/release.jks" >> $GITHUB_ENV
          echo "SIGNING_STORE_PASSWORD=$SIGNING_STORE_PASSWORD" >> $GITHUB_ENV
          echo "SIGNING_KEY_ALIAS=$SIGNING_KEY_ALIAS" >> $GITHUB_ENV
          echo "SIGNING_KEY_PASSWORD=$SIGNING_KEY_PASSWORD" >> $GITHUB_ENV
      - name: Build AAB bundle
        run: |
          ./tools/build --variant gui --arch ${{ matrix.arch }} --system android \
            -nc -android-aab
      - name: Build APK package
        run: |
          ./tools/build --variant gui --arch ${{ matrix.arch }} --system android \
            -nc
      - name: Clean up keychain and provisioning profile
        if: ${{ always() }}
        run: |
          if [ ! -z $SIGNING_STORE_PATH ]; then
            rm -f $SIGNING_STORE_PATH
          fi
      - name: Build Binary (DYLIB)
        run: |
          ./tools/build --variant dylib --arch ${{ matrix.arch }} --system android \
            -nc
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-aab
          path: |
            yass*.aab
          compression-level: 0 # no compression
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-apk
          path: |
            yass*.apk
          compression-level: 0 # no compression
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-gui
          path: |
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.so*
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.dbg
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/libYASS-C.so
      - name: Clean up libyass.so
        run: |
          rm -fv build-android${{ matrix.apilevel }}-${{ matrix.arch }}/libyass.so
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-cli
          path: |
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/yass_cli
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.so*
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.dbg
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-dylib
          path: |
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/devel
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/yass.py
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.so*
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.dbg
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-test
          path: |
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/yass_test
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.so*
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.dbg
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-benchmark
          path: |
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/yass_benchmark
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.so*
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.dbg
            !build-android${{ matrix.apilevel }}-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: android-release-${{ matrix.build_type }}-${{ matrix.arch }}-android${{ matrix.apilevel }}-ndk${{ matrix.ndk_version }}-${{ github.run_id }}-dbg
          path: |
            build-android${{ matrix.apilevel }}-${{ matrix.arch }}/*.dbg
          compression-level: 9 # maximum compression
      - name: "Install dependency: android sysroot (test purpose)"
        if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' }}
        run: |
          ./scripts/get-android-sys.sh ${{ matrix.arch }} "${{ matrix.SYSROOT }}"
      - name: Disable AppArmor Restrictions (x86 and x64)
        if: ${{ matrix.arch == 'x86' ||  matrix.arch == 'x64' }}
        run: |
          # see https://github.com/DevToys-app/DevToys/issues/1373#issuecomment-2985518849
          sudo apt-get install -y apparmor-profiles apparmor-profiles-extra
          sudo ln -s /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/bwrap
          sudo apparmor_parser /etc/apparmor.d/bwrap
      - name: Run tests (x86 and x64)
        if: ${{ matrix.arch == 'x86' ||  matrix.arch == 'x64' }}
        run: |
          # android sysimg doesn't like resolv.conf
          mkdir -p $SYSROOT/sys $SYSROOT/dev $SYSROOT/proc $SYSROOT/data/local/tmp
          bwrap --die-with-parent --ro-bind $SYSROOT / \
            --ro-bind /sys /sys \
            --proc /proc --dev /dev \
            --unshare-all --share-net \
            --bind $PWD/build-android${{ matrix.apilevel }}-${{ matrix.arch }} /data/local/tmp \
            --setenv ANDROID_DATA /data \
            --setenv ANDROID_ROOT /system \
            --setenv TMPDIR /data/local/tmp \
            /data/local/tmp/yass_test
      - name: Run tests (non x86, under qemu emulation)
        if: ${{ matrix.arch != 'x86' && matrix.arch != 'x64' }}
        run: |
          # TMPDIR is required by leveldb unittests
          qemu-${{ matrix.qemu_suffix }} -L $SYSROOT \
            -E ANDROID_DATA=$SYSROOT/data \
            -E ANDROID_ROOT=$SYSROOT/system \
            -E TMPDIR=$PWD/build-android${{ matrix.apilevel }}-${{ matrix.arch }} \
            $PWD/build-android${{ matrix.apilevel }}-${{ matrix.arch }}/yass_test
      - name: Upload dist tarball (including debuginfo)
        if: ${{ github.event_name == 'release' && matrix.build_type == 'Release' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload ${{ github.event.release.tag_name }} yass*.apk yass*.aab yass*.aab.idsig yass*.tgz
