
name: "Build & Test"

on:
  # allow direct trigger
  workflow_dispatch:
  push:
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  GCC_VERSION: "11"
  LLVM_VERSION: "17"
  COMMON_CMAKE_FLAGS: >
    -DSLEEF_SHOW_CONFIG=1
    -DSLEEF_BUILD_GNUABI_LIBS=ON
    -DSLEEF_BUILD_INLINE_HEADERS=ON
    -DSLEEF_BUILD_DFT=ON
    -DSLEEF_BUILD_QUAD=ON
    -DSLEEF_BUILD_SCALAR_LIB=ON
    -DSLEEF_BUILD_STATIC_TEST_BINS=ON
    -DSLEEF_ENFORCE_TESTER=ON
    -DSLEEF_ENFORCE_TESTER3=ON

jobs:
  build-native:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        compiler: [gcc, llvm]

    name: build-native-${{ matrix.compiler }}
    steps:
      - uses: actions/checkout@v4.1.1
        with:
          persist-credentials: false

      - name: Install dependencies
        run: |
          sudo apt-get update -y -qq
          sudo apt-get install -y -qq build-essential curl ninja-build libgmp-dev libmpfr-dev

      # Needed for llvm builds as well for target libraries
      - name: Install gcc
        run: |
          sudo apt-get install -y -qq gcc-${GCC_VERSION}

      - name: Install llvm
        run: |
          curl -o llvm.sh https://apt.llvm.org/llvm.sh
          chmod u+x llvm.sh
          sudo ./llvm.sh ${LLVM_VERSION}
          sudo ln -srf $(which clang-${LLVM_VERSION}) /usr/bin/clang
          rm llvm.sh
        if: ${{ matrix.compiler == 'llvm' }}

      - name: Build native
        shell: bash -ex -o pipefail {0}
        run: |
          EXTRA_CMAKE_FLAGS="-DSLEEF_ENFORCE_SSE2=ON -DSLEEF_ENFORCE_SSE4=ON -DSLEEF_ENFORCE_AVX=ON -DSLEEF_ENFORCE_AVX=ON -DSLEEF_ENFORCE_AVX2=ON -DSLEEF_ENFORCE_AVX512F=ON -DSLEEF_ENFORCE_FMA4=ON"
          cmake -S . -B _build-native -GNinja \
            -DCMAKE_INSTALL_PREFIX=$(pwd)/_install-native \
            -DCMAKE_TOOLCHAIN_FILE=$(pwd)/toolchains/native-${{ matrix.compiler }}.cmake \
            ${COMMON_CMAKE_FLAGS} \
            ${EXTRA_CMAKE_FLAGS}
          cmake --build _build-native
          cmake --install _build-native

      - name: Upload build-native-${{ matrix.compiler }} artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-native-${{ matrix.compiler }}
          path: |
            _build-*
            _install-*
        if: always()

  test-native:
    runs-on: ubuntu-latest
    needs: [build-native]
    strategy:
      fail-fast: false
      matrix:
        compiler: [gcc, llvm]

    name: test-native-${{ matrix.compiler }}
    steps:
      - uses: actions/checkout@v4.1.1
        with:
          persist-credentials: false

      - name: Install dependencies
        run: sudo apt-get update -y -qq && sudo apt-get install -y -qq libgmp-dev libmpfr-dev

      - name: Print host CPU info
        run: |
          cat /proc/cpuinfo

      - name: Download build-native-${{ matrix.compiler }} artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-native-${{ matrix.compiler }}

      - name: Fix _build-native permissions
        run: |
          chmod +x _build-native/bin/*

      - name: Test native
        env:
          CTEST_OUTPUT_ON_FAILURE: "TRUE"
        run: |
          cd _build-native
          ctest -j$(nproc)

      - name: Upload test-native artifacts
        uses: actions/upload-artifact@v3
        with:
          name: test-native
          path: |
            _build-native/Testing
        if: always()

  build-cross:
    runs-on: ubuntu-latest
    needs: [build-native]
    strategy:
      fail-fast: false
      matrix:
        arch: [aarch64, armhf, ppc64el, s390x, riscv64]
        compiler: [gcc, llvm]
        include:
          - arch: armhf
            binfmt: arm
            gnupkg: -arm-linux-gnueabihf
          - arch: ppc64el
            binfmt: ppc64le
            gnupkg: -powerpc64le-linux-gnu
          - arch: aarch64
            debarch: arm64
        exclude:
          # Only GCC trunk supports the RISC-V V intrinsics and https://github.com/riscv-collab/riscv-gnu-toolchain
          # doesn't track a recent enough version yet
          - arch: riscv64
            compiler: gcc

    name: build-${{ matrix.arch }}-${{ matrix.compiler }}
    steps:
      - uses: actions/checkout@v4.1.1
        with:
          persist-credentials: false

      - name: Install dependencies
        run: |
          sudo apt-get update -y -qq
          sudo apt-get install -y -qq build-essential curl ninja-build libgmp-dev libmpfr-dev debootstrap

      # Needed for llvm builds as well for target libraries
      - name: Install gcc
        run: |
          sudo apt-get install -y -qq gcc-${GCC_VERSION}${{ matrix.gnupkg || format('-{0}-linux-gnu', matrix.arch) }}

      - name: Install llvm
        run: |
          curl -o llvm.sh https://apt.llvm.org/llvm.sh
          chmod u+x llvm.sh
          sudo ./llvm.sh ${LLVM_VERSION}
          sudo ln -srf $(which clang-${LLVM_VERSION}) /usr/bin/clang
          rm llvm.sh
        if: ${{ matrix.compiler == 'llvm' }}

      - name: Setup QEMU
        uses: docker/setup-qemu-action@v3.0.0
        with:
          platforms: ${{ matrix.binfmt || matrix.arch }}

      - name: Check sysroot cache
        id: check-sysroot-cache
        uses: actions/cache@v3
        with:
          path: sysroot
          key: sysroot-${{ matrix.arch }}-${{ hashFiles('./.github/workflows/build_and_test.yml') }}

      - name: Create sysroot
        run: |
          sudo debootstrap --arch=${{ matrix.debarch || matrix.arch }} --verbose --include=fakeroot,symlinks,libmpfr-dev,libssl-dev --resolve-deps --variant=minbase --components=main,universe focal sysroot
          # Remove unused files to minimize cache
          sudo chroot sysroot symlinks -cr .
          sudo chown ${USER} -R sysroot
          rm -rf sysroot/{dev,proc,run,sys,var}
          rm -rf sysroot/usr/{sbin,bin,share}
          rm -rf sysroot/usr/lib/{apt,gcc,udev,systemd}
          rm -rf sysroot/usr/libexec/gcc
        if: steps.check-sysroot-cache.outputs.cache-hit != 'true'

      - name: Download build-native-${{ matrix.compiler }} artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-native-${{ matrix.compiler }}

      - name: Fix _build-native permissions
        run: |
          chmod +x _build-native/bin/*

      - name: Build ${{ matrix.arch }}
        shell: bash -ex -o pipefail {0}
        run: |
          EXTRA_CMAKE_FLAGS=""
          if [[ ${{ matrix.arch }} = "aarch64" ]]; then
            EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DSLEEF_ENFORCE_SVE=ON"
          elif [[ ${{ matrix.arch }} = "armhf" ]]; then
            # Disable inline headers, they just don't compile on armhf
            EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DSLEEF_BUILD_INLINE_HEADERS=OFF"
          elif [[ ${{ matrix.arch }} = "ppc64el" ]]; then
            EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DSLEEF_ENFORCE_VSX=ON -DSLEEF_ENFORCE_VSX3=ON"
          elif [[ ${{ matrix.arch }} = "s390x" ]]; then
            EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DSLEEF_ENFORCE_VXE=ON"
            # Disable VXE2 support, QEMU doesn't support some instructions generated by gcc or llvm
            EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DSLEEF_DISABLE_VXE2=ON"
          elif [[ ${{ matrix.arch }} = "riscv64" ]]; then
            EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DSLEEF_ENFORCE_RVVM1=ON -DSLEEF_ENFORCE_RVVM2=ON"
          fi

          cmake -S . -B _build-${{ matrix.arch }} -GNinja \
            -DCMAKE_INSTALL_PREFIX="$(pwd)/_install-${{ matrix.arch }}" \
            -DCMAKE_TOOLCHAIN_FILE=$(pwd)/toolchains/${{ matrix.arch }}-${{ matrix.compiler }}.cmake \
            -DCMAKE_SYSROOT=$(pwd)/sysroot \
            -DNATIVE_BUILD_DIR="$(pwd)/_build-native" \
            ${COMMON_CMAKE_FLAGS} \
            ${EXTRA_CMAKE_FLAGS}
          cmake --build _build-${{ matrix.arch }}
          cmake --install _build-${{ matrix.arch }}

      - name: Upload build-${{ matrix.arch }}-${{ matrix.compiler }} artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-${{ matrix.arch }}-${{ matrix.compiler }}
          path: |
            _build-${{ matrix.arch }}
            _install-${{ matrix.arch }}
        if: always()

  test-cross:
    runs-on: ubuntu-latest
    needs: [build-native, build-cross]
    strategy:
      fail-fast: false
      matrix:
        include:
          # AArch64
          - arch: aarch64
            compiler: gcc
            qemu_cpu: "max,sve=off"
          - arch: aarch64
            compiler: gcc
            qemu_cpu: "max,sve=on,sve128=on"
          - arch: aarch64
            compiler: gcc
            qemu_cpu: "max,sve=on,sve256=on"
          - arch: aarch64
            compiler: gcc
            qemu_cpu: "max,sve=on,sve512=on"
          - arch: aarch64
            compiler: llvm
            qemu_cpu: "max,sve=off"
          - arch: aarch64
            compiler: llvm
            qemu_cpu: "max,sve=on,sve128=on"
          - arch: aarch64
            compiler: llvm
            qemu_cpu: "max,sve=on,sve256=on"
          - arch: aarch64
            compiler: llvm
            qemu_cpu: "max,sve=on,sve512=on"
          # Aarch32
          - arch: armhf
            compiler: gcc
            binfmt: arm
            qemu_cpu: "max"
          - arch: armhf
            compiler: llvm
            binfmt: arm
            qemu_cpu: "max"
          # PPC64
          - arch: ppc64el
            compiler: gcc
            binfmt: ppc64le
            qemu_cpu: "power10"
          - arch: ppc64el
            compiler: llvm
            binfmt: ppc64le
            qemu_cpu: "power10"
          # IBM Z
          # TODO: figure out qemu_cpu variable to make tests pass on QEMU
          - arch: s390x
            compiler: gcc
          - arch: s390x
            compiler: llvm
          # RISC-V
          # - arch: riscv64
          #   compiler: gcc
          #   qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=false"
          # - arch: riscv64
          #   compiler: gcc
          #   qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=true,vlen=128,elen=64,vext_spec=v1.0"
          # - arch: riscv64
          #   compiler: gcc
          #   qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=true,vlen=256,elen=64,vext_spec=v1.0"
          # - arch: riscv64
          #   compiler: gcc
          #   qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=true,vlen=512,elen=64,vext_spec=v1.0"
          - arch: riscv64
            compiler: llvm
            qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=false"
          - arch: riscv64
            compiler: llvm
            qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=true,vlen=128,elen=64,vext_spec=v1.0"
          - arch: riscv64
            compiler: llvm
            qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=true,vlen=256,elen=64,vext_spec=v1.0"
          - arch: riscv64
            compiler: llvm
            qemu_cpu: "rv64,zba=true,zbb=true,zbs=true,v=true,vlen=512,elen=64,vext_spec=v1.0"

    name: "test-${{ matrix.arch }}-${{ matrix.compiler }} (qemu_cpu: \"${{ matrix.qemu_cpu }}\")"
    steps:
      - uses: actions/checkout@v4.1.1
        with:
          persist-credentials: false

      - name: Setup QEMU
        uses: docker/setup-qemu-action@v3.0.0
        with:
          platforms: ${{ matrix.binfmt || matrix.arch }}

      - name: Install dependencies
        run: sudo apt-get update -y -qq && sudo apt-get install -y -qq libgmp-dev libmpfr-dev

      - name: Print host CPU info
        run: |
          cat /proc/cpuinfo

      - name: Download build-native-${{ matrix.compiler }} artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-native-${{ matrix.compiler }}

      - name: Download build-${{ matrix.arch }}-${{ matrix.compiler }} artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-${{ matrix.arch }}-${{ matrix.compiler }}

      - name: Fix _build-native and _build-${{ matrix.arch }} permissions
        run: |
          chmod +x _build-native/bin/* _build-${{ matrix.arch }}/bin/*

      - name: Test ${{ matrix.arch }}
        env:
          CTEST_OUTPUT_ON_FAILURE: "TRUE"
        run: |
          if [[ -n "${{ matrix.qemu_cpu }}" ]]; then
            export QEMU_CPU="${{ matrix.qemu_cpu }}"
          fi
          cd _build-${{ matrix.arch }}
          ctest -j$(nproc)

      - name: Upload test-${{ matrix.arch }}-${{ matrix.compiler }}-${{ strategy.job-index }} artifacts
        uses: actions/upload-artifact@v3
        with:
          name: test-${{ matrix.arch }}-${{ matrix.compiler }}-${{ strategy.job-index }}
          path: |
            _build-${{ matrix.arch }}/Testing
        if: always()
