name: BLAS tests (Linux)

# This file is meant for testing different BLAS/LAPACK flavors and build
# options on Linux. All other yml files for Linux will only test without BLAS
# (mostly because that's easier and faster to build) or with the same 64-bit
# OpenBLAS build that is used in the wheel jobs.
#
# Jobs and their purpose:
#
#   - openblas32_stable_nightly:
#         Uses the 32-bit OpenBLAS builds, both the latest stable release
#         and a nightly build.
#   - openblas_no_pkgconfig_fedora:
#         Test OpenBLAS on Fedora. Fedora doesn't ship .pc files for OpenBLAS,
#         hence this exercises the "system dependency" detection method.
#   - flexiblas_fedora:
#         Tests FlexiBLAS (the default on Fedora for its own packages), via
#         pkg-config. FlexiBLAS allows runtime switching of BLAS/LAPACK
#         libraries, which is a useful capability (not tested in this job).
#   - openblas_cmake:
#         Tests whether OpenBLAS LP64 is detected correctly when only CMake
#         and not pkg-config is installed.
#   - netlib-debian:
#         Installs libblas/liblapack, which in Debian contains libcblas within
#         libblas.
#   - netlib-split:
#         Installs vanilla Netlib blas/lapack with separate libcblas, which is
#         the last option tried in auto-detection.
#   - mkl:
#         Tests MKL installed from PyPI (because easiest/fastest, if broken) in
#         3 ways: both LP64 and ILP64 via pkg-config, and then using the
#         Single Dynamic Library (SDL, or `libmkl_rt`).
#   - blis:
#         Simple test for LP64 via pkg-config
#   - atlas:
#         Simple test for LP64 via pkg-config

on:
  pull_request:
    branches:
      - main
      - maintenance/**
    paths-ignore:
      - '**.pyi'
      - '**.md'
      - '**.rst'
      - 'tools/stubtest/**'

defaults:
  run:
    shell: bash

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

permissions:
  contents: read # to fetch code (actions/checkout)

jobs:
  openblas32_stable_nightly:
    # To enable this workflow on a fork, comment out:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        USE_NIGHTLY_OPENBLAS: [false, true]
    env:
      USE_NIGHTLY_OPENBLAS: ${{ matrix.USE_NIGHTLY_OPENBLAS }}
    name: "Test Linux (${{ matrix.USE_NIGHTLY_OPENBLAS && 'nightly' || 'stable' }} OpenBLAS)"
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt
        # Install OpenBLAS
        if [[ $USE_NIGHTLY_OPENBLAS == "true" ]]; then
            python -m pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple scipy-openblas32
        else
            python -m pip install -r requirements/ci32_requirements.txt
        fi
        mkdir -p ./.openblas
        python -c"import scipy_openblas32 as ob32; print(ob32.get_pkg_config())" > ./.openblas/scipy-openblas.pc

    - name: Build
      shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"'
      env:
        TERM: xterm-256color
      run:
        spin build -- --werror -Dallow-noblas=false -Dpkg_config_path=${PWD}/.openblas

    - name: Check build-internal dependencies
      run:
        ninja -C build -t missingdeps

    - name: Check installed test and stub files
      run:
        python tools/check_installed_files.py $(find ./build-install -path '*/site-packages/numpy')
    - name: Ensure scipy-openblas
      run: |
        set -ex
        spin python tools/check_openblas_version.py -- --min-version 0.3.30

    - name: Test
      shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"'
      env:
        TERM: xterm-256color
      run: |
        spin test -j auto -- --timeout=600 --durations=10


  openblas_no_pkgconfig_fedora:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    container: fedora:39
    name: "OpenBLAS (Fedora, no pkg-config, LP64/ILP64)"
    steps:
    - name: Install system dependencies
      run: |
        dnf install git gcc-gfortran g++ python3-devel openblas-devel -y

    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt

    - name: Build (LP64)
      run: spin build -- -Dblas=openblas -Dlapack=openblas -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10

    - name: Build (ILP64)
      run: |
        rm -rf build
        spin build -- -Duse-ilp64=true -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10


  flexiblas_fedora:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    container: fedora:39
    name: "FlexiBLAS (LP64, ILP64 on Fedora)"
    steps:
    - name: Install system dependencies
      run: |
        dnf install git gcc-gfortran g++ python3-devel flexiblas-devel -y

    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt

    - name: Build
      run: spin build -- -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10

    - name: Build (ILP64)
      run: |
        rm -rf build
        spin build -- -Ddisable-optimization=true -Duse-ilp64=true -Dallow-noblas=false

    - name: Test (ILP64)
      run: spin test -- numpy/linalg --timeout=600 --durations=10


  openblas_cmake:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    name: "OpenBLAS with CMake"
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt
        sudo apt-get update
        sudo apt-get install libopenblas-dev cmake
        sudo apt-get remove pkg-config

    - name: Build
      run: spin build -- -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -j auto -- numpy/linalg  --timeout=600 --durations=10


  netlib-debian:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    name: "Debian libblas/liblapack"
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt
        sudo apt-get update
        sudo apt-get install liblapack-dev pkg-config

    - name: Build
      run: |
        spin build -- -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: |
        spin test -j auto -- numpy/linalg  --timeout=600 --durations=10


  netlib-split:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    container: opensuse/tumbleweed
    name: "OpenSUSE Netlib BLAS/LAPACK"
    steps:
    - name: Install system dependencies
      run: |
        # No blas.pc on OpenSUSE as of Nov 2023, so no need to install pkg-config.
        # If it is needed in the future, use install name `pkgconf-pkg-config`
        zypper install -y git gcc-c++ python3-pip python3-devel blas cblas lapack

    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false

    - name: Install PyPI dependencies
      run: |
        pip install --break-system-packages -r requirements/build_requirements.txt

    - name: Build
      run: |
        spin build -- -Dblas=blas -Dlapack=lapack -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: |
        # do not use test_requirements.txt, it includes coverage which requires
        # sqlite3, which is not available on OpenSUSE python
        pip install --break-system-packages pytest pytest-xdist hypothesis pytest-timeout
        spin test -j auto -- numpy/linalg  --timeout=600 --durations=10


  mkl:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    name: "MKL (LP64, ILP64, SDL)"
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt
        pip install mkl mkl-devel

    - name: Repair MKL pkg-config files and symlinks
      run: |
        # MKL 2023.2 works when installed from conda-forge (except for `-iomp`
        # and `-tbb` pkg-config files), Spack, or with the standalone Intel
        # installer. The standalone installer is the worst option, since it's
        # large and clumsy to install and requires running a setvars.sh script
        # before things work. The PyPI MKL packages are broken and need the
        # fixes in this step. For details, see
        # https://github.com/conda-forge/intel_repack-feedstock/issues/34
        cd $Python3_ROOT_DIR/lib/pkgconfig
        sed -i 's/\/intel64//g' mkl*.pc
        # add the expected .so -> .so.2 symlinks to fix linking
        cd ..
        for i in $( ls libmkl*.so.2 ); do ln -s $i ${i%.*}; done
        for i in $( ls libmkl*.so.3 ); do ln -s $i ${i%.*}; done

    - name: Build with defaults (LP64)
      run: |
        pkg-config --libs mkl-dynamic-lp64-seq  # check link flags
        spin build -- -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10

    - name: Build with ILP64
      run: |
        git clean -xdf > /dev/null
        pkg-config --libs mkl-dynamic-ilp64-seq
        spin build -- -Duse-ilp64=true -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10

    - name: Build without pkg-config (default options, SDL)
      run: |
        git clean -xdf > /dev/null
        pushd $Python3_ROOT_DIR/lib/pkgconfig
        rm mkl*.pc
        popd
        export MKLROOT=$Python3_ROOT_DIR
        spin build -- -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10

  blis:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    name: "BLIS"
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt
        sudo apt-get update
        sudo apt-get install libblis-dev libopenblas-dev pkg-config

    - name: Add BLIS pkg-config file
      run: |
        # Needed because blis.pc missing in Debian:
        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=989076
        # The alternative here would be to use another distro or Miniforge
        sudo cp tools/ci/_blis_debian.pc /usr/lib/x86_64-linux-gnu/pkgconfig/blis.pc
        # Check if the patch works:
        pkg-config --libs blis
        pkg-config --cflags blis

    - name: Build
      run: spin build -- -Dblas=blis -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg --timeout=600 --durations=10

  atlas:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    name: "ATLAS"
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        pip install -r requirements/build_requirements.txt
        pip install -r requirements/build_requirements.txt -r requirements/test_requirements.txt
        sudo apt-get update
        sudo apt-get install libatlas-base-dev pkg-config

    - name: Build
      run: spin build -- -Dblas=blas-atlas -Dlapack=lapack-atlas -Ddisable-optimization=true -Dallow-noblas=false

    - name: Test
      run: spin test -- numpy/linalg
