alef 0.79.0

Opinionated polyglot binding generator for Rust libraries
Documentation
name: CI

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1

jobs:
  validate:
    name: Validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: xberg-io/actions/setup-rust@v1
        with:
          components: "rustfmt, clippy"
          install-llvm-cov: "false"
          macos-dynamic-lookup: "false"
      - uses: taiki-e/install-action@v2
        with:
          tool: cargo-deny,cargo-machete,cargo-sort
      - name: Check formatting
        run: cargo fmt --all -- --check
      - name: Clippy
        run: cargo clippy --workspace --all-targets -- -D warnings -A clippy::needless_update
      - name: Check unused dependencies
        run: cargo machete
      - name: Security and license audit
        run: cargo deny check
      - name: Check Cargo.toml sorting
        run: cargo sort --check --workspace

  poly-validate:
    uses: xberg-io/actions/.github/workflows/reusable-validate.yml@v1
    with:
      setup-rust: true
      setup-python: true

  test:
    name: Test (${{ matrix.os }})
    needs: validate
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-latest
          - macos-latest
          - windows-latest
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v7
      - uses: xberg-io/actions/setup-rust@v1
        with:
          install-llvm-cov: "false"
          cache-key-prefix: ci-test
      # Several `#[test]`s compile alef's own generated output with the real per-language
      # toolchain rather than mocking it (that is the point of them), so those toolchains
      # have to actually be on PATH here, not just in the `generated-output-gate` job.
      # `poly` is deliberately not installed in this job: the tests that exercise it
      # already tolerate its absence (see `e2e::format::format_language`'s non-`--strict`
      # deferral, and `cli_all_format_gate_covers_every_write_phase`'s own
      # `poly_is_available()` branch), so it is not required for this job to pass, only
      # for the `generated-output-gate` job's lanes, which install it themselves.
      - name: Set up JDK
        uses: actions/setup-java@v6
        with:
          distribution: temurin
          # Matches `template_versions::toolchain::JAVA_JVM_TARGET`: alef's generated Java
          # service bindings use the `java.lang.foreign` FFM API, stable only from JDK 22.
          java-version: "25"
      - uses: xberg-io/actions/setup-maven@v1
      - uses: xberg-io/actions/setup-zig@v1
      - uses: astral-sh/setup-uv@v10.0.1
      - name: Install generated Python type checker
        run: uv tool install pyrefly==1.2.0
      - name: Install generated TypeScript compiler
        run: npm install --global typescript@5.9.2
      - name: Run tests
        env:
          ALEF_REQUIRE_PYREFLY: "1"
          ALEF_REQUIRE_TSC: "1"
        run: cargo test --workspace

  # Runs the consumer's own toolchain gate over a tree alef emits, in a scratch directory.
  # The `validate` job above lints alef; this one lints alef's *output*, which nothing
  # else in this workflow looks at. Kept a separate job because it needs poly and
  # cargo-sort — which the three-OS `test` matrix does not install — and because it
  # compiles the emitted crates.
  generated-output-gate:
    name: Generated output gate
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: xberg-io/actions/setup-rust@v1
        with:
          components: "clippy"
          install-llvm-cov: "false"
          cache-key-prefix: ci-generated-output-gate
      - uses: taiki-e/install-action@v2
        with:
          tool: cargo-sort
      - uses: xberg-io/actions/install-homebrew-linux@v1
      # Homebrew 6.x refuses to load formulae from a third-party tap until the tap is
      # explicitly trusted; `brew install` on an untrusted tap fails rather than skipping.
      # `brew trust` is a real subcommand as of 6.x (verified locally); see
      # https://docs.brew.sh/Tap-Trust.
      - name: Trust goldziher/tap
        run: |
          brew tap goldziher/tap
          brew trust goldziher/tap
      - name: Install poly
        run: brew install goldziher/tap/poly
      # Both tools must be present *before* the lanes run. The lanes fail on a missing
      # tool rather than skipping, so this step exists to fail earlier and more legibly,
      # not to decide whether the gate runs.
      - name: Verify downstream tooling is present
        run: |
          cargo sort --version
          poly --version
      # Not ignored, needs no tooling: the isolation guard and the wiring check. Run as
      # its own step so "the gate refuses to lint alef itself" is a named, visible result
      # in the CI log rather than an assertion buried inside a long run.
      - name: Prove the gate refuses to lint alef itself
        run: cargo test --test generated_output_downstream_gate -- --nocapture
      # The lanes and their sabotage proofs. Every test here is #[ignore]d, so dropping
      # `--ignored` would run none of them and still exit 0 — which is why
      # `ci_workflow_runs_the_generated_output_gate` asserts this flag is still here.
      - name: Run downstream gate over the emitted tree
        run: cargo test --test generated_output_downstream_gate -- --ignored --nocapture

  # Compiles alef at the exact floor `Cargo.toml` declares. Nothing else in this workflow
  # does: `rust-toolchain.toml` pins a far newer toolchain, so every other job builds with a
  # compiler that accepts features the declared MSRV rejects. 0.68.0 reached crates.io using
  # `if let` guards (stable from 1.95) while advertising 1.85, and no CI job could have caught
  # it -- an external user did, at install time (issue #262). The toolchain is read from the
  # manifest rather than written here, so it cannot drift out of step with the claim.
  msrv:
    name: Compile at declared MSRV
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - name: Resolve declared MSRV
        id: msrv
        run: |
          version="$(sed -n 's/^rust-version *= *"\(.*\)"/\1/p' Cargo.toml)"
          if [ -z "$version" ]; then
            echo "Cargo.toml declares no rust-version" >&2
            exit 1
          fi
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "Declared MSRV: $version"
      - name: Install the declared toolchain
        run: rustup toolchain install "${{ steps.msrv.outputs.version }}" --profile minimal
      # `+toolchain` on the command line overrides rust-toolchain.toml, which is the entire
      # point of this job.
      - name: Compile at the declared MSRV
        run: cargo "+${{ steps.msrv.outputs.version }}" check --lib --locked

  build:
    name: Build release
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: xberg-io/actions/setup-rust@v1
        with:
          install-llvm-cov: "false"
          cache-key-prefix: ci-build
      - uses: xberg-io/actions/build-rust-cli@v1
        id: build-cli
        with:
          package-name: alef
          binary-name: alef
      - name: Verify binary
        run: ${{ steps.build-cli.outputs.binary-path }} --help