name: Build Artifacts (Linux 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, linux]
    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:
  linux-binary-release:
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: i386
            build_type: Release
            qemu_suffix: i386
            build_options: -use-allocator=tbbmalloc
          - arch: i386
            build_type: Debug
            qemu_suffix: i386
            build_options: -use-allocator=tbbmalloc
          - arch: amd64
            build_type: Release
            qemu_suffix: amd64
            build_options: -use-allocator=tbbmalloc
          - arch: amd64
            build_type: Debug
            qemu_suffix: amd64
            build_options: -use-allocator=tbbmalloc
          - arch: armhf
            build_type: Release
            qemu_suffix: armhf
            build_options: -use-allocator=tcmalloc
          - arch: armel
            build_type: Release
            qemu_suffix: armel
            build_options: -use-allocator=tcmalloc
          - arch: arm64
            build_type: Release
            qemu_suffix: aarch64
            build_options: -use-allocator=tcmalloc
          - arch: mipsel
            build_type: Release
            qemu_suffix: mipsel
            build_options: -use-allocator=tcmalloc
          - arch: mips64el
            build_type: Release
            qemu_suffix: mips64el
            build_options: -use-allocator=tcmalloc
          - arch: loongarch64
            build_type: Release
            qemu_suffix: loongarch64
            build_options: -use-allocator=tcmalloc
          - arch: riscv64
            build_type: Release
            qemu_suffix: riscv64
            build_options: -use-allocator=tcmalloc
          - arch: riscv32
            build_type: Release
            qemu_suffix: riscv32
            build_options:
    runs-on: ubuntu-24.04
    env:
      BUILD_TYPE: ${{ matrix.build_type }}
    permissions:
      contents: write
    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 == 'armhf' }}
        run: |
          cd third_party/libc++abi
          patch -p1 < v8-6.7.17-fix-gcc-unwind-header.patch
      - name: Set SDK_ROOT
        if: ${{ matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
        run: |
          echo "SDK_ROOT=${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot" >> $GITHUB_ENV
      - name: Set SDK_ROOT for riscv64 and riscv32
        if: ${{ matrix.arch == 'riscv64' || matrix.arch == 'riscv32' }}
        run: |
          echo "SDK_ROOT=${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot/sysroot" >> $GITHUB_ENV
      - 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 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: |
            ${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot
          key: ${{ runner.os }}-sysroot-${{ matrix.arch }}-${{ hashFiles('scripts/sysroots.json') }}-v3
      - name: Setup mold linker
        uses: rui314/setup-mold@v1
        with:
          make-default: false
      - name: Setup build flags for mold
        if: ${{ matrix.arch != 'riscv32' && matrix.arch != 'mipsel' && matrix.arch != 'mips64el' }}
        run: |
          echo "EXTRA_BUILD_FLAGS=-use-mold -enable-lto=false" >> $GITHUB_ENV
      - 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
      - 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: "Install dependency: sysroot"
        if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' && matrix.arch != 'loongarch64' && matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
        run: |
          ./scripts/install-sysroot.py --arch ${{ matrix.arch }}
      - name: "Install dependency: sysroot (loongarch64)"
        if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' && matrix.arch == 'loongarch64' }}
        run: |
          curl -L https://github.com/hukeyue/loongson-chromium/releases/download/chromium120/debian_bullseye_loongarch64-sysroot.tar.bz2 | tar --bzip2 -xf -
      - name: "Install dependency: sysroot (riscv64 and riscv32)"
        if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' && (matrix.arch == 'riscv64' || matrix.arch == 'riscv32') }}
        run: |
          mkdir -p debian_bullseye_${{ matrix.arch }}-sysroot
          cd debian_bullseye_${{ matrix.arch }}-sysroot
          curl -L https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/${{ matrix.arch }}-glibc-ubuntu-22.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar --strip-components=1 --gz -xf -
          rm -rf bin include libexec ${{ matrix.arch }}-unknown-linux-gnu share
          cd ..
      - 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 (CLI)
        run: |
          ./tools/build --variant cli --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -nc -build-benchmark -build-test ${{ env.EXTRA_BUILD_FLAGS }} ${{ matrix.build_options }}
      - name: Build Binary (GUI)
        if: ${{ matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
        run: |
          ./tools/build --variant gui --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -nc ${{ env.EXTRA_BUILD_FLAGS }} ${{ matrix.build_options }}
      - name: Build Binary (DYLIB)
        run: |
          ./tools/build --variant dylib --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -nc ${{ env.EXTRA_BUILD_FLAGS }} ${{ matrix.build_options }}
      - uses: actions/upload-artifact@v6
        if: ${{ matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-gui
          path: |
            build-linux-${{ matrix.arch }}/yass
            build-linux-${{ matrix.arch }}/*.so
            !build-linux-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-cli
          path: |
            build-linux-${{ matrix.arch }}/yass_cli
            build-linux-${{ matrix.arch }}/*.so
            !build-linux-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-dylib
          path: |
            build-linux-${{ matrix.arch }}/devel
            build-linux-${{ matrix.arch }}/yass.py
            build-linux-${{ matrix.arch }}/*.so
      - uses: actions/upload-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-test
          path: |
            build-linux-${{ matrix.arch }}/yass_test
            build-linux-${{ matrix.arch }}/*.so
            !build-linux-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-benchmark
          path: |
            build-linux-${{ matrix.arch }}/yass_benchmark
            build-linux-${{ matrix.arch }}/*.so
            !build-linux-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-dbg
          path: |
            build-linux-${{ matrix.arch }}/*.dbg
          compression-level: 9 # maximum compression
      - name: Disable AppArmor Restrictions (i386 and amd64)
        if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
        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 (i386 and amd64)
        if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
        run: |
          rm -rf $SDK_ROOT/tmp
          mkdir -p $SDK_ROOT/etc/ssl/certs $SDK_ROOT/dev $SDK_ROOT/proc $SDK_ROOT/tmp $SDK_ROOT/build
          touch $SDK_ROOT/etc/resolv.conf
          timeout 120 bwrap --die-with-parent --bind $SDK_ROOT / \
            --ro-bind /sys /sys \
            --ro-bind /etc/resolv.conf /etc/resolv.conf \
            --ro-bind /etc/ssl/certs /etc/ssl/certs \
            --proc /proc --dev /dev \
            --unshare-all --share-net \
            --bind $PWD/build-linux-${{ matrix.arch }} /tmp \
            /tmp/yass_test
      - name: Run tests (non x86, under qemu emulation)
        if: ${{ matrix.arch != 'i386' && matrix.arch != 'amd64' }}
        run: |
          # TMPDIR is required by leveldb unittests
          timeout 120 qemu-${{ matrix.qemu_suffix }} -L $SDK_ROOT \
            -E TMPDIR=$PWD/build-linux-${{ matrix.arch }} \
            $PWD/build-linux-${{ matrix.arch }}/yass_test \
            --no_exec_proc_tests
      - 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*.tgz
  linux-binary-release-native-test:
    runs-on: ubuntu-22.04
    needs: linux-binary-release
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: i386
            build_type: Release
            qemu_suffix: i386
            gtest_repeat_times: 10
          - arch: i386
            build_type: Debug
            qemu_suffix: i386
            gtest_repeat_times: 2
          - arch: amd64
            build_type: Release
            qemu_suffix: amd64
            gtest_repeat_times: 10
          - arch: amd64
            build_type: Debug
            qemu_suffix: amd64
            gtest_repeat_times: 2
    steps:
      - name: Change ubuntu mirror
        if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
        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 (test purposes)
        if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
        run: |
          sudo dpkg --add-architecture i386
          sudo apt-get update -qq
          sudo apt-get install -y libcurl4-gnutls-dev:${{ matrix.arch }} # based on sysroot's libcurl
      - name: Download built executables
        uses: actions/download-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-test
      - name: Run tests
        if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
        run: |
          chmod 755 yass_test
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks4
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks4a
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks5
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks5h
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type http
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks4a --ipv6_mode
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks5 --ipv6_mode
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type socks5h --ipv6_mode
          ./yass_test --gtest_shuffle --gtest_repeat=${{ matrix.gtest_repeat_times }} --gtest_throw_on_failure --proxy_type http --ipv6_mode
  linux-binary-release-native-benchmark:
    runs-on: ubuntu-22.04
    needs: linux-binary-release
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: i386
            build_type: Release
            qemu_suffix: i386
          - arch: amd64
            build_type: Release
            qemu_suffix: amd64
    steps:
      - name: Download built executables
        uses: actions/download-artifact@v6
        with:
          name: linux-release-${{ matrix.build_type }}-${{ matrix.arch }}-${{ github.run_id }}-benchmark
      - name: Run benchmarks
        if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
        run: |
          chmod 755 yass_benchmark
          ./yass_benchmark
