fastpfor 0.9.0

FastPFOR lib with C++ Rust wrapper and pure Rust implementation
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

defaults:
  run:
    shell: bash

jobs:
  test:
    name: Test ${{ matrix.os }} (${{ matrix.simd_mode }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        simd_mode: [portable, native]
    env:
      FASTPFOR_SIMD_MODE: ${{ matrix.simd_mode }}
    steps:
      - uses: actions/checkout@v6
        with: { submodules: recursive }
      - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch'
        uses: Swatinem/rust-cache@v2
        with:
          prefix-key: "v0-${{ matrix.simd_mode }}"
      - uses: taiki-e/install-action@v2
        with: { tool: 'just,cargo-binstall' }
      - run: just ci-test

  test-nightly:
    name: Nightly-specific tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with: { submodules: recursive }
      - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch'
        uses: Swatinem/rust-cache@v2
      - uses: taiki-e/install-action@v2
        with: { tool: 'just' }
      - run: rustup install nightly --profile minimal
      - run: just test-publish

  test-msrv:
    name: Test MSRV
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with: { submodules: recursive }
      - if: github.event_name != 'release' && github.event_name != 'workflow_dispatch'
        uses: Swatinem/rust-cache@v2
      - uses: taiki-e/install-action@v2
        with: { tool: 'just' }
      - name: Read MSRV
        id: msrv
        run: echo "value=$(just get-msrv)" >> $GITHUB_OUTPUT
      - name: Install MSRV Rust ${{ steps.msrv.outputs.value }}
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ steps.msrv.outputs.value }}
      - run: just ci_mode=0 ci-test-msrv  # Ignore warnings in MSRV

  fuzz:
    name: Fuzz
    runs-on: ubuntu-latest
    env:
      # The number of seconds to run the fuzz target.
      FUZZ_TIME: 60
    strategy:
      matrix:
        include:
          - fuzz_target: cpp_roundtrip
          - fuzz_target: encode_oracle
          - fuzz_target: decode_oracle
          - fuzz_target: decode_arbitrary
    steps:
      - uses: actions/checkout@v6
        with: {persist-credentials: false, submodules: recursive}
      # Install the nightly Rust channel.
      - run: rustup toolchain install nightly
      - run: rustup default nightly
      # Install and cache `cargo-fuzz`.
      - uses: taiki-e/install-action@v2
        with:
          tool: cargo-binstall
      - run: cargo binstall -y cargo-fuzz@0.13.1 # Pinned to avoid breakage.
      # Build and then run the fuzz target.
      #  --target x86_64-unknown-linux-gnu is necessary to not default to musl, which is not supported by cargo-fuzz.
      - run: cargo fuzz build --target x86_64-unknown-linux-gnu ${{ matrix.fuzz_target }}
      - run: cargo fuzz run --target x86_64-unknown-linux-gnu ${{ matrix.fuzz_target }} -- -max_total_time=${{ env.FUZZ_TIME }}
      # Upload fuzzing artifacts on failure for post-mortem debugging.
      - uses: actions/upload-artifact@v7
        if: failure()
        with:
          name: fuzzing-artifacts-${{ matrix.fuzz_target }}-${{ github.sha }}
          path: fuzz/artifacts

  coverage:
    name: Code Coverage
    if: github.event_name != 'release'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with: { submodules: recursive }
      - uses: Swatinem/rust-cache@v2
      - uses: taiki-e/install-action@v2
        with: { tool: 'just,cargo-llvm-cov' }
      - name: Generate code coverage
        run: just ci-coverage
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: target/llvm-cov/codecov.info

  # This job checks if any of the previous jobs failed or were canceled.
  # This approach also allows some jobs to be skipped if they are not needed.
  ci-passed:
    needs: [ test, test-nightly, test-msrv, fuzz ]
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Result of the needed steps
        run: echo "${{ toJSON(needs) }}"
      - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
        run: exit 1

  release:
    # Some dependencies of the `ci-passed` job might be skipped, but we still want to run if the `ci-passed` job succeeded.
    if: always() && startsWith(github.ref, 'refs/tags/') && needs.ci-passed.result == 'success'
    name: Publish to crates.io
    needs: [ ci-passed ]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with: { submodules: recursive }
      - name: Publish to crates.io
        run: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}