sdk-rust 0.1.0

Canonical Rust core for the Lattix metadata-only control-plane SDK
Documentation
name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      publish:
        description: Publish to crates.io after creating the GitHub release
        required: false
        default: false
        type: boolean

permissions:
  contents: write

env:
  PUBLIC_MIRROR_REPO: LATTIX-IO/sdk-rust-public

jobs:
  verify-version:
    runs-on: ubuntu-latest
    outputs:
      crate_version: ${{ steps.version.outputs.crate_version }}
      tag_name: ${{ steps.version.outputs.tag_name }}
      should_publish: ${{ steps.version.outputs.should_publish }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Verify tag and crate version
        id: version
        shell: bash
        env:
          INPUT_PUBLISH: ${{ inputs.publish }}
        run: |
          set -euo pipefail
          crate_version=$(cargo metadata --no-deps --format-version 1 | python -c "import json,sys; print(json.load(sys.stdin)['packages'][0]['version'])")
          tag_name="${GITHUB_REF_NAME:-v${crate_version}}"
          if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
            expected_tag="v${crate_version}"
            if [[ "${tag_name}" != "${expected_tag}" ]]; then
              echo "::error::Tag ${tag_name} does not match Cargo.toml version ${crate_version}" >&2
              exit 1
            fi
          fi
          should_publish=false
          if [[ "${GITHUB_EVENT_NAME}" == "push" || "${INPUT_PUBLISH:-false}" == "true" ]]; then
            should_publish=true
          fi
          echo "crate_version=${crate_version}" >> "$GITHUB_OUTPUT"
          echo "tag_name=${tag_name}" >> "$GITHUB_OUTPUT"
          echo "should_publish=${should_publish}" >> "$GITHUB_OUTPUT"

  native-assets:
    needs: verify-version
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            archive_name: sdk-rust-native-linux-x86_64.tar.gz
            library_name: libsdk_rust.so
            rust_target: ''
            library_dir: target/release
          - os: macos-14
            archive_name: sdk-rust-native-macos-x86_64.tar.gz
            library_name: libsdk_rust.dylib
            rust_target: x86_64-apple-darwin
            library_dir: target/x86_64-apple-darwin/release
          - os: macos-14
            archive_name: sdk-rust-native-macos-aarch64.tar.gz
            library_name: libsdk_rust.dylib
            rust_target: ''
            library_dir: target/release
          - os: windows-latest
            archive_name: sdk-rust-native-windows-x86_64.zip
            library_name: sdk_rust.dll
            rust_target: ''
            library_dir: target/release
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install extra Rust target
        if: ${{ matrix.rust_target != '' }}
        shell: bash
        run: rustup target add "${{ matrix.rust_target }}"

      - name: Cache Cargo artifacts
        uses: Swatinem/rust-cache@v2

      - name: Build release library
        if: ${{ matrix.rust_target == '' }}
        shell: bash
        run: cargo build --release

      - name: Build release library for explicit target
        if: ${{ matrix.rust_target != '' }}
        shell: bash
        run: cargo build --release --target "${{ matrix.rust_target }}"

      - name: Package native archive (Unix)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          set -euo pipefail
          archive_root="dist/native"
          mkdir -p "${archive_root}/include"
          cp "${{ matrix.library_dir }}/${{ matrix.library_name }}" "${archive_root}/"
          cp "include/lattix_sdk.h" "${archive_root}/include/"
          tar -C dist -czf "dist/${{ matrix.archive_name }}" native

      - name: Package native archive (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $archiveRoot = Join-Path $PWD 'dist/native'
          New-Item -ItemType Directory -Force -Path (Join-Path $archiveRoot 'include') | Out-Null
          Copy-Item (Join-Path $PWD '${{ matrix.library_dir }}/${{ matrix.library_name }}') $archiveRoot -Force
          Copy-Item (Join-Path $PWD 'include/lattix_sdk.h') (Join-Path $archiveRoot 'include/lattix_sdk.h') -Force
          Compress-Archive -Path (Join-Path $PWD 'dist/native/*') -DestinationPath (Join-Path $PWD 'dist/${{ matrix.archive_name }}') -Force

      - name: Upload native archive
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.archive_name }}
          path: dist/${{ matrix.archive_name }}

  github-release:
    needs:
      - verify-version
      - native-assets
    runs-on: ubuntu-latest
    steps:
      - name: Download native artifacts
        uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - name: Create GitHub release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.verify-version.outputs.tag_name }}
          generate_release_notes: true
          files: dist/*
          fail_on_unmatched_files: true
          prerelease: ${{ contains(needs.verify-version.outputs.tag_name, '-') }}

  public-mirror-release:
    needs:
      - verify-version
      - github-release
    runs-on: ubuntu-latest
    permissions:
      contents: read
    env:
      MIRROR_TOKEN: ${{ secrets.GH_PAT }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download native artifacts
        uses: actions/download-artifact@v4
        with:
          path: dist
          merge-multiple: true

      - name: Verify public mirror token is configured
        shell: bash
        run: |
          set -euo pipefail
          if [[ -z "${MIRROR_TOKEN:-}" ]]; then
            echo "::error::GH_PAT secret is not configured for sdk-rust releases." >&2
            exit 1
          fi

      - name: Configure git identity
        shell: bash
        run: |
          set -euo pipefail
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

      - name: Sync repository contents to public mirror
        shell: bash
        run: |
          set -euo pipefail
          mirror_dir="$(mktemp -d)"
          trap 'rm -rf "${mirror_dir}"' EXIT

          git clone "https://x-access-token:${MIRROR_TOKEN}@github.com/${PUBLIC_MIRROR_REPO}.git" "${mirror_dir}"
          rsync -a --delete \
            --exclude '.git/' \
            --exclude 'target/' \
            --exclude 'dist/' \
            ./ "${mirror_dir}/"

          pushd "${mirror_dir}" >/dev/null
          if [[ -n "$(git status --porcelain)" ]]; then
            git add -A
            git commit -m "Mirror ${GITHUB_REPOSITORY}@${GITHUB_SHA}"
            git push origin HEAD:main
          else
            git push origin HEAD:main
          fi

          git tag -f "${{ needs.verify-version.outputs.tag_name }}"
          git push origin "refs/tags/${{ needs.verify-version.outputs.tag_name }}" --force
          popd >/dev/null

      - name: Publish mirrored GitHub release
        shell: bash
        env:
          GH_TOKEN: ${{ secrets.GH_PAT }}
        run: |
          set -euo pipefail
          tag_name="${{ needs.verify-version.outputs.tag_name }}"
          prerelease_flag=()
          if [[ "${tag_name}" == *-* ]]; then
            prerelease_flag+=(--prerelease)
          fi

          if gh release view "${tag_name}" --repo "${PUBLIC_MIRROR_REPO}" >/dev/null 2>&1; then
            gh release upload "${tag_name}" dist/* --repo "${PUBLIC_MIRROR_REPO}" --clobber
          else
            gh release create "${tag_name}" dist/* --repo "${PUBLIC_MIRROR_REPO}" --generate-notes "${prerelease_flag[@]}"
          fi

  publish-readiness:
    if: ${{ needs.verify-version.outputs.should_publish == 'true' }}
    needs:
      - verify-version
      - github-release
      - public-mirror-release
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Verify crates.io publish readiness
        run: cargo publish --locked --dry-run

  publish-crate:
    if: ${{ needs.verify-version.outputs.should_publish == 'true' }}
    needs:
      - verify-version
      - publish-readiness
    runs-on: ubuntu-latest
    permissions:
      contents: read
    env:
      CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Verify crates.io token is configured
        shell: bash
        run: |
          set -euo pipefail
          if [[ -z "${CARGO_REGISTRY_TOKEN:-}" ]]; then
            echo "::error::CARGO_REGISTRY_TOKEN secret is not configured for sdk-rust releases." >&2
            exit 1
          fi

      - name: Publish crate to crates.io
        run: cargo publish --locked