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
48 changes: 33 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ APP = build/Trapps.app
BINARY = build/trapps
SOURCES = $(wildcard Sources/trapps/*.swift)
VERSION = $(shell /usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' Support/Info.plist)
ZIP = build/Trapps-$(VERSION).zip
DMG = build/Trapps-$(VERSION).dmg
DMG_STAGING = build/dmg
VOLNAME = Trapps
CASK_TMPL = packaging/trapps.cask.tmpl
CASK = build/trapps.rb
MACOS_TARGET = apple-macos13.0
Expand Down Expand Up @@ -68,6 +70,18 @@ define SIGN_SPARKLE
codesign --force $(2) --sign "$(1)" "$(APP)/Contents/Frameworks/Sparkle.framework"
endef

# Pack the signed .app into a compressed, read-only .dmg with a drag-to-Applications
# shortcut. ditto (not cp) preserves the code signature. Removes any prior staging
# dir and dmg first so nothing stale leaks in.
define MAKE_DMG
rm -rf $(DMG_STAGING) "$(DMG)"
mkdir -p $(DMG_STAGING)
ditto $(APP) "$(DMG_STAGING)/Trapps.app"
ln -s /Applications "$(DMG_STAGING)/Applications"
hdiutil create -volname "$(VOLNAME)" -srcfolder $(DMG_STAGING) -ov -format UDZO "$(DMG)"
rm -rf $(DMG_STAGING)
endef

# Prefer a real signing identity so the Accessibility grant survives rebuilds;
# fall back to ad-hoc. Override with: make IDENTITY="Apple Development: ..."
IDENTITY ?= $(shell security find-identity -v -p codesigning 2>/dev/null \
Expand Down Expand Up @@ -137,7 +151,7 @@ run: bundle

# --- Distribution ---
# Universal binary, hardened runtime, Developer ID signature. `make release`
# then `make notarize` produces a Gatekeeper-clean zip in build/.
# then `make notarize` produces a Gatekeeper-clean dmg in build/.

build/trapps-arm64: $(SOURCES) $(SPARKLE_FW)
mkdir -p build
Expand All @@ -152,29 +166,33 @@ release: build/trapps-arm64 build/trapps-x86_64 build/AppIcon.icns
echo "error: no 'Developer ID Application' identity in the keychain."; \
echo "Notarization requires one (Apple Developer Program membership)."; \
exit 1; }
rm -rf $(APP) $(ZIP)
rm -rf $(APP)
rm -f build/Trapps-*.zip build/Trapps-*.dmg
mkdir -p $(APP)/Contents/MacOS
lipo -create -output $(APP)/Contents/MacOS/trapps build/trapps-arm64 build/trapps-x86_64
cp Support/Info.plist $(APP)/Contents/Info.plist
$(STAGE_RESOURCES)
$(EMBED_SPARKLE)
$(call SIGN_SPARKLE,$(RELEASE_IDENTITY),--options runtime --timestamp)
codesign --force --options runtime --timestamp --sign "$(RELEASE_IDENTITY)" $(APP)
ditto -c -k --keepParent $(APP) $(ZIP)
@echo "Built $(ZIP); next: make notarize"
$(MAKE_DMG)
@echo "Built $(DMG); next: make notarize"

# Submitting the dmg notarizes the app inside it too (Apple registers the app's
# code hash), so one round-trip covers both. Staple the ticket to the dmg so it
# opens cleanly offline. The app itself is notarized but not stapled - offline
# first launch is a non-issue for a brew-installed, network-downloaded tool.
notarize:
xcrun notarytool submit $(ZIP) $(NOTARY_AUTH) --wait
xcrun stapler staple $(APP)
ditto -c -k --keepParent $(APP) $(ZIP)
@echo "Notarized, stapled, and re-zipped $(ZIP)"
xcrun notarytool submit $(DMG) $(NOTARY_AUTH) --wait
xcrun stapler staple $(DMG)
@echo "Notarized + stapled dmg: $(DMG)"

# Render the Homebrew cask from the template, filling in the current version and
# the sha256 of the built zip. Run after `make release && make notarize`; the
# the sha256 of the built dmg. Run after `make release && make notarize`; the
# resulting build/trapps.rb goes into the homebrew-tap repo. See packaging/README.md.
cask: $(CASK_TMPL)
@test -f $(ZIP) || { echo "error: $(ZIP) not found - run 'make release && make notarize' first."; exit 1; }
@sha=$$(shasum -a 256 $(ZIP) | awk '{print $$1}'); \
@test -f $(DMG) || { echo "error: $(DMG) not found - run 'make release && make notarize' first."; exit 1; }
@sha=$$(shasum -a 256 $(DMG) | awk '{print $$1}'); \
sed -e 's/@@VERSION@@/$(VERSION)/' -e "s/@@SHA256@@/$$sha/" $(CASK_TMPL) > $(CASK); \
echo "Rendered $(CASK) (version $(VERSION), sha256 $$sha)"

Expand All @@ -186,13 +204,13 @@ sparkle-keys: $(SPARKLE_FW)

# Regenerate the appcast for the current version. Pulls the existing feed from the
# pinned '$(APPCAST_TAG)' release, appends this version (enclosure -> this version's
# release dmg/zip), signs everything with your Keychain EdDSA key, and writes
# release dmg), signs everything with your Keychain EdDSA key, and writes
# build/appcast.xml. Run after `make release && make notarize`, then publish with:
# gh release upload $(APPCAST_TAG) build/appcast.xml --clobber
appcast: $(SPARKLE_FW)
@test -f $(ZIP) || { echo "error: $(ZIP) not found - run 'make release && make notarize' first."; exit 1; }
@test -f $(DMG) || { echo "error: $(DMG) not found - run 'make release && make notarize' first."; exit 1; }
@rm -rf build/appcast && mkdir -p build/appcast
@cp $(ZIP) build/appcast/
@cp $(DMG) build/appcast/
@curl -fsSL -o build/appcast/appcast.xml "$(APPCAST_URL)" \
&& echo "merged existing feed" || echo "no existing feed - starting fresh"
$(SPARKLE_BIN)/generate_appcast \
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A single macOS menu bar icon that reveals every menu bar item ("tray app") from

## Install

Download the latest `Trapps-x.y.z.zip` from [Releases](https://github.com/GregTheGreek/trapps/releases), unzip, and drag `Trapps.app` to `/Applications`. Releases are universal (Apple Silicon + Intel), Developer ID-signed, and notarized. Requires macOS 13+.
Download the latest `Trapps-x.y.z.dmg` from [Releases](https://github.com/GregTheGreek/trapps/releases), open it, and drag `Trapps.app` to `/Applications`. Releases are universal (Apple Silicon + Intel), Developer ID-signed, and notarized. Requires macOS 13+.

Or build from source - see below.

Expand Down Expand Up @@ -52,13 +52,13 @@ The Makefile signs with your Apple Development / Developer ID certificate if one
Versioning is automated with [release-please](https://github.com/googleapis/release-please) (`.github/workflows/release-please.yml`); signing and notarization are done locally so the Developer ID private key never leaves the machine.

1. Conventional commits merged to `main` accumulate into an auto-maintained release PR. Merging it bumps the version in `Support/Info.plist` (via the `x-release-please-version` markers) and `CHANGELOG.md`, tags `vX.Y.Z`, and publishes the GitHub release (without assets).
2. Locally, on `main` at that tag, build the signed + notarized zip, attach it, then refresh the Sparkle appcast so existing installs see the update:
2. Locally, on `main` at that tag, build the signed + notarized dmg, attach it (the exact versioned file, not a glob - a wildcard picks up stale artifacts from earlier builds), then refresh the Sparkle appcast so existing installs see the update:

```sh
git pull
V=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' Support/Info.plist)
make release && make notarize
gh release upload "v$V" "build/Trapps-$V.zip"
gh release upload "v$V" "build/Trapps-$V.dmg"

make appcast # writes build/appcast.xml (signed)
gh release upload appcast build/appcast.xml --clobber
Expand Down
6 changes: 3 additions & 3 deletions packaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ Trapps ships as a [Homebrew cask](https://docs.brew.sh/Cask-Cookbook) from a
personal tap so users can `brew install --cask gregthegreek/tap/trapps`.

`trapps.cask.tmpl` is the cask with `@@VERSION@@` / `@@SHA256@@` placeholders.
`make cask` renders it against the freshly built, notarized zip.
`make cask` renders it against the freshly built, notarized dmg.

## Publishing a release to the tap

The tap is a separate repo named `GregTheGreek/homebrew-tap` (the `homebrew-`
prefix is what makes `brew tap gregthegreek/tap` resolve). Its casks live under
`Casks/`.

Prerequisite: a real notarized zip must already be attached to the GitHub
Prerequisite: a real notarized dmg must already be attached to the GitHub
release (see the "Releasing" section in the top-level README) - the cask's
`sha256` is computed from it, and `brew install` downloads it from there.

```sh
make release && make notarize # produces build/Trapps-<version>.zip
make release && make notarize # produces build/Trapps-<version>.dmg
make cask # renders build/trapps.rb (version + sha256 filled in)

# One time, if the tap does not exist yet:
Expand Down
2 changes: 1 addition & 1 deletion packaging/trapps.cask.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cask "trapps" do
version "@@VERSION@@"
sha256 "@@SHA256@@"

url "https://github.com/GregTheGreek/trapps/releases/download/v#{version}/Trapps-#{version}.zip"
url "https://github.com/GregTheGreek/trapps/releases/download/v#{version}/Trapps-#{version}.dmg"
name "Trapps"
desc "Menu bar app revealing every menu bar item, including ones behind the notch"
homepage "https://github.com/GregTheGreek/trapps"
Expand Down
Loading