ime 1.0.0

A fast, lightweight CLI for reading, writing, and wiping metadata from image files.
name: Release

on:
  push:
    tags:
      - "v*" # Trigger on version tags, e.g., v1.0.0, v0.1.0
  workflow_dispatch:
    inputs:
      version:
        description: 'Version tag for manual release (e.g., v0.1.0-test)'
        required: true
        type: string
      publish_to_crates_io:
        description: 'Also publish to crates.io (leave unchecked for test/manual releases)'
        required: false
        type: boolean
        default: false

permissions:
  contents: write

jobs:
  release:
    name: Build and Release
    runs-on: ${{ matrix.os }}
    strategy:
      # Let the other target finish (and upload its asset) even if one leg fails.
      # Matters more now that the Windows leg cross-compiles instead of using its own OS runner.
      fail-fast: false
      matrix:
        include:
          # `primary: true` marks the one leg that generates the release notes/changelog -
          # otherwise each matrix leg would generate and append its own copy (duplicate text).
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            executable_name: ime
            asset_name: ime-linux-amd64
            cross: false
            primary: true
          # Windows binary is cross-compiled from Ubuntu via cargo-xwin instead of running
          # on windows-latest. This avoids the slower/more expensive Windows runner entirely.
          - os: ubuntu-latest
            target: x86_64-pc-windows-msvc
            executable_name: ime.exe
            asset_name: ime-windows-amd64.exe
            cross: true
            primary: false

    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

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

      # Caches ~/.cargo/registry, ~/.cargo/git and target/, keyed on Cargo.lock.
      # The `key` input keeps the two matrix legs (musl vs windows-msvc) in separate caches.
      - name: Cache cargo registry and build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      # mold links noticeably faster than the default linker. Only applies to the native
      # musl build; the cross-compiled Windows build below uses lld-link via clang-cl instead.
      - name: Install mold linker
        if: ${{ !matrix.cross }}
        uses: rui314/setup-mold@v1

      - name: Install musl-tools (Linux target)
        if: ${{ !matrix.cross }}
        run: sudo apt-get update && sudo apt-get install -y musl-tools

      # clang/lld/llvm ship preinstalled on the ubuntu-latest runner image, so cargo-xwin
      # (which drives clang-cl under the hood) needs no extra apt packages to cross-build MSVC.
      - name: Cache cargo-xwin binary
        if: matrix.cross
        id: cargo-xwin-cache
        uses: actions/cache@v6
        with:
          path: ~/.cargo/bin/cargo-xwin
          # Bump the "v1" suffix to force a rebuild if you ever want to update cargo-xwin.
          key: cargo-xwin-bin-v1

      - name: Install cargo-xwin (Windows cross target)
        if: matrix.cross && steps.cargo-xwin-cache.outputs.cache-hit != 'true'
        run: cargo install --locked cargo-xwin

      # cargo-xwin downloads the MS CRT/Windows SDK on first use (several hundred MB).
      # Caching this directory means only the very first run pays that download cost.
      - name: Cache Windows SDK/CRT (xwin)
        if: matrix.cross
        uses: actions/cache@v6
        with:
          path: /home/runner/.xwin-cache
          # Bump the "v1" suffix if you ever need to force a fresh SDK download.
          key: xwin-sdk-v1

      - name: Build binary (native)
        if: ${{ !matrix.cross }}
        run: cargo build --release --target ${{ matrix.target }}

      - name: Build binary (cross-compiled via cargo-xwin)
        if: matrix.cross
        env:
          XWIN_CACHE_DIR: /home/runner/.xwin-cache
        run: cargo xwin build --release --target ${{ matrix.target }}

      - name: Rename binary for upload
        shell: bash
        run: |
          mv target/${{ matrix.target }}/release/${{ matrix.executable_name }} target/${{ matrix.target }}/release/${{ matrix.asset_name }}

      - name: Create Release and Upload Asset
        uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }}
          files: target/${{ matrix.target }}/release/${{ matrix.asset_name }}
          # Only the primary leg generates notes; the other just uploads its asset
          # to the release that already exists by the time it gets here (or vice versa).
          generate_release_notes: ${{ matrix.primary }}

  publish-crate:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: release # Only publish after successful build and GitHub release
    # crates.io publishes are permanent and can't be un-published, so a manual
    # (workflow_dispatch) run only publishes if the checkbox was explicitly ticked.
    # Once the tag-push trigger above is re-enabled, tag pushes always publish.
    if: github.event_name != 'workflow_dispatch' || github.event.inputs.publish_to_crates_io == 'true'
    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

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

      # `cargo publish` builds the crate to verify it, so caching pays off here too.
      - name: Cache cargo registry and build artifacts
        uses: Swatinem/rust-cache@v2
        with:
          key: publish-crate

      - name: Publish to crates.io
        run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}