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
71 changes: 70 additions & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ on:
push:
branches: [main]
# Manual re-run, e.g. after a token or permission failure. Idempotent: it re-opens or refreshes
# the same release PR rather than creating a second one.
# the same release PR rather than creating a second one. `package_tag` re-runs
# ONLY the packaging job for an existing tag, for a build that failed.
workflow_dispatch:
inputs:
package_tag:
description: "Re-build and re-attach the package for this tag (e.g. v1.2.3). Leave empty for a normal run."
required: false
default: ""

permissions:
contents: write
Expand All @@ -31,9 +37,72 @@ jobs:
release-please:
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@v5
id: release
with:
target-branch: main
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

# Attaches an installable package to the release the job above just cut, so a
# release is something you can `pacman -U` instead of a source zip.
#
# IT LIVES HERE, not in a workflow keyed on `release: [published]`, and that is
# the whole point: release-please creates the Release with the default
# GITHUB_TOKEN, and GitHub does not start workflow runs from events raised by
# GITHUB_TOKEN. A `release: published` trigger would never fire on the real
# path -- and would leave no failed run in the Actions tab to notice. This repo
# has already paid for that lesson once.
package:
needs: release-please
if: needs.release-please.outputs.release_created == 'true' || inputs.package_tag != ''
runs-on: ubuntu-latest
# arch=('any'), so the x86_64 image is honest here: nothing is compiled, and
# the python package is co-located with its data rather than dropped into the
# BUILDING interpreter's site-packages. A repo that really compiles needs the
# other treatment -- macarchy-install#18.
container: archlinux:base-devel
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
# The tag, never the default branch: re-running for an older tag must
# not build main's PKGBUILD and clobber that package onto the old release.
ref: ${{ needs.release-please.outputs.tag_name || inputs.package_tag }}

- name: Build the package
env:
TAG: ${{ needs.release-please.outputs.tag_name || inputs.package_tag }}
# -Syu, not -Sy: a partial upgrade can link the fresh python against a
# glibc this image does not have, and it reads as a build bug.
# github-cli because `gh` lives on the RUNNER, not inside the container.
# makepkg refuses to run as root, so it gets a throwaway user.
# --nodeps because `depends` is a runtime contract for the target machine.
run: |
pacman -Syu --noconfirm --needed git python github-cli
# The tag is the truth. Rewriting pkgver here means the package can
# never carry a version the release does not, and release-please needs
# no extra-files entry to keep them in step.
sed -i "s/^pkgver=.*/pkgver=${TAG#v}/" PKGBUILD
grep -q "^pkgver=${TAG#v}$" PKGBUILD
useradd -m build && chown -R build:build .
su build -c 'makepkg -f --nodeps --noconfirm'
ls -l ./*.pkg.tar.*

- name: Attach it to the release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.release-please.outputs.tag_name || inputs.package_tag }}
# A GLOB, not a name: PKGEXT is .zst here and .xz on the maintainer's
# machine, and hardcoding either uploads nothing on the other. The version
# check is what stops a mis-targeted manual re-run from clobbering an old
# release with a package built from a different tag.
run: |
test -n "$(echo ./*.pkg.tar.* )"
ls ./*.pkg.tar.* | grep -q "${TAG#v}" \
|| { echo "built package does not carry $TAG — refusing to upload"; exit 1; }
gh release upload "$TAG" ./*.pkg.tar.* --clobber --repo "$GITHUB_REPOSITORY"
67 changes: 67 additions & 0 deletions PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Maintainer: Philippe Matray <phmatray@gmail.com>
#
# macarchy-core is not shaped like the other packages in this org: most of what
# install.sh lands is USER CONFIGURATION -- theme hooks, shell plugins, a Hypr
# key file -- which pacman cannot write. So the package splits honestly:
#
# system half -> /usr/bin, /usr/lib/systemd/user, /usr/lib/udev/rules.d
# user half -> /usr/share/macarchy-core/, as templates the scriptlet names
#
# It is deliberately NOT a full install. The scriptlet says so and names every
# destination, rather than shipping something that looks complete and is not.
pkgname=macarchy-core
# Rewritten from the tag by the packaging job before makepkg runs. This value is
# the fallback for a manual makepkg from a checkout.
pkgver=0.4.1
pkgrel=1
pkgdesc="macOS behaviours for a MacBook on Omarchy/Asahi: dock, Cmd keys, Cmd+Tab, pinch gestures, auto-brightness"
arch=('any')
url="https://github.com/macarchy/macarchy-core"
license=('MIT')
install=macarchy-core.install
depends=('bash' 'python' 'brightnessctl')
optdepends=('omarchy: the theme hooks and shell plugins target it'
'grim: bar-contrast samples the screen with it'
'imagemagick: bar-contrast reduces the sample with it'
'jq: reading the bar layer geometry'
'hyprland: the gestures and key grammar')
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('SKIP')

package() {
cd "$srcdir/$pkgname-$pkgver"

# --- the system half ------------------------------------------------------
local s
for s in style/* hardware/*; do
[[ -f $s && -x $s ]] || continue
install -Dm755 "$s" "$pkgdir/usr/bin/$(basename "$s")"
done

# The units say ExecStart=%h/.local/bin/… because install.sh symlinks there.
# A package writes nothing into $HOME: verbatim they give 203/EXEC from a
# package that installed perfectly.
local u
for u in systemd/*.service; do
sed 's|%h/\.local/bin/|/usr/bin/|' "$u" > "$srcdir/$(basename "$u").pkg"
grep -q '^ExecStart=/usr/bin/' "$srcdir/$(basename "$u").pkg" # or fail the build
install -Dm644 "$srcdir/$(basename "$u").pkg" \
"$pkgdir/usr/lib/systemd/user/$(basename "$u")"
done
for u in systemd/*.timer; do
install -Dm644 "$u" "$pkgdir/usr/lib/systemd/user/$(basename "$u")"
done

install -Dm644 udev/90-battery-charge-limit.rules \
"$pkgdir/usr/lib/udev/rules.d/90-battery-charge-limit.rules"

# --- the user half, as templates -----------------------------------------
# Every one of these belongs somewhere under $HOME that pacman may not touch.
# macarchy-core.install names each destination.
install -d "$pkgdir/usr/share/$pkgname"
cp -r hooks keys examples shell-plugins "$pkgdir/usr/share/$pkgname/"
cp -r agents "$pkgdir/usr/share/$pkgname/"

install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
}
42 changes: 42 additions & 0 deletions macarchy-core.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
post_install() {
cat <<'NOTE'

macarchy-core's commands, units and udev rule are installed system-wide.

The rest of what install.sh does is USER configuration, and pacman cannot write
into your $HOME. The pieces are under /usr/share/macarchy-core/ — copy the ones
you want:

S=/usr/share/macarchy-core

# theme hooks
install -Dm755 $S/hooks/macarchy-dock-theme ~/.config/omarchy/hooks/theme-set.d/
install -Dm755 $S/hooks/dynamic-wallpaper ~/.config/omarchy/hooks/theme-set.d/
install -Dm755 $S/hooks/macarchy-bar-contrast ~/.config/omarchy/hooks/theme-set.d/
install -Dm755 $S/hooks/aquarium/macarchy-bar-contrast ~/.config/omarchy-aquarium/hooks/

# shell plugins (control centre, notification centre, Cmd+Tab switcher)
cp -r $S/shell-plugins/* ~/.config/omarchy/plugins/

# Cmd-key grammar for Hyprland
install -Dm644 $S/keys/macarchy-keys.lua ~/.config/hypr/macarchy-keys.lua

# starter configs
ls $S/examples/

Then:
systemctl --user enable --now macarchy-auto-appearance.timer macarchy-bar-contrast.timer

Running the repo's ./install.sh instead does all of this for you; the package
exists so the commands and units can be managed by pacman.

NOTE
}

post_upgrade() {
systemctl --user try-restart macarchy-auto-appearance.service macarchy-bar-contrast.service 2>/dev/null || true
}

pre_remove() {
systemctl --user disable --now macarchy-auto-appearance.timer macarchy-bar-contrast.timer 2>/dev/null || true
}
50 changes: 50 additions & 0 deletions tests/test_pkgbuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash
# tests/test_pkgbuild.sh — the package must carry what install.sh carries.
#
# macarchy-core splits: the commands, units and udev rule go system-wide, and
# everything that belongs under $HOME (theme hooks, shell plugins, the Hypr key
# file, the starter configs) ships as templates under /usr/share because pacman
# may not write there. That split is honest only while the scriptlet names every
# destination — so this checks both halves. macarchy-install#17.
set -uo pipefail
cd "$(dirname "$0")/.."

fails=0
check() { local name=$1; shift; if "$@"; then echo "ok $name"; else echo "FAIL $name"; fails=$((fails+1)); fi; }
# Comments name every artefact, so a whole-file grep passes even when the install
# line is gone. Read the code.
code() { grep -v '^[[:space:]]*#' PKGBUILD; }

check "every command install.sh ships is packaged" \
bash -c 'for s in style/* hardware/*; do [ -f "$s" ] && [ -x "$s" ] || continue; grep -q "style/\* hardware/\*" <(grep -v "^[[:space:]]*#" PKGBUILD) || exit 1; done'
check "the units are packaged" grep -q 'systemd/\*.service' <(code)
check "the timers are packaged" grep -q 'systemd/\*.timer' <(code)
check "the udev rule is packaged" grep -q '90-battery-charge-limit.rules' <(code)
check "the user half ships as templates" grep -q 'cp -r hooks keys examples shell-plugins' <(code)

# The units point at $HOME for install.sh's benefit; verbatim that is 203/EXEC.
check "units are repointed off %h" grep -q "sed 's|%h/" <(code)
check "and the rewrite is checked" grep -q "grep -q '\^ExecStart=/usr/bin/'" <(code)
check "the shipped unit really says %h" grep -q '%h/' systemd/macarchy-auto-appearance.service

# The split is only honest if the scriptlet names where each piece goes.
check "there is a scriptlet" grep -q 'install=macarchy-core.install' <(code)
for dest in "hooks/theme-set.d" "omarchy/plugins" "hypr/macarchy-keys.lua" "examples"; do
check "scriptlet names $dest" grep -q "$dest" macarchy-core.install
done
check "scriptlet admits it is partial" grep -q "pacman cannot write" macarchy-core.install

# The workflow lessons from macarchy-install#16, each a real failure there.
WF=.github/workflows/release-please.yml
check "no standalone package workflow" [ ! -e .github/workflows/package.yml ]
check "the job hangs off release_created" grep -q 'release_created' "$WF"
check "not a release: published trigger" bash -c '! grep -q "types: \[published\]" '"$WF"
check "pkgver is rewritten from the tag" grep -q 'pkgver=\${TAG#v}' "$WF"
check "and the rewrite is verified" grep -q 'grep -q "\^pkgver=\${TAG#v}\$" PKGBUILD' "$WF"
check "extra-files is not used" bash -c '! grep -q "extra-files" release-please-config.json'
check "the upload globs" grep -q '\*.pkg.tar.\*' "$WF"
check "the upload clobbers" grep -q -- '--clobber' "$WF"
check "gh is installed in the container" grep -q 'github-cli' "$WF"

(( fails == 0 )) && echo "all ok" || echo "$fails failed"
exit $(( fails > 0 ))
Loading