Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,26 @@ jobs:
VERSION="${DISPATCH_VERSION}"
TAG="manual-${VERSION}"
PUBLISH="${{ inputs.publish }}"
# releases/latest/download/appcast.xml is the feed every installed
# copy reads. A manual run must land as a prerelease so it can never
# become "latest" and offer a test build to all users.
PRERELEASE=true
else
TAG="${GITHUB_REF_NAME}"
case "$TAG" in
v*) VERSION="${TAG#v}" ;;
*) echo "Tag must start with v" >&2; exit 1 ;;
esac
PUBLISH=true
PRERELEASE=false
fi
case "$VERSION" in
"") echo "Version must be non-empty" >&2; exit 1 ;;
esac
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "publish=$PUBLISH" >> "$GITHUB_OUTPUT"
echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT"

- name: Require a changelog for this version
env:
Expand Down Expand Up @@ -187,14 +193,15 @@ jobs:
- name: Generate appcast
env:
VERSION: ${{ steps.ver.outputs.version }}
TAG: ${{ steps.ver.outputs.tag }}
ZIP: ${{ steps.sum.outputs.zip }}
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
run: |
KEY_FILE="${RUNNER_TEMP}/sparkle-ed-private-key"
umask 077
printf '%s' "$SPARKLE_ED_PRIVATE_KEY" > "$KEY_FILE"
SPARKLE_ED_KEY_FILE="$KEY_FILE" \
./scripts/generate-appcast.sh "$VERSION" "$ZIP" "docs/changelog/${VERSION}.md"
./scripts/generate-appcast.sh "$VERSION" "$ZIP" "docs/changelog/${VERSION}.md" "$TAG"
rm -f "$KEY_FILE"

- name: Upload workflow artifacts (dry run / debugging)
Expand Down Expand Up @@ -222,9 +229,15 @@ jobs:
VERSION: ${{ steps.ver.outputs.version }}
ZIP: ${{ steps.sum.outputs.zip }}
DMG: ${{ steps.sum.outputs.dmg }}
PRERELEASE: ${{ steps.ver.outputs.prerelease }}
run: |
if gh release view "$TAG" >/dev/null 2>&1; then
gh release upload "$TAG" "$ZIP" "$DMG" dist/appcast.xml --clobber
elif [ "$PRERELEASE" = "true" ]; then
gh release create "$TAG" "$ZIP" "$DMG" dist/appcast.xml \
--title "WinMice $VERSION" \
--notes-file /tmp/release-notes.md \
--prerelease
else
gh release create "$TAG" "$ZIP" "$DMG" dist/appcast.xml \
--title "WinMice $VERSION" \
Expand All @@ -233,7 +246,10 @@ jobs:

- name: Update Homebrew tap
id: tap
if: steps.ver.outputs.publish == 'true'
# Skipped for manual runs: the cask URL is built from v<version>, which
# does not exist under a manual-<version> tag, so bumping it would point
# every Homebrew user at a 404.
if: steps.ver.outputs.publish == 'true' && steps.ver.outputs.prerelease != 'true'
continue-on-error: true
env:
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion docs/appcast-template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<description sparkle:format="markdown"><![CDATA[
{{CHANGES}}
]]></description>
<enclosure url="https://github.com/anibalribeiro/WinMice/releases/download/v{{VERSION}}/WinMice-{{VERSION}}.zip"
<enclosure url="https://github.com/anibalribeiro/WinMice/releases/download/{{TAG}}/WinMice-{{VERSION}}.zip"
sparkle:edSignature="{{ED_SIGNATURE}}"
length="{{LENGTH}}"
type="application/octet-stream" />
Expand Down
6 changes: 6 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ Release or bumping Homebrew:

Only then cut a real `vX.Y.Z` tag.

A manual run tags `manual-<version>` rather than `v<version>`. If you do check
**publish** on one, it is deliberately constrained so it cannot be mistaken for
a real release: the GitHub Release is created as a **prerelease**, so it never
becomes `releases/latest` and the appcast there is never served to installed
copies, and the Homebrew cask is left alone. Real releases come from tag pushes.

## Release checklist

1. Ensure the Apple, Homebrew, and Sparkle prerequisites above are set, and a
Expand Down
12 changes: 9 additions & 3 deletions scripts/generate-appcast.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#!/bin/sh
set -eu

VERSION="${1:?usage: generate-appcast.sh <version> <zip> <changelog.md>}"
ZIP="${2:?usage: generate-appcast.sh <version> <zip> <changelog.md>}"
CHANGELOG="${3:?usage: generate-appcast.sh <version> <zip> <changelog.md>}"
VERSION="${1:?usage: generate-appcast.sh <version> <zip> <changelog.md> <tag>}"
ZIP="${2:?usage: generate-appcast.sh <version> <zip> <changelog.md> <tag>}"
CHANGELOG="${3:?usage: generate-appcast.sh <version> <zip> <changelog.md> <tag>}"
# The tag is required rather than derived from the version: releases are not
# always tagged v<version>, and an enclosure URL built from the wrong tag sends
# every user to a 404.
TAG="${4:?usage: generate-appcast.sh <version> <zip> <changelog.md> <tag>}"
KEY_FILE="${SPARKLE_ED_KEY_FILE:?SPARKLE_ED_KEY_FILE must point at the exported EdDSA private key}"
TEMPLATE="docs/appcast-template.xml"
OUT="dist/appcast.xml"
Expand Down Expand Up @@ -45,6 +49,7 @@ PUBDATE=$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')

mkdir -p dist
awk -v version="$VERSION" \
-v tag="$TAG" \
-v sig="$ED_SIGNATURE" \
-v len="$LENGTH" \
-v pubdate="$PUBDATE" \
Expand All @@ -56,6 +61,7 @@ awk -v version="$VERSION" \
}
{
gsub(/\{\{VERSION\}\}/, version)
gsub(/\{\{TAG\}\}/, tag)
gsub(/\{\{ED_SIGNATURE\}\}/, sig)
gsub(/\{\{LENGTH\}\}/, len)
gsub(/\{\{PUBDATE\}\}/, pubdate)
Expand Down
36 changes: 32 additions & 4 deletions scripts/test-generate-appcast.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export SPARKLE_ED_KEY_FILE="$TMP/key"

if ! (
cd "$TMP/project"
"$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md
"$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md v9.9.9
) >"$TMP/stdout" 2>"$TMP/stderr"; then
printf 'generate-appcast unexpectedly failed\n' >&2
cat "$TMP/stderr" >&2
Expand Down Expand Up @@ -57,12 +57,40 @@ fi
# Missing changelog must fail rather than emit an appcast with empty notes.
if (
cd "$TMP/project"
"$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/nope.md
"$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/nope.md v9.9.9
) >/dev/null 2>&1; then
echo "expected generate-appcast to fail on a missing changelog" >&2
exit 1
fi

# The download URL must come from the tag the release is actually published
# under, not from the version. A workflow_dispatch run tags manual-<version>,
# and an appcast pointing at v<version> would send every user to a 404.
if ! (
cd "$TMP/project"
"$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md manual-9.9.9
) >/dev/null 2>"$TMP/tag-stderr"; then
printf 'generate-appcast failed for a manual- tag\n' >&2
cat "$TMP/tag-stderr" >&2
exit 1
fi

grep -q 'releases/download/manual-9.9.9/WinMice-9.9.9.zip' "$OUT"
if grep -q 'releases/download/v9.9.9/' "$OUT"; then
echo "enclosure URL ignored the tag and used the version instead" >&2
exit 1
fi

# The tag must be supplied explicitly. Defaulting it to v<version> is what
# allowed the mismatch in the first place, so absence is an error.
if (
cd "$TMP/project"
"$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md
) >/dev/null 2>&1; then
echo "expected generate-appcast to require an explicit tag" >&2
exit 1
fi

# sign_update discovery must pick the EdDSA tool by name, not by find's
# traversal order, and must never fall back to the deprecated DSA script.
DISCOVERY_ROOT="$TMP/discovery"
Expand Down Expand Up @@ -91,7 +119,7 @@ chmod +x "$DISCOVERY_ROOT/project/.build/artifacts/sparkle/Sparkle/bin/sign_upda

if ! (
cd "$DISCOVERY_ROOT/project"
env -u SPARKLE_SIGN_UPDATE "$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md
env -u SPARKLE_SIGN_UPDATE "$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md v9.9.9
) >"$TMP/discovery-stdout" 2>"$TMP/discovery-stderr"; then
printf 'generate-appcast unexpectedly failed while discovering sign_update\n' >&2
cat "$TMP/discovery-stderr" >&2
Expand Down Expand Up @@ -119,7 +147,7 @@ chmod +x "$ZERO_ROOT/project/.build/artifacts/sparkle/Sparkle/bin/old_dsa_script

if (
cd "$ZERO_ROOT/project"
env -u SPARKLE_SIGN_UPDATE "$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md
env -u SPARKLE_SIGN_UPDATE "$ROOT/scripts/generate-appcast.sh" 9.9.9 dist/WinMice-9.9.9.zip docs/changelog-9.9.9.md v9.9.9
) >"$TMP/zero-stdout" 2>"$TMP/zero-stderr"; then
echo "expected generate-appcast to fail when no EdDSA sign_update is found" >&2
exit 1
Expand Down
Loading