diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d392170f6a..ae4c1e53fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,8 @@ jobs: scripts/test-mobile-release-candidate-publisher.sh - name: Mobile worktree identity contract run: scripts/test-mobile-worktree-overrides.sh + - name: Tauri Linux deps guard contract + run: scripts/test-check-tauri-linux-deps.sh - name: File size ratchet unit tests run: node --test scripts/check-file-sizes-core.test.mjs @@ -167,11 +169,14 @@ jobs: libasound2-dev \ libayatana-appindicator3-dev \ libgtk-3-dev \ + libjavascriptcoregtk-4.1-dev \ librsvg2-dev \ libssl-dev \ + libsoup-3.0-dev \ libwebkit2gtk-4.1-dev \ libxdo-dev \ patchelf \ + pkg-config \ wget - name: Get pnpm store directory id: pnpm-cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db0aea637f..904439826e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,15 +101,15 @@ versions in the table above. #### Linux: Tauri system libraries Hermit pins language toolchains, not system libraries. On Linux, the desktop -app's Rust crates link against GTK and WebKitGTK, so `just ci` (and any -`just desktop-tauri-*` recipe) needs these installed system-wide first. On -Debian/Ubuntu: +app's Rust crates link against GTK and WebKitGTK, so `just dev`, `just ci` +(and any `just desktop-tauri-*` recipe) needs these installed system-wide +first. On Debian/Ubuntu: ```bash sudo apt-get install -y --no-install-recommends \ build-essential curl file libasound2-dev libayatana-appindicator3-dev \ - libgtk-3-dev librsvg2-dev libssl-dev libwebkit2gtk-4.1-dev libxdo-dev \ - patchelf wget + libgtk-3-dev libjavascriptcoregtk-4.1-dev librsvg2-dev libssl-dev \ + libsoup-3.0-dev libwebkit2gtk-4.1-dev libxdo-dev patchelf pkg-config wget ``` This is the same list CI installs (see `.github/workflows/ci.yml`), so matching @@ -118,8 +118,9 @@ under different package names — see the [Tauri prerequisites](https://tauri.app/start/prerequisites/) for the equivalents. -Without them, `just ci` fails partway through `just check` with a pkg-config -error such as: +`just dev` checks for these libraries up front and fails fast with the +install line above. Without the check, the missing libraries surface partway +through a cargo build as a pkg-config error such as: ``` The system library `gdk-pixbuf-2.0` required by crate `gdk-pixbuf-sys` was not found. diff --git a/Justfile b/Justfile index 64a1f36daf..5c9ab1c4ac 100644 --- a/Justfile +++ b/Justfile @@ -410,8 +410,14 @@ relay-release: _ensure-migrations cargo run -p buzz-relay --release +# Fail fast on Linux when the Tauri desktop app's native system libraries are +# missing (no-op on other platforms); see CONTRIBUTING.md "Linux: Tauri system +# libraries". Runs as the first dev prerequisite, before bootstrap builds. +_check-tauri-linux-deps: + ./scripts/check-tauri-linux-deps.sh + # Run the desktop Tauri app in dev mode with a local relay (ports and identity derived from worktree) -dev *ARGS: bootstrap _ensure-sidecar-stubs _ensure-migrations +dev *ARGS: _check-tauri-linux-deps bootstrap _ensure-sidecar-stubs _ensure-migrations #!/usr/bin/env bash set -euo pipefail export PATH="{{justfile_directory()}}/bin:$PATH" diff --git a/scripts/check-tauri-linux-deps.sh b/scripts/check-tauri-linux-deps.sh new file mode 100755 index 0000000000..6e2cdf09a9 --- /dev/null +++ b/scripts/check-tauri-linux-deps.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Fail fast on Linux when the Tauri desktop app's native system dependencies +# are missing, so `just dev` does not die deep inside cargo with a cryptic +# pkg-config error. No-op on other platforms. This script never installs +# anything and never calls sudo; it only reports what is missing. +set -euo pipefail + +case "${BUZZ_TEST_PLATFORM:-$(uname -s)}" in + Linux) ;; + *) exit 0 ;; +esac + +remediate() { + { + echo "Error: $1" + echo + echo "just dev builds the Tauri desktop app, which links against GTK and" + echo "WebKitGTK system libraries on Linux. Install them (Debian/Ubuntu):" + echo + echo " sudo apt-get install -y --no-install-recommends \\" + echo " build-essential curl file libasound2-dev libayatana-appindicator3-dev \\" + echo " libgtk-3-dev libjavascriptcoregtk-4.1-dev librsvg2-dev libssl-dev \\" + echo " libsoup-3.0-dev libwebkit2gtk-4.1-dev libxdo-dev patchelf pkg-config wget" + echo + echo "Other distributions ship these under different names — see" + echo "CONTRIBUTING.md, \"Linux: Tauri system libraries\", and" + echo "https://tauri.app/start/prerequisites/." + } >&2 + exit 1 +} + +if ! command -v pkg-config >/dev/null 2>&1; then + remediate "pkg-config is not installed, so the Tauri desktop build cannot locate its native system libraries." +fi + +# pkg-config modules that just dev needs, mapped from the -dev packages +# documented in CONTRIBUTING.md (the same list CI installs). +modules=( + alsa + gtk+-3.0 + libayatana-appindicator3-0.1 + librsvg-2.0 + libsoup-3.0 + openssl + webkit2gtk-4.1 + xdo +) + +missing=() +for module in "${modules[@]}"; do + if ! pkg-config --exists "$module"; then + missing+=("$module") + fi +done + +if ((${#missing[@]} > 0)); then + remediate "missing Tauri native dependencies (pkg-config modules): ${missing[*]}" +fi diff --git a/scripts/test-check-tauri-linux-deps.sh b/scripts/test-check-tauri-linux-deps.sh new file mode 100755 index 0000000000..c8cde478fc --- /dev/null +++ b/scripts/test-check-tauri-linux-deps.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# Contract test for scripts/check-tauri-linux-deps.sh. +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +guard="$repo_root/scripts/check-tauri-linux-deps.sh" +# The mocked pkg-config must be executable, but /tmp is mounted noexec on some +# environments, so default the scratch dir to the repository root and clean it +# up on exit. BUZZ_TEST_TMPDIR overrides the parent directory when needed. +tmp=$(mktemp -d "${BUZZ_TEST_TMPDIR:-$repo_root}/.check-tauri-linux-deps-test.XXXXXX") +trap 'rm -rf "$tmp"' EXIT + +# The guard runs under `#!/usr/bin/env bash`; give each case an isolated PATH +# containing only bash plus whatever stub the case installs, so the host's +# real pkg-config can never leak in. +mock_bin="$tmp/bin" +mkdir -p "$mock_bin" +ln -s "$(command -v bash)" "$mock_bin/bash" + +expect_fail() { + local name="$1" + shift + if out=$("$@" 2>&1); then + echo "expected $name to fail, but it exited 0" >&2 + exit 1 + fi + printf '%s' "$out" +} + +# 1. Non-Linux platforms are a no-op, even with no pkg-config in PATH: the +# guard must exit 0 and emit no output. Capture status and output +# explicitly so a crashing or noisy guard fails the test. +if out=$(BUZZ_TEST_PLATFORM=Darwin PATH="$mock_bin" "$guard" 2>&1); then + status=0 +else + status=$? +fi +if [[ "$status" -ne 0 ]]; then + echo "expected exit 0 on non-Linux, got exit $status" >&2 + exit 1 +fi +if [[ -n "$out" ]]; then + echo "expected no output on non-Linux, got:" >&2 + printf '%s\n' "$out" >&2 + exit 1 +fi + +# 2. Linux without pkg-config fails fast, names the missing command, and +# points at the documented apt install line. +out=$(expect_fail "missing pkg-config" env BUZZ_TEST_PLATFORM=Linux PATH="$mock_bin" "$guard") +grep -F "pkg-config" <<<"$out" >/dev/null +grep -F "apt-get install" <<<"$out" >/dev/null +grep -F "CONTRIBUTING.md" <<<"$out" >/dev/null + +# Check the package tokens inside the printed apt command, rather than merely +# accepting a mention elsewhere in the diagnostic (for example, the missing +# command error itself). +install_command=$(sed -n '/^[[:space:]]*sudo apt-get install /,/^[[:space:]]*$/p' <<<"$out") +if [[ -z "$install_command" ]]; then + echo "expected a non-empty apt install command in the remediation" >&2 + exit 1 +fi +for package in \ + pkg-config \ + libjavascriptcoregtk-4.1-dev \ + libsoup-3.0-dev \ + libwebkit2gtk-4.1-dev; do + if ! grep -F " $package" <<<"$install_command" >/dev/null; then + echo "expected apt install command to contain package '$package':" >&2 + printf '%s\n' "$install_command" >&2 + exit 1 + fi +done + +# 3. Linux with pkg-config but one missing module fails and names the module. +cat > "$mock_bin/pkg-config" <<'MOCK' +#!/usr/bin/env bash +for arg in "$@"; do + if [[ "$arg" == "webkit2gtk-4.1" ]]; then + exit 1 + fi +done +exit 0 +MOCK +chmod +x "$mock_bin/pkg-config" +out=$(expect_fail "missing webkit2gtk-4.1" env BUZZ_TEST_PLATFORM=Linux PATH="$mock_bin" "$guard") +grep -F "webkit2gtk-4.1" <<<"$out" >/dev/null +grep -F "apt-get install" <<<"$out" >/dev/null +grep -F "CONTRIBUTING.md" <<<"$out" >/dev/null +# A module that exists must not be reported as missing. +if grep -F "gtk+-3.0" <<<"$out" >/dev/null; then + echo "gtk+-3.0 was reported missing but the stub resolves it" >&2 + exit 1 +fi + +# 4. Multiple missing modules are all reported in one run. +cat > "$mock_bin/pkg-config" <<'MOCK' +#!/usr/bin/env bash +for arg in "$@"; do + case "$arg" in + webkit2gtk-4.1|libsoup-3.0) exit 1 ;; + esac +done +exit 0 +MOCK +out=$(expect_fail "missing two modules" env BUZZ_TEST_PLATFORM=Linux PATH="$mock_bin" "$guard") +grep -F "webkit2gtk-4.1" <<<"$out" >/dev/null +grep -F "libsoup-3.0" <<<"$out" >/dev/null + +# 5. Linux with all modules present passes silently. +cat > "$mock_bin/pkg-config" <<'MOCK' +#!/usr/bin/env bash +exit 0 +MOCK +BUZZ_TEST_PLATFORM=Linux PATH="$mock_bin" "$guard" + +echo "tauri linux deps guard test passed"