name: Rust

on:
  pull_request:
    paths:
    - '**'
    - '!/*.md'
    - '!/*.org'
    - '!/*.svg'
    - '!/*.png'
    - "!/LICENSE"
    
  push:
    branches:
    - main
    paths:
    - '**'
    - '!/*.md'
    - '!/*.org'
    - '!/*.svg'
    - '!/*.png'
    - "!/LICENSE"
    
  schedule:
  - cron: '12 12 12 * *'

env:
  RUST_LOG: info
  RUST_BACKTRACE: 1
  RUST_VERBOSE: --verbose
  RUSTDOCFLAGS: -D warnings

jobs:
  
  publish:
    name: Publish (dry-run)
    needs: [test, docs]
    strategy:
      matrix:
        package:
          - sdml-errors
          - sdml-core
          - sdml-parse
          - sdml-generate
          - sdml-rdf
          - sdml-tera
          - sdml-cli
    continue-on-error: true
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable
        profile: minimal
        
    - uses: Swatinem/rust-cache@v2
      
    - name: Check publish
      run: |
        for package in $(cargo metadata --no-deps --format-version=1 | jq -r '.packages[] | .name'); do
          cargo publish ${{ env.RUST_VERBOSE }} --package "$package" --dry-run
        done

      
  check_tests:
    name: Check for Test Types
    runs-on: ubuntu-latest
    outputs:
      has_benchmarks: ${{ steps.check_benchmarks.outputs.has_benchmarks }}
      has_examples: ${{ steps.check_examples.outputs.has_examples }}
    steps:
      - name: Check for benchmarks
        id: check_benchmarks
        run: test -d benchmarks && echo "has_benchmarks=1" || echo "has_benchmarks=" >> $GITHUB_OUTPUT
        shell: bash
        
      - name: Check for examples
        id: check_examples
        run: test -d examples && echo "has_examples=1" || echo "has_examples=" >> $GITHUB_OUTPUT
        shell: bash

        
  build:
    name: Build
    needs: [rustfmt, check, clippy]
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
        rust: ["stable", "beta", "nightly"]
        test-features: ["", "--all-features", "--no-default-features"]
    continue-on-error: ${{ matrix.rust != 'stable' }}
    runs-on: ${{ matrix.os }}
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: ${{ matrix.rust }}
        profile: minimal
        
    - uses: Swatinem/rust-cache@v2
      
    - name: Build
      run: cargo build ${{ env.RUST_VERBOSE }} --workspace ${{ matrix.test-features }}

        
  test:
    name: Tests
    needs: [build]
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
        rust: ["stable", "beta", "nightly"]
        test-features: ["", "--all-features", "--no-default-features"]
    continue-on-error: ${{ matrix.rust != 'stable' }}
    runs-on: ${{ matrix.os }}
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: ${{ matrix.rust }}
        profile: minimal
        
    - uses: Swatinem/rust-cache@v2
      
    - name: Test with ${{ matrix.test-features }}
      run: cargo test ${{ env.RUST_VERBOSE }} --workspace ${{ matrix.test-features }}
 
      
  benchmarks:
    name: Benchmarks
    needs: [rustfmt, check, clippy, check_tests]
    if: needs.check_tests.outputs.has_benchmarks
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
        rust: ["stable"]
    runs-on: ${{ matrix.os }}
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: ${{ matrix.rust }}
        profile: minimal
        
    - uses: Swatinem/rust-cache@v2
      
    - name: Run benchmarks with all features
      run: cargo test ${{ env.RUST_VERBOSE }} --workspace --benches --all-features --no-fail-fast

      
  examples:
    name: Examples
    needs: [rustfmt, check, clippy, check_tests]
    if: needs.check_tests.outputs.has_examples
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
        rust: ["stable"]
    runs-on: ${{ matrix.os }}
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable
        
    - uses: Swatinem/rust-cache@v2
      
    - name: Run examples with all features
      run: cargo test ${{ env.RUST_VERBOSE }} --workspace --examples --all-features --no-fail-fast

        
  coverage:   
    name: Code Coverage
    needs: test
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable

    - name: Install Tarpaulin
      run: cargo install cargo-tarpaulin

    - name: Run cargo-tarpaulin
      run: cargo tarpaulin ${{ env.RUST_VERBOSE }} --out Xml --all-features -- --test-threads 1

    - name: Upload to codecov.io
      uses: codecov/codecov-action@v1.0.2
      with:
        token: ${{secrets.CODECOV_TOKEN}}

    - name: Archive code coverage results
      uses: actions/upload-artifact@v4
      with:
        name: code-coverage-report
        path: cobertura.xml

        
  miri:
    name: Miri Tests
    needs: test
    strategy:
      matrix:
        os: ["ubuntu-latest", "windows-latest", "macos-latest"]
        test-features: ["", "--all-features", "--no-default-features"]
    continue-on-error: true
    runs-on: ${{ matrix.os }}
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: nightly
        components: miri

    - name: Pin nightly Toolchain
      run: |
          rustup override set nightly
          cargo miri setup

    - name: Run tests w/miri
      env:
        MIRIFLAGS: -Zmiri-backtrace=full -Zmiri-isolation-error=warn
        # MIRIFLAGS: -Zmiri-disable-isolation
        # MIRIFLAGS: -Zmiri-isolation-error=warn
      run: cargo miri test ${{ env.RUST_VERBOSE }} ${{ matrix.test-features }} -- -Z macro-backtrace


  sanitizer-tests:
    name: Sanitizer Tests
    needs: test
    strategy:
      fail-fast: false
      matrix:
        sanitizer: [address, memory, thread, leak]
        test-features: ["", "--all-features", "--no-default-features"]
    continue-on-error: true
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: nightly
        components: rust-src

    - name: Run tests w/sanitizer
      env:
        RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
        RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
        # only needed by asan
        ASAN_OPTIONS: detect_stack_use_after_return=1
        # Asan's leak detection occasionally complains
        # about some small leaks if backtraces are captured,
        # so ensure they're not
        RUST_BACKTRACE: 0
      run: cargo test -Zbuild-std ${{ env.RUST_VERBOSE }} --target=x86_64-unknown-linux-gnu ${{ matrix.test-features }}


  docs:
    name: Document Generation
    needs: [rustfmt, check, clippy]
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable
        profile: default
        
    - uses: Swatinem/rust-cache@v2
      
    - name: Generate documentation
      run: cargo doc ${{ env.RUST_VERBOSE }} --workspace --all-features --no-deps

      
  rustfmt:
    name: Format
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable
        profile: default
          
    - uses: Swatinem/rust-cache@v2

    - name: Check formatting
      run: cargo fmt ${{ env.RUST_VERBOSE }} --all -- --check

      
  clippy:
    name: Clippy
    strategy:
      matrix:
        test-features: ["", "--all-features", "--no-default-features"]
    runs-on: ubuntu-latest
    permissions:
      checks: write
    env:
      RUSTFLAGS: -Dwarnings
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable
        profile: default
              
    - uses: Swatinem/rust-cache@v2

    - name: Check well-formedness rules
      run: cargo clippy ${{ env.RUST_VERBOSE }} --workspace --no-deps ${{ matrix.test-features }} --all-targets

      
  check:
    name: Check
    strategy:
      matrix:
        test-features: ["", "--all-features", "--no-default-features"]
    runs-on: ubuntu-latest
    permissions:
      checks: write
    env:
      RUSTFLAGS: -Dwarnings
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Install Rust Toolchain
      uses: hecrj/setup-rust-action@v2
      with:
        rust-version: stable
        profile: default
              
    - uses: Swatinem/rust-cache@v2

    - name: Check well-formedness rules
      run: cargo check ${{ env.RUST_VERBOSE }} --workspace --future-incompat-report ${{ matrix.test-features }} --all-targets
