name: ci
on: [ push, pull_request ]

jobs:
  lint:
    name: ${{ matrix.component }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 15
    strategy:
      fail-fast: false
      matrix:
        os: [ ubuntu-latest, macOS-latest, windows-latest ]
        component: [ clippy ]
        include:
          - component: rustfmt
            cargo_cmd: fmt -- --check
            os: ubuntu-latest
          - component: clippy
            cargo_cmd: clippy --locked --all-targets -- -D warnings -A unknown-lints -A clippy::type_complexity -A clippy::new-without-default
    steps:
      - name: Clone repository
        uses: actions/checkout@v5

      - name: Install rust
        uses: ./.github/actions/rust-toolchain
        with:
          components: ${{ matrix.component }}
          # Oldest supported version, keep in sync with README.md
          toolchain: "1.85.0"

      - name: clippy version
        run: cargo clippy --version
        if: ${{ matrix.component == 'clippy' }}

      - name: Check
        run: cargo ${{ matrix.cargo_cmd }}

  check_features:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        feature: [ azure, gcs, gha, memcached, redis, s3, webdav ]
    steps:
      - name: Clone repository
        uses: actions/checkout@v5

      - name: Check feature ${{ matrix.feature }}
        run: cargo check --no-default-features --features ${{ matrix.feature }}

  toml_format:
    runs-on: ubuntu-latest
    steps:
      - name: Clone repository
        uses: actions/checkout@v5

      - name: Check
        run: npx --yes @taplo/cli fmt --check



  test:
    name: test ${{ matrix.os }} rust ${{ matrix.rustc || 'stable' }} ${{ matrix.extra_desc }}
    runs-on: ${{ matrix.os }}
    continue-on-error: ${{ matrix.allow_failure || false }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-22.04
            # Oldest supported version, keep in sync with README.md
            rustc: "1.85.0"
            extra_desc: dist-tests
            extra_args: --no-default-features --features=dist-tests test_dist_ -- --test-threads 1
          - os: ubuntu-22.04
            rustc: stable
            extra_desc: dist-server
            extra_args: --features=dist-server
          - os: ubuntu-22.04
            rustc: stable
          - os: ubuntu-22.04
            rustc: beta
          - os: ubuntu-22.04
            rustc: nightly
            allow_failure: true
            extra_args: --features=unstable
          - os: ubuntu-22.04
            extra_desc: no-default-features
            extra_args: --no-default-features
            allow_failure: true
          - os: ubuntu-22.04
            cuda: "11.8"
            extra_desc: cuda11.8
          - os: ubuntu-24.04
            cuda: "12.8"
            # Oldest supported version, keep in sync with README.md
            rustc: "1.85.0"
            extra_desc: cuda12.8
          # # M1 CPU
          - os: macos-14
          - os: macos-15-intel
          - os: windows-2022
            cuda: "12.8"
            # Oldest supported version, keep in sync with README.md
            rustc: "1.85.0"
            extra_args: --no-fail-fast
            notest_cuda_compilers: clang++
            extra_desc: cuda12.8
          - os: windows-2022
            cuda: "12.8"
            rustc: nightly
            allow_failure: true
            extra_args: --features=unstable
            notest_cuda_compilers: clang++
            extra_desc: cuda12.8
          - os: windows-2022
            cuda: "12.8"
            rustc: beta
            notest_cuda_compilers: clang++
            extra_desc: cuda12.8
            no_coverage: true
    env:
      RUST_BACKTRACE: 1
      COVERAGE_REPORT_DIR: "target/debug"
      COVERAGE_REPORT_FILE: "target/debug/lcov.info"
      BINARY_DIR: "target/debug"
      GRCOV_IGNORE_OPTION: '--ignore build.rs --ignore "/*" --ignore "[a-zA-Z]:/*"'
      GRCOV_EXCLUDE_OPTION: '--excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"'
      NOTEST_CUDA_COMPILERS: ${{ matrix.notest_cuda_compilers || '' }}
      CARGO_PROFILE_DEV_DEBUG: '0' # save disk space
    steps:
      - if: ${{ contains(matrix.os, 'windows') }}
        uses: ilammy/msvc-dev-cmd@v1

      - name: Clone repository
        uses: actions/checkout@v5

      - name: Install rust
        uses: ./.github/actions/rust-toolchain
        with:
          toolchain: ${{ matrix.rustc }}
          components: llvm-tools-preview

      - if: ${{ contains(matrix.os, 'ubuntu') }}
        name: Install gcc & clang for tests
        env:
          DEBIAN_FRONTEND: noninteractive
        run: |
          set -x
          # Conflicts with clang-cuda
          if dpkg -s gcc-14 >/dev/null 2>&1; then
            sudo apt remove -y gcc-14 g++-14
            sudo apt autoremove -y
          fi
          sudo apt install -y --no-install-recommends gcc clang
          echo 'gcc version:'
          gcc --version
          echo 'clang version:'
          clang --version

      - if: ${{ matrix.cuda != '' && (runner.os == 'Linux' || runner.os == 'Windows') }}
        name: Install nvcc
        uses: ./.github/actions/nvcc-toolchain
        with:
          cuda-version: ${{ matrix.cuda }}

      - name: "`grcov` ~ install"
        if: ${{ ! matrix.no_coverage }}
        shell: bash
        run: |
          GRCOV_VERSION="0.10.7"
          if [[ "$RUNNER_OS" == "Windows" ]]; then
            TARGET="x86_64-pc-windows-msvc"
            EXT="zip"
          elif [[ "$RUNNER_OS" == "macOS" && "$RUNNER_ARCH" == "ARM64" ]]; then
            TARGET="aarch64-apple-darwin"
            EXT="tar.bz2"
          elif [[ "$RUNNER_OS" == "macOS" ]]; then
            TARGET="x86_64-apple-darwin"
            EXT="tar.bz2"
          else
            TARGET="x86_64-unknown-linux-musl"
            EXT="tar.bz2"
          fi
          URL="https://github.com/mozilla/grcov/releases/download/v${GRCOV_VERSION}/grcov-${TARGET}.${EXT}"
          curl -sL "$URL" -o "grcov.${EXT}"
          if [[ "$EXT" == "zip" ]]; then
            unzip -q "grcov.${EXT}" -d "$HOME/.cargo/bin/"
          else
            tar -xjf "grcov.${EXT}" -C "$HOME/.cargo/bin/"
          fi

      - name: Create config for testing
        if: ${{ ! matrix.no_coverage }}
        run: |
          mkdir -p .cargo
          echo '[env]
          LLVM_PROFILE_FILE = { value = "target/debug/coverage/default-%p-%8m.profraw", relative = true }' >> .cargo/config.toml

      - if: ${{ matrix.cuda != '' && runner.os == 'Linux' }}
        name: Free disk space for CUDA tests
        run: |
          sudo apt clean
          sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost /usr/share/swift /var/lib/apt/lists/*

      - name: Execute tests
        run: cargo test --locked --lib --bins --tests ${{ matrix.extra_args }}
        env:
          CARGO_INCREMENTAL: "0"
          RUSTC_WRAPPER: ""
          RUSTFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off"

      - name: Upload failure
        if: failure()
        uses: ./.github/actions/artifact_failure
        with:
          name: test-${{ matrix.os }}-${{ matrix.rustc || 'stable' }}-${{ matrix.extra_desc }}

  # coverage:
      - name: Display coverage files
        if: ${{ ! matrix.no_coverage }}
        shell: bash
        run:
          grcov . -s . --binary-path $BINARY_DIR --output-type files $GRCOV_IGNORE_OPTION $GRCOV_EXCLUDE_OPTION | sort --unique

      - name: Generate coverage data (via `grcov`)
        if: ${{ ! matrix.no_coverage }}
        id: coverage
        shell: bash
        run: |
          mkdir -p "${COVERAGE_REPORT_DIR}"
          grcov . -s . --binary-path $BINARY_DIR --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch $GRCOV_IGNORE_OPTION $GRCOV_EXCLUDE_OPTION
          echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT

      - name: Upload coverage results (to Codecov.io)
        if: ${{ ! matrix.no_coverage }}
        uses: codecov/codecov-action@v5
        with:
          files: ${{ steps.coverage.outputs.report }}
          ## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
          flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
          name: codecov-umbrella-${{ matrix.os }}-rust_${{ matrix.rustc || 'stable' }}-${{ matrix.extra_desc }}
          fail_ci_if_error: true
          # verbose: true
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}



  build:
    name: build ${{ matrix.binary || 'sccache' }} ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    container: ${{ fromJson(matrix.container || '{"image":null}') }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-22.04
            target: x86_64-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:x86_64-musl"}'
          - os: ubuntu-22.04
            target: s390x-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:s390x-musl"}'
            lto: "false"
          - os: ubuntu-22.04
            target: s390x-unknown-linux-gnu
          - os: ubuntu-22.04
            target: riscv64gc-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:riscv64gc-musl"}'
            rustflags: -Ctarget-feature=+crt-static
          - os: ubuntu-22.04
            target: loongarch64-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:loongarch64-musl"}'
            rustflags: -Ctarget-feature=+crt-static
          - os: ubuntu-22.04
            binary: sccache-dist
            extra_args: --no-default-features --features="dist-server"
            target: x86_64-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:x86_64-musl"}'
          - os: ubuntu-22.04
            target: aarch64-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:aarch64-musl"}'
          - os: ubuntu-22.04
            binary: sccache-dist
            extra_args: --no-default-features --features="dist-server"
            target: aarch64-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:aarch64-musl"}'
          - os: ubuntu-22.04
            target: armv7-unknown-linux-musleabi
            container: '{"image": "messense/rust-musl-cross:armv7-musleabi"}'
          - os: ubuntu-22.04
            target: i686-unknown-linux-musl
            container: '{"image": "messense/rust-musl-cross:i686-musl"}'
          - os: macos-14
            target: aarch64-apple-darwin
            macosx_deployment_target: 11.0
          - os: macos-15-intel
            target: x86_64-apple-darwin
            macosx_deployment_target: 11.0
          - os: windows-2022
            target: x86_64-pc-windows-msvc
            rustflags: -Ctarget-feature=+crt-static
          - os: windows-2022
            target: aarch64-pc-windows-msvc
            rustflags: -Ctarget-feature=+crt-static
    steps:
      - name: Clone repository
        uses: actions/checkout@v5

      - name: Install rust
        uses: ./.github/actions/rust-toolchain
        with:
          toolchain: ${{ matrix.target == 'aarch64-apple-darwin' && 'beta' || 'stable' }}
          target: ${{ matrix.target }}
        if: ${{ !matrix.container }}

      - name: Install s390x cross-compilation toolchain
        if: ${{ matrix.target == 's390x-unknown-linux-gnu' }}
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-s390x-linux-gnu g++-s390x-linux-gnu
          mkdir -p .cargo
          cat >> .cargo/config.toml <<EOF
          [target.s390x-unknown-linux-gnu]
          linker = "s390x-linux-gnu-gcc"
          EOF

      - name: Install rust-src for s390x-musl
        if: ${{ matrix.target == 's390x-unknown-linux-musl' }}
        run: |
          # Remove corrupted nightly toolchain if it exists
          rustup toolchain uninstall nightly || true
          # Clean up any leftover files
          rm -rf ~/.rustup/toolchains/nightly-* || true
          # Install fresh nightly with minimal profile
          rustup toolchain install nightly --profile minimal
          rustup component add rust-src --toolchain nightly

      - name: fix build openssl error on s390x
        if: ${{  matrix.target  == 's390x-unknown-linux-musl' }}
        run: |
          # see https://github.com/openssl/openssl/issues/27323
          echo CFLAGS="-march=z10 -mzarch" >> $GITHUB_ENV
          echo CXXFLAGS="$CFLAGS" >> $GITHUB_ENV
          echo RUSTFLAGS="-C target-cpu=z10" >> $GITHUB_ENV

      - name: Build (s390x-musl with build-std)
        if: ${{ matrix.target == 's390x-unknown-linux-musl' }}
        run: cargo +nightly build --locked --release --bin ${{ matrix.binary || 'sccache' }} --target ${{ matrix.target }} --features=openssl/vendored ${{ matrix.extra_args }} -Zbuild-std
        env:
          MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
          DEVELOPER_DIR: ${{ matrix.developer_dir }}
          SDKROOT: ${{ matrix.sdkroot }}
          RUSTFLAGS: ${{ env.RUSTFLAGS || matrix.rustflags }}
          CARGO_PROFILE_RELEASE_LTO: ${{ matrix.lto || 'true' }}

      - name: Build
        if: ${{ matrix.target != 's390x-unknown-linux-musl' }}
        run: cargo build --locked --release --bin ${{ matrix.binary || 'sccache' }} --target ${{ matrix.target }} --features=openssl/vendored ${{ matrix.extra_args }}
        env:
          MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
          DEVELOPER_DIR: ${{ matrix.developer_dir }}
          SDKROOT: ${{ matrix.sdkroot }}
          RUSTFLAGS: ${{ env.RUSTFLAGS || matrix.rustflags }}
          CARGO_PROFILE_RELEASE_LTO: ${{ matrix.lto || 'true' }}

      # Workaround for the lack of substring() function in github actions expressions.
      - name: Id
        id: id
        shell: bash
        run: echo "id=${ID#refs/tags/}" >> $GITHUB_OUTPUT
        env:
          ID: ${{ startsWith(github.ref, 'refs/tags/') && github.ref || github.sha }}

      - name: Upload artifacts
        uses: actions/upload-artifact@v7
        with:
          name: ${{ matrix.binary || 'sccache' }}-${{ steps.id.outputs.id }}-${{ matrix.target }}
          path: target/${{ matrix.target }}/release/${{ matrix.binary || 'sccache' }}${{ endsWith(matrix.target, '-msvc') && '.exe' || '' }}
          if-no-files-found: error


  test_freebsd:
    name: test freebsd-15.0 rust stable
    runs-on: ${{ matrix.job.os }}
    timeout-minutes: 70
    strategy:
      fail-fast: false
      matrix:
        job:
          - { os: ubuntu-22.04 }
    steps:
      - uses: actions/checkout@v5
      - name: Prepare, build and test
        uses: vmactions/freebsd-vm@v1
        with:
          mem: 8192
          usesh: true
          sync: rsync
          copyback: false
          prepare: pkg install -y ca_root_nss curl gmake gtar pot sudo
          run: |
            #####################################################################################
            ###  Prepare, build, and test
            #####################################################################################
            ###  based on ref: <https://github.com/rust-lang/rustup/pull/2783>
            ###  and on ref: <https://github.com/uutils/coreutils/commit/86c610a84b8b6c>
            ###  * NOTE: All steps need to be run in this block, otherwise, we are operating back
            ###    on the mac host.
            set -exo pipefail
            #
            ### Basic user setup ################################################################
            TEST_USER=tester
            TEST_USER_HOME="/opt/$TEST_USER"
            REPO_NAME=${GITHUB_WORKSPACE##*/}
            WORKSPACE_PARENT="/home/runner/work/${REPO_NAME}"
            WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}"
            export WORKSPACE
            #
            mkdir -p "$TEST_USER_HOME"
            pw adduser -n "$TEST_USER" -d "$TEST_USER_HOME" -c "Tester" -h -
            chown -R "$TEST_USER":"$TEST_USER" "$TEST_USER_HOME"
            chown -R "$TEST_USER":"$TEST_USER" "/$WORKSPACE_PARENT"/
            cat > /usr/local/etc/sudoers.d/wheel<<EOF
            $TEST_USER ALL=(ALL) NOPASSWD: ALL
            EOF
            #
            ### Install rust stable from rustup  ################################################
            su "$TEST_USER" -c "/bin/sh -exo pipefail" <<"EOH"
            whoami
            echo "$HOME"
            fetch -o /tmp/rustup.sh https://sh.rustup.rs
            sh /tmp/rustup.sh -y --profile=minimal
            ### Run tests #######################################################################
            . "$HOME/.cargo/env"
            "$WORKSPACE/scripts/freebsd-ci-test.sh"
            EOH
            # end
      - name: Upload failure
        if: failure()
        uses: ./.github/actions/artifact_failure
        with:
          name: test-freebsd-15.0-stable

  release:
    name: release
    runs-on: ubuntu-latest
    needs: [ build, lint, test ]
    if: ${{ startsWith(github.ref, 'refs/tags/') }}
    steps:
      - name: Clone repository
        uses: actions/checkout@v5

      - name: Check versions
        run: |
          tag_name=${GITHUB_REF#refs/tags/}
          v=$(grep -m 1 "^version" Cargo.toml|sed -e "s|version = \"\(.*\)\"|\1|")
          if ! echo $tag_name|grep -q $v; then
             echo "Mistmatch of the version:"
             echo "Cargo.toml says $v while the tag is $tag_name"
             exit 2
          fi

      - name: Get artifacts
        uses: actions/download-artifact@v8

      - name: Create release assets
        run: |
          for d in sccache-*; do
            chmod +x "$d/sccache"*
            cp README.md LICENSE "$d/"
            tar -zcvf "$d.tar.gz" "$d"
            echo -n "$(shasum -ba 256 "$d.tar.gz" | cut -d " " -f 1)" > "$d.tar.gz.sha256"
            if [[ $d =~ sccache-.*-pc-windows-.* ]]; then
              zip -r "$d.zip" "$d"
              echo -n "$(shasum -ba 256 "$d.zip" | cut -d " " -f 1)" > "$d.zip.sha256"
            fi
          done

      - name: Create release
        run: |
          sudo apt-get update && sudo apt-get install -y hub
          tag_name=${GITHUB_REF#refs/tags/}
          for f in sccache-*.tar.gz* sccache-*.zip*; do
              if [[ -f "$f" ]]; then
                files="$files -a $f";
              fi
          done
          hub release create -d -m $tag_name $tag_name $files
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
