pdq 0.5.1

PDF operations library
Documentation
name: Release

on:
  push:
    tags: ["v*"]

env:
  CARGO_TERM_COLOR: always

jobs:
  verify:
    name: Verify tag and tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Check tag matches Cargo.toml version
        run: |
          TAG_VERSION="${GITHUB_REF_NAME#v}"
          CARGO_VERSION="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
          if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
            echo "Tag $GITHUB_REF_NAME does not match Cargo.toml version $CARGO_VERSION" >&2
            exit 1
          fi

      - name: Install qpdf (test validator)
        run: sudo apt-get update && sudo apt-get install -y qpdf

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Run tests
        run: cargo test --all-targets

  build:
    name: Build (${{ matrix.target }})
    needs: verify
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-musl
            os: ubuntu-24.04
          - target: aarch64-unknown-linux-musl
            os: ubuntu-24.04-arm
          - target: x86_64-apple-darwin
            os: macos-latest
          - target: aarch64-apple-darwin
            os: macos-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
    steps:
      - uses: actions/checkout@v5

      - name: Install musl toolchain
        if: contains(matrix.target, 'musl')
        run: sudo apt-get update && sudo apt-get install -y musl-tools

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

      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

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

      - name: Package
        shell: bash
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          NAME="pdq-${VERSION}-${{ matrix.target }}"
          mkdir "$NAME"
          cp README.md "$NAME/"
          if [ "$RUNNER_OS" = "Windows" ]; then
            cp "target/${{ matrix.target }}/release/pdq.exe" "$NAME/"
            7z a "$NAME.zip" "$NAME"
            echo "ASSET=$NAME.zip" >> "$GITHUB_ENV"
          else
            cp "target/${{ matrix.target }}/release/pdq" "$NAME/"
            tar czf "$NAME.tar.gz" "$NAME"
            echo "ASSET=$NAME.tar.gz" >> "$GITHUB_ENV"
          fi

      - uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.target }}
          path: ${{ env.ASSET }}
          if-no-files-found: error

  publish:
    name: Publish to crates.io
    needs: verify
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v5

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Check every [patch.crates-io] override is publish-safe
        # cargo silently strips [patch] tables when packaging, so the published
        # crate always builds against the unpatched dependency. Overrides whose
        # loss only costs performance or output quality are reviewed and listed
        # in the allowlist; anything else must be upstreamed before publishing.
        run: |
          allowlist=.github/publish-patch-allowlist.txt
          patched="$(awk '
            /^\[patch\.crates-io\]/ { in_patch = 1; next }
            /^\[/                   { in_patch = 0 }
            in_patch && /=/         { print $1 }
          ' Cargo.toml)"
          status=0
          for crate in $patched; do
            if grep -qxF "$crate" "$allowlist"; then
              echo "note: $crate is allowlisted; publishing without its patch."
            else
              echo "Cargo.toml patches $crate, which is not in $allowlist." >&2
              echo "The published crate would silently drop that patch." >&2
              echo "Upstream it, or add it to the allowlist with a rationale" >&2
              echo "for why losing it is safe for crates.io consumers." >&2
              status=1
            fi
          done
          exit $status

      - name: Check if version is already on crates.io
        id: check
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          # crates.io rejects curl's default User-Agent with a 403
          if curl -sf -A "pdq-release-workflow (github.com/meistrari/pdq)" \
              "https://crates.io/api/v1/crates/pdq/$VERSION" > /dev/null; then
            echo "Version $VERSION already published; skipping." >&2
            echo "published=true" >> "$GITHUB_OUTPUT"
          else
            echo "published=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Authenticate with crates.io (Trusted Publishing)
        if: steps.check.outputs.published == 'false'
        uses: rust-lang/crates-io-auth-action@v1
        id: auth

      - name: Publish
        if: steps.check.outputs.published == 'false'
        run: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

  release:
    name: Publish GitHub release
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Generate checksums
        run: |
          cd artifacts
          sha256sum -- * > SHA256SUMS

      - name: Create release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create "$GITHUB_REF_NAME" \
            --repo "$GITHUB_REPOSITORY" \
            --title "pdq $GITHUB_REF_NAME" \
            --generate-notes \
            artifacts/*