diff --git a/.gitea/workflows/homebrew-formula.yml b/.gitea/workflows/homebrew-formula.yml new file mode 100644 index 0000000..0b70221 --- /dev/null +++ b/.gitea/workflows/homebrew-formula.yml @@ -0,0 +1,86 @@ +name: Sync Homebrew formula + +# On every version tag, recompute the release tarball checksum and push an +# updated Formula/yellowjacket.rb into the Homebrew tap repo. Keeping the tap +# in a separate repo (github.com/Shadow-Puppet/homebrew-yellowjacket) is what +# lets users install with a single command: +# +# brew install shadow-puppet/yellowjacket/yellowjacket +# +# (`shadow-puppet/yellowjacket` is shorthand for the homebrew-yellowjacket repo; +# brew auto-taps it, so no separate `brew tap` step is needed.) + +on: + push: + tags: + - "v*" + +jobs: + sync-formula: + runs-on: ubuntu-latest + env: + # GitHub PAT (or fine-grained token) with write access to the tap repo. + TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + # Gitea source that serves the release tarball referenced by the formula. + SOURCE_TARBALL_BASE: https://git.ljones.me/yonlu/yellowjacket/archive + # separate GitHub tap repo the formula is published to. + TAP_REPO: Shadow-Puppet/homebrew-yellowjacket + steps: + - name: Check out source (for the canonical formula) + uses: actions/checkout@v4 + + - name: Compute version and tarball checksum + run: | + set -euo pipefail + TAG="${GITHUB_REF_NAME}" # e.g. v1.3.0 + VERSION="${TAG#v}" # e.g. 1.3.0 + TARBALL="${SOURCE_TARBALL_BASE}/${TAG}.tar.gz" + + echo "Fetching ${TARBALL}" + # Retry briefly: the tag archive can lag a few seconds behind the push. + for attempt in 1 2 3 4 5; do + if curl -fSsL "$TARBALL" -o release.tar.gz; then + break + fi + echo "attempt ${attempt} failed, retrying..." + sleep 5 + done + + SHA256="$(sha256sum release.tar.gz | cut -d' ' -f1)" + echo "version=${VERSION} sha256=${SHA256}" + + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + echo "SHA256=${SHA256}" >> "$GITHUB_ENV" + + - name: Render the formula with the new version and checksum + run: | + set -euo pipefail + src="packaging/homebrew/Formula/yellowjacket.rb" + # Rewrite only the two managed lines; the interpolated url picks up the + # new version automatically. + sed -E \ + -e "s|^ version \".*\"| version \"${VERSION}\"|" \ + -e "s|^ sha256 \".*\"| sha256 \"${SHA256}\"|" \ + "$src" > yellowjacket.rb + echo "----- rendered formula -----" + cat yellowjacket.rb + + - name: Push to the Homebrew tap repo + run: | + set -euo pipefail + git clone "https://x-access-token:${TAP_TOKEN}@github.com/${TAP_REPO}.git" tap + mkdir -p tap/Formula + cp yellowjacket.rb tap/Formula/yellowjacket.rb + + cd tap + git config user.name "yellowjacket-ci" + git config user.email "yj@yellowjacket.app" + + if git diff --quiet; then + echo "Formula already up to date; nothing to push." + exit 0 + fi + + git add Formula/yellowjacket.rb + git commit -m "yellowjacket ${VERSION}" + git push origin HEAD:main diff --git a/packaging/homebrew/Formula/yellowjacket.rb b/packaging/homebrew/Formula/yellowjacket.rb new file mode 100644 index 0000000..deefd46 --- /dev/null +++ b/packaging/homebrew/Formula/yellowjacket.rb @@ -0,0 +1,65 @@ +# typed: false +# frozen_string_literal: true + +# YellowJacket — cross-platform desktop music player built with Wails (Go + Lit). +# +# This formula builds from source. The Wails toolchain (`go tool wails`) resolves +# from the tool directives in go.mod, and Wails drives the frontend install/build +# itself (pnpm), so only the Go toolchain, Node, and pnpm are needed at build time. +# +# This file is the canonical source. On each tagged release, CI computes the +# tarball checksum and syncs an updated copy into the homebrew-yellowjacket tap +# repo (see .gitea/workflows/homebrew-formula.yml). The `version`/`sha256` lines +# below are what CI rewrites — keep them on their own lines. +class Yellowjacket < Formula + desc "Cross-platform desktop music player — local library, MusicBrainz explore & auto-tag" + homepage "https://git.ljones.me/yonlu/yellowjacket" + version "1.3.0" + url "https://git.ljones.me/yonlu/yellowjacket/archive/v#{version}.tar.gz" + sha256 "11929d9a7a32839f86213502b698a02376b38f0838fa20610408f062423899e5" + license :cannot_represent # custom license — see repository + + head "https://git.ljones.me/yonlu/yellowjacket.git", branch: "main" + + depends_on "go" => :build + depends_on "node" => :build + depends_on "pnpm" => :build + + # Wails targets macOS and Linux. On Linux, Homebrew builds against the system + # WebKitGTK/GTK stack, which must be present (webkit2gtk-4.1, gtk3, alsa-lib). + on_linux do + depends_on "pkg-config" => :build + end + + def install + ENV["CGO_ENABLED"] = "1" + # Keep Go resolving modules from the network into its sandboxed cache. + ENV["GOFLAGS"] = "-mod=mod" + + commit = build.head? ? "HEAD" : "v#{version}" + ldflags = "-s -w -X 'main.version=v#{version}' -X 'main.commit=#{commit}'" + + system "go", "generate", "./..." + system "go", "tool", "wails", "build", + "-tags", "webkit2_41", + "-clean", "-trimpath", + "-ldflags", ldflags + + # Wails emits a .app bundle on macOS and a bare ELF binary on Linux. + if OS.mac? + prefix.install "build/bin/YellowJacket.app" + bin.write_exec_script "#{prefix}/YellowJacket.app/Contents/MacOS/YellowJacket" + else + bin.install "build/bin/yellowjacket" + end + end + + test do + # The GUI binary has no headless mode; assert it was built and is runnable. + if OS.mac? + assert_predicate prefix/"YellowJacket.app/Contents/MacOS/YellowJacket", :executable? + else + assert_predicate bin/"yellowjacket", :executable? + end + end +end diff --git a/packaging/homebrew/README.md b/packaging/homebrew/README.md new file mode 100644 index 0000000..afd272a --- /dev/null +++ b/packaging/homebrew/README.md @@ -0,0 +1,76 @@ +# YellowJacket Homebrew formula + +The formula builds YellowJacket from source on macOS and Linuxbrew, mirroring +the Arch `PKGBUILD`: the Wails toolchain resolves from `go.mod`'s tool +directives and drives the frontend build itself, so the only build inputs are +Go, Node, and pnpm. + +``` +packaging/homebrew/ +└── Formula/ + └── yellowjacket.rb ← canonical source; CI syncs it to the tap repo +``` + +## Installing + +```bash +brew install shadow-puppet/yellowjacket/yellowjacket +``` + +`shadow-puppet/yellowjacket` is Homebrew shorthand for the tap repo +`github.com/Shadow-Puppet/homebrew-yellowjacket`. Brew auto-taps it, so there's +no separate `brew tap` step. To build the tip of `main` instead of the latest +release, add `--HEAD`. + +## How publishing works + +This directory holds the **canonical** formula. The tap users install from lives +in a **separate** repo — `homebrew-yellowjacket` — because Homebrew only +discovers formulae from a repo whose name starts with `homebrew-`, with the +formula at a top-level `Formula/`. Keeping it separate is also why nothing has +to live in this repo's root. + +On every version tag (`v*`), `.gitea/workflows/homebrew-formula.yml`: + +1. downloads the GitHub release tarball for that tag, +2. computes its `sha256`, +3. rewrites the `version` and `sha256` lines in the formula, and +4. commits the result to `homebrew-yellowjacket`'s `Formula/yellowjacket.rb`. + +So a normal release needs **no manual formula edits** — tag, and the tap updates +itself. (This is the Homebrew equivalent of the Arch package's publish workflow.) + +### About the `sha256` + +Homebrew re-downloads the source tarball on each install and refuses to build +unless its checksum matches `sha256` — integrity/tamper detection. The committed +value here is a `REPLACE_WITH_...` placeholder on purpose; the real checksum is +computed and injected by CI at release time, so it never has to be maintained by +hand. (The Arch `PKGBUILD` sidesteps this with `SKIP` because it clones over git +rather than downloading a tarball.) + +## One-time setup + +1. **Create the tap repo:** `Shadow-Puppet/homebrew-yellowjacket` on GitHub, + with a `main` branch. It can start empty — the first tagged release seeds + `Formula/yellowjacket.rb`. +2. **Add a CI secret:** `HOMEBREW_TAP_TOKEN` — a GitHub token with write access + to that repo (a fine-grained PAT scoped to `homebrew-yellowjacket`, Contents: + read/write, is enough). + +That's it. The source repo must be public (or the tap private with an +authenticated `brew install`) for brew to fetch the release tarball. + +## Notes + +- **License**: declared as `license :cannot_represent` (custom license). Replace + with the correct SPDX identifier once the license is finalized. +- **macOS vs Linux**: `wails build` produces a `YellowJacket.app` bundle on + macOS (installed under the Cellar with an `exec` shim in `bin`) and a bare + `yellowjacket` binary on Linux (installed to `bin`). +- **Linuxbrew**: building on Linux additionally needs the system WebKitGTK/GTK + stack (`webkit2gtk-4.1`, `gtk3`, `alsa-lib`) — OS packages, not Homebrew deps. + macOS needs only the Xcode Command Line Tools. +- **Cask alternative**: if you later ship prebuilt macOS `.dmg`/`.zip` artifacts, + a Homebrew *cask* pointing at those installs faster than this source build. + This formula is the source-build path.