vips 0.1.0

Rust bindings for libvips: fast, low-memory image processing with a safe, ergonomic API.
Documentation
# Publish `vips` to crates.io and create a GitHub Release.
#
# Triggers:
#   - push of a version tag (vX.Y.Z or vX.Y.Z-pre)
#   - manual `workflow_dispatch` with a `tag` input (e.g. v0.1.0)
#
# Required repository secret:
#   CRATES_IO_TOKEN — crates.io API token with publish rights for crate `vips`
#
# Release order: publish vips-sys first, then this repo.

name: Release

on:
  push:
    tags:
      - "v[0-9]+.[0-9]+.[0-9]+*"
  workflow_dispatch:
    inputs:
      tag:
        description: "Release tag (e.g. v0.1.0); must match Cargo.toml version"
        required: true
        type: string

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always
  # Tag push → ref name; manual run → workflow input.
  RELEASE_TAG: ${{ inputs.tag || github.ref_name }}

defaults:
  run:
    working-directory: vips-rs

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

jobs:
  # ── Gate: tag/version consistency + multi-OS quality checks ─────────────────
  verify:
    name: Verify (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    timeout-minutes: 30
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            link_tests: true
            gates: true
          - os: macos-latest
            link_tests: true
            gates: false
          - os: windows-latest
            link_tests: false
            gates: false
    steps:
      - name: Checkout vips-rs
        uses: actions/checkout@v7
        with:
          path: vips-rs

      - name: Checkout vips-sys (sibling path dep)
        uses: actions/checkout@v7
        with:
          repository: houseme/vips-sys
          path: vips-sys

      - name: Check tag matches Cargo.toml version
        if: matrix.gates
        shell: bash
        run: |
          set -euo pipefail
          tag="${RELEASE_TAG#v}"
          crate="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)"
          echo "tag=${tag} crate=${crate}"
          if [ "${tag}" != "${crate}" ]; then
            echo "::error::Release tag v${tag} does not match Cargo.toml version ${crate}"
            exit 1
          fi

      - name: Check CHANGELOG has a section for this version
        if: matrix.gates
        shell: bash
        run: |
          set -euo pipefail
          ver="${RELEASE_TAG#v}"
          if ! grep -qE "^## \[${ver}\]" CHANGELOG.md; then
            echo "::error::CHANGELOG.md has no '## [${ver}]' section. Move Unreleased notes before releasing."
            exit 1
          fi
          if grep -qE '^## \[Unreleased\]' CHANGELOG.md; then
            body="$(awk '
              $0 ~ /^## \[Unreleased\]/ {grab=1; next}
              grab && /^## \[/ {exit}
              grab {print}
            ' CHANGELOG.md | sed '/^[[:space:]]*$/d' | sed '/^### /d')"
            if [ -n "${body}" ]; then
              echo "::error::CHANGELOG.md [Unreleased] still has notes. Fold them into [${ver}] before releasing."
              exit 1
            fi
          fi

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy

      - name: Install libvips (Linux)
        if: runner.os == 'Linux' && matrix.link_tests
        run: |
          sudo apt-get update
          sudo apt-get install -y pkg-config libvips libvips-dev libvips-tools

      - name: Install libvips (macOS)
        if: runner.os == 'macOS' && matrix.link_tests
        run: |
          brew install vips pkg-config
          echo "PKG_CONFIG_PATH=$(brew --prefix vips)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" >> "$GITHUB_ENV"

      - name: Cache cargo
        uses: actions/cache@v6
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            vips-rs/target
            vips-sys/target
          key: ${{ runner.os }}-release-cargo-${{ hashFiles('vips-rs/Cargo.lock') }}

      - name: cargo fmt
        if: matrix.gates
        run: cargo fmt --all -- --check

      - name: cargo clippy
        run: cargo clippy --all-targets -- -D warnings

      - name: cargo test
        if: matrix.link_tests
        run: cargo test --all-features

      - name: Package dry-run
        if: matrix.gates
        # Proves the crate is publishable (path deps stripped to version-only).
        run: |
          cargo package --list
          cargo publish --dry-run --locked

  # ── Publish crates.io + GitHub Release (after all platforms pass) ───────────
  publish:
    name: Publish
    needs: verify
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
        with:
          path: vips-rs

      - name: Checkout vips-sys (sibling path dep)
        uses: actions/checkout@v7
        with:
          repository: houseme/vips-sys
          path: vips-sys

      - uses: dtolnay/rust-toolchain@stable

      - name: Publish to crates.io
        run: cargo publish --locked --token "${CRATES_IO_TOKEN}"
        env:
          CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}

      - name: Extract changelog for this version
        id: changelog
        shell: bash
        run: |
          set -euo pipefail
          ver="${RELEASE_TAG#v}"
          awk -v ver="$ver" '
            $0 ~ "^## \\[" ver "\\]" {grab=1; next}
            grab && /^## \[/ {exit}
            grab {print}
          ' CHANGELOG.md > release-notes.md
          if [ ! -s release-notes.md ]; then
            echo "v${ver}" > release-notes.md
          fi
          echo "----- release notes -----"
          cat release-notes.md

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v3
        with:
          tag_name: ${{ env.RELEASE_TAG }}
          name: ${{ env.RELEASE_TAG }}
          body_path: vips-rs/release-notes.md
          draft: false
          prerelease: ${{ contains(env.RELEASE_TAG, '-') }}
          generate_release_notes: false