Add mise tasks, desktop entry, and app icon

mise run install builds a release binary and installs it into ~/.local with
a desktop entry and a hicolor icon.

Two wrinkles specific to a Nix-built GTK app installed outside the dev shell:
the launcher is a generated script rather than a symlink, because a desktop
launcher provides a minimal environment and GTK still needs to be pointed at
its GSettings schemas and icon themes; and install registers Nix GC roots for
every store path in the binary's RPATH, so nix-collect-garbage cannot delete
GTK out from under the installed app.

The runtime paths are exported from the flake's shellHook and read back at
install time, so they cannot drift from what the binary was built against.
This commit is contained in:
Greyson Parrelli
2026-08-11 10:03:41 -04:00
parent 7af5cf72e6
commit 74bc9e8fd3
5 changed files with 218 additions and 6 deletions
+117
View File
@@ -0,0 +1,117 @@
# Nix supplies the whole toolchain (Zig, GTK, libadwaita), so there is
# nothing for mise to install; these tasks just drive it.
[tasks.build]
description = "Build a release binary into zig-out/bin"
run = "nix develop --command zig build -Doptimize=ReleaseFast"
[tasks.build-debug]
description = "Build a debug binary (leak checking, safety checks)"
run = "nix develop --command zig build"
[tasks.run]
description = "Build and run vtabs"
run = "nix develop --command zig build run"
[tasks.fmt]
description = "Format all Zig source"
run = "nix develop --command zig fmt build.zig src"
[tasks.screenshot]
description = "Screenshot vtabs in a throwaway headless compositor"
depends = ["build-debug"]
run = "nix develop --command ./shot.sh"
[tasks.install]
description = "Install vtabs into ~/.local with a desktop entry and icon"
depends = ["build"]
run = '''
#!/usr/bin/env bash
set -euo pipefail
data="${XDG_DATA_HOME:-$HOME/.local/share}"
bindir="$HOME/.local/bin"
libexec="$HOME/.local/libexec/vtabs"
apps="$data/applications"
icons="$data/icons/hicolor/scalable/apps"
gcroots="$data/vtabs/gcroots"
# The binary carries an RPATH into the Nix store, so it runs anywhere, but it
# still has to be told where GTK's GSettings schemas and icon themes live or
# it aborts on a missing schema. Read those paths back out of the dev shell
# that built it rather than hardcoding store hashes.
schemas="$(nix develop --command printenv VTABS_GSETTINGS_SCHEMA_DIR)"
datadirs="$(nix develop --command printenv VTABS_RUNTIME_DATA_DIRS)"
mkdir -p "$bindir" "$libexec" "$apps" "$icons" "$gcroots"
install -m755 zig-out/bin/vtabs "$libexec/vtabs"
# A launcher script rather than a symlink: a desktop launcher starts the app
# with a minimal environment, so the GTK runtime paths must be set here.
# Existing values are preserved so this composes with the rest of the session.
cat > "$bindir/vtabs" <<EOF
#!/bin/sh
# Generated by \`mise run install\` — re-run that instead of editing.
export GSETTINGS_SCHEMA_DIR="$schemas\${GSETTINGS_SCHEMA_DIR:+:\$GSETTINGS_SCHEMA_DIR}"
export XDG_DATA_DIRS="$datadirs:\${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
exec "$libexec/vtabs" "\$@"
EOF
chmod 755 "$bindir/vtabs"
sed "s|@EXEC@|$bindir/vtabs|" dist/dev.greyson.vtabs.desktop.in \
> "$apps/dev.greyson.vtabs.desktop"
install -m644 dist/dev.greyson.vtabs.svg "$icons/dev.greyson.vtabs.svg"
# Pin the store paths the installed binary links against. Without this a
# later `nix-collect-garbage` deletes GTK and the installed app stops
# starting, with no obvious connection to the command that broke it.
# Nix roots are transitive, so rooting each RPATH entry covers its closure.
rm -f "$gcroots"/*
if command -v readelf >/dev/null; then
readelf -d "$libexec/vtabs" \
| sed -n 's/.*R\(UN\)\?PATH.*\[\(.*\)\]/\2/p' \
| tr ':' '\n' \
| sed -n 's|^\(/nix/store/[^/]*\).*|\1|p' \
| sort -u \
| while read -r path; do
nix-store --realise --add-root "$gcroots/$(basename "$path")" \
--indirect "$path" >/dev/null 2>&1 || true
done
echo "pinned $(find "$gcroots" -maxdepth 1 -type l | wc -l) store paths against garbage collection"
else
echo "warning: readelf not found; store paths left unpinned (nix-collect-garbage may break the install)" >&2
fi
# Let the desktop notice the new entry and icon right away.
command -v update-desktop-database >/dev/null && update-desktop-database -q "$apps" || true
command -v gtk-update-icon-cache >/dev/null && gtk-update-icon-cache -qtf "$data/icons/hicolor" 2>/dev/null || true
echo "installed $bindir/vtabs"
case ":$PATH:" in
*":$bindir:"*) ;;
*) echo "note: $bindir is not on your PATH; the desktop entry still works" >&2 ;;
esac
'''
[tasks.uninstall]
description = "Remove everything `install` put into ~/.local"
run = '''
#!/usr/bin/env bash
set -euo pipefail
data="${XDG_DATA_HOME:-$HOME/.local/share}"
apps="$data/applications"
rm -f "$HOME/.local/bin/vtabs"
rm -rf "$HOME/.local/libexec/vtabs"
rm -f "$apps/dev.greyson.vtabs.desktop"
rm -f "$data/icons/hicolor/scalable/apps/dev.greyson.vtabs.svg"
# Dropping the roots lets Nix reclaim the runtime closure on its next GC.
rm -rf "$data/vtabs"
command -v update-desktop-database >/dev/null && update-desktop-database -q "$apps" || true
command -v gtk-update-icon-cache >/dev/null && gtk-update-icon-cache -qtf "$data/icons/hicolor" 2>/dev/null || true
echo "uninstalled vtabs"
'''