name: release
on:
push:
tags: ["v*.*.*"]
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
guard:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.v.outputs.version }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - id: v
run: |
set -eu
tag=${GITHUB_REF#refs/tags/v}
manifest=$(awk -F'"' '/^version/ {print $2; exit}' Cargo.toml)
if [ "$tag" != "$manifest" ]; then
echo "tag v$tag but Cargo.toml says $manifest" >&2
exit 1
fi
echo "version=$tag" >> "$GITHUB_OUTPUT"
- run: make check
build:
needs: guard
strategy:
fail-fast: false
matrix:
include:
- { target: aarch64-apple-darwin, os: macos-latest }
- { target: x86_64-apple-darwin, os: macos-latest }
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
- { target: aarch64-unknown-linux-gnu, os: ubuntu-latest }
- { target: x86_64-unknown-linux-musl, os: ubuntu-latest }
- { target: x86_64-pc-windows-msvc, os: windows-latest }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - run: rustup target add ${{ matrix.target }}
- name: cross toolchain
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'x86_64-unknown-linux-musl' }}
run: |
set -eu
sudo apt-get update
if [ "${{ matrix.target }}" = aarch64-unknown-linux-gnu ]; then
sudo apt-get install -y gcc-aarch64-linux-gnu
echo 'CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc' >> "$GITHUB_ENV"
else
sudo apt-get install -y musl-tools
fi
- run: cargo build --release --locked --target ${{ matrix.target }}
- name: archive
run: |
set -eu
name=git-attest-${{ needs.guard.outputs.version }}-${{ matrix.target }}
mkdir -p "dist/$name"
if [ "${{ matrix.os }}" = windows-latest ]; then
cp "target/${{ matrix.target }}/release/git-attest.exe" "dist/$name/"
else
cp "target/${{ matrix.target }}/release/git-attest" "dist/$name/"
fi
cp README.md LICENSE SPEC.md "dist/$name/"
tar czf "dist/$name.tar.gz" -C dist "$name"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with:
name: ${{ matrix.target }}
path: dist/*.tar.gz
publish:
needs: [guard, build]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: { path: art, merge-multiple: true }
- run: cd art && sha256sum ./*.tar.gz > SHA256SUMS
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 with:
files: |
art/*.tar.gz
art/SHA256SUMS
generate_release_notes: true
body: |
Pin by commit: `uses: fredericrous/attest@${{ github.sha }} # v${{ needs.guard.outputs.version }}`
publish-crates:
needs: [guard, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - name: CARGO_REGISTRY_TOKEN is set
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -eu
if [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then
echo "::error::no CARGO_REGISTRY_TOKEN; the library must reach crates.io"
exit 1
fi
- run: cargo publish --locked
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: the version resolves on crates.io
env:
VERSION: ${{ needs.guard.outputs.version }}
run: |
set -eu
for i in 1 2 3 4 5 6 7 8 9 10; do
if cargo info "git-attest@$VERSION" > /dev/null 2>&1; then
echo "git-attest@$VERSION resolves"
exit 0
fi
sleep $((i * 6))
done
echo "::error::git-attest@$VERSION does not resolve on crates.io"
exit 1
move-major-alias:
needs: [guard, publish, publish-crates]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: move the major alias
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
version="${{ needs.guard.outputs.version }}"
# Refuse rather than invent. An empty version here previously became
# the tag `v`; a release that cannot name its own major is a bug, not
# something to paper over.
case "$version" in
[0-9]*.[0-9]*.[0-9]*) ;;
*) echo "guard produced no usable version: '$version'" >&2; exit 1 ;;
esac
major=v${version%%.*}
# The git refs API, NOT `git push`.
#
# GITHUB_TOKEN is refused when it pushes a ref whose commit carries a
# .github/workflows/ file differing from the default branch:
#
# refusing to allow a GitHub App to create or update workflow
# `.github/workflows/release.yaml` without `workflows` permission
#
# That is invisible on an ordinary release, where the tagged commit
# IS the default branch, and it fires on a tag cut from a hotfix
# branch or on a re-run after main has moved — i.e. exactly when
# somebody is already dealing with something else. Updating a ref
# moves a pointer and never presents file contents, so the guard does
# not apply. No checkout is needed either.
if gh api "repos/$GITHUB_REPOSITORY/git/refs/tags/$major" > /dev/null 2>&1; then
gh api -X PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$major" \
-f sha="$GITHUB_SHA" -F force=true > /dev/null
else
gh api -X POST "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/$major" -f sha="$GITHUB_SHA" > /dev/null
fi
# Verify the EFFECT, and that it is the RIGHT ref — the previous
# version checked only that *a* ref it had pushed existed, which was
# true of the wrongly-named `v` it had just created.
got=$(gh api "repos/$GITHUB_REPOSITORY/git/refs/tags/$major" --jq .object.sha)
[ "$got" = "$GITHUB_SHA" ] || {
echo "$major points at $got, expected $GITHUB_SHA" >&2; exit 1; }
echo "$major -> $got"