Repository navigation
Prove the release-package pattern on macarchy-touchbar #16
Description
Part of #14 — Releases publish no installable package, only GitHub's source zip.
Blocked by: none — can start immediately
Problem
macarchy-touchbar v0.4.0's release carries no installable artifact. It is the proof case for the whole job: real code, a python package, a udev rule, a modules-load drop-in and a user unit — every shape the other repos have between them.
Proposed solution
A PKGBUILD in the repo and a workflow that, on release publication, builds it in an archlinux:base-devel container and attaches the result to that release.
Already built by hand on the target machine, so the layout below is observed and not designed:
usr/bin/macarchy-touchbar
usr/lib/modules-load.d/macarchy-touchbar.conf
usr/lib/systemd/user/macarchy-touchbar.service
usr/lib/udev/rules.d/70-macarchy-touchbar.rules
usr/lib/python3.14/site-packages/macarchy_touchbar/…
Area
ci
📋 Spec
Goal
macarchy-touchbar's next release carries one *.pkg.tar.* asset that installs with pacman -U.
Design
PKGBUILD at the repo root, arch=('any'), source=() carrying two entries: the release tarball, and the Material Symbols font that install.sh:27 currently curls unpinned from master. In a PKGBUILD it gets a real sha256sum — strictly better than the status quo, and it is what makes the package self-contained.
package() mirrors install.sh file for file. site-packages is derived, never hardcoded: it carries the interpreter version.
The workflow triggers on release: [published] only, builds as a non-root user (makepkg refuses root), runs makepkg --nodeps (the depends are a runtime contract, and the container has no reason to resolve them), and uploads with a glob — PKGEXT differs between machines (.xz here, .zst in a stock container).
Acceptance criteria
gh release view <tag> --json assetslists exactly one*.pkg.tar.*.tar -tfon that asset containsusr/bin/macarchy-touchbarand the udev, modules-load and systemd paths.- The font is inside the package — the installed machine needs no network.
- The workflow does not run on push or pull_request.
- Re-running it on the same release replaces the asset rather than failing.
Testing decisions
Seams under test: the built package's file list (tar -tf) and the release's asset list (gh release view --json assets).
Prior art: macarchy-install/tests/clean-machine.sh — prove an install by observing the result, not the script.
A good test here: a repo test asserting the PKGBUILD's package() covers every path install.sh installs, so the two channels cannot drift.
Out of scope
- AUR publication; package signing; any change to
install.sh.
🛠️ Implementation plan
For agentic workers: execute this plan task-by-task with
implement-issue(subagent-per-task for broad plans, inline for small ones). Steps use checkbox (- [ ]) syntax for tracking.
Goal: one installable package on macarchy-touchbar's next release.
Architecture: PKGBUILD at the repo root; a workflow beside release-please.yml; pytest suites under tests/.
Tech stack: bash, makepkg 7, GitHub Actions, archlinux:base-devel.
Seams under test: the built package's file list (tar -tf) and the release's asset list (gh release view --json assets).
Global constraints
arch=('any')— no compilation in this repo.- Never hardcode
PKGEXTor thesite-packagespath. - The package must not fetch anything at install time.
- Conventional Commit PR title decides the release bump; commits below use
feat. - Commit trailer:
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>.
Task 1: let the daemon find its data outside the checkout
Files: modify macarchy_touchbar/daemon.py, macarchy_touchbar/draw.py; test tests/test_data_root.py.
Interfaces: a single data_root() resolving, in order: $MACARCHY_TOUCHBAR_DATA, /usr/share/macarchy-touchbar, then the checkout root one level above the package. modules/, config/layouts.toml and fonts/*.codepoints all go through it.
- Step 1: Write the failing test in
tests/test_data_root.py(seam:data_root()'s return value) — assert the env var wins, then the packaged path when it exists, then the checkout root. - Step 2: Run
python3 -m pytest tests/test_data_root.py -q→ FAIL (no such function). - Step 3: Implement
data_root()once and routedaemon.py:43,:71,:158anddraw.py:25through it. The checkout fallback keeps./install.shworking unchanged. - Step 4: Run the whole suite → PASS; the daemon still starts from a checkout.
- Step 5: Commit:
feat: resolve the data directory instead of assuming the checkout.
Task 2: the PKGBUILD, with a test that pins it to install.sh
Files: create PKGBUILD; test tests/test_pkgbuild.py.
Interfaces: pkgname=macarchy-touchbar, arch=('any'), source=(<release tarball> <Material Symbols font>), package() mirroring install.sh.
- Step 1: Write the failing test in
tests/test_pkgbuild.py(seam: the PKGBUILD text vsinstall.sh's installed paths) — assert every destinationinstall.shwrites has a counterpart inpackage(). - Step 2: Run
python3 -m pytest tests/test_pkgbuild.py -q→ FAIL (no PKGBUILD). - Step 3: Write the PKGBUILD, modelled on
omarchy-aikit/PKGBUILD'spackage()but versioned from the tag rather thangit describe. Pin the font with a realsha256sum. - Step 4: Build it for real:
makepkg -f --nodeps, thentar -tfthe result and check the five paths. - Step 5: Re-run the suite → PASS.
- Step 6: Commit:
feat(pkg): a PKGBUILD that mirrors install.sh.
Task 3: publish it on every release
Files: create .github/workflows/package.yml.
Interfaces: on: release: [published]; job builds in archlinux:base-devel and runs gh release upload "$TAG" *.pkg.tar.* --clobber.
- Step 1: Write the workflow: non-root build user,
makepkg --nodeps, glob upload with--clobber. - Step 2: Assert the trigger is release-only —
grep -c 'pull_request\|push:' .github/workflows/package.ymlmust be 0. - Step 3: Commit:
feat(ci): attach the package to every release. - Step 4: Merge, cut a release, and read the asset back with
gh release view --json assets→ exactly one*.pkg.tar.*. - Step 5: Download it and
tar -tfit; confirm the five paths and the font. - Step 6: Re-run the workflow on the same release; confirm it replaces rather than fails.
- Step 7: Record the result on Releases publish no installable package, only GitHub's source zip #14's
## Decisions so far.
Activity
phmatray commentedon Sep 4, 2026
Found before starting: the daemon cannot be packaged as-is
The plan above was written from
install.sh's file list. That was the wrong source — it says where files go, not how the program finds them again.macarchy_touchbar/daemon.py:23anddraw.py:24:ROOTis the parent of the python package. In a git checkout that is the repo root, which does holdmodules/,config/andfonts/. In a package,macarchy_touchbar/lands insite-packages/, soROOTbecomes/usr/lib/python3.14/site-packages/— and the daemon would look formodules/andconfig/layouts.tomlthere.So the package cannot be a pure packaging change: the daemon has to learn to find its data somewhere other than one directory up from its own code. Shipping the PKGBUILD without that would produce an artifact that installs cleanly and then fails to start, which is worse than no artifact.
Plan revised — a new Task 1 makes the data directory resolvable; the PKGBUILD and workflow follow it, renumbered. No box was ticked before this edit.
One more thing the proof turned up
install.sh:27fetches the Material Symbols font frommaster, unpinned. Downloading it today from the commit the PKGBUILD will pin gives a different file from the copy installed on 2 Sep — 15,107,604 bytes against 15,090,976. Two machines set up a week apart do not have the same font today. The PKGBUILD pins the commit and carries asha256sum, which is a fix for that as a side effect rather than a goal.phmatray commentedon Sep 4, 2026
Done. v0.5.0 of
macarchy-touchbarcarries an installable package, built in CI from its own tag:All five acceptance criteria checked against the downloaded artifact, not the build log.
macarchy/macarchy-touchbar#12 and #13.
Four things that had to be fixed that the plan did not predict
The daemon could not be packaged at all. It resolved
modules/andconfig/one directory above its own python package — the repo root in a checkout,site-packages/once installed. Found before the PKGBUILD was written, which is the only reason this is not an artifact that installs cleanly and comes up blank.A
release: [published]workflow would never have fired. release-please creates the Release withGITHUB_TOKEN, and GitHub raises no workflow run from aGITHUB_TOKENevent. Every release would have published with no package and no failed run anywhere to notice. The job now hangs off release-please's ownrelease_createdoutput, in the same workflow. This repo had already paid for that lesson once — the Tauri build was folded in for the same reason.extra-filesbroke release-please outright. ListingPKGBUILDthere made it fail to build a release PR (unexpected token '(' at 6:32), so the packaging feat merged and produced no release. The job rewritespkgverfrom the tag instead and greps to confirm — stricter, and one less coupling.GitHub's default squash body loses the feat. #12's squash opens with
* feat: …bullets, which release-please parses as nested commits; the top-levelfeat(pkg):was dropped and it proposed 0.4.1 with no Features section. Closed that PR and restored the entry withRelease-As: 0.5.0.merge-pr's own doctrine says to write the squash body deliberately — worth heeding on every multi-commit PR from here.For #17 and #18
The pattern to copy is
.github/workflows/release-please.yml'spackagejob — not a standalone workflow. And write the squash body.