desktop: one name across all three install channels (issue 2075)
The app answered to three different names depending on how it arrived, and the
part that actually hurt was WM_CLASS. Reading tauri-bundler settles what it is:
the generated .desktop template writes StartupWMClass={{exec}} where exec is
main_binary_name, and tao creates its GtkApplication with a NULL app id
(enableGTKAppId defaults off), so GTK falls back to the program name. WM_CLASS
is the binary name, nothing else.
Which inverts this issue's premise. The rename could not break grouping,
because two channels weren't grouping in the first place: pacman ships
/usr/bin/thoughtsync and the AppImage's AppRun execs thoughtsync-desktop, while
all three hand-written entries hardcoded StartupWMClass=ThoughtSync — a string
no binary in any channel has ever reported. Only the .deb worked, and only
because Tauri generates its entry from the binary and never consulted us.
So: thoughtsync everywhere, carried by the build target itself via Cargo [[bin]]
plus mainBinaryName rather than by the install path, since the target name is
what the desktop reads. The pacman package sheds its -desktop suffix and
declares conflict+replaces so an upgrade retires the old one instead of landing
beside it and fighting over /usr/bin/thoughtsync.
The .deb verifier now asserts binary path, Exec and StartupWMClass all agree,
which is the part that keeps this fixed: the .deb's entry is the one no human
writes, so it's the one that drifts silently.
Package: thought-sync stays. tauri-bundler derives it as kebab-case(productName)
with no override, and rewriting a control archive on every build is a poor trade
for one uninstall command.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,20 +10,25 @@
|
||||
# that installs and then won't launch because a library it needs was never
|
||||
# declared.
|
||||
#
|
||||
# Four checks, cheapest first:
|
||||
# Five checks, cheapest first:
|
||||
# 1. Print the control file + contents — the generated metadata becomes ground
|
||||
# truth in the build log instead of an assumption.
|
||||
# 2. dpkg-shlibdeps: the canonical Debian answer for "what does this ELF
|
||||
# 2. Naming: the binary is /usr/bin/thoughtsync and the generated .desktop
|
||||
# entry's StartupWMClass matches it. The app used to identify itself three
|
||||
# different ways depending on install channel (issue 2075); this is what
|
||||
# keeps the .deb — the only channel whose entry Tauri generates for us —
|
||||
# from drifting away from the two we write by hand.
|
||||
# 3. dpkg-shlibdeps: the canonical Debian answer for "what does this ELF
|
||||
# actually need". Compared against what the package declares.
|
||||
# 3. Every declared dependency resolves to a real package in apt (catches a
|
||||
# 4. Every declared dependency resolves to a real package in apt (catches a
|
||||
# typo in the hand-written list, which would break install for everyone).
|
||||
# 4. If a docker CLI is present, install into a clean debian container — the
|
||||
# 5. If a docker CLI is present, install into a clean debian container — the
|
||||
# highest-fidelity check, since the build image already has the -dev
|
||||
# packages installed and so can't prove resolution on its own.
|
||||
#
|
||||
# Check 4 is opportunistic on purpose: the build image is not guaranteed to carry
|
||||
# Check 5 is opportunistic on purpose: the build image is not guaranteed to carry
|
||||
# a docker CLI, and adding one at job time would violate "the image is the
|
||||
# toolchain" (rule 5). Checks 1-3 are self-contained and always run.
|
||||
# toolchain" (rule 5). Checks 1-4 are self-contained and always run.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -63,7 +68,41 @@ BIN="$(find "$WORK/root" -type f -path '*/bin/*' -print -quit)"
|
||||
[ -n "$BIN" ] || { echo "ERROR: no binary found under */bin/ in the package" >&2; exit 1; }
|
||||
echo " (binary: ${BIN#"$WORK/root"})"
|
||||
|
||||
# --- 2. what the ELF actually needs -----------------------------------------
|
||||
# --- 2. naming is consistent -------------------------------------------------
|
||||
# CANON is the one name the app answers to everywhere: the binary, the CLI
|
||||
# command, the icon, and the WM_CLASS the window reports. Hardcoded here on
|
||||
# purpose — this literal IS the contract the three install channels are held to.
|
||||
# Deliberately NOT asserted: the control file's `Package:` field, which is
|
||||
# `thought-sync`. tauri-bundler derives it as kebab-case(productName) with no
|
||||
# config override, so "ThoughtSync" splits at the hump. Fixing it would mean
|
||||
# unpacking and rewriting the control archive on every build — a fragile step for
|
||||
# a cosmetic gain on one uninstall command. Left as a known wart (issue 2075).
|
||||
CANON="thoughtsync"
|
||||
|
||||
installed_bin="${BIN#"$WORK/root"}"
|
||||
[ "$installed_bin" = "/usr/bin/$CANON" ] ||
|
||||
note_fail "binary is at $installed_bin, expected /usr/bin/$CANON (mainBinaryName in tauri.conf.json)."
|
||||
|
||||
# Tauri generates this entry from the binary name, so a mismatch means the config
|
||||
# and the bundler have diverged — exactly the drift that made the app group under
|
||||
# a different taskbar icon depending on how it was installed.
|
||||
entry="$(find "$WORK/root/usr/share/applications" -name '*.desktop' -print -quit 2>/dev/null || true)"
|
||||
if [ -z "$entry" ]; then
|
||||
note_fail "no .desktop entry in the package — the app would not appear in any menu."
|
||||
else
|
||||
echo "--- desktop entry ($(basename "$entry")) ---"
|
||||
sed 's/^/ /' "$entry"
|
||||
wmclass="$(sed -n 's/^StartupWMClass=//p' "$entry" | head -1)"
|
||||
[ "$wmclass" = "$CANON" ] ||
|
||||
note_fail "StartupWMClass is '${wmclass:-<unset>}', expected '$CANON' — the taskbar icon will not group."
|
||||
# `%u`/`%U` field codes may follow, so match the first word only.
|
||||
exec_cmd="$(sed -n 's/^Exec=//p' "$entry" | head -1 | awk '{print $1}')"
|
||||
[ "$exec_cmd" = "$CANON" ] ||
|
||||
note_fail "Exec runs '${exec_cmd:-<unset>}', expected '$CANON'."
|
||||
fi
|
||||
[ "$fail" -eq 0 ] && echo "OK: binary, Exec and StartupWMClass all agree on '$CANON'."
|
||||
|
||||
# --- 3. what the ELF actually needs -----------------------------------------
|
||||
if command -v dpkg-shlibdeps >/dev/null 2>&1; then
|
||||
# dpkg-shlibdeps insists on a debian/control in the working directory even with
|
||||
# -O (write to stdout); a stub is enough to let it do the ELF analysis.
|
||||
@@ -110,7 +149,7 @@ else
|
||||
echo "WARN: dpkg-shlibdeps unavailable — skipping the ELF dependency check." >&2
|
||||
fi
|
||||
|
||||
# --- 3. declared deps are real packages -------------------------------------
|
||||
# --- 4. declared deps are real packages -------------------------------------
|
||||
if apt-cache policy dpkg >/dev/null 2>&1 && [ -n "$(apt-cache policy dpkg 2>/dev/null)" ]; then
|
||||
for pkg in $declared; do
|
||||
if [ -z "$(apt-cache policy "$pkg" 2>/dev/null)" ]; then
|
||||
@@ -120,7 +159,7 @@ if apt-cache policy dpkg >/dev/null 2>&1 && [ -n "$(apt-cache policy dpkg 2>/dev
|
||||
[ "$fail" -eq 0 ] && echo "OK: every declared dependency exists in apt."
|
||||
fi
|
||||
|
||||
# --- 4. clean-container install (opportunistic) -----------------------------
|
||||
# --- 5. clean-container install (opportunistic) -----------------------------
|
||||
# The build image already has libwebkit2gtk-4.1-dev etc. installed, so installing
|
||||
# here would pass no matter what we declared. Only a pristine container proves
|
||||
# apt can actually resolve the package for a real user.
|
||||
|
||||
Reference in New Issue
Block a user