forjar 1.4.1

Rust-native Infrastructure as Code — bare-metal first, BLAKE3 state, provenance tracing
Documentation
name: Binary Release

# Attaches cross-compiled `forjar` binaries to a GitHub Release as tar.gz +
# sha256 on github-hosted runners. This runs in parallel with the existing
# release.yml (which uses [self-hosted, clean-room] runners) and is the
# durable path for binary attachment — github-hosted runners are not
# subject to self-hosted infra availability/queueing.
#
# Background: v1.3.0 only had 1/6 expected binaries attached because the
# self-hosted matrix in release.yml didn't complete reliably. This workflow
# reliably attaches 4 Linux binaries (musl + gnu × x86_64 + aarch64).
# macOS targets are intentionally deferred to a follow-up.
#
# Triggered by:
#   - `release: published` — fires automatically when a release is published
#   - `workflow_dispatch` — manual re-run / backfill, takes a tag input
#
# Targets: 4 Linux (x86_64/aarch64 × musl/gnu). musl variants are static
# and ideal for Docker / scratch / Alpine; gnu variants are smaller and
# match typical Linux distributions. aarch64 builds use `cross`.

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      tag:
        description: 'Release tag to attach binaries to (e.g. v1.3.0)'
        required: true
        type: string

permissions:
  contents: write  # upload assets to the release

concurrency:
  group: binary-release-${{ github.event.release.tag_name || inputs.tag }}
  cancel-in-progress: false

env:
  CROSS_VERSION: v0.2.5

jobs:
  build:
    name: ${{ matrix.target }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-musl
            cross: false
          - target: x86_64-unknown-linux-gnu
            cross: false
          - target: aarch64-unknown-linux-musl
            cross: true
          - target: aarch64-unknown-linux-gnu
            cross: true
    steps:
      - name: Resolve release tag
        id: tag
        run: |
          TAG="${{ github.event.release.tag_name || inputs.tag }}"
          if [ -z "$TAG" ]; then
            echo "::error::No tag resolved (release event missing tag_name and no dispatch input)"
            exit 1
          fi
          echo "tag=$TAG" >> "$GITHUB_OUTPUT"
          echo "Building forjar for $TAG → ${{ matrix.target }}"

      - name: Checkout at tag
        uses: actions/checkout@v4
        with:
          ref: ${{ steps.tag.outputs.tag }}

      # forjar depends on provable-contracts via Cargo path dep — replicate
      # the checkout + symlink + pv codegen dance from release.yml so the
      # build can resolve `../provable-contracts` and generate
      # `src/generated_contracts.rs` before `cargo build`.
      - name: Checkout provable-contracts (path dep)
        uses: actions/checkout@v4
        with:
          repository: paiml/provable-contracts
          path: provable-contracts

      - name: Symlink provable-contracts for Cargo path deps
        run: ln -sf "$GITHUB_WORKSPACE/provable-contracts" "$GITHUB_WORKSPACE/../provable-contracts"

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      # The workspace pins a specific channel in `rust-toolchain.toml`
      # which overrides the dtolnay action for any cargo invocation in
      # the tree. Add the target explicitly to that pinned toolchain so
      # native cross-compiles (x86_64-musl) don't fail with `can't find
      # crate for core` mid-build.
      - name: Install target on workspace-pinned toolchain
        if: ${{ !matrix.cross }}
        run: rustup target add ${{ matrix.target }}

      - name: Generate contract assertions — pv codegen
        run: |
          if ! command -v pv >/dev/null 2>&1; then
            cargo install provable-contracts-cli --locked || true
          fi
          PV=$(command -v pv 2>/dev/null || true)
          if [ -z "$PV" ]; then
            echo "::warning::pv not found — skipping contract generation"
          else
            PC_CONTRACTS="$GITHUB_WORKSPACE/../provable-contracts/contracts"
            if [ -f src/lib.rs ] && grep -q 'mod generated_contracts' src/lib.rs && [ ! -f src/generated_contracts.rs ]; then
              if [ -d "$PC_CONTRACTS" ]; then
                "$PV" codegen "$PC_CONTRACTS" -o src/generated_contracts.rs || true
              elif [ -d contracts ]; then
                "$PV" codegen contracts/ -o src/generated_contracts.rs || true
              fi
            fi
          fi

      - name: Install musl tools
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y --no-install-recommends musl-tools

      # Cross is used for aarch64 targets — pinned to v0.2.5 (matches the
      # legacy release.yml convention).
      - name: Install cross
        if: matrix.cross
        run: |
          dir="$RUNNER_TEMP/cross"
          mkdir -p "$dir"
          curl -sL "https://github.com/cross-rs/cross/releases/download/${CROSS_VERSION}/cross-x86_64-unknown-linux-musl.tar.gz" \
            | tar xz -C "$dir"
          echo "$dir" >> "$GITHUB_PATH"

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2
        with:
          shared-key: binary-${{ matrix.target }}

      - name: Build forjar
        run: |
          if [ "${{ matrix.cross }}" = "true" ]; then
            cross build --release --features vendored-openssl --bin forjar --target ${{ matrix.target }} --locked
          else
            cargo build --release --features vendored-openssl --bin forjar --target ${{ matrix.target }} --locked
          fi

      - name: Strip binary
        run: |
          TARGET="${{ matrix.target }}"
          BIN_PATH="target/${TARGET}/release/forjar"
          # Each cross image ships only the matching ${triple}-strip;
          # the gnu image lacks musl-strip and vice-versa.
          case "$TARGET" in
            aarch64-unknown-linux-musl) STRIP=aarch64-linux-musl-strip ;;
            aarch64-unknown-linux-gnu)  STRIP=aarch64-linux-gnu-strip ;;
            *)                          STRIP="" ;;
          esac
          if [ -n "$STRIP" ]; then
            docker run --rm -v "$PWD/target:/target:Z" \
              "ghcr.io/cross-rs/${TARGET}:main" \
              "$STRIP" "/$BIN_PATH" || true
          else
            strip "$BIN_PATH" || true
          fi
          ls -la "$BIN_PATH"

      - name: Package archive
        id: package
        run: |
          VERSION="${{ steps.tag.outputs.tag }}"
          TARGET="${{ matrix.target }}"
          ARCHIVE="forjar-${VERSION}-${TARGET}"
          mkdir -p "$ARCHIVE"
          cp "target/${TARGET}/release/forjar" "$ARCHIVE/"
          for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE; do
            [ -f "$f" ] && cp "$f" "$ARCHIVE/"
          done
          tar czf "${ARCHIVE}.tar.gz" "$ARCHIVE"
          shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
          echo "archive=${ARCHIVE}.tar.gz" >> "$GITHUB_OUTPUT"
          echo "sha=${ARCHIVE}.tar.gz.sha256" >> "$GITHUB_OUTPUT"
          du -h "${ARCHIVE}.tar.gz"

      - name: Upload assets to release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release upload "${{ steps.tag.outputs.tag }}" \
            "${{ steps.package.outputs.archive }}" \
            "${{ steps.package.outputs.sha }}" \
            --clobber

  summary:
    name: Summary
    needs: build
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Write summary
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          TAG="${{ github.event.release.tag_name || inputs.tag }}"
          STATUS="${{ needs.build.result }}"
          {
            echo "### Binary Release: $TAG"
            echo ""
            if [ "$STATUS" = "success" ]; then
              echo "- Build matrix: **4/4 targets succeeded**"
            else
              echo "- Build matrix: **partial** (status=$STATUS)"
            fi
            echo "- Targets:"
            echo "  - x86_64-unknown-linux-musl"
            echo "  - x86_64-unknown-linux-gnu"
            echo "  - aarch64-unknown-linux-musl"
            echo "  - aarch64-unknown-linux-gnu"
          } >> "$GITHUB_STEP_SUMMARY"