name: Post-release checks
on:
push:
branches: [master]
schedule:
- cron: "43 5 * * *"
workflow_dispatch:
inputs:
version:
description: "printpdf version to verify (default: Cargo.toml version)"
required: false
permissions:
contents: read
concurrency:
group: post-release-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'push' }}
env:
INDEX_URL: https://index.crates.io/pr/in/printpdf
jobs:
resolve:
name: Resolve release version
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version: ${{ steps.pick.outputs.version }}
published: ${{ steps.pick.outputs.published }}
steps:
- uses: actions/checkout@v4
- name: Pick the version to verify and confirm it is on crates.io
id: pick
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_VERSION: ${{ inputs.version }}
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
set -euo pipefail
manifest_version() {
cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version'
}
case "$EVENT_NAME" in
workflow_dispatch)
VERSION="${INPUT_VERSION:-$(manifest_version)}"
;;
schedule)
# Latest published, not Cargo.toml: between releases these are the same,
# and right after a publish the cron should test what consumers get.
VERSION=$(curl -fsSL "$INDEX_URL" | jq -r 'select(.yanked | not) | .vers' | sort -V | tail -1)
;;
*)
VERSION=$(manifest_version)
;;
esac
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
is_published() {
# -e: nonzero exit when the version is absent (or only present yanked).
curl -fsSL "$INDEX_URL" \
| jq -e --arg v "$VERSION" 'select(.vers == $v and (.yanked | not))' > /dev/null
}
# A push whose head commit message starts with "release" claims this version
# is being published right now — wait for the index instead of skipping.
RELEASE_COMMIT=false
if [ "$EVENT_NAME" = "push" ] \
&& printf '%s' "$HEAD_COMMIT_MESSAGE" | head -n1 | grep -qi '^release'; then
RELEASE_COMMIT=true
fi
ATTEMPTS=1
if [ "$RELEASE_COMMIT" = true ]; then
ATTEMPTS=40 # x 30s = 20 minutes
fi
PUBLISHED=false
for i in $(seq 1 "$ATTEMPTS"); do
if is_published; then
PUBLISHED=true
break
fi
[ "$i" -lt "$ATTEMPTS" ] && sleep 30
done
echo "published=$PUBLISHED" >> "$GITHUB_OUTPUT"
{
echo "### Post-release check: printpdf \`$VERSION\`"
echo ""
echo "- on crates.io (not yanked): **$PUBLISHED**"
echo "- trigger: \`$EVENT_NAME\`, release commit: \`$RELEASE_COMMIT\`"
} >> "$GITHUB_STEP_SUMMARY"
if [ "$RELEASE_COMMIT" = true ] && [ "$PUBLISHED" = false ]; then
echo "::error::Commit message says 'release', but printpdf $VERSION never appeared on crates.io (waited 20 minutes). Publish failed or the version was not bumped."
exit 1
fi
if [ "$PUBLISHED" = false ]; then
echo "::notice::printpdf $VERSION is not on crates.io — nothing to verify (normal for pre-release pushes)."
fi
installable:
name: "consumer install: ${{ matrix.name }}"
needs: resolve
if: needs.resolve.outputs.published == 'true'
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- { name: "default features, linux", os: ubuntu-latest, args: "" }
- { name: "default features, windows", os: windows-latest, args: "" }
- { name: "default features, macos", os: macos-latest, args: "" }
- { name: "no default features", os: ubuntu-latest, args: "--no-default-features" }
- { name: "jpeg only (issue #279)", os: ubuntu-latest, args: "--no-default-features --features jpeg", regression279: true }
- { name: "all image formats", os: ubuntu-latest, args: "--no-default-features --features images,png,jpeg,gif,tiff,bmp,webp,ico,tga,hdr,dds,pnm" }
- { name: "svg", os: ubuntu-latest, args: "--no-default-features --features svg" }
- { name: "html_multithreaded", os: ubuntu-latest, args: "--no-default-features --features html_multithreaded" }
- { name: "default features, wasm32-unknown-unknown", os: ubuntu-latest, args: "", target: wasm32-unknown-unknown }
steps:
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target || '' }}
- name: cargo add printpdf@=${{ needs.resolve.outputs.version }} in a fresh project
shell: bash
env:
VERSION: ${{ needs.resolve.outputs.version }}
ADD_ARGS: ${{ matrix.args }}
TARGET: ${{ matrix.target || '' }}
run: |
set -euo pipefail
cargo new "$RUNNER_TEMP/consumer"
cd "$RUNNER_TEMP/consumer"
# $ADD_ARGS is intentionally word-split.
cargo add "printpdf@=$VERSION" $ADD_ARGS
if [ -n "$TARGET" ]; then
cargo check --target "$TARGET"
else
cargo check
fi
- name: "Regression: known-bad old pins in the consumer lockfile must still build"
if: matrix.regression279 == true
shell: bash
run: |
set -euo pipefail
cd "$RUNNER_TEMP/consumer"
# Consumers rarely have a fresh lockfile, so the published crate must build
# at its declared dependency FLOORS — a floor that doesn't compile is exactly
# how #279 shipped (every time <= 0.3.47 broke inside lopdf's `time` feature).
# time 0.3.36 and image 0.25.2 are printpdf's declared floors.
# Unpinnable (floors moved) = skip; the scenario is then inexpressible.
PINNED=false
if cargo update -p time --precise 0.3.36; then PINNED=true; else
echo "::notice::time 0.3.36 no longer pinnable in this graph — skipping that scenario."
fi
if cargo update -p image --precise 0.25.2; then PINNED=true; else
echo "::notice::image 0.25.2 no longer pinnable in this graph — skipping that scenario."
fi
if [ "$PINNED" = true ]; then
cargo check
fi