Skip to content

Prove the release-package pattern on macarchy-touchbar #16

Closed

Description

@phmatray

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

  1. gh release view <tag> --json assets lists exactly one *.pkg.tar.*.
  2. tar -tf on that asset contains usr/bin/macarchy-touchbar and the udev, modules-load and systemd paths.
  3. The font is inside the package — the installed machine needs no network.
  4. The workflow does not run on push or pull_request.
  5. 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 PKGEXT or the site-packages path.
  • 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 route daemon.py:43, :71, :158 and draw.py:25 through it. The checkout fallback keeps ./install.sh working 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 vs install.sh's installed paths) — assert every destination install.sh writes has a counterpart in package().
  • Step 2: Run python3 -m pytest tests/test_pkgbuild.py -q → FAIL (no PKGBUILD).
  • Step 3: Write the PKGBUILD, modelled on omarchy-aikit/PKGBUILD's package() but versioned from the tag rather than git describe. Pin the font with a real sha256sum.
  • Step 4: Build it for real: makepkg -f --nodeps, then tar -tf the 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.yml must 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 -tf it; 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

  1. added
    bugSomething isn't working
    priority: mediumThe default tier — worth doing, not urgent
    effort: mediumA handful of tasks inside one layer
    area: cithe workflows and the hermetic test suites
    on Sep 4, 2026
  2. phmatray commented on Sep 4, 2026

    @phmatray
    ContributorAuthor

    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:23 and draw.py:24:

    ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    specs = discover(os.path.join(ROOT, "modules"), …)
    Config.load(os.path.join(ROOT, "config", "layouts.toml"))
    CODEPOINTS = os.path.join(ROOT, "fonts", "MaterialSymbolsRounded.codepoints")

    ROOT is the parent of the python package. In a git checkout that is the repo root, which does hold modules/, config/ and fonts/. In a package, macarchy_touchbar/ lands in site-packages/, so ROOT becomes /usr/lib/python3.14/site-packages/ — and the daemon would look for modules/ and config/layouts.toml there.

    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:27 fetches the Material Symbols font from master, 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 a sha256sum, which is a fix for that as a side effect rather than a goal.

  3. phmatray commented on Sep 4, 2026

    @phmatray
    ContributorAuthor

    Done. v0.5.0 of macarchy-touchbar carries an installable package, built in CI from its own tag:

    macarchy-touchbar-0.5.0-1-any.pkg.tar.zst   6.6 MB
    
    pkgname = macarchy-touchbar
    pkgver  = 0.5.0-1          ← rewritten from the tag, verified in .PKGINFO
    arch    = any
    
    usr/bin/macarchy-touchbar
    usr/lib/{udev/rules.d,modules-load.d,systemd/user}/…
    usr/share/macarchy-touchbar/{macarchy_touchbar,modules,config,fonts}/…
    ExecStart=/usr/bin/macarchy-touchbar daemon
    

    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/ and config/ 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 with GITHUB_TOKEN, and GitHub raises no workflow run from a GITHUB_TOKEN event. Every release would have published with no package and no failed run anywhere to notice. The job now hangs off release-please's own release_created output, in the same workflow. This repo had already paid for that lesson once — the Tauri build was folded in for the same reason.

    extra-files broke release-please outright. Listing PKGBUILD there made it fail to build a release PR (unexpected token '(' at 6:32), so the packaging feat merged and produced no release. The job rewrites pkgver from 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-level feat(pkg): was dropped and it proposed 0.4.1 with no Features section. Closed that PR and restored the entry with Release-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's package job — not a standalone workflow. And write the squash body.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: cithe workflows and the hermetic test suitesbugSomething isn't workingeffort: mediumA handful of tasks inside one layerpriority: mediumThe default tier — worth doing, not urgent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions