Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat: ccebuild — one metadata-driven build/install for the whole workspace
The per-crate Makefiles enumerate their binaries by hand, so any crate with
extra [[bin]] targets or no Makefile at all was simply never installed: of the
workspace's 33 bin targets only 22 were named anywhere, leaving cce-bevel, the
three cce-keyring-unlock* helpers, and six whole crates (browser, calendar,
map, preview, secrets, terminal) to be caught by hand or not at all. cce-bevel
was found hours stale and the keyring binaries three weeks stale.
ccebuild derives everything from `cargo metadata`, so new crates and new
src/bin/*.rs files need no edit here:
ccebuild build one whole-workspace release build (building per-crate
with -p resolves a different unified feature set and
re-invalidates crates on every alternation)
ccebuild install every bin target (minus dev-only vk-smoke), the cce->cce-fx
symlink, helper scripts, and user units
ccebuild status built-vs-installed drift, plus running processes that are
NOT on their installed binary -- the case reinstalling
cannot fix, and which no timestamp check would reveal
Workspace root is resolved via $CCE_WORKSPACE or `cargo locate-project`, never
hardcoded. cce-display-manager's units are system-scoped and deliberately
excluded from the user-unit install.
Verified: reproduces the hand-rolled install exactly (32 binaries + 3 helper
scripts + 3 user units, vk-smoke excluded); STALE and MISSING both fire on
injected drift and clear on reinstall; status flags the five processes
currently running deleted inodes.
Co-Authored-By: Claude Fable 5 <[email protected]>
Makefile | 1 +
scripts/ccebuild | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 187 insertions(+)
diff --git a/Makefile b/Makefile
index af0d77c..5dbd205 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@ install: build
install -m 755 scripts/cce-desktop-menu ~/.local/bin/cce-desktop-menu
install -m 755 scripts/cce-app-menu ~/.local/bin/cce-app-menu
install -m 755 scripts/gpu-watcher ~/.local/bin/gpu-watcher
+ install -m 755 scripts/ccebuild ~/.local/bin/ccebuild
mkdir -p ~/.config/systemd/user
install -m 644 scripts/gpu-watcher.service ~/.config/systemd/user/gpu-watcher.service
diff --git a/scripts/ccebuild b/scripts/ccebuild
new file mode 100755
index 0000000..2faa9c0
--- /dev/null
+++ b/scripts/ccebuild
@@ -0,0 +1,186 @@
+#!/usr/bin/env bash
+# ccebuild — build, install and inspect every binary in the cce workspace.
+#
+# The per-crate Makefiles name their binaries by hand, so a crate with extra
+# [[bin]] targets (cce-ui's cce-bevel/cce-ramp, cce-display-manager's keyring
+# helpers) or a crate with no Makefile at all silently never gets installed —
+# 11 of the workspace's 33 bin targets were unreachable that way. Everything
+# here is derived from `cargo metadata` instead, so new crates and new
+# src/bin/*.rs files are picked up with no edit to this script.
+
+set -euo pipefail
+
+# Dev-only binaries: built by the workspace, never installed.
+EXCLUDE=(vk-smoke)
+
+# Crates whose .service files are SYSTEM units (/etc/systemd/system, root-owned).
+# They are not user units and must not land in ~/.config/systemd/user.
+SYSTEM_UNIT_CRATES=(cce-display-manager)
+
+PREFIX="${CCE_PREFIX:-$HOME/.local}"
+BINDIR="$PREFIX/bin"
+UNITDIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
+
+die() { printf 'ccebuild: %s\n' "$*" >&2; exit 1; }
+
+# The workspace root. Never hardcoded: honour $CCE_WORKSPACE, else ask cargo
+# about the current directory (this script lives in ~/.local/bin once installed,
+# so its own path says nothing about where the source tree is).
+resolve_workspace() {
+ if [ -n "${CCE_WORKSPACE:-}" ]; then
+ [ -f "$CCE_WORKSPACE/Cargo.toml" ] || die "CCE_WORKSPACE=$CCE_WORKSPACE has no Cargo.toml"
+ printf '%s\n' "$CCE_WORKSPACE"
+ return
+ fi
+ local manifest
+ if manifest=$(cargo locate-project --workspace --message-format plain 2>/dev/null); then
+ dirname "$manifest"
+ return
+ fi
+ die "not inside the cce workspace — cd into it, or set CCE_WORKSPACE=/path/to/cce"
+}
+
+# Every bin target cargo knows about, minus EXCLUDE. This is the whole point of
+# the script: one authoritative list, not 20 hand-written ones.
+workspace_bins() {
+ cargo metadata --manifest-path "$WS/Cargo.toml" --no-deps --format-version 1 2>/dev/null \
+ | jq -r '.packages[].targets[] | select(.kind | index("bin")) | .name' \
+ | sort -u \
+ | while read -r bin; do
+ case " ${EXCLUDE[*]} " in *" $bin "*) continue ;; esac
+ printf '%s\n' "$bin"
+ done
+}
+
+# User-session unit files shipped by crates (system units excluded — see above).
+user_units() {
+ find "$WS" -maxdepth 3 -name '*.service' -not -path "$WS/target/*" 2>/dev/null \
+ | while read -r unit; do
+ local crate
+ crate=$(basename "$(dirname "$unit")")
+ [ "$crate" = scripts ] && crate=$(basename "$(dirname "$(dirname "$unit")")")
+ case " ${SYSTEM_UNIT_CRATES[*]} " in *" $crate "*) continue ;; esac
+ printf '%s\n' "$unit"
+ done
+}
+
+cmd_build() {
+ printf '==> building workspace (release)\n'
+ # One invocation for the whole workspace. Building per-crate with -p would
+ # resolve a different unified feature set and re-invalidate crates on every
+ # alternation between the two command shapes.
+ ( cd "$WS" && cargo build --release --workspace )
+}
+
+cmd_install() {
+ [ "${1:-}" = --no-build ] || cmd_build
+
+ mkdir -p "$BINDIR" "$UNITDIR"
+ local n=0 missing=()
+
+ printf '==> installing binaries -> %s\n' "$BINDIR"
+ while read -r bin; do
+ if [ ! -f "$WS/target/release/$bin" ]; then
+ missing+=("$bin")
+ continue
+ fi
+ # `install` unlinks the destination first, so replacing a binary that is
+ # currently running is safe (the live process keeps its inode).
+ install -m 755 "$WS/target/release/$bin" "$BINDIR/$bin"
+ n=$((n + 1))
+ done < <(workspace_bins)
+
+ # The compositor is installed as cce-fx and invoked as `cce`.
+ if [ -f "$BINDIR/cce-fx" ]; then
+ ln -sf cce-fx "$BINDIR/cce"
+ fi
+
+ printf '==> installing helper scripts\n'
+ local s
+ for s in "$WS"/cce-compositor/scripts/*; do
+ [ -f "$s" ] || continue
+ case "$s" in *.service) continue ;; esac
+ install -m 755 "$s" "$BINDIR/$(basename "$s")"
+ n=$((n + 1))
+ done
+
+ printf '==> installing user units -> %s\n' "$UNITDIR"
+ local u
+ while read -r u; do
+ [ -n "$u" ] || continue
+ install -m 644 "$u" "$UNITDIR/$(basename "$u")"
+ done < <(user_units)
+ systemctl --user daemon-reload 2>/dev/null || true
+
+ if [ ${#missing[@]} -gt 0 ]; then
+ printf 'ccebuild: NOT BUILT, skipped: %s\n' "${missing[*]}" >&2
+ fi
+ printf '==> installed %d files\n' "$n"
+}
+
+# Report drift three ways: built-vs-installed, and installed-vs-running. The
+# last one matters because an app launched straight out of target/ (or from a
+# stale path) keeps running old code that no reinstall can touch.
+cmd_status() {
+ local built inst stale=0 missing=0 ok=0
+ printf '%-28s %-8s %s\n' NAME STATE DETAIL
+ while read -r bin; do
+ built="$WS/target/release/$bin"
+ inst="$BINDIR/$bin"
+ if [ ! -f "$built" ]; then
+ printf '%-28s %-8s %s\n' "$bin" "nobuild" "not in target/release"
+ continue
+ fi
+ if [ ! -f "$inst" ]; then
+ printf '%-28s %-8s %s\n' "$bin" "MISSING" "never installed"
+ missing=$((missing + 1))
+ elif [ "$built" -nt "$inst" ]; then
+ printf '%-28s %-8s %s\n' "$bin" "STALE" "built $(date -r "$built" +%m-%d_%H:%M:%S) > installed $(date -r "$inst" +%m-%d_%H:%M:%S)"
+ stale=$((stale + 1))
+ else
+ ok=$((ok + 1))
+ fi
+ done < <(workspace_bins)
+ printf -- '-- %d up to date, %d stale, %d missing\n' "$ok" "$stale" "$missing"
+
+ printf '\n==> running processes not using the installed binary\n'
+ local found=0 pid exe base
+ for pid in $(pgrep -f 'cce-' 2>/dev/null || true); do
+ exe=$(readlink "/proc/$pid/exe" 2>/dev/null) || continue
+ base=${exe%% (deleted)}
+ base=$(basename "$base")
+ case "$base" in cce*|ccectl) ;; *) continue ;; esac
+ if [ "$exe" != "$BINDIR/$base" ]; then
+ printf ' %-8s %-24s %s\n' "$pid" "$base" "$exe"
+ found=1
+ fi
+ done
+ [ "$found" -eq 0 ] && printf ' (none — every running cce process is on its installed binary)\n'
+}
+
+usage() {
+ cat <<'EOF'
+usage: ccebuild <command>
+
+ build cargo build --release --workspace
+ install [--no-build] build, then install every bin target, helper script
+ and user unit (derived from cargo metadata)
+ status show built-vs-installed drift, and running processes
+ that are not on their installed binary
+
+environment:
+ CCE_WORKSPACE workspace root (default: located from the current directory)
+ CCE_PREFIX install prefix (default: ~/.local)
+EOF
+}
+
+command -v jq >/dev/null || die "jq is required"
+WS=$(resolve_workspace)
+
+case "${1:-}" in
+ build) cmd_build ;;
+ install) shift; cmd_install "$@" ;;
+ status) cmd_status ;;
+ -h|--help|help|"") usage ;;
+ *) die "unknown command '$1' (try: ccebuild --help)" ;;
+esac