name: Build Artifacts (RPM)
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]
    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:
  docker_publish:
    strategy:
      fail-fast: false
      matrix:
        include:
          - container: 'fedora41'
            platform: 'linux/amd64'
          - container: 'alpine320'
            platform: 'linux/amd64'
          - container: 'i386-alpine320'
            platform: 'linux/386'
          - container: 'armhf-alpine320'
            platform: 'linux/arm/v7'
          - container: 'aarch64-alpine320'
            platform: 'linux/arm64'
          - container: 'opensuse15'
            platform: 'linux/amd64'
    runs-on: ubuntu-24.04
    permissions:
      packages: write
    steps:
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v4
        with:
          image: tonistiigi/binfmt:qemu-v7.0.0-28
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4
        with:
          platforms: ${{ matrix.platform }}
      - uses: actions/checkout@v5
      - name: Cache docker files (dummy)
        id: docker-cache
        uses: actions/cache@v5
        with:
          path: |
            docker/dummy
          key: ${{ runner.os }}-docker-${{ matrix.container }}-${{ hashFiles(format('docker/{0}.Dockerfile', matrix.container)) }}-v1
      - name: Login to GitHub Container Registry
        if: ${{ steps.docker-cache.outputs.cache-hit != 'true' }}
        uses: docker/login-action@v4
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Build and Push Docker Image
        if: ${{ steps.docker-cache.outputs.cache-hit != 'true' }}
        run: |
          cd docker
          docker buildx build --cache-from ghcr.io/hukeyue/${{ matrix.container }} \
            --platform ${{ matrix.platform }} \
            --push \
            -f ${{ matrix.container }}.Dockerfile \
            -t ghcr.io/hukeyue/${{ matrix.container }} \
            .
  rpm-release:
    strategy:
      fail-fast: false
      matrix:
        include:
          # - container: 'fedora41'
          #   gui_variant: gtk3
          #   use_cet: true
          # - container: 'fedora41'
          #   gui_variant: gtk4
          #   use_cet: true
          # https://rpmfind.net/linux/rpm2html/search.php?query=glibc-devel&submit=Search+...&system=&arch=
          # glibc: 2.40 for fedora41
          # - container: 'fedora41'
          #   gui_variant: qt6
          #   use_cet: true
          - container: 'opensuse15'
            gui_variant: gtk3
          - container: 'opensuse15'
            gui_variant: qt5
          # except for gtk4 and qt6 packages
          # glibc: 2.31 for opensuse15.5
          - container: 'opensuse15'
            gui_variant: gtk4
          - container: 'opensuse15'
            gui_variant: qt6
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    needs: docker_publish
    container:
      image: 'ghcr.io/hukeyue/${{ matrix.container }}'
    steps:
      - uses: actions/checkout@v5
      - name: Checkout with shallow submodules
        run: |
          # ignore annoying failures
          git config --global --add safe.directory "$PWD"
          # unshallow must come first otherwise submodule may be get unshallowed
          git fetch --tags --unshallow
          git submodule update --init --depth 1
      - 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: "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: Set clang environment
        run: |
          # use custom compiler
          echo "CC=${{ github.workspace }}/third_party/llvm-build/Release+Asserts/bin/clang" >> $GITHUB_ENV
          echo "CXX=${{ github.workspace }}/third_party/llvm-build/Release+Asserts/bin/clang++" >> $GITHUB_ENV
          echo "USE_CLANG=1" >> $GITHUB_ENV
      - name: Set cet option
        if: ${{ matrix.use_cet == true }}
        run: |
          # use CET
          echo "USE_CET=1" >> $GITHUB_ENV
      - name: Set c++ standard library environment
        run: |
          # use custom libc++
          echo "USE_LIBCXX=1" >> $GITHUB_ENV
      - name: Set QT6 environment
        if: ${{ matrix.gui_variant == 'qt6' }}
        run: |
          echo "USE_QT6=1" >> $GITHUB_ENV
      - name: Set QT5 environment
        if: ${{ matrix.gui_variant == 'qt5' }}
        run: |
          echo "USE_QT5=1" >> $GITHUB_ENV
      - name: Set GTK4 environment
        if: ${{ matrix.gui_variant == 'gtk4' }}
        run: |
          echo "USE_GTK4=1" >> $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 RPM packages
        run: |
          ./scripts/build-rpm.sh
      - uses: actions/upload-artifact@v6
        with:
          name: rpm-release-${{ matrix.container }}-${{ matrix.gui_variant }}-${{ github.run_id }}
          path: |
            yass*.rpm
          compression-level: 0 # no compression
      - name: Upload dist tarball (including debuginfo)
        if: ${{ github.event_name == 'release' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload --clobber ${{ github.event.release.tag_name }} yass*.rpm
  musl-release:
    strategy:
      fail-fast: false
      matrix:
        include:
          - container: 'alpine320'
            arch: amd64
            build_type: MinSizeRel
            platform: 'linux/amd64'
            build_options:
          - container: 'i386-alpine320'
            arch: i386
            build_type: MinSizeRel
            platform: 'linux/386'
            build_options:
          - container: 'armhf-alpine320'
            arch: armhf
            build_type: MinSizeRel
            platform: 'linux/arm/v7'
            build_options:
          - container: 'aarch64-alpine320'
            arch: aarch64
            build_type: MinSizeRel
            platform: 'linux/arm64'
            build_options:
    runs-on: ubuntu-24.04
    permissions:
      contents: write
    env:
      BUILD_TYPE: ${{ matrix.build_type }}
    needs: docker_publish
    steps:
      - uses: actions/checkout@v5
      - name: Checkout with shallow submodules
        run: |
          # ignore annoying failures
          git config --global --add safe.directory "$PWD"
          # unshallow must come first otherwise submodule may be get unshallowed
          git fetch --tags --unshallow
          git submodule update --init --depth 1
      - name: Set SDK_ROOT
        run: |
          echo "SDK_ROOT=${{ github.workspace }}/${{ matrix.arch }}-alpine320-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: "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: 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: Populate sysroot from docker image
        env:
          DOCKER_DEFAULT_PLATFORM: ${{ matrix.platform }}
        run: |
          docker pull ghcr.io/hukeyue/${{ matrix.container }}
          mkdir -p "${{ env.SDK_ROOT }}"
          docker export $(docker create ghcr.io/hukeyue/${{ matrix.container }}) | tar -C "${{ env.SDK_ROOT }}" -xf -
      - 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: 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
      - name: Build Binary (CLI)
        run: |
          ./tools/build --variant cli --arch ${{ matrix.arch }} --system linux --subsystem musl --sysroot "${{ env.SDK_ROOT }}" -use-static-build -nc -build-test -build-benchmark ${{ matrix.build_options }}
      - name: Build Binary (GUI)
        run: |
          ./tools/build --variant gui --arch ${{ matrix.arch }} --system linux --subsystem musl --sysroot "${{ env.SDK_ROOT }}" -nc ${{ matrix.build_options }}
      - name: Build Binary (DYLIB)
        run: |
          ./tools/build --variant dylib --arch ${{ matrix.arch }} --system linux --subsystem musl --sysroot "${{ env.SDK_ROOT }}" -use-static-build -nc ${{ matrix.build_options }}
      - uses: actions/upload-artifact@v6
        with:
          name: musl-release-${{ matrix.build_type }}-${{ matrix.container }}-${{ github.run_id }}-gui
          path: |
            build-linux-musl-${{ matrix.arch }}/yass
            build-linux-musl-${{ matrix.arch }}/*.so
            !build-linux-musl-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: musl-release-${{ matrix.build_type }}-${{ matrix.container }}-${{ github.run_id }}-cli
          path: |
            build-linux-musl-${{ matrix.arch }}/yass_cli
            build-linux-musl-${{ matrix.arch }}/*.so
            !build-linux-musl-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: musl-release-${{ matrix.build_type }}-${{ matrix.container }}-${{ github.run_id }}-dylib
          path: |
            build-linux-musl-${{ matrix.arch }}/devel
            build-linux-musl-${{ matrix.arch }}/yass.py
            build-linux-musl-${{ matrix.arch }}/*.so
      - uses: actions/upload-artifact@v6
        with:
          name: musl-release-${{ matrix.build_type }}-${{ matrix.container }}-${{ github.run_id }}-test
          path: |
            build-linux-musl-${{ matrix.arch }}/yass_test
            build-linux-musl-${{ matrix.arch }}/*.so
            !build-linux-musl-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: musl-release-${{ matrix.build_type }}-${{ matrix.container }}-${{ github.run_id }}-benchmark
          path: |
            build-linux-musl-${{ matrix.arch }}/yass_benchmark
            build-linux-musl-${{ matrix.arch }}/*.so
            !build-linux-musl-${{ matrix.arch }}/libYASS-C.so
      - uses: actions/upload-artifact@v6
        with:
          name: musl-release-${{ matrix.build_type }}-${{ matrix.container }}-${{ github.run_id }}-dbg
          path: |
            build-linux-musl-${{ 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: |
          bwrap --die-with-parent --bind "$SDK_ROOT" / \
            --ro-bind /sys /sys \
            --ro-bind /etc/resolv.conf /etc/resolv.conf \
            --proc /proc --dev /dev \
            --unshare-all --share-net \
            --bind $PWD/build-linux-musl-${{ matrix.arch }} /tmp \
            /tmp/yass_test
      - name: Run tests (non x86, under qemu emulation)
        if: ${{ matrix.arch != 'i386' && matrix.arch != 'amd64' }}
        run: |
          # copy required resolv.conf from host
          cp -fv /etc/resolv.conf "$SDK_ROOT/etc/resolv.conf"
          # TMPDIR is required by leveldb unittests
          qemu-${{ matrix.arch }} -L $SDK_ROOT \
            -E TMPDIR=$PWD/build-linux-musl-${{ matrix.arch }} \
            $PWD/build-linux-musl-${{ matrix.arch }}/yass_test \
            --no_exec_proc_tests
      - name: Upload dist tarball (including debuginfo)
        if: ${{ github.event_name == 'release' && matrix.build_type == 'MinSizeRel' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload ${{ github.event.release.tag_name }} yass*.tgz
      - name: Cleanup before Cache callbacks
        run: |
          rm -rf build-*

