minlz 1.0.2

S2 compression format - compatible with klauspost/compress/s2
Documentation
name: Release

on:
  push:
    tags:
      - 'v*.*.*'
  # Manual trigger for re-packaging binaries against an existing tag (e.g.
  # if the original release-assets job failed for environmental reasons).
  # The crates.io publish job is gated on `push` so manual runs only
  # rebuild + re-upload binaries.
  workflow_dispatch:
    inputs:
      tag:
        description: 'Existing tag to package binaries for (e.g. v0.1.5)'
        required: true
        type: string

env:
  CARGO_TERM_COLOR: always

jobs:
  build-release:
    name: Build Release
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            asset_os: linux-x86_64
          - os: ubuntu-latest
            target: aarch64-unknown-linux-gnu
            asset_os: linux-aarch64
          - os: macos-latest
            target: x86_64-apple-darwin
            asset_os: macos-x86_64
          - os: macos-latest
            target: aarch64-apple-darwin
            asset_os: macos-aarch64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            asset_os: windows-x86_64

    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          # On workflow_dispatch we need to check out the requested tag,
          # not the workflow's default branch.
          ref: ${{ inputs.tag || github.ref }}

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

      - name: Install aarch64 cross linker
        if: matrix.target == 'aarch64-unknown-linux-gnu'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
          echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV

      - name: Build library
        run: cargo build --release --target ${{ matrix.target }}

      - name: Run library tests
        if: "!contains(matrix.target, 'aarch64')"
        run: cargo test --release --target ${{ matrix.target }}

      - name: Build minlz-tools binaries
        working-directory: minlz-tools
        run: cargo build --release --target ${{ matrix.target }}

      - name: Upload binaries
        uses: actions/upload-artifact@v7
        with:
          name: tools-${{ matrix.asset_os }}
          path: |
            minlz-tools/target/${{ matrix.target }}/release/s2c
            minlz-tools/target/${{ matrix.target }}/release/s2d
            minlz-tools/target/${{ matrix.target }}/release/s2c.exe
            minlz-tools/target/${{ matrix.target }}/release/s2d.exe
          if-no-files-found: error
          retention-days: 7

  release-assets:
    name: Package and attach to GitHub release
    runs-on: ubuntu-latest
    needs: build-release
    if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
    permissions:
      contents: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
        with:
          # On workflow_dispatch we want the tag, not the default branch.
          # Need full history + tag objects so we can read the annotated
          # tag message for release notes.
          ref: ${{ inputs.tag || github.ref }}
          fetch-depth: 0

      - name: Install llvm-lipo (for macOS universal binary)
        run: |
          # Ubuntu's `llvm` package installs versioned binaries like
          # /usr/lib/llvm-18/bin/llvm-lipo but does NOT create a bare
          # `llvm-lipo` symlink. Install the package, then expose
          # whichever version is available as a plain `llvm-lipo`.
          sudo apt-get update
          sudo apt-get install -y llvm
          LIPO=$(ls /usr/lib/llvm-*/bin/llvm-lipo /usr/bin/llvm-lipo-* 2>/dev/null | sort -V | tail -1)
          if [ -z "$LIPO" ]; then
            echo "::error::llvm-lipo not found after installing the llvm package" >&2
            exit 1
          fi
          sudo ln -sf "$LIPO" /usr/local/bin/llvm-lipo
          llvm-lipo --version

      - name: Download build artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts

      - name: Build archives
        env:
          # On workflow_dispatch GITHUB_REF_NAME is the workflow's branch
          # (e.g. "master"), not the requested tag; use the input instead.
          INPUT_TAG: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          VERSION="${INPUT_TAG:-$GITHUB_REF_NAME}"
          mkdir -p dist

          package_unix() {
            local label="$1"  # e.g. linux-x86_64
            local srcdir="$2"  # path containing s2c and s2d
            local name="minlz-tools-${VERSION}-${label}"
            local stage="stage/${name}"
            mkdir -p "${stage}"
            cp "${srcdir}/s2c" "${srcdir}/s2d" "${stage}/"
            chmod +x "${stage}/s2c" "${stage}/s2d"
            cp LICENSE "${stage}/"
            cp minlz-tools/README.md "${stage}/"
            tar -C stage -czf "dist/${name}.tar.gz" "${name}"
            (cd dist && sha256sum "${name}.tar.gz" > "${name}.tar.gz.sha256")
          }

          # Linux x86_64
          package_unix linux-x86_64 artifacts/tools-linux-x86_64

          # Linux aarch64
          package_unix linux-aarch64 artifacts/tools-linux-aarch64

          # macOS universal (lipo x86_64 + aarch64)
          mkdir -p artifacts/tools-macos-universal
          llvm-lipo -create \
            artifacts/tools-macos-x86_64/s2c \
            artifacts/tools-macos-aarch64/s2c \
            -output artifacts/tools-macos-universal/s2c
          llvm-lipo -create \
            artifacts/tools-macos-x86_64/s2d \
            artifacts/tools-macos-aarch64/s2d \
            -output artifacts/tools-macos-universal/s2d
          package_unix macos-universal artifacts/tools-macos-universal

          # Windows x86_64
          name="minlz-tools-${VERSION}-windows-x86_64"
          stage="stage/${name}"
          mkdir -p "${stage}"
          cp artifacts/tools-windows-x86_64/s2c.exe "${stage}/"
          cp artifacts/tools-windows-x86_64/s2d.exe "${stage}/"
          cp LICENSE "${stage}/"
          cp minlz-tools/README.md "${stage}/"
          (cd stage && zip -r "../dist/${name}.zip" "${name}")
          (cd dist && sha256sum "${name}.zip" > "${name}.zip.sha256")

          echo "::group::Archive contents"
          ls -lh dist/
          echo "::endgroup::"

      - name: Create or update GitHub release
        env:
          GH_TOKEN: ${{ github.token }}
          INPUT_TAG: ${{ inputs.tag }}
        run: |
          set -euo pipefail
          VERSION="${INPUT_TAG:-$GITHUB_REF_NAME}"
          # Use the annotated tag message as the release body so we don't
          # have to duplicate the notes. Fall back to a stub for lightweight
          # tags or empty messages.
          NOTES="$(git tag -l --format='%(contents)' "${VERSION}")"
          if [ -z "${NOTES}" ]; then
            NOTES="Release ${VERSION}"
          fi
          if ! gh release view "${VERSION}" >/dev/null 2>&1; then
            gh release create "${VERSION}" \
              --title "${VERSION}" \
              --notes "${NOTES}"
          fi
          gh release upload "${VERSION}" dist/* --clobber

  publish-crate:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: build-release
    # Only publish on actual tag pushes; workflow_dispatch is for re-packaging
    # binaries against an already-published tag.
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
    environment: release
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Authenticate to crates.io
        uses: rust-lang/crates-io-auth-action@v1
        id: auth

      - name: Publish minlz
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: cargo publish

      - name: Wait for crates.io index to update
        run: sleep 30

      - name: Publish minlz-tools
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        working-directory: minlz-tools
        run: cargo publish