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
17 changes: 17 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,9 @@ jobs:
- name: Test updater channel isolation and release versions
run: python3 -m pytest scripts/tests/test_generate_latest_json.py scripts/tests/test_configure_tauri_release.py -q

- name: Test host-ABI gate
run: python3 -m pytest scripts/tests/test_host_abi_gate.py -q

build-qt:
name: Build Qt artifacts (${{ matrix.os }}${{ (matrix.research || (github.event_name == 'workflow_dispatch' && inputs.edition == 'research') || endsWith(github.ref_name, '-research')) && ', research' || '' }})${{ needs.nightly-submodules.outputs.latest == 'true' && ' [latest submodules]' || '' }}
needs: nightly-submodules
Expand Down Expand Up @@ -1258,6 +1261,12 @@ jobs:
run: |
echo "RELEASE=${{ startsWith(github.ref_name, 'v') || github.ref_name == 'master' }}" >> "$GITHUB_ENV"
echo "TAURI_BUILD=true" >> "$GITHUB_ENV"
# Don't bundle libwayland into the Tauri AppImage: a copy from the
# Ubuntu build image clashes with the host's newer Mesa (crashes or a
# blank webview on e.g. Arch/KDE Wayland; tauri-apps/tauri#15665,
# tauri-apps/tauri#15976). Read by linuxdeploy, which Tauri's bundler
# runs; honored from @tauri-apps/cli 2.12 (tauri-apps/tauri#16062).
echo "LINUXDEPLOY_EXCLUDED_LIBRARIES=libwayland-*" >> "$GITHUB_ENV"
Comment thread
ErikBjare marked this conversation as resolved.

- name: Set tag metadata
if: startsWith(github.ref, 'refs/tags/v')
Expand Down Expand Up @@ -1550,6 +1559,14 @@ jobs:
esac
done

- name: Host-ABI gate — verify the Tauri AppImage
if: runner.os == 'Linux'
# Only libwayland for now: the AppImage's bundled GTK 3 / WebKitGTK
# (Ubuntu 24.04) needs its glib, so glib stays bundled here.
env:
HOST_ABI_LIBS_LIST: libwayland-client libwayland-cursor libwayland-egl libwayland-server
run: bash scripts/package/host-abi-gate.sh dist/activitywatch-tauri*-linux-*.AppImage

- name: Package dmg
if: runner.os == 'macOS'
run: |
Expand Down
6 changes: 6 additions & 0 deletions scripts/package/host-abi-gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ HOST_ABI_LIBS=(
libglib-2.0
libgobject-2.0
)
# HOST_ABI_LIBS_LIST (space-separated) replaces the list, e.g. for a bundle that
# must keep glib (the Tauri AppImage's GTK/WebKitGTK needs the glib it was built
# against).
if [ -n "${HOST_ABI_LIBS_LIST:-}" ]; then
read -r -a HOST_ABI_LIBS <<< "$HOST_ABI_LIBS_LIST"
fi
Comment thread
ErikBjare marked this conversation as resolved.

if [ "$#" -eq 0 ]; then
echo "Usage: $0 <bundle-dir|bundle.zip|bundle.AppImage> [...]" >&2
Expand Down
63 changes: 63 additions & 0 deletions scripts/tests/test_host_abi_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Tests for scripts/package/host-abi-gate.sh on fake bundle trees."""

import os
import subprocess
from pathlib import Path
from typing import Optional

import pytest

GATE = Path(__file__).parents[1] / "package" / "host-abi-gate.sh"
WAYLAND_ONLY = "libwayland-client libwayland-cursor libwayland-egl libwayland-server"


def run_gate(bundle: Path, libs_list: Optional[str] = None) -> int:
env = {k: v for k, v in os.environ.items() if k != "HOST_ABI_LIBS_LIST"}
if libs_list is not None:
env["HOST_ABI_LIBS_LIST"] = libs_list
return subprocess.run(
["bash", str(GATE), str(bundle)], env=env, capture_output=True
).returncode


def make_bundle(tmp_path: Path, *libs: str) -> Path:
lib_dir = tmp_path / "bundle" / "usr" / "lib"
lib_dir.mkdir(parents=True)
(lib_dir / "libgtk-3.so.0").touch() # never flagged
for name in libs:
(lib_dir / name).touch()
return tmp_path / "bundle"


def test_clean_bundle_passes(tmp_path: Path):
assert run_gate(make_bundle(tmp_path)) == 0


@pytest.mark.parametrize("lib", ["libglib-2.0.so.0", "libgio-2.0.so.0", "libwayland-client.so.0"])
def test_default_list_rejects_host_abi_libs(tmp_path: Path, lib: str):
assert run_gate(make_bundle(tmp_path, lib)) == 1


def test_override_rejects_wayland(tmp_path: Path):
bundle = make_bundle(tmp_path, "libglib-2.0.so.0", "libwayland-egl.so.1")
assert run_gate(bundle, WAYLAND_ONLY) == 1


def test_override_replaces_rather_than_extends_default(tmp_path: Path):
# The Tauri AppImage keeps glib on purpose: with the wayland-only list,
# glib must pass, while the default list still rejects it.
bundle = make_bundle(tmp_path, "libglib-2.0.so.0", "libgobject-2.0.so.0")
assert run_gate(bundle, WAYLAND_ONLY) == 0
assert run_gate(bundle) == 1


def test_empty_override_keeps_default(tmp_path: Path):
assert run_gate(make_bundle(tmp_path, "libglib-2.0.so.0"), "") == 1


def test_symlinked_name_is_flagged(tmp_path: Path):
# The forbidden name exists only as a link to a harmless file, so this
# fails if the gate stops checking symlinks.
bundle = make_bundle(tmp_path)
(bundle / "usr" / "lib" / "libwayland-client.so.0").symlink_to("libgtk-3.so.0")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ P2 — The new test test_symlinked_name_is_flagged creates a symlink libwayland-client.so.0 pointing to libgtk-3.so.0. The gate script's scan_dir resolves symlinks and checks the target's basename. In this case, the target is libgtk-3.so.0, which is not in the wayland-only list, so the gate should NOT flag it. However, the test asserts run_gate(bundle, WAYLAND_ONLY) == 1, expecting the gate to fail. The test comment says 'The forbidden name exists only as a link to a harmless file, so this fails if the gate stops checking symlinks.' But the gate checks both the link name and the target name. The link name libwayland-client.so.0 matches the wayland list, so the gate flags it. The test is correct in that the gate flags the symlink by its own name, but the comment is misleading. The test does not actually verify that the target name is checked; it verifies that the link name is checked. This is a test-defect because the test's stated purpose (checking symlink target resolution) is not what it tests. The test would pass even if the gate only checked the link name and never resolved the target, so it does not pin the symlink-target behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive. test_symlinked_name_is_flagged and its comment explicitly test a forbidden link name pointing to a harmless file; neither claims to test target resolution. That is intentional: the loader can request the forbidden soname even when its target has a different name. Changing the assertion to success would weaken the gate, and pointing at a forbidden regular file would reintroduce the masking problem Greptile already identified and Erik fixed.

Verified on current head 0fadc0400067: all 8 gate tests pass. Replayed the exact fixture against an unchanged gate (exit 1) and a scratch mutation removing -type l from find (exit 0). The test therefore catches loss of symlink scanning exactly as its comment says. No code change needed.

assert run_gate(bundle, WAYLAND_ONLY) == 1
Loading