# Release workflow for codex-rs.
# To release, follow a workflow like:
# ```
# git tag -a rust-v0.1.0 -m "Release 0.1.0"
# git push origin rust-v0.1.0
# ```

name: rust-release
on:
  push:
    tags:
      - "rust-v*.*.*"

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

jobs:
  tag-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Validate tag matches Cargo.toml version
        shell: bash
        run: |
          set -euo pipefail
          echo "::group::Tag validation"

          # 1. Must be a tag and match the regex
          [[ "${GITHUB_REF_TYPE}" == "tag" ]] \
            || { echo "❌  Not a tag push"; exit 1; }
          [[ "${GITHUB_REF_NAME}" =~ ^rust-v[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta)(\.[0-9]+)?)?$ ]] \
            || { echo "❌  Tag '${GITHUB_REF_NAME}' doesn't match expected format"; exit 1; }

          # 2. Extract versions
          tag_ver="${GITHUB_REF_NAME#rust-v}"
          cargo_ver="$(grep -m1 '^version' codex-rs/Cargo.toml \
                        | sed -E 's/version *= *"([^"]+)".*/\1/')"

          # 3. Compare
          [[ "${tag_ver}" == "${cargo_ver}" ]] \
            || { echo "❌  Tag ${tag_ver} ≠ Cargo.toml ${cargo_ver}"; exit 1; }

          echo "✅  Tag and Cargo.toml agree (${tag_ver})"
          echo "::endgroup::"

  build:
    needs: tag-check
    name: ${{ matrix.runner }} - ${{ matrix.target }}
    runs-on: ${{ matrix.runner }}
    timeout-minutes: 30
    defaults:
      run:
        working-directory: codex-rs

    strategy:
      fail-fast: false
      matrix:
        include:
          - runner: macos-14
            target: aarch64-apple-darwin
          - runner: macos-14
            target: x86_64-apple-darwin
          - runner: ubuntu-24.04
            target: x86_64-unknown-linux-musl
          - runner: ubuntu-24.04
            target: x86_64-unknown-linux-gnu
          - runner: ubuntu-24.04-arm
            target: aarch64-unknown-linux-musl
          - runner: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu

    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@1.88
        with:
          targets: ${{ matrix.target }}

      - uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            ${{ github.workspace }}/codex-rs/target/
          key: cargo-release-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}

      - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}}
        name: Install musl build tools
        run: |
          sudo apt install -y musl-tools pkg-config

      - name: Cargo build
        run: cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-exec --bin codex-linux-sandbox

      - name: Stage artifacts
        shell: bash
        run: |
          dest="dist/${{ matrix.target }}"
          mkdir -p "$dest"

          cp target/${{ matrix.target }}/release/codex-exec "$dest/codex-exec-${{ matrix.target }}"
          cp target/${{ matrix.target }}/release/codex "$dest/codex-${{ matrix.target }}"

        # After https://github.com/openai/codex/pull/1228 is merged and a new
        # release is cut with an artifacts built after that PR, the `-gnu`
        # variants can go away as we will only use the `-musl` variants.
      - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'x86_64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'aarch64-unknown-linux-musl' }}
        name: Stage Linux-only artifacts
        shell: bash
        run: |
          dest="dist/${{ matrix.target }}"
          cp target/${{ matrix.target }}/release/codex-linux-sandbox "$dest/codex-linux-sandbox-${{ matrix.target }}"

      - name: Compress artifacts
        shell: bash
        run: |
          # Path that contains the uncompressed binaries for the current
          # ${{ matrix.target }}
          dest="dist/${{ matrix.target }}"

          # For compatibility with environments that lack the `zstd` tool we
          # additionally create a `.tar.gz` alongside every single binary that
          # we publish. The end result is:
          #   codex-<target>.zst          (existing)
          #   codex-<target>.tar.gz       (new)
          #   ...same naming for codex-exec-* and codex-linux-sandbox-*

          # 1. Produce a .tar.gz for every file in the directory *before* we
          #    run `zstd --rm`, because that flag deletes the original files.
          for f in "$dest"/*; do
            base="$(basename "$f")"
            # Skip files that are already archives (shouldn't happen, but be
            # safe).
            if [[ "$base" == *.tar.gz ]]; then
              continue
            fi

            # Create per-binary tar.gz
            tar -C "$dest" -czf "$dest/${base}.tar.gz" "$base"

            # Also create .zst (existing behaviour) *and* remove the original
            # uncompressed binary to keep the directory small.
            zstd -T0 -19 --rm "$dest/$base"
          done

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.target }}
          # Upload the per-binary .zst files as well as the new .tar.gz
          # equivalents we generated in the previous step.
          path: |
            codex-rs/dist/${{ matrix.target }}/*

  release:
    needs: build
    name: release
    runs-on: ubuntu-latest

    steps:
      - uses: actions/download-artifact@v4
        with:
          path: dist

      - name: List
        run: ls -R dist/

      - name: Define release name
        id: release_name
        run: |
          # Extract the version from the tag name, which is in the format
          # "rust-v0.1.0".
          version="${GITHUB_REF_NAME#rust-v}"
          echo "name=${version}" >> $GITHUB_OUTPUT

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          name: ${{ steps.release_name.outputs.name }}
          tag_name: ${{ github.ref_name }}
          files: dist/**
          # Mark as prerelease only when the version has a suffix after x.y.z
          # (e.g. -alpha, -beta). Otherwise publish a normal release.
          prerelease: ${{ contains(steps.release_name.outputs.name, '-') }}

      - uses: facebook/dotslash-publish-release@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag: ${{ github.ref_name }}
          config: .github/dotslash-config.json
