granite-cli 0.2.7

CLI for discovering, configuring, and launching AI workflows powered by IBM Granite models.
name: CI

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

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  check:
    uses: ./.github/workflows/check.yml

  cross-build:
    name: Build + test (${{ matrix.target }})
    needs: [check]
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
          - target: aarch64-apple-darwin
            os: macos-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
          # DISABLED: These runners seem to be unavailable on GitHub Actions,
          #   so they block builds
          #- target: x86_64-apple-darwin
          #  os: macos-13

    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # 1.96.1
        with:
          toolchain: 1.99
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
        with:
          key: ${{ matrix.target }}

      - name: Install cross-compilation tools
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu

      - name: Build
        run: cargo build --locked --release --target ${{ matrix.target }} --all-targets --all-features
        env:
          CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc

      - name: Test
        if: matrix.target != 'aarch64-unknown-linux-gnu'
        run: cargo test --locked --target ${{ matrix.target }} --all-targets --all-features

  coverage:
    name: Coverage
    needs: [check]
    runs-on: ubuntu-latest
    env:
      # Minimum line coverage, in percent: for the whole crate, and for the
      # lines a pull request adds or changes.
      COVERAGE_MIN_TOTAL: "80"
      COVERAGE_MIN_DIFF: "80"
      DIFF_COVER_VERSION: "10.6.0"

    steps:
      - name: Checkout repository
        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          # diff-cover compares against the base branch
          fetch-depth: 0

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # 1.96.1
        with:
          toolchain: 1.99
          components: llvm-tools-preview

      - name: Cache cargo build artifacts
        uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
        with:
          key: coverage

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28
        with:
          tool: cargo-llvm-cov@0.9.1

      - name: Test with coverage
        run: cargo llvm-cov --no-report --locked --all-targets --all-features

      - name: Generate reports
        run: |
          cargo llvm-cov report --lcov --output-path lcov.info
          cargo llvm-cov report --html --output-dir coverage
          cargo llvm-cov report --json --summary-only --output-path coverage-summary.json
          lines=$(jq '.data[0].totals.lines.percent' coverage-summary.json)
          printf '## Coverage\n\nLine coverage: **%.2f%%** (minimum %s%%)\n\n' \
            "$lines" "$COVERAGE_MIN_TOTAL" >> "$GITHUB_STEP_SUMMARY"

      - name: Check coverage of changed lines
        if: github.event_name == 'pull_request'
        env:
          BASE_REF: ${{ github.base_ref }}
        run: |
          status=0
          pipx run "diff-cover==${DIFF_COVER_VERSION}" lcov.info \
            --compare-branch="origin/${BASE_REF}" \
            --format markdown:diff-cover.md,json:diff-cover.json \
            --fail-under="$COVERAGE_MIN_DIFF" || status=$?
          cat diff-cover.md >> "$GITHUB_STEP_SUMMARY"
          exit "$status"

      - name: Check total coverage
        if: ${{ !cancelled() }}
        run: cargo llvm-cov report --summary-only --fail-under-lines "$COVERAGE_MIN_TOTAL"

      - name: Upload coverage reports
        id: upload-reports
        if: ${{ !cancelled() }}
        uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        with:
          name: coverage
          path: |
            lcov.info
            coverage/html
            diff-cover.md
          if-no-files-found: ignore

      # Pull requests from forks run with a read-only token, so this job
      # cannot comment on them. It uploads the comment body and the pull
      # request number as the "coverage-comment" artifact, and
      # coverage-comment.yml posts the comment.
      - name: Write pull request comment
        if: ${{ github.event_name == 'pull_request' && !cancelled() && hashFiles('coverage-summary.json') != '' }}
        env:
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPORT_URL: ${{ steps.upload-reports.outputs.artifact-url }}
          RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
        run: |
          # Prints a pass or fail mark for a percentage against a minimum,
          # using the same comparison as the checks above.
          mark() { awk -v v="$1" -v m="$2" 'BEGIN { print (v < m) ? ":x:" : ":white_check_mark:" }'; }

          total=$(jq '.data[0].totals.lines.percent' coverage-summary.json)
          changed=0
          {
            printf '## Coverage\n\n'
            printf '| | Line coverage | Minimum | |\n|---|---:|---:|:-:|\n'
            printf '| Whole crate | %.2f%% | %s%% | %s |\n' \
              "$total" "$COVERAGE_MIN_TOTAL" "$(mark "$total" "$COVERAGE_MIN_TOTAL")"
            if [ -f diff-cover.json ]; then
              changed=$(jq '.total_num_lines' diff-cover.json)
              missing=$(jq '.total_num_violations' diff-cover.json)
              if [ "$changed" -eq 0 ]; then
                printf '| Changed lines | none with coverage data | %s%% | |\n' "$COVERAGE_MIN_DIFF"
              else
                diff=$(jq '.total_percent_covered' diff-cover.json)
                printf '| Changed lines (%s of %s covered) | %.2f%% | %s%% | %s |\n' \
                  "$((changed - missing))" "$changed" "$diff" "$COVERAGE_MIN_DIFF" \
                  "$(mark "$diff" "$COVERAGE_MIN_DIFF")"
              fi
            fi
            printf '\n'
            # A comment body is limited to 65536 characters; the per-file
            # listing goes to the job summary only when it is longer.
            if [ -f diff-cover.md ] && [ "$changed" -gt 0 ]; then
              if [ "$(wc -c < diff-cover.md)" -le 50000 ]; then
                printf '<details><summary>Changed lines by file</summary>\n\n'
                cat diff-cover.md
                printf '\n</details>\n\n'
              else
                printf 'The list of changed lines by file is too long for a comment; it is in the workflow run summary.\n\n'
              fi
            fi
            if [ -n "$REPORT_URL" ]; then
              printf '[Coverage report (HTML and LCOV, zip)](%s) ยท ' "$REPORT_URL"
            fi
            printf '[Workflow run](%s)\n' "$RUN_URL"
          } > comment.md
          mkdir -p coverage-comment
          mv comment.md coverage-comment/comment.md
          echo "$PR_NUMBER" > coverage-comment/pr-number

      - name: Upload pull request comment
        if: ${{ github.event_name == 'pull_request' && !cancelled() && hashFiles('coverage-comment/comment.md') != '' }}
        uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
        with:
          name: coverage-comment
          path: coverage-comment
          retention-days: 7