Skip to content

Add Snap packaging - #32

Merged
LiukDiihMieu merged 12 commits into
mainfrom
packaging/snap
Jun 26, 2026
Merged

Add Snap packaging#32
LiukDiihMieu merged 12 commits into
mainfrom
packaging/snap

Conversation

@LiukDiihMieu

@LiukDiihMieu LiukDiihMieu commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Adds Snap packaging for npyquick, alongside the existing PyPI / AppImage / Windows artifacts: a recipe, store metadata, a sandbox-aware error message, and a gated CI workflow that publishes to the Snap Store's edge channel.

What's here

Recipesnap/snapcraft.yaml

  • Strict confinement, base: core24, built with LXD (snapcraft pack --use-lxd locally, canonical/action-build in CI).
  • gnome extension for the Qt6 desktop runtime; home + removable-media plugs.
  • stage-packages for the xcb/xkb libs Qt's X11 plugin needs, so it runs on X11, not just Wayland.
  • Store metadata (title/summary/description/version) adopted from the AppStream metainfo already maintained for the AppImage (adopt-info + parse-info) — one source, shared with the AppImage.

Desktop integrationsnap/gui/

  • Desktop entry + icon via the native convention (Icon=${SNAP}/meta/gui/npyquick.png), so the launcher icon resolves.

Appsrc/npyquick/app.py

  • When confined as a Snap, opening a file the sandbox can't reach now shows a clear message (move it under home / use the pip or AppImage build / connect removable-media) instead of a raw "No such file". Gated on $SNAP, so the pip and AppImage builds are unaffected. Covered by tests.

CI.github/workflows/snap.yml

  • Separate from the wheel-derived release flow (the snap builds its own runtime and ships to a different store). On workflow_dispatch or a v* tag it builds + smoke-tests, then publishes to the edge channel behind a manually-approved snap-store environment. Promotion edge → stable stays manual.

Docssnap/README.md, a README install section + Snap Store badge, and a Snap section in docs/RELEASE_CHECKLIST.md.

Store

The name npyquick is registered and the snap is live on the edge channel (https://snapcraft.io/npyquick). Listing text/icon are pushed with snapcraft upload-metadata; links and screenshots are set in the web dashboard.

Verified (real installs)

  • Builds via LXD; installs with --dangerous and from the store (--edge).
  • .npy (image / table / cross-section / histogram) and .npz (key picker) views work.
  • Menu / desktop icon shows correctly.
  • Runs on both X11 and Wayland, and a core24 snap installs and runs on a 22.04 host.
  • Sandbox boundary holds; out-of-reach files show the new hint.

Known limitation

Double-click file association doesn't work: .npy/.npz have no MIME type registered on the host, and snapd doesn't register a snap's custom MIME types (not a confinement issue — classic has the same gap). Open via the menu icon, then drag-and-drop or File > Open. Upstreaming a .npy type to shared-mime-info would be the real fix (separate, long-horizon).

Before merge

  • Create the snap-store environment + SNAPCRAFT_STORE_CREDENTIALS secret, and dispatch the workflow once to verify end-to-end.
  • Promote a revision to stable (the README badge and snap install npyquick point there).

🤖 Generated with Claude Code

LiukDiihMieu and others added 3 commits June 23, 2026 09:20
Strict-confinement Snap built with `snapcraft pack --use-lxd`. Uses the
gnome extension for the Qt6 desktop runtime and reuses the .desktop entry
and icon already produced for the AppImage. Plugs home and removable-media
cover opening .npy/.npz files the user picks.

Manual build only for now; auto-publish (Store name, CI upload, version
single-sourcing via adopt-info) is a follow-up.

Known limitation: double-click file association does not work — snapd does
not register a snap's custom MIME types (application/x-npy/-npz) with the
host, so the launcher cannot route .npy files in. Launch via the menu icon
and use File > Open instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The launcher could not resolve the bundled icon because the reused AppImage
desktop entry references a themed icon name, which snapd does not map to an
in-snap icon. Switch to the native snap/gui/ convention: a snap-specific
npyquick.desktop with Icon=${SNAP}/meta/gui/npyquick.png (an absolute in-snap
path) and the icon copied to snap/gui/npyquick.png. snapd auto-detects these
from meta/gui, so the apps.desktop key and the dump part are no longer needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread snap/snapcraft.yaml Outdated
# ${SNAP}/meta/gui/npyquick.png — an absolute in-snap path, which is what
# the launcher needs to resolve a bundled icon.
plugs:
# npyquick opens .npy files the user picks from anywhere on disk.

@LiukDiihMieu LiukDiihMieu Jun 23, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

comment not correct.

Suggested change
# npyquick opens .npy files the user picks from anywhere on disk.
# Allow opening user data from the home directory.
# Files under /mnt, /media, or /run/media require the removable-media
# interface to be connected manually.

Comment thread snap/snapcraft.yaml Outdated
Comment on lines +35 to +36
# Qt6's xcb platform plugin hard-requires libxcb-cursor; it's a frequent
# "works in dev, blank window in the snap" gotcha. Revisit after a build.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Suggested change
# Qt6's xcb platform plugin hard-requires libxcb-cursor; it's a frequent
# "works in dev, blank window in the snap" gotcha. Revisit after a build.
# The gnome extension provides common desktop integration pieces
# such as fonts, themes, icons, Wayland/X11, and related runtime setup.
# PySide6/Qt itself still comes from the Python dependencies.

Address review feedback: the home plug comment claimed files could be opened
from "anywhere on disk", which is wrong under strict confinement — clarify it
covers the home directory and that /mnt, /media, /run/media need the
removable-media interface connected. Also reword the gnome extension comment:
it provides desktop integration pieces (fonts, themes, icons, Wayland/X11),
while PySide6/Qt itself comes from the Python dependencies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LiukDiihMieu

LiukDiihMieu commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

Uploaded to snapcraft and installed on another machine. npyquick command returns the following error.

❯ npyquick
qt.qpa.plugin: From 6.5.0, xcb-cursor0 or libxcb-cursor0 is needed to load the Qt xcb platform plugin.
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: eglfs, linuxfb, minimal, minimalegl, offscreen, vkkhrdisplay, vnc, wayland-brcm, wayland-egl, wayland, xcb.

Aborted (core dumped)

However, we already have libxcb-cursor0 in our snapcraft.yaml.


Debug:

snap run --shell npyquick

and then in that shell

$ QXCB="$(find "$SNAP" -name libqxcb.so | head -n 1)"
echo "$QXCB"
ldd "$QXCB" | grep "not found"
/snap/npyquick/1/lib/python3.10/site-packages/PySide6/Qt/plugins/platforms/libqxcb.so
	libxkbcommon-x11.so.0 => not found
	libxcb-icccm.so.4 => not found
	libxcb-keysyms.so.1 => not found
	libxcb-shape.so.0 => not found
	libxcb-xkb.so.1 => not found
	libxcb-icccm.so.4 => not found
	libxcb-keysyms.so.1 => not found
	libxcb-shape.so.0 => not found
	libxcb-xkb.so.1 => not found
	libxkbcommon-x11.so.0 => not found

LiukDiihMieu and others added 8 commits June 23, 2026 19:45
The snap ran on Wayland but crashed on X11 with "Could not load the Qt
platform plugin xcb". Qt's message blames libxcb-cursor0, but that lib was
already present and resolvable; `ldd` on libqxcb.so inside the snap showed the
real gaps were libxkbcommon-x11, libxcb-icccm, libxcb-keysyms, libxcb-shape
and libxcb-xkb — needed by libQt6XcbQpa and not supplied by the gnome
platform. Add the corresponding packages to stage-packages. Verified on an
X11 host: the window opens and ldd reports no missing libraries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the hand-written version/summary/description from snapcraft.yaml and
adopt them via adopt-info + parse-info from the AppStream metainfo already
maintained for the AppImage, so the two stay in one place. A new `metadata`
part ships that file at usr/share/metainfo/ inside the snap. Keep the explicit
icon (AppStream has no <icon>) and the contact/issues/source-code links
(AppStream URLs are not adopted). Recompress the store/launcher icon from
~1 MB to ~13 KB to stay well under the store's 256 KB limit.

Store listing note: `snapcraft upload` only bumps the revision; the public
page's summary/description/icon are updated with `snapcraft upload-metadata
<snap> --force`, while links and screenshots are dashboard-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When running confined as a Snap, opening a file outside the sandbox's reach
(strict confinement only grants the home folder and removable media) failed
with a bare "No such file or directory", which is confusing — the file plainly
exists and the pip/AppImage build opens it fine. The common trigger is a
symlink in home pointing to another mount (e.g. ~/Lab -> /data on another
drive): confinement resolves the symlink to its real path, which the home
interface doesn't cover.

load_file now detects this case ($SNAP set + the resolved path outside
$SNAP_REAL_HOME and the removable-media roots) and shows a short explanation:
move the file under home / use the pip or AppImage build, or — for external
drives — connect the removable-media interface. The hint is gated on $SNAP so
the pip and AppImage builds are unaffected. Covered by tests in
test_app_workflow.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hint previously fired for any failure on a path that resolved outside
home/removable-media, which could mask the real error of a reachable file. Only
return the hint when the path is genuinely unreachable from inside the Snap
(os.path.exists is false); a visible-but-failing file keeps its real error.

Also refine the wording: add sudo to the removable-media connect command
(a normal user needs it), and say the package "can only access files in your
home folder and connected removable-media locations" so it doesn't imply
removable media is reachable without connecting the interface.

Add tests for a home symlink that resolves onto another drive and for a
visible external path not overriding the real error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve SNAP_REAL_HOME with realpath before the prefix check so a home
directory that is itself a symlink (or on another mount) still matches.
Shorten the out-of-reach message. Parametrize the removable-media test over
the /media, /run/media and /mnt roots.

Verified on a real Snap install: a file on another drive shows the hint, a
corrupt file under home keeps its real error, home files open normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Linux (Snap) install section after the AppImage one, a Snap Store
version badge, and note the two ways the Snap differs from the AppImage/pip
builds: sandboxed file access (home + connected removable media) and no
double-click file association. Also picks up the AppImage filename fix
(npyquick-<version>-x86_64.AppImage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A manual Linux Snap section (built on an Ubuntu 22.04 host with LXD, not the
tag CI): build, dangerous-install smoke test across views, menu icon, X11 and
Wayland sessions, sandbox-hint check, edge upload, promotion to stable, and
store metadata.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A separate snap.yml (workflow_dispatch + v* tags) builds the snap, smoke-tests
it, and uploads to the Snap Store edge channel behind a manually-approved
snap-store environment. Promotion edge -> stable stays manual. Also switches
the snap base to core24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LiukDiihMieu LiukDiihMieu changed the title Add Snap packaging (manual build) Add Snap packaging Jun 26, 2026
@LiukDiihMieu
LiukDiihMieu merged commit cda816c into main Jun 26, 2026
8 checks passed
@LiukDiihMieu
LiukDiihMieu deleted the packaging/snap branch June 26, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant