mitts 0.3.29

Rust-first build and package tooling with a shared artifact cache
Documentation
# Mitts Build — native tests and cache-isolation gates.
# Copyright (c) 2026 Alexander Panasenko
# Contact: alex@prod.codes
# Author: https://prod.codes/about/
# Project: https://github.com/alex09x/mitts
# SPDX-License-Identifier: MIT OR Apache-2.0
name: CI

on:
  pull_request:
  push:
    branches: [main]
  workflow_call:
    inputs:
      ref:
        type: string
        default: ''

permissions:
  contents: read

# A newer push to the same pull request supersedes the older run. Pushes to main and reused
# release runs get a unique group, so they are never cancelled or queued behind each other.
concurrency:
  group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.run_id }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  automation:
    runs-on: ubuntu-22.04
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          persist-credentials: false
      - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
        with:
          python-version: '3.12'
      - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
        with:
          toolchain: 1.98.0
      - name: Build Mitts test driver
        run: |
          set -euo pipefail
          MITTS_INSTALL_ROOT="$RUNNER_TEMP/mitts-bootstrap" sh site/install.sh
          "$RUNNER_TEMP/mitts-bootstrap/bin/mitts" build --locked \
            --target-dir "$RUNNER_TEMP/mitts-test-driver" --cache-dir "$RUNNER_TEMP/mitts-build-cache"
          printf 'MITTS_CI_BIN=%s\n' "$RUNNER_TEMP/mitts-test-driver/debug/mitts" >> "$GITHUB_ENV"
      - name: Workflow and installer regressions
        run: python -m unittest discover -s scripts/release -p 'test_*.py' -v
      - name: Validate GitHub Actions syntax
        run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -color
  native:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04, macos-15]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 90
    env:
      RUST_TEST_THREADS: '1'
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          persist-credentials: false
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          ref: ${{ inputs.ref || github.sha }}
          path: source
          fetch-depth: 0
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
        with:
          toolchain: 1.98.0
          components: rustfmt, clippy, llvm-tools-preview
      - name: Install latest stable Mitts
        env:
          MITTS_INSTALL_ROOT: ${{ runner.temp }}/mitts-bootstrap
        run: |
          sh site/install.sh
          binary="$MITTS_INSTALL_ROOT/bin/mitts"
          "$binary" --version
          printf 'MITTS_CI_BIN=%s\nMITTS_CI_CACHE_DIR=%s\n' "$binary" "$RUNNER_TEMP/mitts-ci-cache" >> "$GITHUB_ENV"
      - name: Native Bash 3.2 isolation-runner regressions
        if: runner.os == 'macOS'
        run: python3 -m unittest discover -s scripts/release -p test_ci_scripts.py -v
      - name: Build and use the exact-source Mitts driver
        working-directory: source
        run: |
          "$MITTS_CI_BIN" build --locked --bin mitts --cache-dir "$MITTS_CI_CACHE_DIR" --target-dir "$RUNNER_TEMP/mitts-candidate"
          binary="$RUNNER_TEMP/mitts-candidate/debug/mitts"
          "$binary" --version
          printf 'MITTS_CI_BIN=%s\n' "$binary" >> "$GITHUB_ENV"
      - name: Formatting
        working-directory: source
        run: python3 "$GITHUB_WORKSPACE/scripts/release/check_format.py" --source . --mitts "$MITTS_CI_BIN"
      - name: Clippy
        working-directory: source
        run: '"$MITTS_CI_BIN" clippy --workspace --all-targets --cache-dir "$MITTS_CI_CACHE_DIR" -- -D warnings'
      - name: Full native suite with LLVM coverage in an isolated store
        working-directory: source
        env:
          MITTS_TEST_COMMAND: coverage
        run: |
          ulimit -n 4096
          mkdir -p .ci-tools
          cp "$GITHUB_WORKSPACE/scripts/run_tests_isolated.sh" .ci-tools/run-tests.sh
          bash .ci-tools/run-tests.sh -c --no-fail-fast --lcov --output-dir "$GITHUB_WORKSPACE/coverage"
      - name: Documentation tests through Mitts
        working-directory: source
        run: bash .ci-tools/run-tests.sh --doc
      - name: Coverage summary
        if: always()
        run: python3 scripts/release/coverage_summary.py coverage/lcov.info source "$GITHUB_STEP_SUMMARY"
      - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        if: always()
        with:
          name: coverage-${{ matrix.os }}
          path: coverage/
          if-no-files-found: error
      - name: Source manifests and lockfile remain unchanged
        if: always()
        working-directory: source
        run: git diff --exit-code -- Cargo.toml Cargo.lock
  # The per-target isolation probe builds and runs every test target alone, which used to be
  # a 45 minute serial tail of the native job on Linux. Shards run in parallel with the full
  # suite; together they still cover every target exactly once per operating system.
  probe:
    name: probe (${{ matrix.os }} ${{ matrix.shard }})
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-22.04, shard: '1/4' }
          - { os: ubuntu-22.04, shard: '2/4' }
          - { os: ubuntu-22.04, shard: '3/4' }
          - { os: ubuntu-22.04, shard: '4/4' }
          - { os: macos-15, shard: '1/2' }
          - { os: macos-15, shard: '2/2' }
    runs-on: ${{ matrix.os }}
    timeout-minutes: 60
    env:
      RUST_TEST_THREADS: '1'
      MITTS_PROBE_SHARD: ${{ matrix.shard }}
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          persist-credentials: false
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          ref: ${{ inputs.ref || github.sha }}
          path: source
          fetch-depth: 0
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
        with:
          toolchain: 1.98.0
          components: rustfmt, clippy, llvm-tools-preview
      - name: Install latest stable Mitts
        env:
          MITTS_INSTALL_ROOT: ${{ runner.temp }}/mitts-bootstrap
        run: |
          sh site/install.sh
          binary="$MITTS_INSTALL_ROOT/bin/mitts"
          "$binary" --version
          printf 'MITTS_CI_BIN=%s\nMITTS_CI_CACHE_DIR=%s\n' "$binary" "$RUNNER_TEMP/mitts-ci-cache" >> "$GITHUB_ENV"
      - name: Build and use the exact-source Mitts driver
        working-directory: source
        run: |
          "$MITTS_CI_BIN" build --locked --bin mitts --cache-dir "$MITTS_CI_CACHE_DIR" --target-dir "$RUNNER_TEMP/mitts-candidate"
          binary="$RUNNER_TEMP/mitts-candidate/debug/mitts"
          "$binary" --version
          printf 'MITTS_CI_BIN=%s\n' "$binary" >> "$GITHUB_ENV"
      - name: Prove isolation for this shard of test targets with Mitts runner
        working-directory: source
        run: |
          set -euo pipefail
          ulimit -n 4096
          # Use the portable runner even when validating an older release tag.
          # Untracked tools leave the exact source's tracked tree unchanged.
          mkdir -p .ci-tools
          cp "$GITHUB_WORKSPACE/scripts/probe_store_isolation.sh" .ci-tools/probe.sh
          /bin/bash .ci-tools/probe.sh
      - name: Source manifests and lockfile remain unchanged
        if: always()
        working-directory: source
        run: git diff --exit-code -- Cargo.toml Cargo.lock
  # The published setup and save actions, run from this checkout exactly as a consumer would.
  action:
    name: action (${{ matrix.os }})
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-22.04, macos-15]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
        with:
          toolchain: 1.98.0
      - id: mitts
        uses: ./
        with:
          cache-key: action-smoke
      - name: Installed Mitts builds a crate through the restored cache root
        shell: bash
        env:
          EXPECTED_VERSION: ${{ steps.mitts.outputs.version }}
          EXPECTED_CACHE_DIR: ${{ steps.mitts.outputs.cache-dir }}
        run: |
          set -euo pipefail
          test "$(mitts --version | awk '{print $2}')" = "$EXPECTED_VERSION"
          test "$MITTS_CACHE_DIR" = "$EXPECTED_CACHE_DIR"
          smoke="$RUNNER_TEMP/action-smoke"
          mkdir -p "$smoke/src"
          printf '[package]\nname = "action_smoke"\nversion = "0.1.0"\nedition = "2021"\n' > "$smoke/Cargo.toml"
          printf 'fn main() { println!("ok"); }\n' > "$smoke/src/main.rs"
          cd "$smoke"
          mitts build --target-dir "$smoke/target"
          test "$("$smoke/target/debug/action_smoke")" = ok
          test -d "$MITTS_CACHE_DIR"
      - uses: ./save
        with:
          max-bytes: '1073741824'
  gate:
    if: always()
    needs: [automation, native, probe, action]
    runs-on: ubuntu-22.04
    steps:
      - name: Require every check
        env:
          AUTOMATION: ${{ needs.automation.result }}
          NATIVE: ${{ needs.native.result }}
          PROBE: ${{ needs.probe.result }}
          ACTION: ${{ needs.action.result }}
        run: test "$AUTOMATION" = success && test "$NATIVE" = success && test "$PROBE" = success && test "$ACTION" = success