alef 0.82.1

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, or alef's own compiler-oracle
      # probes, 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. This
          # JDK is also `javac`, the oracle `ALEF_REQUIRE_JAVAC` below requires.
          java-version: "25"
      - uses: xberg-io/actions/setup-maven@v1
      - uses: xberg-io/actions/setup-zig@v1
      - uses: xberg-io/actions/setup-elixir@v1
        with:
          elixir-version: "1.19"
          otp-version: "28"
      - 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
      # The C# identifier-grammar oracle (`ALEF_REQUIRE_DOTNET` below) needs a real `dotnet` on
      # PATH; nothing else in this job installed one before this step existed.
      - uses: actions/setup-dotnet@v6
        with:
          dotnet-version: "10.0.x"
      # The Kotlin identifier-grammar oracle (`ALEF_REQUIRE_KOTLINC` below) needs a real
      # `kotlinc` on PATH; nothing else in this job installed one before this step existed.
      # Pinned to the exact commit for `fwilhe2/setup-kotlin` tag v2.0 -- a third-party action,
      # so SHA-pinned rather than trusting a floating tag, matching the `Goldziher/poly` pin in
      # the `generated-output-gate` job below. `version:` pins the exact kotlinc release
      # `codegen::identifier_grammar`'s docs say the Kotlin grammar was measured against, so a
      # `fwilhe2/setup-kotlin` default-version bump can't silently change what CI verifies
      # against. ~keep
      - name: Install kotlinc
        uses: fwilhe2/setup-kotlin@ee9692514da313706b193d808526812102a344e4 # v2.0
        with:
          version: "2.4.10"
      - name: Run tests
        env:
          ALEF_REQUIRE_ELIXIR: "1"
          ALEF_REQUIRE_PYREFLY: "1"
          ALEF_REQUIRE_TSC: "1"
          # These four make the javac/dotnet/kotlinc identifier-grammar oracle tests
          # (`tests/identifier_grammar_compiler_oracle.rs`) and the two dotnet-compile canaries
          # scattered across the C# backend tests panic instead of silently skipping when their
          # compiler is missing -- a missing toolchain must fail this job loudly, not let the
          # gate pass unverified. See `required_javac_mode_fails_when_toolchain_is_unavailable`
          # and its dotnet/kotlinc siblings for the proof this panic actually fires.
          ALEF_REQUIRE_JAVAC: "1"
          ALEF_REQUIRE_DOTNET: "1"
          ALEF_REQUIRE_KOTLINC: "1"
          # Go needs no dedicated setup step above (GitHub-hosted runner images preinstall a Go
          # LTS toolchain), but the two `go test` compile canaries in
          # `backends::go::gen_bindings::service_api::tests` silently skipped when `go` was
          # missing just the same as the dotnet ones did -- see `required_go` there.
          ALEF_REQUIRE_GO: "1"
        run: cargo test --workspace
      # Every Elixir escaping oracle is #[ignore]d, so the step above runs none of them and
      # still exits 0. They evaluate generated Elixir through the real interpreter -- the only
      # checks in this repo that do -- so dropping `--ignored` or the filter here retires them
      # silently. `elixir_oracle::gate` reads THIS step's actual `run:` line (not the block, and
      # not this comment, which contains both needles) and fails if either goes missing.
      - name: Run the Elixir escaping oracles
        run: cargo test --lib elixir_oracle -- --ignored --nocapture

  # 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
      # Pinned to an exact poly release, not `latest`: the `[hooks.pre-commit.commands.
      # added-large-files]` entry in poly.toml shells out to `poly hooks check
      # --added-large-files`, which poly's own `--help` labels internal/hidden -- not part
      # of its user-facing surface, so a future poly could rename or drop it without that
      # being a documented breaking change. `Goldziher/poly` is the project's own
      # composite action; it forwards `version:` to the project's checksum-verified
      # `install.sh`, so this is a true, verified pin, not a presence check. See
      # https://github.com/Goldziher/poly#github-actions. ~keep
      - name: Install poly
        # Goldziher/poly@v0 resolved to tag v0.23.0 (commit
        # 5f609e9f6913a9e0e9046fc02fe76c0515a8e276) when this pin was written -- pinned to that
        # commit SHA below because a major-version tag like `v0` is mutable and can be
        # repointed, so the SHA is the real supply-chain pin; `version: v0.23.0` in `with:` is
        # kept as the human-readable/functional pin the action itself resolves against. To bump
        # poly, update both the SHA below and `version:` together. ~keep
        uses: Goldziher/poly@2303580a69638d6887db17d2f4e9bbffe7c4218b # v0.24.0
        with:
          version: v0.23.0
      # 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. The poly assertions are deliberately strict
      # rather than a bare `poly --version`: (1) the version must match the pin above
      # exactly, so an accidental `latest` resolution or a stale cache is caught here
      # instead of silently linting with the wrong poly; (2) `poly hooks check
      # --added-large-files --help` must still resolve, because that hidden subcommand is
      # exactly what `poly.toml`'s large-file guard depends on -- if a poly upgrade ever
      # renames or removes it, this is where that shows up as a loud, named CI failure
      # instead of the guard silently no-op'ing on every future commit. ~keep
      - name: Verify downstream tooling is present
        run: |
          cargo sort --version

          installed_poly_version="$(poly --version | awk '{print $2}')"
          if [ "$installed_poly_version" != "0.23.0" ]; then
            echo "::error::expected poly 0.23.0 (pinned for the poly.toml added-large-files hook), got ${installed_poly_version:-<empty>}. Update the pinned Goldziher/poly uses: SHA and version: input above together with this check." >&2
            exit 1
          fi
          echo "poly ${installed_poly_version} matches the pinned version."

          if ! poly hooks check --added-large-files --help >/dev/null 2>&1; then
            echo "::error::'poly hooks check --added-large-files' no longer resolves. This is the hidden/internal subcommand that poly.toml's [hooks.pre-commit.commands.added-large-files] shells out to for the repo's large-file guard -- if poly renamed or dropped it, that guard is now silently not running on every commit. Fix poly.toml (see the 'flag if a public surface can replace the hidden command' note in CHANGELOG.md) before bumping the pinned poly version." >&2
            exit 1
          fi
          echo "poly hooks check --added-large-files is still available."
      # 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